关闭

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

标签: android导航ViewGroupindicator
12433人阅读 评论(26) 收藏 举报
分类:

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


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

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

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

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

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,就是这一行代码,控制了我们的指示符的滑动。

最后来看看效果图吧:


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



26
1
查看评论
发表评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场

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

转载请注明出处:http://blog.csdn.net/qibin0506/article/details/42046559 如果你还不知道什么是Tab指示符,相信在你看过网易新闻的这效果后,一定会豁然开朗:‘ 就是导航栏下面那个红色的长条,今天我们也来实现一下这效果。...
  • u012124764
  • u012124764
  • 2015-01-28 13:33
  • 486

数据格式,访问信息以及操作数指示符

数据格式,访问信息以及操作数指示符 前言 说好的今天去学车,穿上衣服又脱了,谁到了11点.哈哈,没啥吊事就接着干呗.本次讲的内容可以成为汇编的基础,因为汇编预言大部分时候在操作一些我们平时看不到的东西,因此本文想告诉大家汇编语言都是在操作什么玩意.或者更准确的说,各种汇编指令都是在操作什么样的对...
  • shanyongxu
  • shanyongxu
  • 2015-08-17 13:57
  • 1021

分类的线性回归方法

分类的指示矩阵回归方法考虑将每个相应类型通过一个指示变量编码,这样,如果有K个类,那么对于每一个输入,输出时一个K维向量,其中,如果G=kG=k,那么Yk=1Y_k=1,否则Yk=0Y_k=0,训练集的N个输入形成一个N*K的指示响应矩阵(indicator response matrix)Y。 ...
  • u014664226
  • u014664226
  • 2016-08-13 15:07
  • 543

Proc *C/C++入门之指示器变量

用户能够将任何一个宿主变量同一个指示变量进行关联。指示器变量必须被定义为 2 个字 节的整数类型( short),在 SQL 语句中,如果没有指定 INDICATOR 关键字,指示变量必须 紧跟在与其关联的宿主变量后。如果使用 DECLARE SECTION 声明宿主变量,则相关指示 变量也必...
  • lzjsqn
  • lzjsqn
  • 2017-01-11 16:01
  • 484

修改TabLayout底部导航条Indicator的长短

关于Tablayout,使用的应该很频繁了,但是底部导航条长短是固定死的,需要自己来改动长短,找了半天没找着方法,看了下官方建议,可以通过映射来修改自己想要的长短,其实也就几行代码的问题,看代码: public static void setIndicator(Context context...
  • hedong_77
  • hedong_77
  • 2016-10-30 14:45
  • 6836

自定义实现带三角下标的TabLayout

在开发中,我们常常需要ViewPager结合Fragment一起使用,如下图:我们可以使用Design support library库的TabLayout去实现,但是TabLayout只能用横线指示器,如果想要其他指示器,比如三角下标,该控件就不能用了。我们可以找网上成熟的轮子进行修改,比如:Pa...
  • qq_27258799
  • qq_27258799
  • 2017-07-06 15:35
  • 867

RadioButton使用(一)实现底部带指示条的Tab选项卡

在对之前的项目中做重构时候发现一个界面的tab选项卡,之前一直 用着看是不能滑动的,但是在代码中发现是用viewpager实现的后面强制把滑动给禁掉了,代码显示有点多也很乱。既然问了领导需求没有滑动只能点击,那么就没有必要再用viewpager去实现了。重构后采用RadioButton,主要利用Ra...
  • qq_34471736
  • qq_34471736
  • 2017-08-06 14:37
  • 295

Android 开源框架ViewPageIndicator 和 ViewPager 仿网易新闻客户端Tab标签

之前用JakeWharton的开源框架ActionBarSherlock和ViewPager实现了对网易新闻客户端Tab标签的功能,ActionBarSherlock是在3.0以下的机器支持ActionBar的功能,有兴趣的可以看看开源框架ActionBarSherlock 和 ViewPager...
  • xiaanming
  • xiaanming
  • 2013-09-01 11:32
  • 160908

C++11decltype类型指示符

C++11 decltype类型指示符
  • sinat_16709955
  • sinat_16709955
  • 2017-06-03 17:14
  • 283

C++的属性指示符

C++的属性指示符,有点类似Java中Anotation, 是可以实现定义的。它主要是用来引入一些对象,类型,代码的属性,也可以理解为限制对象,类型,代码的一些行为。它为实现定义的语言扩展提供标准统一的语法,比如GNU和IBM的__attribute__((…)),微软的__declspec()语言...
  • duandianR
  • duandianR
  • 2017-05-27 22:00
  • 796
    个人资料
    • 访问:648153次
    • 积分:6909
    • 等级:
    • 排名:第3919名
    • 原创:80篇
    • 转载:0篇
    • 译文:2篇
    • 评论:625条
    文章分类
    博客专栏
    友情链接

    鸿洋_

    Aggie的博客

    梁肖技术中心

    极客导航

    最新评论