Android-ObservableScrollViewでパララックスなスクロールを実装する
Android-ObservableScrollViewはScrollViewやListViewなどのスクロール可能なウィジェットのスクロール状態を取得して、スクロールに関するイベントをハンドリングできるようにするためのライブラリです。
このAndroid-ObservableScrollViewを使用することで、Google Play Storeのような、スクロール量に応じてToolbarの表示/非表示や各アプリ詳細画面のヘッダイメージの表示/非表示の切り替えを簡単に実装することができます。今回はプレイストアのアプリ詳細画面のような、パララックスなスクロールを実装してみます。
このライブラリはサンプルが充実しており、パララックスの実装もgithub上で詳しい解説があります。ただし若干内容が古い箇所もあるため、サンプルを参考に一部変更しています。
公式のサンプルは以下を参照してください。ちなみに作者の方がQiitaで記事を公開されていますが、そちらも内容が充実しているので一度目を通しておくのがよいと思います。
Google Playストアアプリのようにスクロール時にActionBarやToolbarの表示を切り替える - Qiita
導入
現在の最新バージョンは1.6.0のようなので、例のごとく以下の記述をbuild.gradle
のdependencies
に追加します。
compile 'com.github.ksoichiro:android-observablescrollview:1.6.0'
レイアウト定義
基本的なレイアウト構成は以下のとおりです。以下は公式サンプルからの引用です*1。
<FrameLayout> <ObservableScrollView> <RelativeLayout> <ImageView/> <View/> <TextView/> </RelativeLayout> </ObservableScrollView> <Toolbar/> </FrameLayout>
ルート要素のFrameLayout
の下にObservableScrollView
とToolbar
を配置する構成です。ObservableScrollView
以下には1つのウィジェットしか置けないため、メインコンテンツとなるImageView
とTextView
(TextViewじゃなくても任意の要素で良い)を配置するためにRelativeLayout
でラップします。
ImageView
とTextView
の間にあるView
が今回のポイントで、このView
をアンカーとすることで画像とテキスト部分のスクロール量を独立してセットできるようになります。
スクロール部分を抜粋すると以下のようになります。
<com.github.ksoichiro.android.observablescrollview.ObservableScrollView xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/scrollView" android:layout_width="match_parent" android:layout_height="match_parent" android:fillViewport="true"> <RelativeLayout android:layout_width="match_parent" android:layout_height="wrap_content"> <ImageView android:id="@+id/image_header" android:layout_width="match_parent" android:layout_height="@dimen/parallax_image_height" android:scaleType="centerCrop" android:src="@drawable/sample" /> <View android:id="@+id/anchor" android:layout_width="match_parent" android:layout_height="@dimen/parallax_image_height" android:minHeight="@dimen/parallax_image_height" /> <TextView android:id="@+id/tv_main" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_below="@id/anchor" android:background="@android:color/white" android:paddingBottom="@dimen/activity_vertical_margin" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" android:text="@string/sample_text" /> </RelativeLayout> </com.github.ksoichiro.android.observablescrollview.ObservableScrollView> </FrameLayout>
ポイントはImageView
とView
の高さを同じにすることと、TextView
にlayout_below=@id/anchor
を指定することです。これにより、テキストのスクロールが画像ではなく画像と全く同じサイズの透明なView
に追従するようになります。
後ほど説明しますが、実際にスクロールが発生した際に画像部分のスクロール量を小さくすることで、テキスト部分が画像に覆いかぶさる様にスクロールし、パララックス効果を生み出します。
Toolbar
部分の定義は以下のようにします。公式サンプル作成時はビューの重なりを表現するためのelevation
が使えなかったらしいためグラデーション用のビューが追加されていたようですが、現在は問題なく使用できるため直接指定しています。
<android.support.v7.widget.Toolbar android:id="@+id/toolbar" android:layout_width="match_parent" android:layout_height="wrap_content" android:background="?attr/colorPrimary" android:elevation="8dp" android:minHeight="?attr/actionBarSize" app:titleTextColor="@android:color/white" />
パララックスの実装
Activity
側で必要なのはObservableScrollViewCallbacks
を実装して、スクロール状態の変化時に画像部分のスクロール量を小さくする処理と、スクロール量に合わせてToolbar
のalpha値を変更する処理です。
実際のActivity
の実装は以下のようになります。
public class MainActivity extends AppCompatActivity implements ObservableScrollViewCallbacks { @InjectView(R.id.image_header) ImageView mHeaderImage; @InjectView(R.id.toolbar) Toolbar mToolbar; @InjectView(R.id.scrollView) ObservableScrollView mScrollView; private int mParallaxHeight; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ButterKnife.inject(this); setSupportActionBar(mToolbar); mScrollView.setScrollViewCallbacks(this); // Toolbarの背景色をalpha=0でセットする mToolbar.setBackgroundColor( ScrollUtils.getColorWithAlpha(0, getResources().getColor(R.color.colorPrimary))); // 画像領域の高さを取得 mParallaxHeight = getResources().getDimensionPixelSize(R.dimen.parallax_image_height); } @Override public void onScrollChanged(int scrollY, boolean firstScroll, boolean dragging) { int baseColor = getResources().getColor(R.color.colorPrimary); // 現在のスクロール量からalpha値を計算する(0~1.0) float alpha = Math.min(1, (float) scrollY / mParallaxHeight); mToolbar.setBackgroundColor(ScrollUtils.getColorWithAlpha(alpha, baseColor)); mHeaderImage.setTranslationY(scrollY / 2); } @Override public void onDownMotionEvent() { } @Override public void onUpOrCancelMotionEvent(ScrollState scrollState) { } @Override protected void onRestoreInstanceState(Bundle savedInstanceState) { super.onRestoreInstanceState(savedInstanceState); onScrollChanged(mScrollView.getCurrentScrollY(), false, false); } }
全くスクロールしていない状態ではToolbarの背景は透明で、下方向にスクロールすることで、画像の移動とともに徐々にToolbarの背景色が表示されるようになっています。
onScrollChanged
はスクロールイベントが発生するごとに実行されます。ここでToolbarの背景色のalpha値を0~1.0の間で計算してセットすることで、スクロール位置に応じたToolbarの表示/非表示を実現しています。パララックス効果は単純に同メソッド内でスクロール量の半分だけ画像部分を動かす(scrollY / 2
)ことで実現しています。
Toolbarの表示切替と画像のスクロール量の変更を行うことで、Play Store風のパララックススクロールが比較的簡単に実装できました。この状態でアプリを起動すると以下のようになります。これが起動直後。
下方向にスクロールすると、画像部分が少しずつスクロールされていき、それに応じて徐々にToolbarが表示されるのが分かります。
サンプルコード
今回のサンプルプロジェクトは以下で公開しています。細かい部分は実際のサンプルをダウンロードして確認してください。
なおToolbarを使用する場合はActionBarを使用しないテーマになっていないとToolbarが2つ表示されるような状態になるため注意してください*2。
*1:引用元でも触れられているが、ルート要素は別にFrameLayoutではなくRelativeLayoutでももちろんよい
*2:ちなみにToolbarとActionBarの違いについてはこのページが分かりやすかったです: MaterialDesignことはじめ ActionBar編 - Qiita