Android 自定義控件實現刮刮卡效果 真的就只是刮刮卡么

jopen 8年前發布 | 8K 次閱讀 Android開發 移動開發

轉載請標明出處:http://blog.csdn.net/lmj623565791/article/details/40162163 , 本文出自:【張鴻洋的博客】

很久以前也過一個html5的刮刮卡效果~~上次看到有人寫Android的刮刮卡效果~~于是乎產生了本篇博客~~此類例子也比較多了,大家可以百度看看~不過還是通過本例子,帶大家發掘一下,里面隱含的知識~

1、Xfermode以及PorterDuff

如果大家還記得,曾經在博客:完美實現圖片圓角和圓形 簡單介紹過圓角的實現原理也是基于這個。

首先我們看一下官方的例子,很好的展示了16種Mode的效果:


注:先繪制的Dst,再繪制的Src。

好了,看了這個圖,我來問大家幾個問題:

問題1、如果我想實現圓形圖片,怎么實現?

答:先繪制我們的圖片,然后在上面繪制一個圓,最后生成的效果就是圓形圖片;等等,怎么就生成了,請看上面的SrcIn這種模式;

先繪制的Dst,然后設置DstIn,然后繪制Src;最后效果是留下了二者交集且是Dst的部分;下面我們把我們的答案帶進去。

先繪制圖片,然后設置DstIn,然后繪制圓形;最后效果是留下了二者交集且是圖片的部分;嗯,交集是什么,圓形;圓形內容是什么,圖片;搜噶,有點感覺了。

----

等等,我還有有個思路,先繪制圓形,然后設置SrcIn,再繪制我們的圖片;也能生成我們的圓形圖片。我們來看看:

SrcIn最終保留的依然是交集,但是顯示為后繪制的,也就是我們的圖片,搜噶,這樣也可以。

 

問題2、如果我想實現圓角圖片,怎么實現?

答:擦,看了上面的答案,你還沒思路么。把繪制圓形,改成繪制圓角矩形。請問你還有什么問的,額,,,木有了。

嗯,把問題1的圓形改成圓角,按照相同的繪制過程就實現了我們的圓角圖片了。

 

問題3、這和我們的刮刮卡有毛線關系?

答:怎么沒有關系,,,你先繪制刮獎層,然后設置DST_OUT,然后把用戶手觸摸的線條繪制上去;用戶觸摸到刮獎層的部分(交集部分)會被消除,也是就說刮獎層被我們擦掉了~

這不就是刮獎么。等等,獎呢?

獎無非就是文本,或者圖片,提前繪制一下,然后在其上繪制刮獎層,設置DST_OUT,然后把用戶觸摸繪制上去;這樣消失以后就能看到背后的獎了~~~對了,現在還有個app叫脫什么妹子衣服,先繪制妹子,然后繪制衣服,然后擦~~這個和刮獎像不像,額,我什么都沒說。

 

搜噶,經過上面的3個問題,大家應該明白了,什么圓角,圓形,刮刮卡,其實原理就這么簡單,,,

 

2、簡易畫板的實現

我們的刮刮卡需要掌握繪圖,當然了這里不要求你有美術天分,會瞎涂鴉就可以了~~

下面開始我們的一個簡易的畫板,其實就是可以在上面畫點線條,當然你也可以簽個名,我們的View的叫做GuaGuaKa:

1、初步GuaGuaKa

package com.zhy.view;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;

public class GuaGuaKa extends View
{

    /**
     * 繪制線條的Paint,即用戶手指繪制Path
     */
    private Paint mOutterPaint = new Paint();
    /**
     * 記錄用戶繪制的Path
     */
    private Path mPath = new Path();
    /**
     * 內存中創建的Canvas
     */
    private Canvas mCanvas;
    /**
     * mCanvas繪制內容在其上
     */
    private Bitmap mBitmap;

    private int mLastX;
    private int mLastY;

    public GuaGuaKa(Context context)
    {
        this(context, null);
    }

    public GuaGuaKa(Context context, AttributeSet attrs)
    {
        this(context, attrs, 0);
    }

    public GuaGuaKa(Context context, AttributeSet attrs, int defStyle)
    {
        super(context, attrs, defStyle);
        init();
    }

    private void init()
    {
        mPath = new Path();

    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
    {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        int width = getMeasuredWidth();
        int height = getMeasuredHeight();
        // 初始化bitmap
        mBitmap = Bitmap.createBitmap(width, height, Config.ARGB_8888);
        mCanvas = new Canvas(mBitmap);
        // 設置畫筆
        mOutterPaint.setColor(Color.RED);
        mOutterPaint.setAntiAlias(true);
        mOutterPaint.setDither(true);
        mOutterPaint.setStyle(Paint.Style.STROKE);
        mOutterPaint.setStrokeJoin(Paint.Join.ROUND); // 圓角
        mOutterPaint.setStrokeCap(Paint.Cap.ROUND); // 圓角
        // 設置畫筆寬度
        mOutterPaint.setStrokeWidth(20);
    }

    @Override
    protected void onDraw(Canvas canvas)
    {
        drawPath();
        canvas.drawBitmap(mBitmap, 0, 0, null);

    }

    /**
     * 繪制線條
     */
    private void drawPath()
    {
        mCanvas.drawPath(mPath, mOutterPaint);

    }

    @Override
    public boolean onTouchEvent(MotionEvent event)
    {
        int action = event.getAction();
        int x = (int) event.getX();
        int y = (int) event.getY();
        switch (action)
        {
        case MotionEvent.ACTION_DOWN:
            mLastX = x;
            mLastY = y;
            mPath.moveTo(mLastX, mLastY);
            break;
        case MotionEvent.ACTION_MOVE:

            int dx = Math.abs(x - mLastX);
            int dy = Math.abs(y - mLastY);

            if (dx > 3 || dy > 3)
                mPath.lineTo(x, y);

            mLastX = x;
            mLastY = y;
            break;
        }

        invalidate();
        return true;
    }

}

代碼量比較少,我們在內存中搞了一個mCanvas,創建了一個mBitmap,然后通過mCanvas使用我們預先設置的mOuterPaint在我們的mBitmap上繪制mPath;

mPath里面的數據怎么搞呢?就是onTouchEvent里面不斷的moveTo,lineTo就好了~~代碼還是很隨意的

最后,注意我們繪制內存上的mBitmap上面,然后我們通過view的canvas,把我們的mBitmap展現。咦,怎么有點雙緩沖的感腳。

好了,現在你就可以在我們的畫板上肆掠了:

下面看布局文件以及運行效果:

2、布局文件及運行效果

布局文件:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >

    <com.zhy.view.GuaGuaKa
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</RelativeLayout>

運行效果:

看到我渾厚的字體沒有,等以后寫不動程序了,我就去當書法家~

 

好了,我們的簡易畫板完成以后,我們開始考慮正題,一步一步逼近我們的刮刮板,現在我們準備這樣做,首先在背后繪制一張圖片,然后繪制一個遮蓋層,然后我們繪畫的過程就是擦除遮蓋層。

 

3、擦除的第一次實現

鑒于很多朋友的意見,我決定這次的圖片用風景圖,遠離xxx , 感謝seven提供的圖片~

1、繪制遮蓋層

其實遮蓋層就是一個顏色:

@Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
    {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        int width = getMeasuredWidth();
        int height = getMeasuredHeight();
        // 初始化bitmap
        mBitmap = Bitmap.createBitmap(width, height, Config.ARGB_8888);
        mCanvas = new Canvas(mBitmap);
        setUpOutPaint();
        //繪制這改成
        mCanvas.drawColor(Color.parseColor("#c0c0c0"));
    }

和上面貼的代碼就多了最后一行,另外我們的paint的設置抽取出去了~

2、drawPath

文章起初的原理終于要用上了,我們在繪制Path的時候,需要設置一個模式,這里是DST_OUT ,想想有點小激動~

private void drawPath()
    {

        mOutterPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT));
        mCanvas.drawPath(mPath, mOutterPaint);
    }

3、onDraw

onDraw里面也要做一點修改

@Override
    protected void onDraw(Canvas canvas)
    {
        canvas.drawBitmap(mBackBitmap, 0, 0, null);
        drawPath();
        canvas.drawBitmap(mBitmap, 0, 0, null);
    }

好了,到此完成,其實就添加了幾行代碼,就完成了我們簡易畫板到刮刮卡的轉變;

下面看效果:

有沒有撥開云霧見天明的感覺~~

到此我們的刮刮卡的原理,以及初步的實現結束了~~還是很簡單的~接下來就是后續的完善工作

 

4、刮刮卡的完善

我們準備把獎項改為字體,將字體繪制在屏幕的中間;

那么直接把上例繪制圖片改為繪制字體就行了,不過多了一個繪制字體畫筆的設置:

有變化的代碼:

private Paint mBackPint = new Paint();
    private Rect mTextBound = new Rect();
    private String mText = "500,0000,000";
    /**
     * 初始化canvas的繪制用的畫筆
     */
    private void setUpBackPaint()
    {
        mBackPint.setStyle(Style.FILL);
        mBackPint.setTextScaleX(2f);
        mBackPint.setColor(Color.DKGRAY);
        mBackPint.setTextSize(22);
        mBackPint.getTextBounds(mText, 0, mText.length(), mTextBound);
    }

    @Override
    protected void onDraw(Canvas canvas)
    {
        // canvas.drawBitmap(mBackBitmap, 0, 0, null);
        //繪制獎項
        canvas.drawText(mText, getWidth() / 2 - mTextBound.width() / 2,
                getHeight() / 2 + mTextBound.height() / 2, mBackPint);

        drawPath();
        canvas.drawBitmap(mBitmap, 0, 0, null);
    }

下面看看我中了多錢:

好了,到此已經完全實現了,大家按照例子,結合自己需求修改即可,里面所涉及的原理相信已經解釋清楚了;對了,差點忘了,刮刮卡一般都有一個功能,當你掛了差不多的時候,涂層會自動清除,下面我們嘗試添加該功能。

 

5、統計刮開區域已占的百分比

我們在ACTION_UP的時候就行計算,首先我們還是給大家灌輸下計算的原理,如果大家用心看了,應該知道我們所有的操作基本都在mBitmap,現在我們獲得mBItmap上所有的像素點的數據,統計被清除的區域(被清除的像素為0);最后與我們圖片的總像素數做個除法元算,就可以拿到我們清除的百分比了;

不過,計算可能會是一個耗時的操作,具體速度跟圖片大小有關,所以我們決定使用異步的方式去計算:

/**
     * 統計擦除區域任務
     */
    private Runnable mRunnable = new Runnable()
    {
        private int[] mPixels;

        @Override
        public void run()
        {

            int w = getWidth();
            int h = getHeight();

            float wipeArea = 0;
            float totalArea = w * h;

            Bitmap bitmap = mBitmap;

            mPixels = new int[w * h];

            /**
             * 拿到所有的像素信息
             */
            bitmap.getPixels(mPixels, 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 (mPixels[index] == 0)
                    {
                        wipeArea++;
                    }
                }
            }

            /**
             * 根據所占百分比,進行一些操作
             */
            if (wipeArea > 0 && totalArea > 0)
            {
                int percent = (int) (wipeArea * 100 / totalArea);
                Log.e("TAG", percent + "");

                if (percent > 70)
                {
                    isComplete = true;
                    postInvalidate();
                }
            }
        }

    };

有了這個任務,我們在ACTION_UP的時候就行調用:
case MotionEvent.ACTION_UP:
            new Thread(mRunnable).start();
            break;
注意任務結束,會把一個isComplete設置為true;當為true時,我們直接展現刮獎區

@Override
    protected void onDraw(Canvas canvas)
    {
        drawBackText(canvas);

        if (!isComplete)
        {
            drawPath();
            canvas.drawBitmap(mBitmap, 0, 0, null);
        }

    }

到此刮獎區的計算就結束了。

下面在演示前,我做了一些簡單的美化,具體大家到時候看源碼就可以。

到此我們的刮刮卡制作就結束了,另外如果大家希望再完善,可以把里面很多常量設置成變量,添加對外的set方法,或者抽取成自定義屬性,在布局文件進行定義都可以~~~

有一點需要說明一下,對于我們刮刮卡這個案例,我們布局文件如果寬高設置為wrap_content,也會占滿屏幕,主要是因為我覺得刮刮卡這個view沒必要wrap_content;但是如果你希望支持wrp_content,可以參考Android 自定義View (一) 。

 

源碼點擊下載

---------------------------------------------------------------------------------------------------------

建了一個QQ群,方便大家交流。群號:55032675

----------------------------------------------------------------------------------------------------------

博主部分視頻已經上線,如果你不喜歡枯燥的文本,請猛戳(初錄,期待您的支持):

1、高仿微信5.2.1主界面及消息提醒

2、高仿QQ5.0側滑



來自: http://blog.csdn.net//lmj623565791/article/details/40162163

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!