Android自定義View之點選效果
最近在做新版本,各種UI效果都需要自定義,而自定義View點選效果問題一直困擾著我。各種找資料也沒有找到自己想要的東西,可能是我關鍵字打的不對吧。最後在檢視TextView的原始碼時解決了我的問題,由於原始碼功能太多,不易查詢,特此提取記錄。
UI效果
預設效果
點選效果
ClickEffectView程式碼
public class ClickEffectView extends View {
private ColorStateList mTextColorList = ColorStateList.valueOf(Color.GRAY);
private String mContextText;
private Drawable mBgDrawable;
private int mCurTextColor;
private Paint mTextPaint;
private Rect mRect;
public ClickEffectView(Context context) {
this(context,null);
}
public ClickEffectView(Context context, AttributeSet attrs) {
this (context, attrs,0);
}
public ClickEffectView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
/*繫結StyleAble自定義屬性,使ClickEffectView支援XML賦值屬性值*/
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.ClickEffectView) ;
ColorStateList color = null;
color = typedArray.getColorStateList(R.styleable.ClickEffectView_cev_TextColor);
if (color != null) {
setTextColor(color);
}
String text = typedArray.getString(R.styleable.ClickEffectView_cev_Text);
setText(text);
mBgDrawable = typedArray.getDrawable(R.styleable.ClickEffectView_cev_Background);
typedArray.recycle();
mTextPaint = createPaint();
mTextPaint.setTextSize(30);
mRect = new Rect();
}
protected Paint createPaint() {
Paint paint = new Paint();
paint.setAntiAlias(true);
paint.setDither(true);
paint.setFilterBitmap(true);
paint.setXfermode(null);
return paint;
}
@Override
protected void drawableStateChanged() {
super.drawableStateChanged();
updateState();
}
@Override
protected void onDraw(Canvas canvas) {
int width = canvas.getWidth();
int height = canvas.getHeight();
if(mBgDrawable != null){
mBgDrawable.setBounds(0,0,width,height);
mBgDrawable.draw(canvas);
}
//在繪製之前,重新設定顏色,以保證是根據getDrawableState()得到的值
mTextPaint.setColor(mCurTextColor);
mRect.set(0 , 0, width, height);
Paint.FontMetricsInt fontMetrics = mTextPaint.getFontMetricsInt() ;
int baseline = (mRect. bottom + mRect.top - fontMetrics.bottom - fontMetrics.top ) / 2;
// 下面這行是實現水平居中,drawText對應改為傳入targetRect.centerX()
mTextPaint.setTextAlign(Paint.Align. CENTER);
canvas.drawText(TextUtils. isEmpty( mContextText ) ? "" : mContextText ,
mRect.centerX() , baseline, mTextPaint);
}
public void setTextColor(ColorStateList textColor) {
this.mTextColorList = textColor;
updateState();
}
public void setText(String text) {
if(TextUtils.isEmpty(text)){
this.mContextText = "";
}
this.mContextText = text;
}
private void updateState() {
boolean invalidate = false;
//獲取View當前狀態
int[] states = getDrawableState();
//獲取狀態對應的顏色
int textColor = mTextColorList.getColorForState(states, 0);
if(textColor != mCurTextColor){
mCurTextColor = textColor;
invalidate = true;
}
if(mBgDrawable != null){
mBgDrawable.setState(states);
invalidate = true;
}
if(invalidate){
invalidate();
}
}
}
自定義屬性
/res/values/attrs.xml
<declare-styleable name="ClickEffectView">
<!--文字顏色-->
<attr name="cev_TextColor" format="color"/>
<!--文字內容-->
<attr name="cev_Text" format="string"/>
<!--背景-->
<attr name="cev_Background" format="reference"/>
</declare-styleable>
思考
上面View是在按下時改變View的背景以及文字顏色來實現的,要想達到上面的效果,那麼問題來了。
1.怎麼偵聽View的點選狀態呢?
答:偵聽View的點選狀態可以實現View的drawableStateChanged()函式,當View的狀態改變時都會呼叫這個函式來通知View。比如說:按下狀態(pressed),聚焦狀態(focused),選中狀態(selected)。
2.這麼多狀態都會通知drawableStateChanged()函式,那怎麼知道當前View是什麼狀態呢?
答:View通過 int[] getDrawableState() 函式來獲取獲取當前的狀態。
3.那麼我們要怎麼去儲存View的這些動態UI特徵,又怎麼通過一個int[]的View狀態資訊來讀取這些特徵?
答:Android已經為我們提供了方法了,像顏色的動態特徵儲存及讀取可使用ColorStateList類,而背景的動態特徵儲存及讀取則可使用Drawable類。
ColorStateList
儲存,在/res/color目錄下建立資原始檔
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<!--按下後的綠色-->
<item android:color="#00ff00" android:state_pressed="true"/>
<!--預設的灰色-->
<item android:color="#666666"/>
</selector>
讀取:
//獲取View當前狀態
int[] states = getDrawableState();
//獲取狀態對應的顏色
int textColor = mTextColorList.getColorForState(states, 0);
Drawable
儲存,在/res/drawable目錄下建立資原始檔
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<!--按下的綠色矩形-->
<item android:drawable="@drawable/shape_rectangle_bg_green_border_green_press"
android:state_pressed="true" />
<!--預設的灰色矩形-->
<item android:drawable="@drawable/shape_rectangle_bg_gray_border_gray_normal"/>
</selector>
讀取:
//獲取View當前狀態
int[] states = getDrawableState();
//Drawable會根據當前狀態來改變
mBgDrawable.setState(states);
4.狀態偵聽及資訊儲存都已搞定了,那麼View怎麼去及時的更新View呢?
答:我們只要在drawableStateChanged()函式中通過getDrawableState(),去改變我們想要改變的屬性,然後呼叫View的invalidate()函式,則會呼叫onDraw()去重繪View了,不過不要忘了在onDraw()函式中繪製時,將這些動態屬性賦值,這樣才能保證每次重繪的時候拿到的是根據getDrawableStae()獲取的值。
5.怎麼在XML檔案中設定這些屬性?
答:要想在XML中設定這些屬性,則需要根據屬性繫結styleable自定義屬性。這裡就不詳細講了。