自定义波纹点击效果的Button

背景

不知道大家在开发md风格的项目中,各种形状各种颜色的Button是怎么实现的,对于我这样的一个菜鸟来说,就傻傻的一种样式写一个<ripple/>,这样一个项目下来包含大量定义按钮样式的xml文件,不但看着蛋疼,后期维护起来也非常恼火,于是乎开始自定义按钮吧。

自定义按钮

说起自定义View其实并不难,这里的自定义Button更加简单,都不用重写onDraw、onLayout、onMeasure等方法,那就直接上代码,是在不明白的同学请学习下自定义控件的基础知识。
首先是直接在项目的res/values下新建一个attrs.xml文件,然后在里面定义一些可能用到的属性

<!--自定义Button属性-->
    <declare-styleable name="CustomButton">
        <attr name="bgColor" format="color" />
        <attr name="bgColorPress" format="color" />
        <attr name="bgColorDisable" format="color" />
        <attr name="textColor" format="color" />
        <attr name="textColorPress" format="color" />
        <attr name="textColorDisable" format="color" />
        <attr name="cornersRadius" 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>
        <attr name="strokeWidth" format="integer" />
        <attr name="strokeColor" format="color" />
        <attr name="rippleColor" format="color" />
    </declare-styleable>

这里根据需求定义了以上属性,当如如果有更复杂的需求就定义相应的属性,各位大神自由发挥。
然后就是新建一个类CustomButton继承自Button,把所有属性列举出来,然后获取相应属性的值即可,由于比较简单,注释也比较详细,就直接贴代码了
CustomButton.java

package com.liuqiang.customviewlibrary;

import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.GradientDrawable;
import android.graphics.drawable.RippleDrawable;
import android.graphics.drawable.StateListDrawable;
import android.util.AttributeSet;
import android.widget.Button;

/**
 * Created by liuqiang on 2017/11/3.
 * 自定义带波纹点击效果的Button,支持圆角矩形,圆形按钮等样式,可通过配置文件改变按下后的样式
 */
public class CustomButton extends Button {
    private static String TAG = "CustomButton";
    private Context mContext;
    /**
     * 按钮的背景色
     */
    private int bgColor = 0;
    /**
     * 按钮被按下时的背景色
     */
    private int bgColorPress = 0;
    /**
     * 按钮不可用的背景色
     */
    private int bgColorDisable = 0;

    /**
     * 按钮正常时文字的颜色
     */
    private int textColor;
    /**
     * 按钮被按下时文字的颜色
     */
    private int textColorPress;
    /**
     * 按钮不可点击时文字的颜色
     */
    private int textColorDisable;
    /**
     * 按钮的形状
     */
    private int shapeType;
    /**
     * 矩形时有效,4个角的radius
     */
    private float cornersRadius;
    /**
     * 边框线宽度
     */
    private int strokeWidth = 0;
    /**
     * 边框线颜色
     */
    private int strokeColor;

    private ColorStateList rippleColor;


    //shape的样式
    public static final int RECTANGLE = 0;
    public static final int OVAL = 1;
    public static final int LINE = 2;
    public static final int RING = 3;



    private GradientDrawable gradientDrawable = null;



    public CustomButton(Context context) {
        this(context,null);
    }

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

    public CustomButton(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        mContext = context;
        getAttr(attrs);
        init();
    }

    private void getAttr(AttributeSet attrs) {
        if (attrs == null) {
            return;
        }
        TypedArray a = mContext.obtainStyledAttributes(attrs, R.styleable.CustomButton);
        if (a != null) {
            bgColor = a.getColor(R.styleable.CustomButton_bgColor, 0);
            bgColorPress = a.getColor(R.styleable.CustomButton_bgColorPress, 0);
            bgColorDisable = a.getColor(R.styleable.CustomButton_bgColorDisable, 0);

            textColor = a.getColor(R.styleable.CustomButton_textColor, 0);
            textColorPress = a.getColor(R.styleable.CustomButton_textColorPress, 0);
            textColorDisable = a.getColor(R.styleable.CustomButton_textColorDisable, 0);

            shapeType = a.getInt(R.styleable.CustomButton_shape, GradientDrawable.RECTANGLE);
            cornersRadius = a.getFloat(R.styleable.CustomButton_cornersRadius, 0);

            strokeWidth = a.getInteger(R.styleable.CustomButton_strokeWidth,0);
            strokeColor = a.getColor(R.styleable.CustomButton_strokeColor,0);

            rippleColor = a.getColorStateList(R.styleable.CustomButton_rippleColor);
            if (rippleColor == null || rippleColor.getDefaultColor() == 0) {
                rippleColor = createRippleColorStateList(Color.GRAY);
            }
        }
    }
    private void init() {
        setClickable(true);
//        setBackground(getDrawable(android.R.attr.state_enabled));
        setBackground(getSelector());
        setTextColor(createColorStateList());
    }

    /**
     * 设置GradientDrawable
     *
     * @param state 按钮状态
     * @return gradientDrawable
     */
    public GradientDrawable getDrawable(int state) {
        gradientDrawable = new GradientDrawable();
        setShape();
        setBorder();
        setRadius();
        setSelectorColor(state);
        return gradientDrawable;
    }

    /**
     * 设置shape类型
     */
    private void setShape() {

        switch (shapeType) {
            case RECTANGLE:
                gradientDrawable.setShape(GradientDrawable.RECTANGLE);
                break;
            case OVAL:
                gradientDrawable.setShape(GradientDrawable.OVAL);
                break;
            case LINE:
                gradientDrawable.setShape(GradientDrawable.LINE);
                break;
            case RING:
                gradientDrawable.setShape(GradientDrawable.RING);
                break;
        }
    }
    /**
     * 设置边框  宽度  颜色  虚线  间隙
     */
    private void setBorder() {
        gradientDrawable.setStroke(strokeWidth, strokeColor, 0, 0);
    }

    /**
     * 只有类型是矩形的时候设置圆角半径才有效
     */
    private void setRadius() {
        if (shapeType == GradientDrawable.RECTANGLE) {
            if (cornersRadius != 0) {
                gradientDrawable.setCornerRadius(cornersRadius);//设置圆角的半径
            }
        }
    }

    /**
     * 设置Selector的不同状态的颜色
     *
     * @param state 按钮状态
     */
    private void setSelectorColor(int state) {
        switch (state) {
            case android.R.attr.state_pressed:
                gradientDrawable.setColor(bgColorPress);
                break;
            case -android.R.attr.state_enabled:
                gradientDrawable.setColor(bgColorDisable);
                break;
            case android.R.attr.state_enabled:
                gradientDrawable.setColor(bgColor);
                break;
        }
    }

    /**
     * 设置按钮的Selector
     *
     * @return stateListDrawable
     */
    public Drawable getSelector() {
        StateListDrawable stateListDrawable = new StateListDrawable();
        //注意该处的顺序,只要有一个状态与之相配,背景就会被换掉
        //所以不要把大范围放在前面了,如果sd.addState(new[]{},normal)放在第一个的话,就没有什么效果了
        stateListDrawable.addState(new int[]{android.R.attr.state_pressed, android.R.attr.state_enabled}, getDrawable(android.R.attr.state_pressed));
        stateListDrawable.addState(new int[]{-android.R.attr.state_enabled}, getDrawable(-android.R.attr.state_enabled));
        stateListDrawable.addState(new int[]{}, getDrawable(android.R.attr.state_enabled));

        if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.LOLLIPOP) {
            return stateListDrawable;
        }else{

            RippleDrawable rippleDrawable = new RippleDrawable(rippleColor,stateListDrawable,null);
            return rippleDrawable;
        }
    }
    /** 设置不同状态时其文字颜色 */
    private ColorStateList createColorStateList() {
        int[] colors = new int[] { textColorPress, textColorDisable, textColor};
        int[][] states = new int[3][];
        states[0] = new int[] { android.R.attr.state_pressed, android.R.attr.state_enabled };
        states[1] = new int[] { -android.R.attr.state_enabled};
        states[2] = new int[] { android.R.attr.state_enabled };
        ColorStateList colorList = new ColorStateList(states, colors);
        return colorList;
    }

    /** 设置默认ripple颜色 */
    private ColorStateList createRippleColorStateList(int color) {
        int[] colors = new int[] {color};
        int[][] states = new int[1][];
        states[0] = new int[] { };
        ColorStateList colorList = new ColorStateList(states, colors);
        return colorList;
    }

    /////////////////对外暴露的方法//////////////

    /**
     * 设置Shape类型
     *
     * @param type 类型
     * @return 对象
     */
    public CustomButton setShapeType(int type) {
        this.shapeType = type;
        return this;
    }


    /**
     * 设置按下的颜色
     *
     * @param color 颜色
     * @return 对象
     */
    public CustomButton setBgPressedColor(int color) {
        this.bgColorPress = getResources().getColor(color);
        return this;
    }

    /**
     * 设置正常的颜色
     *
     * @param color 颜色
     * @return 对象
     */
    public CustomButton setBgNormalColor(int color) {
        this.bgColor = getResources().getColor(color);
        return this;
    }

    /**
     * 设置不可点击的颜色
     *
     * @param color 颜色
     * @return 对象
     */
    public CustomButton setBgDisableColor(int color) {
        this.bgColorDisable = getResources().getColor(color);
        return this;
    }

    /**
     * 设置按下的颜色
     *
     * @param color 颜色
     * @return 对象
     */
    public CustomButton setTextPressedColor(int color) {
        this.textColorPress = getResources().getColor(color);
        return this;
    }

    /**
     * 设置正常的颜色
     *
     * @param color 颜色
     * @return 对象
     */
    public CustomButton setTextNormalColor(int color) {
        this.textColor = getResources().getColor(color);
        return this;
    }

    /**
     * 设置不可点击的颜色
     *
     * @param color 颜色
     * @return 对象
     */
    public CustomButton setTextDisableColor(int color) {
        this.textColorDisable = getResources().getColor(color);
        return this;
    }

    /**
     * 设置边框宽度
     *
     * @param strokeWidth 边框宽度值
     * @return 对象
     */
    public CustomButton setStrokeWidth(int strokeWidth) {
        this.strokeWidth = strokeWidth;
        return this;
    }

    /**
     * 设置边框颜色
     *
     * @param strokeColor 边框颜色
     * @return 对象
     */
    public CustomButton setStrokeColor(int strokeColor) {
        this.strokeColor = getResources().getColor(strokeColor);
        return this;
    }


    /**
     * 设置圆角半径
     *
     * @param radius 半径
     * @return 对象
     */
    public CustomButton setCornersRadius(float radius) {
        this.cornersRadius = radius;
        return this;
    }

    public CustomButton setRippleColor(int color){
        this.rippleColor = createRippleColorStateList(getResources().getColor(color));
        return this;
    }

    /**
     * 使用shape
     * 所有与shape相关的属性设置之后调用此方法才生效
     */
    public void use() {
        init();
    }


    /**
     * 单位转换工具类
     *
     * @param context  上下文对象
     * @param dipValue 值
     * @return 返回值
     */
    private int dip2px(Context context, float dipValue) {
        final float scale = context.getResources().getDisplayMetrics().density;
        return (int) (dipValue * scale + 0.5f);
    }

}

在应用主题是md主题的情况下,默认的按钮是自带波纹点击效果的,如果用<ripple/>作为按钮的background是完全没问题的,但在自定义的按钮中设置了按钮的背景色,波纹效果就会消失,刚开始想不到办法解决,网上搜索的自定义波纹点击按钮都是要自己绘制,稍显复杂,优点是可以兼容5.0以下的版本。于是只能自己摸索,终于找到了一个简单方法。<ripple/>标签最终都会被解析成RippleDrawable,可不可以直接实例化一个RippleDrawable呢,答案是可以的,直接看上面的代码,主要代码就这一段:

/**
     * 设置按钮的Selector
     *
     * @return stateListDrawable
     */
    public Drawable getSelector() {
        StateListDrawable stateListDrawable = new StateListDrawable();
        //注意该处的顺序,只要有一个状态与之相配,背景就会被换掉
        //所以不要把大范围放在前面了,如果sd.addState(new[]{},normal)放在第一个的话,就没有什么效果了
        stateListDrawable.addState(new int[]{android.R.attr.state_pressed, android.R.attr.state_enabled}, getDrawable(android.R.attr.state_pressed));
        stateListDrawable.addState(new int[]{-android.R.attr.state_enabled}, getDrawable(-android.R.attr.state_enabled));
        stateListDrawable.addState(new int[]{}, getDrawable(android.R.attr.state_enabled));

        if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.LOLLIPOP) {
            return stateListDrawable;
        }else{

            RippleDrawable rippleDrawable = new RippleDrawable(rippleColor,stateListDrawable,null);
            return rippleDrawable;
        }
    }

在设置按钮的背景drawable之前,实例化一个RippleDrawable。
第一参数就是波纹颜色;
第二个参数是平时我们设置的按钮背景selector,如果直接用selector作为背景是没有波纹点击效果的,这里直接把xml转化成StateListDrawable,然后再作为RippleDrawable的content;
第三个参数是控制波纹范围的drawable,这里直接传null。
好了,自定义带波纹效果的Button完成,代码很简单,注释也比较细,就不多解释了。代码传送门

用法

xml方式

app:shape="rectangle" 设置按钮的形状,矩形(包括带圆角的矩形)、圆形、线形、环形(环形一直显示不对,不知道怎么回事)
app:bgColor="@color/colorPrimary" 按钮可点击时的背景色
app:bgColorPress="@color/colorPrimaryDark" 按钮按下时的背景色
app:bgColorDisable="@color/white" 按钮不可用时的背景色
app:cornersRadius="20" 当shape为矩形时的圆角角度
app:strokeWidth="10" 边线宽
app:strokeColor="@color/black" 边线颜色
app:textColor="@color/white" 按钮正常状态时的文字颜色
app:textColorPress="@color/black" 按钮被按下时的文字颜色
app:textColorDisable="@color/gray" 按钮不可用时文字颜色
app:rippleColor="@color/colorAccent" 按钮被触摸时的波纹颜色
注意自定义命名空间 xmlns:app="http://schemas.android.com/apk/res-auto"

<com.liuqiang.customviewlibrary.CustomButton
            android:id="@+id/customButton"
            android:layout_width="match_parent"
            android:layout_height="50dp"
            android:text="自定义按钮(xml设置)"
            android:textColor="@color/white"
            android:textSize="20sp"
            android:gravity="center"

            app:shape="rectangle"
            app:bgColor="@color/colorPrimary"
            app:bgColorPress="@color/colorPrimaryDark"
            app:bgColorDisable="@color/white"
            app:cornersRadius="20"
            app:strokeWidth="10"
            app:strokeColor="@color/black"
            app:textColor="@color/white"
            app:textColorPress="@color/black"
            app:textColorDisable="@color/gray"
            app:rippleColor="@color/colorAccent"
    />

代码方式

customButton_code = (CustomButton) findViewById(R.id.customButton1);
        customButton_code.setShapeType(CustomButton.RECTANGLE)
                .setBgNormalColor(R.color.colorPrimary)
                .setBgPressedColor(R.color.colorPrimaryDark)
                .setBgDisableColor(R.color.white)
                .setCornersRadius(20)
                .setStrokeColor(R.color.black)
                .setStrokeWidth(10)
                .setTextNormalColor(R.color.white)
                .setTextPressedColor(R.color.black)
                .setTextDisableColor(R.color.gray)
                .setRippleColor(R.color.colorAccent)
                .use();

注意设置完属性后记得调用use()方法。

总结

如果你也在苦恼按钮的样式太多,需要写大量的<ripple/><shape/>等xml文件,不妨用这种方式,扩展性也非常强,包括颜色渐变、点击动画等。欢迎

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,293评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,604评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,958评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,729评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,719评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,630评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,000评论 3 397
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,665评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,909评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,646评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,726评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,400评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,986评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,959评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,996评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,481评论 2 342

推荐阅读更多精彩内容