CompoundButton 源码分析

CompoundButton 源码分析原文地址:https://github.com/Tikitoo/AndroidSdkSourceAnalysis/blob/master/article/CompoundButton%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90.md CompoundButton是一个有两种状态(选中和未选中/checkdunchecked)的Button。当你按下(pres…

大家好,又见面了,我是你们的朋友全栈君。

原文地址:https://github.com/Tikitoo/AndroidSdkSourceAnalysis/blob/master/article/CompoundButton%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90.md

 

CompoundButton 是一个有两种状态(选中和未选中 / checkd unchecked)的Button。当你按下(pressed)或者点击(clicked),它的状态会自动改变。

特点

  • 是一个抽象类(abstract),所以我们不能直接使用它,只有自定义实现或者系统已经提供的它的一直子类(ToggleButton,Checkbox,RadioButton 等等)。
  • 继承自Button,而Button 继承自TextView,所以Button,TextView 的特性CompoundButton 都是具备的。
  • 实现自Checkable 接口(interface),利用它可以设置状态(setChecked(boolean checked)),获取状态(isChecked())和切换状态(toggle())。

最后的效果图就是这样的。
CompoundButton 源码分析

分析

// 选中和未选中的状态
private boolean mChecked;

private boolean mBroadcasting;

private Drawable mButtonDrawable;

private ColorStateList mButtonTintList = null;
// 就是水波纹和背景颜色混合的方式
private PorterDuff.Mode mButtonTintMode = null;

private boolean mHasButtonTint = false;
private boolean mHasButtonTintMode = false;

// 状态监听
private OnCheckedChangeListener mOnCheckedChangeListener;
private OnCheckedChangeListener mOnCheckedChangeWidgetListener;

 

这是一些局部变量,在后面的分析会用到。

我们先来看看CompoundButton 自定义控件有哪些属性 \data\res\values\attrs.xml

 <declare-styleable name="CompoundButton">
      <!-- 设置状态  true: 选中; false: 未选中 -->
      <attr name="checked" format="boolean" />
      <!-- 绘制按钮图形,一般为Drawable 资源 (e.g. checkbox, radio button, etc). -->
      <attr name="button" format="reference" />
      <!-- 对绘制的按钮图形着色 -->
      <attr name="buttonTint" format="color" />
      <!-- 对着色设置模式 -->
      <attr name="buttonTintMode">
          <enum name="src_over" value="3" />
          ...
      </attr>
  </declare-styleable>

 

然后再来看看怎么绘制,先来看看构造方法

public CompoundButton(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
    super(context, attrs, defStyleAttr, defStyleRes);

    // 这里的获取自定义的CompoundButton 就是上面的定义的CompoundButton
    final TypedArray a = context.obtainStyledAttributes(
            attrs, com.android.internal.R.styleable.CompoundButton, defStyleAttr, defStyleRes);

    // 用于绘制按钮图形
    final Drawable d = a.getDrawable(com.android.internal.R.styleable.CompoundButton_button);
    if (d != null) {
        setButtonDrawable(d);
    }

    // 对绘制的按钮图形着色设置模式
    if (a.hasValue(R.styleable.CompoundButton_buttonTintMode)) {
        mButtonTintMode = Drawable.parseTintMode(a.getInt(
                R.styleable.CompoundButton_buttonTintMode, -1), mButtonTintMode);
        mHasButtonTintMode = true;
    }

    // 对绘制的按钮图形着色
    if (a.hasValue(R.styleable.CompoundButton_buttonTint)) {
        mButtonTintList = a.getColorStateList(R.styleable.CompoundButton_buttonTint);
        mHasButtonTint = true;
    }

    // 设置状态
    final boolean checked = a.getBoolean(
            com.android.internal.R.styleable.CompoundButton_checked, false);
    setChecked(checked);

    a.re cycle();

    applyButtonTint();
}

 

在构造方法中,获取自定义属性的各个属性,

  • button:用于绘制按钮图形,然后调用setButtonDrawable() 来绘制。
  • buttonTint:绘制的按钮着色。使用一个boolean 标识符来设置的,然后会在applyButtonTint() 中统一处理。它们两个分别用作给和
  • buttonTintMode:和设置着色模式。设置方式和buttonTint 几乎一样。不过它的一些属性,参考这篇文章来看看具体不同的着色模式效果是怎么样的。android5.x新特性之Tinting
  • checked:是设置选中状态,在setChecked() 中设置。

绘制按钮图形

@Nullable
public void setButtonDrawable(@Nullable Drawable drawable) {
    if (mButtonDrawable != drawable) {
        if (mButtonDrawable != null) {
            // 取消对View 的引用
            mButtonDrawable.setCallback(null);
            // 取消绘制对象相关联的调度,当我们重新绘制一个Drawable,可以调用此方法
            unscheduleDrawable(mButtonDrawable);
        }

        mButtonDrawable = drawable;

        if (drawable != null) {
            drawable.setCallback(this);
            drawable.setLayoutDirection(getLayoutDirection());
            if (drawable.isStateful()) {
                drawable.setState(getDrawableState());
            }
            drawable.setVisible(getVisibility() == VISIBLE, false);
            setMinHeight(drawable.getIntrinsicHeight());
            applyButtonTint();
        }
    }
}

 

这个方法,就是用于绘制按钮图形,首先会判断我们设置的Drawable 和初始会的mButtonDrawable 是否相等,如果不等,将会对初始化的mButtonDrawable 对象,取消对View 的引用(setCallback(null)),并且取消mButtonDrawable 相关联调度(unscheduleDrawable()),然后将我们新设置的Drawable 赋值给mButtonDrawable 对象,然后再设置引用,设置布局的方向,然后判断如果状态改变时,重新设置状态。最后调用applyButtonTint()。

着色

private void applyButtonTint() {
    if (mButtonDrawable != null && (mHasButtonTint || mHasButtonTintMode)) {
        mButtonDrawable = mButtonDrawable.mutate();

        if (mHasButtonTint) {
            mButtonDrawable.setTintList(mButtonTintList);
        }

        if (mHasButtonTintMode) {
            mButtonDrawable.setTintMode(mButtonTintMode);
        }

        // The drawable (or one of its children) may not have been
        // stateful before applying the tint, so let's try again.
        if (mButtonDrawable.isStateful()) {
            mButtonDrawable.setState(getDrawableState());
        }
    }
}

 

这个方法主要就是设置mButtonDrawable 的Tint(着色)和TintMode(着色模式),之前在构造方法,setButtonDrawable() 方法中都会调用此方法,因为这两个属性都是基于mButtonDrawable 来设置的,而这个两个属性是根据两个Boolean 属性mHasButtonTint 和mHasButtonTintMode 来识别的,然后为true,就表示设置。而他们两个属性也有setButtonTintList 和setButtonTintMode() 方法来设置两个属性,将两个boolean 属性设置为true,并且调用applyButtonTint() 来设置的。

绘制

protected void onDraw(Canvas canvas) {
    final Drawable buttonDrawable = mButtonDrawable;
    if (buttonDrawable != null) {
        final int verticalGravity = getGravity() & Gravity.VERTICAL_GRAVITY_MASK;
        final int drawableHeight = buttonDrawable.getIntrinsicHeight();
        final int drawableWidth = buttonDrawable.getIntrinsicWidth();

        final int top;
        switch (verticalGravity) {
            case Gravity.BOTTOM:
                top = getHeight() - drawableHeight;
                break;
            case Gravity.CENTER_VERTICAL:
                top = (getHeight() - drawableHeight) / 2;
                break;
            default:
                top = 0;
        }
        final int bottom = top + drawableHeight;
        final int left = isLayoutRtl() ? getWidth() - drawableWidth : 0;
        final int right = isLayoutRtl() ? getWidth() : drawableWidth;

        buttonDrawable.setBounds(left, top, right, bottom);

        final Drawable background = getBackground();
        if (background != null) {
            background.setHotspotBounds(left, top, right, bottom);
        }
    }

    super.onDraw(canvas);

    if (buttonDrawable != null) {
        final int scrollX = mScrollX;
        final int scrollY = mScrollY;
        if (scrollX == 0 && scrollY == 0) {
            buttonDrawable.draw(canvas);
        } else {
            canvas.translate(scrollX, scrollY);
            buttonDrawable.draw(canvas);
            canvas.translate(-scrollX, -scrollY);
        }
    }
}

 

这些属性都初始化好了,那就可以来绘制了,我们都知道自定义重写onDraw() 方法来绘制视图,CompoundButton 也重写了此方法,将我们设置了各种属性的mButtonDrawable 复制给局部变量buttonDrawable,然后根据对其方式(Gravity) 属性来来具体绘制buttonDrawable。然后调用父类的onDraw(),最后在根据时候是滑动通过Canvas 来绘制,如果水平和垂直滑动为0,则直接绘制即可,如果不为零则需要调用translate 对canvas 的重新绘制。

设置选中(checked)状态

public void setChecked(boolean checked) {
    if (mChecked != checked) {
        mChecked = checked;
        refreshDrawableState();
        notifyViewAccessibilityStateChangedIfNeeded(
                AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);

        // 避免多次调用setChecked() 来多次调用回调监听
        if (mBroadcasting) {
            return;
        }

        mBroadcasting = true;
        if (mOnCheckedChangeListener != null) {
            mOnCheckedChangeListener.onCheckedChanged(this, mChecked);
        }
        if (mOnCheckedChangeWidgetListener != null) {
            mOnCheckedChangeWidgetListener.onCheckedChanged(this, mChecked);
        }

        mBroadcasting = false;            
    }
}


// 设置监状态听
public void setOnCheckedChangeListener(OnCheckedChangeListener listener) {
    mOnCheckedChangeListener = listener;
}

void setOnCheckedChangeWidgetListener(OnCheckedChangeListener listener) {
    mOnCheckedChangeWidgetListener = listener;
}

 

设置状态,就是传入一个Boolean 值来设置状态,如果和初始化的mChecked 相反,才会调用,然后调用refreshDrawableState() 来刷新绘制的状态,然后下面就是设置状态改变的监听,通过mBroadcasting 属性来避免多次设置回调,每次调用mBroadcasting,如果为true,则返回。发现有两个监听,我们仔细看下面setXxxListener() 的方法,而下面那个setOnCheckedChangeWidgetListener() 方法不是Public,所以说不是对外开放的,我们是不能调用的,文档中说明是仅供内部使用。因此我们想要监听选中状态,可以使用setOnCheckedChangeListener()。重写onCheckedChanged(CompoundButton buttonView, boolean isChecked) 即可。对了,除了setChecked() 可以设置状态,toggle() 每次也会setChecked() 方法,每次都会讲状态设置为相反的。还有isChecked() 方法,判断当前的选中状态。

状态保存

static class SavedState extends BaseSavedState {
    boolean checked;
    SavedState(Parcelable superState) {
        super(superState);
    }

    private SavedState(Parcel in) {
        super(in);
        checked = (Boolean)in.readValue(null);
    }

    @Override
    public void writeToParcel(Parcel out, int flags) {
        super.writeToParcel(out, flags);
        out.writeValue(checked);
    }

    public static final Parcelable.Creator<SavedState> CREATOR
            = new Parcelable.Creator<SavedState>() {
        public SavedState createFromParcel(Parcel in) {
            return new SavedState(in);
        }

        public SavedState[] newArray(int size) {
            return new SavedState[size];
        }
    };
}

@Override
public Parcelable onSaveInstanceState() {
    Parcelable superState = super.onSaveInstanceState();
    SavedState ss = new SavedState(superState);
    ss.checked = isChecked();
    return ss;
}

@Override
public void onRestoreInstanceState(Parcelable state) {
    SavedState ss = (SavedState) state;

    super.onRestoreInstanceState(ss.getSuperState());
    setChecked(ss.checked);
    requestLayout();
}

 

保存状态是自定义一个SavedState,继承自BaseSavedState,然后Parcelable 将Boobean 类型checked 属性序列化,判断是否选中,在onSaveInstanceState() 中,保存,然后在onRestoreInstanceState() 获取序列化的属性,重新调用setChecked() 设置属性。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/133886.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)


相关推荐

  • 可变参数列表

    可变参数列表

    2021年10月31日
  • WinHTTP AutoProxy 函数

    WinHTTP AutoProxy 函数WinHTTPAutoProxy函数WinHTTPimplementstheWPADprotocolusingtheWinHttpGetProxyForUrlfunctionalongwithtwosupportingutilityfunctions,WinHttpDetectAutoProxyConfigUrlandWinHttpGet

  • 漫谈C语言及如何学习C语言

    漫谈C语言及如何学习C语言云风最近写了一篇博客《C语言的前世今生》。作为长期使用C语言开发网络游戏服务器的程序员,云风是有理由写这样一篇文字,不过还是感觉谈的不够深入,C语言在业界使用的现状没有怎么描写,有些意犹未尽。在这里想

  • Redis过期键的删除策略[通俗易懂]

    文章目录立即删除惰性删除定时删除Redis使用的策略Redis中有个设置时间过期的功能,即对存储在redis数据库中的值可以设置一个过期时间。作为一个缓存数据库,这是非常实用的。如我们一般项目中的token或者一些登录信息,尤其是短信验证码都是有时间限制的,按照传统的数据库处理方式,一般都是自己判断过期,这样无疑会严重影响项目性能。我们setkey的时候,都可以给一个expir…

  • 机器学习——下采样(under-sampling)「建议收藏」

    下采样(under-sampling)什么是下采样?当原始数据的分类极不均衡时,如下图我们要想用这样的数据去建模显然是存在问题的。尤其是在我们更关心少数类的问题的时候数据分类不均衡会更加的突出,例如,信用卡诈骗、病例分析等。在这样的数据分布的情况下,运用机器学习算法的预测模型可能会无法做出准确的预测,最后的模型显然是趋向于预测多数集的,少数集可能会被当做噪点或被忽视,相比多数集,少数集被…

  • 协议和协定有什么区别_协议和合同是一回事吗

    协议和协定有什么区别_协议和合同是一回事吗1、https协议需要到CA(CertificateAuthority,证书颁发机构)申请证书,一般免费证书较少,因而需要一定费用。(原来网易官网是http,而网易邮箱是https。)2、http是超文本传输协议,信息是明文传输,https则是具有安全性的ssl加密传输协议。3、http和https使用的是完全不同的连接方式,用的端口也不一样,前者是80,后者是443。4、http的连接很简单,是无状态的。Https协议是由SSL+Http协议构建的可进行加密传输、身份认证的网络协议,比http.

    2022年10月11日

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号