android 日程安排view,RecyclerView 列表控件中简单实现时间线
時間
時間,時間,時間啊;走慢一點吧~
看見很多軟件中都有時間線的東西,貌似天氣啊,旅游啊什么的最多了;具體實現方式很多,在本篇文章中講解一種自定義View封裝的方式。
效果
先來看看效果。
分析
軟件中,可以看見前面的時間線也就是線條加上圓圈組成;當然這里的圓圈與線條也都是可以隨意換成其他的,比如圖片等等。
當然這里最簡單的來說,是上面一個線條,然后一個圓圈,然后下面一個線條;上線條在第一條數據時不做顯示,下線條在最后一條數據時不做顯示。
這里自定義布局部分也就是把旁邊的線條與圓圈封裝到一起,并使用簡單的方法來控制是否顯示。?當封裝好了后,與旁邊的文字部分也就是水瓶方向的線性布局了,然后設置為每一個的RecyclerView 的Item的布局也就完成了。
控件
控件很簡單,首先我們繼承View,取名為 TimeLineMarker 就OK。
Attrs 屬性
開始控件之前先準備好需要的屬性。<?xml ?version="1.0"?encoding="utf-8"?>
在這里也就準備了線條的大小、開始線條、結束線條、中間標示部分及大小。
屬性與現實private?int?mMarkerSize?=?24;
private?int?mLineSize?=?12;
private?Drawable?mBeginLine;
private?Drawable?mEndLine;
private?Drawable?mMarkerDrawable;
@Override
protected?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);
}
兩個大小屬性,3個具體的Drawable,然后在onDraw方法中進行具體的顯示也就OK。
構造與屬性初始化
在上面我們定義了屬性,在這里我們在構造函數中獲取XML所設置的屬性。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?attributes
final?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);
}
Drawable 的位置與大小初始化
屬性啥的有了,具體的Drawable 也有了,要顯示的地方調用也是OK了;但是如果沒有進行進行具體的位置調整這一切也都沒有意義。@Override
protected?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)?{
//?Size
int?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 方法進行具體的運算,而運算的時間點就是當控件的大小改變(onSizeChanged)的時候。
在初始化中采用了一定的投機取巧;這里利用了上內邊距與下內邊距分別作為上線條與下線條的長度;而線條與中間的標識都采用了水平距中。
其他設置方法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();
}
}
在設置中,首先判斷是否更改,如果更改那么就更新并重新計算位置;隨后刷新界面。到這里,控件差不多準備OK了,其中還有很多可以完善的地方,比如加上快捷設置顏色什么的,也可以加上大小計算的東西。同時還可以加上時間線是水瓶還是垂直等等。在這里就不累贅介紹哪些了。下面來看看如何使用。
使用
XML布局
ITEM布局item_time_line.xml<?xml ?version="1.0"?encoding="utf-8"?>
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">
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"?/>
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"?/>
在這里我們之間使用順序布局,左邊是TimelIne控件,右邊是一個簡單的字體控件,具體使用中可以細化一些。?在TImeLine控件中我們的Mark是使用的drawable/ic_timeline_default_marker;這個就是一個簡單的圓圈而已;對于自己美化可以使用一張圖片代替或者更加復雜的布局;當然上面的線條就更加簡單了,就直接使用顏色代替。<?xml ?version="1.0"?encoding="utf-8"?>
android:shape="oval">
android:width="1dp"
android:color="@color/black_alpha_32"?/>
主界面XML RecyclerView
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: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"?/>
在這里就是加上了一個RecyclerView 控件在主界面就OK。
Java代碼部分
在開始之前先來看看我們的文件具體有些神馬。
widget中就是具體的自定義控件,model是具體的數據模型,adapter部分,這里有一個Recyclerview的adapter文件,以及一個具體的Item TimeLineViewHolder,當然在這里還定義了一個ItemType類,該類用來標示每個Item的類型,比如頭部,第一個,普通,最后一個,底部等等。
TimeLineModel.javapackage?net.qiujuer.example.timeline.model;
/**
*?Created?by?qiujuer
*?on?15/8/23.
*/
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;
}
}
一個名字,一個年齡也就OK。
ItemType.javapackage?net.qiujuer.example.timeline.adapter;
/**
*?Created?by?qiujuer
*?on?15/8/23.
*/
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;
}
分別定義了幾個靜態值,分別代表普通、頭部、底部、開始、結束、原子;當然其中有些可以不用定義。
TimeLineViewHolder.javapackage?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;
/**
*?Created?by?qiujuer
*?on?15/8/23.
*/
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中每個Item需要實現的Holder類。?在該類中,我們在構造函數中需要傳入一個根View同時傳入一個當然item的狀態。?隨后使用find….找到控件,在這里我們把TextView保存起來,而TimeLineView找到后直接進行初始化設置。?根據傳入的ItemType來判斷是否是第一個,最后一個,以及原子;然后設置TimeLineView的屬性。?在下面的setData方法中我們顯示具體的Model數據。
TimeLineAdapter.java
適配器部分,我們需要做的工作是;根據具體的數據渲染上對應的界面就OK。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;
/**
*?Created?by?qiujuer
*?on?15/8/23.
*/
public?class?TimeLineAdapter?extends?RecyclerView.Adapter?{
private?List?mDataSet;
public?TimeLineAdapter(List?models)?{
mDataSet?=?models;
}
@Override
public?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;
}
@Override
public?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);
}
@Override
public?void?onBindViewHolder(TimeLineViewHolder?timeLineViewHolder,?int?i)?{
timeLineViewHolder.setData(mDataSet.get(i));
}
@Override
public?int?getItemCount()?{
return?mDataSet.size();
}
}在這里需要著重說一下:我復寫了getItemViewType方法;在該方法中我們需要設置對應的Item的類型;在這里傳入的是item的坐標,需要返回的是item的具體狀態,該狀態標示是int類型;在這里我使用的是ItemType的靜態屬性。
該方法會在調用onCreateViewHolder方法之前調用;而onCreateViewHolder方法中的第二個參數int值也就是從getItemViewType之中來;所以我們可以在這里進行對應的數據狀態標示。
而在onCreateViewHolder方法中我們返回一個:TimeLineViewHolder就OK,隨后在onBindViewHolder方法中進行數據初始化操作。
MainActivity.java
上面所有都準備好了,下面就進行具體的顯示。?在這里就只貼出核心代碼了;篇幅也是有些長。private?RecyclerView?mRecycler;
@Override
protected?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?getData()?{
List?models?=?new?ArrayList();
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;
}
在這里就是傻瓜的操作了,流程就是準備好對應的數據,裝進Adapter,準備好對應的布局方式,然后都設置到RecyclerView中就OK。
效果
來看看具體的效果:
效果雖然簡單,但是也算是五臟具全;其中無非就是控件的自定義。這個自定義是可以擴展的,大家可以擴展為水平方向試試。
代碼
寫在最后
文章的開始截屏來源于:最近沒事兒搗鼓了一個APP[UPMiss],一個簡單的生日,紀念日提醒軟件;歡迎大家嘗鮮。
{UPMiss} 思念你的夏天?下載地址:百度?這個審核有問題,明明沒有支付的東西,結果說有支付的SDK存在,不得不說百度的自動審核有很大漏洞。
豌豆莢?新版2.0還在審核中!
========================================================?作者:qiujuer
總結
以上是生活随笔為你收集整理的android 日程安排view,RecyclerView 列表控件中简单实现时间线的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: NumPy (6)-结构化数据类型数组
- 下一篇: numpy(5)-astype