Android自定义recyclerView实现时光轴效果
时光轴效果在很多app上都有出现,例如淘宝中快递的跟踪,本文将使用recyclerview实现时光轴效果,我们会到自定义控件,首先先看一下效果图:

接下来是步骤分析
1自定义属性
这个大家应该都了解了,根据我们之前的分析,直接在attrs.xml中进行声明
<declare-styleable name="timeline"> <attr name="beginline" format="reference|color"></attr> <attr name="endline" format="reference|color"></attr> <attr name="linewidth" format="dimension"></attr> <attr name="timelineimage" format="color|reference"></attr> <attr name="timelineimagesize" format="dimension"></attr> </declare-styleable>
进行一下各个属性的声明
• beginline:开始的线条
• endline:下面的线条
• linewidth:线条的宽度
• timelineimage:中间的圆形
• timelineimagesize:中间的圆形的大小,这里默认他的宽高一致
2.自定义timeline继承view,构造方法如下
private int linewidth;
private drawable mbeginline;
private drawable mendline;
private drawable mtimelineimage;
private int mtimelineimagesize;
public timeline(context context) {
this(context,null);
}
public timeline(context context, attributeset attrs) {
this(context,attrs,0);
}
public timeline(context context, attributeset attrs, int defstyleattr) {
super(context, attrs, defstyleattr);
typedarray a = context.obtainstyledattributes(attrs, r.styleable.timeline);
linewidth = a.getdimensionpixeloffset(r.styleable.timeline_linewidth,15);
mbeginline = a.getdrawable(r.styleable.timeline_beginline);
mendline = a.getdrawable(r.styleable.timeline_endline);
mtimelineimage = a.getdrawable(r.styleable.timeline_timelineimage);
mtimelineimagesize = a.getdimensionpixelsize(r.styleable.timeline_timelineimagesize,25);
a.recycle();
}3.复写onmeasure方法
我们都知道自定义控件,一般需要重写onmeasure,ondraw,onlayout方法,这里onmeasure需要对wrap_content的情况进行特殊处理,具体原因请看源码
protected void onmeasure(int widthmeasurespec, int heightmeasurespec) {
super.onmeasure(widthmeasurespec, heightmeasurespec);
int w = timelinemarkersize + getpaddingleft() + getpaddingright();
int h = timelinemarkersize + getpaddingtop() + getpaddingbottom();
int widthsize = resolvesizeandstate(w, widthmeasurespec, 0);
int heightsize = resolvesizeandstate(h, heightmeasurespec, 0);
int widthspecmode = measurespec.getmode(widthmeasurespec);
int widthspecsize = measurespec.getsize(widthmeasurespec);
int heightspecmode = measurespec.getmode(heightmeasurespec);
int heightspecsize = measurespec.getsize(heightmeasurespec);
// 处理宽高都为 wrap_content 的情况
if (widthspecmode == measurespec.at_most && heightspecmode == measurespec.at_most) {
setmeasureddimension(default_width, default_height);
}
// 处理宽为 wrap_content 的情况
else if (widthspecmode == measurespec.at_most) {
setmeasureddimension(default_width, widthsize);
}
// 处理高为 wrap_content 的情况
else if (heightspecmode == measurespec.at_most) {
setmeasureddimension(heightsize, default_height);
}
}看过view源码的同学应该知道,在控件进行测量的时候,需要根据
specmode来进行具体的操作,view中提供了resolvesizeandstate方法来进行判断,该方法源码如下:
public static int resolvesizeandstate(int size, int measurespec, int childmeasuredstate) {
int result = size;
int specmode = measurespec.getmode(measurespec);
int specsize = measurespec.getsize(measurespec);
switch (specmode) {
case measurespec.unspecified:
result = size;
break;
case measurespec.at_most:
if (specsize < size) {
result = specsize | measured_state_too_small;
} else {
result = size;
}
break;
case measurespec.exactly:
result = specsize;
break;
}
return result | (childmeasuredstate&measured_state_mask);
}4.ondraw方法
@override
protected void ondraw(canvas canvas) {
super.ondraw(canvas);
if (mbeginline != null) {
mbeginline.draw(canvas);
}
if (mendline != null) {
mendline.draw(canvas);
}
if (mtimelineimage!=null){
mtimelineimage.draw(canvas);
}
}5.onsizechange
@override
protected void onsizechanged(int w, int h, int oldw, int oldh) {
int paddingleft = getpaddingleft();
int paddingright = getpaddingright();
int paddingtop = getpaddingtop();
int paddingbottom = getpaddingbottom();
//父容器的宽高
int width = getwidth();
int height = getheight();
int childwidth = width - paddingleft - paddingright;
int childheight = height - paddingtop - paddingbottom;
mtimelineimagesize = math.min(mtimelineimagesize, math.min(childheight, childwidth));
if (mtimelineimage != null) {
mtimelineimage.setbounds(paddingleft, paddingtop, paddingleft + mtimelineimagesize, paddingtop + mtimelineimagesize);
bounds = mtimelineimage.getbounds();
} else {
bounds = new rect(paddingleft, paddingtop, paddingleft + childwidth, paddingtop + childheight);
}
if (mbeginline != null) {
int lineleft = mtimelineimage.getbounds().centerx() - (linewidth >> 1);
mbeginline.setbounds(lineleft, 0, lineleft + linewidth, mtimelineimage.getbounds().top);
}
if (mendline != null) {
int lineleft = mtimelineimage.getbounds().centerx() - (linewidth >> 1);
mendline.setbounds(lineleft, mtimelineimage.getbounds().bottom, lineleft + linewidth, height);
}
}这里需要说明的是,我们的mbeginline的长度,其实是我们自定义控件的paddingtop高度,同理mendline的长度是paddingbottom高度,所以我们在使用这个控件时,一般都会设置paddingtop和paddingbottom
6.使用timeline控件
以下是recyclerview中一个item的布局,多个item拼接起来就是一条时光轴,这里需要说明的是,我们的 linearlayout使用的高度模式是wrap_content,这里我的textview设置了android:paddingtop="30dp",如果不对textview设置android:paddingtop,会发现timelineview控件是看不见的,这是由于父控件wrap_content,那么父控件包裹textview的内容,那么父控件的高度就是textview的高度,这样timelineview设置了android:paddingtop="34dp",这个高度是大于父控件的高度的,所以就看不到timelineview了,除非我们给linearlayout的android:layout_height="wrap_content",修改成固定的高度
<?xml version="1.0" encoding="utf-8"?> <linearlayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:paddingleft="16dp" android:paddingright="16dp"> <com.example.jikeyoujikeyou.timelinedemo2.timelineview android:id="@+id/timelineview" android:layout_width="wrap_content" android:layout_height="match_parent" android:clickable="true" android:focusable="true" android:focusableintouchmode="true" android:paddingbottom="8dp" android:paddingleft="4dp" android:paddingright="4dp" android:paddingtop="34dp" app:beginline="#ff0000" app:endline="#ff0000" app:linewidth="3dp" app:timelinemarker="@drawable/timeline_marker" app:timelinemarkersize="24dp" /> <textview style="@style/base.textappearance.appcompat.title" android:id="@+id/timelinename" android:layout_width="wrap_content" android:layout_height="wrap_content" android:ellipsize="end" android:paddingtop="30dp" android:singleline="true" android:text="name" android:textcolor="@color/grey_700" android:textsize="16sp" /> </linearlayout>
7.最后就是recyclerview的使用
recyclerview的使用大家应该都很熟悉了,无非就是设置adapter,viewholder等,这里不再赘述,还有一点需要强调的是itemviewtype有四种情况,第一个,最后一个,中间,还有只有一个四种情况情况,根据这几种情况,有选择设置mbeginline与 mendline是否进行绘制
timelineadapter代码:
package com.example.jikeyoujikeyou.timelinedemo;
import android.annotation.targetapi;
import android.content.context;
import android.graphics.drawable.drawable;
import android.os.build;
import android.support.v7.widget.recyclerview;
import android.view.layoutinflater;
import android.view.view;
import android.view.viewgroup;
import android.widget.textview;
import java.util.arraylist;
import java.util.list;
import java.util.random;
/**
* created by jikeyoujikeyou on 16/7/22.
*/
public class timelineadapter extends recyclerview.adapter<timelineadapter.viewholder> {
private list<timelineitem> datas ;
public timelineadapter(list<timelineitem> datas) {
super();
this.datas = datas;
}
@override
public viewholder oncreateviewholder(viewgroup parent, int viewtype) {
layoutinflater layoutinflater = layoutinflater.from(parent.getcontext());
view view = layoutinflater.inflate(r.layout.item_timeline, null);
return new viewholder(view, parent.getcontext(), viewtype);
}
@override
public void onbindviewholder(viewholder holder, int position) {
timelineitem timelineitem = datas.get(position);
holder.tv_name.settext(timelineitem.gettimelinename());
}
@override
public int getitemcount() {
return datas.size();
}
@override
public int getitemviewtype(int position) {
int size = datas.size() - 1;
if (size == 0) {
return timelineitemtype.atom;
} else if (position == 0) {
return timelineitemtype.start;
} else if (position == size) {
return timelineitemtype.end;
} else {
return timelineitemtype.normal;
}
}
class viewholder extends recyclerview.viewholder {
private textview tv_name;
private timeline timeline;
public viewholder(view itemview, context context, int viewtype) {
super(itemview);
tv_name = (textview) itemview.findviewbyid(r.id.name);
timeline = (timeline) itemview.findviewbyid(r.id.timelineview);
drawable drawable = context.getresources().getdrawable(r.drawable.timeline_marker);
drawable drawable2 = context.getresources().getdrawable(r.drawable.timeline_marker2);
drawable drawable3 = context.getresources().getdrawable(r.drawable.timeline_marker3);
drawable drawable4 = context.getresources().getdrawable(r.drawable.timeline_marker4);
drawable drawable5 = context.getresources().getdrawable(r.drawable.timeline_marker5);
random random = new random();
final int i = random.nextint(5);
final drawable drawables[] = {drawable, drawable2, drawable3, drawable4, drawable5};
timeline.settimelineimage(drawables[i]);
if (viewtype == timelineitemtype.start) {
timeline.setbeginline(null);
} else if (viewtype == timelineitemtype.end) {
timeline.setendline(null);
} else if (viewtype == timelineitemtype.atom) {
timeline.setbeginline(null);
timeline.setendline(null);
}
}
}
class timelineitemtype {
//正常
public final static int normal = 0;
//开始
public final static int start = 1;
//结束
public final static int end = 2;
//只有一条数据,那么beginline和endline都没有
public final static int atom = 3;
}
}
mainactivity代码:
<pre name="code" class="java">public class mainactivity extends appcompatactivity {
private list<timelineitem> mdatas;
@override
protected void oncreate(bundle savedinstancestate) {
super.oncreate(savedinstancestate);
setcontentview(r.layout.activity_main);
initdata();
recyclerview recyclerview = (recyclerview) findviewbyid(r.id.recyclerview);
linearlayoutmanager linearlayoutmanager = new linearlayoutmanager(this);
linearlayoutmanager.setorientation(linearlayoutmanager.vertical);
recyclerview.setlayoutmanager(linearlayoutmanager);
timelineadapter adapter = new timelineadapter(mdatas);
recyclerview.setadapter(adapter);
}
private void initdata() {
mdatas = new arraylist<>();
mdatas.add(new timelineitem("爸爸生日"));
mdatas.add(new timelineitem("妈妈生日"));
mdatas.add(new timelineitem("姐姐生日"));
mdatas.add(new timelineitem("女神生日"));
mdatas.add(new timelineitem("前任生日"));
}
}运行项目,就会呈现本文一开始的效果。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持萬仟网。
看完文章,还可以扫描下面的二维码下载快手极速版领4元红包
除了扫码领红包之外,大家还可以在快手极速版做签到,看视频,做任务,参与抽奖,邀请好友赚钱)。
邀请两个好友奖最高196元,如下图所示:







