今天要分享的是Android上的刮刮卡控件隐轩。按照國際慣例,先上效果圖渤早。
主要的功能:
- 自定義刮開后的文字职车、圖片;
- 自定義覆蓋層的顏色、圖片悴灵;
- 刮開大部分刮涂層后扛芽,自動清除剩余刮涂層;
按照國際慣例积瞒,線上效果圖:
源碼放在最后
一川尖、實現(xiàn)思路
- 繪制獎區(qū)文字;
- 繪制刮涂層茫孔;
- 監(jiān)聽用戶touch事件叮喳,跟隨用戶touch軌跡清除掛涂層對應(yīng)位置的像素;
- 監(jiān)聽掛涂層剩余像素缰贝,當(dāng)達(dá)到一個閾值時馍悟,清空所有像素。
二剩晴、繪制獎區(qū)文字
首先需要new一個專門負(fù)責(zé)繪制文字的paint锣咒。然后后面根據(jù)自定義屬性設(shè)置的文字大小、顏色赞弥,以及獎區(qū)的內(nèi)容毅整,使用canvas.drawText()方法去繪制文字。
但是要把文字繪制在刮刮卡的正中間绽左,就需要我們自己去計算drawText的起始點坐標(biāo)悼嫉。這里的思路是:
- 在onMeasure中獲取整個刮刮卡的高和寬;
- 計算要顯示的文字所占的整個Rect的高和寬拼窥;
- 用整個(刮刮卡的寬 - 文字所占的寬)/2得到x軸的起始坐標(biāo)承粤;
- 用整個(刮刮卡的高 - 文字所占的高)/2得到y(tǒng)軸的起始坐標(biāo);
//計算獎區(qū)文字區(qū)域的大小闯团,用于后面計算獎區(qū)文字起始點的坐標(biāo)
mPrizeTextPaint.setColor(mPrizeTextColor);
mPrizeTextPaint.setTextSize(mPrizeTextSize);
mTextBound = new Rect();
mPrizeTextPaint.getTextBounds(mPrizeContent, 0, mPrizeContent.length(), mTextBound);
//繪制獎區(qū)內(nèi)容辛臊,這里要繪制在整個View的正中間.
canvas.drawText(mPrizeContent, getWidth() / 2 - mTextBound.width() / 2,
getHeight() / 2 + mTextBound.height() / 2, mPrizeTextPaint);
三、繪制刮涂層
這個是繪制整個控件的重點房交。這里的思路是new一個bitmap對象彻舰,把刮涂層涂層以及后面用戶手指刮開的路徑混合在一起,然后通過canvas繪制到這個bitmap上候味。最后在onDraw的時候刃唤,使用整個控件的canvas對象去繪制出這個bitmap對象,從而實現(xiàn)刮涂效果白群。
先在onMeasure中計算出整個刮刮卡的寬和高尚胞,然后new一個一樣大小的bitmap對象。使用一個canvas對象來繪制到這個bitmap中帜慢。
int width = getMeasuredWidth();
int height = getMeasuredHeight();
//初始化刮涂層的畫布,并將刮涂層的內(nèi)容全部保存到bitmap對象中笼裳。
//最后在onDraw中調(diào)用控件的canvas去繪制這個bitmap唯卖,從而實現(xiàn)刮獎的效果。
mBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
mCoverCanvas = new Canvas(mBitmap);
mCoverCanvas.drawColor(Color.parseColor("#c0c0c0"));//先draw src躬柬,后面會draw dist
監(jiān)聽用戶擦除的路徑拜轨。通過重寫onTouchEvent事件。通過一個Path對象來記錄用戶擦除的痕跡允青。每touch一次橄碾,就在Path上增加一條。
@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;
mTouchPath.moveTo(mLastX, mLastY);
break;
case MotionEvent.ACTION_MOVE:
mTouchPath.lineTo(event.getX(), event.getY());
float dx = Math.abs(x - mLastX);
float dy = Math.abs(y - mLastY);
if (dx > 3 || dy > 3) {
mTouchPath.lineTo(x, y);
}
mLastX = x;
mLastY = y;
break;
case MotionEvent.ACTION_UP:
//計算已經(jīng)刮完的像素
if (!isCompleted)
new Thread(mRunnable).start();
break;
}
if (!isCompleted)
invalidate();
return true;//消費掉touch事件
}
然后設(shè)置涂層畫筆的Xfermode屬性颠锉,也就是圖像的混合模式繪制出來法牲。
/**
* 繪制用戶手指刮過的路徑
*/
private void drawPath() {
mCoverPaint.setStyle(Paint.Style.STROKE);
mCoverPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT));
mCoverCanvas.drawPath(mTouchPath, mCoverPaint);
}
經(jīng)過上面的這些操作眉枕,我們定義的bitmap就成了灰色涂層和用戶觸摸軌跡混合的一個對象了。最后就在onDraw中繪制到canvas中就可以了怜森。
canvas.drawBitmap(mBitmap, 0, 0, null);
四速挑、監(jiān)聽刮涂的面積,當(dāng)達(dá)到一定閾值的時候副硅,自動清空剩余的涂層
這里就需要在用戶每次在otionEvent.ACTION_UP的時候去判斷我們涂層的bitmap對象有多少像素的像素值為0了(像素值為0代表透明)姥宝。然后計算一下透明的像素數(shù)量占總像素的百分比。如果這個百分比超過閾值恐疲,那么再重新繪制到額時候腊满,就不去繪制掛涂層,就可以實現(xiàn)清空剩余掛涂層的效果了培己。
為了不因為計算這個像素數(shù)量而引起UI線程阻塞碳蛋,我們另開一個線程來計算。同時設(shè)置一個volatile修飾的布爾對象來標(biāo)識是否達(dá)到閾值省咨。主線程通過這個標(biāo)識來判斷是否需要繪制掛涂層肃弟。
private Runnable mRunnable = new Runnable()
{
@Override
public void run() {
int w = getWidth();
int h = getHeight();
float wipeArea = 0;
float totalArea = w * h;
Bitmap bitmap = mBitmap;
int[] mPixels = new int[w * h];
bitmap.getPixels(mPixels, 0, w, 0, 0, w, h);
//計算被擦除的區(qū)域(也就是像素值為0)的像素數(shù)之和
for (int i = 0; i < w; i++) {
for (int j = 0; j < h; j++) {
int index = i + j * w;
if (mPixels[index] == 0) {
wipeArea++;
}
}
}
//計算擦除的像素數(shù)與總像素數(shù)的百分比
if (wipeArea > 0 && totalArea > 0) {
int percent = (int) (wipeArea * 100 / totalArea);
if (percent > 60) {
isCompleted = true;
postInvalidate();
}
}
}
};
最后獻(xiàn)上源碼,有任何問題的朋友零蓉,歡迎在下面留言笤受。
源碼下載