1. 程式人生 > >BottomNavigationView實現底部導航欄的實現(一)

BottomNavigationView實現底部導航欄的實現(一)

支援庫的依賴(Android Design Support Library)

開始之前打一波推廣,推廣的就是我自己,最近喜歡hexo的部落格主題,自己上手搭建一個,從開始使用到Hexo的個性化配置,自己邊學習邊記錄,形成了Hexo搭建個人網站的一個體系,同步在個人的部落格上:

本文是對底部導航學習和最後筆記記錄,使用的是BottomNavigationView 控制元件.

選取常見的圖示:

注意: 下面的連線請在微信上開啟

支援庫的依賴

首先是上程式碼:

// 支援庫
compile 'com.android.support:design:25.3.1'

不然是不會出現對應的提示;

開始新建佈局書寫

開始之前學習一下子 ColorStateList 型別的資源:

ColorStateList物件可以在XML中定義,像color一樣使用,它能根據它應用到的View物件的狀態實時改變顏色。例如,Button可以存在多種狀態(pressed、focused或other),如果使用ColorStateList,你就能為它的每個狀態提供不同的顏色。

新建專案開始決定來書寫:

添加布局檔案時候注意的事項:

  1. 屬性一: iteamBackground 指定的是底部導航欄的背景顏色,預設是主題的顏色;
  2. 屬性二: iteamIconTint 指的是導航欄中圖片的顏色
  3. 屬性三: iteamTextColor 指的是導航欄文字的顏色
  4. 屬性四: menu

BottomNavigationView在使用時,除了普通空間的屬性外,還需要注意如下幾個特有屬性:

app:itemBackground:指定底部導航欄的背景顏色,預設是當前主題的背景色,白色or黑色;

app:itemIconTint:指定底部導航欄元素圖示的著色方式,預設元素選中時icon顏色為@color/colorPrimary;
app:itemTextColor:指定底部導航欄元素文字的著色方式;

app:menu:使用Menu的形式為底部導航欄指定元素;

首先是程式碼的展示:

<android.support.design.widget.BottomNavigationView
    android:id="@+id/bottom_navigation"
    android:layout_alignParentBottom="true"
    app:itemBackground="@color/colorAccent"
    app:itemIconTint="@color/status_text"
    app:itemTextColor="@color/yellow"
    app:menu="@menu/bottom_navigation_main"
    android:layout_width="match_parent"
    android:layout_height="wrap_content" />

之後是使用men給底部導航指定對應的元素:

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">

<item
    android:id="@+id/action_favorites"
    android:enabled="true"
    android:icon="@mipmap/canvas"
    android:title="@string/text_favorites"
    app:showAsAction="ifRoom" />
<item
    android:id="@+id/action_schedules"
    android:enabled="true"
    android:icon="@mipmap/ic_launcher"
    android:title="@string/text_schedules"
    app:showAsAction="ifRoom" />
<item
    android:id="@+id/action_music"
    android:enabled="true"
    android:icon="@mipmap/ic_launcher"
    android:title="@string/text_music"
    app:showAsAction="ifRoom" />

</menu>

展示的效果圖:

實現選中和未選中的顏色變化

我們看到的底部導航欄的實現中,選中和未選中的顏色是不一致的,我們看到 itemIconTint屬性和itemTextColor都是前面提到過的ColorStatusList物件,也就是我們可以設定對應的選擇和未選中時候的顏色list.

新建bottom_item_icon_tint_list.xml檔案,設定的是圖示選擇和未選中的顏色的著色的方式.

### 記錄新建一個drawable的xml小操作 ###

時間長了,這個都忘記了,首先是一個具有狀態對應的像color使用一樣的drawable.

在drawable選中,右擊→new →Drawable Resources file

展示如下:

輸入檔案的名稱後(後面不需要.xml,只需要的是檔案的名稱),建立成功展示如下:

程式碼展示和說明:

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_checked="true" android:color="@color/background_blue" ></item>
<item android:state_checked="false"  android:color="@color/yellow"></item>
</selector>

一個狀態對應於一個iteam,我們這裡設定的圖片的著色是選擇blue,未選中黃色,我們依次設定文字的顏色一致.

感慨一下子,書寫程式碼要注意力很集中,不集中出現的錯誤需要我們花費更大的精力去執行檢查錯誤.

選中和未選中圖片的變化的設定

上面的設定是在選中和未選中的時候著色的設定,如果是圖片的話怎麼做.

選中使用的是一個顏色,那我們也可以在選中的時候使用一張圖片,沒有選中的時候使用一張圖片. 那麼就是一個選擇器,我們直接建立.

bottom_png_list.xml;

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_checked="true" android:drawable="@drawable/music"></item>
<item android:drawable="@drawable/music_gray"></item>

</selector>

圖片展示如下:

我們使用的meu選單設定元素到底部導航欄裡面的,引用圖片的位置換一下就行了!

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">

<item
    android:id="@+id/action_favorites"
    android:enabled="true"
    android:icon="@drawable/bottom_png_list"
    android:title="@string/text_favorites"
    app:showAsAction="ifRoom" />
<item
    android:id="@+id/action_schedules"
    android:enabled="true"
    android:icon="@drawable/bottom_png_list"
    android:title="@string/text_schedules"
    app:showAsAction="ifRoom" />
<item
    android:id="@+id/action_music"
    android:enabled="true"
    android:icon="@drawable/bottom_png_list"
    android:title="@string/text_music"
    app:showAsAction="ifRoom" />

</menu>

此時不需要設定 itemIconTint和itemTextColor 顏色

也可以在程式碼中設定 iconTint為null:

mBottomNavigationView2.setItemIconTintList(null);

執行展示:

下面的就是對選單menu的監聽和頁面的切換.

四個導航欄的問題

測試的模擬器的解析度是 720*480 p

Java 中Math.min(a,b)是比較a和b 的大小,返回的是較小的數值.

設定和前面的一樣,我們不在展示,展示的是出現的問題.

我們直接進BottomNavigationView的原始碼檢視:

BottomNavigationView原始碼

說明最大值item有一個值:(這個值是5)

我們設定五個iteam看看,沒有出錯,展示的圖片如下:

最大支援的是5個iteam,我們加到六個iteam,看看結果是怎麼樣的:

結果是直接出錯了,我們直接看看錯誤的資訊是展示:

這裡面明確的說明BottomNavigationView支援的最大的number of iteam 是5

2018/2/28 13:57:02

我們繼續下面的這個問題,為什麼沒有平分佈局,展示原始碼如下:

出現了一個新類,在後面設定了setBottomNavigationMenuView

進入到BottomNavigationMeunView

我們直接看 onMeasure()方法.

// 原始碼 - BottomNavigationMeunView.class
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    final int width = MeasureSpec.getSize(widthMeasureSpec);
    final int count = getChildCount();

    final int heightSpec = MeasureSpec.makeMeasureSpec(mItemHeight, MeasureSpec.EXACTLY);

    if (mShiftingMode) {
        final int inactiveCount = count - 1;
        final int activeMaxAvailable = width - inactiveCount * mInactiveItemMinWidth;
        final int activeWidth = Math.min(activeMaxAvailable, mActiveItemMaxWidth);
        final int inactiveMaxAvailable = (width - activeWidth) / inactiveCount;
        final int inactiveWidth = Math.min(inactiveMaxAvailable, mInactiveItemMaxWidth);
        int extra = width - activeWidth - inactiveWidth * inactiveCount;
        for (int i = 0; i < count; i++) {
            mTempChildWidths[i] = (i == mSelectedItemPosition) ? activeWidth : inactiveWidth;
            if (extra > 0) {
                mTempChildWidths[i]++;
                extra--;
            }
        }
    } else {
        final int maxAvailable = width / (count == 0 ? 1 : count);
        final int childWidth = Math.min(maxAvailable, mActiveItemMaxWidth);
        int extra = width - childWidth * count;
        for (int i = 0; i < count; i++) {
            mTempChildWidths[i] = childWidth;
            if (extra > 0) {
                mTempChildWidths[i]++;
                extra--;
            }
        }
    }

    int totalWidth = 0;
    for (int i = 0; i < count; i++) {
        final View child = getChildAt(i);
        if (child.getVisibility() == GONE) {
            continue;
        }
        child.measure(MeasureSpec.makeMeasureSpec(mTempChildWidths[i], MeasureSpec.EXACTLY),
                heightSpec);
        ViewGroup.LayoutParams params = child.getLayoutParams();
        params.width = child.getMeasuredWidth();
        totalWidth += child.getMeasuredWidth();
    }
    setMeasuredDimension(
            ViewCompat.resolveSizeAndState(totalWidth,
                    MeasureSpec.makeMeasureSpec(totalWidth, MeasureSpec.EXACTLY), 0),
            ViewCompat.resolveSizeAndState(mItemHeight, heightSpec, 0));
}

快速執行: shift + F10 連線上的會直接回車

執行停止的命令: ctrl+ F2 debug執行的命令: shift + F9 連線的話是直接回車就行.

我debug一些資訊:

學習一個英文單詞: inactive 閒置的;

閒置的iteam最大寬度是:144 閒置的iteam最小的寬度是: 84 啟用的iteam的最大的寬度是252 iteam的高度是: 84

我們看到一個判斷 mShitingMode,我們看看這個變數

後面有沒有對這個變數的再次賦值:

我們這裡看到menu.size有一個判斷, 判斷值是3 大於三是true,不大於是false

當mShitingMode=false的時候

此時表示的iteam的數量在三或者之下:

dubug的展示圖片:

我們看到此時所有的childWidth的寬度是240 這個寬度= 獲取的width/count (此時debug的count是3)

當mShifingMode = true的時候

對onMeasure進行debug測試:

此時的模擬器 720*480 count 數量是5

看出選中的寬度是 252 限制的寬度是117

問題的處理

全域性搜尋: double shift;

當我們iteam是大於5的時候,我們獲取到 mShifingMode,設定為false,就會平分了!

還有注意的一點,忘記說了:

複製網上的程式碼:

/**
 * Created by Administrator on 2018/2/28.
 */

public class BottomNavigationViewHelper {
@SuppressLint("RestrictedApi")
public static void disableShiftMode(BottomNavigationView view) {
    BottomNavigationMenuView menuView = (BottomNavigationMenuView) view.getChildAt(0);
    try {
        Field shiftingMode = menuView.getClass().getDeclaredField("mShiftingMode");
        shiftingMode.setAccessible(true);
        shiftingMode.setBoolean(menuView, false);
        shiftingMode.setAccessible(false);
        for (int i = 0; i < menuView.getChildCount(); i++) {
            BottomNavigationItemView item = (BottomNavigationItemView) menuView.getChildAt(i);
            //noinspection RestrictedApi
            item.setShiftingMode(false);
            // set once again checked value, so view will be updated
            //noinspection RestrictedApi
            item.setChecked(item.getItemData().isChecked());
        }
    } catch (NoSuchFieldException e) {
        Log.e("BNVHelper", "Unable to get shift mode field", e);
    } catch (IllegalAccessException e) {
        Log.e("BNVHelper", "Unable to change value of shift mode", e);
    }
}
}

在Activity中的使用:

執行看看:

其他的設定對底部導航

設定如下:

第一設定圖片和文字之間的距離

直接在dimens中設定:

<dimen tools:override="true" name="design_bottom_navigation_margin">5dp</dimen>

展示效果如下:

增加底部導航欄的高度

預設的底部導航欄的高度是 56dp

<dimen tools:override="true" name="design_bottom_navigation_height">84dp</dimen>

展示如下:

設定選中後字型不變

我們現在設定的選中的時候字型會變大,我們設定為不變:

<dimen tools:override="true" name="design_bottom_navigation_text_size">12sp</dimen>
<dimen tools:override="true" name="design_bottom_navigation_active_text_size">12sp</dimen>

設定選中和未選中的字型的大小一致解決:

不展示了!

最後放一下子我學習的微信公眾號