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

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

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

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

先来看看构造方法。

  1. public Indicator(Context context, AttributeSet attrs) {
  2. super(context, attrs);
  3. setBackgroundColor(Color.TRANSPARENT);  // 必须设置背景,否则onDraw不执行
  4. // 获取自定义属性 指示符的颜色
  5. TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.Indicator, 0, 0);
  6. mColor = ta.getColor(R.styleable.Indicator_color, 0X0000FF);
  7. ta.recycle();
  8. // 初始化paint
  9. mPaint = new Paint();
  10. mPaint.setColor(mColor);
  11. mPaint.setAntiAlias(true);
  12. }

第三行需要注意下,我们在自定义的LinearLayout设置了一个背景,而且注释写着必须设置背景,这是为什么呢? 因为ViewGroup默认是不走onDraw方法的,因为ViewGroup是不需要绘制的,需要绘制的是ViewGroup的子item,这里我们设置一下背景颜色,ViewGroup就会走onDraw方法去绘制它自己的背景,那么我们需要onDraw吗? 当然需要,我们要在onDraw中绘制指示符。

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

  1. @Override
  2. protected void onFinishInflate() {
  3. super.onFinishInflate();
  4. mChildCount = getChildCount();  // 获取子item的个数
  5. }

在onFinishLayout方法中,我们做的工作也很简单,就是获取子item的个数,因为我们需要根据LinearLayout的宽度和子item的个数来确定指示符的宽度。

  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. setMeasuredDimension(width, height);
  9. }

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

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

  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方法。

  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就完成了,来使用一下试试吧:

首先在布局文件中:

  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. <org.loader.indicatortest.Indicator
  9. android:id="@+id/indicator"
  10. android:layout_width="match_parent"
  11. android:layout_height="wrap_content"
  12. android:paddingBottom="10dip"
  13. android:paddingTop="10dip"
  14. android:weightSum="4"
  15. indicator:color="#FFFF0000" >
  16. <TextView
  17. android:id="@+id/tab_one"
  18. android:layout_width="0dip"
  19. android:layout_height="wrap_content"
  20. android:layout_weight="1"
  21. android:gravity="center_horizontal"
  22. android:text="TAB1" />
  23. <TextView
  24. android:id="@+id/tab_two"
  25. android:layout_width="0dip"
  26. android:layout_height="wrap_content"
  27. android:layout_weight="1"
  28. android:gravity="center_horizontal"
  29. android:text="TAB2" />
  30. <TextView
  31. android:id="@+id/tab_three"
  32. android:layout_width="0dip"
  33. android:layout_height="wrap_content"
  34. android:layout_weight="1"
  35. android:gravity="center_horizontal"
  36. android:text="TAB3" />
  37. <TextView
  38. android:id="@+id/tab_four"
  39. android:layout_width="0dip"
  40. android:layout_height="wrap_content"
  41. android:layout_weight="1"
  42. android:gravity="center_horizontal"
  43. android:text="TAB4" />
  44. </org.loader.indicatortest.Indicator>
  45. <android.support.v4.view.ViewPager
  46. android:id="@+id/container"
  47. android:layout_width="match_parent"
  48. android:layout_height="wrap_content"/>
  49. </LinearLayout>

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

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

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

看onPageScrolled中的一行代码:

  1. mIndicator.scroll(position, positionOffset);

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

Tab指示符——Indicator的更多相关文章

  1. 怎样 TabHostFragment自己定义 tab键(indicator)

    1 获得 tabHostFragment: ActionBarActivity activity2 = (ActionBarActivity) activity; mTabHost = new Fra ...

  2. android上FragmentTabHost实现自己定义Tab Indicator

    近期一直在做安卓开发,发现Tab布局能够用FragmentTabHost来实现,唯一不好的就是不能实现带图标的tabindicator, V4版本号中的尽管API有支持,可是不管怎么设置Drawabl ...

  3. ViewPager +Fragment 滑动游标

    一.我的博客https://github.com/anan03/ananwork/tree/master/anan1.加入compile 'com.gxz.pagerslidingtabstrip:l ...

  4. Android ActionBar 一步一步分析 (转)

    原文摘自:http://blog.csdn.net/android2me/article/details/8874846 1.Action Bar 介绍 我们能在应用中看见的actionbar一般就是 ...

  5. [AS/400] Control Language(CL) 基本概念

    本文内容源于 Go4AS400 简介 AS400 Control Language(CL) 是由指令(Command)组合而成,用于控制操作和调用系统功能.在 CL 程序中,指令用于和系统 OS400 ...

  6. tab使用 TabActivity TabHost Tabspec常用方法

    本文是参考Android官方提供的sample里面的ApiDemos的学习总结.   TabActivity   首先Android里面有个名为TabActivity来给我们方便使用.其中有以下可以关 ...

  7. 类似掌盟的Tab页 Android 开源框架ViewPageIndicator 和 ViewPager 仿网易新闻客户端Tab标签 (转)

    原博客地址  :http://blog.csdn.net/xiaanming/article/details/10766053 本文转载,记录学习用,如有需要,请到原作者网站查看(上面这个网址) 之前 ...

  8. Android UI学习 - Tab的学习和使用(转)

      本文是参考Android官方提供的sample里面的ApiDemos的学习总结.   TabActivity   首先Android里面有个名为TabActivity来给我们方便使用.其中有以下可 ...

  9. [原创]Android自定义View之IndicatorView,显示当前tab页所处位置的View

    概述 Android IndicatorView的灵感来源于SlidingTabView,虽然有句"不重复"造轮子在先,本着练手的目的,还是写了一个功能较为简单的类似view. 其 ...

随机推荐

  1. float数据在内存中是怎么存储的 AND IEEE754测试程序

    float类型数字在计算机中用4个字节存储.遵循IEEE-754格式标准: 一个浮点数有2部分组成:底数m和指数e 底数部分 使用二进制数来表示此浮点数的实际值指数部分 占用8bit的二进制数,可表示 ...

  2. pigcms 标签读不出

    错误:{pigcms:jc['portrait']} 正确:{pigcms:$jc['portrait']}

  3. Sql Server 里的向上取整、向下取整、四舍五入取整

    ==================================================== [四舍五入取整截取] select round(54.56,0) ============== ...

  4. 其他主流开源硬件简介BeagleBone Black快速入门

    其他主流开源硬件简介BeagleBone Black快速入门 1.3 其他主流开源硬件简介 开源硬件种类繁多,但主要有两款开源硬件常与BeagleBone比较.它们就是Arduino和Raspberr ...

  5. WPF之TextBox

    1. TextBox实现文字垂直居中 TextBox纵向长度比较长但文字字体比较小的时候,在输入时就会发现文字不是垂直居中的. 而使用中我们发现,TextBox虽然可以设置文字的水平对齐方式,但却没有 ...

  6. python 代码片段2

    #coding=utf-8 # python没有花括号.我们用对齐来代替括号:通常是四个空格(任意数目的空格货tab也都可以) # python 注释用 # # python不需要像其他语言那样需要首 ...

  7. BZOJ2584 : [Wc2012]memory

    xy方向分开考虑 用扫描线处理出拓扑序,第二问直接回答拓扑序, 第一问: 将操作倒过来,变成加入线段,用线段树维护区间拓扑序的最值 #include<cstdio> #include< ...

  8. ios batchRequest

    https://github.com/facebook/pop AF 为什么要用 一个线程去进行  1.这有什么好处  因为其生产额外的线程也要 开销的   asi 和af都是这么去做的  2.本身所 ...

  9. [Cocos2d-x For WP8]MotionStreak拖尾效果

    拖尾效果是指在在游戏中,一个精灵在运动的过程中会留下一个短暂的轨迹效果,在游戏里面如打斗的特效往往会需要用到这种效果来给运动的增加绚丽的效果.那么在Cocos2D-x里面我们可以使用一种内置的拖动渐隐 ...

  10. windows 中配置 nodejs npm

    npm安装模块的方式有两种:本地安装,全局安装. 这两者的区别在于,在用npm install xxx时加不加-g这个选项. 本地安装的路径在win中是当前cmd的路径.比如,我一打开cmd时,路径是 ...