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("前任生日")); } }
运行项目,就会呈现本文一开始的效果。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。