自定義View——Path圖形與邏輯運算

本文是我在閱讀了網絡上其他作者的優秀內容之后做的摘錄轉載,其中有對內容的補充。
原來地址:http://www.idtkm.com/customview/customview4/

涉及知識

Path

類型 API 描述
添加路徑 addArc, addCircle, addOval, addPath, addRect, addRoundRect, arcTo 依次為添加圓弧、圓、橢圓、路徑、矩形、圓角矩形、圓弧
移動起點 moveTo 移動起點位置,僅對之后路徑產生影響
移動終點 setLastPoint 移動上一次的終點位置,對前后的路徑都會產生影響
直線 lineTo 增加一條道指定點的直線
貝塞爾 quadTo, cubicTo 二階、三階貝塞爾曲線
閉合路徑 close 路徑終點連接到起點
邏輯運算 op A\B(DIFFERENCE), A∩B(INTERSECT), B\A(REVERSE_DIFFERENCE), A∪B(UNION), A⊕B(XOR)
替換路徑 set 用新的路徑替換當前路徑
重置 reset, rewind 清除path使它為空,清除path但保留內部的數據結構
計算邊界 computeBounds 計算路徑的矩形邊界
閉合方向 Direction 順時針方向閉合Path(CW),逆時針方向閉合Path(CCW)

本來這章應該是PieChart的實戰,可是我在編寫的時候發現了一個設置背景圖片的bug。作為一個強迫癥(? _ ?),我只好引入了Path來解決這個bug,所以就有了這一篇內容。

一、什么是Path

官方描述: Path class 封裝了由直線、二次、三次貝塞爾曲線構成的多重曲線幾何路徑。它可以用canvas.drawPath(path,paint)方法繪圖,填充和線都可以(根據paint的樣式),或者它可以用于在繪圖路徑上裁剪或者繪出文本。
我的理解: Path由任意多條直線、二次貝塞爾或三次貝塞爾曲線組成,可以選擇填充或者描邊模式,可以使用它裁剪畫布或者繪制文字。

二、添加路徑

1、lineTo,moveTo

在之前的文章中,使用canvas的函數繪制過[坐標系]
(http://www.idtkm.com/customview/customview2/),這次使用path來繪制。

  • a、創建畫筆

創建畫筆并初始化

  //創建畫筆
  private Paint mPaint = new Paint();
  private void initPaint(){ //初始化畫筆   
  mPaint.setStyle(Paint.Style.FILL);//設置畫筆類型 
  mPaint.setAntiAlias(true);//抗鋸齒}
  • b、繪制坐標軸

使用onSizeChanged方法,獲取根據父布局等因素確認的View寬高

  //寬高private int mWidth;
  private int mHeight;
  @Override
  protected void onSizeChanged(int w, int h, int oldw, int oldh) { 
      super.onSizeChanged(w, h, oldw, oldh); 
      mWidth = w; 
      mHeight = h;
  }

把原點從左上角移動到畫布中心,繪制原點與四個端點

   private Path mPath = new Path();
   canvas.translate(mWidth/2,mHeight/2);// 將畫布坐標原點移動到中心位置
   //繪制坐標原點
   mPaint.setColor(Color.BLACK);//設置畫筆顏色
   mPaint.setStrokeWidth(10);//為了看得清楚,設置了較大的畫筆寬度
   canvas.drawPoint(0,0,mPaint);//繪制坐標軸4個斷點
   canvas.drawPoints(new float[]{ 
        mWidth/2*0.8f,0 ,0,mHeight/2*0.8f ,-mWidth/2*0.8f,0 ,0,-mHeight/2*0.8f},mPaint);

增加坐標軸與箭頭的Path,在完成后使用canvas.drawPath一次進行繪制

  mPaint.setStrokeWidth(1);//恢復畫筆默認寬度
  //x軸mPath.moveTo(-mWidth/2*0.8f,0);
  //移動path起點到(-mWidth/2*0.8f,0)
  mPath.lineTo(mWidth/2*0.8f,0);
  //直線終點為(mWidth/2*0.8f,0)
  //y軸
  mPath.moveTo(0,-mHeight/2*0.8f);
  //移動path起點到(0,-mHeight/2*0.8f)
  mPath.lineTo(0,mHeight/2*0.8f);
  //直線終點為(0,mHeight/2*0.8f)
  //x箭頭
  mPath.moveTo(mWidth/2*0.8f*0.95f,-mWidth/2*0.8f*0.05f);
  mPath.lineTo(mWidth/2*0.8f,0);
  mPath.lineTo(mWidth/2*0.8f*0.95f,mWidth/2*0.8f*0.05f);
  //y箭頭
  mPath.moveTo(mWidth/2*0.8f*0.05f,mHeight/2*0.8f-mWidth/2*0.8f*0.05f);
  mPath.lineTo(0,mHeight/2*0.8f);
  mPath.lineTo(-mWidth/2*0.8f*0.05f,mHeight/2*0.8f-mWidth/2*0.8f*0.05f);
  //繪制
  Pathcanvas.drawPath(mPath,mPaint);

可以看出moveTo方法,可以移動下一次增加path的起點,而lineTo中的參數,即為直線的終點。

2、addArc與arcTo

方法 區別
addArc 畫一段圓弧
arcTo 畫一段圓弧,當上一次的終點與圓弧起點未連接時,可以設置是否連接這兩點

addArc

  r = Math.min(mWidth,mHeight)*0.6f/2;
  mRectF.left = 0;
  mRectF.top = -r;
  mRectF.right = r;
  mRectF.bottom = 0;
  mPath.addArc(mRectF,-60,180);
  //繪制
  Pathcanvas.drawPath(mPath,mPaint);

再來看看arcTo

  arcTomPath.moveTo(0,0);
  mPath.arcTo(mRectF,-60,180);
  //繪制
  Pathcanvas.drawPath(mPath,mPaint);

可以看到arcTo多了一條從原點到圓弧起點的直線,而如果設置為

  mPath.arcTo(mRectF,-60,180,false);

效果將和addArc相同。

三、圓角圖片以及更多形狀圖片

繼承ImageView,重寫父類的onSizeChanged方法,獲取View尺寸,之后根據View大小對圖片進行壓縮。

   @Override
   protected void onSizeChanged(int w, int h, int oldw, int oldh) {
           super.onSizeChanged(w, h, oldw, oldh);
           mViewWidth = w; 
           mViewHeight = h; 
           size();//切割尺寸計算 
           scaleBitmap();//壓縮圖片尺寸函數
    }

onDraw方法中進行樣式繪制,在其中使用clipPath的方法來實現圓角圖片。

    @Override
    protected void onDraw(Canvas canvas) { 
          canvas.translate(mViewWidth/2,mViewHeight/2);//將畫布坐標原點移動到中心位置  
          canvas.clipPath(pathFigure(), Region.Op.INTERSECT);//切割 mPath.reset();   
          canvas.drawBitmap(b,rect,rect,mPaint);
    }

scaleBitmap方法中對圖片的尺寸進行壓縮

    private void scaleBitmap(){ 
          Drawable drawable = getDrawable();//獲取圖片 
          if (drawable == null) { 
              return; 
          } if (getWidth() == 0 || getHeight() == 0) {
              return; 
          } if (!(drawable instanceof BitmapDrawable)) { 
              return; 
          } 
          b = ((BitmapDrawable) drawable).getBitmap();//獲取bitmap 
          if (null == b) { 
                return; 
          } 
          float scaleWidth = (float) length/b.getWidth();
          float scaleHeight = (float) length/b.getHeight();      
          matrix.postScale(scaleWidth,scaleHeight);//縮放矩陣    
          b=Bitmap.createBitmap(b,0,0,b.getWidth(),b.getHeight(),matrix,true);//壓縮圖片
    }

size方法中設置canvas的切割尺寸

    protected void size(){ 
        length = Math.min(mViewWidth,mViewHeight)/2; 
        rect = new Rect(-(int) length, -(int) length, (int) length, (int) length);//繪制圖片矩陣
     }

現在就是發揮想象力的時候啦?(?),來編寫pathFigure()方法吧*

  • a、先編寫一個簡單的圓形圖片樣式

    protected Path pathFigure(){ 
        switch (modeFlag){ 
            case CIRCLE: 
                  mPath.addCircle(0,0,length,         Path.Direction.CW);//增加圓的path,順時針閉合圓 
                  break;
         } 
         return mPath;
     }
    

  • b、增加一個圓角圖片樣式

      private RectF rectF = new RectF();
      case ROUNDRECT:
      rectF.left = -length; 
      rectF.top = -length; 
      rectF.right = length; 
      rectF.bottom = length;
      mPath.addRoundRect(rectF,radius,radius, Path.Direction.CW);//圓角矩形,radius為圓角的半徑,順時針閉合圓角矩形 break;
    

  • c、再增加一個扇形樣式

(PS:為了可以獲得更多的圖片面積,需要把圓心下移一個length的距離,半徑擴大到之前的兩倍)

  case SECTOR: 
      rectF.left = -length*2;
      rectF.top = -length; 
      rectF.right = length*2;
      rectF.bottom = length*3;
      mPath.moveTo(0,length); 
      mPath.arcTo(rectF,angle,-angle*2-180);//繪制圓弧 break;

四、邏輯運算

兩條Path可通過多種邏輯運算進行結合,形成新的Path。

API如下:

op(Path path, Path.Op op)op(Path path1, Path path2, Path.Op op)

邏輯運算具有五種類型:

方法 描述 示意圖
DIFFERENCE B在A中的相對補集,即A減去A與B的交集
補集1
REVERSE_DIFFERENCE A在B中的相對補集合,即B減去B與A的交集
補集2
INTERSECT A與B的交集
交集
UNION A與B的合集
合集
XOR A與B的合集減去A與B的交集
異或

使用Path.op方法再給圓角圖片類,增加一種環形樣式:

  case RING:
     rectF.left = -length*2;
     rectF.top = -length; 
     rectF.right = length*2; 
     rectF.bottom = length*3;
     mPath1.moveTo(0,length);
     mPath1.arcTo(rectF,angle,-angle*2-180);//較大的圓弧 
     
     rectF.left = -length/2; 
     rectF.top = length/2; 
     rectF.right = length/2; 
     rectF.bottom = length*3/2;
     mPath2.moveTo(0,length);
     mPath2.arcTo(rectF,angle,-angle*2-180);//較小的圓弧 
     mPath.op(mPath1,mPath2, Path.Op.XOR);//異或獲取環形

五、小結

本文介紹了Path的基本使用方法與邏輯運算,同時通過圓角圖片的例子,進行了實戰。使用Path方法,還可以增加更多有趣的圖形,比如star,多邊形,格子圖等等。如果在閱讀過程中,有任何疑問與問題,歡迎與我聯系。

原作者博客地址

GitHub:https://github.com/Idtk
博客:http://www.idtkm.com
郵箱:Idtkma@gmail.com
PS: 示例中使用的方法,相對消耗內存,更合適的是設置反向填充來完成圓角圖片的生成,FigureImageView為反向填充的方法,OldFigureImageView為示例中的方法 源碼涉及知識

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容