1. 程式人生 > >Android-自定義view之無所不能的path

Android-自定義view之無所不能的path

自定義view之無所不能的path

最近專案中需要完成以下這個需求
這裡寫圖片描述

UI給我了五張圖片,我感覺太浪費了,自定義view完全可以做而且適配起來更加的方便

最終實現效果

  • 專案效果
    這裡寫圖片描述

  • 擴充套件
    擴充套件1

    擴充套件2

需要知道技術點

在實現這個過程之前,我們需要了解path的一系列的原理(如果你瞭解path的用法直接跳過)

PathMeasure(是一個用來測量Path的類,主要有以下方法)

這裡寫圖片描述

  • setPath、 isClosed 和 getLength

這三個方法都如字面意思一樣,非常簡單,這裡就簡單是敘述一下,不再過多講解。
setPath 是 PathMeasure 與 Path 關聯的重要方法,效果和 建構函式 中兩個引數的作用是一樣的。
isClosed 用於判斷 Path 是否閉合,但是如果你在關聯 Path 的時候設定 forceClosed 為 true 的話,這個方法的返回值則一定為true。
getLength 用於獲取 Path 的總長度

  • getSegment
//返回值(boolean)    判斷擷取是否成功    true 表示擷取成功,結果存入dst中,false 擷取失敗,不會改變dst中內容
//startD    開始擷取位置距離 Path 起點的長度 取值範圍: 0 <= startD < stopD <= Path總長度
//stopD 結束擷取位置距離 Path 起點的長度 取值範圍: 0 <= startD < stopD <= Path總長度
//dst   擷取的 Path 將會新增到 dst 中    注意: 是新增,而不是替換
//startWithMoveTo   起始點是否使用 moveTo  用於保證擷取的 Path 第一個點位置不變
//如果 startD、stopD 的數值不在取值範圍 [0, getLength] 內,或者 startD == stopD 則返回值為 false,不會改變 dst 內容。 //如果在安卓4.4或者之前的版本,在預設開啟硬體加速的情況下,更改 dst 的內容後可能繪製會出現問題,請關閉硬體加速或者給 dst 新增一個單個操作,例如: dst.rLineTo(0, 0) boolean getSegment (float startD, float stopD, Path dst, boolean startWithMoveTo)
  • getPosTan
/*這個方法是用於得到路徑上某一長度的位置以及該位置的正切值:
引數    作用    備註
返回值(boolean)    判斷獲取是否成功    true表示成功,資料會存入 pos 和 tan 中,
false 表示失敗,pos 和 tan 不會改變
distance    距離 Path 起點的長度   取值範圍: 0 <= distance <= getLength
pos 該點的座標值  座標值: (x==[0], y==[1])
tan 該點的正切值  正切值: (x==[0], y==[1])
*/
boolean getPosTan (float distance, float[] pos, float[] tan)
  • getMatrix

這個方法是用於得到路徑上某一長度的位置以及該位置的正切值的矩陣:

/*
返回值(boolean)    判斷獲取是否成功    true表示成功,資料會存入matrix中,false 失敗,matrix內容不會改變
distance    距離 Path 起點的長度   取值範圍: 0 <= distance <= getLength
matrix  根據 falgs 封裝好的matrix 會根據 flags 的設定而存入不同的內容
flags   規定哪些內容會存入到matrix中   可選擇
POSITION_MATRIX_FLAG(位置)
ANGENT_MATRIX_FLAG(正切)

*/
boolean getMatrix (float distance, Matrix matrix, int flags)

實現

可以明顯的看出這個view的5個園的圓心都在一個大的圓上
這裡寫圖片描述

通過path得到一個園,然後將圓分割5份

Path pathCircle = new Path();
pathCircle.addCircle(with / 2, hight / 2, hight / 2 - pading - radius, Path.Direction.CW);

通過PathMeasure的getPosTan方法得到等分點在圓上的座標,然後判斷當前的狀態,給選中的狀態圓不同的顏色值

 float[] position = new float[2];
        for (int index = 0; index < 5; index++) {
            if (currentPosition == index) {
                paint.setColor(Color.RED);
            } else {
                paint.setColor(Color.BLUE);
            }
            float allLength = pathMeasure.getLength();
            distance = (allLength / 5) * (index + 1);
            pathMeasure.getPosTan(distance, position, tan);
            canvas.drawCircle(position[0], position[1], radius, paint);
   }

這裡寫圖片描述

實現完以後我們發現問題,圓的位置每個圓環的位置和效果圖不是一樣的,那是為什麼呢?

其實在path新增大圓的時候我們只能控制path路徑的軌跡方向,並不能指定其開始位置,而且現在我們寫死了很多變數:顏色,圓環數等*
解決辦法:那我們用arc(圓弧)去畫指定其實位置;通過指定要屬性實現動態新增屬性;

優化

畫出圓弧,指定開始位置為正上方及時-90°

Path pathCircle = new Path();
RectF rectF = new RectF(pading + radius, pading + radius, with - pading - radius, hight - pading - radius);
pathCircle.arcTo(rectF, -90, 359);

通過自定義屬性動態指定引數

    //    寬
    private int with;
    //    高
    private int hight;
    //    間距
    private int pading;
    //    小圓環半徑
    private int radius;
    //    圓環寬度
    private int paintWith;
    //    圓環數
    private int pie;
    //    當前選中圓環
    private int currentPosition;
    //    正常顏色
    private int normalColor;
    //    選中顏色
    private int clickColor;
    //    畫筆
    private Paint paint;


    public ProgressCircleView(Context context, AttributeSet attrs) {
        super(context, attrs);
        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ProgressCircleOldView);
        pading = a.getDimensionPixelOffset(R.styleable.ProgressCircleOldView_pading, 0);
        radius = a.getDimensionPixelOffset(R.styleable.ProgressCircleOldView_radius, 10);
        paintWith = a.getDimensionPixelOffset(R.styleable.ProgressCircleOldView_paintWith, 4);
        pie = a.getInt(R.styleable.ProgressCircleOldView_pie, 5);
        currentPosition = a.getInt(R.styleable.ProgressCircleOldView_currentPosition, 0);
        normalColor = a.getColor(R.styleable.ProgressCircleOldView_normalColor, Color.BLUE);
        clickColor = a.getColor(R.styleable.ProgressCircleOldView_clickColor, Color.RED);
        a.recycle();
        initPaint();
    }

對應的xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="ProgressCircleOldView">
        <!--間距-->
        <attr name="pading" format="dimension"/>
        <!--小圓環半徑-->
        <attr name="radius" format="dimension"/>
        <!--圓環寬度-->
        <attr name="paintWith" format="dimension"/>
        <!--圓環數-->
        <attr name="pie" format="integer"/>
        <!--當前選中圓環-->
        <attr name="currentPosition" format="integer"/>
        <!--正常顏色-->
        <attr name="normalColor" format="color"/>
        <!-- 選中顏色-->
        <attr name="clickColor" format="color"/>
    </declare-styleable>
</resources>

得到座標點,畫出圓

 @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        float[] position = new float[2];
        float[] tan = new float[2];
        float distance;
        Path pathCircle = new Path();
        RectF rectF = new RectF(pading + radius, pading + radius, with - pading - radius, hight - pading - radius);
        pathCircle.arcTo(rectF, -90, 359);
        PathMeasure pathMeasure = new PathMeasure(pathCircle, false);
        for (int index = 0; index < pie; index++) {
            if (currentPosition == index) {
                paint.setColor(clickColor);
            } else {
                paint.setColor(normalColor);
            }
            float allLength = pathMeasure.getLength();
            distance = (allLength / pie) * (index);
            pathMeasure.getPosTan(distance, position, tan);
            canvas.drawCircle(position[0], position[1], radius, paint);
        }
    }

到這裡我們基本已經完成了這個需求了但是估計大家還是沒有講PathMeasure沒有很好的理解,所以就有了下面的擴充套件

擴充套件

這裡寫圖片描述

上面的效果在很多場景中我們都能用到,不如載入、經度顯示等;其實通過動畫我們也可以實現,但是自定義view也是可以的,而且它的效率更高,
靈活性更加好,功能也可以做的更加強大,主要是你實現起來還很簡單哦!

其實上面的矩形和圓軌跡都是走的同樣的邏輯,不過是path添加了不同的圖形,所以你可以自由發揮哦,所以就拿上面的圓形進度為例子來講解了

path給定一個圖形

  Path path = new Path();
  path.addCircle(600, 400, 100, Path.Direction.CCW);

通過比getPosTan得到位置和偏移量

//        按照比例獲取
        progress = progress < 1 ? progress + 0.0005 : 0;
        Matrix matrix = new Matrix();
        paint.setColor(Color.YELLOW);
        measure.getPosTan((int) (measure.getLength() * progress), position, tan);

通過得到的點座標畫出箭頭

        Path path1 = new Path();
        path1.moveTo(position[0] - 20, position[1] + 20);
        path1.lineTo(position[0], position[1]);
        path1.lineTo(position[0] + 20, position[1] + 20);
//        是否閉合,閉合就是三角形了
        path1.close();

通過tan得到箭頭的偏移量

  Path path2 = new Path();
        float degrees = (float) (Math.atan2(tan[1], tan[0]) * 180.0 / Math.PI);
        matrix.setRotate(degrees + 90, position[0], position[1]);
        path2.addPath(path1, matrix);

通過getSegment得到進度上擷取的弧線,連結箭頭

  //        進度線
        measure.getSegment(-1000, (int) (measure.getLength() * progress), path2, true);
        paint.setColor(Color.BLUE);
        canvas.drawPath(path2, paint);

最後不斷的重新整理介面重畫

    /**
     * 繪製panth上每一個點的位置
     * 帶箭頭的進度框
     *
     * @param canvas
     */
    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    private void PaintMatr(Canvas canvas) {
        paint.setStrokeWidth(10);
        paint.setStyle(Paint.Style.STROKE);
        Path path = new Path();
        path.addCircle(600, 400, 100, Path.Direction.CCW);
        PathMeasure measure = new PathMeasure(path, false);
//        按照比例獲取
        progress = progress < 1 ? progress + 0.0005 : 0;
        Matrix matrix = new Matrix();
        paint.setColor(Color.YELLOW);
        measure.getPosTan((int) (measure.getLength() * progress), position, tan);
        canvas.drawPath(path, paint);

//        箭頭
        paint.setColor(Color.RED);
        Path path1 = new Path();
        path1.moveTo(position[0] - 20, position[1] + 20);
        path1.lineTo(position[0], position[1]);
        path1.lineTo(position[0] + 20, position[1] + 20);
//        是否閉合,閉合就是三角形了
        path1.close();
        Path path2 = new Path();
        float degrees = (float) (Math.atan2(tan[1], tan[0]) * 180.0 / Math.PI);
        matrix.setRotate(degrees + 90, position[0], position[1]);
        path2.addPath(path1, matrix);
        //        進度線
        measure.getSegment(-1000, (int) (measure.getLength() * progress), path2, true);
        paint.setColor(Color.BLUE);
        canvas.drawPath(path2, paint);
        invalidate();
    }

到這裡你也是path就完事了 no no no其實path還能結合SVG( 是一種向量圖,內部用的是 xml 格式化儲存方式儲存這操作和資料,你完全可以將 SVG 看作是 Path 的各項操作簡化書寫後的儲存格式)

svg和path的結合

SVG 是一種向量圖,內部用的是 xml 格式化儲存方式儲存這操作和資料,你完全可以將 SVG 看作是 Path 的各項操作簡化書寫後的儲存格式
他們結合能創找出很多意想不到的東西,有興趣的同學可以自己去研究一下

demo

原始碼

建議

相關推薦

Android-定義view無所不能的path

自定義view之無所不能的path 最近專案中需要完成以下這個需求 UI給我了五張圖片,我感覺太浪費了,自定義view完全可以做而且適配起來更加的方便 最終實現效果 專案效果 擴充套件 需要知道技術點 在實現這個過程之前,

Android定義ViewIndicatorView,顯示當前tab頁處位置的View

概述 Android IndicatorView的靈感來源於SlidingTabView,雖然有句“不重複”造輪子在先,本著練手的目的,還是寫了一個功能較為簡單的類似view。 其比SlidingTabView在功能上欠缺的一點是:暫時沒有新增“當內容顯示不

Android 定義View繪圖工具類Canvas+Paint+Path(onDraw方法)基礎詳解

本章節講述三個繪圖工具類Canvas(畫布),Paint(畫筆),Path(路徑) 1.Canvas(畫布)相關方法詳解 1.1. 方法:Canvas() 作用:建立一個空的畫布,可以使

Android定義View使用Path繪製手勢軌跡和水波效果

先看下效果圖: 繪製軌跡 繪製手指的軌跡主要是攔截View的onTouchEvent()方法,並根據手指的軌跡繪製path。path中有兩種可以實現的方法 1、Path.lineTo(x,y)方法 public class MoveP

Android定義View分貝儀

一、說明        最近在整理自定義View方面的知識,偶爾看到meizu工具箱的分貝儀效果,感覺它的實效效果還挺好的,遂想自己模擬實現練練手,廢話不多說,直接開擼。 二、效果圖 首先看一下效果圖: 看效果還挺炫酷

Android定義ViewCanvas

https://www.jianshu.com/p/fb18c28d6627 用繼承View的方式來自定義View,我們就需要重寫onDraw方法,也就是得咱自己來畫圖了。畫圖就得用到畫筆和畫布,也就是Paint和Canvas。我們來了解下Canvas。 Canvas Canvas我們可

Android 定義ViewCanvas詳解

自定義View的相關文章: Android 實現一個簡單的自定義View Android 自定義View步驟 Android Paint詳解 Android 自定義View之Canvas相關方法說明 Android 自定義View例項之 “京東跑”

Android : 定義View流式佈局

寫了一個很簡單的佈局 這是周圍圓框的drawable <?xml version="1.0" encoding="utf-8"?> <shape xmlns:android="http://schemas.android.com/apk/res/android">

android定義View仿通訊錄側邊欄滑動,實現A-Z字母檢索

我們的手機通訊錄一般都有這樣的效果,如下圖: OK,這種效果大家都見得多了,基本上所有的android手機通訊錄都有這樣的效果。那我們今天就來看看這個效果該怎麼實現。 一.概述 1.頁面功能分析 整體上來說,左邊是一個ListView,右邊是一個自定義View,但

Android定義ViewgetTextBounds()

在Android自定義View的過程中一定會用到Paint,而paint屬性中有一個方法getTextBounds(String text,int start,int end,Rext bounds),它的中文解釋是:返回一個包含中文的矩形邊界,位置為(start,end) 英文解釋:Retur

Android定義View定義屬性

在Android開發中經常會用到自定義View的情況,而在自定義View時,自定義屬性是必須用到的。 1、新建一個自定義View如CustomView 它的自定義屬性主要是通過declare-styleable標籤為其配置自定義屬性。具體做法是:在res/values/目錄下增加一個reso

Android -- 定義viewStepView

先看看實現的效果: 2,首先我們來看看我們常規的自定義view的基礎步驟吧         1,繼承View,重寫構造方法 2,自定義屬性 3,重寫onMeasure()測量控制元件高度 4,重寫onDra

Android 定義View 可隨意拖動的View

因為趕專案本人停更兩個月 從今天開始又可以更新了 今天說一下這個可隨意拖動的view 簡單說一下這個view效果 和 發展 一開始這種效果是使用在網頁端的特別是購物類 例如某寶 某東 購物車和客服視窗 都有使用這個懸浮可拖動的設計效果 後來才發展到的移動端 還有

Android 定義View咖啡動畫

文章目錄效果畫杯子畫杯墊畫煙霧 效果 大概思路 自定義view,直接繼承view 複寫onSizeChanged()方法,在此計算杯墊,杯子,煙霧效果的path 在onDraw()方法中,描繪杯墊,杯子 處理煙霧動畫效果 畫杯子 這裡需要畫兩部分內容,第

Android 定義View下雨動畫

文章目錄效果思路畫雲畫雨滴優化 效果 開始前先做個熱身( ˘•灬•˘ ) 自己實現比較容易,但是到了要出部落格整理思路,總結要點的時候就撓頭,不知雲所以,所以最簡單的還是 如果對安卓UI有興趣的朋友可以加我好友互相探討, 思路 思路比較簡單,整個view無

android定義View定義EditText(新增刪除功能)

           忙忙碌碌20天,新的專案終於接近尾聲了。今天公司召集幾個使用者體驗師和美工一起吐糟這20天做的這個新產品,對於產品提出了很多建議,這幾天就改介面了。在這個專案中大量的使用了EditText元件,並且添加了刪除功能。這裡面都是用RelativeLayou

android定義view畫圓隨著手指移動

public class MyView extends View { private Paint mFanPaint,mTextPaint;//扇形畫筆和文字畫筆 public float AxisX=100; public float AxisY=100; public MyView(

Android定義view實現帶checkbox的Snackbar

前言 最近專案要求實現一個類似於snackbar功能,但是又不完全是snackbar的外掛,本來想在Google提供的snackbar裡面進行更改,但是這樣太麻煩了,於是自己動手實現了一個snackbar。先看下效果圖: 1.要解決的問題 1.彈框裡面除了文字提示之外還有一個按鈕,這個按

android定義View3D索引效果

效果圖: 我的小霸王太卡了。 最近工作比較忙,今天搞了一下午才搞出來這個效果,這種效果有很多種實現方式,最常見的應該是用貝塞爾曲線實現的。今天我們來看另一種不同的實現方式,只需要用到 canvas.scale(),有沒有很好奇是怎麼實現的呢。 首先來說一下思路,只要有了思

android 定義view側滑效果

效果圖: 看網上的都是兩個view拼接,預設右側的不顯示,水平移動的時候把右側的view顯示出來。但是看最新版QQ上的效果不是這樣的,但給人的感覺卻很好,所以獻醜來一發比較高仿的。 知識點: 1、ViewDragHelper 的用法; 2、滑動衝突的解決; 3、自定