轉載請註明出處:https://blog.csdn.net/kong_gu_you_lan/article/details/105572617
本文出自 容華謝後的博客
0.寫在前面
先看下效果圖,功能雖然簡單,但是實現的時候谷歌、百度了很久也沒有找到解決方案,提這個問題的人不少,但是回答的人一個也沒有,十分鬱悶,在此記錄,分享給各位。
1.半透明畫筆
先按照常規的方法實現一個簡單的畫板:
public class SketchpadView extends View {
private Paint mPaint;
private Path mPath;
private float mLastX;
private float mLastY;
private Bitmap mBufferBitmap;
private Canvas mBufferCanvas;
public SketchpadView(Context context) {
this(context, null);
}
public SketchpadView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public SketchpadView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
// 抗鋸齒、防抖動
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);
// 畫筆模式爲描邊
mPaint.setStyle(Paint.Style.STROKE);
// 拐角爲圓角
mPaint.setStrokeJoin(Paint.Join.ROUND);
// 兩端爲圓角
mPaint.setStrokeCap(Paint.Cap.ROUND);
// 畫筆寬度
mPaint.setStrokeWidth(50);
// 畫筆顏色
mPaint.setColor(getResources().getColor(R.color.colorAccent));
// 畫筆透明度,先設置顏色,再設置透明度0-255
mPaint.setAlpha(80);
// 筆跡路徑
mPath = new Path();
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
// 雙緩存機制
mBufferBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_4444);
mBufferCanvas = new Canvas(mBufferBitmap);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (mBufferBitmap != null) {
canvas.drawBitmap(mBufferBitmap, 0, 0, null);
}
}
@SuppressLint("ClickableViewAccessibility")
@Override
public boolean onTouchEvent(MotionEvent event) {
float x = event.getX();
float y = event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mLastX = x;
mLastY = y;
mPath.moveTo(x, y);
break;
case MotionEvent.ACTION_MOVE:
mPath.quadTo(mLastX, mLastY, (x + mLastX) / 2, (y + mLastY) / 2);
mBufferCanvas.drawPath(mPath, mPaint);
mLastX = x;
mLastY = y;
invalidate();
break;
case MotionEvent.ACTION_UP:
mPath.reset();
invalidate();
break;
}
return true;
}
}
注意:在初始化畫筆的時候,我們給畫筆設置了80的透明度,透明度一定要在顏色之後設置,因爲顏色中也存在透明通道,會覆蓋已設置的透明度。
// 畫筆顏色
mPaint.setColor(getResources().getColor(R.color.colorAccent));
// 畫筆透明度,先設置顏色,再設置透明度0-255
mPaint.setAlpha(80);
看下效果:
咦,爲什麼還一段一段的顏色漸變?
這裏需要了解下Paint類中的一個很重要的方法setXfermode(Xfermode xfermode),參數Xfermode有三個子類:AvoidXfermode、PixelXorXfermode和PorterDuffXfermode,前兩個類在API 16被遺棄了,重點看下PorterDuffXfermode這個類,PorterDuffXfermode類主要用於圖形合成時的圖像過渡模式計算,共有18種過渡模式,本文重點看下其中的兩種模式SRC_OVER、SRC。
如果沒有調用setXfermode方法,Paint繪製默認採用的是SRC_OVER模式,即先繪製第一筆,繪製第二筆時,第二筆與第一筆重合的部分,會進行疊加,顏色會變得越來越深。
這就解釋了爲什麼上面的效果中,會出現一段一段的的顏色漸變筆跡,是因爲半透明的筆跡疊加了,現在我們將模式修改爲SRC,即重合的部分只顯示第二筆,看下效果:
透明效果出來了,但是存在一個問題,就是每段筆跡重合的部分,沒有疊加顏色變深的效果,繼續往下看。
2.筆跡疊加
修改下代碼:
public class SketchpadView extends View {
private Paint mPaint;
private Path mPath;
private float mLastX;
private float mLastY;
private Bitmap mBufferBitmap;
private Canvas mBufferCanvas;
public SketchpadView(Context context) {
this(context, null);
}
public SketchpadView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public SketchpadView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
// 抗鋸齒、防抖動
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);
// 畫筆模式爲描邊
mPaint.setStyle(Paint.Style.STROKE);
// 拐角爲圓角
mPaint.setStrokeJoin(Paint.Join.ROUND);
// 兩端爲圓角
mPaint.setStrokeCap(Paint.Cap.ROUND);
// 畫筆寬度
mPaint.setStrokeWidth(50);
// 畫筆顏色
mPaint.setColor(getResources().getColor(R.color.colorAccent));
// 畫筆透明度,先設置顏色,再設置透明度0-255
mPaint.setAlpha(80);
// 筆跡路徑
mPath = new Path();
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
// 雙緩存機制
mBufferBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_4444);
mBufferCanvas = new Canvas(mBufferBitmap);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (mBufferBitmap != null) {
canvas.drawBitmap(mBufferBitmap, 0, 0, null);
}
// 修改1
// ACTION_MOVE時,將筆跡臨時繪製在畫布上
canvas.drawPath(mPath, mPaint);
}
@SuppressLint("ClickableViewAccessibility")
@Override
public boolean onTouchEvent(MotionEvent event) {
float x = event.getX();
float y = event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mLastX = x;
mLastY = y;
mPath.moveTo(x, y);
break;
case MotionEvent.ACTION_MOVE:
mPath.quadTo(mLastX, mLastY, (x + mLastX) / 2, (y + mLastY) / 2);
// 修改2
// 將繪製方法移到ACTION_UP中
mLastX = x;
mLastY = y;
invalidate();
break;
case MotionEvent.ACTION_UP:
// 修改3
// ACTION_UP時,將當前一筆的筆跡,繪製到緩存畫布上
mBufferCanvas.drawPath(mPath, mPaint);
mPath.reset();
invalidate();
break;
}
return true;
}
}
修改後的代碼,沒有設置Paint的圖像模式,默認爲疊加狀態,在ACTION_MOVE時,沒有將筆跡實時繪製在緩存畫布上,而是臨時繪製在實際畫布上,ACTION_UP時再將當前一筆的筆跡一次性繪製到緩存畫布,然後再繪製到實際畫布中,這樣一筆一筆下來,就會產生疊加的筆跡效果了,看下最終效果:
3.寫在最後
到這裏半透明畫筆的筆跡疊加效果就介紹完了,如有錯誤或者遺漏的地方可以給我留言評論,謝謝!
代碼已上傳至GitHub,歡迎Star、Fork!
GitHub地址:https://github.com/alidili/Demos/tree/master/TranslucentPaintDemo
本文Demo的Apk下載地址:https://github.com/alidili/Demos/raw/master/TranslucentPaintDemo/TranslucentPaintDemo.apk