Android应用中仿今日头条App制作ViewPager指示器
一、概述
顶部viewpager指示器的字体变色,该效果图是这样的:
大概是今天头条的app,神奇的地方就在于,切换viewpager页面的时候,顶部指示器改成了字体颜色的变化,个人觉得还是不错的。
那么核心的地方就是做一个支持字体这样逐渐染色就可以了,我大概想了32s,扫描了一些可能实现的方案,最终定位了一个靠谱的,下面我就带大家开始实现的征程。
实现之前贴一下我们的效果图:
1、简单使用
效果如上图了,关于颜失色的改变我添加了两个方向,一个是左方向,一个是有方向。
单纯的使用,可能觉得没什么意思,下面看结合viewpager使用的一个例子。
2、结合viewpager使用
可以看到我们切换页面的时候,上面的指示器的效果,棒棒哒~~~
当然了,学会了原理,你可以扩展,可以做个性的进度条,可以将字体变色改为背景色变色,可以把方向改为上下,太多了,自己去抠脚想把。
二、原理
看完效果图,有木有什么思路~~~花几分钟想想,因为原理很简单~~
我大致想了下,目测绘制半个字估计不行,那么就在绘制范围上下功夫,你可以全部绘制,但是我控制显示的范围,所以上述效果:
其实是绘制了两遍字体,但是呢,分别控制了绘制的显示范围,实现了逐渐变色的效果,那么对于范围的控制,有什么方便的api么,显然是有的
canvas有个cliprect的方法~~~ok,原理分析完毕~~
三、实现
说到实现,那第一步肯定又是自定义属性,我们这里的属性,需要text,textsize,textorigincolor,textchangecolor,progress,大致看一下,应该都能看出来作用吧,看不出来没事,结合下面的代码。tip:我们的view叫做colortrackview,感谢小七的命名。
1、自定义属性和获取
attr.xml
<?xml version="1.0" encoding="utf-8"?> <resources> <attr name="text" format="string" /> <attr name="text_size" format="dimension" /> <attr name="text_origin_color" format="color|reference" /> <attr name="text_change_color" format="color|reference" /> <attr name="progress" format="float" /> <attr name="direction"> <enum name="left" value="0" /> <enum name="right" value="1" /> </attr> <declare-styleable name="colortrackview"> <attr name="text" /> <attr name="text_size" /> <attr name="text_origin_color" /> <attr name="text_change_color" /> <attr name="progress" /> <attr name="direction" /> </declare-styleable> </resources>
然后在我们的colortrackview的构造方法中进行获取这些个渣渣属性:
public class colortrackview extends view { private int mtextstartx; public enum direction { left , right ; } private int mdirection = direction_left; private static final int direction_left = 0 ; private static final int direction_right= 1 ; public void setdirection(int direction) { mdirection = direction; } private string mtext = "张鸿洋"; private paint mpaint; private int mtextsize = sp2px(30); private int mtextorigincolor = 0xff000000; private int mtextchangecolor = 0xffff0000; private rect mtextbound = new rect(); private int mtextwidth; private int mrealwidth; private float mprogress; public colortrackview(context context) { super(context, null); } public colortrackview(context context, attributeset attrs) { super(context, attrs); mpaint = new paint(paint.anti_alias_flag); typedarray ta = context.obtainstyledattributes(attrs, r.styleable.colortrackview); mtext = ta.getstring(r.styleable.colortrackview_text); mtextsize = ta.getdimensionpixelsize( r.styleable.colortrackview_text_size, mtextsize); mtextorigincolor = ta.getcolor( r.styleable.colortrackview_text_origin_color, mtextorigincolor); mtextchangecolor = ta.getcolor( r.styleable.colortrackview_text_change_color, mtextchangecolor); mprogress = ta.getfloat(r.styleable.colortrackview_progress, 0); mdirection = ta.getint(r.styleable.colortrackview_direction, mdirection); ta.recycle(); mpaint.settextsize(mtextsize); measuretext(); } private void measuretext() { mtextwidth = (int) mpaint.measuretext(mtext); mpaint.gettextbounds(mtext, 0, mtext.length(), mtextbound); }
可以看到我同时贴出了成员变量,大家简单看下就行了,都比较简单。
获取了属性,初始化完成一些成员变量以后,那么应该走向我们的measure之旅了~~
2、onmeasure
@override protected void onmeasure(int widthmeasurespec, int heightmeasurespec) { int width = measurewidth(widthmeasurespec); int height = measureheight(heightmeasurespec); setmeasureddimension(width, height); mrealwidth = getmeasuredwidth() - getpaddingleft() - getpaddingright(); mtextstartx = mrealwidth / 2 - mtextwidth / 2; } private int measureheight(int measurespec) { int mode = measurespec.getmode(measurespec); int val = measurespec.getsize(measurespec); int result = 0; switch (mode) { case measurespec.exactly: result = val; break; case measurespec.at_most: case measurespec.unspecified: result = mtextbound.height(); break; } result = mode == measurespec.at_most ? math.min(result, val) : result; return result + getpaddingtop() + getpaddingbottom(); } private int measurewidth(int measurespec) { int mode = measurespec.getmode(measurespec); int val = measurespec.getsize(measurespec); int result = 0; switch (mode) { case measurespec.exactly: result = val; break; case measurespec.at_most: case measurespec.unspecified: // result = mtextbound.width(); result = mtextwidth; break; } result = mode == measurespec.at_most ? math.min(result, val) : result; return result + getpaddingleft() + getpaddingright(); }
关于测量,也是比较传统的写法,根据传入的widthmeasurespec、heightmeasurespec,利用measurespec分别获取模式和值,如何是exactly万事大吉,如果是at_most、unspecified那么就进行自己测量需要的空间,当然了,最好注意如果是at_most不应该大于父类传入的值。
这里提一下,如果偷懒的话,可以选择继承textview,然后测量就不需要写了,textview默认帮你实现了,还能利用textview的一些属性,不过咱们这个例子比较简单,我最终还是选择了继承view,继承view有种everything under control 的感觉。
测量完成以后,不用说都是绘制了。
3、ondraw
@override protected void ondraw(canvas canvas) { super.ondraw(canvas); int r = (int) (mprogress* mtextwidth +mtextstartx ); if(mdirection == direction_left) { drawchangeleft(canvas, r); draworiginleft(canvas, r); }else { draworiginright(canvas, r); drawchangeright(canvas, r); } } private void drawchangeright(canvas canvas, int r) { drawtext(canvas, mtextchangecolor, (int) (mtextstartx +(1-mprogress)*mtextwidth), mtextstartx+mtextwidth ); } private void draworiginright(canvas canvas, int r) { drawtext(canvas, mtextorigincolor, mtextstartx, (int) (mtextstartx +(1-mprogress)*mtextwidth) ); } private void drawchangeleft(canvas canvas, int r) { drawtext(canvas, mtextchangecolor, mtextstartx, (int) (mtextstartx + mprogress * mtextwidth) ); } private void draworiginleft(canvas canvas, int r) { drawtext(canvas, mtextorigincolor, (int) (mtextstartx + mprogress * mtextwidth), mtextstartx +mtextwidth ); } private void drawtext(canvas canvas , int color , int startx , int endx) { mpaint.setcolor(color); canvas.save(canvas.clip_save_flag); canvas.cliprect(startx, 0, endx, getmeasuredheight()); canvas.drawtext(mtext, mtextstartx, getmeasuredheight() / 2 + mtextbound.height() / 2, mpaint); canvas.restore(); }
绘制的核心就在于利用mprogress和方向去计算应该clip的范围,具体的参考代码,没什么难点。有了范围以后,无非就是drawtext~~~这里只讲主要代码。
主要的方法介绍完毕,我们就该测试了。
四、测试
1、简单测试
布局文件
<relativelayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" xmlns:zhy="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent" > <com.zhy.view.colortrackview android:id="@+id/id_changetextcolorview" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_centerinparent="true" android:background="#44ff0000" android:padding="10dp" zhy:progress="0" zhy:text="张鸿洋" zhy:text_change_color="#ffff0000" zhy:text_origin_color="#ff000000" zhy:text_size="60sp" /> <linearlayout android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_alignparentbottom="true" android:gravity="center" android:orientation="horizontal" > <button android:id="@+id/id_left" android:layout_width="wrap_content" android:layout_height="wrap_content" android:onclick="startleftchange" android:text="startleft" /> <button android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_torightof="@id/id_left" android:onclick="startrightchange" android:text="startright" /> </linearlayout> </relativelayout>
注意我们的自定义属性的命名空间,该布局就一个colortrackview,然后两个按钮来控制进度。
simpleuseactivity:
package com.zhy.viewpagerindicator; import android.animation.objectanimator; import android.annotation.suppresslint; import android.app.activity; import android.os.bundle; import android.view.view; import com.zhy.view.colortrackview; public class simpleuseactivity extends activity { colortrackview mview; @override protected void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.activity_simple_main); mview = (colortrackview) findviewbyid(r.id.id_changetextcolorview); } @suppresslint("newapi") public void startleftchange(view view) { mview.setdirection(0); objectanimator.offloat(mview, "progress", 0, 1).setduration(2000) .start(); } @suppresslint("newapi") public void startrightchange(view view) { mview.setdirection(1); objectanimator.offloat(mview, "progress", 0, 1).setduration(2000) .start(); } }
这里拿属性动画进行的测试,没有导入3.0以下兼容包,有需要自己导入。
效果图就是上面张鸿洋那张。
2、结合viewpager
布局文件:
<linearlayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:zhy="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > <linearlayout android:layout_width="match_parent" android:layout_height="50dp" android:orientation="horizontal" > <com.zhy.view.colortrackview android:id="@+id/id_tab_01" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" zhy:progress="1" zhy:text="简介" zhy:text_change_color="#ffff0000" zhy:text_origin_color="#ff000000" zhy:text_size="18sp" /> <com.zhy.view.colortrackview android:id="@+id/id_tab_02" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" zhy:text="评价" zhy:text_change_color="#ffff0000" zhy:text_origin_color="#ff000000" zhy:text_size="18sp" /> <com.zhy.view.colortrackview android:id="@+id/id_tab_03" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" zhy:text="相关" zhy:text_change_color="#ffff0000" zhy:text_origin_color="#ff000000" zhy:text_size="18sp" /> </linearlayout> <android.support.v4.view.viewpager android:id="@+id/id_viewpager" android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1" > </android.support.v4.view.viewpager> </linearlayout>
3个colortrackview代表tab,下面是viewpager
viewpageruseactivity:
package com.zhy.viewpagerindicator; import java.util.arraylist; import java.util.list; import android.os.bundle; import android.support.v4.app.fragment; import android.support.v4.app.fragmentactivity; import android.support.v4.app.fragmentpageradapter; import android.support.v4.view.viewpager; import android.support.v4.view.viewpager.onpagechangelistener; import android.util.log; import com.zhy.view.colortrackview; public class viewpageruseactivity extends fragmentactivity { private string[] mtitles = new string[] { "简介", "评价", "相关" }; private viewpager mviewpager; private fragmentpageradapter madapter; private tabfragment[] mfragments = new tabfragment[mtitles.length]; private list<colortrackview> mtabs = new arraylist<colortrackview>(); @override protected void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.activity_vp_main); initviews(); initdatas(); initevents(); } private void initevents() { mviewpager.setonpagechangelistener(new onpagechangelistener() { @override public void onpageselected(int position) { } @override public void onpagescrolled(int position, float positionoffset, int positionoffsetpixels) { if (positionoffset > 0) { colortrackview left = mtabs.get(position); colortrackview right = mtabs.get(position + 1); left.setdirection(1); right.setdirection(0); log.e("tag", positionoffset+""); left.setprogress( 1-positionoffset); right.setprogress(positionoffset); } } @override public void onpagescrollstatechanged(int state) { } }); } private void initdatas() { for (int i = 0; i < mtitles.length; i++) { mfragments[i] = (tabfragment) tabfragment.newinstance(mtitles[i]); } madapter = new fragmentpageradapter(getsupportfragmentmanager()) { @override public int getcount() { return mtitles.length; } @override public fragment getitem(int position) { return mfragments[position]; } }; mviewpager.setadapter(madapter); mviewpager.setcurrentitem(0); } private void initviews() { mviewpager = (viewpager) findviewbyid(r.id.id_viewpager); mtabs.add((colortrackview) findviewbyid(r.id.id_tab_01)); mtabs.add((colortrackview) findviewbyid(r.id.id_tab_02)); mtabs.add((colortrackview) findviewbyid(r.id.id_tab_03)); } }
tabfragment
package com.zhy.viewpagerindicator; import java.util.random; import android.graphics.color; import android.os.bundle; import android.support.v4.app.fragment; import android.view.gravity; import android.view.layoutinflater; import android.view.view; import android.view.viewgroup; import android.widget.textview; public class tabfragment extends fragment { public static final string title = "title"; private string mtitle = "defaut value"; @override public void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); if (getarguments() != null) { mtitle = getarguments().getstring(title); } } @override public view oncreateview(layoutinflater inflater, viewgroup container, bundle savedinstancestate) { textview tv = new textview(getactivity()); tv.settextsize(60); random r = new random(); tv.setbackgroundcolor(color.argb(r.nextint(120), r.nextint(255), r.nextint(255), r.nextint(255))); tv.settext(mtitle); tv.setgravity(gravity.center); return tv; } public static tabfragment newinstance(string title) { tabfragment tabfragment = new tabfragment(); bundle bundle = new bundle(); bundle.putstring(title, title); tabfragment.setarguments(bundle); return tabfragment; } }
效果图就是上面“结合viewpager使用”的那张。