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

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

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

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

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

public class Indicator extends LinearLayout {private Paint mPaint; // 画指示符的paintprivate int mTop; // 指示符的topprivate int mLeft; // 指示符的leftprivate int mWidth; // 指示符的widthprivate 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();// 初始化paintmPaint = new Paint();mPaint.setColor(mColor);mPaint.setAntiAlias(true);}@Overrideprotected void onFinishInflate() {super.onFinishInflate();mChildCount = getChildCount();  // 获取子item的个数}@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {super.onMeasure(widthMeasureSpec, heightMeasureSpec);mTop = getMeasuredHeight(); // 测量的高度即指示符的顶部位置int width = getMeasuredWidth(); // 获取测量的总宽度int height = 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();}@Overrideprotected 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();// 初始化paintmPaint = 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 = 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.Indicatorandroid: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" ><TextViewandroid:id="@+id/tab_one"android:layout_width="0dip"android:layout_height="wrap_content"android:layout_weight="1"android:gravity="center_horizontal"android:text="TAB1" /><TextViewandroid:id="@+id/tab_two"android:layout_width="0dip"android:layout_height="wrap_content"android:layout_weight="1"android:gravity="center_horizontal"android:text="TAB2" /><TextViewandroid:id="@+id/tab_three"android:layout_width="0dip"android:layout_height="wrap_content"android:layout_weight="1"android:gravity="center_horizontal"android:text="TAB3" /><TextViewandroid: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.ViewPagerandroid: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);@Overrideprotected 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() {@Overridepublic boolean isViewFromObject(View arg0, Object arg1) {return arg0 == arg1;}@Overridepublic int getCount() {return mViews.size();}@Overridepublic Object instantiateItem(ViewGroup container, int position) {View view = mViews.get(position);container.addView(view);return view;}@Overridepublic void destroyItem(ViewGroup container, int position,Object object) {container.removeView(mViews.get(position));}});mContainer.setOnPageChangeListener(new OnPageChangeListener() {@Overridepublic void onPageSelected(int position) {}@Overridepublic void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {mIndicator.scroll(position, positionOffset);}@Overridepublic 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);}}@Overridepublic 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效果了。

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

  1. 玩转直播+短视频 京东打造“史上最简单618”

    疫情之下,直播+短视频成为新时代的"弄潮儿",也成为本届"618"的最大看点之一! 与往年不同,本届"618"购物节,京东在站内站外联动布置 ...

  2. “婚礼哥”蹿红网络:用巨资打造史上最完美的婚礼征服女明星

    "婚礼哥"蹿红网络:用巨资打造史上最完美的婚礼征服女明星 近日以来,以让所有"北漂爱情不再白漂"以及对当红女明星徐千雅网络示爱"我要做你一生的北京情人 ...

  3. Android 自定义控件打造史上最简单的侧滑菜单

    侧滑菜单在很多应用中都会见到,最近QQ5.0侧滑还玩了点花样~~对于侧滑菜单,一般大家都会自定义ViewGroup,然后隐藏菜单栏,当手指滑动时,通过Scroller或者不断的改变leftMargin ...

  4. 打造史上最小尺寸.Net Core单文件应用程序

    .Net Core支持将应用程序发布成单文件进行部署和分发. 以下示例将Windows应用作为独立的单文件应用程序发布: dotnet publish -r win-x64 -c Release /p ...

  5. [Perl] Python 与 Perl 合并,打造史上最牛语言 Parrot

    本文链接: http://www.php-oa.com/2010/04/01/python-perl-parrot.html 据Perl官方网站的消息,Perl之父Larry Wall和Python之 ...

  6. 打造史上最没有技术含量的多级(无限级)ListView

    项目中需要使用一个多层级关系的ListView,问了下需求,回答是无限级...好吧,参考了网上的各种资源,似乎对无限级这种都没有很好的解决方案,特别是在后台给我的数据仅仅告诉我上一级的名称,大致就是{ ...

  7. powerbuilder TriggerEvent 参数_NLP界“威震天”袭来!英伟达1小时83亿参数打造史上最大语言模型...

    [新智元导读]英伟达一举创造了2个壮举!训练出了世界上最大的语言模型--MegatronLM,包含83亿参数,比BERT大24倍,比GPT-2大5.6倍:还打破了实时对话AI的记录,仅耗时53分钟即可 ...

  8. lzma打包exe_【原创】手写PE文件,打造史上最小LZMA解压DLL

    因程序需求,需要在VB中调用LZMA解压数据,经过N天研究出此成果- 什么是LZMA:LZMA应该是目前世界上数一数二的压缩算法--压缩时相同的时间得到压缩比最高,解压时速度极快且几乎不占内存.如果你 ...

  9. 超级百亿补贴,史上最大优惠:京东11.11开启

    2020年的特殊市场环境下,人们的消费行为更加趋于理性.对于即将到来的年终大促,消费者需要更大的促销力度和更多的新鲜玩法,来激发心中压抑已久的购物欲望. 在此背景下,10月19日,京东以一场别开生面的 ...

  10. 声网连麦+直播+视频+游戏“史上最强”社交直播方案 打造陌陌全新8.0改版

    做直播的,每天烧钱买流量,用户留存却是个问题.留不住用户,天天买量,天天流失.怎么破?做社交的,烧钱获客,赢得了口碑,却不知道如何变现,怎么破?做App的,装机量是上去了,用户活跃度和打开率却一直do ...

最新文章

  1. 陶哲轩挑战失败的百年数学问题,被两名在家隔离的数学家破解了
  2. python 模拟浏览器下载文件-python爬虫:使用Selenium模拟浏览器行为
  3. 2020年推荐系统工程师炼丹手册RecSys版
  4. 数学--数论-多重集排列组合与母函数
  5. JS中格式化数据保留两位小数
  6. X 分钟速成 Python
  7. element-ui滚动条组件el-scrollbar
  8. ubuntu 下的文件搜索
  9. access mysql oracle_Oracle访问Access实现数据同步
  10. 从事IT行业的应该如何学习最高效的休息方式 1
  11. python 使用win32api截图全解释
  12. linux makefile教程,Makefile简单入门教程
  13. 推荐系统--MovieLens数据集
  14. 游戏实战篇|原神人物模型下载教程,并在Unity中给模型 添加动画【伯嫖最新原神所有人物模型】文末送书
  15. Java常见问题之Data too long for column 'orResponse' at row 1
  16. 风淋门控制器c语言程序代码,C语言程序设计 第10章位运算
  17. COCOS2D 播放声音
  18. 给自己的学习任务1:做一个工资计算器app
  19. CSR8675项目实战:BlueBrowsing蓝牙播放器
  20. 朴素贝叶斯的应用_TF-IDF实例

热门文章

  1. 用C语言读把SGY地震数据读成txt
  2. 自制H5页面工具盘点 宣传/相册/邀请函
  3. ios根号怎么打_ios计算器开根号 苹果手机计算器怎么开根号 详情介绍
  4. Chrome上关于微信网页版WeChat不能正常登录的解决方案
  5. python_业余学习_分词工具jieba 正则表达式清洗微博文本特殊符号 最大匹配算法
  6. BERT源码embedding_lookup解析
  7. 《百词斩背单词》四级词汇(3486个)-1
  8. 可以作用c语言常量的是,正确的C语言常量是什么?
  9. matlab中停止调试快捷键,matlab停止运行快捷键
  10. php改写单词的大小写strtoupper/strtolower/ucfirst/ucwords