BottomNavigationView擴(kuò)展

最近開(kāi)展新的項(xiàng)目滔以,首當(dāng)其中的問(wèn)題就是首頁(yè)導(dǎo)航。

雖然之前就已經(jīng)知道BottomNavigationView的存在氓拼,但是一直沒(méi)有使用你画。原因也很明顯,BottomNavigationView存在兩個(gè)非常嚴(yán)重的問(wèn)題:

  1. 使用png格式的圖標(biāo)時(shí)無(wú)法顯示原本的圖案桃漾;
  2. 默認(rèn)有位移動(dòng)畫(huà)坏匪,且無(wú)法通過(guò)配置進(jìn)行取消。


    默認(rèn)效果.gif
    默認(rèn)效果.gif

而這次不想再次進(jìn)行一次自定義撬统,決定采用官方的BottomNavigationView适滓。那么就要解決上面提到的兩個(gè)問(wèn)題。

本著不重復(fù)造輪子的原則(主要是懶)恋追,先搜索了一下這兩個(gè)問(wèn)題的解決方案凭迹。

其中無(wú)法顯示png圖標(biāo)的問(wèn)題可以完美解決罚屋。但是去除位移動(dòng)畫(huà)的處理都不完美,并且全網(wǎng)的答案驚人一致嗅绸。問(wèn)題如下:

僅解決了位移動(dòng)畫(huà)的問(wèn)題脾猛,但是并未解決圖標(biāo)及文本變大的問(wèn)題。

沒(méi)有時(shí)間看完下面改造過(guò)程的鱼鸠,直接復(fù)制這個(gè)文件添加到項(xiàng)目中即可:BottomNavigationViewExtension猛拴。

禁用位移動(dòng)畫(huà)

要禁用位移動(dòng)畫(huà),首先要找到使位移動(dòng)畫(huà)生效的代碼蚀狰。我們定位到BottomNavigationView愉昆,分析代碼后發(fā)現(xiàn)該View實(shí)際上是對(duì)BottomNavigationMenuView的包裝:

// ... 無(wú)關(guān)代碼省略
private final BottomNavigationMenuView mMenuView;

public BottomNavigationView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    
        // Create the menu
        mMenu = new BottomNavigationMenu(context);

        mMenuView = new BottomNavigationMenuView(context);
       
        // 將一個(gè)BottomNavigationMenuView添加到了當(dāng)前View中
        addView(mMenuView, params);
    }

則位移動(dòng)畫(huà)實(shí)際生效應(yīng)該在BottomNavigationMenuView中,那么我們繼續(xù)分析BottomNavigationMenuView的代碼:

public void buildMenuView() {
    // ...
    // 無(wú)關(guān)代碼省略 
    mButtons = new BottomNavigationItemView[mMenu.size()];
    mShiftingMode = mMenu.size() > 3; // 當(dāng)Menu中的item數(shù)量>3時(shí)麻蹋,默認(rèn)開(kāi)啟了位移動(dòng)畫(huà)
    for (int i = 0; i < mMenu.size(); i++) {
        // ...
        // 無(wú)關(guān)代碼省略
        BottomNavigationItemView child = getNewItem();
        child.setShiftingMode(mShiftingMode);
        addView(child);
    }
}

我們發(fā)現(xiàn)跛溉,在菜單中條目的數(shù)量大于3時(shí),開(kāi)啟了位移動(dòng)畫(huà)(mShiftingMode=true)倒谷。所以,如果要禁用位移動(dòng)畫(huà)的話(huà)深夯,需要將mShiftingMode的值設(shè)置為false诺苹,并且重建菜單收奔。

但是mShiftingMode的賦值是在重建菜單的時(shí)候進(jìn)行的,在不修改源碼的情況下此處無(wú)法做出修改质蕉。故繼續(xù)分析BottomNavigationItemView模暗。

@Override
    public void setChecked(boolean checked) {
        // ... 省略無(wú)關(guān)代碼
        if (mShiftingMode) {
            // 當(dāng)mShiftingMode=true時(shí)兑宇,該分支生效
            if (checked) {
                // ... 省略無(wú)關(guān)代碼
                mLargeLabel.setVisibility(VISIBLE);
                mLargeLabel.setScaleX(1f);
                mLargeLabel.setScaleY(1f);
            } else {
                // ... 省略無(wú)關(guān)代碼
                mLargeLabel.setVisibility(INVISIBLE);
                mLargeLabel.setScaleX(0.5f);
                mLargeLabel.setScaleY(0.5f);
            }
            mSmallLabel.setVisibility(INVISIBLE);
        } else {
            // 當(dāng)mShiftingMode=false時(shí)隶糕,該分支生效
            if (checked) {
                LayoutParams iconParams = (LayoutParams) mIcon.getLayoutParams();
                iconParams.gravity = Gravity.CENTER_HORIZONTAL | Gravity.TOP;
                // 選中時(shí),圖標(biāo)的頂部外邊距增加了mDefaultMargin+mShiftAmount
                iconParams.topMargin = mDefaultMargin + mShiftAmount;
                mIcon.setLayoutParams(iconParams);
                
                mLargeLabel.setVisibility(VISIBLE);
                mSmallLabel.setVisibility(INVISIBLE);

                mLargeLabel.setScaleX(1f);
                mLargeLabel.setScaleY(1f);
                mSmallLabel.setScaleX(mScaleUpFactor);
                mSmallLabel.setScaleY(mScaleUpFactor);
            } else {
                LayoutParams iconParams = (LayoutParams) mIcon.getLayoutParams();
                iconParams.gravity = Gravity.CENTER_HORIZONTAL | Gravity.TOP;
                iconParams.topMargin = mDefaultMargin;
                mIcon.setLayoutParams(iconParams);
                
                mLargeLabel.setVisibility(INVISIBLE);
                mSmallLabel.setVisibility(VISIBLE);

                mLargeLabel.setScaleX(mScaleDownFactor);
                mLargeLabel.setScaleY(mScaleDownFactor);
                mSmallLabel.setScaleX(1f);
                mSmallLabel.setScaleY(1f);
            }
        }

        refreshDrawableState();
    }

從上述代碼分析,要禁用位移動(dòng)畫(huà)测秸,需要將BottomNavigationItemView.mShiftingMode的值設(shè)置為false霎冯。如果想要不修改源碼,則此處需要使用反射慷荔。為了使用方便显晶,定義一個(gè)BottomNavigationView的擴(kuò)展方法:

fun BottomNavigationView.disableShiftMode() {
  try {
    val bottomNavigationMenuView = getChildAt(0) as BottomNavigationMenuView
    // BottomNavigationMenuView未提供setter方法來(lái)控制位移動(dòng)畫(huà)的開(kāi)關(guān)磷雇,在不修改源碼的前提下唯笙,只能通過(guò)反射來(lái)實(shí)現(xiàn)
    val shiftingMode = bottomNavigationMenuView.javaClass.getDeclaredField("mShiftingMode")
    shiftingMode.setBooleanValue(bottomNavigationMenuView, false)
    val childCount = bottomNavigationMenuView.childCount
    for (i in 0 until childCount) {
      val bottomNavigationItemView = bottomNavigationMenuView.getChildAt(
          i) as BottomNavigationItemView
      bottomNavigationItemView.setShiftingMode(false)
      // 重建菜單崩掘,該方法限制為進(jìn)在com.android.support包中才能夠調(diào)用,不知何時(shí)就會(huì)被隱藏掉.在被隱藏掉的時(shí)候可以嘗試采用反射的方式進(jìn)行調(diào)用
    bottomNavigationMenuView.updateMenuView()
  } catch (e: Exception) {
    e.printStackTrace()
  }
}
    
private fun Field.setBooleanValue(obj: Any, value: Boolean) {
  isAccessible = true
  setBoolean(obj, value)
  isAccessible = false
}

注意:BottomNavigationMenuView.updateMenuView()BottomNavigationItemView.setShiftingMode()被限制為僅能在com.android.support包中調(diào)用苞慢,如果在將來(lái)的support包中將這兩個(gè)方法私有化英妓,則可以嘗試通過(guò)反射的方式來(lái)調(diào)用蔓纠。

修改完畢后效果如下:


禁用位移動(dòng)畫(huà).gif

統(tǒng)一所有條目中圖標(biāo)邊距和文本的大小

在禁用了位移動(dòng)畫(huà)后贺纲,我們發(fā)現(xiàn)在選中一個(gè)條目的時(shí)候,條目的圖標(biāo)和文本框還是有輕微的位移侮措。我們繼續(xù)向下分析:

@Override
    public void setChecked(boolean checked) {
        // ... 省略無(wú)關(guān)代碼
        if (mShiftingMode) {
            // 當(dāng)mShiftingMode=true時(shí)乖杠,該分支生效
            if (checked) {
                // ... 省略無(wú)關(guān)代碼
                mLargeLabel.setVisibility(VISIBLE);
                mLargeLabel.setScaleX(1f);
                mLargeLabel.setScaleY(1f);
            } else {
                // ... 省略無(wú)關(guān)代碼
                mLargeLabel.setVisibility(INVISIBLE);
                mLargeLabel.setScaleX(0.5f);
                mLargeLabel.setScaleY(0.5f);
            }
            mSmallLabel.setVisibility(INVISIBLE);
        } else {
            // 當(dāng)mShiftingMode=false時(shí),該分支生效
            if (checked) {
                LayoutParams iconParams = (LayoutParams) mIcon.getLayoutParams();
                iconParams.gravity = Gravity.CENTER_HORIZONTAL | Gravity.TOP;
                // 選中時(shí)胧洒,圖標(biāo)的頂部外邊距增加了mDefaultMargin+mShiftAmount
                iconParams.topMargin = mDefaultMargin + mShiftAmount;
                mIcon.setLayoutParams(iconParams);
                // 選中時(shí)卫漫,mLargeLabel顯示列赎、mSmallLabel隱藏
                mLargeLabel.setVisibility(VISIBLE);
                mSmallLabel.setVisibility(INVISIBLE);

                mLargeLabel.setScaleX(1f);
                mLargeLabel.setScaleY(1f);
                mSmallLabel.setScaleX(mScaleUpFactor);
                mSmallLabel.setScaleY(mScaleUpFactor);
            } else {
                LayoutParams iconParams = (LayoutParams) mIcon.getLayoutParams();
                iconParams.gravity = Gravity.CENTER_HORIZONTAL | Gravity.TOP;
                // 未選中時(shí),圖標(biāo)的頂部外邊距為默認(rèn)值
                iconParams.topMargin = mDefaultMargin;
                mIcon.setLayoutParams(iconParams);
                
                // 未選中時(shí)饼煞,mLargeLabel隱藏砖瞧、mSmallLabel顯示
                mLargeLabel.setVisibility(INVISIBLE);
                mSmallLabel.setVisibility(VISIBLE);
                
                mLargeLabel.setScaleX(mScaleDownFactor);
                mLargeLabel.setScaleY(mScaleDownFactor);
                mSmallLabel.setScaleX(1f);
                mSmallLabel.setScaleY(1f);
            }
        }

        refreshDrawableState();
    }

從上面的代碼中可以看出嚷狞,問(wèn)題原因在于選中的條目的圖標(biāo)增加了頂部外邊距mShiftAmount感耙,以及大小兩個(gè)文本框的顯隱變化即硼。

為了解決這兩個(gè)問(wèn)題屡拨,做出如下擴(kuò)展:

/**
 * 統(tǒng)一每個(gè)條目的文本大小和圖標(biāo)的外邊距
 */
fun BottomNavigationView.unifyItems(forceUpdate: Boolean = true) {
  try {
    val bottomNavigationMenuView = getChildAt(0) as BottomNavigationMenuView
    val childCount = bottomNavigationMenuView.childCount
    for (i in 0 until childCount) {
      val child = bottomNavigationMenuView.getChildAt(i) as BottomNavigationItemView
      val clazz = child.javaClass

      // 使選中/未選中條目的頂部邊距不發(fā)生變化
      val shiftAmountField = clazz.getDeclaredField("mShiftAmount")
      shiftAmountField.setIntValue(child, 0)

      // 使選中/未選中條目的文本保持同樣的大小
      child.unifyTextSize()
    }

    if (forceUpdate) {
      bottomNavigationMenuView.updateMenuView()
    }
  } catch (e: Exception) {
    e.printStackTrace()
  }
}

private fun BottomNavigationItemView.unifyTextSize() {
  val baselineLayout = getChildAt(1) as BaselineLayout
  val smallLabel = baselineLayout.getChildAt(0) as TextView
  val largeLabel = baselineLayout.getChildAt(1) as TextView
  largeLabel.setTextSize(TypedValue.COMPLEX_UNIT_PX, smallLabel.textSize)
}

private fun Field.setIntValue(obj: Any, value: Int) {
  isAccessible = true
  setInt(obj, value)
  isAccessible = false
}

效果如下:


統(tǒng)一條目.gif
統(tǒng)一條目.gif

顯示圖標(biāo)原本圖案

/**
 * 禁用`app:itemIconTint`屬性,讓[BottomNavigationView]的圖標(biāo)顯示原本的顏色
 */
fun BottomNavigationView.disableIconTint() {
  this.itemIconTintList = null
}

最終效果如下:


最終效果.gif
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市绝编,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌窟勃,老刑警劉巖秉氧,帶你破解...
    沈念sama閱讀 211,817評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件汁咏,死亡現(xiàn)場(chǎng)離奇詭異作媚,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)轰驳,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,329評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門(mén)级解,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)勤哗,“玉大人掩驱,你說(shuō)我怎么就攤上這事欧穴。” “怎么了拼苍?”我有些...
    開(kāi)封第一講書(shū)人閱讀 157,354評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵疮鲫,是天一觀(guān)的道長(zhǎng)俊犯。 經(jīng)常有香客問(wèn)我,道長(zhǎng)燕侠,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,498評(píng)論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮杖虾,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘奇适。我一直安慰自己,他們只是感情好葛账,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,600評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布籍琳。 她就那樣靜靜地躺著趋急,像睡著了一般呜达。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上查近,一...
    開(kāi)封第一講書(shū)人閱讀 49,829評(píng)論 1 290
  • 那天,我揣著相機(jī)與錄音册烈,去河邊找鬼。 笑死矮冬,一個(gè)胖子當(dāng)著我的面吹牛次哈,可吹牛的內(nèi)容都是我干的窑滞。 我是一名探鬼主播,決...
    沈念sama閱讀 38,979評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼此改!你這毒婦竟也來(lái)了共啃?” 一聲冷哼從身側(cè)響起移剪,我...
    開(kāi)封第一講書(shū)人閱讀 37,722評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤纵苛,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后取试,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體贝椿,經(jīng)...
    沈念sama閱讀 44,189評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡瑟蜈,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,519評(píng)論 2 327
  • 正文 我和宋清朗相戀三年渣窜,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了乔宿。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,654評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡掂林,死狀恐怖泻帮,靈堂內(nèi)的尸體忽然破棺而出锣杂,到底是詐尸還是另有隱情,我是刑警寧澤赖阻,帶...
    沈念sama閱讀 34,329評(píng)論 4 330
  • 正文 年R本政府宣布火欧,位于F島的核電站布隔,受9級(jí)特大地震影響衅檀,放射性物質(zhì)發(fā)生泄漏哀军。R本人自食惡果不足惜打却,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,940評(píng)論 3 313
  • 文/蒙蒙 一柳击、第九天 我趴在偏房一處隱蔽的房頂上張望捌肴。 院中可真熱鬧,春花似錦秽五、人聲如沸饥悴。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,762評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)贷揽。三九已至棠笑,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間擒滑,已是汗流浹背腐晾。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,993評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留丐一,地道東北人藻糖。 一個(gè)月前我還...
    沈念sama閱讀 46,382評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像库车,于是被迫代替她去往敵國(guó)和親巨柒。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,543評(píng)論 2 349

推薦閱讀更多精彩內(nèi)容

  • 1柠衍、通過(guò)CocoaPods安裝項(xiàng)目名稱(chēng)項(xiàng)目信息 AFNetworking網(wǎng)絡(luò)請(qǐng)求組件 FMDB本地?cái)?shù)據(jù)庫(kù)組件 SD...
    陽(yáng)明先生_X自主閱讀 15,969評(píng)論 3 119
  • 終于看完《默讀》了。 一時(shí)間不太好說(shuō)牺勾,一個(gè)縱橫交錯(cuò)的巨大犯罪網(wǎng)絡(luò),一群?jiǎn)市牟】竦氖芎φ呓M織回还,幾個(gè)活生生的警察,一段...
    劉小花Crystal閱讀 493評(píng)論 0 0
  • 前段時(shí)間朋友圈公眾大號(hào)紛紛轉(zhuǎn)載一篇文章,雖然有些換個(gè)題目、換種說(shuō)法坎缭,但依舊是換湯不換藥,講的是同一類(lèi)心靈雞湯憎夷。文章...
    地己閱讀 250評(píng)論 0 0
  • 來(lái)澳洲快要一年了。 自己有變得更好嗎? 真的不是很清楚饮焦。的確是掌握了幾項(xiàng)技能,可是自己的弱點(diǎn):愛(ài)花錢(qián)硼啤、控制不住自己...
    言?shī)?/span>閱讀 797評(píng)論 0 0
  • 風(fēng)景如畫(huà)的城市亏镰,游人如織,行色匆匆逼肯,他們來(lái)自世界上的不同國(guó)度,里亞托橋下三椿,行船川流不息,每個(gè)來(lái)到這個(gè)城市的人蛋叼,都不...
    白素閱讀 341評(píng)論 1 1