Android 底部導(dǎo)航控件實例代碼
一、先給大家展示下最終效果

通過以上可以看到,圖一是簡單的使用,圖二、圖三中為結(jié)合ViewPager共同使用,而且都可以隨ViewPager的滑動漸變色,不同點(diǎn)是圖二為選中非選中兩張圖片,圖三的選中非選中是一張圖片只是做了顏色變化。
二、 需求
我們希望做可以做成這樣的,可以在xml布局中引入控件并綁定數(shù)據(jù),在代碼中設(shè)置監(jiān)聽回調(diào),并且配置使用要非常簡單!
三、需求分析
根據(jù)我們多年做不明確需求項目的經(jīng)驗,以上需求還算明確。那么我們可以采用在LinearLayout添加子View控件,這個子View控件就是我們自定義的每個tab條目,當(dāng)然對LinearLayout要設(shè)置權(quán)重。
需求大致明確之后就先設(shè)計每個條目的子View控件,這個子View控件是一個可以切換狀態(tài)變化的,一張、兩張都可以切換狀態(tài)(參考圖一、圖三)。那么這個View要可以設(shè)置底部顯示的文字,設(shè)置選中時顏色、未選中時顏色、選中時圖片、未選中時圖片、文字大小、設(shè)置是否有指示點(diǎn)、設(shè)置指示點(diǎn)大小、設(shè)置指示點(diǎn)圖片等等。
四、Tab條目接口
通過需求分析,我們可以定義如下的Tab子View操作接口:

仔細(xì)的朋友會發(fā)現(xiàn),為什么在接口中沒有設(shè)置選中圖片以及設(shè)置非選中時圖片,那是因為這個屬性不是通用的,在不同的實現(xiàn)中再去定義。
五、Tab條目實現(xiàn)

類的繼承關(guān)系及說明:
類圖如下所示:

在TabViewBase中主要的方法就是測量,其他的都是對接口的簡單實現(xiàn)。
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
// 得到繪制icon的寬
int bitmapWidth = Math.min(getMeasuredWidth() - getPaddingLeft()
- getPaddingRight(), getMeasuredHeight() - getPaddingTop()
- getPaddingBottom() - mTextBound.height());
int left = getMeasuredWidth() / 2 - bitmapWidth / 2;
int top = (getMeasuredHeight() - mTextBound.height()) / 2 - bitmapWidth / 2;
// 設(shè)置icon的繪制范圍
mIconRect = new Rect(left, top, left + bitmapWidth, top + bitmapWidth);
// 設(shè)置指示點(diǎn)的范圍
int indicatorRadius = mIndicatorSize / 2;
int tabRealHeight = bitmapWidth + mTextBound.height();
mIndicatorRect = new Rect(left + tabRealHeight* 4/5 - indicatorRadius, top, left+tabRealHeight* 4/5 + indicatorRadius, top + mIndicatorSize);
}
在以上代碼中可以看到,測量文字的高度,用控件的高度減去文字的高度和控件的寬度對比,取較小的為圖片的大小,也就是設(shè)置的圖片要為正方形,否則會產(chǎn)生變形。
看下普通兩張圖片切換的TabView的繪制:

@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
setupTargetBitmap(canvas);
drawIndicator(canvas);
if(null != mText) {
drawTargetText(canvas);
}
}
/**
* 繪制圖標(biāo)圖片
* @param canvas
*/
private void setupTargetBitmap(Canvas canvas) {
canvas.drawBitmap(isSelected ? mSelectedIconBitmap : mUnselectedIconBitmap, null, mIconRect, null);
}
/**
* 繪制指示點(diǎn)
* @param canvas
*/
protected void drawIndicator(Canvas canvas) {
if(isIndicateDisplay) {
canvas.drawBitmap(mIndicatorBitmap, null, mIndicatorRect, null);
}
}
/**
* 繪制文字
* @param canvas
*/
protected void drawTargetText(Canvas canvas) {
mTextPaint.setColor(isSelected ? mSelectedColor : mUnselectedColor);
canvas.drawText(mText, mIconRect.left + mIconRect.width() / 2
- mTextBound.width() / 2,
mIconRect.bottom + mTextBound.height(), mTextPaint);
}
可以看到非常的簡單,就是繪制圖標(biāo)圖片以及繪制指示點(diǎn),在繪制圖標(biāo)圖片時判斷當(dāng)前條目是否在選中狀態(tài),根據(jù)是否選中來繪制不同的圖片,在繪制指示點(diǎn)的時候首先判斷下是否設(shè)置了顯示指示點(diǎn)。如果有底部文字,那么久繪制底部文字。
在ViewPager兩張圖片圖片的時,我們再把效果圖拿過來觀察下:

這里有兩種模式,即隨著ViewPager的滾動圖標(biāo)漸變及普通變化。OK,了解之后我們就能很輕松的來編寫它的繪制了,可以通過繪制兩張圖片,但是在繪制的時候控制它的透明度就可以啦,是不是也很簡單。
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
int alpha = (int) Math.ceil((255 * mAlpha));
drawSourceBitmap(canvas, alpha);
drawTargetBitmap(canvas, alpha);
if(null != mText) {
drawSourceText(canvas, alpha);
drawTargetText(canvas, alpha);
}
drawIndicator(canvas);
}
/**
* 繪制未選中圖標(biāo)
* @param canvas
* @param alpha
*/
private void drawSourceBitmap(Canvas canvas, int alpha) {
mPaint.setAntiAlias(true);
mPaint.setDither(true);
mPaint.setAlpha(255 - alpha);
canvas.drawBitmap(mUnselectedIconBitmap, null, mIconRect, mPaint);
}
/**
* 繪制選中圖標(biāo)
* @param canvas
* @param alpha
*/
private void drawTargetBitmap(Canvas canvas, int alpha) {
mPaint.setAntiAlias(true);
mPaint.setDither(true);
mPaint.setAlpha(alpha);
canvas.drawBitmap(mSelectedIconBitmap, null, mIconRect, mPaint);
}
/**
* 畫未選中文字
* @param canvas
* @param alpha
*/
private void drawSourceText(Canvas canvas, int alpha) {
mTextPaint.setTextSize(mTextSize);
mTextPaint.setColor(mUnselectedColor);
mTextPaint.setAlpha(255 - alpha);
canvas.drawText(mText, mIconRect.left + mIconRect.width() / 2
- mTextBound.width() / 2,
mIconRect.bottom + mTextBound.height(), mTextPaint);
}
/**
* 畫選中文字
* @param canvas
* @param alpha
*/
private void drawTargetText(Canvas canvas, int alpha) {
mTextPaint.setColor(mSelectedColor);
mTextPaint.setAlpha(alpha);
canvas.drawText(mText, mIconRect.left + mIconRect.width() / 2
- mTextBound.width() / 2,
mIconRect.bottom + mTextBound.height(), mTextPaint);
}
代碼中的mAlpha是ViewPager滾動的百分比,然后分別繪制選中以及未選中的圖標(biāo)和文本,但是繪制的時候設(shè)置的透明度不同,這樣就會有一個漸變的效果。
在ViewPager單張圖片圖片的時,我們再把效果圖拿過來觀察下:

private void setupTargetBitmap(int alpha) {
mBitmap = Bitmap.createBitmap(getMeasuredWidth(), getMeasuredHeight(), Config.ARGB_8888);
mCanvas = new Canvas(mBitmap);
mPaint = new Paint();
mPaint.setColor(mSelectedColor);
mPaint.setAntiAlias(true);
mPaint.setDither(true);
mPaint.setAlpha(alpha);
mCanvas.drawRect(mIconRect, mPaint);
mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));
mPaint.setAlpha(255);
mCanvas.drawBitmap(mIconBitmap, null, mIconRect, mPaint);
}
private void drawSourceText(Canvas canvas, int alpha) {
mTextPaint.setTextSize(mTextSize);
mTextPaint.setColor(mUnselectedColor);
mTextPaint.setAlpha(255 - alpha);
canvas.drawText(mText, mIconRect.left + mIconRect.width() / 2
- mTextBound.width() / 2,
mIconRect.bottom + mTextBound.height(), mTextPaint);
}
private void drawTargetText(Canvas canvas, int alpha) {
mTextPaint.setColor(mSelectedColor);
mTextPaint.setAlpha(alpha);
canvas.drawText(mText, mIconRect.left + mIconRect.width() / 2
- mTextBound.width() / 2,
mIconRect.bottom + mTextBound.height(), mTextPaint);
}
繪制的過程大致與兩張圖片相同,不同點(diǎn)就是在繪制圖片的時候Paint設(shè)置 Xfermode,來控制顏色的漸變。
OK,Tab條目的自定義View搞定之后剩下的就簡單多了。
六、定義屬性
接下來就是封裝繼承自LinearLayout的整體控件,先來定義下屬性。

可以看到tabIcons為單張圖片漸變效果特殊的,tabSelectedIcons和tabUnselectedIcon為兩張圖標(biāo)切換效果特殊的。
七、 控件編寫
由于三中樣式有公共的部分,我們進(jìn)行積累抽取。類圖結(jié)構(gòu)如下:

1. 構(gòu)造函數(shù)初始化自定義屬性
在TabIndicatorBase中初始化自定義屬性
private void init(Context context, AttributeSet attrs) {
setOrientation(LinearLayout.HORIZONTAL);
setGravity(Gravity.CENTER);
//Load defaults from resources
final Resources res = getResources();
final int defaultSelectedColor = res.getColor(R.color.default_tab_view_selected_color);
final int defaultUnselectedColor = res.getColor(R.color.default_tab_view_unselected_color);
final float defaultTextSize = res.getDimension(R.dimen.default_tab_view_text_size);
final float defaultTabPadding = res.getDimension(R.dimen.default_tab_view_padding);
final float defaultIndicatorSize = res.getDimension(R.dimen.default_tab_view_indicator_size);
// Styleables from XML
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.TabIndicator);
// 讀取布局中,各個tab使用的文字
if (a.hasValue(R.styleable.TabIndicator_tabLabels)) {
mLabels = a.getTextArray(R.styleable.TabIndicator_tabLabels);
}
mSelectedColor = a.getColor(R.styleable.TabIndicator_tabSelectedColor, defaultSelectedColor);
mUnselectedColor = a.getColor(R.styleable.TabIndicator_tabUnselectedColor, defaultUnselectedColor);
mTextSize = (int) a.getDimension(R.styleable.TabIndicator_tabTextSize, defaultTextSize);
mIndicatorSize = (int) a.getDimension(R.styleable.TabIndicator_TabIndicatorSize, defaultIndicatorSize);
mTabPadding = (int) a.getDimension(R.styleable.TabIndicator_tabItemPadding, defaultTabPadding);
handleStyledAttributes(a);
a.recycle();
initView();
}
由于有些屬性不是公共的,這里定義handleStyleAttributes(a)的抽象方法,在子類中去實現(xiàn)。
2. 初始化View
/**
* 初始化控件
*/
private void initView() {
LayoutParams params = new LayoutParams(0, LayoutParams.MATCH_PARENT, 1);
params.gravity = Gravity.CENTER;
int size = getTabSize();
for (int i = 0; i < size; i++) {
final int index = i;
T tabItemView = createTabView();
tabItemView.setPadding(mTabPadding, mTabPadding, mTabPadding, mTabPadding);
// 圖標(biāo)及文字
if(null != mLabels) {
tabItemView.setText(mLabels[index]);
tabItemView.setTextSize(mTextSize);
}
tabItemView.setSelectedColor(mSelectedColor);
tabItemView.setUnselectedColor(mUnselectedColor);
tabItemView.setIndicatorSize(mIndicatorSize);
setProperties(tabItemView, i);
this.addView(tabItemView, params);
tabItemView.setTag(index); // CheckedTextView設(shè)置索引作為tag,以便后續(xù)更改顏色、圖片等
mCheckedList.add(tabItemView); // 將CheckedTextView添加到list中,便于操作
tabItemView.setOnClickListener(new OnClickListener()
@Override
public void onClick(View v) {
setTabsDisplay(index); // 設(shè)置底部圖片和文字的顯示
if (null != mTabListener) {
mTabListener.onTabSelected(index); // tab項被選中的回調(diào)事件
}
}
});
// 初始化 底部菜單選中狀態(tài),默認(rèn)第一個選中
if (i == 0) {
tabItemView.setSelected(true);
} else {
tabItemView.setSelected(false);
}
}
}
生成Tab條目以及設(shè)置特殊的屬性都通過抽象方法的方式交給子類去完成。
/** * 生成TabView * @return */ protected abstract T createTabView(); /** * 設(shè)置特殊屬性 * @param t */ protected abstract void setProperties(T t, int index);
3. 子類實現(xiàn)
由于這里都比較簡單,我們選取其中一個簡單的雙圖標(biāo)圖片來說明:
@Override
protected void handleStyledAttributes(TypedArray a) {
// 讀取布局中,各個tab使用的圖標(biāo)
int selectedIconsResId = a.getResourceId(R.styleable.TabIndicator_tabSelectedIcons, 0);
TypedArray ta = getContext().getResources().obtainTypedArray(selectedIconsResId);
int len = ta.length();
mSelectedDrawableIds = new int[len];
for(int i = 0; i < len; i++) {
mSelectedDrawableIds[i] = ta.getResourceId(i, 0);
}
int unselectedIconsResId = a.getResourceId(R.styleable.TabIndicator_tabUnselectedIcons, 0);
ta = getContext().getResources().obtainTypedArray(unselectedIconsResId);
len = ta.length();
mUnselectedDrawableIds = new int[len];
for(int i = 0; i < len; i++) {
mUnselectedDrawableIds[i] = ta.getResourceId(i, 0);
}
ta.recycle();
}
這里讀取了xml中配置的選中及未選中圖標(biāo)
生成TabView
@Override
protected TabView createTabView() {
return new TabView(getContext());
}
設(shè)置特殊屬性
@Override
protected void setProperties(TabView tabView, int index) {
tabView.setSelectedIcon(mSelectedDrawableIds[index]);
tabView.setUnselectedIcon(mUnselectedDrawableIds[index]);
}
獲取條目個數(shù)
@Override
protected int getTabSize() {
return mSelectedDrawableIds.length;
}
八、源碼及示例
給大家提供一個github的地址: Android-TabIndicator
另外,歡迎 star or f**k me on github!
九、一行引入庫
如果您的項目使用 Gradle 構(gòu)建, 只需要在您的build.gradle文件添加下面一行到 dependencies :
compile 'com.kevin:tabindicator:1.0.1'
關(guān)于小編給大家分享的Android 底部導(dǎo)航控件實例代碼就到此結(jié)束了,希望對大家有所幫助!
- android效果TapBarMenu繪制底部導(dǎo)航欄的使用方式示例
- Android design包自定義tablayout的底部導(dǎo)航欄的實現(xiàn)方法
- 關(guān)注Ionic底部導(dǎo)航按鈕tabs在android情況下浮在上面的處理
- Android Activity與Fragment實現(xiàn)底部導(dǎo)航器
- Android仿微信頁面底部導(dǎo)航效果代碼實現(xiàn)
- Android 開發(fā)之BottomBar+ViewPager+Fragment實現(xiàn)炫酷的底部導(dǎo)航效果
- Android程序開發(fā)之Fragment實現(xiàn)底部導(dǎo)航欄實例代碼
- Android BottomNavigationBar底部導(dǎo)航控制器使用方法詳解
- Android實現(xiàn)底部導(dǎo)航欄功能(選項卡)
- Android BottomNavigationView底部導(dǎo)航效果
相關(guān)文章
Android測量每秒幀數(shù)Frames Per Second (FPS)的方法
這篇文章主要介紹了Android測量每秒幀數(shù)Frames Per Second (FPS)的方法,涉及Android針對多媒體文件屬性操作的相關(guān)技巧,具有一定參考借鑒價值,需要的朋友可以參考下2015-10-10
Android布局中margin與padding的區(qū)別及說明
這篇文章主要介紹了Android布局中margin與padding的區(qū)別及說明,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-01-01
Android編程添加快捷方式(Short)到手機(jī)桌面的方法(含添加,刪除及查詢)
這篇文章主要介紹了Android編程添加快捷方式(Short)到手機(jī)桌面的方法,含有針對桌面快捷方式的添加,刪除及查詢的操作實現(xiàn)技巧,需要的朋友可以參考下2016-01-01
Android應(yīng)用自動跳轉(zhuǎn)到應(yīng)用市場詳情頁面的方法
最近在工作中遇到一個需求,推廣部門要求實現(xiàn)應(yīng)用自動跳轉(zhuǎn)到應(yīng)用市場詳情頁面,通過查找一些資料,實現(xiàn)出來了,覺得有必要整理下方便以后或者有需要的朋友們參考借鑒,下面來一起詳細(xì)看看Android應(yīng)用自動跳轉(zhuǎn)到應(yīng)用市場詳情頁面的方法吧。2016-12-12
Android編程之客戶端通過socket與服務(wù)器通信的方法
這篇文章主要介紹了Android編程之客戶端通過socket與服務(wù)器通信的方法,結(jié)合實例形式分析了Android基于socket通訊的具體步驟與相關(guān)使用技巧,具有一定參考借鑒價值,需要的朋友可以參考下2015-11-11
Android異步加載數(shù)據(jù)和圖片的保存思路詳解
這篇文章主要介紹了Android異步加載數(shù)據(jù)和圖片的保存思路詳解的相關(guān)資料,需要的朋友可以參考下2016-04-04
Android SQLite數(shù)據(jù)庫加密的操作方法
因為Android自帶的SQLite數(shù)據(jù)庫本身是沒有實現(xiàn)加密的,那我們?nèi)绾螌崿F(xiàn)對數(shù)據(jù)庫的加密呢?今天通過本文給大家介紹下Android SQLite數(shù)據(jù)庫加密的操作方法,一起看看吧2021-09-09

