Android自定义View - 优惠卷

本文介绍了一款Android优惠券View控件,支持圆形、椭圆、三角和正方形边缘图案,具备灵活的API支持和背景展示功能。采用模板方法设计模式实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言

在写这个之前也是因为在项目中使用到了类似 优惠卷 的这个东西,然后呢我就在网上找了一些demo看,但是发现他们都存在一些缺陷而且比较单一功能太简单。为什么这么说呢,我给大家说明一下,请看下图:

我发现背景被白色半圆覆盖了,随后翻看了一下Api惊喜的发现有一个设置半圆颜色的,我果断设置了一个transparent,运行一看,连白色的圆角都不见了,变成了一整块 因为在demo中这个圆角是通过调用 canvas drawCircle 方法被绘制出来的,并且绘制在图层之上,所以无法显示出下面的背景图。而且只提供了半圆这一种图形,所以我说相对比较单一。正因如此我来给大家介绍我的 CouponView 最好的Android优惠卷 。先放几张效果图,再讲解实现过程

1.特点

  • 提供了4种边缘图案(圆形椭圆三角正方
  • 可自定义灵活的api支持
  • 以剪裁的方案切入,可展示覆盖在下面的背景图
  • 使用模板方法设计模式完成本例

2.效果图

已在 Github 开源:CouponView,欢迎 Star

3.实现过程讲解

3.1 类文件结构

DrawModel: CouponModel的父类定义一个绘制的框架控制行为,和初始化Paint等工作。

CouponModel: 实现父类的抽象方法,进行具体的绘制

CouponView: view 继承 FrameLayout 负责接收自定义的属性,和处理相关操作,通过调用 DrawModel的模板方法 drawing()实现边缘图形的绘制

3.2源码分析

  • 先来看CouponView
步骤1: 初始化自定义属性
    public CouponView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        if (attrs != null) {
            TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.CouponView, 0, 0);
            //边缘图形类型
            drawType = typedArray.getInt(R.styleable.CouponView_drawShapeType, CIRCLE);
            //图形与图形之间的间隙
            dashGap = typedArray.getDimensionPixelOffset(R.styleable.CouponView_dashGap, 5);
            //半径
            dashWidth = typedArray.getDimensionPixelOffset(R.styleable.CouponView_dashWidth, 10);
            //背景色
            bgc = typedArray.getColor(R.styleable.CouponView_bgc, bgc);

            //虚线的颜色
            lineColor = typedArray.getColor(R.styleable.CouponView_lineColor, Color.WHITE);
            //虚线的宽度
            lineWidth = typedArray.getDimensionPixelOffset(R.styleable.CouponView_lineWidth, dpToPx(2));

            //是否绘制左边边缘的虚线
            isDrawLeftLine = typedArray.getBoolean(R.styleable.CouponView_isDrawLeftLine, false);
            isDrawTopLine = typedArray.getBoolean(R.styleable.CouponView_isDrawTopLine, false);
            isDrawRightLine = typedArray.getBoolean(R.styleable.CouponView_isDrawRightLine, false);
            isDrawBottomLine = typedArray.getBoolean(R.styleable.CouponView_isDrawBottomLine, false);

            //是否绘制左边边缘的图形
            isDrawLeftShape = typedArray.getBoolean(R.styleable.CouponView_isDrawLeftShape, false);
            isDrawTopShape = typedArray.getBoolean(R.styleable.CouponView_isDrawTopShape, false);
            isDrawRightShape = typedArray.getBoolean(R.styleable.CouponView_isDrawRightShape, false);
            isDrawBottomShape = typedArray.getBoolean(R.styleable.CouponView_isDrawBottomShape, false);

            //虚线距离自身顶部距离
            lineMarginTop = typedArray.getDimensionPixelOffset(R.styleable.CouponView_lineMarginTop, dashWidth);
            lineMarginBottom = typedArray.getDimensionPixelOffset(R.styleable.CouponView_lineMarginBottom, dashWidth);
            lineMarginLeft = typedArray.getDimensionPixelOffset(R.styleable.CouponView_lineMarginLeft, dashWidth);
            lineMarginRight = typedArray.getDimensionPixelOffset(R.styleable.CouponView_lineMarginRight, dashWidth);

            typedArray.recycle();
        }
    }

复制代码
步骤2:初始化绘制图层&计算边缘图形的数量
    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);

        initDrawCanvas(w, h);
        calculateItemNum(w, h);
    }
    
   /**
     * 初始化绘制图层
     *
     * @param w
     * @param h
     */
    private void initDrawCanvas(int w, int h) {

        if (getBackground() == null) {//背景为空,设置为透明
            setBackgroundColor(Color.TRANSPARENT);
        }

        // 初始化遮盖图层
        mBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);

        //1.将Canvas指定区域转换为一个Bitmap对象,或者可以理解为将Bitmap保存到Canvas中
        //2.在onDraw方法里面Canvas将新建这个Bitmap对象,新建一个画布
        mCanvas = new Canvas(mBitmap);
        // 绘制图层颜色
        mCanvas.drawColor(bgc);

        //初始化DrawModel
        drawModel = new CouponModel(this);

        drawModel.setCanvas(mCanvas);
    }
    
    
      /**
     * item数量的 计算公式 :
     * Num =  ( w - dashGap ) / ( 2 * dashWidth + dashGap);
     */
    public void calculateItemNum(int w, int h) {
        if (isDrawLeftLine || isDrawRightLine || isDrawLeftShape || isDrawRightShape) {
            drawModel.measureVelNum(h);
        }
        if (isDrawTopLine || isDrawBottomLine || isDrawTopShape || isDrawBottomShape) {
            drawModel.measureHorNum(w);
        }
    }
复制代码
步骤3:绘制锯齿
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        //新建一个bitmap进行绘制
        canvas.drawBitmap(mBitmap, 0, 0, null);
        //DrawModel处理绘制
        drawModel.drawing();
    }
复制代码

DrawModel

public abstract class DrawModel {

    protected final CouponView view;

    //item数量
    protected int horItemSize;
    protected int verItemSize;

    //水平方向除item外多余的部分
    protected float horRedundancy;
    //垂直方向除item外多余的部分
    protected float verRedundancy;

    protected Paint shapePaint;
    protected Paint linePaint;

    protected Canvas mCanvas;


    private void initPaint() {
        //初始化绘制边缘图形的paint
        shapePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        shapePaint.setDither(true);
        shapePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT));
        shapePaint.setStyle(Paint.Style.FILL);

        //初始化绘制虚线的paint
        linePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        linePaint.setStyle(Paint.Style.FILL);
        linePaint.setColor(view.getLineColor());
        linePaint.setStrokeWidth(view.getLineWidth());
    }

    public DrawModel(CouponView view) {
        this.view = view;
        initPaint();
    }

    public void setCanvas(Canvas canvas) {
        this.mCanvas = canvas;
    }

    /**
     * 绘制的入口
     */
    public void drawing() {//模板方法

        if (view.getDrawType() == CIRCLE) {
            drawCircle();
        } else if (view.getDrawType() == OVAL) {
            drawOval();
        } else if (view.getDrawType() == TRIANGLE) {
            drawTriangle();
        } else if (view.getDrawType() == SQUARE) {
            drawSquare();
        }

        if (view.isDrawLeftLine() || view.isDrawRightLine() || view.isDrawTopLine() || view.isDrawTopLine()) {
            drawLine();
        }
    }


    /**
     * 测量垂直item数目
     *
     * @param h
     */
    public void measureVelNum(int h) {
        if (verRedundancy == 0) {
            verRedundancy = (h - view.getDashGap()) % (view.getDashWidth() * 2 + view.getDashGap());
        }
        verItemSize = (h - view.getDashGap()) / (view.getDashWidth() * 2 + view.getDashGap());
    }

    /**
     * 测量水平的item数目
     *
     * @param w
     */
    public void measureHorNum(int w) {
        if (horRedundancy == 0) {
            horRedundancy = (w - view.getDashGap()) % (view.getDashWidth() * 2 + view.getDashGap());
        }
        horItemSize = (w - view.getDashGap()) / (view.getDashWidth() * 2 + view.getDashGap());
    }

    /**
     * 绘制正方形
     */
    protected abstract void drawSquare();

    /**
     * 绘制三角形
     */
    protected abstract void drawTriangle();

    /**
     * 绘制椭圆
     */
    protected abstract void drawOval();

    /**
     * 绘制三角形
     */
    protected abstract void drawCircle();

    /**
     * 绘制虚线
     */
    protected abstract void drawLine();
}
复制代码

作为模板方法设计模式的父类提供了一个模板方法 drawing() 实现对基本方法的调度这也是这个设计模式的定义表现,以及初始化 Paint 和计算出水平方向垂直方向边缘图形的数量。关于设计模式可以参考我的这些文章那些你要知道的设计模式

public class CouponModel extends DrawModel {

    public CouponModel(CouponView view) {
        super(view);
    }

    @Override
    protected void drawCircle() {

        //遍历垂直方向可绘制size
        for (int i = 0; i < verItemSize; i++) {
            //圆心位置 = 间隙 + item半径 + 垂直方向多余部分/2 + (间隙 + item半径*2) * i
            float y = view.getDashGap() + view.getDashWidth() + verRedundancy / 2 + ((view.getDashGap() + view.getDashWidth() * 2) * i);
            if (view.isDrawLeftShape()) {
                mCanvas.drawCircle(0, y, view.getDashWidth(), shapePaint);
            }
            if (view.isDrawRightShape()) {
                mCanvas.drawCircle(view.getWidth(), y, view.getDashWidth(), shapePaint);
            }
        }

        //遍历水平方向可绘制size
        for (int i = 0; i < horItemSize; i++) {
            float x = view.getDashGap() + view.getDashWidth() + horRedundancy / 2 + ((view.getDashGap() + view.getDashWidth() * 2) * i);
            if (view.isDrawTopShape()) {
                mCanvas.drawCircle(x, 0, view.getDashWidth(), shapePaint);
            }
            if (view.isDrawBottomShape()) {
                mCanvas.drawCircle(x, view.getHeight(), view.getDashWidth(), shapePaint);
            }
        }
    }

    @Override
    protected void drawOval() {
        for (int i = 0; i < horItemSize; i++) {
            float x = view.getDashGap() + view.getDashWidth() + horRedundancy / 2 + ((view.getDashGap() + view.getDashWidth() * 2) * i);

            RectF rectf = new RectF();
            // 设置椭圆大小
            rectf.left = x - view.getDashWidth();
            rectf.right = x + view.getDashWidth();
            // 绘制上面的椭圆
            if (view.isDrawTopShape()) {
                rectf.top = 0;
                rectf.bottom = view.getDashWidth();
                mCanvas.drawOval(rectf, shapePaint);
            }
            // 绘制下面的椭圆
            if (view.isDrawBottomShape()) {
                rectf.top = view.getHeight() - view.getDashWidth();
                rectf.bottom = view.getHeight();
                mCanvas.drawOval(rectf, shapePaint);
            }
        }

        for (int i = 0; i < verItemSize; i++) {
            float y = view.getDashGap() + view.getDashWidth() + verRedundancy / 2 + ((view.getDashGap() + view.getDashWidth() * 2) * i);
            // 定义椭圆对象
            RectF rectf = new RectF();
            // 设置椭圆大小
            rectf.top = y - view.getDashWidth();
            rectf.bottom = y + view.getDashWidth();
            // 绘制椭圆
            if (view.isDrawLeftShape()) {
                rectf.left = 0;
                rectf.right = view.getDashWidth();
                mCanvas.drawOval(rectf, shapePaint);
            }

            // 绘制椭圆
            if (view.isDrawRightShape()) {
                rectf.left = view.getWidth() - view.getDashWidth();
                rectf.right = view.getWidth();
                mCanvas.drawOval(rectf, shapePaint);
            }
        }
    }

    @Override
    protected void drawSquare() {

        for (int i = 0; i < verItemSize; i++) {
            float y = view.getDashGap() + view.getDashWidth() + verRedundancy / 2 + ((view.getDashGap() + view.getDashWidth() * 2) * i);

            RectF rectf = new RectF();
            // 设置正方形大小
            rectf.top = y - view.getDashWidth() / 2;
            rectf.bottom = y + view.getDashWidth();

            // 如果isDrawLeftSide为true
            if (view.isDrawLeftShape()) {
                rectf.left = 0;
                rectf.right = view.getDashWidth() ;
                mCanvas.drawRect(rectf, shapePaint);
            }

            if (view.isDrawRightShape()) {
                rectf.left = view.getWidth() - view.getDashWidth();
                rectf.right = view.getWidth();
                mCanvas.drawRect(rectf, shapePaint);
            }
        }

        for (int i = 0; i < horItemSize; i++) {
            float x = view.getDashGap() + view.getDashWidth() + horRedundancy / 2 + ((view.getDashGap() + view.getDashWidth() * 2) * i);
            mCanvas.drawRect(0, x, 0, view.getDashWidth(), shapePaint);

            RectF rectf = new RectF();
            // 设置正方形大小
            rectf.left = x - view.getDashWidth() / 2;
            rectf.right = x + view.getDashWidth() ;

            // 如果isDrawTopSide为true
            if (view.isDrawTopShape()) {
                rectf.top = 0;
                rectf.bottom = view.getDashWidth();
                mCanvas.drawRect(rectf, shapePaint);
            }

            if (view.isDrawBottomShape()) {
                rectf.top = view.getHeight() - view.getDashWidth();
                rectf.bottom = view.getHeight();
                mCanvas.drawRect(rectf, shapePaint);
            }
        }
    }

    @Override
    protected void drawTriangle() {
        for (int i = 0; i < verItemSize; i++) {
            float y = view.getDashGap() + view.getDashWidth() + verRedundancy / 2 + ((view.getDashGap() + view.getDashWidth() * 2) * i);

            Path path = new Path();
            //如果isDrawLeftSide为true
            if (view.isDrawLeftShape()) {
                // 设置三角形的点
                path.moveTo(0, y - view.getDashWidth());
                path.lineTo(0, y + view.getDashWidth());
                path.lineTo(view.getDashWidth(), y);
                path.close();
                mCanvas.drawPath(path, shapePaint);
            }

            if (view.isDrawRightShape()) {
                path.moveTo(view.getWidth(), y - view.getDashWidth());
                path.lineTo(view.getWidth(), y + view.getDashWidth());
                path.lineTo(view.getWidth() - view.getDashWidth(), y);
                path.close();
                mCanvas.drawPath(path, shapePaint);
            }
        }

        for (int i = 0; i < horItemSize; i++) {
            float x = view.getDashGap() + view.getDashWidth() + horRedundancy / 2 + ((view.getDashGap() + view.getDashWidth() * 2) * i);

            Path path = new Path();
            //如果isDrawTopSide为true
            if (view.isDrawTopShape()) {
                // 设置三角形的点
                path.moveTo(x - view.getDashWidth(), 0);
                path.lineTo(x + view.getDashWidth(), 0);
                path.lineTo(x, view.getDashWidth());
                path.close();
                mCanvas.drawPath(path, shapePaint);
            }

            if (view.isDrawBottomShape()) {
                path.moveTo(x - view.getDashWidth(), view.getHeight());
                path.lineTo(x + view.getDashWidth(), view.getHeight());
                path.lineTo(x, view.getHeight() - view.getDashWidth());
                path.close();
                mCanvas.drawPath(path, shapePaint);
            }
        }
    }

    @Override
    protected void drawLine() {

        //遍历垂直方向可绘制size
        for (int i = 0; i < verItemSize; i++) {
            //圆心位置 = 间隙 + item半径 + 垂直方向多余部分/2 + (间隙 + item半径*2) * i
            float y = view.getDashGap() + view.getDashWidth() + verRedundancy / 2 + ((view.getDashGap() + view.getDashWidth() * 2) * i);
            if (view.isDrawLeftLine()) {
                mCanvas.drawLine(view.getLineMarginLeft(), y - view.getDashWidth(), view.getLineMarginLeft(), y + view.getDashWidth(), linePaint);
            }
            if (view.isDrawRightLine()) {
                mCanvas.drawLine(view.getWidth() - view.getLineMarginRight(), y - view.getDashWidth(), view.getWidth() - view.getLineMarginRight(), y + view.getDashWidth(), linePaint);
            }
        }

        //遍历水平方向可绘制size
        for (int i = 0; i < horItemSize; i++) {
            float x = view.getDashGap() + view.getDashWidth() + horRedundancy / 2 + ((view.getDashGap() + view.getDashWidth() * 2) * i);
            if (view.isDrawTopLine()) {
                mCanvas.drawLine(x - view.getDashWidth(), view.getLineMarginTop(), x + view.getDashWidth(), view.getLineMarginTop(), linePaint);
            }
            if (view.isDrawBottomLine()) {
                mCanvas.drawLine(x - view.getDashWidth(), view.getHeight() - view.getLineMarginBottom(), x + view.getDashWidth(), view.getHeight() - view.getLineMarginBottom(), linePaint);
            }
        }
    }
}
复制代码

作为子类实现父类中定义的基本方法,绘制圆形椭圆三角正方还有虚线。通过遍历边缘图形的数量size大小,结合自定义属性中的 dashGap 间隙, dashWidthcode 半径,和在 CouponView onSizeChanged 方法中计算得出冗余部分的宽度,确定每个边缘图形的坐标位置进行绘制。


至此,该优惠卷View控件源码分析完毕。感谢阅读!

完整源码地址:https://github.com/xwc520/CouponView

已在 Github 开源:CouponView,欢迎 留言, Star

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值