Android刮刮卡的实现

        做了几年开发以前都是去看书, 看大神的博客, 看别人的文章, 确实收获了不少, 遇到不会的就查, 看到别人写好的第三方控件就拿过来用 , 使用别人制造的轮子感觉灰常好偷笑, 还节省时间, 慢慢的发现代码写的很快, 轮子用的很熟, 但是具体细节还是不懂, 光顾着埋头写代码, 使用轮子了, 对具体的知识点还是一知半解.  不是说不应该使用别人的轮子,  不重复造轮子是个好习惯, 但是我们在使用别人的轮子的同时也要对别人造的轮子理解透彻, 这样才能更好的进行改进么不是 大笑,  而且我们也不能只是看书, 看博客, 看文章, 看完之后需要动手写一下, 而且写的过程中我相信肯定不是一帆风顺的(/ □ \)(我写例子的时候真是各种趟坑啊, 也许是我技术不到家),  因此我萌发了写博客的念头,  这样不仅能督促我看书学习,  通过写博客的过程中还能让我对知识点掌握的更加牢固

        这是本人第一次写博客, 写的不好勿喷哈,  建议随便提,  我好借此改正,  但是可以说我技术搓, 文笔差, 甚至长得丑偷笑,  只要不人身攻击, 不涉及家人就好,  好的意见一定接受, 我写的也没啥高深的玩意, 大神们可以忽略了, 都是写看过的书, 或者知识点,  写篇博客权当做一点笔记了, 平时工作也挺忙, 所以也只能抽空写写.

        接下来进入正题, 刮刮卡的实现,  我相信很多同学都看过很多大神的博客(例如我看过的大神 鸿洋的博客点击打开链接 )或者书籍了, 其实这个也是蛮简单的, 没啥太多技术含量,  我这里也是在别人基础上写的demo而已, 做个笔记, 加深记忆, 如果哪位同学以前没看过相关只是, 又刚好需要用到, 也可以借鉴下大笑.

        先上图, 

                                

        见图, 灰色区域就是自定义的view, 火拳艾斯就是被遮挡的图片, 红色部分是自定义view的background, 这里我对艾斯的图片进行了等比例的缩放, 这样可以更好的适应自定义view, 接下来就是具体实现方式了

        

1.用到关键知识点PorterDuffXfermode

    这里借别人博客里的一张图做讲解, 如下图所示:一张图做讲解

                                         

        这张图形象的解释了PorterDuffXfermode所支持的几种mode类型, 其中Dst是先绘制的图形, 而Src是后绘制的图形.

        本人文字表述能力着实有限难过, 这里就不用文字描述各个mode的意思了, 相信大家根据上图能理解各个mode的意思, 这里主要用到的mode是SRC_IN, 也就是上图第二行第二个模式

        实现原理很简单, 

扫描二维码关注公众号,回复: 4146183 查看本文章

    1.先画出Dst的bitmap也就是准备好的艾斯的图片, 然后再画一个和view同等宽高的遮挡图也就是灰色区域的bitmap

    2.利用view的onTouchEvent方法, 获取用户手指的移动, 运用Path对象画出手指移动的轨迹

    3.利用设置了Xfermode属性, 并且Alpha的值不要设置为0(其实不要设置为不透明即可)的画笔, 画出手指的移动轨迹, 因为选取的PorterDuffXfermode模式是SRC_IN, 因此就只会显示我们的手机移动轨迹的那片区域, 有因为我们设置有透明度所以手指经过的地方就会变得透明, 就把后面的艾斯的图片给显示出来了,  这就完成了类似刮刮卡的效果

        下面介绍主要代码

public ScratchImageView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
}
    private void init() {
        mPaint = new Paint();
        // 透明度 不设置为255 即可
        mPaint.setAlpha(0);
        mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
        mPaint.setStyle(Paint.Style.STROKE);
        // 设置结合处的形状为圆角
        mPaint.setStrokeJoin(Paint.Join.ROUND);
        mPaint.setStrokeWidth(50);
        // 设置结尾处的形状为圆角
        mPaint.setStrokeCap(Paint.Cap.ROUND);
        mPath = new Path();
    }

        首先是构造方法, 及初始化方法, 初始化里主要对画笔对象做了初始化工作, 主要是设置了透明度和Xfermode属性

        

        接下来是对我们的遮盖层和被遮盖层进行初始化工作

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        // 获取控件的宽和高
        viewWidth = getWidth();
        viewHeight = getHeight();

        // 遮盖层
        mFgBitmap = Bitmap.createBitmap(viewWidth, viewHeight, Bitmap.Config.ARGB_8888);
        // 底层也就是背景层(被遮盖层)
        mBgBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.ace);

        // bitmap的宽高和view的宽高也许不是等比例的, 这里计算需要缩放的比例
        float scaleWidth = mFgBitmap.getWidth() * 1.0f / mBgBitmap.getWidth();
        float scaleHeight = mFgBitmap.getHeight() * 1.0f / mBgBitmap.getHeight();

        // 为了保证图片能够等比例缩放, 而不是宽/高会被拉伸, 这里要取得相对小的那个值
        float scale = Math.min(scaleWidth, scaleHeight);

        // 通过矩阵进行缩放
        Matrix matrix = new Matrix();
        matrix.postScale(scale, scale);
        // 得到新的图片
        mBgBitmap = Bitmap.createBitmap(mBgBitmap, 0, 0, mBgBitmap.getWidth(), mBgBitmap.getHeight(), matrix,
                true);
        mCanvas = new Canvas(mFgBitmap);
        mCanvas.drawColor(Color.GRAY);
    }

          这里我选择onSizeChanged方法进行初始化的原因是, 这个方法是在视图大小发生改变的时候回调, 也就是说这个方法里我们能够方便的正确拿到view的大小, 这里对代码稍微做下讲解, 首先获取view的宽高, 然后根据view的宽高得到遮盖层的bitmap对象, 在加载了被遮盖层的bitmap后,  我们根据view的宽高和mBgBitmap的宽高进行比较, 为了让图片进行等比例缩放并很好的填充view, 我们计算下宽高的缩放比, 根据缩放比对mBgBitmap进行了缩放, 

            

            接下来是最重要的onDraw方法

    @Override
    protected void onDraw(Canvas canvas) {
        // 第一个矩形区域表示要画的bitmap的大小
        Rect bgBtimapRect = new Rect(0, 0, mBgBitmap.getWidth(), mBgBitmap.getHeight());
        // 为了使得bitmap绘制在view的正中心, 这里计算一下起始的x, y的值
        int left = viewWidth / 2 - mBgBitmap.getWidth() / 2;
        int top = viewHeight / 2 - mBgBitmap.getHeight() / 2;
        // 第二个矩形区域表示bitmap要绘制的区域
        Rect bgBitmapStartRect = new Rect(left, top,
                mBgBitmap.getWidth() + left, mBgBitmap.getHeight() + top);
        canvas.drawBitmap(mBgBitmap, bgBtimapRect, bgBitmapStartRect, null);
        canvas.drawBitmap(mFgBitmap, 0, 0, null);
        mCanvas.drawPath(mPath, mPaint);
    }

        这个方法里我们首先画出来mBgBitmap对象, 这里用的是canvase的这个

public void drawBitmap(@NonNull Bitmap bitmap, @Nullable Rect src, @NonNull Rect dst,@Nullable Paint paint)方法, 这里着重说明一下第二和第三个参数, 该方法的第二个参数src是指所要绘制的bitmap的区域, 第三个参数dst是指bitmap所要绘制在那个矩形区域内, 再看前面几行代码, 这里主要就是计算第三个参数的过程, 防止图片在不能完美填充view的情况下, 通过计算让图片位于view的正中央, 这样两幅图画完了,  最后一行画用户手指的路径即可

        最后看一下onTouchEvent, 

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                mPath.reset();
                mPath.moveTo(event.getX(), event.getY());
                break;

            case MotionEvent.ACTION_MOVE:
                mPath.lineTo(event.getX(), event.getY());
                break;
        }
        invalidate();
        return true;
    }
        这里没什么可仔细说明的, 就是action_down的时候moveto到指定位置,  在action_move的时候, 利用path对象的lineTo方法获取路径, 最后电泳invalidate()方法 , 调用onDraw进行绘制, 这样整个刮刮卡就完成了

        

         最后贴一下完整的代码

public class ScratchImageView2 extends View {

    private String TAG = "ScratchImageView2";

    // 被遮挡的bitmap, 和用于遮挡的bitmap
    private Bitmap mBgBitmap, mFgBitmap;
    // 画笔对象, 主要设置了setXfermode 和透明度
    private Paint mPaint;
    private Canvas mCanvas;
    private Path mPath;

    // 控件的宽和高
    private int viewWidth;
    private int viewHeight;

    public ScratchImageView2(Context context) {
        super(context);
    }

    public ScratchImageView2(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public ScratchImageView2(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }


    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        // 获取控件的宽和高
        viewWidth = getWidth();
        viewHeight = getHeight();

        // 遮盖层
        mFgBitmap = Bitmap.createBitmap(viewWidth, viewHeight, Bitmap.Config.ARGB_8888);
        // 底层也就是背景层(被遮盖层)
        mBgBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.ace);

        // bitmap的宽高和view的宽高也许不是等比例的, 这里计算需要缩放的比例
        float scaleWidth = mFgBitmap.getWidth() * 1.0f / mBgBitmap.getWidth();
        float scaleHeight = mFgBitmap.getHeight() * 1.0f / mBgBitmap.getHeight();

        // 为了保证图片能够等比例缩放, 而不是宽/高会被拉伸, 这里要取得相对小的那个值
        float scale = Math.min(scaleWidth, scaleHeight);

        // 通过矩阵进行缩放
        Matrix matrix = new Matrix();
        matrix.postScale(scale, scale);
        // 得到新的图片
        mBgBitmap = Bitmap.createBitmap(mBgBitmap, 0, 0, mBgBitmap.getWidth(), mBgBitmap.getHeight(), matrix,
                true);
        mCanvas = new Canvas(mFgBitmap);
        mCanvas.drawColor(Color.GRAY);
    }

    private void init() {
        mPaint = new Paint();
        // 透明度 不设置为255 即可
        mPaint.setAlpha(0);
        mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
        mPaint.setStyle(Paint.Style.STROKE);
        // 设置结合处的形状为圆角
        mPaint.setStrokeJoin(Paint.Join.ROUND);
        mPaint.setStrokeWidth(50);
        // 设置结尾处的形状为圆角
        mPaint.setStrokeCap(Paint.Cap.ROUND);
        mPath = new Path();
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                mPath.reset();
                mPath.moveTo(event.getX(), event.getY());
                break;

            case MotionEvent.ACTION_MOVE:
                mPath.lineTo(event.getX(), event.getY());
                break;
        }
        invalidate();
        return true;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        // 第一个矩形区域表示要画的bitmap的大小
        Rect bgBtimapRect = new Rect(0, 0, mBgBitmap.getWidth(), mBgBitmap.getHeight());
        // 为了使得bitmap绘制在view的正中心, 这里计算一下起始的x, y的值
        int left = viewWidth / 2 - mBgBitmap.getWidth() / 2;
        int top = viewHeight / 2 - mBgBitmap.getHeight() / 2;
        // 第二个矩形区域表示bitmap要绘制的区域
        Rect bgBitmapStartRect = new Rect(left, top,
                mBgBitmap.getWidth() + left, mBgBitmap.getHeight() + top);
        canvas.drawBitmap(mBgBitmap, bgBtimapRect, bgBitmapStartRect, null);
        canvas.drawBitmap(mFgBitmap, 0, 0, null);
        mCanvas.drawPath(mPath, mPaint);
    }
}
           完事, 第一次写博客,  写了好长时间,  现在感觉大神们写博客分享东西 真的是很辛苦的一件事,  更别说大神们还能把复杂的只是讲解的很透彻,  再次真心感谢网上那些乐于奉献的大神们, 我希望自己也能坚持下去


猜你喜欢

转载自blog.csdn.net/c1392851600/article/details/79593897