打造史上最容易使用的Tab指示符——Indicator,tabindicator
打造史上最容易使用的Tab指示符——Indicator,tabindicator
如果你还不知道什么是Tab指示符,相信在你看过网易新闻的这效果后,一定会豁然开朗:‘
就是导航栏下面那个红色的长条,今天我们也来实现一下这效果。。。我们的代码很简单,而且很容易使用,初步统计,一行代码就可以使用这样的indicator。
恩,我项目在还没加这个效果之前用了一个LinearLayout,里面的多个item代码多个tab,那如何添加Indicator呢? 我选择了重写LinearLayout。
先说说我们的思路吧。 其实思路也很简单,就是在咱们的导航下面画一个小矩形,不断的改变这个矩形距离左边的位置。
思路就这么简单,有了思路,接下来就是实现了,看代码:
public class Indicator extends LinearLayout { private Paint mPaint; // 画指示符的paint private int mTop; // 指示符的top private int mLeft; // 指示符的left private int mWidth; // 指示符的width private int mHeight = 5; // 指示符的高度,固定了 private int mColor; // 指示符的颜色 private int mChildCount; // 子item的个数,用于计算指示符的宽度 public Indicator(Context context, AttributeSet attrs) { super(context, attrs); setBackgroundColor(Color.TRANSPARENT); // 必须设置背景,否则onDraw不执行 // 获取自定义属性 指示符的颜色 TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.Indicator, 0, 0); mColor = ta.getColor(R.styleable.Indicator_color, 0X0000FF); ta.recycle(); // 初始化paint mPaint = new Paint(); mPaint.setColor(mColor); mPaint.setAntiAlias(true); } @Override protected void onFinishInflate() { super.onFinishInflate(); mChildCount = getChildCount(); // 获取子item的个数 } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); mTop = getMeasuredHeight(); // 测量的高度即指示符的顶部位置 int width = getMeasuredWidth(); // 获取测量的总宽度 int height = getMeasuredHeight() + mTop + mHeight; // 重新定义一下测量的高度 mWidth = width / mChildCount; // 指示符的宽度为总宽度/item的个数 setMeasuredDimension(width, height); } /** * 指示符滚动 * @param position 现在的位置 * @param offset 偏移量 0 ~ 1 */ public void scroll(int position, float offset) { mLeft = (int) ((position + offset) * mWidth); invalidate(); } @Override protected void onDraw(Canvas canvas) { // 圈出一个矩形 Rect rect = new Rect(mLeft, mTop, mLeft + mWidth, mTop + mHeight); canvas.drawRect(rect, mPaint); // 绘制该矩形 super.onDraw(canvas); } }
代码加上注释60多行,是不是很简单? 下面我们来分析一下代码。
先来看看构造方法。
public Indicator(Context context, AttributeSet attrs) { super(context, attrs); setBackgroundColor(Color.TRANSPARENT); // 必须设置背景,否则onDraw不执行 // 获取自定义属性 指示符的颜色 TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.Indicator, 0, 0); mColor = ta.getColor(R.styleable.Indicator_color, 0X0000FF); ta.recycle(); // 初始化paint mPaint = new Paint(); mPaint.setColor(mColor); mPaint.setAntiAlias(true); }第三行需要注意下,我们在自定义的LinearLayout设置了一个背景,而且注释写着必须设置背景,这是为什么呢? 因为ViewGroup默认是不走onDraw方法的,因为ViewGroup是不需要绘制的,需要绘制的是ViewGroup的子item,这里我们设置一下背景颜色,ViewGroup就会走onDraw方法去绘制它自己的背景,那么我们需要onDraw吗? 当然需要,我们要在onDraw中绘制指示符。
接下来的三行代码就是获取咱们的自定义属性,也就是指示符的颜色,再继续,初始化了绘制指示符的paint。
@Override protected void onFinishInflate() { super.onFinishInflate(); mChildCount = getChildCount(); // 获取子item的个数 }在onFinishLayout方法中,我们做的工作也很简单,就是获取子item的个数,因为我们需要根据LinearLayout的宽度和子item的个数来确定指示符的宽度。
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); mTop = getMeasuredHeight(); // 测量的高度即指示符的顶部位置 int width = getMeasuredWidth(); // 获取测量的总宽度 int height = getMeasuredHeight() + mTop + mHeight; // 重新定义一下测量的高度 mWidth = width / mChildCount; // 指示符的宽度为总宽度/item的个数 setMeasuredDimension(width, height); }
继续走,在onMeasure方法中,首先我们调用了父类的onMeasure,目的就是让他调用默认的代码去测量,接下来我们通过getMeasuredHeight获取测量的高度,该高度对我们有什么用处呢? 我的指示符的top值就是测量的高度。再往下走,获取测量的宽度,并且重新定义了测量的高度,因为我们要把指示符的高度也加上。width/mChildCount上面我们提过,是要计算指示符的宽度,最后我们把调整后的height值保存起来,让它默认去layout吧。
接下来的一个自定义方法,我们先不说,先来看看onDraw方法。
@Override protected void onDraw(Canvas canvas) { // 圈出一个矩形 Rect rect = new Rect(mLeft, mTop, mLeft + mWidth, mTop + mHeight); canvas.drawRect(rect, mPaint); // 绘制该矩形 super.onDraw(canvas); }
在onDraw中我们要做的工作就更容易了,就是找位置把我们的指示符画上,可以看到,指示符我们使用了一个矩形,left的值是我们要在外面不断改变的。
最后再看看自定义个那个scroll方法。
/** * 指示符滚动 * @param position 现在的位置 * @param offset 偏移量 0 ~ 1 */ public void scroll(int position, float offset) { mLeft = (int) ((position + offset) * mWidth); invalidate(); }接受两个参数,其实就是对应ViewPager.OnPageChangeListener.onPageScrolled(int position, float positionOffset, int positionOffsetPixels)前两个参数,这里我们尽量把工作都交给了咱们的Indicator,如此一来外面用起来就相当方便了。
那么我们都是做了哪些工作呢?1、计算矩形left的值,2、重绘。
看看left的值是如何计算的,position和offset相加再乘指示符的宽度,为什么呢? 想想,position的值是值当前ViewPager显示第几页,也就是当前是第几个tab,offset指的是从当前页偏移了百分之几,也就是说偏移量是一个0~1的值,这样(position + offset) * mWidth的结果也巧好就是我们需要的矩形的left的值。
至此,我们自定义个Indicator就完成了,来使用一下试试吧:
首先在布局文件中:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:indicator="http://schemas.android.com/apk/res/org.loader.indicatortest" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" tools:context=".MainActivity" > <org.loader.indicatortest.Indicator android:id="@+id/indicator" android:layout_width="match_parent" android:layout_height="wrap_content" android:paddingBottom="10dip" android:paddingTop="10dip" android:weightSum="4" indicator:color="#FFFF0000" > <TextView android:id="@+id/tab_one" android:layout_width="0dip" android:layout_height="wrap_content" android:layout_weight="1" android:gravity="center_horizontal" android:text="TAB1" /> <TextView android:id="@+id/tab_two" android:layout_width="0dip" android:layout_height="wrap_content" android:layout_weight="1" android:gravity="center_horizontal" android:text="TAB2" /> <TextView android:id="@+id/tab_three" android:layout_width="0dip" android:layout_height="wrap_content" android:layout_weight="1" android:gravity="center_horizontal" android:text="TAB3" /> <TextView android:id="@+id/tab_four" android:layout_width="0dip" android:layout_height="wrap_content" android:layout_weight="1" android:gravity="center_horizontal" android:text="TAB4" /> </org.loader.indicatortest.Indicator> <android.support.v4.view.ViewPager android:id="@+id/container" android:layout_width="match_parent" android:layout_height="wrap_content"/> </LinearLayout>
首先定义了一系列的“tab”, 接下来就是一个ViewPager,再来看看Activity中。
public class MainActivity extends Activity implements OnClickListener { private Indicator mIndicator; private TextView mTabOne; private TextView mTabTwo; private TextView mTabThree; private TextView mTabFour; private ViewPager mContainer; private ArrayList<TextView> mViews = new ArrayList<TextView>(4); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mIndicator = (Indicator) findViewById(R.id.indicator); mContainer = (ViewPager) findViewById(R.id.container); mTabOne = (TextView) findViewById(R.id.tab_one); mTabTwo = (TextView) findViewById(R.id.tab_two); mTabThree = (TextView) findViewById(R.id.tab_three); mTabFour = (TextView) findViewById(R.id.tab_four); mTabOne.setOnClickListener(this); mTabTwo.setOnClickListener(this); mTabThree.setOnClickListener(this); mTabFour.setOnClickListener(this); initViews(); mContainer.setAdapter(new PagerAdapter() { @Override public boolean isViewFromObject(View arg0, Object arg1) { return arg0 == arg1; } @Override public int getCount() { return mViews.size(); } @Override public Object instantiateItem(ViewGroup container, int position) { View view = mViews.get(position); container.addView(view); return view; } @Override public void destroyItem(ViewGroup container, int position, Object object) { container.removeView(mViews.get(position)); } }); mContainer.setOnPageChangeListener(new OnPageChangeListener() { @Override public void onPageSelected(int position) { } @Override public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { mIndicator.scroll(position, positionOffset); } @Override public void onPageScrollStateChanged(int position) { } }); } private void initViews() { for(int i=0;i<4;i++) { TextView tv = new TextView(this); tv.setText("hello android" + i); mViews.add(tv); } } @Override public void onClick(View v) { switch (v.getId()) { case R.id.tab_one: mContainer.setCurrentItem(0); break; case R.id.tab_two: mContainer.setCurrentItem(1); break; case R.id.tab_three: mContainer.setCurrentItem(2); break; case R.id.tab_four: mContainer.setCurrentItem(3); break; } } }
好吧,写的很混乱,挑重点看,其实重点就一行代码,在开始的地方我也说过了,我们用一行代码就可以使用。
看onPageScrolled中的一行代码:
mIndicator.scroll(position, positionOffset);
ok,就是这一行代码,控制了我们的指示符的滑动。
最后来看看效果图吧:
是不是很方便,也很简单? 以后我们只需要将Indicator拷到项目中,一行代码就可以搞定这种酷炫的tab效果了。