Android原生实现控件选择背景变色方案(API28及以上)

Android控件点击/选择后控件背景变色的实现方式有很多种,例如使用selector的xml文件实现。这里介绍一下另一种Android原生的点击/选择实现方案(API28及以上),也就是ColorStateListDrawable

ColorStateListDrawable是一个可根据不同状态显示不同颜色的Drawable。

实现效果,选择前/选择后:
在这里插入图片描述
这里我们利用继承LinearLayoutCompat的方式来实现:

属性

创建自定义属性:

    <attr name="carbon_chipStyle" format="reference" />
    <declare-styleable name="Chip">
        <attr name="android:text"/>
        <attr name="android:background" />
        <attr name="pressed_color" format="color"/>
        <attr name="checked_color" format="color"/>
        <attr name="un_enable_color" format="color"/>
        <attr name="carbon_icon" />
        <attr name="carbon_removable" format="boolean" />
        <attr name="android:checked" />
    </declare-styleable>

布局

创建布局文件

<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="wrap_content"
    android:layout_height="match_parent"
    android:orientation="horizontal">

    <FrameLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_vertical">

        <FrameLayout
            android:id="@+id/carbon_chipContent"
            android:layout_width="@dimen/carbon_iconSize"
            android:layout_height="@dimen/carbon_iconSize"
            android:layout_margin="@dimen/carbon_chipCloseMargin"
            tools:visibility="gone" />

        <ImageView
            android:id="@+id/carbon_chipCheck"
            android:layout_width="@dimen/carbon_iconSize"
            android:layout_height="@dimen/carbon_iconSize"
            android:layout_margin="@dimen/carbon_chipCloseMargin"
            android:visibility="gone"
            android:src="@drawable/carbon_check"
            tools:visibility="visible" />
    </FrameLayout>

    <TextView
        android:id="@+id/carbon_chipText"
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="1"
        android:gravity="center_vertical"
        android:layout_marginHorizontal="@dimen/carbon_chipPadding"
        tools:text="text" />

    <ImageView
        android:id="@+id/carbon_chipClose"
        android:layout_width="@dimen/carbon_iconSize"
        android:layout_height="@dimen/carbon_iconSize"
        android:layout_gravity="center_vertical"
        android:layout_margin="@dimen/carbon_chipCloseMargin"
        android:scaleType="center"
        android:src="@drawable/carbon_remove" />

</merge>

public class Chip extends LinearLayoutCompat implements Checkable {

    /**
     * Interface definition for a callback to be invoked when the checked state of a chip
     * changed.
     */
    public interface OnCheckedChangeListener {
        /**
         * Called when the checked state of a chip has changed.
         *
         * @param chip      The chip whose state has changed.
         * @param isChecked The new checked state of buttonView.
         */
        void onCheckedChanged(Chip chip, boolean isChecked);
    }

    private FrameLayout content;
    private ImageView check;
    private TextView title;
    private ImageView close;
    private OnRemoveListener onRemoveListener;
    private boolean checkedState = false;
    private OnCheckedChangeListener onCheckedChangeListener;

    public interface OnRemoveListener {
        void onDismiss();
    }

    public Chip(Context context) {
        super(context, null, R.attr.carbon_chipStyle);
        initChip(null, R.attr.carbon_chipStyle, R.style.carbon_Chip);
    }

    public Chip(Context context, CharSequence text) {
        super(context, null, R.attr.carbon_chipStyle);
        initChip(null, R.attr.carbon_chipStyle, R.style.carbon_Chip);
        setText(text);
    }

    public Chip(Context context, AttributeSet attrs) {
        super(context, attrs, R.attr.carbon_chipStyle);
        initChip(attrs, R.attr.carbon_chipStyle, R.style.carbon_Chip);
    }

    public Chip(Context context, AttributeSet attrs, @AttrRes int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initChip(attrs, defStyleAttr, R.style.carbon_Chip);
    }

    private static int[] colorStateIds = new int[]{
            R.styleable.Chip_android_background,
            R.styleable.Chip_pressed_color,
            R.styleable.Chip_checked_color,
            R.styleable.Chip_un_enable_color
    };

    private void initChip(AttributeSet attrs, @AttrRes int defStyleAttr, @StyleRes int defStyleRes) {
        inflate(getContext(), R.layout.carbon_chip, this);
        title = findViewById(R.id.carbon_chipText);
        content = findViewById(R.id.carbon_chipContent);
        check = findViewById(R.id.carbon_chipCheck);
        close = findViewById(R.id.carbon_chipClose);

        close.setOnClickListener(v -> {
            if (onRemoveListener != null)
                onRemoveListener.onDismiss();
        });

        TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.Chip, defStyleAttr, defStyleRes);
		// 初始化背景
        Carbon.initDefaultBackground(this, a, colorStateIds);
		// 初始化相关自定义属性
        setText(a.getString(R.styleable.Chip_android_text));
        setIcon(Carbon.getDrawable(this, a, R.styleable.Chip_carbon_icon, 0));
        setRemovable(a.getBoolean(R.styleable.Chip_carbon_removable, false));
       
        a.recycle();
    }

    @Deprecated
    public void setText(String text) {
        setText((CharSequence) text);
    }

    public void setText(CharSequence text) {
        if (text != null) {
            title.setText(text);
            title.setVisibility(View.VISIBLE);
        } else {
            title.setVisibility(View.GONE);
        }
    }

    public void setText(int resId) {
        setText(getResources().getString(resId));
    }

    public String getText() {
        return (String) title.getText();
    }

    public View getTitleView() {
        return title;
    }

    public void setIcon(int iconRes) {
        content.removeAllViews();
        if (iconRes == 0) {
            content.setVisibility(GONE);
            return;
        }
        content.setVisibility(VISIBLE);
        ImageView icon = new ImageView(getContext());
        content.addView(icon);
        icon.setImageResource(iconRes);
    }

    public void setIcon(Drawable drawable) {
        content.removeAllViews();
        if (drawable == null) {
            content.setVisibility(GONE);
            return;
        }
        content.setVisibility(VISIBLE);
        ImageView icon = new ImageView(getContext());
        content.addView(icon);
        icon.setImageDrawable(drawable);
    }

    public void setIcon(Bitmap bitmap) {
        content.removeAllViews();
        if (bitmap == null) {
            content.setVisibility(GONE);
            return;
        }
        content.setVisibility(VISIBLE);
        ImageView icon = new ImageView(getContext());
        content.addView(icon);
        icon.setImageBitmap(bitmap);
    }

    @Deprecated
    public Drawable getIcon() {
        if (content.getChildCount() > 0 && content.getChildAt(0) instanceof ImageView)
            return ((ImageView) content.getChildAt(0)).getDrawable();
        return null;
    }

    @Deprecated
    public View getIconView() {
        if (content.getChildCount() > 0 && content.getChildAt(0) instanceof ImageView)
            return content.getChildAt(0);
        return null;
    }

    public View getContentView() {
        if (content.getChildCount() > 0)
            return content.getChildAt(0);
        return null;
    }

    public void setContentView(View view) {
        content.removeAllViews();
        if (view != null) {
            content.setVisibility(VISIBLE);
            content.addView(view);
        } else {
            content.setVisibility(GONE);
        }
    }

    public void setRemovable(boolean removable) {
        close.setVisibility(removable ? VISIBLE : GONE);
    }

    public boolean isRemovable() {
        return close.getVisibility() == VISIBLE;
    }

    public void setOnRemoveListener(OnRemoveListener onRemoveListener) {
        this.onRemoveListener = onRemoveListener;
    }

}

重点在于为控件手动设置一个ColorListDrawable充当背景图片:

// 为控件设置一个背景图片
 public static void initDefaultBackground(View view, TypedArray a, int[] ids) {
        Drawable d = getDefaultColorDrawable(view, a, ids);
        if (d != null)
            view.setBackgroundDrawable(d);
    }
   // 根据我们提供的android:background,pressed_color,checked_color,un_enable_color的值生成一个ColorStateListDrawable
    public static Drawable getDefaultColorDrawable(View view, TypedArray a, int[] ids) {
        ColorStateList color = getDefaultColorStateList(view, a, ids);
        if (color != null) {
            Drawable d = null;
            if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.Q) {
                d = new ColorStateListDrawable(color);
            }
            return d;
        }
        return null;
    }

    public static ColorStateList getDefaultColorStateList(View view, TypedArray a, int[] ids) {
        Context context = view.getContext();
        int chip_bg = ids[0];
        int chip_pressed_bg = ids[1];
        int chip_checked_bg = ids[2];
        int chip_un_enable_bg = ids[3];

        if (!a.hasValue(chip_bg))
            return null;
        int backgroundColor = a.getColor(chip_bg, 0);
        int pressedBgColor = a.getColor(chip_pressed_bg,ContextCompat.getColor(context, R.color.carbon_colorControlPressed));
        int checkedBgColor = a.getColor(chip_checked_bg,ContextCompat.getColor(context,R.color.carbon_colorControlActivated));
        int unEnableBgColor = a.getColor(chip_un_enable_bg,ContextCompat.getColor(context,R.color.carbon_colorControlDisabled));


        return ColorStateListFactory.getInstance().make(context,backgroundColor,
                pressedBgColor,
                checkedBgColor,
                unEnableBgColor,
                getThemeColor(context,com.google.android.material.R.attr.colorError));
    }

ColorStateListFactory

状态和颜色一一对应

public ColorStateList make(Context context,int defaultColor,int pressed,int activated,int disabled,int invalid){
        return new ColorStateList(
                new int[][]{
                    new int[]{-android.R.attr.state_enabled}, // unenable
                    new int[]{android.R.attr.state_pressed}, // pressed
                    new int[]{android.R.attr.state_checked}, //checked
                    new int[]{android.R.attr.state_activated},//activated
                    new int[]{android.R.attr.state_selected},//selected
                    new int[]{android.R.attr.state_focused},//focused
                    new int[]{}
                },
                new int[]{
                    disabled,
                    pressed,
                    activated,
                    activated,
                    activated,
                    activated,
                    defaultColor
                });
    }

这样,我们就实现了按下控件,控件的背景颜色就会改变。

但是,LinearCompact本身是没有check状态的,因此这就需要我们为它添加check状态。

Checkable接口

Chip实现Checkable接口:

public class Chip extends LinearLayoutCompat implements Checkable {
    // 定义状态集
    private static final int[] CHECKED_STATE_SET = {
            android.R.attr.state_checked
    };
    
    public interface OnCheckedChangeListener {
        /**
         * Called when the checked state of a chip has changed.
         *
         * @param chip      The chip whose state has changed.
         * @param isChecked The new checked state of buttonView.
         */
        void onCheckedChanged(Chip chip, boolean isChecked);
    }

...


public void toggle() {
        setChecked(!isChecked());
    }

    @Override
    public boolean performClick() {
        toggle();

        if (onCheckedChangeListener != null)
            onCheckedChangeListener.onCheckedChanged(this, isChecked());

        final boolean handled = super.performClick();
        if (!handled) {
            // View only makes a sound effect if the onClickListener was
            // called, so we'll need to make one here instead.
            playSoundEffect(SoundEffectConstants.CLICK);
        }

        return handled;
    }

    @ViewDebug.ExportedProperty
    public boolean isChecked() {
        return checkedState;
    }

    /**
     * <p>Changes the checked state of this chip.</p>
     * 第二步
     * 在设置状态时却没有触发到这个状态。所以我们需要自己去触发这个check状态。
     * @param checked true to check the chip, false to uncheck it
     */
    public void setChecked(boolean checked) {
        if (this.checkedState != checked) {
            checkedState = checked;
            check.setVisibility(checked ? VISIBLE : GONE);
            // 在状态改变时,调用refreshDrawableState()刷新状态。
            refreshDrawableState();
        }
    }
	
	// 第一步,我们要把状态给加进去。我们需要重写protected int[] onCreateDrawableState(int extraSpace)方法;
	 /**
     * 先调用父类的onCreateDrawableState方法得到状态数组对象drawableState,但是参数extraSpace要加上1,因为我们要往里面增加一个状态。
     * 然后判断在代码逻辑中,是否为选中状态,如果是的话,调用mergeDrawableStates(drawableState, CHECKED_STATE_SET)方法把我们的状态值给加进去,
     * 最终返回drawableState。
     * @param extraSpace if non-zero, this is the number of extra entries you
     * would like in the returned array in which you can place your own
     * states.
     *
     * @return
     */
    @Override
    protected int[] onCreateDrawableState(int extraSpace) {
        final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
        if (isChecked()) {
            mergeDrawableStates(drawableState, CHECKED_STATE_SET);
        }
        return drawableState;
    }

    /**
     * Register a callback to be invoked when the checked state of this chip changes.
     *
     * @param listener the callback to call on checked state change
     */
    public void setOnCheckedChangeListener(OnCheckedChangeListener listener) {
        onCheckedChangeListener = listener;
    }



}

怎么使用

<com.chinatsp.shapebutton.chip.Chip
                android:id="@+id/chip"
                android:layout_width="100dp"
                android:layout_height="@dimen/carbon_chipHeight"
                android:layout_margin="16dp"
                android:text="HELLO"
                android:background="@color/carbon_defaultColorControl"
                android:clickable="true"
                android:checked="false"
                app:checked_color="@color/carbon_red_700"
                app:un_enable_color="@color/carbon_grey_700"/>

完整代码可查看:
Chip部分

猜你喜欢

转载自blog.csdn.net/jxq1994/article/details/133706635