Android自定義佈局實現優惠券效果
最近需要實現一個凹凸效果的擬物化優惠券效果,我一看,本來想用.9圖片做背景實現的,雖說圖片做背景實現省事兒方便,但是能用程式碼實現最好不過了,最終我還是選擇了用程式碼來實現,於是有了下文。
1.完整程式碼
先看完整的程式碼,後面我們再對程式碼逐一的解釋
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.方法解釋
1、CouponDisplayView繼承自RelativeLayout,通過列印日誌測試已知View的執行順序如下:
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,且只有在明確呼叫的時候才會呼叫
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的數量將一個一個的圓繪製在螢幕上
@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;}
有需要設定自定義屬性的我在這裡寫一下哈,嘻嘻
1、在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
。
2、然後在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>
3、在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,設定自定義樣式屬性到此就寫完了。