Android自定義控件系列(一)—Button七十二變

忙了一段時間,終於有時間整理整理之前所用到的一些知識,分享給大家,希望給同學們有些幫助,同時也是對自己的知識有個鞏固的過程。

在Android的開發中比較常用的控件就是Button了,但是我們平時使用Button時是怎樣來設置按下和擡起顯示不同的效果呢?我想一般的實現方式就是定義一個selector的xml文件,然後在裏面根據不同的state來設置不同的圖片,但是當Button控件非常多的時候,就要寫對應數量的xml文件,導致大碼非常臃腫。

今天我們換種方式來改變這個樣式,只需要兩行代碼即可實現按下的效果,同時支持圓角和圓形的按鈕的樣式。先看下效果圖,這是我寫的一個demo
這裏寫圖片描述

接下來講一下主要代碼:
第一步 自定義屬性
在res/values/目錄下新建attrs.xml文件,

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <!--公共屬性-->
    <attr name="backColor" format="color" />
    <attr name="backColorPress" format="color" />
    <attr name="backGroundImage" format="reference" />
    <attr name="backGroundImagePress" format="reference" />
    <attr name="textColor" format="color" />
    <attr name="textColorPress" format="color" />

    <declare-styleable name="buttonM">
        <attr name="backColor" />
        <attr name="backColorPress" />
        <attr name="backGroundImage"  />
        <attr name="backGroundImagePress" />
        <attr name="textColor" />
        <attr name="textColorPress" />
        <attr name="fillet" format="boolean" />
        <attr name="radius" format="float" />
        <attr name="shape">
            <enum name="rectangle" value="0" />
            <enum name="oval" value="1" />
            <enum name="line" value="2" />
            <enum name="ring" value="3" />
        </attr>
    </declare-styleable>

</resources>

具體屬性的含義在java代碼中都有描述
第二步 創建ButtonM類使其繼承Button,代碼如下:

package com.landptf.view;

import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.GradientDrawable;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.widget.Button;

import com.landptf.R;

/**
 * Created by landptf on 2016/10/25.
 * 自定義Button,支持圓角矩形,圓形按鈕等樣式,可通過配置文件改變按下後的樣式
 * 若通過代碼設置圓角或者圓形,需要先調用setFillet方法將fillet設置爲true
 */
public class ButtonM extends Button {
    private static String TAG = "ButtonM";
    /**
     * 按鈕的背景色
     */
    private int backColor = 0;
    /**
     * 按鈕被按下時的背景色
     */
    private int backColorPress = 0;
    /**
     * 按鈕的背景圖片
     */
    private Drawable backGroundDrawable = null;
    /**
     * 按鈕被按下時顯示的背景圖片
     */
    private Drawable backGroundDrawablePress = null;
    /**
     * 按鈕文字的顏色
     */
    private ColorStateList textColor = null;
    /**
     * 按鈕被按下時文字的顏色
     */
    private ColorStateList textColorPress = null;
    private GradientDrawable gradientDrawable = null;
    /**
     * 是否設置圓角或者圓形等樣式
     */
    private boolean fillet = false;
    /**
     * 標示onTouch方法的返回值,用來解決onClick和onTouch衝突問題
     */
    private boolean isCost = true;

    public ButtonM(Context context) {
        super(context, null);
    }

    public ButtonM(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public ButtonM(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.buttonM, defStyle, 0);
        if (a != null) {
            //設置背景色
            ColorStateList colorList = a.getColorStateList(R.styleable.buttonM_backColor);
            if (colorList != null) {
                backColor = colorList.getColorForState(getDrawableState(), 0);
                if (backColor != 0) {
                    setBackgroundColor(backColor);
                }
            }
            //記錄按鈕被按下時的背景色
            ColorStateList colorListPress = a.getColorStateList(R.styleable.buttonM_backColorPress);
            if (colorListPress != null){
                backColorPress = colorListPress.getColorForState(getDrawableState(), 0);
            }
            //設置背景圖片,若backColor與backGroundDrawable同時存在,則backGroundDrawable將覆蓋backColor
            backGroundDrawable = a.getDrawable(R.styleable.buttonM_backGroundImage);
            if (backGroundDrawable != null){
                setBackgroundDrawable(backGroundDrawable);
            }
            //記錄按鈕被按下時的背景圖片
            backGroundDrawablePress = a.getDrawable(R.styleable.buttonM_backGroundImagePress);
            //設置文字的顏色
            textColor = a.getColorStateList(R.styleable.buttonM_textColor);
            if (textColor != null){
                setTextColor(textColor);
            }
            //記錄按鈕被按下時文字的顏色
            textColorPress = a.getColorStateList(R.styleable.buttonM_textColorPress);
            //設置圓角或圓形等樣式的背景色
            fillet = a.getBoolean(R.styleable.buttonM_fillet, false);
            if (fillet){
                getGradientDrawable();
                if (backColor != 0) {
                    gradientDrawable.setColor(backColor);
                    setBackgroundDrawable(gradientDrawable);
                }
            }
            //設置圓角矩形的角度,fillet爲true時才生效
            float radius = a.getFloat(R.styleable.buttonM_radius, 0);
            if (fillet && radius != 0){
                setRadius(radius);
            }
            //設置按鈕形狀,fillet爲true時才生效
            int shape = a.getInteger(R.styleable.buttonM_shape, 0);
            if (fillet && shape != 0) {
                setShape(shape);
            }
            a.recycle();
        }
        setOnTouchListener(new OnTouchListener() {
            @Override
            public boolean onTouch(View arg0, MotionEvent event) {
                //根據touch事件設置按下擡起的樣式
                return setTouchStyle(event.getAction());
            }
        });
    }

    /**
     * 根據按下或者擡起來改變背景和文字樣式
     * @param state
     * @return isCost
     *  爲解決onTouch和onClick衝突的問題
     *  根據事件分發機制,如果onTouch返回true,則不響應onClick事件
     *  因此採用isCost標識位,當用戶設置了onClickListener則onTouch返回false
     */
    private boolean setTouchStyle(int state){
        if (state == MotionEvent.ACTION_DOWN) {
            if (backColorPress != 0) {
                if (fillet){
                    gradientDrawable.setColor(backColorPress);
                    setBackgroundDrawable(gradientDrawable);
                }else {
                    setBackgroundColor(backColorPress);
                }
            }
            if (backGroundDrawablePress != null) {
                setBackgroundDrawable(backGroundDrawablePress);
            }
            if (textColorPress != null) {
                setTextColor(textColorPress);
            }
        }
        if (state == MotionEvent.ACTION_UP) {
            if (backColor != 0) {
                if (fillet){
                    gradientDrawable.setColor(backColor);
                    setBackgroundDrawable(gradientDrawable);
                }else {
                    setBackgroundColor(backColor);
                }
            }
            if (backGroundDrawable != null) {
                setBackgroundDrawable(backGroundDrawable);
            }
            if (textColor != null) {
                setTextColor(textColor);
            }
        }
        return isCost;
    }

    /**
     * 重寫setOnClickListener方法,解決onTouch和onClick衝突問題
     * @param l
     */
    @Override
    public void setOnClickListener(OnClickListener l) {
        super.setOnClickListener(l);
        isCost = false;
    }

    /**
     * 設置按鈕的背景色
     * @param backColor
     */
    public void setBackColor(int backColor) {
        this.backColor = backColor;
        if (fillet){
            gradientDrawable.setColor(backColor);
            setBackgroundDrawable(gradientDrawable);
        }else {
            setBackgroundColor(backColor);
        }
    }

    /**
     * 設置按鈕被按下時的背景色
     * @param backColorPress
     */
    public void setBackColorPress(int backColorPress) {
        this.backColorPress = backColorPress;
    }

    /**
     * 設置按鈕的背景圖片
     * @param backGroundDrawable
     */
    public void setBackGroundDrawable(Drawable backGroundDrawable) {
        this.backGroundDrawable = backGroundDrawable;
        setBackgroundDrawable(backGroundDrawable);
    }

    /**
     * 設置按鈕被按下時的背景圖片
     * @param backGroundDrawablePress
     */
    public void setBackGroundDrawablePress(Drawable backGroundDrawablePress) {
        this.backGroundDrawablePress = backGroundDrawablePress;
    }

    /**
     * 設置文字的顏色
     * @param textColor
     */
    public void setTextColor(int textColor) {
        if (textColor == 0) return;
        this.textColor = ColorStateList.valueOf(textColor);
        //此處應加super關鍵字,調用父類的setTextColor方法,否則會造成遞歸導致內存溢出
        super.setTextColor(this.textColor);
    }

    /**
     * 設置按鈕被按下時文字的顏色
     * @param textColorPress
     */
    public void setTextColorPress(int textColorPress) {
        if (textColorPress == 0) return;
        this.textColorPress = ColorStateList.valueOf(textColorPress);
    }

    /**
     * 設置按鈕是否設置圓角或者圓形等樣式
     * @param fillet
     */
    public void setFillet(boolean fillet){
        this.fillet = fillet;
        getGradientDrawable();
    }

    /**
     * 設置圓角按鈕的角度
     * @param radius
     */
    public void setRadius(float radius){
        if (!fillet) return;
        getGradientDrawable();
        gradientDrawable.setCornerRadius(radius);
        setBackgroundDrawable(gradientDrawable);
    }

    /**
     * 設置按鈕的形狀
     * @param shape
     */
    public void setShape(int shape){
        if (!fillet) return;
        getGradientDrawable();
        gradientDrawable.setShape(shape);
        setBackgroundDrawable(gradientDrawable);
    }

    private void getGradientDrawable() {
        if (gradientDrawable == null){
            gradientDrawable = new GradientDrawable();
        }
    }

}

註釋基本上寫的比較詳細,下面主要說一下這裏面涉及的一些知識點
1 關於onTouch返回值問題,如果返回true表示要消費該點擊事件,後續的所有事件都交給他處理,同時onTouchEvent將不會執行,因此onClick也得不到執行,在這裏通過重寫setOnClickListener設置變量來改變返回值。具體關於View的事件分發機制可以查閱有關文檔,網上很多這方面的教程。

2 如果想要通過java代碼來設置圓角或者圓形時,必須先設置setFillet(true),然後再設置背景色,形狀或者角度等參數。通過xml文件則無限制

最後講一下怎麼使用,這裏以設置圓角矩形爲例,分別通過xml和java代碼實現,其他的可參考源碼。

1 xml

<com.landptf.view.ButtonM
    android:id="@+id/btm_radius_color_xml"
    android:layout_width="0dp"
    android:layout_height="50dp"
    android:layout_weight="1"
    android:gravity="center"
    android:text="點擊改變背景色"
    landptf:backColor="#ff3300"
    landptf:backColorPress="#ff33ff"
    landptf:fillet="true"
    landptf:radius="30"
    landptf:textColor="@android:color/white" />

2 java

ButtonM btmRadiusColorJava = (ButtonM) findViewById(R.id.btm_radius_color_java);
if (btmRadiusColorJava != null) {
    btmRadiusColorJava.setFillet(true);
    btmRadiusColorJava.setRadius(30);
    btmRadiusColorJava.setTextColor(Color.parseColor("#ffffff"));
    btmRadiusColorJava.setBackColor(Color.parseColor("#ff3300"));
    btmRadiusColorJava.setBackColorPress(Color.parseColor("#ff33ff"));
    btmRadiusColorJava.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            Toast.makeText(ButtonMTestActivity.this, "java代碼實現", Toast.LENGTH_SHORT).show();
        }
    });
}

代碼已託管到開源中國的碼雲上,歡迎下載,地址:https://git.oschina.net/landptf/landptf.git

發佈了21 篇原創文章 · 獲贊 5 · 訪問量 2萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章