Android
MaterialDesign

Google Play ストアのトップページのUIを考察してみた

はじめに

Qiita初投稿です。ここ1年はAndroidをメインでやってます。
マテリアルデザインを勉強していて、PlayストアのUIがいい感じだったので、構成を考察してみました。
(個人的な考察なので、実際のところはわかりません。)

  • Playストアのバージョン 9.4.18

PlayStore.png

(2018/04/17 更新)

  • GitHubにサンプルアプリのソースをあげました
  • こんな感じに動きます

material_ui_sample.gif

前提条件

  • Android Studio 3.1.0
  • targetSdkVersion 27
  • minSdkVersion 21
  • compileSdkVersion 27

レイアウト構成

DrawerLayout + NavigationView

  • 基本的なナビゲーションドロワーの構成

CoordinatorLayout + AppBarLayout

  • この構成により、コンテンツ部分のスクロールと連動してToolbarが隠れるようになっています

TabLayout + ViewPager

  • メインのコンテンツ部分は基本的なタブの構成
  • ポイントはタブ選択時のアニメーション
    • Revealと呼ばれるもので、詳細は後述します
    • カテゴリごとにヘッダー部分の色が変化します

SearchView + AnimatedVectorDrawable

  • 検索バーの部分
  • ポイントは検索バーにフォーカスが当たるとメニューアイコンがアニメーションするところ
  • AnimatedVectorDrawableを使えば簡単にできそう

icon.gif

実装方法について

ヘッダー部分のReveal

概要

  • タブ選択時にAppBarLayoutの背景色がRevealというアニメーションで切り替わるようになっています
  • Revealはユーザーがタップした時に波紋状に広がるアニメーションのことです

考察

  • ステータスバーの部分も綺麗にアニメーションしていたので、ステータスバーを透過させてるのかと思ったのですが、コンテンツをスクロールするとToolbarがステータスバーの下に隠れてるので、透過ではなさそう。。。
  • AppBarLayoutとステータスバーの部分で2つアニメーション用のViewがあればできそう

実装方法

  • AppBarLayoutとステータスバーは透過させて、それぞれのアニメーション用のViewと背景用のViewを用意すればできます
  • アニメーションの起点はタブにCustomViewを設定して、そこから計算できそう
  • イケてないかもしれませんが、こんなレイアウト構成なら実装できました
<DrawerLayout>
  <CoordinatorLayout>
    <FrameLayout>
      <View/> <!-- AppBarLayoutのアニメーション時の背景用 -->
      <View/> <!-- AppBarLayoutのアニメーション用 -->
    </FrameLayout>
    <AppBarLayout>
      <Toolbar/>
      <TabLayout/>
    </AppBarLayout>
    <FrameLayout>
      <View/> <!-- ステータスバーのアニメーション時の背景用 -->
      <View/> <!-- ステータスバーのアニメーション用 -->
    </FrameLayout>
    <ViewPager/>
  </CoordinatorLayout>
  <NavigationView/>
</DrawerLayout>
  • アニメーションの実装はViewAnimationUtils.createCircularReveal()を使えば簡単にできます。
  • 複数アニメーションはAnimatorSetでまとめると良いです
private View reveal;          // Reveal用のView(AppBarLayout)
private View statusReveal;    // Reveal用のView(ステータスバー)
private View bgReveal;        // アニメーション中の背景用(AppBarLayout)
private View bgStatusReveal;  // アニメーション中の背景用(ステータスバー)

/**
 * Revealの開始
 * @param position 選択したタブのposition
 * @param centerX アニメーション起点のX座標
 * @param centerY アニメーション起点のY座標
 */
private void startReveal(int position, int centerX, int centerY) {
  // タブのpositionから色を取得
  final @ColorRes int colorResId = getTabColorAtPosition(position);
  final float radius = Math.max(reveal.getWidth(), reveal.getHeight()) * 1.2f;

  reveal.setBackgroundResource(colorResId);
  statusReveal.setBackgroundResource(colorResId);

  AnimatorSet animatorSet = new AnimatorSet();
  animatorSet.setDuration(500);
  animatorSet.playTogether(
    ViewAnimationUtils.createCircularReveal(reveal, centerX, centerY, 0, radius),
    ViewAnimationUtils.createCircularReveal(statusReveal, centerX, centerY, 0, radius));
  animatorSet.addListener(new AnimatorListenerAdapter() {
    @Override
    public void onAnimationEnd(Animator animation) {
      super.onAnimationEnd(animation);
      bgReveal.setBackgroundResource(colorResId);
      bgStatusReveal.setBackgroundResource(colorResId);
    }
  });
  animatorSet.start();
}

コンテンツスクロール時にToolbarを隠す

概要

  • スクロールと連動して、Toolbarなどを隠したり、小さくしたりできます

考察

  • Toolbarだけならlayout_scrollFlagsにオプションをセットすればできます
  • Reveal用に独自のViewを用意したので、それら用にBehaviorを実装する必要があります

実装方法

  • CoordinatorLayout.Behaviorを使えばViewに対しての振る舞いを実装できます
  • Behaviorは以下のようなシンプルな実装で十分です
public class HeaderBehavior extends CoordinatorLayout.Behavior<View> {
  private int defaultDependencyTop = -1;

  public HeaderBehavior(Context context, AttributeSet attrs) {
    super(context, attrs);
  }

  @Override
  public boolean layoutDependsOn(CoordinatorLayout parent, View view, View dependency) {
    return dependency instanceof AppBarLayout;
  }

  @Override
  public boolean onDependentViewChanged(CoordinatorLayout parent, View view, View dependency) {
    if (defaultDependencyTop == -1) {
      defaultDependencyTop = dependency.getTop();
    }
    view.setTranslationY(dependency.getTop() - defaultDependencyTop);
    return true;
  }
}
  • レイアウトのxmlで以下のように設定します
<CoordinatorLayout>
  <FrameLayout app:layout_behavior="com.XXX.XXX.HeaderBehavior" />
  <Toolbar app:layout_scrollFlags="scroll|enterAlways" />
  <TabLayout/>
  <FrameLayout/>
</CoordinatorLayout>

検索バーのアイコン

概要

  • 検索バーをタップした時にアイコンがアニメーションして別のアイコンに切り替わります
  • 2つの機能をトグルのようにアニメーションで切り替えるのが主流のようです
  • Creative customization - Iconsに記載されています

考察

  • AnimatedVectorDrawableのアイコンさえ用意すれば、簡単に実装可能でした
  • アニメーションアイコンの作成にはAndroid Icon Animatorを使うと良さそうです

実装方法

  • アイコンがあれば、アイコンタップ時にアイコン切り替えとアニメーションの開始を呼ぶだけでできます
private boolean menuFlag = false;
private Toolbar toolbar;
private AnimatedVectorDrawable menuDrawable;
private AnimatedVectorDrawable arrowDrawable;

@Override
protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);

  toolbar = (Toolbar) findViewById(R.id.toolbar);
  menuDrawable = (AnimatedVectorDrawable) getDrawable(R.drawable.ic_menu_24dp);
  arrowDrawable = (AnimatedVectorDrawable) getDrawable(R.drawable.ic_arrow_24dp);

  toolbar.setNavigationOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
      if (menuFlag) {
        toolbar.setNavigationIcon(arrowDrawable);
        arrowDrawable.start();
      } else {
        toolbar.setNavigationIcon(menuDrawable);
        menuDrawable.start();
      }
      menuFlag = !menuFlag;
    }
  });
}

最後に

ちょっと長くなってしまいましたが、分かりにくいところや、ご指摘あればコメントいただけると幸いです。
今はAndroidですが、色々興味はあるので、いろんなジャンルで投稿できたらと思ってます。