打造史上最容易使用的Tab指示符——Indicator

来源:互联网 发布:淘宝怎么弄天猫积分 编辑:程序博客网 时间:2024/05/24 02:09

转载请注明出处:http://blog.csdn.net/qibin0506/article/details/42046559

如果你还不知道什么是Tab指示符,相信在你看过网易新闻的这效果后,一定会豁然开朗:‘


就是导航栏下面那个红色的长条,今天我们也来实现一下这效果。。。我们的代码很简单,而且很容易使用,初步统计,一行代码就可以使用这样的indicator。

恩,我项目在还没加这个效果之前用了一个LinearLayout,里面的多个item代码多个tab,那如何添加Indicator呢? 我选择了重写LinearLayout。

先说说我们的思路吧。 其实思路也很简单,就是在咱们的导航下面画一个小矩形,不断的改变这个矩形距离左边的位置。

思路就这么简单,有了思路,接下来就是实现了,看代码:

[java] view plaincopy
  1. public class Indicator extends LinearLayout {  
  2.     private Paint mPaint; // 画指示符的paint  
  3.       
  4.     private int mTop; // 指示符的top  
  5.     private int mLeft; // 指示符的left  
  6.     private int mWidth; // 指示符的width  
  7.     private int mHeight = 5// 指示符的高度,固定了  
  8.     private int mColor; // 指示符的颜色  
  9.     private int mChildCount; // 子item的个数,用于计算指示符的宽度  
  10.       
  11.     public Indicator(Context context, AttributeSet attrs) {  
  12.         super(context, attrs);  
  13.         setBackgroundColor(Color.TRANSPARENT);  // 必须设置背景,否则onDraw不执行  
  14.           
  15.         // 获取自定义属性 指示符的颜色  
  16.         TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.Indicator, 00);  
  17.         mColor = ta.getColor(R.styleable.Indicator_color, 0X0000FF);  
  18.         ta.recycle();  
  19.           
  20.         // 初始化paint  
  21.         mPaint = new Paint();  
  22.         mPaint.setColor(mColor);  
  23.         mPaint.setAntiAlias(true);  
  24.     }  
  25.       
  26.     @Override  
  27.     protected void onFinishInflate() {  
  28.         super.onFinishInflate();  
  29.         mChildCount = getChildCount();  // 获取子item的个数  
  30.     }  
  31.       
  32.     @Override  
  33.     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  
  34.         super.onMeasure(widthMeasureSpec, heightMeasureSpec);  
  35.         mTop = getMeasuredHeight(); // 测量的高度即指示符的顶部位置  
  36.         int width = getMeasuredWidth(); // 获取测量的总宽度  
  37.         int height = mTop + mHeight; // 重新定义一下测量的高度  
  38.         mWidth = width / mChildCount; // 指示符的宽度为总宽度/item的个数  
  39.           
  40.         setMeasuredDimension(width, height);  
  41.     }  
  42.       
  43.     /** 
  44.      * 指示符滚动 
  45.      * @param position 现在的位置 
  46.      * @param offset  偏移量 0 ~ 1 
  47.      */  
  48.     public void scroll(int position, float offset) {  
  49.         mLeft = (int) ((position + offset) * mWidth);  
  50.         invalidate();  
  51.     }  
  52.       
  53.     @Override  
  54.     protected void onDraw(Canvas canvas) {  
  55.         // 圈出一个矩形  
  56.         Rect rect = new Rect(mLeft, mTop, mLeft + mWidth, mTop + mHeight);  
  57.         canvas.drawRect(rect, mPaint); // 绘制该矩形  
  58.         super.onDraw(canvas);  
  59.     }  
  60. }  

代码加上注释60多行,是不是很简单?  下面我们来分析一下代码。

先来看看构造方法。

[java] view plaincopy
  1. public Indicator(Context context, AttributeSet attrs) {  
  2.     super(context, attrs);  
  3.     setBackgroundColor(Color.TRANSPARENT);  // 必须设置背景,否则onDraw不执行  
  4.           
  5.     // 获取自定义属性 指示符的颜色  
  6.     TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.Indicator, 00);  
  7.     mColor = ta.getColor(R.styleable.Indicator_color, 0X0000FF);  
  8.     ta.recycle();  
  9.           
  10.     // 初始化paint  
  11.     mPaint = new Paint();  
  12.     mPaint.setColor(mColor);  
  13.     mPaint.setAntiAlias(true);  
  14. }  
第三行需要注意下,我们在自定义的LinearLayout设置了一个背景,而且注释写着必须设置背景,这是为什么呢? 因为ViewGroup默认是不走onDraw方法的,因为ViewGroup是不需要绘制的,需要绘制的是ViewGroup的子item,这里我们设置一下背景颜色,ViewGroup就会走onDraw方法去绘制它自己的背景,那么我们需要onDraw吗? 当然需要,我们要在onDraw中绘制指示符。

接下来的三行代码就是获取咱们的自定义属性,也就是指示符的颜色,再继续,初始化了绘制指示符的paint。

[java] view plaincopy
  1. @Override  
  2. protected void onFinishInflate() {  
  3.     super.onFinishInflate();  
  4.     mChildCount = getChildCount();  // 获取子item的个数  
  5. }  
在onFinishLayout方法中,我们做的工作也很简单,就是获取子item的个数,因为我们需要根据LinearLayout的宽度和子item的个数来确定指示符的宽度。

[java] view plaincopy
  1. @Override  
  2. protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  
  3.     super.onMeasure(widthMeasureSpec, heightMeasureSpec);  
  4.     mTop = getMeasuredHeight(); // 测量的高度即指示符的顶部位置  
  5.     int width = getMeasuredWidth(); // 获取测量的总宽度  
  6.     int height = mTop + mHeight; // 重新定义一下测量的高度  
  7.     mWidth = width / mChildCount; // 指示符的宽度为总宽度/item的个数  
  8.           
  9.     setMeasuredDimension(width, height);  
  10. }  

继续走,在onMeasure方法中,首先我们调用了父类的onMeasure,目的就是让他调用默认的代码去测量,接下来我们通过getMeasuredHeight获取测量的高度,该高度对我们有什么用处呢? 我的指示符的top值就是测量的高度。再往下走,获取测量的宽度,并且重新定义了测量的高度,因为我们要把指示符的高度也加上。width/mChildCount上面我们提过,是要计算指示符的宽度,最后我们把调整后的height值保存起来,让它默认去layout吧。

接下来的一个自定义方法,我们先不说,先来看看onDraw方法。

[java] view plaincopy
  1. @Override  
  2. protected void onDraw(Canvas canvas) {  
  3.     // 圈出一个矩形  
  4.     Rect rect = new Rect(mLeft, mTop, mLeft + mWidth, mTop + mHeight);  
  5.     canvas.drawRect(rect, mPaint); // 绘制该矩形  
  6.     super.onDraw(canvas);  
  7. }  

在onDraw中我们要做的工作就更容易了,就是找位置把我们的指示符画上,可以看到,指示符我们使用了一个矩形,left的值是我们要在外面不断改变的。

最后再看看自定义个那个scroll方法。

[java] view plaincopy
  1. /** 
  2.  * 指示符滚动 
  3. * @param position 现在的位置 
  4.  * @param offset  偏移量 0 ~ 1 
  5.  */  
  6. public void scroll(int position, float offset) {  
  7.     mLeft = (int) ((position + offset) * mWidth);  
  8.     invalidate();  
  9. }  
接受两个参数,其实就是对应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就完成了,来使用一下试试吧:

首先在布局文件中:

[html] view plaincopy
  1. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  2.     xmlns:indicator="http://schemas.android.com/apk/res/org.loader.indicatortest"  
  3.     xmlns:tools="http://schemas.android.com/tools"  
  4.     android:layout_width="match_parent"  
  5.     android:layout_height="match_parent"  
  6.     android:orientation="vertical"  
  7.     tools:context=".MainActivity" >  
  8.   
  9.     <org.loader.indicatortest.Indicator  
  10.         android:id="@+id/indicator"  
  11.         android:layout_width="match_parent"  
  12.         android:layout_height="wrap_content"  
  13.         android:paddingBottom="10dip"  
  14.         android:paddingTop="10dip"  
  15.         android:weightSum="4"  
  16.         indicator:color="#FFFF0000" >  
  17.   
  18.         <TextView  
  19.             android:id="@+id/tab_one"  
  20.             android:layout_width="0dip"  
  21.             android:layout_height="wrap_content"  
  22.             android:layout_weight="1"  
  23.             android:gravity="center_horizontal"  
  24.             android:text="TAB1" />  
  25.   
  26.         <TextView  
  27.             android:id="@+id/tab_two"  
  28.             android:layout_width="0dip"  
  29.             android:layout_height="wrap_content"  
  30.             android:layout_weight="1"  
  31.             android:gravity="center_horizontal"  
  32.             android:text="TAB2" />  
  33.   
  34.         <TextView  
  35.             android:id="@+id/tab_three"  
  36.             android:layout_width="0dip"  
  37.             android:layout_height="wrap_content"  
  38.             android:layout_weight="1"  
  39.             android:gravity="center_horizontal"  
  40.             android:text="TAB3" />  
  41.   
  42.         <TextView  
  43.             android:id="@+id/tab_four"  
  44.             android:layout_width="0dip"  
  45.             android:layout_height="wrap_content"  
  46.             android:layout_weight="1"  
  47.             android:gravity="center_horizontal"  
  48.             android:text="TAB4" />  
  49.     </org.loader.indicatortest.Indicator>  
  50.   
  51.     <android.support.v4.view.ViewPager  
  52.         android:id="@+id/container"  
  53.         android:layout_width="match_parent"  
  54.         android:layout_height="wrap_content"/>  
  55. </LinearLayout>  

首先定义了一系列的“tab”, 接下来就是一个ViewPager,再来看看Activity中。

[java] view plaincopy
  1. public class MainActivity extends Activity implements OnClickListener {  
  2.     private Indicator mIndicator;  
  3.     private TextView mTabOne;  
  4.     private TextView mTabTwo;  
  5.     private TextView mTabThree;  
  6.     private TextView mTabFour;  
  7.     private ViewPager mContainer;  
  8.       
  9.     private ArrayList<TextView> mViews = new ArrayList<TextView>(4);  
  10.       
  11.     @Override  
  12.     protected void onCreate(Bundle savedInstanceState) {  
  13.         super.onCreate(savedInstanceState);  
  14.         setContentView(R.layout.activity_main);  
  15.           
  16.         mIndicator = (Indicator) findViewById(R.id.indicator);  
  17.         mContainer = (ViewPager) findViewById(R.id.container);  
  18.           
  19.         mTabOne = (TextView) findViewById(R.id.tab_one);  
  20.         mTabTwo = (TextView) findViewById(R.id.tab_two);  
  21.         mTabThree = (TextView) findViewById(R.id.tab_three);  
  22.         mTabFour = (TextView) findViewById(R.id.tab_four);  
  23.           
  24.         mTabOne.setOnClickListener(this);  
  25.         mTabTwo.setOnClickListener(this);  
  26.         mTabThree.setOnClickListener(this);  
  27.         mTabFour.setOnClickListener(this);  
  28.           
  29.         initViews();  
  30.         mContainer.setAdapter(new PagerAdapter() {  
  31.             @Override  
  32.             public boolean isViewFromObject(View arg0, Object arg1) {  
  33.                 return arg0 == arg1;  
  34.             }  
  35.               
  36.             @Override  
  37.             public int getCount() {  
  38.                 return mViews.size();  
  39.             }  
  40.               
  41.             @Override  
  42.             public Object instantiateItem(ViewGroup container, int position) {  
  43.                 View view = mViews.get(position);  
  44.                 container.addView(view);  
  45.                 return view;  
  46.             }  
  47.               
  48.             @Override  
  49.             public void destroyItem(ViewGroup container, int position,  
  50.                     Object object) {  
  51.                 container.removeView(mViews.get(position));  
  52.             }  
  53.         });  
  54.           
  55.         mContainer.setOnPageChangeListener(new OnPageChangeListener() {  
  56.             @Override  
  57.             public void onPageSelected(int position) {  
  58.             }  
  59.               
  60.             @Override  
  61.             public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {  
  62.                 mIndicator.scroll(position, positionOffset);  
  63.             }  
  64.               
  65.             @Override  
  66.             public void onPageScrollStateChanged(int position) {  
  67.                   
  68.             }  
  69.         });  
  70.     }  
  71.       
  72.     private void initViews() {  
  73.         for(int i=0;i<4;i++) {  
  74.             TextView tv = new TextView(this);  
  75.             tv.setText("hello android" + i);  
  76.             mViews.add(tv);  
  77.         }  
  78.     }  
  79.   
  80.     @Override  
  81.     public void onClick(View v) {  
  82.         switch (v.getId()) {  
  83.         case R.id.tab_one:  
  84.             mContainer.setCurrentItem(0);  
  85.             break;  
  86.         case R.id.tab_two:  
  87.             mContainer.setCurrentItem(1);  
  88.             break;  
  89.         case R.id.tab_three:  
  90.             mContainer.setCurrentItem(2);  
  91.             break;  
  92.         case R.id.tab_four:  
  93.             mContainer.setCurrentItem(3);  
  94.             break;  
  95.         }  
  96.     }  
  97. }  

好吧,写的很混乱,挑重点看,其实重点就一行代码,在开始的地方我也说过了,我们用一行代码就可以使用。

看onPageScrolled中的一行代码:

[java] view plaincopy
  1. mIndicator.scroll(position, positionOffset);  

ok,就是这一行代码,控制了我们的指示符的滑动。

最后来看看效果图吧:


是不是很方便,也很简单? 以后我们只需要将Indicator拷到项目中,一行代码就可以搞定这种酷炫的tab效果了。

0 0