LoginSignup
20
14

More than 5 years have passed since last update.

Twitter が Android で「いいね」アニメーションをどのように実現しているのか調査した件(AnimatedStateListDrawable)

Last updated at Posted at 2017-10-07

概要

Twitter が「いいね」ボタンのアニメーションをどのように実装しているのか調査してみました :mag:

調べてみた結果 :computer:

GitHub で公開されている Twitter Kit for Android のコミット「Add like heart animations for API 21+(2015 年 11 月 9 日)」によると、AnimatedStateListDrawable を用いているようでした。

AnimatedStateListDrawable とは

API レベル 21 (Lollipop) から追加された機能で、画像をパラパラ漫画の要領でアニメーションにします。

parapara_manga.png

Twitter の「いいね」ボタンは res/drawable-xhdpi 内にある 60 枚の PNG 画像を使用していました。

Android 4.4 (KitKat) 以下の対応はどうしているのか

Twitterres/drawableres/drawable-v21 パッケージ直下に同名の XML ファイルを作成して、中身を <selector > タグと <animated-selector > タグで書き分ける事により、Android 4.4 以下でアニメーション非対応とし、Android 5.0 以上でアニメーションを有効にしていました。

ボタンの ON/OFF

ボタン ON 時のアニメーションが実施されている途中にボタンをタップすると、アニメーションがキャンセルされ OFF 状態に切り替わりました。

サンプル

Twitter Kit for AndroidImageButton を拡張したクラスとコミット「Add like heart animations for API 21+」を参考にアニメーション付きボタンを作ってみます。

リソース

39 枚の PNG 画像を用いてボタン ON 時のアニメーションを作成します。

スクリーンショット 2017-10-08 3.02.17.png

コード

res/values/styles.xml
<resources>
    <declare-styleable name="AnimationImageButton">
        <attr name="state_toggled_on" format="boolean" />
    </declare-styleable>
</resources>
res/drawable-v21/star_animation.xml
<animated-selector xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <item
        android:id="@+id/animation_on"
        app:state_toggled_on="true">
        <bitmap android:src="@drawable/star_animation_01" />
    </item>

    <item android:id="@+id/animation_off">
        <bitmap android:src="@drawable/off" />
    </item>

    <transition
        android:fromId="@+id/animation_off"
        android:toId="@+id/animation_on">
        <animation-list>
            <item android:duration="30">
                <bitmap android:src="@drawable/star_animation_01" />
            </item>
            <item android:duration="30">
                <bitmap android:src="@drawable/star_animation_02" />
            </item>
            <item android:duration="30">
                <bitmap android:src="@drawable/star_animation_03" />
            </item>
            <item android:duration="30">
                <bitmap android:src="@drawable/star_animation_04" />
            </item>
            <item android:duration="30">
                <bitmap android:src="@drawable/star_animation_05" />
            </item>
            <item android:duration="30">
                <bitmap android:src="@drawable/star_animation_06" />
            </item>
            <item android:duration="30">
                <bitmap android:src="@drawable/star_animation_07" />
            </item>
            <item android:duration="30">
                <bitmap android:src="@drawable/star_animation_08" />
            </item>
            <item android:duration="30">
                <bitmap android:src="@drawable/star_animation_09" />
            </item>
            <item android:duration="30">
                <bitmap android:src="@drawable/star_animation_10" />
            </item>
            <item android:duration="30">
                <bitmap android:src="@drawable/star_animation_11" />
            </item>
            <item android:duration="30">
                <bitmap android:src="@drawable/star_animation_12" />
            </item>
            <item android:duration="30">
                <bitmap android:src="@drawable/star_animation_13" />
            </item>
            <item android:duration="30">
                <bitmap android:src="@drawable/star_animation_14" />
            </item>
            <item android:duration="30">
                <bitmap android:src="@drawable/star_animation_15" />
            </item>
            <item android:duration="30">
                <bitmap android:src="@drawable/star_animation_16" />
            </item>
            <item android:duration="30">
                <bitmap android:src="@drawable/star_animation_17" />
            </item>
            <item android:duration="30">
                <bitmap android:src="@drawable/star_animation_18" />
            </item>
            <item android:duration="30">
                <bitmap android:src="@drawable/star_animation_19" />
            </item>
            <item android:duration="30">
                <bitmap android:src="@drawable/star_animation_20" />
            </item>
            <item android:duration="30">
                <bitmap android:src="@drawable/star_animation_21" />
            </item>
            <item android:duration="30">
                <bitmap android:src="@drawable/star_animation_22" />
            </item>
            <item android:duration="30">
                <bitmap android:src="@drawable/star_animation_23" />
            </item>
            <item android:duration="30">
                <bitmap android:src="@drawable/star_animation_24" />
            </item>
            <item android:duration="30">
                <bitmap android:src="@drawable/star_animation_25" />
            </item>
            <item android:duration="30">
                <bitmap android:src="@drawable/star_animation_26" />
            </item>
            <item android:duration="30">
                <bitmap android:src="@drawable/star_animation_27" />
            </item>
            <item android:duration="30">
                <bitmap android:src="@drawable/star_animation_28" />
            </item>
            <item android:duration="30">
                <bitmap android:src="@drawable/star_animation_29" />
            </item>
            <item android:duration="30">
                <bitmap android:src="@drawable/star_animation_30" />
            </item>
            <item android:duration="30">
                <bitmap android:src="@drawable/star_animation_31" />
            </item>
            <item android:duration="30">
                <bitmap android:src="@drawable/star_animation_32" />
            </item>
            <item android:duration="30">
                <bitmap android:src="@drawable/star_animation_33" />
            </item>
            <item android:duration="30">
                <bitmap android:src="@drawable/star_animation_34" />
            </item>
            <item android:duration="30">
                <bitmap android:src="@drawable/star_animation_35" />
            </item>
            <item android:duration="30">
                <bitmap android:src="@drawable/star_animation_36" />
            </item>
            <item android:duration="30">
                <bitmap android:src="@drawable/star_animation_37" />
            </item>
            <item android:duration="30">
                <bitmap android:src="@drawable/star_animation_38" />
            </item>
            <item android:duration="30">
                <bitmap android:src="@drawable/star_animation_39" />
            </item>
        </animation-list>
    </transition>
</animated-selector>
res/drawable/star_animation.xml
<selector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto">
    <item android:drawable="@drawable/star_animation_01" app:state_toggled_on="true" />
    <item android:drawable="@drawable/off" android:state_enabled="false" />
</selector>
AnimationImageButton.java
package com.sample.application;

import android.content.Context;
import android.support.v7.widget.AppCompatImageButton;
import android.util.AttributeSet;

/**
 * アニメーション付き {@link AppCompatImageButton}
 */
public class AnimationImageButton extends AppCompatImageButton {

    private static final int[] STATE_TOGGLED_ON;
    boolean isToggledOn;

    public AnimationImageButton(Context context) {
        this(context, null);
    }

    public AnimationImageButton(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public AnimationImageButton(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        setImageResource(R.drawable.star_animation);
        setToggledOn(false);
    }

    public int[] onCreateDrawableState(int extraSpace) {
        int[] drawableState = super.onCreateDrawableState(extraSpace + 2);
        if (isToggledOn) {
            mergeDrawableStates(drawableState, STATE_TOGGLED_ON);
        }

        return drawableState;
    }

    public boolean performClick() {
        toggle();
        return super.performClick();
    }

    public void setToggledOn(boolean isToggledOn) {
        this.isToggledOn = isToggledOn;
        setContentDescription(isToggledOn ? "ON" : "OFF");
        refreshDrawableState();
    }

    public void toggle() {
        this.setToggledOn(!isToggledOn);
    }

    public boolean isToggledOn() {
        return isToggledOn;
    }

    static {
        STATE_TOGGLED_ON = new int[]{R.attr.state_toggled_on};
    }
}
res/layout/activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <com.sample.application.AnimationImageButton
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
    <!-- アニメーション付きボタン -->
</android.support.constraint.ConstraintLayout>
MainActivity.java
package com.sample.application;

import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import android.view.View;

public class MainActivity extends AppCompatActivity {

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

        findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {

            }
        });
    }
}

実行結果 :arrow_forward:

animation.gif

関連項目

iOS と Android、React Native で高品質なアニメーションを簡単に作成できる Lottie というライブラリがあります。

20
14
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
20
14