Tab指示符——Indicator
先说说我们的思路吧。 其实思路也很简单,就是在咱们的导航下面画一个小矩形,不断的改变这个矩形距离左边的位置。
思路就这么简单,有了思路,接下来就是实现了,看代码:
- 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 = 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 = 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,就是这一行代码,控制了我们的指示符的滑动。
Tab指示符——Indicator的更多相关文章
- 怎样 TabHostFragment自己定义 tab键(indicator)
1 获得 tabHostFragment: ActionBarActivity activity2 = (ActionBarActivity) activity; mTabHost = new Fra ...
- android上FragmentTabHost实现自己定义Tab Indicator
近期一直在做安卓开发,发现Tab布局能够用FragmentTabHost来实现,唯一不好的就是不能实现带图标的tabindicator, V4版本号中的尽管API有支持,可是不管怎么设置Drawabl ...
- ViewPager +Fragment 滑动游标
一.我的博客https://github.com/anan03/ananwork/tree/master/anan1.加入compile 'com.gxz.pagerslidingtabstrip:l ...
- Android ActionBar 一步一步分析 (转)
原文摘自:http://blog.csdn.net/android2me/article/details/8874846 1.Action Bar 介绍 我们能在应用中看见的actionbar一般就是 ...
- [AS/400] Control Language(CL) 基本概念
本文内容源于 Go4AS400 简介 AS400 Control Language(CL) 是由指令(Command)组合而成,用于控制操作和调用系统功能.在 CL 程序中,指令用于和系统 OS400 ...
- tab使用 TabActivity TabHost Tabspec常用方法
本文是参考Android官方提供的sample里面的ApiDemos的学习总结. TabActivity 首先Android里面有个名为TabActivity来给我们方便使用.其中有以下可以关 ...
- 类似掌盟的Tab页 Android 开源框架ViewPageIndicator 和 ViewPager 仿网易新闻客户端Tab标签 (转)
原博客地址 :http://blog.csdn.net/xiaanming/article/details/10766053 本文转载,记录学习用,如有需要,请到原作者网站查看(上面这个网址) 之前 ...
- Android UI学习 - Tab的学习和使用(转)
本文是参考Android官方提供的sample里面的ApiDemos的学习总结. TabActivity 首先Android里面有个名为TabActivity来给我们方便使用.其中有以下可 ...
- [原创]Android自定义View之IndicatorView,显示当前tab页所处位置的View
概述 Android IndicatorView的灵感来源于SlidingTabView,虽然有句"不重复"造轮子在先,本着练手的目的,还是写了一个功能较为简单的类似view. 其 ...
随机推荐
- cocos2dx游戏开发——微信打飞机学习笔记(七)——Enemy的搭建
一.文件创建~ Enemy.h Enemy.cpp 二.How to do? 由于我是已经完成成个游戏的功能,所以我会将游戏中enemy所需要的很多功能基本上都先考虑到了,如果大家自己在做的时候也许没 ...
- CC2540开发板学习笔记(一)——LED点亮
一.实验内容: 点亮LDE1.2 二.实验原理: 1.电路原理图: 就一个发光二极管串联一个电阻.电阻是为了防止电流过大.利用发光二极管的单向导电性,在P1为高电平是点亮LED,在低电平是熄灭LED. ...
- Kinect学习笔记(六)——深度数据测量技术及应用
一.Kinect视角场 1.43°垂直方向和57°水平方向可视范围. 2.视角场常量值定义 属性 描述 Format 获取或设置深度图像格式 MaxDepth 获取最大深度值 MinDepth 获取最 ...
- MSSQL 2005 列转行应用案例
/*MSSQL 2005 列转行应用案例 By claro(陈亮) 2008-12-2 转载请包含此信息*/ --test table KuCunMX If object_id ('KuCunMX') ...
- 二分搜索 UVALive 6076 Yukari's Birthday (12长春K)
题目传送门 题意:问使得sum (k^i) = n || n -1 (1 <= i <= r) 的min (r*k)组合的r和k 分析:r的最大不会超过40,枚举r,二分搜索k.注意会爆 ...
- HDU3247 Resource Archiver(AC自动机+BFS+DP)
题目,求最短的包含所有n个DNA片段且不包含任何一个病毒片段的序列. 容易用所有DNA片段和病毒片段建一个AC自动机,构造fail时处理一下各个结点后缀是DNA或者病毒的情况,然后dp[S][u]表示 ...
- Android 蓝牙 BLE 开发笔记
最近公司头戴换了一块蓝牙4.0 BLE模块,所以我们Android组要适配 BLE.Android BLE 需要 4.3 以上系统,api 还是非常简单的, 第一步就是扫描, 扫描到设备后就可以连接了 ...
- it's hard to say
Ew,it's hard to begin.In fact I don't know what to say either.So here is a sad story.First of all ,m ...
- python 代码片段5
#coding=utf-8 # python 有两个主要数据类型:int和float.根据Kiss原则,python只有一宗整数类型int. print 3**3 print int('123') p ...
- redis AND memcache
memcache文章索引 MEMCACHE问题集锦[转] MEMCACHED 高可用方案 REPCACHED NOSQL之[MEMCACHED]学习 当 MySQL 和 Memcached 遇到尾部空 ...