Android通過自定義view實現(xiàn)刮刮樂效果詳解
前言
已經有兩個月沒有更新博客了,其實這篇文章我早在兩個月前就寫好了,一直保存在草稿箱里沒有發(fā)布出來。原因是有一些原理性的東西還沒了解清楚,最近抽時間研究了一下混合模式,終于也理解了刮刮樂是怎么實現(xiàn)的,所以想繼續(xù)分享一下自己的一些心得,先上效果圖。
效果圖:

實現(xiàn)原理
其實刮刮樂實現(xiàn)原理也不算很復雜,最關鍵的還是需要了解Paint的混合模式。因為刮刮樂是由兩個bitmap組成的,一個是源圖另一個是目標圖,我們需要把目標圖的顏色改成灰色,在源圖上面蓋上了一張灰色的目標圖。當手指滑動屏幕時paint會在新的canvas上畫出路徑,由于新的canvas會持有一個新的bitmap,最終兩個bitmap的像素點重疊時就顯示源圖的部分,從而實現(xiàn)了刮刮樂的效果。這里用的是混合模式中的PorterDuff.Mode.DST_IN。

關鍵代碼:
pathPaint.setXfermode(new PorterDuffXfermode((PorterDuff.Mode.DST_IN)));
關鍵步驟
創(chuàng)建bitmap
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mBitmapBackground = getBitmap(mBitmapBackground, w, h);
mBitmapFront = Bitmap.createBitmap(mBitmapBackground.getWidth(),
mBitmapBackground.getHeight(), Bitmap.Config.ARGB_8888);
mCanvas.setBitmap(mBitmapFront);
drawText(mCanvas, w, h);
}
onSizeChanged方法里面創(chuàng)建了兩個bitmap,一個是背景圖,另一個是覆蓋在背景圖上面的bitmap。然后是在上面的bitmap上面繪制文字。
繪制文字
private void drawText(Canvas canvas, int mWidth, int mHeight) {
String text = "趕緊刮開吧";
canvas.drawColor(Color.GRAY);
Paint.FontMetrics fm = mPaintText.getFontMetrics();
int mTxtWidth = (int) mPaintText.measureText(text, 0, text.length());
int mTxtHeight = (int) Math.ceil(fm.descent - fm.ascent);
int x = mWidth / 2 - mTxtWidth / 2; //文字在畫布中的x坐標
int y = mHeight / 2 + mTxtHeight / 4; //文字在畫布中的y坐標
canvas.drawText(text, x, y, mPaintText);
}
調用canvas的drawText方法繪制文字,x,y是文字中心位置的坐標。測量文字寬高有兩種方式,這里用的是更精確的getFontMetrics方法。
畫路徑
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
path.reset();
path.moveTo(event.getX(), event.getY());//原點移動至手指的觸摸點
break;
case MotionEvent.ACTION_MOVE:
path.lineTo(event.getX(), event.getY());
break;
}
mCanvas.drawPath(path, pathPaint);
invalidate();
return true;
}最終效果圖

完整代碼
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import androidx.annotation.Nullable;
import com.example.androidprogressbar.R;
public class ScratchCard extends View {
private Bitmap mBitmapBackground;
private Bitmap mBitmapFront;
private Canvas mCanvas;
private Paint pathPaint;
private Path path;
private Paint mPaintText;
public ScratchCard(Context context) {
super(context);
init();
}
public ScratchCard(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init();
}
public ScratchCard(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
pathPaint = new Paint();
pathPaint.setAlpha(0);
pathPaint.setStyle(Paint.Style.STROKE);
pathPaint.setStrokeWidth(70);
pathPaint.setXfermode(new PorterDuffXfermode((PorterDuff.Mode.DST_IN)));//混合模式
pathPaint.setStrokeJoin(Paint.Join.ROUND);//線段之間連接處的樣式
pathPaint.setStrokeCap(Paint.Cap.ROUND);//設置畫筆的線冒樣式
path = new Path();
mBitmapBackground = BitmapFactory.decodeResource(getResources(), R.drawable.card);
mCanvas = new Canvas();
mPaintText = new Paint();
mPaintText.setColor(Color.WHITE);
mPaintText.setTextSize(100);
mPaintText.setStrokeWidth(20);
}
@Override
protected void onDraw(Canvas canvas) {
canvas.drawBitmap(mBitmapBackground, 0, 0, null);
canvas.drawBitmap(mBitmapFront, 0, 0, null);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mBitmapBackground = getBitmap(mBitmapBackground, w, h);
mBitmapFront = Bitmap.createBitmap(mBitmapBackground.getWidth(),
mBitmapBackground.getHeight(), Bitmap.Config.ARGB_8888);
mCanvas.setBitmap(mBitmapFront);
drawText(mCanvas, w, h);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
path.reset();
path.moveTo(event.getX(), event.getY());//原點移動至手指的觸摸點
break;
case MotionEvent.ACTION_MOVE:
path.lineTo(event.getX(), event.getY());
break;
}
mCanvas.drawPath(path, pathPaint);
invalidate();
return true;
}
private void drawText(Canvas canvas, int mWidth, int mHeight) {
String text = "趕緊刮開吧";
canvas.drawColor(Color.GRAY);
Paint.FontMetrics fm = mPaintText.getFontMetrics();
int mTxtWidth = (int) mPaintText.measureText(text, 0, text.length());
int mTxtHeight = (int) Math.ceil(fm.descent - fm.ascent);
int x = mWidth / 2 - mTxtWidth / 2; //文字在畫布中的x坐標
int y = mHeight / 2 + mTxtHeight / 4; //文字在畫布中的y坐標
canvas.drawText(text, x, y, mPaintText);
}
public Bitmap getBitmap(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);
// 得到新的圖片
Bitmap newbm = Bitmap.createBitmap(bm, 0, 0, width, height, matrix, true);
return newbm;
}
}以上就是Android通過自定義view實現(xiàn)刮刮樂效果詳解的詳細內容,更多關于Android刮刮樂的資料請關注腳本之家其它相關文章!
相關文章
Android數據持久化之ContentProvider機制詳解
這篇文章主要介紹了Android數據持久化之ContentProvider機制,結合實例形式分析了ContentProvider機制的原理與相關使用技巧,需要的朋友可以參考下2017-05-05
Android自定義下拉刷新控件RefreshableView
這篇文章主要介紹了Android自定義下拉刷新控件RefreshableView,支持所有控件的下拉刷新,具有一定的參考價值,感興趣的小伙伴們可以參考一下2016-11-11
使用Android WebSocket實現(xiàn)即時通訊功能
即時通訊(Instant Messaging)最重要的毫無疑問就是即時,不能有明顯的延遲,要實現(xiàn)IM的功能其實并不難,目前有很多第三方,比如極光的JMessage,都比較容易實現(xiàn)。本文通過實例代碼給大家分享Android WebSocket實現(xiàn)即時通訊功能,一起看看吧2019-10-10
Android自定義ImageView實現(xiàn)圓角功能
這篇文章主要為大家詳細介紹了Android自定義ImageView實現(xiàn)圓角功能,具有一定的參考價值,感興趣的小伙伴們可以參考一下2018-12-12
Jetpack Compose自定義動畫與Animatable詳解
在今年的Google/IO大會上,亮相了一個全新的 Android 原生 UI 開發(fā)框架-Jetpack Compose, 與蘋果的SwiftIUI一樣,Jetpack Compose是一個聲明式的UI框架,這篇文章主要介紹了Jetpack Compose自定義動畫與Animatable2022-10-10
flutter showModalBottomSheet常用屬性及說明
這篇文章主要介紹了flutter showModalBottomSheet常用屬性及說明,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-09-09

