欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

Android应用中仿今日头条App制作ViewPager指示器

程序员文章站 2024-02-28 14:08:46
一、概述 顶部viewpager指示器的字体变色,该效果图是这样的: 大概是今天头条的app,神奇的地方就在于,切换viewpager页面的时候,顶部指示器改成...

一、概述
顶部viewpager指示器的字体变色,该效果图是这样的:

Android应用中仿今日头条App制作ViewPager指示器

大概是今天头条的app,神奇的地方就在于,切换viewpager页面的时候,顶部指示器改成了字体颜色的变化,个人觉得还是不错的。
那么核心的地方就是做一个支持字体这样逐渐染色就可以了,我大概想了32s,扫描了一些可能实现的方案,最终定位了一个靠谱的,下面我就带大家开始实现的征程。
实现之前贴一下我们的效果图:
1、简单使用

Android应用中仿今日头条App制作ViewPager指示器

效果如上图了,关于颜失色的改变我添加了两个方向,一个是左方向,一个是有方向。
单纯的使用,可能觉得没什么意思,下面看结合viewpager使用的一个例子。
2、结合viewpager使用

Android应用中仿今日头条App制作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使用”的那张。