Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android-自定義view之無所不能的path

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

編輯:關於Android編程

最近項目中需要完成以下這個需求

demo

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

最終實現效果

項目效果

demo

擴展

demo

demo

需要知道技術點

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

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

demo

setPath、 isClosed 和 getLength

這三個方法都如字面意思一樣,非常簡單,這裡就簡單是敘述一下,不再過多講解。
setPath 是 PathMeasure 與 Path 關聯的重要方法,效果和 構造函數 中兩個參數的作用是一樣的。
isClZ喎?/kf/ware/vc/" target="_blank" class="keylink">vc2VkINPD09rF0LbPIFBhdGggyse38bHVus+jrLWrysfI57n7xOPU2rnYwaogUGF0aCC1xMqxuvLJ6NbDIGZvcmNlQ2xvc2VkIM6qIHRydWUgtcS7sKOs1eK49re9t6i1xLe1u9jWtdTy0ru2qM6qdHJ1ZaGjPGJyIC8+DQpnZXRMZW5ndGgg08PT2rvxyKEgUGF0aCC1xNfcs6S2yDwvcD4NCmdldFNlZ21lbnQNCjxwcmUgY2xhc3M9"brush:java;"> //返回值(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個園的圓心都在一個大的圓上

demo

通過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);
   }

demo

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

其實在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



    
        
        
        
        
        
        
        
        
        
        
        
        
        
        
    

得到坐標點,畫出圓

 @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沒有很好的理解,所以就有了下面的擴展

擴展

demo

上面的效果在很多場景中我們都能用到,不如加載、經度顯示等;其實通過動畫我們也可以實現,但是自定義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 的各項操作簡化書寫後的存儲格式
他們結合能創找出很多意想不到的東西,有興趣的同學可以自己去研究一下

SVG解析成Path的解析庫

github開源庫

demo

項目源碼地址*戳我

帶卡片滑動結合地址*戳我

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved