Java Android開発入門 - TabLayout、Fragment、プロパティアニメーション

機能概要

  • Java言語を使用
  • 主な機能:
    • ex1: SeekBarでLottieアニメーションの再生を制御し、自動再生機能を実装
    • ex2: 下部の入力パラメータで中央ビューのアニメーション効果を制御
    • ex3: TabLayoutとFragmentを使用したタブ切り替え機能を実装し、切り替え後5秒間Lottieアニメーションを再生し、アニメーション終了後にフェードイン・フェードアウト効果を追加

スクリーンショット

前提知識

Javaで開発を行います。

Android開発入門: http://hukai.me/android-training-course-in-chinese/basics/index.html

Java入門: https://www.runoob.com/java/java-tutorial.html

Fragment参考資料

• 公式ドキュメント:

https://developer.android.com/guide/fragments

• 日本語翻訳:

https://juejin.cn/post/6900739309826441224#heading-29

https://juejin.cn/post/6901453354463920135

• Fragmentソースコード解説:

https://juejin.cn/post/6844904086437904398

Animation参考資料

• プロパティアニメーション:

https://developer.android.com/guide/topics/graphics/prop-animation

• ベジェ曲線ビジュアライザー:

https://cubic-bezier.com/

• 非公式まとめ:

https://juejin.cn/post/6844903465211133959

• その他の参考資料: プロパティアニメーション

https://rengwuxian.com/123.html

https://rengwuxian.com/127.html

• Lottie Androidガイド:

https://airbnb.io/lottie/#/android

• Lottieリソース:

https://lottiefiles.com/

プロジェクト構造

プロジェクトはテンプレートを基にAndroid StudioのTabbed Activityテンプレートを組み合わせて構築しました。ファイル数が多いため、重要なもののみを説明します。

Javaファイル

上から順に:

  • 1-3: 各ボタンに対応するActivityクラス
  • 4: メイン画面のActivity、3つのボタンのロジックを制御し、異なるActivityに遷移
  • 5: RecyclerViewのカスタムAdapterクラス(前回のチュートリアルから流用)。主にビューの内容をロードおよび制御する方法を定義し、アイテムのクリックイベントを含む。今回はViewHolderも内部に配置
  • 6: このクラスは不要でした。各ページのビュー内容を制御するデフォルトテンプレート用のクラス
  • 7: SectionsPagerAdapterのgetItemを呼び出す際に、指定されたページのFragmentインスタンスをインスタンス化する必要がある。PlaceholderFragment.newInstanceを使用
  • 8: カスタムビュータイプ、虹色テキストを作成可能
  • 9: カスタムSectionsPagerAdapter、FragmentPagerAdapterを継承し、Fragmentの表示効果を制御。タブのテキスト、総ページ数などを含む

レイアウトファイル

それぞれ対応するActivityのレイアウト。詳細な説明は省略しますが、注意点を説明します。

XMLにLottieアニメーションを追加

Lottieアニメーションの追加方法は前提知識に記載されています。このアプリではex1.xmlとfragment_placeholder.xmlに追加する必要があります(もちろんIDの重複に注意してください)。

    <com.airbnb.lottie.LottieAnimationView
        android:id="@+id/animation_view"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        app:lottie_loop="true"
        app:lottie_rawRes="@raw/material_wave_loading" />

app:lottie_rawResはJSON形式のアニメーションファイルの場所を指定し、通常rawフォルダー下に配置します。

fragment_placeholder.xmlにはanimation_view2とrecycler_viewのみが含まれ、各Fragmentはこの2つのビューのフェードイン・フェードアウトによって実現されます。

機能実装

チェックボックスでアニメーション再生状態を切り替え

ここでのloopCheckBoxはCheckBox型です

        loopCheckBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
            @Override
            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                if (isChecked) {
                    // 自動再生が選択された場合、Lottieアニメーションを再生し、手動での進捗調整を禁止
                    animationView.playAnimation();
                    seekBar.setEnabled(false);
                } else {
                    // 自動再生が解除された場合、Lottieアニメーションを停止し、手動での進捗調整を許可
                    animationView.pauseAnimation();
                    seekBar.setEnabled(true);
                }
            }
        });

SeekBarでアニメーション進捗を制御

ここでのseekBarはSeekBar型で、findViewById(R.id.seekbar)でバインドされています

        seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
            @Override
            public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
                // TODO ex1-2: どの関数を呼び出すべきか
                // ヒント1: https://airbnb.io/lottie/#/android?id=custom-animators を参照
                // ヒント2: OnProgressChangedの間にマウスを置き、F1をクリックしてSeekBarのドキュメントを確認、または公式サイトで確認 https://developer.android.google.cn/reference/android/widget/SeekBar.OnSeekBarChangeListener.html#onProgressChanged(android.widget.SeekBar,%20int,%20boolean

                // progressが変更されるたびにこの関数が呼び出されるため、その都度新しいprogressに対応するパーセンテージを渡すだけ
                // progressは通常1-100の整数なので、getMax()が最適
                animationView.setProgress((float)progress/seekBar.getMax());

            }

ex2の実装

色の選択は色ビューにクリックリスナーを追加し、ColorPickerクラスで表示される色選択ウィンドウを通じて実現します。

複雑なアニメーション効果はAnimatorSetで複数のアニメーションを連結して実現され、色の制御にはObjectAnimator.ofArgb()が使用され、その他はofFloat()が使用されます。

ex3の実装

メインActivityは以下の通り

public class MainTabActivity extends AppCompatActivity {

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

        // TODO: ex3-1. ViewPagerとFragmentを使用してスライド可能なインターフェースを追加
        TabPagerAdapter tabPagerAdapter = new TabPagerAdapter(this, getSupportFragmentManager());
        ViewPager viewPager = findViewById(R.id.view_pager);
        viewPager.setAdapter(tabPagerAdapter);

        // TODO: ex3-2, TabLayoutでタブサポートを追加
        TabLayout tabLayout = findViewById(R.id.tab_layout);
        tabLayout.setupWithViewPager(viewPager);
    }
}
 

TabPagerAdapterクラス

public class TabPagerAdapter extends FragmentPagerAdapter {

    @StringRes
    private static final int[] TAB_TITLES = new int[]{R.string.tab_title_1, R.string.tab_title_2, R.string.tab_title_3};
    private final Context context;

    public TabPagerAdapter(Context context, FragmentManager fm) {
        super(fm);
        this.context = context;
    }

    @Override
    public Fragment getItem(int position) {
        // getItemは指定されたページのFragmentをインスタンス化するために呼び出されます。
        // 以下に定義されているContentFragmentを返します。
        return ContentFragment.newInstance(position);
    }

    @Nullable
    @Override
    public CharSequence getPageTitle(int position) {
        return context.getResources().getString(TAB_TITLES[position]);
    }

    @Override
    public int getCount() {
        // 合計3ページを表示
        return 3;
    }
}

ContentFragmentクラス

public class ContentFragment extends Fragment {

    private LottieAnimationView loadingAnimation;
    private RecyclerView contentList;
    private Context fragmentContext;

    private List<String> dataList = new ArrayList<>();

    private static final String ARG_TAB_INDEX = "tab_index";

    public static ContentFragment newInstance(int index) {
        ContentFragment fragment = new ContentFragment();
        Bundle args = new Bundle();
        args.putInt(ARG_TAB_INDEX, index);
        fragment.setArguments(args);
        return fragment;
    }

    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        // TODO ex3-3: fragment_contentを修正し、ローディングコントロールとリストビューコントロールを追加
        View rootView = inflater.inflate(R.layout.fragment_content, container, false);
        loadingAnimation = rootView.findViewById(R.id.loading_animation);
        contentList = rootView.findViewById(R.id.content_recycler);
        fragmentContext = container.getContext();
        return rootView;
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // 配列を初期化
        for (int i = 1; i <= 100; i++) {
            dataList.add(String.format("Item %d", i));
        }
    }

    @Override
    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        // アニメーションを再生
        loadingAnimation.playAnimation();

        // RecyclerViewを表示
        contentList.setLayoutManager(new LinearLayoutManager(fragmentContext));
        contentList.setAdapter(new ContentAdapter(dataList));
        contentList.setVisibility(View.INVISIBLE); // 最初は表示しない

        ObjectAnimator fadeOut = ObjectAnimator.ofFloat(loadingAnimation,
                "alpha", 1f, 0f); // フェードアウト効果、alphaを1から0へ
        fadeOut.setDuration(1000); // フェードアウト1秒

        ObjectAnimator fadeIn = ObjectAnimator.ofFloat(contentList,
                "alpha", 0f, 1f);
        fadeIn.setDuration(1000);

        // アニメーションセットにまとめて同時に実行
        final AnimatorSet transition = new AnimatorSet();
        transition.playTogether(fadeIn, fadeOut);

        getView().postDelayed(new Runnable() {
            @Override
            public void run() {
                // ここは5秒後に実行される
                // TODO ex3-4: アニメーションを実装し、Lottieコントロールをフェードアウト、リストデータをフェードイン
                contentList.setAlpha(0f); // まず透明度を0に設定してから表示
                contentList.setVisibility(View.VISIBLE);
                transition.start();
            }
        }, 5000);

        // 5秒待たずに透明度を設定したい場合は、このdelayを使用
        // transition.setStartDelay(5000);
        // transition.start();

    }

}

主な注意点

  • setRepeatCount()は再生回数ではなく繰り返し回数を設定します。1に設定すると2回再生されます
  • フェードインを遅らせたいビューの場合、まずvisibilityをGONEに設定し、フェードイン直前に透明度を0に設定し、visibilityをVISIBLEにします

説明

この例では、異なるFragment間の切り替え時のアニメーションが時々表示され、時々表示されないことがあります。

これはFragmentがデフォルトで次のページをプリロードするためで、tab1とtab2が両方ロードされている場合、tab2に入るとアニメーションが表示されず、tab3に入るとアニメーションが表示されます。

デフォルトのプリロードをキャンセルするには、現在のFragmentが表示されているかどうかを判断するカスタムサブクラスを作成する必要があります。参考: https://www.jianshu.com/p/0e2d746e3a3d

タグ: Java Android TabLayout Fragment PropertyAnimation

7月3日 19:40 投稿