このエントリは先日参加した「まったりAndroid Framework Code Reading #5」の成果です٩( ‘ω’ )و
Support LibraryのBottomNavigationViewのコード読んできたのでまとめるよ!
BottomNavigationViewとは
Support Library 25.0.0から追加された画面下部に配置するviewです。
API Referenceはこちら
なぜBottomNavigationVIewを読もうと思ったか?
iOSではおなじみの下タブデザインをAndroidでも取り入れたいという話はよく出てくるかと思いますが、このBottomNavigationViewはボタンの数が多くなってくるほどめちゃめちゃアニメーションが入ってきます。
アニメーションいらないし、inactiveなボタンだとtext表示されないしで、割とプロダクトに取り入れるのはハードル高かったりします。 そういった場合、自分でcustom viewを作ることになると思うのですが、次に問題になるのはmenuをxmlで設定できるようにするか?という部分になります。 menuで設定できた方が綺麗だけど、そこまで汎用性高める必要もないしもはやコード量によるよなぁと思って、では本家ではどれくらいのコード量なのか?を知るために読んでみました。
で、どうだった?
アニメーションを含めるとBottomNavigationViewを構成する関連クラスは7つほどでした。というわけでmenuで設定できる必要はないかなーという結論。Library作っていろんなプロダクトで使い回すなら別ですけどね。
わかったこと
関連コードとそれぞれ読んだ時のメモを残しておきます。
BottomNavigationView
- コードはここ
- FrameLayoutをextendsしたクラス
- prensenter, menu, menuViewをそれぞれお互いにバインド
- custom attributeを取得してmenuViewにセット
- custom attributeからmenuを取ってきてinflateするメソッドがpublicなので、コードから任意のタイミングでinflateできるっぽい
181 /** 182 * Inflate a menu resource into this navigation view. 183 * 184 * <p>Existing items in the menu will not be modified or removed.</p> 185 * 186 * @param resId ID of a menu resource to inflate 187 */ 188 public void inflateMenu(int resId) { 189 mPresenter.setUpdateSuspended(true); 190 getMenuInflater().inflate(resId, mMenu); 191 mPresenter.initForMenu(getContext(), mMenu); 192 mPresenter.setUpdateSuspended(false); 193 mPresenter.updateMenuView(true); 194 }
- このView自体はただのFrameLayoutでコンテナ状態
- menu viewをaddView()することによりViewの描画を行う
- menu viewがさらにitem viewを再帰的にaddViewしている
- menu viewをaddView()することによりViewの描画を行う
- itemIconTintがattributeで指定されていなかったら、disable/ emptyには
android.R.attr.textColorSecondary
がが設定される。checkedはprimary - BottomNavigationMenuにcall backを設定
- 提供するinterfaceはonMenuItemSelected()とonMenuModeChange()
BottomNavigationMenu
- コードはここ
- 50行程度の小さなクラス
- MenuBuilderクラスの拡張
- @hideかつpublic
- このクラスのMAX_ITEM_COUNTは5
- sub menuは非サポート
BottomNavigationMenuView
- コードはここ
- 300行程度のクラス
- ViewGroupの拡張、MenuViewをimplements
- @hideかつpublic
- BottomNavigationViewから強制的にGravity.centerをセットされる
111 mMenuView = new BottomNavigationMenuView(context); 112 FrameLayout.LayoutParams params = new FrameLayout.LayoutParams( 113 ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); 114 params.gravity = Gravity.CENTER; 115 mMenuView.setLayoutParams(params);
- initialize()でメンバで持ってるMenuが更新されてもcheckedの位置はリセットされず引き継ぐ
97 @Override 98 public void initialize(MenuBuilder menu) { 99 mMenu = menu; 100 if (mMenu == null) return; 101 if (mMenu.size() > mActiveButton) { 102 mMenu.getItem(mActiveButton).setChecked(true); 103 } 104 }
- BottomNavigationItemViewの配列を持っている
- Pools.SynchronizedPoolというhideなクラスを使ってButtonのオンジェクトをプールしている
- Buttonが3つ以上ある場合はアニメーション(shifting mode)
- アニメーション機能ははBottomNavigationAnimationHelper* クラスに移譲
- ここでいうアニメーションとは、ボタン自体の移動をさす
- ボタンのコンテンツのサイズが変わるtransitionはBottomNavigationItemViewの責務
BottomNavigationItemView
- コードはここ
- 実際のボタンを担当するクラス
- FrameLayoutをextends, MenuView.ItemViewをimplements
- active/inactiveが切り替わったタイミングでボタン内のコンテンツ(lable, icon)の拡大・縮小、表示・非表示の制御を行う
BottomNavigationPresenter
- コードはここ
- 100行程度の小さなクラス
- MenuPresenterをimplements
- MenuPresenterはhideなinterface
- @hideかつpublic
- updateViewのロックは意外とフラグ制御
BottomNavigationAnimationHelperIcs
- コードはここ
- ボタンが3つ以上だった時のanimationのhelper class
- AutoTransitionとTransitionManagerを使ってanimation
- material motionの土台となってるのはこのクラスっぽい
- 意外とめっちゃシンプルなコードでかけるっぽい
30 BottomNavigationAnimationHelperIcs() { 31 mSet = new AutoTransition(); 32 mSet.setOrdering(TransitionSet.ORDERING_TOGETHER); 33 mSet.setDuration(ACTIVE_ANIMATION_DURATION_MS); 34 mSet.setInterpolator(new FastOutSlowInInterpolator()); 35 TextScale textScale = new TextScale(); 36 mSet.addTransition(textScale); 37 } 38 39 void beginDelayedTransition(ViewGroup view) { 40 TransitionManager.beginDelayedTransition(view, mSet); 41 }
BottomNavigationAnimationHelperBase
- コードはここ
- ICSより前のOSだとこっちが使われる
- 中身はこれだけ(つまりanimationしない)
21 class BottomNavigationAnimationHelperBase { 22 void beginDelayedTransition(ViewGroup view) { 23 // Do nothing. 24 } 25 }