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

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

  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. Codeforces Round #Pi (Div. 2) D. One-Dimensional Battle Ships set乱搞

    D. One-Dimensional Battle ShipsTime Limit: 2 Sec Memory Limit: 256 MB 题目连接 http://codeforces.com/con ...

  2. thinkphp 目录

    WWW\User\Home\Conf\settings.php 1. APP_PATH . 'Home/Conf/settings.php 2.dirname( APP_PATH ) . '/User ...

  3. [HTTP那些事] JSON数据

    随着Android的发展,各路大神的贡献,我们可用的轮子越来越多.比如HTTP请求框架,有自家的Volley,Square的okhttp, async-http-lib, 还有聚合版的xUtils以及 ...

  4. Visual Studio提示Bonjour backend初始化失败

    Visual Studio提示Bonjour backend初始化失败 错误信息:The Bonjour backend failed to initialize, automatic Mac Bui ...

  5. ural 1152. False Mirrors

    1152. False Mirrors Time limit: 2.0 secondMemory limit: 64 MB Background We wandered in the labyrint ...

  6. Transactional topologies —— 事务拓扑

    事务拓扑是怎么回事? Storm guarantees that every message will be played through the topology at least once. St ...

  7. BZOJ 2282 & 树的直径

    SDOI2011的Dayx第2题 题意: 在树中找到一条权值和不超过S的链(为什么是链呢,因为题目中提到“使得路径的两端都是城市”,如果不是链那不就不止两端了吗——怎么这么机智的感觉...),使得不在 ...

  8. ZOJ 3494 (AC自动机+高精度数位DP)

    题目链接:  http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemCode=3494 题目大意:给定一些被禁止的BCD码.问指定范围内不含有 ...

  9. wp7 中 HubTile控件自定义大小。

    http://blog.csdn.net/matrixcl/article/details/7057291 (转) Toolkit(http://silverlight.codeplex.com/)中 ...

  10. 【BZOJ】1087: [SCOI2005]互不侵犯King(状压dp)

    http://www.lydsy.com:808/JudgeOnline/problem.php?id=1087 状压dp是第一次写啊,我也是才学TAT.状压dp一般都用一个值表示集合作为dp的一个状 ...