短視頻PHP源碼Xfermode實現刮刮卡

先繪製Dst在繪製Src,我們需要知道一點,使用Xfermode混合模式繪製後,受影響的區域永遠是我們的src原圖像區域。下面我們來看一下這16中混合模式的具體意思

private static final Xfermode[] sModes = {
            //所繪製的不會提交到畫布上
            new PorterDuffXfermode(PorterDuff.Mode.CLEAR),
            //顯示上層繪製的圖片
            new PorterDuffXfermode(PorterDuff.Mode.SRC),
            //顯示下層繪製的圖片
            new PorterDuffXfermode(PorterDuff.Mode.DST),
            //上下層都顯示,上層層居上
            new PorterDuffXfermode(PorterDuff.Mode.SRC_OVER),
            //上下層都顯示,下層居上
            new PorterDuffXfermode(PorterDuff.Mode.DST_OVER),
            //取兩層繪製的交集,顯示上層
            new PorterDuffXfermode(PorterDuff.Mode.SRC_IN),
            //取兩層繪製的交集,顯示下層
            new PorterDuffXfermode(PorterDuff.Mode.DST_IN),
            //取上層繪製的非交集部分,其餘部分變成透明
            new PorterDuffXfermode(PorterDuff.Mode.SRC_OUT),
            //取下層繪製的非交集部分,其餘部分變成透明
            new PorterDuffXfermode(PorterDuff.Mode.DST_OUT),
            //取上層的交集部分和下層的非交集部分
            new PorterDuffXfermode(PorterDuff.Mode.SRC_ATOP),
            //取下層交集部分和上層非交集部分
            new PorterDuffXfermode(PorterDuff.Mode.DST_ATOP),
            //除去兩層的交集部分
            new PorterDuffXfermode(PorterDuff.Mode.XOR),
            //取全部區域,交集部分顏色加深
            new PorterDuffXfermode(PorterDuff.Mode.DARKEN),
            //取兩圖層全部區域,交集部分顏色點亮
            new PorterDuffXfermode(PorterDuff.Mode.LIGHTEN),
            //取兩層交集部分,顏色加深
            new PorterDuffXfermode(PorterDuff.Mode.MULTIPLY),
            //取兩圖層全部區域,交集部分變爲透明色
            new PorterDuffXfermode(PorterDuff.Mode.SCREEN),
    };

知道了上面的屬性的意思,我們來想一下我們刮刮卡的思路一個圖片上面蒙上一層,手指劃過的區域讓上層變成透明,就顯示出下層的圖片了。

我們去上面屬性中找,就可以找到DST_OUT這個屬性,它的意思是取下層繪製的非交集部分,交集部分變成透明

所以我們可以先繪製一個刮獎層,爲dst層,手指一動的路徑爲src層。設置爲DST_OUT屬性,那麼兩者交集的地方就會變成透明瞭。

當然也可以是用SRC_OUT,使用SRC_OUT就是遮罩層繪製在path上面。使用DST_OUT就是遮罩層繪製在path下面

  private void init() {
        mPaint = new Paint();
        mPaint.setAntiAlias(true);
        mPaint.setStrokeWidth(30);
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setStrokeCap(Paint.Cap.ROUND);

        //禁止硬件加速
        setLayerType(LAYER_TYPE_SOFTWARE,null);
        //結果圖片
        mBitmapRes = BitmapFactory.decodeResource(getResources(), R.mipmap.guagua);
        //遮罩層圖片
        mSrcBitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.gua);
        //創建一個可繪製path的bitmap
        mDstBitmap = Bitmap.createBitmap(mSrcBitmap.getWidth(),mSrcBitmap.getHeight(), Bitmap.Config.ARGB_8888);
        mCanvas = new Canvas(mDstBitmap);
    }

首先禁止硬件加速,有些手機不支持,然後初始化結果圖片,遮罩層圖片和一個可繪製path的bitmap

下面分別使用DST_OUT和SRC_OUT兩種方式來實現繪製

    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //繪製結果圖片
        canvas.drawBitmap(mBitmapRes,0,0,null);

//        //第一種 先繪製遮罩層在繪製path,path的畫筆使用DST_OUT模式
//        mCanvas.drawBitmap(mSrcBitmap,0,0,null);
//        mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT));
//        mCanvas.drawPath(mPath,mPaint);
//        //繪製目標圖像
//        canvas.drawBitmap(mDstBitmap,0,0,null);

        //第二種//遮罩層繪製在path上面,遮罩層的畫筆使用SRC_OUT模式
        //新建一個圖層,不然會把原始圖層也當成dst層了。上面的方法之所以不用起一個新圖層,因爲遮罩層和path都是繪製在新new的canvas中了。
        int layerId = canvas.saveLayer(0,0,getWidth(),getHeight(),mPaint,Canvas.ALL_SAVE_FLAG);
        //繪製路徑
        mCanvas.drawPath(mPath,mPaint);
        //繪製目標圖像
        canvas.drawBitmap(mDstBitmap,0,0,mPaint);
        mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_OUT));

        canvas.drawBitmap(mSrcBitmap,0,0,mPaint);
        mPaint.setXfermode(null);
        canvas.restoreToCount(layerId);
    }

第一種先繪製遮罩層在繪製path,path的畫筆使用DST_OUT模式,第二種遮罩層繪製在path上面,遮罩層的畫筆使用SRC_OUT模式。

第二種方法使用canvas.saveLayer新建了一個圖層,是因爲如果不新建一個圖層,就會把原始圖層當成dst層了。第一種方式,因爲我們先new了一個自己的Canvas,然後把遮罩層繪製在了new出來的畫布上了,path也是繪製在這個畫布上。

最後就是path的路徑了,很簡單通過onTouchEvent獲得

    public boolean onTouchEvent(MotionEvent event) {
        super.onTouchEvent(event);
        switch (event.getAction()){
            case MotionEvent.ACTION_DOWN:
                mEventX = event.getX();
                mEventY = event.getY();
                mPath.moveTo(mEventX,mEventY);
                break;
            case MotionEvent.ACTION_MOVE:
                float endX = (mEventX+event.getX())/2;
                float endY = (mEventY+event.getY())/2;
                //可以用lineTo也可以用quadTo一個是直線一個是以階貝塞爾曲線
                mPath.quadTo(mEventX,mEventY,endX,endY);
//                mPath.lineTo(event.getX(),event.getY());
                mEventX = event.getX();
                mEventY = event.getY();
                invalidate();
                break;
        }
        return true;
    }

如果有需求,當擦除一半的遮罩層後,擡起手自動全部消除,參考鴻洋的博客我們可以統計mDstBitmap的像素數據,被清除的像素變成0。統計爲0的像素點跟總的像素點相除,大於某個值之後比如0.5就不繪製遮罩層了。

由於圖片可能會很大,所以在子線程中處理統計的操作。在MotionEvent.ACTION_UP中啓動線程計算。

private Runnable mRunnable = new Runnable()
    {
        private int[] mPixels;
        @Override
        public void run()
        {
            int w = mDstBitmap.getWidth();
            int h = mDstBitmap.getHeight();

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

            Bitmap bitmap = mDstBitmap;

            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);
                if (percent > 50)
                {
                    isComplete = true;
                    postInvalidate();
                }
            }
        }
    };

最後我們可以在OnMeasure方法中,將我們整個view的大小設置爲我們背景圖片的大小,就可以在佈局文件中愉快的玩耍啦。不然在佈局文件中總是已match_parent的形式存在。

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    // 寬的測量規格
    int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
    // 寬的測量尺寸
    int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
    // 高度的測量規格
    int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
    // 高度的測量尺寸
    int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);

    //根據View的邏輯得到,比如TextView根據設置的文字計算wrap_content時的大小。
    //這兩個數據根據實現需求計算。
    int wrapWidth = mBitmapRes.getWidth();
    int wrapHeight = mBitmapRes.getHeight();

    // 如果是是AT_MOST則對哪個進行特殊處理
    if(widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST){
        setMeasuredDimension(wrapWidth, wrapHeight);
    }else if(widthSpecMode == MeasureSpec.AT_MOST){
        setMeasuredDimension(wrapWidth, heightSpecSize);
    }else if(heightSpecMode == MeasureSpec.AT_MOST){
        setMeasuredDimension(widthSpecSize, wrapHeight);
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章