Android实用的优惠券控件

作者: 一枕黄粱终成梦 | 来源:发表于2017-04-25 17:36 被阅读114次

    前言

    最近需要做一个优惠券功能,于是找了找,发现网上大多数优惠券控件的都是直接利用Paint绘制一个白色的新图层然后叠加上去,但是这样处理的话,当背景不是纯白色的时候,就会暴露出如下图问题:

    网上常见项目的效果图

    对于有点强迫症的人来说,看着怎么都有点难受。好吧,俗话说自己动手丰衣足食,咱就自己动手弄个更加完善的出来。

    完整代码项目地址在文章尾部有链接,需要的可以自行下载。

    正文

    为了解决掉边缘锯齿的问题,我用另外的思路实现了这个功能,虽然已经有较多人造出了轮子,但咱们还是谈一下本项目的优势:

    1. 优惠券控件边缘锯齿形状有:圆形、椭圆、三角形、正方形四种样式可选;
    2. 锯齿的间距、大小、控件颜色等可自定义设置,定制性和灵活性更强;
    3. 边缘锯齿是抠掉了变成透明的,而不是绘制白色的叠加上去,更符合客观世界实际物质认知。

    效果图

    这里写图片描述

    实现步骤

    一开始我想着,哎~?直接把Paint 给设置成DST_OUT 不就完事了吗? 结果试了这种方法之后发现,扣掉的部分不是变成透明,而是黑色... 尴尬,想了想大概是因为LinearLayout有默认处理背景颜色,所以不能直接对控件的Canvas 画布抠图,这样行不通。那么咱另外添加一个Canvas 当做背景然后抠图,自定义控件背景默认透明就好了。

    本项目实现的大致思路和步骤如下:

    1. 定义一个VoucherView类继承自LinearLayout;
    2. 在attrs.xml文件里面声明所需自定义属性;
    3. 将自定义控件背景利用代码给设置成完全透明(详见VoucherView类的initDrawCanvas方法);
    4. 创建一个mCanvas画布并通过自定义属性BgColor获取需设定的颜色;
    5. 创建一个mPaint画笔,用于绘制边缘锯齿;
    6. 利用图像合成类PorterDuffXfermode并给Paint画笔设置DST_OUT模式;
    7. 获取自定义属性drawType,根据自定义属性设定的形状,绘制边缘锯齿;
    8. mCanvas画布擦掉mPaint画笔所绘制的形状部分。

    源代码

    自定义控件 VoucherView类:

    package com.voucher;
    
    import android.content.Context;
    import android.content.res.TypedArray;
    import android.graphics.Bitmap;
    import android.graphics.Canvas;
    import android.graphics.Color;
    import android.graphics.Paint;
    import android.graphics.Path;
    import android.graphics.PorterDuff;
    import android.graphics.PorterDuffXfermode;
    import android.graphics.RectF;
    import android.util.AttributeSet;
    import android.widget.LinearLayout;
    
    
    /**
     * @TODO<自定义优惠券控件>
     * @author 小嵩
     * @date 2017-4-14
     */
    
    public class VoucherView extends LinearLayout {
        private Paint mPaint;
    
        //item间距 默认是5dp
        private float mGap;
    
        //绘制的图层
        private Bitmap mBitmap;
        private Canvas mCanvas;
    
        //item半径 默认是10dp
        private float mRadius ;
    
        //item数量
        private int mCircleNum_H;
        private int mCircleNum_V;
    
        //除过item和间隙外多余出来的部分
        private float mRemain_H;//水平
        private float mRemain_V;//垂直
    
    
        //画笔颜色
        private int mPaintColor;
    
        //指定绘制的方向
        private int mOrientation;
        public final static int DRAW_HORIZONTAL = 0;//水平
        public final static int DRAW_VERTICAL = 1;//垂直
        public final static int DRAW_AROUND = 2;//全部
    
        //锯齿形状 (圆形,椭圆,三角形,正方形)
        private int drawType;
        private static final int CIRCLE = 0;
        private static final int ELLIPSE = 1;
        private static final int TRIANGLE = 2;
        private static final int SQUARE = 3;
    
    
    
        @Override
        public void setOrientation(int orientation) {
        }
    
        public VoucherView(Context context) {
            this(context,null);
        }
    
        public VoucherView(Context context, AttributeSet attrs) {
            this(context, attrs,0);
    
        }
    
        public VoucherView(Context context, AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
    
              if (attrs != null) {
                TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.voucherView, 0, 0);
                drawType = a.getInt(R.styleable.voucherView_drawType, CIRCLE);
                mOrientation = a.getInt(R.styleable.voucherView_orientation,DRAW_HORIZONTAL);//默认水平方向
                mGap = a.getDimensionPixelOffset(R.styleable.voucherView_mGap, 5);
                mRadius = a.getDimensionPixelOffset(R.styleable.voucherView_mRadius, 10);
                mPaintColor = a.getColor(R.styleable.voucherView_BgColor, 0xFFc0c0c0);
                a.recycle();//回收内存
            }
            
            initPaint();
        }
    
        private void initPaint() {//边缘锯齿画笔
            mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
            mPaint.setDither(true);
            mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT));
            mPaint.setStyle(Paint.Style.FILL);
        }
    
        /**
         *  item数量的 计算公式 :
         *  circleNum = (int) ((w-gap)/(2*radius+gap));
         */
    
        @Override
        protected void onSizeChanged(int w, int h, int oldw, int oldh) {
            super.onSizeChanged(w, h, oldw, oldh);
    
            initDrawCanvas(w, h);
    
            switch (mOrientation){
                case DRAW_HORIZONTAL:
                    measureHorNum(w);
                    break;
                case DRAW_VERTICAL:
                    measureVelNum(h);
                    break;
                case DRAW_AROUND:
                    measureHorNum(w);
                    measureVelNum(h);
                    break;
            }
        }
    
    
        /**
         * 初始化绘制图层
         * @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);
            mCanvas = new Canvas(mBitmap);
            // 绘制图层颜色
            mCanvas.drawColor(mPaintColor);
        }
    
        /**
         * 测量水平的item数目
         * @param w
         */
        private void measureHorNum(int w) {
            if(mRemain_H==0){
                mRemain_H=(w-mGap)%(mRadius*2+mGap);
            }
            mCircleNum_H=(int)((w-mGap)/(mRadius*2+mGap));
        }
        /**
         * 测量垂直item数目
         * @param h
         */
        private void measureVelNum(int h) {
            if(mRemain_V==0){
                mRemain_V=(h-mGap)%(mRadius*2+mGap);
            }
            mCircleNum_V=(int)((h-mGap)/(mRadius*2+mGap));
        }
    
    
        /**
         * 绘制锯齿
         * @param canvas
         */
        @Override
        protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);
            canvas.drawBitmap(mBitmap, 0, 0, null);//绘制图层
    
           switch (mOrientation){
    
               /**
                * 水平方向
                */
               case DRAW_HORIZONTAL:
                   if (drawType==CIRCLE){//圆形
                       drawHorCircle();
                   }else if (drawType==ELLIPSE){//椭圆
                       drawHorEllipse();
                   }else if(drawType==TRIANGLE){//三角形
                       drawHorTriangle();
                   }else if(drawType==SQUARE){//正方形
                       drawHorSquare();
                   }
                   break;
    
               /**
                * 垂直方向
                */
               case DRAW_VERTICAL:
                   if (drawType==CIRCLE){//圆形
                       drawVelCircle();
                   }else if (drawType==ELLIPSE){//椭圆
                       drawVelEllipse();
                   }else if(drawType==TRIANGLE){//三角形
                       drawVelTriangle();
                   }else if(drawType==SQUARE){//正方形
                       drawVelSquare();
                   }
                   break;
    
               /**
                * 四周方向
                */
               case DRAW_AROUND:
                   if (drawType==CIRCLE){//圆形
                       drawHorCircle();
                       drawVelCircle();
                   }else if (drawType==ELLIPSE){//椭圆
                       drawHorEllipse();
                       drawVelEllipse();
                   }else if (drawType==TRIANGLE){//三角形
                       drawHorTriangle();
                       drawVelTriangle();
                   }else if(drawType==SQUARE){//正方形
                       drawHorSquare();
                       drawVelSquare();
                   }
                   break;
           }
        }
    
    
        ////***********************************************************////
    
        /**
         * 绘制水平的圆
         */
        private void drawHorCircle() {
            for (int i=0;i<mCircleNum_H;i++){
                float x = mGap+mRadius+mRemain_H/2+((mGap+mRadius*2)*i);
                mCanvas.drawCircle(x,0,mRadius,mPaint);
                mCanvas.drawCircle(x,getHeight(),mRadius,mPaint);
            }
        }
    
        /**
         * 绘制水平的椭圆
         */
        private void drawHorEllipse() {
            for (int i=0;i<mCircleNum_H;i++){
                float x = mGap+mRadius+mRemain_H/2+((mGap+mRadius*2)*i);
                // 定义椭圆对象
                RectF rectf = new RectF();
                // 设置椭圆大小
                rectf.left = x-mRadius;
                rectf.right = x+mRadius;
                rectf.top = 0;
                rectf.bottom = mRadius;
                // 绘制上面的椭圆
                mCanvas.drawOval(rectf, mPaint);
                rectf.top = getHeight()-mRadius;
                rectf.bottom = getHeight();
                // 绘制下面的椭圆
                mCanvas.drawOval(rectf, mPaint);
            }
        }
    
        /**
         * 绘制水平的三角形
         */
        private void drawHorTriangle() {
            for (int i=0;i<mCircleNum_H;i++){
                float x = mGap+mRadius+mRemain_H/2+((mGap+mRadius*2)*i);
                // 绘制三角形
                Path path = new Path();
                // 设置多边形的点
                path.moveTo(x-mRadius,0);
                path.lineTo(x+mRadius,0);
                path.lineTo(x, mRadius);
                path.lineTo(x-mRadius,0);
                // 使这些点构成封闭的多边形
                path.close();
                mCanvas.drawPath(path,mPaint);
    
                //绘制下边缘
                path.moveTo(x-mRadius,getHeight());
                path.lineTo(x+mRadius,getHeight());
                path.lineTo(x,getHeight()-mRadius);
                path.lineTo(x-mRadius,getHeight());
                // 使这些点构成封闭的多边形
                path.close();
                mCanvas.drawPath(path,mPaint);
            }
        }
        /**
         * 绘制水平的正方形
         */
        private void drawHorSquare() {
            for (int i=0;i<mCircleNum_H;i++){
                float x = mGap+mRadius+mRemain_H/2+((mGap+mRadius*2)*i);
    
                mCanvas.drawRect(0,x,0,mRadius,mPaint);
                // 定义正方形对象
                RectF rectf = new RectF();
                // 设置正方形大小
                rectf.left = x-mRadius/2;
                rectf.right = x+mRadius/2;
                rectf.top = 0;
                rectf.bottom = mRadius;
                // 绘制上面的正方形
                mCanvas.drawRect(rectf, mPaint);
                rectf.top = getHeight()-mRadius;
                rectf.bottom = getHeight();
                // 绘制下面的正方形
                mCanvas.drawRect(rectf, mPaint);
            }
        }
    
        ////***********************************************************////
    
        /**
         * 绘制垂直的圆
         */
        private void drawVelCircle() {
            for (int i=0;i<mCircleNum_V;i++){
                float y = mGap+mRadius+mRemain_V/2+((mGap+mRadius*2)*i);
                mCanvas.drawCircle(0,y,mRadius,mPaint);
                mCanvas.drawCircle(getWidth(),y,mRadius,mPaint);
            }
        }
    
    
        /**
         * 绘制垂直的椭圆
         */
        private void drawVelEllipse() {
            for (int i=0;i<mCircleNum_V;i++){
                float y = mGap+mRadius+mRemain_V/2+((mGap+mRadius*2)*i);
                // 定义椭圆对象
                RectF rectf = new RectF();
                // 设置椭圆大小
                rectf.left = 0;
                rectf.right = mRadius;
                rectf.top = y-mRadius;
                rectf.bottom = y+mRadius;
                // 绘制椭圆
                mCanvas.drawOval(rectf, mPaint);
                rectf.left = getWidth()-mRadius;
                rectf.right = getWidth();
                // 绘制椭圆
                mCanvas.drawOval(rectf, mPaint);
            }
        }
    
        /**
         * 绘制垂直的三角形
         */
        private void drawVelTriangle() {
            for (int i=0;i<mCircleNum_V;i++){
                float y = mGap+mRadius+mRemain_V/2+((mGap+mRadius*2)*i);
                // 绘制三角形
                Path path = new Path();
                // 设置多边形的点
                path.moveTo(0,y-mRadius);
                path.lineTo(0,y+mRadius);
                path.lineTo(mRadius,y);
                path.lineTo(0,y-mRadius);
                // 使这些点构成封闭的多边形
                path.close();
                mCanvas.drawPath(path,mPaint);
    
                //绘制下边缘
                path.moveTo(getWidth(),y-mRadius);
                path.lineTo(getWidth(),y+mRadius);
                path.lineTo(getWidth()-mRadius,y);
                path.lineTo(getWidth(),y-mRadius);
                // 使这些点构成封闭的多边形
                path.close();
                mCanvas.drawPath(path,mPaint);
            }
        }
    
    
        /**
         * 绘制垂直的椭圆
         */
        private void drawVelSquare() {
            for (int i=0;i<mCircleNum_V;i++){
                float y = mGap+mRadius+mRemain_V/2+((mGap+mRadius*2)*i);
                // 定义椭圆对象
                RectF rectf = new RectF();
                // 设置椭圆大小
                rectf.left = 0;
                rectf.right = mRadius/2;
                rectf.top = y-mRadius/2;
                rectf.bottom = y+mRadius;
                // 绘制椭圆
                mCanvas.drawRect(rectf, mPaint);
                rectf.left = getWidth()-mRadius;
                rectf.right = getWidth();
                // 绘制椭圆
                mCanvas.drawRect(rectf, mPaint);
            }
        }
    }
    
    

    在values目录下 - 创建 attrs.xml 文件添加如下代码:

    <?xml version="1.0" encoding="utf-8"?>
    <resources>
        <declare-styleable name="voucherView">
    
            <attr name="drawType">
                <enum name="circle" value="0"/>
                <enum name="ellipse" value="1"/>
                <enum name="triangle" value="2"/>
                <enum name="square" value="3"/>
            </attr>
    
            <attr name="orientation">
                <enum name="horizontal" value="0"/>
                <enum name="vertical" value="1"/>
                <enum name="around" value="2"/>
            </attr>
    
            <attr name="mGap" format="dimension"/>
            <attr name="mRadius" format="dimension"/>
            <attr name="BgColor" format="color"/>
    
        </declare-styleable>
    </resources>
    

    关于使用

    </br>
    </br>

    1.引入依赖

    有三种方式:

    • Jcenter库
     compile 'com.xiaosong520:voucher:1.0.1'
    
    • 引入Module
       下载源代码,然后将voucher组件拷贝到工程去,并添加Module依赖
    • 直接拷贝代码到项目
       直接将项目中 VoucherView 类 以及 attrs.xml 文件里面的自定义属性拷贝到项目中去(Jcenter库后续会添加,具体情况以GitHub项目Readme文档为准)。
      </br>
      </br>

    2.在需要使用的布局文件中添加控件,代码如下

    (路径请替换成实际项目中 VoucherView 类所在的路径)。

    根部局添加如下属性:

    xmlns:VoucherView="http://schemas.android.com/apk/res-auto"

    添加控件:

     <com.voucher.VoucherView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:padding="30dp"
            android:layout_centerVertical="true"
            android:layout_centerHorizontal="true"
            VoucherView:drawType="circle"
            VoucherView:orientation="horizontal"
            VoucherView:mGap="5dp"
            VoucherView:mRadius="5dp"
            VoucherView:BgColor="#FFA90F">
            
     <!--优惠券实际内容部分-->
     <include
                layout="@layout/include_content"/>
            
      </com.voucher.VoucherView>
    

    将include 布局部分替换成你实际所需要的布局就行了,接下来万事大吉,可以愉快地使用了~

    </br>
    </br>

    3.可自定义的属性及参数表格(attrs)

    method(方法名称) format(参数格式) description(描述)
    drawType enum(枚举) 有圆形、椭圆、三角形、正方形这四种边缘锯齿形状
    orientation enum(枚举) 包含 horizontal、vertical、around 这三种方向,分别表示水平、垂直、四周。
    mGap dimension(尺寸) 该参数控制边缘锯齿之间的间隔宽度
    mRadius dimension(尺寸) 该参数控制边缘锯齿的半径长度
    BgColor color(颜色) 该参数控制自定义控件的背景颜色

    </br>
    </br>

    GitHub项目链接地址:EasyVoucherView

    欢迎提出建议和指出不足,如果感觉对你有帮助的话欢迎Star支持一下,也非常乐意Fork和Pull Request~

    相关文章

      网友评论

        本文标题:Android实用的优惠券控件

        本文链接:https://www.haomeiwen.com/subject/lgovzttx.html