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

實(shí)現(xiàn)原理
其實(shí)刮刮樂實(shí)現(xiàn)原理也不算很復(fù)雜,最關(guān)鍵的還是需要了解Paint的混合模式。因?yàn)楣喂螛肥怯蓛蓚€(gè)bitmap組成的,一個(gè)是源圖另一個(gè)是目標(biāo)圖,我們需要把目標(biāo)圖的顏色改成灰色,在源圖上面蓋上了一張灰色的目標(biāo)圖。當(dāng)手指滑動(dòng)屏幕時(shí)paint會(huì)在新的canvas上畫出路徑,由于新的canvas會(huì)持有一個(gè)新的bitmap,最終兩個(gè)bitmap的像素點(diǎn)重疊時(shí)就顯示源圖的部分,從而實(shí)現(xiàn)了刮刮樂的效果。這里用的是混合模式中的PorterDuff.Mode.DST_IN。

關(guān)鍵代碼:
pathPaint.setXfermode(new PorterDuffXfermode((PorterDuff.Mode.DST_IN)));
關(guān)鍵步驟
創(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)建了兩個(gè)bitmap,一個(gè)是背景圖,另一個(gè)是覆蓋在背景圖上面的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坐標(biāo)
int y = mHeight / 2 + mTxtHeight / 4; //文字在畫布中的y坐標(biāo)
canvas.drawText(text, x, y, mPaintText);
}
調(diào)用canvas的drawText方法繪制文字,x,y是文字中心位置的坐標(biāo)。測(cè)量文字寬高有兩種方式,這里用的是更精確的getFontMetrics方法。
畫路徑
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
path.reset();
path.moveTo(event.getX(), event.getY());//原點(diǎn)移動(dòng)至手指的觸摸點(diǎn)
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);//設(shè)置畫筆的線冒樣式
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());//原點(diǎn)移動(dòng)至手指的觸摸點(diǎn)
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坐標(biāo)
int y = mHeight / 2 + mTxtHeight / 4; //文字在畫布中的y坐標(biāo)
canvas.drawText(text, x, y, mPaintText);
}
public Bitmap getBitmap(Bitmap bm, int newWidth, int newHeight) {
// 獲得圖片的寬高
int width = bm.getWidth();
int height = bm.getHeight();
// 計(jì)算縮放比例
float scaleWidth = ((float) newWidth) / width;
float scaleHeight = ((float) newHeight) / height;
// 取得想要縮放的matrix參數(shù)
Matrix matrix = new Matrix();
matrix.postScale(scaleWidth, scaleHeight);
// 得到新的圖片
Bitmap newbm = Bitmap.createBitmap(bm, 0, 0, width, height, matrix, true);
return newbm;
}
}以上就是Android通過自定義view實(shí)現(xiàn)刮刮樂效果詳解的詳細(xì)內(nèi)容,更多關(guān)于Android刮刮樂的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Android數(shù)據(jù)持久化之ContentProvider機(jī)制詳解
這篇文章主要介紹了Android數(shù)據(jù)持久化之ContentProvider機(jī)制,結(jié)合實(shí)例形式分析了ContentProvider機(jī)制的原理與相關(guān)使用技巧,需要的朋友可以參考下2017-05-05
Android解析服務(wù)器端發(fā)來的xml數(shù)據(jù)示例
Android跟服務(wù)器交互數(shù)據(jù),有時(shí)數(shù)據(jù)量大時(shí),就需要以xml形式的交互數(shù)據(jù),下面與大家分享下使用XmlPullParser來解析xml數(shù)據(jù),感興趣的朋友可以參考下哈2013-06-06
Android自定義下拉刷新控件RefreshableView
這篇文章主要介紹了Android自定義下拉刷新控件RefreshableView,支持所有控件的下拉刷新,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-11-11
loadavg數(shù)據(jù)異常引發(fā)問題起源分析
這篇文章主要為大家介紹了loadavg數(shù)據(jù)異常引發(fā)問題起源分析詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-11-11
Android端使用Modbus協(xié)議的簡(jiǎn)單方法
Modbus協(xié)議是全球第一個(gè)用于工業(yè)現(xiàn)場(chǎng)的總線協(xié)議,與外設(shè)交互可以采用串口通信,tcp等方式,這篇文章主要給大家介紹了關(guān)于Android端使用Modbus協(xié)議的簡(jiǎn)單方法,需要的朋友可以參考下2021-11-11
使用Android WebSocket實(shí)現(xiàn)即時(shí)通訊功能
即時(shí)通訊(Instant Messaging)最重要的毫無(wú)疑問就是即時(shí),不能有明顯的延遲,要實(shí)現(xiàn)IM的功能其實(shí)并不難,目前有很多第三方,比如極光的JMessage,都比較容易實(shí)現(xiàn)。本文通過實(shí)例代碼給大家分享Android WebSocket實(shí)現(xiàn)即時(shí)通訊功能,一起看看吧2019-10-10
Android自定義ImageView實(shí)現(xiàn)圓角功能
這篇文章主要為大家詳細(xì)介紹了Android自定義ImageView實(shí)現(xiàn)圓角功能,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-12-12
Jetpack Compose自定義動(dòng)畫與Animatable詳解
在今年的Google/IO大會(huì)上,亮相了一個(gè)全新的 Android 原生 UI 開發(fā)框架-Jetpack Compose, 與蘋果的SwiftIUI一樣,Jetpack Compose是一個(gè)聲明式的UI框架,這篇文章主要介紹了Jetpack Compose自定義動(dòng)畫與Animatable2022-10-10
Android實(shí)現(xiàn)短信驗(yàn)證碼自動(dòng)填寫功能
這篇文章主要介紹了Android實(shí)現(xiàn)短信驗(yàn)證碼自動(dòng)填寫功能,感興趣的小伙伴們可以參考一下2015-12-12
flutter showModalBottomSheet常用屬性及說明
這篇文章主要介紹了flutter showModalBottomSheet常用屬性及說明,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-09-09

