Android使用自定義View實現(xiàn)橫行時間軸效果
前言
本篇文章會說下如何使用并且要用麻煩的自定義 view 去實現(xiàn)時間軸效果,以及如何分析、實現(xiàn)自定義 view。 需要具備的知識:Paint、Canvas、自定義 view 的繪制流程。
歡迎留言交流。
一、已經(jīng)有很多 RecycleView 實現(xiàn)時間軸的例子,為何還要費勁的使用自定義 view 去實現(xiàn)時間軸?
首先看下最終想要的效果:
根據(jù)上圖可以總結(jié)出以下幾點:
- 每個階段要顯示時間、階段名、狀態(tài)圖標(biāo)、中間有虛線;
- 文字上下交錯顯示;
- 相鄰階段的文字在垂直方向上是可以相交的;
- 時間軸的個數(shù)不確定,但是要鋪滿屏幕并且不可滑動; 如果只實現(xiàn)上兩點的效果,使用 RecycleView 無疑是最好的選擇,但是要同時實現(xiàn)以上整個效果目前想到的最好的辦法就是使用自定義 view。
二、如何開始?
相信也有人跟我一樣,對自定義的繪制過程 view、canvas、path、paint 的使用有了解,但是真的要去寫自定義 view 確不知道從何開始,不知道第一步如何下手。我個人的總結(jié)就是:想要的太多,遲遲不動手,所以有想法一定要去動手試驗! 不要想著寫完第一次運行就是最終想展示的完美效果,而是要抱著整體拆分成不重復(fù)的小塊,然后去繪制重復(fù)塊,然后去一點點實現(xiàn)一步步完美的心態(tài)才能做出來。
所以首先要把想實現(xiàn)的 view 拆分成一個個小的可繪制的并且沒有重復(fù)的塊,以目前想實現(xiàn)的時間軸效果來說,最小可繪制無重復(fù)塊也就是只包括一個時間結(jié)點的塊如圖:
它包括:
- 垂直居中的一條虛線;
- 一個表示狀態(tài)的圖標(biāo);
- 一個顯示時間的文本塊;
- 一個顯示階段名的文本塊;
三、開始畫
有了上面的分析,接下來就要開始畫了。
1. 畫中間的線
首先畫虛線,如果虛線不知道怎么畫,可以先畫一條實線,然后再去找畫虛線的方法。
使用 canvas 中畫線的方法 drawLine(float startX, float startY, float stopX, float stopY, @NonNull Paint paint)
根據(jù)參數(shù)得知需知道線的起點與終點坐標(biāo)以及一個 paint 對象,因為是垂直居中且橫穿整個控件的直線所以可以確定兩個點的 y 坐標(biāo)是一樣的,也就是控件高的一半,起點的 x 坐標(biāo)為0,終點的 x 坐標(biāo)為控件的寬。也就是知道控件的寬和高之后就可以繪制出這條線。獲取控件的寬高,可以在 onMeasure 方法中獲?。?/p>
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); mViewWidth = MeasureSpec.getSize(widthMeasureSpec) - dip2px(mContext, mSafeDistance * 2); mViewHeight = MeasureSpec.getSize(heightMeasureSpec); }
畫線的代碼(在 onDraw 方法中添加,下面其它的繪制方法同樣是在 onDraw 方法中添加):
// 定義畫筆,并設(shè)置相關(guān)屬性 Paint mLinePaint = new Paint(); mLinePaint.setColor(Color.parseColor("#999999")); mLinePaint.setStrokeWidth(1); mLinePaint.setStyle(Paint.Style.STROKE); // 畫虛線 canvas.drawLine(0, mViewHeight / 2, mViewWidth, mViewHeight / 2, mLinePaint);
2. 畫圖標(biāo)
canvas 畫圖標(biāo)的方法:drawBitmap(@NonNull Bitmap bitmap, float left, float top, @Nullable Paint paint),根據(jù)方法的參數(shù)去分析如何準備值,這里需要一個 bitmap 對象,起點坐標(biāo)以及 paint。bitmap 對象可以將資源文件 drawable 轉(zhuǎn)為 bitmap 格式;坐標(biāo)就是控件的中心點。畫圖標(biāo)的代碼:
// 圖標(biāo) x,y 坐標(biāo) Bitmap statusBitmap = BitmapFactory.decodeResource(mContext.getResources(), R.mipmap.ic_no_pass); float bX = mViewWidth / 2; // 垂直的中心點在圖標(biāo)的頂部,所以要減去 bitmap 高的一半 float bY = mViewHeight / 2 - statusBitmap.getHeight() / 2f; Paint mBitmapPaint = new Paint(); mBitmapPaint.setFilterBitmap(true); canvas.drawBitmap(statusBitmap, bX, bY, mBitmapPaint);
3. 畫文本
canvas 畫文本的方法:drawText(@NonNull String text, float x, float y, @NonNull Paint paint),依然是根據(jù)方法得知需要知道繪制的內(nèi)容,開始的坐標(biāo)點以及 paint。當(dāng)文本在圖標(biāo)上方時,文本的 y 坐標(biāo)需要使用圖標(biāo)的 y 坐標(biāo)減去文本到圖標(biāo)的距離,x 坐標(biāo)同圖片的 x 坐標(biāo)一樣;當(dāng)文本在圖標(biāo)下方時,文本的 y 坐標(biāo)需要使用圖標(biāo)的 y 坐標(biāo)加上文本到圖標(biāo)的距離。畫文本的代碼:
// 定義畫筆 Paint mDatePaint = new Paint(); mDatePaint.setColor(Color.parseColor("#666666")); mDatePaint.setTextSize(dip2px(mContext, 12)); mDatePaint.setStyle(Paint.Style.FILL); mDatePaint.setTextAlign(Paint.Align.CENTER); mDatePaint.setAntiAlias(true); Paint mNamePain = new Paint(); mNamePain.setColor(Color.parseColor("#666666")); mNamePain.setTextSize(dip2px(mContext, 13)); mNamePain.setStyle(Paint.Style.FILL); mNamePain.setTextAlign(Paint.Align.CENTER); mNamePain.setAntiAlias(true); // 定義坐標(biāo)變量 float dateX = bX + statusBitmap.getWidth() / 2f; float dateY; dateY = mViewHeight / 2 - dip2px(mContext, 19); // 畫文字,在圖標(biāo)上 canvas.drawText("有效時間", dateX, dateY, mNamePain); canvas.drawText("09.27-09.29", dateX, dateY - dateTextHeight mDatePaint); // 畫文字,在圖標(biāo)下 dateY = mViewHeight / 2 + dip2px(mContext, 19); canvas.drawText("09.27-09.29", dateX, dateY, mDatePaint); canvas.drawText("有效時間", dateX, dateY + dateTextHeigh, mNamePain);
4. 由局部到整體
上面已經(jīng)完成了只有一個時間點的繪制,接下來思考如果有多個時間點時如何繪制。只有一個時間點時計算坐標(biāo)是以控件的寬高進行計算,那么當(dāng)有兩個時間點的時候需要首先把控件均分成兩部分,然后在均分的部分中計算對應(yīng)的坐標(biāo),完成繪制。當(dāng)有三個時間點的時候需要均分為三部分,然后在各自的部分計算對應(yīng)的坐標(biāo),完成繪制。所以得到不論時間點的個數(shù)有多少繪制的方法不會改變,需要改變的是繪制時候用到的點的坐標(biāo)。其實已經(jīng)可以看出,當(dāng)多個點的時候需要循環(huán)一下,代碼如下:
// 得到多個點時,其中每個部分的寬,itevW 也就等同與上面只有一個時間點時控件的寬 float itemW = mViewWidth / mDataList.size(); for (int i = 0; i < mDataList.size(); i++) { // 完成相關(guān)計算、繪制 }
5. 完善
到這里,整個分析及繪制就差不多要結(jié)束了。
繪制虛線的方法:mLinePaint.setPathEffect(new DashPathEffect(new float[]{10, 10}, 0));
繪制虛線時遇到一個問題,在手機上不顯示虛線效果,后來查到需要關(guān)閉 view 層的硬件加速:setLayerType(View.LAYER_TYPE_SOFTWARE, null);
繪制文本時的中心點計算需要注意下
paint、bitmap 等變量的初始化建議放到初始化方法中去做,不建議在 onDraw 方法中做。
四、總結(jié)
關(guān)于自定義 view 一定要多看,多嘗試。
好了,以上就是這篇文章的全部內(nèi)容了,希望本文的內(nèi)容對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,謝謝大家對腳本之家的支持。
相關(guān)文章
使用Kotlin開發(fā)Android應(yīng)用的初體驗
本篇文章主要介紹了使用Kotlin開發(fā)Android應(yīng)用的初體驗,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-05-05Android開發(fā)之開門狗在程序鎖中的應(yīng)用實例
這篇文章主要介紹了Android開發(fā)之開門狗在程序鎖中的應(yīng)用,以完整實例形式分析了程序鎖的使用技巧,需要的朋友可以參考下2016-02-02淺談Android為RecyclerView增加監(jiān)聽以及數(shù)據(jù)混亂的小坑
下面小編就為大家?guī)硪黄獪\談Android為RecyclerView增加監(jiān)聽以及數(shù)據(jù)混亂的小坑。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-04-04Android自定義ToolBar并實現(xiàn)沉浸式的方法
這篇文章主要給大家介紹了關(guān)于Android自定義ToolBar并實現(xiàn)沉浸式的相關(guān)資料,文中通過示例代碼介紹的非常詳細,對各位Android開發(fā)者們具有一定的參考學(xué)習(xí)價值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧2020-05-05一文了解Android?ViewModelScope?如何自動取消協(xié)程
這篇文章主要介紹了一文了解Android?ViewModelScope?如何自動取消協(xié)程,文章圍繞主題站展開詳細的內(nèi)容介紹,具有一定參考價值,感興趣的小伙伴可以參考一下2022-07-07