<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

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

          共 10271字,需瀏覽 21分鐘

           ·

          2021-11-08 11:39

          關(guān)于刮刮卡的實(shí)現(xiàn)效果不需要做太多解釋,特別是在電商APP中,每當(dāng)做活動(dòng)的時(shí)候都會(huì)有它的身影存在,趁著美好周末,來實(shí)現(xiàn)下這個(gè)效果,也算是對(duì)零碎知識(shí)點(diǎn)的一個(gè)整合。




          所涉及的知識(shí)點(diǎn):


          1、自定義View的一些流程
          2、雙緩沖繪圖機(jī)制
          3、Paint的繪圖模式
          4、觸摸事件的一些流程
          5、Bitmap的相關(guān)知識(shí)

          實(shí)現(xiàn)思路:


          其實(shí)非常簡(jiǎn)單,首先我們需要確定所要繪圖的區(qū)域,然后對(duì)這塊區(qū)域進(jìn)行多層的繪圖(背景層,前景層),然后去監(jiān)聽觸摸事件,把手指觸摸的區(qū)域的前景層給消除即可。

          首先我們先來實(shí)現(xiàn)一個(gè)簡(jiǎn)單版的:

          步驟:

          1、繪制圖片作為背景層
          2、繪制一張和背景層大小一致的灰色圖層作為前景層
          3、監(jiān)聽手指的觸摸區(qū)域,把對(duì)應(yīng)區(qū)域的前景層消除

          1、首先繪制圖片作為背景層,這個(gè)太簡(jiǎn)單了,我們把資源文件轉(zhuǎn)成Bitmap對(duì)象,然后利用onDraw(Canvas canvas)里的Canvas畫出來即可。
          //背景圖mBackGroundBitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.background);
              @Override    protected void onDraw(Canvas canvas) {        //繪制背景層        canvas.drawBitmap(mBackGroundBitmap, 0, 0, null);    }

          2、再來繪制一張和背景層大小一致的灰色圖層作為前景層,這里我們需要用到繪圖的雙緩沖機(jī)制(這里的緩沖區(qū)指Bitmap對(duì)象)。

          雙緩沖機(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);
              @Override    protected void onDraw(Canvas canvas) {        //繪制背景層        canvas.drawBitmap(mBackGroundBitmap, 0, 0, null);        //繪制前景層        canvas.drawBitmap(mForeGroundBitmap, 0, 0, null);    }

          運(yùn)行此時(shí)的代碼,你會(huì)發(fā)現(xiàn)背景層已經(jīng)和前景層融為一體(其實(shí)是2個(gè)圖層,類似于PS里的圖層疊加)

          3、監(jiān)聽手指的觸摸區(qū)域,把對(duì)應(yīng)區(qū)域的前景層消除,這里我們需要用到一個(gè)技巧,在Paint畫筆API中給我們提供了一個(gè)PorterDuffXfermode,它有點(diǎn)想數(shù)學(xué)里的交并集,是用來控制兩個(gè)圖像之間的混合顯示模式。



          在這里它會(huì)先去繪制DST層再繪制SRC層,那么對(duì)應(yīng)著下來就是背景層(DST)和前景層(SRC),那么在這個(gè)圖像我們?cè)趺慈ミx擇模式呢?

          這里我們需要取的是背景層的內(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));

          然后我們重寫onTouchEvent在手指按下屏幕和滑動(dòng)屏幕的時(shí)候利用Path去記錄我們想要擦除的路徑即可。
              @Override    public 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; }

          接下來我們來實(shí)現(xiàn)一個(gè)完整版的刮刮卡:

          步驟:

          1、繪制中獎(jiǎng)信息作為背景層
          2、繪制一張和中獎(jiǎng)信息同等大小的刮獎(jiǎng)封面作為前景層
          3、監(jiān)聽手指的觸摸區(qū)域,把對(duì)應(yīng)區(qū)域的前景層消除
          4、在消除大部分區(qū)域的時(shí)候,講中獎(jiǎng)信息完整展示
          步驟1、2、3和前面大體一致,這里我就不詳細(xì)說了,
          來講一下需要注意的幾個(gè)點(diǎn):

          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);
              @Override    protected void onDraw(Canvas canvas) {        canvas.drawText(mText, mBitmap.getWidth() / 2 - mRect.width() / 2, mBitmap.getHeight() / 2 + mRect.height() / 2, mTextPaint);    }

          這樣我們就繪制好了背景層的中獎(jiǎng)信息,再來就是前景層,和上面一樣我們利用資源文件轉(zhuǎn)Bitmap對(duì)象然后綁定Canvas并繪制上刮刮卡圖案
                 //通過資源文件創(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);

          剩下的利用Path來記錄用戶手指觸摸路徑就是一樣的了,這里我們額外來添加一個(gè)功能,使得當(dāng)用戶在刮刮卡上刮的區(qū)域范圍超過50%后,自動(dòng)消除刮刮卡前景層。

          我們通過Bitmap的getPixels方法就可以拿到Bitmap的像素信息,由于這里涉及到了計(jì)算,這是個(gè)耗時(shí)操作,所以這里我們開啟一個(gè)子線程來執(zhí)行任務(wù)
          private Runnable mRunnable = new Runnable() {        int[] pixels;
          @Override public 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(); } }
          } };

          首先我們聲明一個(gè)數(shù)組來記錄像素點(diǎn)信息,數(shù)組的大小即為像素總數(shù)的大小也就是Bitmap的寬高,然后我們?cè)趏nTouchEvent里的ACTION_UP中去計(jì)算被擦除的像素值,這里的for循環(huán)可能有的朋友會(huì)看的有點(diǎn)懵,沒著急,我畫一張圖,你就能懂。



          我們第一層for循環(huán)i指的是Bitmap的寬,第二次層for循環(huán)j指的是Bitmap的高,那么index=i+jw,假設(shè)這個(gè)Bitmap的像素大小是3*3,那么index的值就是0,3,6,1,4,7,2,5,8,是不是有感覺了?我們遍歷像素點(diǎn)是按照縱向下來的,當(dāng)pixels的值為0的時(shí)候,證明已經(jīng)是被用戶擦除掉的像素點(diǎn)。

          當(dāng)被擦除的區(qū)域超出50%,我們就在onDraw里去控制不讓canvas繪制前景圖即可。
              @Override    protected 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;//前景圖Canvas private 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);
          }

          @Override protected 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); } }

          @Override public 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;
          @Override public 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(); } }
          } };}

          源碼地址:
          https://github.com/Lichenwei-Dev/ScratchCardView

          到這里就結(jié)束啦。
          瀏覽 44
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  国产变态另类 | 三级片视频在线播放 | 一级久久久 | 自拍激情网址 | 亚洲免费视频欧洲免费视频 |