指に追従するアニメーション

「Lollipop のステータスバーを引っ張り出した時に、ステータスバーのアイコンが動いたり、設定アイコンがくるくるってなるけど、どうやってるの?」

心の声「知らんがな(´・ω・`)ggry」

ggっても出てこなかった。

確かに、どうやっているんだろう。

調査

エミュレータを起動して、Hierarchy View を見てみると、ステータスバーの当該の部品は StatusBarHeaderView というビューらしい。そこからソースを追ってみた。


ざっくり仕組みを説明すると、

  • QuickSettingsパネルの高さに連動して、変化量を計算して設定
  • onLayoutを契機として、変化量からそれぞれの部品のアニメーションさせる値を計算
  • それぞれの部品にsetX()やsetTranslationX()やsetRotation()等を設定して部品を再描画

変化量を計算して設定

呼び出し元では、QuickSettingsパネルの高さから、変化量(t: transform値)を計算している。

http://tools.oesf.biz/android-5.1.1_r1.0/xref/frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java#1107

   1107     private void setQsExpansion(float height) {
...
   1120         mHeader.setExpansion(getHeaderExpansionFraction());
   1158     private float getHeaderExpansionFraction() {
   1159         if (!mKeyguardShowing) {
   1160             return getQsExpansionFraction();
    635     private float getQsExpansionFraction() {
    636         return Math.min(1f, (mQsExpansionHeight - mQsMinExpansionHeight)
    637                 / (getTempQsMaxExpansion() - mQsMinExpansionHeight));

変化量は、0.0, 0.1, 0.2, 0.3, .., 1.0 のようになる。


計算方法

変化量=((現在の位置)-(移動前の位置))/((移動後の位置)-(移動前の位置))
ただし、変化量<=1.0

計算して求めた変化量をStatusBarHeaderViewに設定する。

http://tools.oesf.biz/android-5.1.1_r1.0/xref/frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeaderView.java#454

    454     public void setExpansion(float t) {
    455         if (!mExpanded) {
    456             t = 0f;
    457         }
    458         mCurrentT = t;

onLayout契機に変化量からアニメーションさせる値を計算

位置が変化するたびにonLayoutが通知される。

onLayoutを契機として、それぞれの部品をアニメーションさせる値を計算する。

http://tools.oesf.biz/android-5.1.1_r1.0/xref/frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeaderView.java#183

    183     @Override
    184     protected void onLayout(boolean changed, int l, int t, int r, int b) {
    185         super.onLayout(changed, l, t, r, b);
    186         if (mCaptureValues) {
    187             if (mExpanded) {
    188                 captureLayoutValues(mExpandedValues);
    189             } else {
    190                 captureLayoutValues(mCollapsedValues);
    191             }
    192             mCaptureValues = false;
    193             updateLayoutValues(mCurrentT);

updateLayoutParamsの引数 t: Transform値は、0.0, 0.1, 0.2, 0.3, .., 1.0 のように変化していく。

http://tools.oesf.biz/android-5.1.1_r1.0/xref/frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeaderView.java#470

    470     private void updateLayoutValues(float t) {
    471         if (mCaptureValues) {
    472             return;
    473         }
    474         mCurrentValues.interpoloate(mCollapsedValues, mExpandedValues, t);
    475         applyLayoutValues(mCurrentValues);

スケールや位置や回転を設定する。

http://tools.oesf.biz/android-5.1.1_r1.0/xref/frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeaderView.java#591

    591     private void applyLayoutValues(LayoutValues values) {
//時計のサイズ(スケール)を変更
    592         mTime.setScaleX(values.timeScale);
    593         mTime.setScaleY(values.timeScale);
//時計の位置を変更
    594         mClock.setY(values.clockY - mClock.getHeight());
...
//設定アイコンの位置を変更しつつ回転
    622         mSettingsButton.setTranslationY(mSystemIconsSuperContainer.getTranslationY());
    623         mSettingsButton.setTranslationX(values.settingsTranslation);
    624         mSettingsButton.setRotation(values.settingsRotation);

参考

「onMeasureとonLayoutについて理解する」
onLayoutについてわかりやすく解説されています。
件のステータスバーのアニメーションでは、onLayoutを契機としています。

関連記事

ViewPager のアニメーションについてまとめた記事。
Transform値の考え方は、ここと共通します。