Android进阶——自定义View之继承TextView巧用DrawableLeft实现自己的CheckableTextView

引言

Android自带的许多控件已经十分强大,甚至很多功能都已经有现成的控件去使用了,不过界面效果是肯定会打折扣的,幸好android控件自身的扩展性十分优秀,很多时候我们只需要简单继承下现有控件扩展些许功能就能得到一个全新的控件,比如说前面Android进阶——自定义View之继承系统控件实现自带删除按钮动画效果和软键盘自动悬浮于文本框下方 的核心思想就是如此。

一、自定义CheckableItemView概述

之前的项目中曾经使用过单选列表Android进阶——RecycleView的使用之自定义单选列表(二) ,当时由于使用单选列表的业务不多,没有使用自定义的View去实现,最近新项目中又使用了单选和多选列表,而且还比较频繁,有必要封装下,于是就自定义了一个选项Item,支持单选和多选。效果如图:
Android进阶——自定义View之继承TextView巧用DrawableLeft实现自己的CheckableTextView_第1张图片

二、Android继承系统控件实现自定义View的通用步骤

  • 继承系统自带控件并实现相应的构造方法

一般实现这三种即可

    public CheckedableItemView(Context context) {
        super(context);
        this.context=context;
    }

    public CheckedableItemView(Context context, AttributeSet attrs) {
        super(context, attrs);
        this.context=context;
        init(attrs);
    }

    public CheckedableItemView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        this.context=context;
        init(attrs);
    }
  • 自定义属性
  • 构造方法中完成额外的初始化工作,一般包括获取自定义属性的值并初始化到控件上(只有初始化到控件之后我们定义的属性才真正的发挥作用)、设置一些常见的属性等
  • 完成功能逻辑的处理,比如说你要去实现勾选效果
  • 覆写自带的交互方法
  • 提供人机交互回调接口

一句话概括其根本思想就是继承系统控件的通用功能,通过去重写构造方法或者其他方法去改变其逻辑,从而实现扩展功能

二、自定义CheckableItemView设计思想

1、绘制勾选框

TextView本身就自带drawableLeft、drawableRight、drawableTop、drawableRight属性可以在TextView的左右上下绘制对应的drawable,再设置下padding值即可,设置drawble之前一定要记得setBounds否则无法显示。

    private void setRightIcon(){

        if(isChecked) {
            rightCheckedIcon.setBounds(0, 0, 68, 68);//一定要记得先设置setBounds,这里我设置为68*68
            setCompoundDrawables(getCompoundDrawables()[0],
                    getCompoundDrawables()[1], rightCheckedIcon, getCompoundDrawables()[3]);
        }else {
            rightIcon.setBounds(0, 0, 68, 68);
            setCompoundDrawables(getCompoundDrawables()[0],
                    getCompoundDrawables()[1], rightIcon, getCompoundDrawables()[3]);
        }
    }

2、模拟勾选前和勾选后的效果

这里我用的是两套UI来简单实现UI效果,但是我们TextView 本身并没有提供类似Checked方法,这里我是使用重写onTouchEvent方法来实现的,但是重写了onTouchEvent这个方法是针对整个TextView的,所以我这里自己处理了下仅仅在Touch在勾选框区域才会去处理,此处需要注意OnTouch事件的传递机制,只有在 MotionEvent.ACTION_DOWN 返回为true时,才会继续产生MotionEvent.ACTION_MOVE、UP事件

    /**
     * 模拟点击事件当我们按下的位置 在  TextView的宽度 - 图标到控件右边的间距 - 图标的宽度  和
     * TextView的宽度 - 图标到控件右边的间距之间就相当于点击了右边的Icon
     */
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()){
            case MotionEvent.ACTION_DOWN:
                return true;//只有在 MotionEvent.ACTION_DOWN 返回为true时,才会继续产生MotionEvent.ACTION_MOVE/UP事件,
            case MotionEvent.ACTION_UP:
                if (getCompoundDrawables()[2] != null) {

                    boolean isRightClick = event.getX() > (getWidth() - getTotalPaddingRight())
                            && (event.getX() < ((getWidth() - getPaddingRight())));
                    if (isRightClick) {
                        if(isCheckedable) {
                            doChecked();
                        }
                    }
                }
            default:
                break;
        }

        return super.onTouchEvent(event);
    }

四、自定义CheckableItemView的实现

1、自定义属性

 <declare-styleable name="checkedable_view">
 
        <attr name="rightIcon" format="reference">attr>
        <attr name="rightCheckedIcon" format="reference">attr>
        <attr name="isChecked" format="boolean">attr>
        <attr name="modeType" format="integer">attr>
    declare-styleable>

2、CheckableItemView.java

package crazy.com.customview.widget;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.widget.TextView;

import crazy.com.customview.R;

/**
 * auther: Crazy.Mo
 * Date: 2017/3/30
 * Time:10:09
 * Des:
 */

public class CheckedableItemView extends TextView {

    private Context context;
    private int modeType;//默认单选0,多选 1
    private boolean isCheckedable=true;
    private boolean isChecked=false;
    private int rightIconId;
    private Drawable rightIcon;//未选中
    private Drawable rightCheckedIcon;//选中

    public CheckedableItemView(Context context) {
        super(context);
        this.context=context;
    }

    public CheckedableItemView(Context context, AttributeSet attrs) {
        super(context, attrs);
        this.context=context;
        init(attrs);
    }

    public CheckedableItemView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        this.context=context;
        init(attrs);
    }
    private void init(AttributeSet attrs){
        initAttrs(attrs);
        setRightIcon();
    }

    private void initAttrs(AttributeSet attrs){
        TypedArray types = context.obtainStyledAttributes(attrs,
                R.styleable.checkedable_view);
        try {
            modeType = types.getInt( R.styleable.checkedable_view_modeType,0 );
            isCheckedable = types.getBoolean( R.styleable.checkedable_view_isChecked,true );
            rightIcon=types.getDrawable(R.styleable.checkedable_view_rightIcon);
            rightCheckedIcon=types.getDrawable(R.styleable.checkedable_view_rightCheckedIcon);
        } finally {
            types.recycle(); // TypeArray用完需要recycle
        }
    }

    public boolean isCheckedable() {
        return isCheckedable;
    }

    public void setCheckedable(boolean checkedable) {
        isCheckedable = checkedable;
    }

    public boolean isChecked() {
        return isChecked;
    }

    public void setChecked(boolean checked) {
        isChecked = checked;
    }

    private void setRightIcon(){

        if(isChecked) {
            rightCheckedIcon.setBounds(0, 0, 68, 68);
            setCompoundDrawables(getCompoundDrawables()[0],
                    getCompoundDrawables()[1], rightCheckedIcon, getCompoundDrawables()[3]);
        }else {
            rightIcon.setBounds(0, 0, 68, 68);
            setCompoundDrawables(getCompoundDrawables()[0],
                    getCompoundDrawables()[1], rightIcon, getCompoundDrawables()[3]);
        }
    }

    /**
     * 模拟点击事件当我们按下的位置 在  TextView的宽度 - 图标到控件右边的间距 - 图标的宽度  和
     * TextView的宽度 - 图标到控件右边的间距之间就相当于点击了右边的Icon
     */
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()){
            case MotionEvent.ACTION_DOWN:
                return true;//只有在 MotionEvent.ACTION_DOWN 返回为true时,才会继续产生MotionEvent.ACTION_MOVE/UP事件,
            case MotionEvent.ACTION_UP:
                if (getCompoundDrawables()[2] != null) {

                    boolean isRightClick = event.getX() > (getWidth() - getTotalPaddingRight())
                            && (event.getX() < ((getWidth() - getPaddingRight())));
                    if (isRightClick) {
                        if(isCheckedable) {
                            doChecked();
                        }
                    }
                }
            default:
                break;
        }

        return super.onTouchEvent(event);
    }

    private void doChecked(){
        if(isChecked){
            rightIcon.setBounds(0, 0, 68, 68);
            setCompoundDrawables(getCompoundDrawables()[0],getCompoundDrawables()[1], rightIcon, getCompoundDrawables()[3]);
            isChecked=false;
        }else {
            rightCheckedIcon.setBounds(0, 0, 68, 68);
            setCompoundDrawables(getCompoundDrawables()[0], getCompoundDrawables()[1], rightCheckedIcon, getCompoundDrawables()[3]);
            isChecked=true;
        }
    }
}

3、使用

shape_edit_found.xml


<shape xmlns:android="http://schemas.android.com/apk/res/android">

        
        <stroke android:width="0.3dp" android:color="#6bb9a9" />

        
        <corners android:radius="4.5dp" />

        
        <solid android:color="#ffffff" />

shape>

activity_main.xml


<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:crazymo="http://schemas.android.com/apk/res-auto"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="crazy.com.customview.MainActivity">

    <crazy.com.customview.widget.CheckedableItemView
        android:id="@+id/single_checked"
        android:gravity="center|left"
        android:layout_marginTop="6dp"
        android:layout_marginLeft="10dp"
        android:layout_marginRight="10dp"
        android:paddingLeft="6dp"
        android:paddingRight="6dp"
        android:layout_width="match_parent"
        android:layout_height="56dp"
        android:background="@drawable/shape_edit_found"
        android:text="你的性别是?"
        android:textSize="26sp"
        crazymo:modeType="0"
        crazymo:rightIcon="@mipmap/single"
        crazymo:rightCheckedIcon="@mipmap/single_selected"/>

    <crazy.com.customview.widget.CheckedableItemView
        android:id="@+id/multi_checked"
        android:gravity="center|left"
        android:paddingLeft="6dp"
        android:paddingRight="6dp"
        android:layout_marginLeft="10dp"
        android:layout_marginRight="10dp"
        android:layout_width="match_parent"
        android:layout_height="56dp"
        android:background="@drawable/shape_edit_found"
        android:layout_below="@id/single_checked"
        android:text="你喜欢的运动?"
        android:textSize="26sp"
        crazymo:modeType="1"
        crazymo:rightIcon="@mipmap/multi"
        crazymo:rightCheckedIcon="@mipmap/multi_selected"/>
RelativeLayout>

这个控件主要就是分享一种思想,灵活的借用系统自带的功能去扩展自己的控件,灵活地封装会减少很多不必要工作。源码直通车

你可能感兴趣的:(Android,进阶,Android自定义View)