Android自定義實(shí)現(xiàn)刮刮卡效果


所涉及的知識(shí)點(diǎn):
2、雙緩沖繪圖機(jī)制
3、Paint的繪圖模式
4、觸摸事件的一些流程
5、Bitmap的相關(guān)知識(shí)
實(shí)現(xiàn)思路:
1、繪制圖片作為背景層
2、繪制一張和背景層大小一致的灰色圖層作為前景層
3、監(jiān)聽手指的觸摸區(qū)域,把對(duì)應(yīng)區(qū)域的前景層消除
//背景圖mBackGroundBitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.background);
@Overrideprotected void onDraw(Canvas canvas) {//繪制背景層canvas.drawBitmap(mBackGroundBitmap, 0, 0, null);}
雙緩沖機(jī)制:先將要繪制的圖形以對(duì)象的形式存放在內(nèi)存中,作為繪制緩沖區(qū),然后在這個(gè)對(duì)象上進(jìn)行一系列的操作,然后再將其繪制到屏幕,避免過多的操作使得在繪制的過程中出現(xiàn)屏幕閃爍現(xiàn)象。
//背景圖mBackGroundBitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.background);//創(chuàng)建一個(gè)和背景圖大小一致的Bitmap對(duì)象作為裝載畫布mForeGroundBitmap = Bitmap.createBitmap(mBackGroundBitmap.getWidth(), mBackGroundBitmap.getHeight(), Config.ARGB_8888);//與Canvas進(jìn)行綁定mCanvas = new Canvas(mForeGroundBitmap);//涂成灰色mCanvas.drawColor(Color.GRAY);
@Overrideprotected void onDraw(Canvas canvas) {//繪制背景層canvas.drawBitmap(mBackGroundBitmap, 0, 0, null);//繪制前景層canvas.drawBitmap(mForeGroundBitmap, 0, 0, null);}
3、監(jiān)聽手指的觸摸區(qū)域,把對(duì)應(yīng)區(qū)域的前景層消除,這里我們需要用到一個(gè)技巧,在Paint畫筆API中給我們提供了一個(gè)PorterDuffXfermode,它有點(diǎn)想數(shù)學(xué)里的交并集,是用來控制兩個(gè)圖像之間的混合顯示模式。

這里我們需要取的是背景層的內(nèi)容,也就是DST和 SRC的交集,然后內(nèi)容區(qū)域顯示DST,那么也就是DstIn模式,來看下關(guān)于畫筆Paint的設(shè)置。
mPaint = new Paint();mPaint.setAlpha(0);mPaint.setAntiAlias(true);mPaint.setStyle(Paint.Style.STROKE);mPaint.setStrokeCap(Paint.Cap.ROUND);mPaint.setStrokeJoin(Paint.Join.ROUND);mPaint.setStrokeWidth(80);mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));
@Overridepublic boolean onTouchEvent(MotionEvent event) {switch (event.getAction()) {case MotionEvent.ACTION_DOWN:mLastX = (int) event.getX();mLastY = (int) event.getY();mPath.moveTo(mLastX, mLastY);break;case MotionEvent.ACTION_MOVE:mLastX = (int) event.getX();mLastY = (int) event.getY();mPath.lineTo(mLastX, mLastY);break;case MotionEvent.ACTION_UP:break;default:break;}mCanvas.drawPath(mPath, mPaint);invalidate();return true;}
步驟:
1、繪制中獎(jiǎng)信息作為背景層
2、繪制一張和中獎(jiǎng)信息同等大小的刮獎(jiǎng)封面作為前景層
3、監(jiān)聽手指的觸摸區(qū)域,把對(duì)應(yīng)區(qū)域的前景層消除
4、在消除大部分區(qū)域的時(shí)候,講中獎(jiǎng)信息完整展示
1、在繪制中獎(jiǎng)信息(文本)的時(shí)候,如何確定繪制的位置:

首先我們需要知道任何的控件在Android的布局中外層都是一個(gè)矩形的,A代表刮刮卡繪制區(qū)域,B代表中獎(jiǎng)信息繪制區(qū)域,所以在這里我們繪制文本信息的起始點(diǎn)應(yīng)該是A布局寬的一半減去B布局寬的一半,同理,高也應(yīng)該是A布局高的一半減去B布局高的一半,這里我們把B布局,也就是文字控件的大小信息用一個(gè)Rect對(duì)象來存儲(chǔ),而這里的A布局即為Bitmap背景圖的大小。
//文字畫筆mTextPaint = new Paint();mTextPaint.setAntiAlias(true);mTextPaint.setColor(Color.GREEN);mTextPaint.setStyle(Paint.Style.FILL);mTextPaint.setTextSize(30);mTextPaint.getTextBounds(mText, 0, mText.length(), mRect);
@Overrideprotected void onDraw(Canvas canvas) {canvas.drawText(mText, mBitmap.getWidth() / 2 - mRect.width() / 2, mBitmap.getHeight() / 2 + mRect.height() / 2, mTextPaint);}
//通過資源文件創(chuàng)建Bitmap對(duì)象mBitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.background);//新建同等大小的Bitmap對(duì)象mForeBitmap = Bitmap.createBitmap(mBitmap.getWidth(), mBitmap.getHeight(), Bitmap.Config.ARGB_8888);//雙緩沖,裝載畫布mForeCanvas = new Canvas(mForeBitmap);mForeCanvas.drawBitmap(mBitmap, 0, 0, null);
我們通過Bitmap的getPixels方法就可以拿到Bitmap的像素信息,由于這里涉及到了計(jì)算,這是個(gè)耗時(shí)操作,所以這里我們開啟一個(gè)子線程來執(zhí)行任務(wù)
private Runnable mRunnable = new Runnable() {int[] pixels;@Overridepublic void run() {int w = mForeBitmap.getWidth();int h = mForeBitmap.getHeight();float wipeArea = 0;float totalArea = w * h;pixels = new int[w * h];/*** pixels 接收位圖顏色值的數(shù)組* offset 寫入到pixels[]中的第一個(gè)像素索引值* stride pixels[]中的行間距個(gè)數(shù)值(必須大于等于位圖寬度)??梢詾樨?fù)數(shù)* x 從位圖中讀取的第一個(gè)像素的x坐標(biāo)值。* y 從位圖中讀取的第一個(gè)像素的y坐標(biāo)值* width 從每一行中讀取的像素寬度* height 讀取的行數(shù)*/mForeBitmap.getPixels(pixels, 0, w, 0, 0, w, h);for (int i = 0; i < w; i++) {for (int j = 0; j < h; j++) {int index = i + j * w;if (pixels[index] == 0) {wipeArea++;}}}if (wipeArea > 0 && totalArea > 0) {int percent = (int) (wipeArea * 100 / totalArea);if (percent > 50) {isClear = true;postInvalidate();}}}};

當(dāng)被擦除的區(qū)域超出50%,我們就在onDraw里去控制不讓canvas繪制前景圖即可。
@Overrideprotected void onDraw(Canvas canvas) {canvas.drawText(mText, mForeBitmap.getWidth() / 2 - mRect.width() / 2, mForeBitmap.getHeight() / 2 + mRect.height() / 2, mTextPaint);if (!isClear) {canvas.drawBitmap(mForeBitmap, 0, 0, null);}}
/**?*?刮刮卡(完善版)*/public class ScratchCardView2 extends View {//處理文字private String mText = "恭喜您中獎(jiǎng)啦!!";private Paint mTextPaint;private Rect mRect;//處理圖層private Paint mForePaint;private Path mPath;private Bitmap mBitmap;//加載資源文件private Canvas mForeCanvas;//前景圖Canvasprivate Bitmap mForeBitmap;//前景圖Bitmap//記錄位置private int mLastX;private int mLastY;private volatile boolean isClear;//標(biāo)志是否被清除public ScratchCardView2(Context context) {this(context, null);}public ScratchCardView2(Context context, AttributeSet attrs) {this(context, attrs, 0);}public ScratchCardView2(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);init();}private void init() {mRect = new Rect();mPath = new Path();//文字畫筆mTextPaint = new Paint();mTextPaint.setAntiAlias(true);mTextPaint.setColor(Color.GREEN);mTextPaint.setStyle(Paint.Style.FILL);mTextPaint.setTextSize(30);mTextPaint.getTextBounds(mText, 0, mText.length(), mRect);//擦除畫筆mForePaint = new Paint();mForePaint.setAntiAlias(true);mForePaint.setAlpha(0);mForePaint.setStrokeCap(Paint.Cap.ROUND);mForePaint.setStrokeJoin(Paint.Join.ROUND);mForePaint.setStyle(Paint.Style.STROKE);mForePaint.setStrokeWidth(30);mForePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));//通過資源文件創(chuàng)建Bitmap對(duì)象mBitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.background);mForeBitmap = Bitmap.createBitmap(mBitmap.getWidth(), mBitmap.getHeight(), Bitmap.Config.ARGB_8888);//雙緩沖,裝載畫布mForeCanvas = new Canvas(mForeBitmap);mForeCanvas.drawBitmap(mBitmap, 0, 0, null);}@Overrideprotected void onDraw(Canvas canvas) {canvas.drawText(mText, mForeBitmap.getWidth() / 2 - mRect.width() / 2, mForeBitmap.getHeight() / 2 + mRect.height() / 2, mTextPaint);if (!isClear) {canvas.drawBitmap(mForeBitmap, 0, 0, null);}}@Overridepublic boolean onTouchEvent(MotionEvent event) {switch (event.getAction()) {case MotionEvent.ACTION_DOWN:mLastX = (int) event.getX();mLastY = (int) event.getY();mPath.moveTo(mLastX, mLastY);break;case MotionEvent.ACTION_MOVE:mLastX = (int) event.getX();mLastY = (int) event.getY();mPath.lineTo(mLastX, mLastY);break;case MotionEvent.ACTION_UP:new Thread(mRunnable).start();break;default:break;}mForeCanvas.drawPath(mPath, mForePaint);invalidate();return true;}/*** 開啟子線程計(jì)算被擦除的像素點(diǎn)*/private Runnable mRunnable = new Runnable() {int[] pixels;@Overridepublic void run() {int w = mForeBitmap.getWidth();int h = mForeBitmap.getHeight();float wipeArea = 0;float totalArea = w * h;pixels = new int[w * h];/*** pixels 接收位圖顏色值的數(shù)組* offset 寫入到pixels[]中的第一個(gè)像素索引值* stride pixels[]中的行間距個(gè)數(shù)值(必須大于等于位圖寬度)。可以為負(fù)數(shù)* x 從位圖中讀取的第一個(gè)像素的x坐標(biāo)值。* y 從位圖中讀取的第一個(gè)像素的y坐標(biāo)值* width 從每一行中讀取的像素寬度* height 讀取的行數(shù)*/mForeBitmap.getPixels(pixels, 0, w, 0, 0, w, h);for (int i = 0; i < w; i++) {for (int j = 0; j < h; j++) {int index = i + j * w;if (pixels[index] == 0) {wipeArea++;}}}if (wipeArea > 0 && totalArea > 0) {int percent = (int) (wipeArea * 100 / totalArea);if (percent > 50) {isClear = true;postInvalidate();}}}};}
評(píng)論
圖片
表情
