Android自定義recyclerView實(shí)現(xiàn)時(shí)光軸效果
時(shí)光軸效果在很多app上都有出現(xiàn),例如淘寶中快遞的跟蹤,本文將使用recyclerView實(shí)現(xiàn)時(shí)光軸效果,我們會(huì)到自定義控件,首先先看一下效果圖:
接下來(lái)是步驟分析
1自定義屬性
這個(gè)大家應(yīng)該都了解了,根據(jù)我們之前的分析,直接在attrs.xml中進(jìn)行聲明
<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>
進(jìn)行一下各個(gè)屬性的聲明
• beginLine:開始的線條
• endLine:下面的線條
• lineWidth:線條的寬度
• timeLineImage:中間的圓形
• timeLineImageSize:中間的圓形的大小,這里默認(rèn)他的寬高一致
2.自定義TimeLine繼承View,構(gòu)造方法如下
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.復(fù)寫onMeasure方法
我們都知道自定義控件,一般需要重寫onMeasure,onDraw,onLayout方法,這里onMeasure需要對(duì)wrap_content的情況進(jìn)行特殊處理,具體原因請(qǐng)看源碼
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源碼的同學(xué)應(yīng)該知道,在控件進(jìn)行測(cè)量的時(shí)候,需要根據(jù)
specMode來(lái)進(jìn)行具體的操作,View中提供了resolveSizeAndState方法來(lái)進(jìn)行判斷,該方法源碼如下:
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的長(zhǎng)度,其實(shí)是我們自定義控件的paddingTop高度,同理mEndLine的長(zhǎng)度是paddingBottom高度,所以我們?cè)谑褂眠@個(gè)控件時(shí),一般都會(huì)設(shè)置paddingTop和paddingBottom
6.使用TimeLine控件
以下是recyclerView中一個(gè)item的布局,多個(gè)item拼接起來(lái)就是一條時(shí)光軸,這里需要說明的是,我們的 LinearLayout使用的高度模式是wrap_content,這里我的TextView設(shè)置了android:paddingTop="30dp",如果不對(duì)TextView設(shè)置android:paddingTop,會(huì)發(fā)現(xiàn)TimeLineView控件是看不見的,這是由于父控件wrap_content,那么父控件包裹TextView的內(nèi)容,那么父控件的高度就是TextView的高度,這樣TimeLineView設(shè)置了android:paddingTop="34dp",這個(gè)高度是大于父控件的高度的,所以就看不到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的使用大家應(yīng)該都很熟悉了,無(wú)非就是設(shè)置adapter,viewHolder等,這里不再贅述,還有一點(diǎn)需要強(qiáng)調(diào)的是ItemViewType有四種情況,第一個(gè),最后一個(gè),中間,還有只有一個(gè)四種情況情況,根據(jù)這幾種情況,有選擇設(shè)置mBeginLine與 mEndLine是否進(jìn)行繪制
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; ? ? ? ? //結(jié)束 ? ? ? ? public final static int END = 2; ? ? ? ? //只有一條數(shù)據(jù),那么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("前任生日")); ? ? ? } }
運(yùn)行項(xiàng)目,就會(huì)呈現(xiàn)本文一開始的效果。
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Android WebView如何判斷是否滾動(dòng)到底部
大家好,本篇文章主要講的是Android WebView如何判斷是否滾動(dòng)到底部,感興趣的同學(xué)趕快來(lái)看一看吧,對(duì)你有幫助的話記得收藏一下2022-01-01Android使用MulticastSocket實(shí)現(xiàn)多點(diǎn)廣播圖片
這篇文章主要為大家詳細(xì)介紹了Android使用MulticastSocket實(shí)現(xiàn)多點(diǎn)廣播圖片,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-01-01Android launcher中模擬按home鍵的實(shí)現(xiàn)
這篇文章主要介紹了Android launcher中模擬按home鍵的實(shí)現(xiàn)的相關(guān)資料,需要的朋友可以參考下2017-05-05實(shí)例解析Android系統(tǒng)中的ContentProvider組件用法
這篇文章主要介紹了Android系統(tǒng)中的ContentProvider組件用法,舉例講解了ContentProvider傳遞數(shù)據(jù)及監(jiān)聽ContentProvider數(shù)據(jù)改變的方法,十分詳細(xì),需要的朋友可以參考下2016-04-04Android 中LayoutInflater.inflate()方法的介紹
這篇文章主要介紹了Android 中LayoutInflater.inflate()方法的介紹的相關(guān)資料,希望通過本文大家能掌握這部分內(nèi)容,需要的朋友可以參考下2017-09-09