1. 程式人生 > >Android自定義View之點選效果

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自定義屬性。這裡就不詳細講了。

原始碼地址