前言
最近需要做一個優(yōu)惠券功能桐玻,于是找了找简识,發(fā)現(xiàn)網(wǎng)上大多數(shù)優(yōu)惠券控件的都是直接利用Paint繪制一個白色的新圖層然后疊加上去,但是這樣處理的話忿危,當背景不是純白色的時候达箍,就會暴露出如下圖問題:
對于有點強迫癥的人來說,看著怎么都有點難受铺厨。好吧缎玫,俗話說自己動手豐衣足食,咱就自己動手弄個更加完善的出來解滓。
完整代碼項目地址在文章尾部有鏈接赃磨,需要的可以自行下載。
正文
為了解決掉邊緣鋸齒的問題洼裤,我用另外的思路實現(xiàn)了這個功能邻辉,雖然已經(jīng)有較多人造出了輪子,但咱們還是談一下本項目的優(yōu)勢:
- 優(yōu)惠券控件邊緣鋸齒形狀有:圓形腮鞍、橢圓值骇、三角形、正方形四種樣式可選移国;
- 鋸齒的間距吱瘩、大小、控件顏色等可自定義設(shè)置迹缀,定制性和靈活性更強使碾;
- 邊緣鋸齒是摳掉了變成透明的,而不是繪制白色的疊加上去祝懂,更符合客觀世界實際物質(zhì)認知票摇。
效果圖
實現(xiàn)步驟
一開始我想著,哎~嫂易?直接把Paint 給設(shè)置成DST_OUT 不就完事了嗎兄朋? 結(jié)果試了這種方法之后發(fā)現(xiàn),扣掉的部分不是變成透明怜械,而是黑色... 尷尬颅和,想了想大概是因為LinearLayout有默認處理背景顏色,所以不能直接對控件的Canvas 畫布摳圖缕允,這樣行不通峡扩。那么咱另外添加一個Canvas 當做背景然后摳圖,自定義控件背景默認透明就好了障本。
本項目實現(xiàn)的大致思路和步驟如下:
- 定義一個VoucherView類繼承自LinearLayout教届;
- 在attrs.xml文件里面聲明所需自定義屬性响鹃;
- 將自定義控件背景利用代碼給設(shè)置成完全透明(詳見VoucherView類的initDrawCanvas方法);
- 創(chuàng)建一個mCanvas畫布并通過自定義屬性BgColor獲取需設(shè)定的顏色案训;
- 創(chuàng)建一個mPaint畫筆买置,用于繪制邊緣鋸齒;
- 利用圖像合成類PorterDuffXfermode并給Paint畫筆設(shè)置DST_OUT模式强霎;
- 獲取自定義屬性drawType忿项,根據(jù)自定義屬性設(shè)定的形狀,繪制邊緣鋸齒城舞;
- 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~