最近須要實現一個凹凸效果的擬物化優惠券效果,我一看,原本想用.9圖片作背景實現的,雖然說圖片作背景實現省事兒方便,可是能用代碼實現最好不過了,最終我仍是選擇了用代碼來實現,因而有了下文。android
最終效果圖git
demo下載地址github
###1.完整代碼 先看完整的代碼,後面咱們再對代碼逐一的解釋canvas
public class CouponDisplayView extends RelativeLayout {
private Paint mPaint;
private Paint mPaint2;
// 圓間距
private float gap = 0;
// 半徑
private float radius = 20;
// 圓數量
private int circleNum;
private float remain;
private int color;
public CouponDisplayView(Context context) {
super(context);
}
public CouponDisplayView(Context context, AttributeSet attrs) {
super(context, attrs);
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setDither(true);
mPaint.setColor(color);
mPaint.setStyle(Paint.Style.FILL);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
if (remain == 0) {
remain = (int) (w - gap) % (2 * radius + gap);
}
circleNum = (int) ((w - gap) / (2 * radius + gap));
}
public CouponDisplayView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
for (int i = 0; i < circleNum; i++) {
float x = gap + radius + remain / 2 + ((gap + radius * 2) * i);
canvas.drawCircle(x, 0, radius, mPaint);
}
mPaint2 = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint2.setDither(true);
mPaint2.setColor(getResources().getColor(R.color.divider_color_car));
mPaint2.setStyle(Paint.Style.FILL);
Paint paint = new Paint();
paint.setStyle(Paint.Style.STROKE);
paint.setColor(Color.DKGRAY);
Path path = new Path();
path.moveTo(0, getHeight() / 2 + 60);
path.lineTo(getWidth(), getHeight() / 2 + 60);
PathEffect effects = new DashPathEffect(new float[]{15, 15, 15, 15}, 2);
paint.setPathEffect(effects);
canvas.drawPath(path, paint);
canvas.drawCircle(0, getHeight() / 2 + 60, radius, mPaint2);
canvas.drawCircle(getWidth(), getHeight() / 2 + 60, radius, mPaint2);
}
public void setColor(int color) {
this.color = color;
}
}
複製代碼
###2.方法解釋 一、CouponDisplayView繼承自RelativeLayout,經過打印日誌測試已知View的執行順序以下:bash
CouponDisplayView(context,attrs,defStyleAttr)
CouponDisplayView(context,attrs)
onSizeChanged()
onDraw()
複製代碼
onSizeChanged(int w, int h, int oldw, int oldh)
當view的大小發生變化時觸發 onDraw(Canvas canvas)
負責將View繪製在屏幕上 public CouponDisplayView(Context context)
Java代碼直接new一個CouponDisplayView實例的時候,會調用這個只有一個參數的構造函數 public CouponDisplayView(Context context, AttributeSet attrs)
在默認的XML佈局文件中建立的時候調用這個有兩個參數的構造函數。AttributeSet類型的參數負責把XML佈局文件中所自定義的屬性經過AttributeSet帶入到View內; public CouponDisplayView(Context context,AttributeSet attrs, int defStyleAttr)
構造函數中第三個參數是默認的Style,這裏的默認的Style是指它在當前Application或者Activity所用的Theme中的默認Style,且只有在明確調用的時候纔會調用ide
###3.代碼實現思路 從上面的效果圖來看,這個自定義View和普通的Linearlayout,RelativeLayout同樣,只是上下兩邊多了相似於半圓鋸齒的形狀,咱們須要在上下兩條線上畫一個個白色的小圓來實現這種效果。 假如咱們上下線的半圓以及半圓與半圓之間的間距是固定的,那麼不一樣尺寸的屏幕確定會畫出不一樣數量的半圓,那麼咱們只須要根據控件的寬度來獲取能畫的半圓數。 咱們觀察效果圖會發現,圓的數量老是圓間距數量-1,函數
也就是說,假設圓的數量是circleNum,那麼圓間距就是circleNum+1,因此咱們能夠根據這個計算出circleNum: 這裏gap就是圓間距,radius是圓半徑,w是view的寬
佈局
circleNum = (int) ((w-gap)/(2*radius+gap));
複製代碼
1 、重寫onSizeChanged()方法,根據上面的圓的半徑和圓間距來計算須要畫的圓數量circleNum測試
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
if (remain == 0) {
remain = (int) (w - gap) % (2 * radius + gap);
}
circleNum = (int) ((w - gap) / (2 * radius + gap));
}
複製代碼
2.接下來只須要重寫onDraw()方法,簡單的根據circleNum的數量將一個一個的圓繪製在屏幕上ui
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
for (int i = 0; i < circleNum; i++) {
float x = gap + radius + remain / 2 + ((gap + radius * 2) * i);
canvas.drawCircle(x, 0, radius, mPaint);
}
}
複製代碼
3.畫中間的黑色虛線
Paint paint = new Paint();
paint.setStyle(Paint.Style.STROKE);
paint.setColor(Color.DKGRAY);
Path path = new Path();
path.moveTo(0, getHeight() / 2 + 60);
path.lineTo(getWidth(), getHeight() / 2 + 60);
PathEffect effects = new DashPathEffect(new float[]{15, 15, 15, 15}, 2);
paint.setPathEffect(effects);
canvas.drawPath(path, paint);
複製代碼
4.畫兩邊居中的半圓
mPaint2 = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint2.setDither(true);
mPaint2.setColor(getResources().getColor(R.color.divider_color_car));
mPaint2.setStyle(Paint.Style.FILL);
canvas.drawCircle(0, getHeight() / 2 + 60, radius, mPaint2);
canvas.drawCircle(getWidth(), getHeight() / 2 + 60, radius, mPaint2);
複製代碼
代碼分析完畢
###3.設置自定義樣式屬性
考慮到複用地方不是不少,因此上面的代碼沒有寫自定義樣式屬性,而是用了
public void setColor(int color) {this.color = color;}
有須要設置自定義屬性的我在這裏寫一下哈,嘻嘻
一、在res/values/ 下創建一個attr.xml , 在裏面定義咱們的須要用到的屬性以及聲明相對應屬性的取值類型
<?xml version="1.0" encoding="utf-8"?>
<resources>
//半圓顏色
<attr name="radiusColor" format="color" />
<declare-styleable name="CouponDisplayView">
<attr name="radiusColor" />
</declare-styleable>
</resources>
複製代碼
上面定義的半圓顏色的屬性,format屬性的取值類型總共有10種,包括:string
,color
,demension
,integer
,enum
,reference
,float
,boolean
,fraction
,flag
。
二、而後在XML佈局中聲明咱們的自定義View
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:custom="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<--注意:必定要引入xmlns:custom="http://schemas.android.com/apk/res-auto"
custom名字能夠自定義-->
<com.xxx.xxx.CouponDisplayView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#FBB039"
android:orientation="horizontal"
android:padding="16dp"
custom:radiusColor="@Color/red">
............
</com.xxx.xxx.CouponDisplayView>
</LinearLayout>
複製代碼
三、在View的構造方法中,得到咱們的xml佈局文件中定義的顏色
public CouponDisplayView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
Log.d("mDebug", "CouponDisplayView context,attrs,defStyleAttr");
TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.CouponDisplayView, defStyleAttr, 0);
for (int i = 0; i < a.getIndexCount(); i++) {
int attr = a.getIndex(i);
switch (attr) {
case R.styleable.CouponDisplayView_radiusColor:
radius = a.getDimensionPixelSize(R.styleable.CouponDisplayView_radiusColor, 10);
break;
}
}
a.recycle();
}
複製代碼
OK,設置自定義樣式屬性到此就寫完了。