Android實用的優(yōu)惠券控件

前言

最近需要做一個優(yōu)惠券功能桐玻,于是找了找简识,發(fā)現(xiàn)網(wǎng)上大多數(shù)優(yōu)惠券控件的都是直接利用Paint繪制一個白色的新圖層然后疊加上去,但是這樣處理的話忿危,當背景不是純白色的時候达箍,就會暴露出如下圖問題:

網(wǎng)上常見項目的效果圖

對于有點強迫癥的人來說,看著怎么都有點難受铺厨。好吧缎玫,俗話說自己動手豐衣足食,咱就自己動手弄個更加完善的出來解滓。

完整代碼項目地址在文章尾部有鏈接赃磨,需要的可以自行下載。

正文

為了解決掉邊緣鋸齒的問題洼裤,我用另外的思路實現(xiàn)了這個功能邻辉,雖然已經(jīng)有較多人造出了輪子,但咱們還是談一下本項目的優(yōu)勢:

  1. 優(yōu)惠券控件邊緣鋸齒形狀有:圓形腮鞍、橢圓值骇、三角形、正方形四種樣式可選移国;
  2. 鋸齒的間距吱瘩、大小、控件顏色等可自定義設(shè)置迹缀,定制性和靈活性更強使碾;
  3. 邊緣鋸齒是摳掉了變成透明的,而不是繪制白色的疊加上去祝懂,更符合客觀世界實際物質(zhì)認知票摇。

效果圖

這里寫圖片描述

實現(xiàn)步驟

一開始我想著,哎~嫂易?直接把Paint 給設(shè)置成DST_OUT 不就完事了嗎兄朋? 結(jié)果試了這種方法之后發(fā)現(xiàn),扣掉的部分不是變成透明怜械,而是黑色... 尷尬颅和,想了想大概是因為LinearLayout有默認處理背景顏色,所以不能直接對控件的Canvas 畫布摳圖缕允,這樣行不通峡扩。那么咱另外添加一個Canvas 當做背景然后摳圖,自定義控件背景默認透明就好了障本。

本項目實現(xiàn)的大致思路和步驟如下:

  1. 定義一個VoucherView類繼承自LinearLayout教届;
  2. 在attrs.xml文件里面聲明所需自定義屬性响鹃;
  3. 將自定義控件背景利用代碼給設(shè)置成完全透明(詳見VoucherView類的initDrawCanvas方法);
  4. 創(chuàng)建一個mCanvas畫布并通過自定義屬性BgColor獲取需設(shè)定的顏色案训;
  5. 創(chuàng)建一個mPaint畫筆买置,用于繪制邊緣鋸齒;
  6. 利用圖像合成類PorterDuffXfermode并給Paint畫筆設(shè)置DST_OUT模式强霎;
  7. 獲取自定義屬性drawType忿项,根據(jù)自定義屬性設(shè)定的形狀,繪制邊緣鋸齒城舞;
  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<自定義優(yōu)惠券控件>
 * @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數(shù)量
    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();//回收內(nèi)存
        }
        
        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數(shù)量的 計算公式 :
     *  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){//背景未設(shè)置情況下,設(shè)置為透明背景
            setBackgroundColor(Color.TRANSPARENT);
        }

        // 初始化鋸齒遮蓋圖層
        mBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
        mCanvas = new Canvas(mBitmap);
        // 繪制圖層顏色
        mCanvas.drawColor(mPaintColor);
    }

    /**
     * 測量水平的item數(shù)目
     * @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數(shù)目
     * @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();
            // 設(shè)置橢圓大小
            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();
            // 設(shè)置多邊形的點
            path.moveTo(x-mRadius,0);
            path.lineTo(x+mRadius,0);
            path.lineTo(x, mRadius);
            path.lineTo(x-mRadius,0);
            // 使這些點構(gòu)成封閉的多邊形
            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());
            // 使這些點構(gòu)成封閉的多邊形
            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();
            // 設(shè)置正方形大小
            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();
            // 設(shè)置橢圓大小
            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();
            // 設(shè)置多邊形的點
            path.moveTo(0,y-mRadius);
            path.lineTo(0,y+mRadius);
            path.lineTo(mRadius,y);
            path.lineTo(0,y-mRadius);
            // 使這些點構(gòu)成封閉的多邊形
            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);
            // 使這些點構(gòu)成封閉的多邊形
            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();
            // 設(shè)置橢圓大小
            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目錄下 - 創(chuàng)建 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>

關(guān)于使用

</br>
</br>

1.引入依賴

有三種方式:

  • Jcenter庫
 compile 'com.xiaosong520:voucher:1.0.1'
  • 引入Module
     下載源代碼拉馋,然后將voucher組件拷貝到工程去榨为,并添加Module依賴
  • 直接拷貝代碼到項目
     直接將項目中 VoucherView 類 以及 attrs.xml 文件里面的自定義屬性拷貝到項目中去(Jcenter庫后續(xù)會添加,具體情況以GitHub項目Readme文檔為準)椅邓。
    </br>
    </br>

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

(路徑請?zhí)鎿Q成實際項目中 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">
        
 <!--優(yōu)惠券實際內(nèi)容部分-->
 <include
            layout="@layout/include_content"/>
        
  </com.voucher.VoucherView>

將include 布局部分替換成你實際所需要的布局就行了景馁,接下來萬事大吉板壮,可以愉快地使用了~

</br>
</br>

3.可自定義的屬性及參數(shù)表格(attrs)

method(方法名稱) format(參數(shù)格式) description(描述)
drawType enum(枚舉) 有圓形、橢圓合住、三角形绰精、正方形這四種邊緣鋸齒形狀
orientation enum(枚舉) 包含 horizontal、vertical透葛、around 這三種方向笨使,分別表示水平、垂直僚害、四周硫椰。
mGap dimension(尺寸) 該參數(shù)控制邊緣鋸齒之間的間隔寬度
mRadius dimension(尺寸) 該參數(shù)控制邊緣鋸齒的半徑長度
BgColor color(顏色) 該參數(shù)控制自定義控件的背景顏色

</br>
</br>

GitHub項目鏈接地址:EasyVoucherView

歡迎提出建議和指出不足,如果感覺對你有幫助的話歡迎Star支持一下萨蚕,也非常樂意Fork和Pull Request~

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末靶草,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子岳遥,更是在濱河造成了極大的恐慌奕翔,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,602評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件浩蓉,死亡現(xiàn)場離奇詭異派继,居然都是意外死亡宾袜,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,442評論 2 382
  • 文/潘曉璐 我一進店門驾窟,熙熙樓的掌柜王于貴愁眉苦臉地迎上來庆猫,“玉大人,你說我怎么就攤上這事纫普≡暮罚” “怎么了?”我有些...
    開封第一講書人閱讀 152,878評論 0 344
  • 文/不壞的土叔 我叫張陵昨稼,是天一觀的道長。 經(jīng)常有香客問我拳锚,道長假栓,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,306評論 1 279
  • 正文 為了忘掉前任霍掺,我火速辦了婚禮匾荆,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘杆烁。我一直安慰自己牙丽,他們只是感情好,可當我...
    茶點故事閱讀 64,330評論 5 373
  • 文/花漫 我一把揭開白布兔魂。 她就那樣靜靜地躺著烤芦,像睡著了一般。 火紅的嫁衣襯著肌膚如雪析校。 梳的紋絲不亂的頭發(fā)上构罗,一...
    開封第一講書人閱讀 49,071評論 1 285
  • 那天,我揣著相機與錄音智玻,去河邊找鬼遂唧。 笑死,一個胖子當著我的面吹牛吊奢,可吹牛的內(nèi)容都是我干的盖彭。 我是一名探鬼主播,決...
    沈念sama閱讀 38,382評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼页滚,長吁一口氣:“原來是場噩夢啊……” “哼召边!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起逻谦,我...
    開封第一講書人閱讀 37,006評論 0 259
  • 序言:老撾萬榮一對情侶失蹤掌实,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后邦马,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體贱鼻,經(jīng)...
    沈念sama閱讀 43,512評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡宴卖,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,965評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了邻悬。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片症昏。...
    茶點故事閱讀 38,094評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖父丰,靈堂內(nèi)的尸體忽然破棺而出肝谭,到底是詐尸還是另有隱情,我是刑警寧澤蛾扇,帶...
    沈念sama閱讀 33,732評論 4 323
  • 正文 年R本政府宣布攘烛,位于F島的核電站,受9級特大地震影響镀首,放射性物質(zhì)發(fā)生泄漏坟漱。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,283評論 3 307
  • 文/蒙蒙 一更哄、第九天 我趴在偏房一處隱蔽的房頂上張望芋齿。 院中可真熱鬧,春花似錦成翩、人聲如沸觅捆。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,286評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽栅炒。三九已至,卻和暖如春庸论,著一層夾襖步出監(jiān)牢的瞬間职辅,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,512評論 1 262
  • 我被黑心中介騙來泰國打工聂示, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留域携,地道東北人。 一個月前我還...
    沈念sama閱讀 45,536評論 2 354
  • 正文 我出身青樓鱼喉,卻偏偏與公主長得像秀鞭,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子扛禽,可洞房花燭夜當晚...
    茶點故事閱讀 42,828評論 2 345

推薦閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 171,514評論 25 707
  • 原文鏈接:https://github.com/opendigg/awesome-github-android-u...
    IM魂影閱讀 32,901評論 6 472
  • 內(nèi)容抽屜菜單ListViewWebViewSwitchButton按鈕點贊按鈕進度條TabLayout圖標下拉刷新...
    皇小弟閱讀 46,708評論 22 664
  • 發(fā)現(xiàn) 關(guān)注 消息 iOS 第三方庫锋边、插件、知名博客總結(jié) 作者大灰狼的小綿羊哥哥關(guān)注 2017.06.26 09:4...
    肇東周閱讀 12,029評論 4 62
  • 對照你喜歡的照片编曼,看看和我拍照片時想表達的是否一樣? 1豆巨、內(nèi)心火熱單純 2、頭腦里總是有許多錯綜復(fù)雜的念頭掐场,但自己...
    水默雪嶺閱讀 296評論 0 0