Android自定义的TabBar

简介:

http://www.oschina.net/code/snippet_731007_24519

Android自定义的TabBar,自定义了几个属性,可以在xml布局文件中使用,Tab的标题、图标等属性可以在布局文件中完成配置,Java代码中只需要指定Tab跳转的监听接口。可以在所有Android版本使用,建议配合ViewPager使用。

attrs.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="TabBar">
        <attr name="icons" format="reference" />
        <!-- icons是一个存放drawable资源文件名的数组 -->
        <attr name="titles" format="reference" />
        <!-- titles是一个存放string的数组 -->
        <attr name="IconAboveTitle" format="boolean" />
        <!-- 允许指定分隔线的drawable -->
        <attr name="Seperator" format="reference" />
    </declare-styleable> 
</resources>

TabBar.java

package com.pupa.common.widget;

import com.pupa.common.util.StringHelper;
import com.pupa.TabBarDemo.R;

import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.Display;
import android.view.Gravity;
import android.view.View;
import android.view.WindowManager;
import android.widget.ImageView;
import android.widget.ImageView.ScaleType;
import android.widget.LinearLayout;
import android.widget.TextView;

public class TabBar extends LinearLayout {
	private static final String TAG = "TabBar";
	private final TabBar mTabBar; // 自己
	private final Context mContext;
	private final AttributeSet mAttrs;
	private int mDefStyle;

	private Paint mPaint;
	private int mCurrentTabMaskColor;
	private int mCurrentTabMaskAlpha;

	private String mResPackageName;
	private CharSequence[] mTitles;
	private CharSequence[] mIconNames;
	private Drawable[] mIcons;
	private Drawable mSeperator;
	private int mSeperatorResId;
	private int mSeperatorWidth;

	private boolean mIconAboveTitle;

	private int mCurrentTabIndex;
	private int mTabCount;
	private int mTabWidth;
	private int mPosition;
	private OnCurrentTabChangedListener mTabChangedListener;

	public static final int POSITION_TOP = 1; // 位于顶部或者底部
	public static final int POSITION_BOTTOM = 2;

	public TabBar(Context context) {
		this(context, null);
		// TODO Auto-generated constructor stub
	}

	public TabBar(Context context, AttributeSet attrs) {
		super(context, attrs);
		setWillNotDraw(false); // 重要!!!
		mContext = context;
		mAttrs = attrs;
		mCurrentTabIndex = -1;
		mTabCount = 0;
		mSeperatorWidth = 0;
		mPosition = POSITION_TOP;
		mCurrentTabMaskColor = Color.BLACK;
		mCurrentTabMaskAlpha = 0x5f;
		mPaint = new Paint();
		mTabBar = this;
		init();
		// TODO Auto-generated constructor stub
	}
	
	@SuppressWarnings("deprecation")
	public void init() {
		getResourcesFromXml();
		this.setOrientation(LinearLayout.HORIZONTAL);
		this.setPadding(0, 0, 0, 0);

		WindowManager wm = (WindowManager) mContext
				.getSystemService(Context.WINDOW_SERVICE);
		Display dp = wm.getDefaultDisplay();
		mTabWidth = dp.getWidth();

		mTabCount = mTitles.length;

		if (mTabCount > 0) {
			if (mSeperator != null) {
				Bitmap bmp = BitmapFactory.decodeResource(getResources(),
						mSeperatorResId);
				mSeperatorWidth = bmp.getWidth();
				mTabWidth = mTabWidth - (mTabCount - 1) * mSeperatorWidth;
				bmp.recycle();
				bmp = null;
			}
			mTabWidth = mTabWidth / mTabCount; // 计算每个tab的宽度
			mCurrentTabIndex = 0;
		}

		LayoutParams inParams = new LayoutParams(LayoutParams.MATCH_PARENT,
				LayoutParams.WRAP_CONTENT);
		LayoutParams outParams = new LayoutParams(mTabWidth,
				LayoutParams.WRAP_CONTENT);
		LayoutParams params = new LayoutParams(LayoutParams.WRAP_CONTENT,
				LayoutParams.WRAP_CONTENT);
		View.OnClickListener clkListener = new View.OnClickListener() {

			@Override
			public void onClick(View v) {
				// TODO Auto-generated method stub
				int index = (Integer) v.getTag();
				if (index != mCurrentTabIndex) {
					if (mTabChangedListener != null)
						mTabChangedListener.onCurrentTabChanged(index);
					mCurrentTabIndex = index;
					mTabBar.invalidate();
				}
			}
		};
		
		//逐个添加Tab
		for (int i = 0; i < mTabCount; ++i) {
			LinearLayout tab = new LinearLayout(mContext);
			tab.setOrientation(LinearLayout.VERTICAL);
			tab.setPadding(0, 0, 0, 0);
			tab.setTag(i); // 设置内部标号
			tab.setClickable(true);
			ImageView imv = new ImageView(mContext);
			imv.setScaleType(ScaleType.CENTER);
			if (i < mIcons.length)
				imv.setImageDrawable(mIcons[i]);
			TextView tv = new TextView(mContext);
			tv.setGravity(Gravity.CENTER_HORIZONTAL);
			tv.setText(mTitles[i]);
			if (mIconAboveTitle) { // 图标在标题之上
				tab.addView(imv, inParams);
				tab.addView(tv, inParams);
			} else { // 标题在图标之上
				tab.addView(tv, inParams);
				tab.addView(imv, inParams);
			}
			tab.setOnClickListener(clkListener);
			this.addView(tab, outParams);
			if (mSeperator != null && i < mTabCount - 1) {
				ImageView sep = new ImageView(mContext);
				sep.setImageDrawable(mSeperator);
				this.addView(sep, params);
			}
		}
	}

	/**
	 * 设置当前Tab的序号
	 * 
	 * @param index
	 *            你想指定的Tab的序号
	 */
	public void setCurrentTab(int index) {
		if (index > -1 && index < mTabCount&&index!=mCurrentTabIndex) {
			mCurrentTabIndex = index;
			this.invalidate();
			if (mTabChangedListener != null)
				mTabChangedListener.onCurrentTabChanged(mCurrentTabIndex);
		}
	}

	public void setOnCurrentTabChangedListener(
			OnCurrentTabChangedListener listener) {
		mTabChangedListener = listener;
	}

	/**
	 * 设置TabBar在顶端还是底端.真实位置由你的Activity的布局文件决定,这里仅仅是作一个标识, 根据这个信息可以增加一些自定义的效果
	 * 
	 * @param i
	 *            顶端TabBar.POSITION_TOP或底端TabBar.POSITION_BOTTOM
	 */
	public void setTabBarPosition(int i) {
		mPosition = i;
	}

	/**
	 * 设定工程中R.java文件的包名,因为在解析出各个Tab的icon时要用到。如果是默认值则无需指定
	 * 
	 * @param name
	 *            R.java文件的包名
	 */
	public void setResourcesPackageName(String name) {
		mResPackageName = name;
	}

	/**
	 * 设置Tab选中后的颜色,默认alpha为0x5f
	 * 
	 * @param c
	 *            rgb颜色值
	 */
	public void setCurrentTabMaskColor(int rgb) {
		mCurrentTabMaskColor = rgb;
	}

	/**
	 * 设置Tab选中后的颜色.为什么要重载这个方法呢?因为我总是记不住Alpha值0和255谁是全透明, 于是宁愿把ARGB颜色中A跟RGB分开设置。。
	 * 
	 * @param rgb
	 *            rgb颜色值
	 * @param a
	 *            alpha值
	 */
	public void setCurrentTabMaskColor(int rgb, int a) {
		mCurrentTabMaskColor = rgb;
		mCurrentTabMaskAlpha = a;
	}

	/**
	 * 获取Tab个数
	 * 
	 * @return Tab个数
	 */
	public int getTabCount() {
		return mTabCount;
	}

	@Override
	protected void onDraw(Canvas canvas) {
		super.onDraw(canvas);
		int h = this.getHeight();
		if (mCurrentTabIndex > -1 && mCurrentTabIndex < mTabCount) {
			int startX = (mTabWidth + mSeperatorWidth) * mCurrentTabIndex;
			mPaint.setColor(mCurrentTabMaskColor);
			mPaint.setAlpha(mCurrentTabMaskAlpha);
			mPaint.setStyle(Paint.Style.FILL);
			canvas.drawRect(new Rect(startX, 0, startX + mTabWidth, h), mPaint);
		}
	}

	/**
	 * 从布局文件的属性值中解析出各个资源
	 */
	private void getResourcesFromXml() {
		TypedArray ta = mContext.obtainStyledAttributes(mAttrs,
				R.styleable.TabBar, 0, 0);
		mIconNames = ta.getTextArray(R.styleable.TabBar_icons);
		mTitles = ta.getTextArray(R.styleable.TabBar_titles);
		mIconAboveTitle = ta
				.getBoolean(R.styleable.TabBar_IconAboveTitle, true);
		mSeperator = ta.getDrawable(R.styleable.TabBar_Seperator);
		mSeperatorResId = ta.getResourceId(R.styleable.TabBar_Seperator, -1);

		if (!StringHelper.notNullAndNotEmpty(mResPackageName))
			mResPackageName = mContext.getPackageName();

		if (mTitles == null) {
			mTitles = new CharSequence[0]; // 避免为null
		}

		if (mIconNames == null) {
			mIconNames = new CharSequence[0]; // 避免为null
		}

		Resources res = mContext.getResources();
		mIcons = new Drawable[mIconNames.length];
		for (int i = 0; i < mIconNames.length; ++i) {
			int id = res.getIdentifier(mIconNames[i].toString(), "drawable",
					mResPackageName);
			if (id != 0)
				mIcons[i] = res.getDrawable(id);
		}

		ta.recycle();
	}

	public interface OnCurrentTabChangedListener {
		public void onCurrentTabChanged(int index);
	}
}

arrays.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>

    <string-array name="tab_titles">
        <item>Tab1</item>
        <item>Tab2</item>
    </string-array>
    <string-array name="tab_icons">
        <item>tab1_icon</item>
        <item>tab2_icon</item>
    </string-array>

</resources>

activity_main.xml

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:pupa="http://schemas.android.com/apk/res/com.pupa.TabDemo"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity" >

    <com.pupa.common.widget.TabBar
        android:id="@+id/MainTabBar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentTop="true"
        android:background="@android:drawable/title_bar"
        pupa:Seperator="@drawable/tab_seperator"
        pupa:icons="@array/tab_icons"
        pupa:titles="@array/tab_titles" >
    </com.pupa.common.widget.TabBar>

    <android.support.v4.view.ViewPager
        android:id="@+id/MainViewPager"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_below="@id/MainTabBar" />

</RelativeLayout>

TabBarDemo.zip ~ 703KB     下载


相关文章
|
4月前
|
缓存 前端开发 Android开发
安卓开发中的自定义视图:从零到英雄
【10月更文挑战第42天】 在安卓的世界里,自定义视图是一块画布,让开发者能够绘制出独一无二的界面体验。本文将带你走进自定义视图的大门,通过深入浅出的方式,让你从零基础到能够独立设计并实现复杂的自定义组件。我们将探索自定义视图的核心概念、实现步骤,以及如何优化你的视图以提高性能和兼容性。准备好了吗?让我们开始这段创造性的旅程吧!
77 1
|
5月前
|
Android开发 开发者
安卓应用开发中的自定义视图
【9月更文挑战第37天】在安卓开发的海洋中,自定义视图犹如一座座小岛,等待着勇敢的探索者去发现其独特之处。本文将带领你踏上这段旅程,从浅滩走向深海,逐步揭开自定义视图的神秘面纱。
65 3
|
7月前
|
存储 Shell Android开发
基于Android P,自定义Android开机动画的方法
本文详细介绍了基于Android P系统自定义开机动画的步骤,包括动画文件结构、脚本编写、ZIP打包方法以及如何将自定义动画集成到AOSP源码中。
172 2
基于Android P,自定义Android开机动画的方法
|
5月前
|
数据可视化 Android开发 开发者
安卓应用开发中的自定义View组件
【10月更文挑战第5天】在安卓应用开发中,自定义View组件是提升用户交互体验的利器。本篇将深入探讨如何从零开始创建自定义View,包括设计理念、实现步骤以及性能优化技巧,帮助开发者打造流畅且富有创意的用户界面。
182 0
|
7月前
|
供应链 物联网 区块链
未来触手可及:探索新兴技术的趋势与应用安卓开发中的自定义视图:从基础到进阶
【8月更文挑战第30天】随着科技的飞速发展,新兴技术如区块链、物联网和虚拟现实正在重塑我们的世界。本文将深入探讨这些技术的发展趋势和应用场景,带你领略未来的可能性。
|
7月前
|
测试技术 Android开发 Python
探索软件测试的艺术:从基础到高级安卓应用开发中的自定义视图
【8月更文挑战第29天】在软件开发的世界中,测试是不可或缺的一环。它如同艺术一般,需要精细的技巧和深厚的知识。本文旨在通过浅显易懂的语言,引领读者从软件测试的基础出发,逐步深入到更复杂的测试策略和工具的使用,最终达到能够独立进行高效测试的水平。我们将一起探索如何通过不同的测试方法来确保软件的质量和性能,就像艺术家通过不同的色彩和笔触来完成一幅画作一样。
|
4月前
|
搜索推荐 前端开发 Android开发
安卓应用开发中的自定义视图实现
【10月更文挑战第30天】在安卓开发的海洋中,自定义视图是那抹不可或缺的亮色,它为应用界面的个性化和交互体验的提升提供了无限可能。本文将深入探讨如何在安卓平台创建自定义视图,并展示如何通过代码实现这一过程。我们将从基础出发,逐步引导你理解自定义视图的核心概念,然后通过一个实际的代码示例,详细讲解如何将理论应用于实践,最终实现一个美观且具有良好用户体验的自定义控件。无论你是想提高自己的开发技能,还是仅仅出于对安卓开发的兴趣,这篇文章都将为你提供价值。
|
4月前
|
Android开发 开发者 UED
安卓开发中自定义View的实现与性能优化
【10月更文挑战第28天】在安卓开发领域,自定义View是提升应用界面独特性和用户体验的重要手段。本文将深入探讨如何高效地创建和管理自定义View,以及如何通过代码和性能调优来确保流畅的交互体验。我们将一起学习自定义View的生命周期、绘图基础和事件处理,进而探索内存和布局优化技巧,最终实现既美观又高效的安卓界面。
81 5
|
5月前
|
XML 前端开发 Java
安卓应用开发中的自定义View组件
【10月更文挑战第5天】自定义View是安卓应用开发的一块基石,它为开发者提供了无限的可能。通过掌握其原理和实现方法,可以创造出既美观又实用的用户界面。本文将引导你了解自定义View的创建过程,包括绘制技巧、事件处理以及性能优化等关键步骤。
|
6月前
|
Android开发 开发者
安卓开发中的自定义视图:从入门到精通
【9月更文挑战第19天】在安卓开发的广阔天地中,自定义视图是一块充满魔力的土地。它不仅仅是代码的堆砌,更是艺术与科技的完美结合。通过掌握自定义视图,开发者能够打破常规,创造出独一无二的用户界面。本文将带你走进自定义视图的世界,从基础概念到实战应用,一步步展示如何用代码绘出心中的蓝图。无论你是初学者还是有经验的开发者,这篇文章都将为你打开一扇通往创意和效率的大门。让我们一起探索自定义视图的秘密,将你的应用打造成一件艺术品吧!
103 10

热门文章

最新文章