国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 系統 > Android > 正文

Android 自定義view之畫圖板實現方法

2019-10-22 18:16:20
字體:
來源:轉載
供稿:網友

看效果: 中間一個畫圖板 上方小控件用來顯示實時畫出的圖形 下方小控件用來做一些畫圖的控制 2個小控件都能移動

view,畫圖板,Android

順帶還有一個刮刮卡效果,只需要改一個參數:

view,畫圖板,Android

自定義view首先要自定義屬性:

在values下面創建attrs.xml:

 <!--畫圖板-->  <declare-styleable name="DrawImg">    <attr name="PaintColor" />      //畫筆顏色    <attr name="PaintWidth" />      // 畫筆寬度    <attr name="CanvasImg" />      //畫板圖片  </declare-styleable>  <!--指定單位-->  <attr name="PaintColor" format="color" />      <attr name="PaintWidth" format="dimension" />       <attr name="CanvasImg" format="reference" />

對于下面3行指定單位的代碼可以放出來,可以讓多個自定義view 都能使用。

接下來新建自定義view類繼承view,重寫前3個構造方法

紅線標注是android studio 3.0.0對于參數提示的新特性

view,畫圖板,Android

通過this 讓前2個構造方法都實現3個參數的構造方法。
簡單說一下構造方法。一個參數的構造方法是在代碼中 new 時用到,2個參數的構造方法在布局xml中用到,3個參數的基本就是自定義view類中使用,大概就是這樣。

接下來從attrs.xml中通過TypedArray取出自定義屬性:

//從attrs文件中取出各個屬性    TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.DrawImg, defStyleAttr, 0);    for (int i = 0; i < a.getIndexCount(); i++) {      int attr = a.getIndex(i);      switch (attr) {        case R.styleable.DrawImg_PaintWidth:    //畫筆寬度          paintWidth = a.getDimensionPixelSize(attr, (int) TypedValue.applyDimension(              TypedValue.COMPLEX_UNIT_DIP, -1, getResources().getDisplayMetrics()));          break;        case R.styleable.DrawImg_PaintColor:    //畫筆顏色          paintColor = a.getColor(attr, Color.GREEN);          break;        case R.styleable.DrawImg_CanvasImg:     //畫板圖片          hasCanvasImg = a.getResourceId(attr, -1);          break;      }    }    //設置默認畫筆寬度    if (paintWidth == -1) {      paintWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 20, context.getResources().getDisplayMetrics());    }    //取出bitmap    if (hasCanvasImg != -1) {      bitmap = BitmapFactory.decodeResource(getResources(), hasCanvasImg);    }    //onMeasure可能走多次,onDraw創建對象更不好 所以把畫筆路徑new在這里    path = new Path();

需要默認值的設置默認值,以免布局中沒有用到自定義屬性導致報錯。

重寫自定義view關鍵方法onMeasure(),onDraw()。onMeasure()用來指定這個自定義view 的大小,onDraw()用來進行實時繪圖

最重要的3個東西:畫布Canvas,畫筆Paint,路徑Path

代碼略長但是注釋很全,把需要注意的提出來

在newPaint()方法中,paint有一個setXfermode()方法,這個表示圖形混合方式,有18種 ~(比下圖多了ADD和OVERLAY)~。給張圖看一下。這里我們用到2種 SRC_IN和 DST_OUT。

SRC_IN:取兩層交集部分,顯示上層
DST_OUT:取兩層非交集部分,顯示下層
說實話這么說也很難懂,還是要自己動手試一試,不過這里只要知道:
使用SRC_IN就會有一個畫圖板的效果
使用DST_OUT就會有一個刮刮卡的效果

view,畫圖板,Android

/**   * onMeasure常見方法   * 1) getChildCount():獲取子View的數量;   * 2) getChildAt(i):獲取第i個子控件;   * 3) subView.getLayoutParams().width/height:設置或獲取子控件的寬或高;   * 4) measureChild(child, widthMeasureSpec, heightMeasureSpec):測量子View的寬高;   * 5) child.getMeasuredHeight/width():執行完measureChild()方法后就可以通過這種方式獲取子View的寬高值;   * 6) getPaddingLeft/Right/Top/Bottom():獲取控件的四周內邊距;   * 7) setMeasuredDimension(width, height):重新設置控件的寬高。如果寫了這句代碼,就需要刪除   * “super. onMeasure(widthMeasureSpec, heightMeasureSpec);”這行代碼。   */  @Override  protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {    /**     * getMode獲取測量模式(下面3種) 和 getSize獲取測量值     *     * EXACTLY:當寬高值設置為具體值時使用,如100dp、match_parent等,此時取出的size是精確的尺寸;     * AT_MOST:當寬高值設置為wrap_content時使用,此時取出的size是控件最大可獲得的空間;     * UNSPECIFIED:當沒有指定寬高值時使用(很少見)。     *     * */    //測量模式_寬    int widthMode = MeasureSpec.getMode(widthMeasureSpec);    //測量模式_高    int heightMode = MeasureSpec.getMode(heightMeasureSpec);    //寬度    int widthSize = MeasureSpec.getSize(widthMeasureSpec);    //高度    int heightSize = MeasureSpec.getSize(heightMeasureSpec);    //設置view寬度    //如果布局中給出了準確的寬度,直接使用寬度,否則設置圖片寬度為view寬度    if (widthMode == MeasureSpec.EXACTLY) {      width = widthSize;    } else {      if (hasCanvasImg != -1) {        //如果設置了圖片,使用圖片寬        width = bitmap.getWidth();      } else {        //沒有設置圖片并且也沒給準確的view寬高 設置一個寬默認值        width = 500;      }    }    //設置view高度同上    if (heightMode == MeasureSpec.EXACTLY) {      height = heightSize;    } else {      if (hasCanvasImg != -1) {        height = bitmap.getHeight();      } else {        height = 500;      }    }    //重新設置view的寬高    setMeasuredDimension(width, height);    //設置畫布以及畫筆    newPaint();  }  private void newPaint() {    //根據參數創建一個新的bitmap 最后一個參數為為儲存形式    newBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);    //保存bitmap中所有像素點的數組     bmPixels = new int[newBitmap.getWidth() * newBitmap.getHeight()];    //new帶參的Canvas,其中的bitmap參數 必須通過createBitmap得到;    //否則會報錯:IllegalStateException : Immutable bitmap passed to Canvas constructor    canvas = new Canvas(newBitmap);    if (hasCanvasImg == -1) {      //如果沒有設置圖片,則默認用灰色覆蓋      canvas.drawColor(Color.GRAY);    } else {      //把設置的圖片縮放到view大小      bitmap = zoomBitmap(this.bitmap, width, height);      canvas.drawBitmap(bitmap, 0, 0, null);    }    // 準備繪制刮卡線條的畫筆    paint = new Paint();    paint.setColor(paintColor);    paint.setStyle(Paint.Style.STROKE);    paint.setStrokeWidth(paintWidth);    //設置是否使用抗鋸齒功能,抗鋸齒功能會消耗較大資源,繪制圖形的速度會減慢    paint.setAntiAlias(true);    //設置是否使用圖像抖動處理,會使圖像顏色更加平滑飽滿,更加清晰    paint.setDither(true);    //當設置畫筆樣式為STROKE或FILL_OR_STROKE時,設置筆刷的圖形樣式    paint.setStrokeCap(Paint.Cap.ROUND);    //設置繪制時各圖形的結合方式    paint.setStrokeJoin(Paint.Join.ROUND);    //設置圖形重疊時的處理方式    /**     * SRC_IN:取兩層繪制交集。顯示上層     */    paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));  }  //這個onDraw方法只有一句代碼,意思是在手指移動的同時把畫板圖片繪制出來  @Override  protected void onDraw(Canvas canvas) {    canvas.drawBitmap(newBitmap, 0, 0, null);    super.onDraw(canvas);  }  //將指定圖片縮放到指定寬高,返回新的圖片Bitmap對象  public static Bitmap zoomBitmap(Bitmap bm, int newWidth, int newHeight) {    // 獲得圖片的寬高    int width = bm.getWidth();    int height = bm.getHeight();    // 計算縮放比例    float scaleWidth = ((float) newWidth) / width;    float scaleHeight = ((float) newHeight) / height;    // 取得想要縮放的matrix參數    Matrix matrix = new Matrix();    matrix.postScale(scaleWidth, scaleHeight);    // 得到新的圖片    return Bitmap.createBitmap(bm, 0, 0, width, height, matrix, true);  }

這是一堆對于這個view來說比較復雜的代碼,但是功能很簡單,我們做了2件事:

1.通過MeasureSpec.getMode(測量模式),計算出整個控件的寬高
2.通過canvas.drawBitmap在畫布上畫出bitmap,同時 new 出畫筆 Paint 給它設置顏色,粗細等屬性

注意:

1.onDraw()方法在每次調用invalidate(),或者視圖變化時都會重走,所以不能在里面 new 東西.
2.有一個int[]類型的數組 bmPixels,這里大概說一下是個什么意思,具體的解釋在Bitmap類getPixels和createBitmap方法詳解中有說道。

bmPixels: 我們通過bitmap的寬度乘以高度,可以的到一個int[]類型的數組,這個數組就是組成bitmap的所有像素點,某一個像素點為0的時候就說明他是沒有顏色,!0就說明是有顏色的。

既然是畫圖,那肯定要監聽手指移動,onTouchEvent()方法:

@Override  public boolean onTouchEvent(MotionEvent event) {    int currX = (int) event.getX();    int currY = (int) event.getY();    switch (event.getAction()) {      case MotionEvent.ACTION_DOWN:        //按下時,設置線條的起始點準備繪制        path.moveTo(currX, currY);        break;      case MotionEvent.ACTION_MOVE:        //滑動時,繪制路徑        path.lineTo(currX, currY);        break;      case MotionEvent.ACTION_UP:    }    // 繪制線條,請求重繪整個控件    canvas.drawPath(path, paint);    //請求View樹進行重繪,即draw()方法,如果視圖的大小發生了變化,還會調用layout()方法。    invalidate();    return true;  }

這個就很簡單,手指按下時記錄位置,path.moveTo給path設置起始點位置,移動時通過path.lineTo()方法記錄路徑,同時使用 canvas.drawPath(path, paint)直接繪制出來,invalidate()通知視圖更新。

寫到這里,在xml布局中使用這個view,已經能畫一畫了

我們的畫筆Paint類,可以指定顏色,粗細,模式,等等,這樣我們就可以寫一些公開的方法,給它動態的設置這些屬性,從而讓畫筆更加多樣性。

//設置畫筆顏色  public void setPaintColor(int color) {    //path = new Path();      path.reset();    paint.setColor(color);  }  //設置畫筆類型  public void setPaintMode(int style) {    //path = new Path();      path.reset();    /**     * SRC_IN:取兩層交集部分,顯示上層     * DST_OUT:取兩層非交集部分,顯示下層     */    if (style == 1) {      paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));    } else {      paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT));    }    resetCanvaas();  }  //設置畫布重置  public void resetCanvaas() {    //path = new Path();      path.reset();    canvas.drawBitmap(bitmap, 0, 0, null);    invalidate();    listener.bitmapChangeListener(bitmap);  }

上面代碼 設置畫筆顏色 ,設置畫筆類型以及畫布重置為什么都要new Path呢,因為如果不新開一個路徑給畫筆,當你設置了新的顏色,用的還是以前的Path,畫筆就會把以前的Path也重新設置新顏色,而不是保持原來的顏色。

這樣就會出現一個問題,每次都在new Path,new一次創建一次,占用一次內存,想到一些避免方法,但是本文畫圖不是重點,就不在論述。(已改用path.reset())

效果中的右上角,顯示了一個float類型的數,它是在刮刮卡模式下,已經抹掉部分所占bitmap的比例,onMeasure()方法中有一個int[]類型的數組 bmPixels ,這個時候我們就要利用這個數組來得到這個比例。

view,畫圖板,Android

在onTouchEvent()方法的case MotionEvent.ACTION_UP加上一些代碼:

@Override  public boolean onTouchEvent(MotionEvent event) {    int currX = (int) event.getX();    int currY = (int) event.getY();    switch (event.getAction()) {      case MotionEvent.ACTION_DOWN:        //按下時,設置線條的起始點準備繪制        path.moveTo(currX, currY);        break;      case MotionEvent.ACTION_MOVE:        //滑動時,繪制路徑        path.lineTo(currX, currY);        //通過回調,實時把bitmap顯示出去        listener.bitmapChangeListener(newBitmap);        break;      case MotionEvent.ACTION_UP:        //抬起手指時,計算圖片抹去了多少        int nullPixel = 0;        newBitmap.getPixels(bmPixels, 0, width, 0, 0, width, height);        for (int i = 0; i < bmPixels.length; i++) {          //抹去部分的像素點在數組中就會表示為0,找出為0的個數          if (bmPixels[i] == 0) {            nullPixel++;          }        }        //計算抹去部分所占的百分比        listener.showBitmapClear((float) nullPixel / (float) bmPixels.length);        break;    }    // 繪制線條,請求重繪整個控件    canvas.drawPath(path, paint);    //請求View樹進行重繪,即draw()方法,如果視圖的大小發生了變化,還會調用layout()方法。    invalidate();    return true;  }

有一句 newBitmap.getPixels(bmPixels, 0, width, 0, 0, width, height);在getPixels方法詳解中有解釋,它的作用就是把newBitmap 中所有的像素點全部取出來,放到方法中的第一個參數bmPixels中。這個時候,我們再通過for循環遍歷bmPixels數組,等于0的說明是沒有顏色被抹掉的,統計他們的數量,計算他們所占的比例,就能算出抹掉的比例。同理我們也可以改變等于0這個判斷條件,讓他等于其他顏色,這樣也就可以計算其他顏色所占比例。
寫個回調接口,在代碼中取出來就OK了。

//回調接口  public interface bitmapListener {    //實時的把繪制的bitmap顯示在imageview 上    void bitmapChangeListener(Bitmap bitmap);    //顯示抹掉比例    void showBitmapClear(float clear);  }  public void addBitmapListener(bitmapListener bitmapListener) {    this.listener = bitmapListener;  }

有2個接口,一個實時的展示bitmap,一個展示抹去比例。


注:相關教程知識閱讀請移步到Android開發頻道。
發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 黑河市| 墨竹工卡县| 湘乡市| 神农架林区| 乌审旗| 巫溪县| 克东县| 禹州市| 资中县| 竹山县| 色达县| 交城县| 卓尼县| 正宁县| 体育| 康平县| 平泉县| 元阳县| 团风县| 襄垣县| 平利县| 鹤岗市| 常山县| 镇康县| 常宁市| 株洲县| 宣威市| 会宁县| 晋中市| 西乌| 桃园市| 乳源| 通江县| 马山县| 宁陵县| 奈曼旗| 宁波市| 德安县| 甘肃省| 长治市| 化德县|