RecyclerView列表控件漂亮时间线实现
時(shí)間都去哪了;時(shí)間你走慢一點(diǎn)吧
很多軟件中都有時(shí)間線的東西,比如天氣,計(jì)劃,旅游等時(shí)間線最多了;具體實(shí)現(xiàn)方式很多,在本篇文章中講解一種自定義View封裝的方式實(shí)現(xiàn)時(shí)間線效果,PS:這也是面試中也時(shí)常會(huì)問到的知識點(diǎn)
1. 效果圖
2. 實(shí)現(xiàn)分析
軟件中,可以看見前面的時(shí)間線也就是線條加上圓圈組成;當(dāng)然這里的圓圈與線條也都是可以隨意換成其他的,比如圖片等等。
當(dāng)然這里最簡單的來說,是上面一個(gè)線條,然后一個(gè)圓圈,然后下面一個(gè)線條;上線條在第一條數(shù)據(jù)時(shí)不做顯示,下線條在最后一條數(shù)據(jù)時(shí)不做顯示。
這里自定義布局部分也就是把旁邊的線條與圓圈封裝到一起,并使用簡單的方法來控制是否顯示。
當(dāng)封裝好了后,與旁邊的文字部分也就是水瓶方向的線性布局了,然后設(shè)置為每一個(gè)的RecyclerView 的Item的布局也就完成了。
3. 自定義控件
控件很簡單,首先我們繼承View,取名為 TimeLineMarker 就OK。
3.1 自定義屬性
開始控件之前先準(zhǔn)備好需要的屬性。
<?xml version="1.0" encoding="utf-8"?> <resources><declare-styleable name="TimeLineMarker"><attr name="markerSize" format="dimension" /><attr name="marker" format="color|reference" /><attr name="beginLine" format="color|reference" /><attr name="endLine" format="color|reference" /><attr name="lineSize" format="dimension" /></declare-styleable> </resources>在這里也就準(zhǔn)備了線條的大小、開始線條、結(jié)束線條、中間標(biāo)示部分及大小。
3.2 屬性與實(shí)現(xiàn)
private int mMarkerSize = 24;private int mLineSize = 12;private Drawable mBeginLine;private Drawable mEndLine;private Drawable mMarkerDrawable;@Overrideprotected void onDraw(Canvas canvas) {if (mBeginLine != null) {mBeginLine.draw(canvas);}if (mEndLine != null) {mEndLine.draw(canvas);}if (mMarkerDrawable != null) {mMarkerDrawable.draw(canvas);}super.onDraw(canvas);}兩個(gè)大小屬性,3個(gè)具體的Drawable,然后在onDraw方法中進(jìn)行具體的顯示也就OK。
3.3 構(gòu)造與屬性初始化
在上面我們定義了屬性,在這里我們在構(gòu)造函數(shù)中獲取XML所設(shè)置的屬性。
public TimeLineMarker(Context context) {this(context, null);}public TimeLineMarker(Context context, AttributeSet attrs) {this(context, attrs, 0);}public TimeLineMarker(Context context, AttributeSet attrs, int defStyle) {super(context, attrs, defStyle);init(attrs);}private void init(AttributeSet attrs) {// Load attributesfinal TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.TimeLineMarker, 0, 0);mMarkerSize = a.getDimensionPixelSize(R.styleable.TimeLineMarker_markerSize,mMarkerSize);mLineSize = a.getDimensionPixelSize(R.styleable.TimeLineMarker_lineSize,mLineSize);mBeginLine = a.getDrawable(R.styleable.TimeLineMarker_beginLine);mEndLine = a.getDrawable(R.styleable.TimeLineMarker_endLine);mMarkerDrawable = a.getDrawable(R.styleable.TimeLineMarker_marker);a.recycle();if (mBeginLine != null)mBeginLine.setCallback(this);if (mEndLine != null)mEndLine.setCallback(this);if (mMarkerDrawable != null)mMarkerDrawable.setCallback(this);}3.4 Drawable 的位置與大小初始化
屬性啥的有了,具體的Drawable 也有了,要顯示的地方調(diào)用也是OK了;但是如果沒有進(jìn)行進(jìn)行具體的位置調(diào)整這一切也都沒有意義。
@Overrideprotected void onSizeChanged(int w, int h, int oldw, int oldh) {super.onSizeChanged(w, h, oldw, oldh);initDrawableSize();}private void initDrawableSize() {int pLeft = getPaddingLeft();int pRight = getPaddingRight();int pTop = getPaddingTop();int pBottom = getPaddingBottom();int width = getWidth();int height = getHeight();int cWidth = width - pLeft - pRight;int cHeight = height - pTop - pBottom;Rect bounds;if (mMarkerDrawable != null) {// Sizeint markerSize = Math.min(mMarkerSize, Math.min(cWidth, cHeight));mMarkerDrawable.setBounds(pLeft, pTop,pLeft + markerSize, pTop + markerSize);bounds = mMarkerDrawable.getBounds();} else {bounds = new Rect(pLeft, pTop, pLeft + cWidth, pTop + cHeight);}int halfLineSize = mLineSize >> 1;int lineLeft = bounds.centerX() - halfLineSize;if (mBeginLine != null) {mBeginLine.setBounds(lineLeft, 0, lineLeft + mLineSize, bounds.top);}if (mEndLine != null) {mEndLine.setBounds(lineLeft, bounds.bottom, lineLeft + mLineSize, height);}}initDrawableSize 方法進(jìn)行具體的運(yùn)算,而運(yùn)算的時(shí)間點(diǎn)就是當(dāng)控件的大小改變(onSizeChanged)的時(shí)候。
在初始化中采用了一定的投機(jī)取巧;這里利用了上內(nèi)邊距與下內(nèi)邊距分別作為上線條與下線條的長度;而線條與中間的標(biāo)識都采用了水平距中。
3.5 其他設(shè)置方法
public void setLineSize(int lineSize) {if (mLineSize != lineSize) {this.mLineSize = lineSize;initDrawableSize();invalidate();}}public void setMarkerSize(int markerSize) {if (this.mMarkerSize != markerSize) {mMarkerSize = markerSize;initDrawableSize();invalidate();}}public void setBeginLine(Drawable beginLine) {if (this.mBeginLine != beginLine) {this.mBeginLine = beginLine;if (mBeginLine != null) {mBeginLine.setCallback(this);}initDrawableSize();invalidate();}}public void setEndLine(Drawable endLine) {if (this.mEndLine != endLine) {this.mEndLine = endLine;if (mEndLine != null) {mEndLine.setCallback(this);}initDrawableSize();invalidate();}}public void setMarkerDrawable(Drawable markerDrawable) {if (this.mMarkerDrawable != markerDrawable) {this.mMarkerDrawable = markerDrawable;if (mMarkerDrawable != null) {mMarkerDrawable.setCallback(this);}initDrawableSize();invalidate();}}在設(shè)置中,首先判斷是否更改,如果更改那么就更新并重新計(jì)算位置;隨后刷新界面。
到這里,控件差不多準(zhǔn)備OK了,其中還有很多可以完善的地方,比如加上快捷設(shè)置顏色什么的,也可以加上大小計(jì)算的東西。同時(shí)還可以加上時(shí)間線是水瓶還是垂直等等。在這里就不累贅介紹哪些了。下面來看看如何使用。
3.6 使用XML布局ITEM布局item_time_line.xml
<?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"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="wrap_content"android:orientation="horizontal"android:paddingLeft="@dimen/lay_16"android:paddingRight="@dimen/lay_16"tools:ignore="MissingPrefix"><net.qiujuer.example.timeline.widget.TimeLineMarker android:id="@+id/item_time_line_mark"android:layout_width="wrap_content"android:layout_height="match_parent"android:paddingBottom="@dimen/lay_16"android:paddingLeft="@dimen/lay_4"android:paddingRight="@dimen/lay_4"android:paddingTop="@dimen/lay_16"app:beginLine="@color/black_alpha_32"app:endLine="@color/black_alpha_32"app:lineSize="2dp"app:marker="@drawable/ic_timeline_default_marker"app:markerSize="24dp" /><TextView android:id="@+id/item_time_line_txt"android:layout_width="match_parent"android:layout_height="wrap_content"android:layout_gravity="center"android:paddingBottom="@dimen/lay_16"android:paddingLeft="@dimen/lay_4"android:paddingRight="@dimen/lay_4"android:paddingTop="@dimen/lay_16"android:textColor="@color/grey_600"android:textSize="@dimen/font_16" /></LinearLayout>在這里我們之間使用順序布局,左邊是TimelIne控件,右邊是一個(gè)簡單的字體控件,具體使用中可以細(xì)化一些。
在TImeLine控件中我們的Mark是使用的drawable/ic_timeline_default_marker;這個(gè)就是一個(gè)簡單的圓圈而已;對于自己美化可以使用一張圖片代替或者更加復(fù)雜的布局;當(dāng)然上面的線條就更加簡單了,就直接使用顏色代替。
<?xml version="1.0" encoding="utf-8"?> <shape xmlns:android="http://schemas.android.com/apk/res/android"android:shape="oval"><solid android:color="@color/cyan_500" /><stroke android:width="1dp"android:color="@color/black_alpha_32" /> </shape>3.7 主界面XML RecyclerView
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"android:paddingBottom="@dimen/activity_vertical_margin"android:paddingLeft="@dimen/activity_horizontal_margin"android:paddingRight="@dimen/activity_horizontal_margin"android:paddingTop="@dimen/activity_vertical_margin"tools:context=".MainActivity"><android.support.v7.widget.RecyclerView android:id="@+id/time_line_recycler"android:layout_width="match_parent"android:layout_height="match_parent"android:clickable="true"android:fadeScrollbars="true"android:fadingEdge="none"android:focusable="true"android:focusableInTouchMode="true"android:overScrollMode="never"android:scrollbarSize="2dp"android:scrollbarThumbVertical="@color/cyan_500"android:scrollbars="vertical" /></RelativeLayout>在這里就是加上了一個(gè)RecyclerView 控件在主界面就OK。
4. Java代碼部分
在開始之前先來看看我們的文件具體有些神馬。
widget中就是具體的自定義控件,model是具體的數(shù)據(jù)模型,adapter部分,這里有一個(gè)Recyclerview的adapter文件,以及一個(gè)具體的Item TimeLineViewHolder,當(dāng)然在這里還定義了一個(gè)ItemType類,該類用來標(biāo)示每個(gè)Item的類型,比如頭部,第一個(gè),普通,最后一個(gè),底部等等。
4.1 TimeLineModel.java
package net.qiujuer.example.timeline.model;public class TimeLineModel {private String name;private int age;public TimeLineModel() {}public TimeLineModel(String name, int age) {this.name = name;this.age = age;}public int getAge() {return age;}public String getName() {return name;}public void setAge(int age) {this.age = age;}public void setName(String name) {this.name = name;} }一個(gè)名字,一個(gè)年齡。
4.2 ItemType.java
package net.qiujuer.example.timeline.adapter;public class ItemType {public final static int NORMAL = 0;public final static int HEADER = 1;public final static int FOOTER = 2;public final static int START = 4;public final static int END = 8;public final static int ATOM = 16; }分別定義了幾個(gè)靜態(tài)值,分別代表普通、頭部、底部、開始、結(jié)束、原子;當(dāng)然其中有些可以不用定義。
4.3 TimeLineViewHolder.java
package net.qiujuer.example.timeline.adapter;import android.support.v7.widget.RecyclerView; import android.view.View; import android.widget.TextView; import net.qiujuer.example.timeline.R; import net.qiujuer.example.timeline.model.TimeLineModel; import net.qiujuer.example.timeline.widget.TimeLineMarker;public class TimeLineViewHolder extends RecyclerView.ViewHolder {private TextView mName;public TimeLineViewHolder(View itemView, int type) {super(itemView);mName = (TextView) itemView.findViewById(R.id.item_time_line_txt);TimeLineMarker mMarker = (TimeLineMarker) itemView.findViewById(R.id.item_time_line_mark);if (type == ItemType.ATOM) {mMarker.setBeginLine(null);mMarker.setEndLine(null);} else if (type == ItemType.START) {mMarker.setBeginLine(null);} else if (type == ItemType.END) {mMarker.setEndLine(null);}}public void setData(TimeLineModel data) {mName.setText("Name:" + data.getName() + " Age:" + data.getAge());} }該文件為RecyclerView 的Adapter中每個(gè)Item需要實(shí)現(xiàn)的Holder類。
在該類中,我們在構(gòu)造函數(shù)中需要傳入一個(gè)根View同時(shí)傳入一個(gè)當(dāng)然item的狀態(tài)。
隨后使用find….找到控件,在這里我們把TextView保存起來,而TimeLineView找到后直接進(jìn)行初始化設(shè)置。
根據(jù)傳入的ItemType來判斷是否是第一個(gè),最后一個(gè),以及原子;然后設(shè)置TimeLineView的屬性。
在下面的setData方法中我們顯示具體的Model數(shù)據(jù)。
4.4 TimeLineAdapter.java
適配器部分,我們需要做的工作是;根據(jù)具體的數(shù)據(jù)渲染上對應(yīng)的界面。
package net.qiujuer.example.timeline.adapter;import android.support.v7.widget.RecyclerView; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import net.qiujuer.example.timeline.R; import net.qiujuer.example.timeline.model.TimeLineModel; import java.util.List;public class TimeLineAdapter extends RecyclerView.Adapter<TimeLineViewHolder> {private List<TimeLineModel> mDataSet;public TimeLineAdapter(List<TimeLineModel> models) {mDataSet = models;}@Overridepublic int getItemViewType(int position) {final int size = mDataSet.size() - 1;if (size == 0)return ItemType.ATOM;else if (position == 0)return ItemType.START;else if (position == size)return ItemType.END;else return ItemType.NORMAL;}@Overridepublic TimeLineViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) {// Create a new view.View v = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.item_time_line, viewGroup, false);return new TimeLineViewHolder(v, viewType);}@Overridepublic void onBindViewHolder(TimeLineViewHolder timeLineViewHolder, int i) {timeLineViewHolder.setData(mDataSet.get(i));}@Overridepublic int getItemCount() {return mDataSet.size();} }在這里需要著重說一下:我復(fù)寫了getItemViewType()方法;在該方法中我們需要設(shè)置對應(yīng)的Item的類型;在這里傳入的是item的坐標(biāo),需要返回的是item的具體狀態(tài),該狀態(tài)標(biāo)示是int類型;在這里我使用的是ItemType的靜態(tài)屬性。
該方法會(huì)在調(diào)用onCreateViewHolder方法之前調(diào)用;而onCreateViewHolder方法中的第二個(gè)參數(shù)int值也就是從getItemViewType之中來;所以我們可以在這里進(jìn)行對應(yīng)的數(shù)據(jù)狀態(tài)標(biāo)示。
而在onCreateViewHolder()方法中我們返回一個(gè):TimeLineViewHolder就OK,隨后在onBindViewHolder方法中進(jìn)行數(shù)據(jù)初始化操作。
4.5 MainActivity.java
上面所有都準(zhǔn)備好了,下面就進(jìn)行具體的顯示。 在這里就只貼出核心代碼了;篇幅也是有些長。
private RecyclerView mRecycler;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);mRecycler = (RecyclerView) findViewById(R.id.time_line_recycler);initRecycler();}private void initRecycler() {LinearLayoutManager layoutManager = new LinearLayoutManager(this);layoutManager.setOrientation(LinearLayoutManager.VERTICAL);TimeLineAdapter adapter = new TimeLineAdapter(getData());mRecycler.setLayoutManager(layoutManager);mRecycler.setAdapter(adapter);}private List<TimeLineModel> getData() {List<TimeLineModel> models = new ArrayList<TimeLineModel>();models.add(new TimeLineModel("XiaoMing", 21));models.add(new TimeLineModel("XiaoFang", 20));models.add(new TimeLineModel("XiaoHua", 25));models.add(new TimeLineModel("XiaoA", 22));models.add(new TimeLineModel("XiaoNiu", 23));return models;}在這里就是傻瓜的操作了,流程就是準(zhǔn)備好對應(yīng)的數(shù)據(jù),裝進(jìn)Adapter,準(zhǔn)備好對應(yīng)的布局方式,然后都設(shè)置到RecyclerView中就OK
效果雖然簡單,但是也算是五臟具全;其中無非就是控件的自定義。這個(gè)自定義是可以擴(kuò)展的,大家可以擴(kuò)展為水平方向試試。
GitHub地址:https://github.com/qiujuer/BeFoot
5. UPMiss
GitHub地址:https://github.com/qiujuer/UPMiss
原文出處:http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2015/0825/3364.html
總結(jié)
以上是生活随笔為你收集整理的RecyclerView列表控件漂亮时间线实现的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: EventBus3.0开发详解 近万开发
- 下一篇: Java基础:反射