Android TextView disable颜色坑

Posted freeCodeSunny

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android TextView disable颜色坑相关的知识,希望对你有一定的参考价值。

       今天在项目中发现我们有一个小细节以前运行好好的视觉效果现在不对了,应该说有一段时间不对了,只是没有关注这一个小细节,刚好今天改代码碰到他,就探究了一下,顺便把解决方案给记录一下。

还原整个过程

       这里我们来还原一下整个过程,我们就写一个类似的效果来复现一下整个过程,看看到底是什么原因。

布局

        我有如下一个TextView的布局:

 <TextView
        android:id="@+id/enable_text"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_margin="30dp"
        android:text="坑爹啊!"
        android:textColor="@color/white"/>

       这里就一个TextView, 内容就直接写在这里了,设置的textColor为white,这里white的定义如下: #ffffff。

运行效果

       之后我们在项目中开始运行,TextView有一个点击事件,点击后响应一些逻辑,但是当处于某一个时机的时候TextView的状态是disable的,不能被点击的,这时TextView操作如下:

textView.setEnabled(false);
textView.setText("不能操作");

       这里设置了TextView为disable,并且设置了一个文案,这里就硬编码了,主要是为了看效果!

       坑爹的事情就此发生,运行后文案从白色变成了黑色。这就奇怪了,我并没有设置这个disable的颜色,那这个黑色是从哪儿来的?

       当即我又重新创建了一demo,完全的复现了上面的整个逻辑,但是这次效果是正确的?wtf???

       当时就懵逼了,看了看源代码,但是只看了后面创建的源码? 发现并没有什么问题,又在想是我两边设置的主题不一致?两边设置为一样的主题后,发现问题还是存在,这就奇怪了。两边代码基本一致,但是效果截然不同。

       后来我就想看看不同的主题是否会影响文字颜色,之后我又重新改变了第一工程的主题,发现disable颜色改变了,从白色变成了灰色,这里可以得出一个结论,主题会影响TextView的disable状态的颜色,但是具体是怎么影响的呐?我查看了整个主题关于TextView的属性,并没有设置disable状态的颜色。我就改变了第二个主题的颜色,但是第二个工程改变了主题颜色还是不会变。你他妈在逗我???

       看来此路不通,只好又回到第一步,来比较两个工程,除了代码不一致,其他还有什么地方不一样?后来还真发现有不一致的地方,发现两边的所使用的兼容包版本不一致,出问题的使用的 compile ‘com.android.support:appcompat-v7:23.0.0’, 而正确的使用的是 compile ‘com.android.support:appcompat-v7:23.1.1’,那这里就可以明确的认为兼容包里做了不同的操作:

代码探究

       那这里我就来分别看看不同版本里面的代码到底做了啥处理。

23.0.0

       兼容包里的代码,TextView最终都会走到AppCompatTextView,那我们来看看代码:

public class AppCompatTextView extends TextView 

    private AppCompatTextHelper mTextHelper;

    public AppCompatTextView(Context context) 
        this(context, null);
    

    public AppCompatTextView(Context context, AttributeSet attrs) 
        this(context, attrs, android.R.attr.textViewStyle);
    

    public AppCompatTextView(Context context, AttributeSet attrs, int defStyleAttr) 
        super(context, attrs, defStyleAttr);

        mTextHelper = new AppCompatTextHelper(this);
        mTextHelper.loadFromAttributes(attrs, defStyleAttr);
    

    @Override
    public void setTextAppearance(Context context, int resId) 
        super.setTextAppearance(context, resId);
        if (mTextHelper != null) 
            mTextHelper.onSetTextAppearance(context, resId);
        
    

       这里看到AppCompatTextView里面创建了一个AppCompatTextHelper,在mTextHelper 里面对TextView进行了处理, mTextHelper.loadFromAttributes(attrs, defStyleAttr)的代码如下:

void loadFromAttributes(AttributeSet attrs, int defStyleAttr) 
    Context context = mView.getContext();

    // First read the TextAppearance style id
    TypedArray a = context.obtainStyledAttributes(attrs, VIEW_ATTRS, defStyleAttr, 0);
    final int ap = a.getResourceId(0, -1);
    a.recycle();

    // Now check TextAppearance's textAllCaps value
    if (ap != -1) 
        TypedArray appearance = context.obtainStyledAttributes(ap, R.styleable.TextAppearance);
        if (appearance.hasValue(R.styleable.TextAppearance_textAllCaps)) 
            setAllCaps(appearance.getBoolean(R.styleable.TextAppearance_textAllCaps, false));
        
        appearance.recycle();
    

    // Now read the style's value
    a = context.obtainStyledAttributes(attrs, TEXT_APPEARANCE_ATTRS, defStyleAttr, 0);
    if (a.hasValue(0)) 
        setAllCaps(a.getBoolean(0, false));
    
    a.recycle();

    final ColorStateList textColors = mView.getTextColors();
    if (textColors != null && !textColors.isStateful()) 
        // If we have a ColorStateList which isn't stateful, create one which includes
        // a disabled state

        final int disabledTextColor;
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) 
            // Pre-Lollipop, we will use textColorSecondary with android:disabledAlpha
            // applied
            disabledTextColor = ThemeUtils.getDisabledThemeAttrColor(context,
                    android.R.attr.textColorSecondary);
         else 
            // With certain styles on Lollipop, there is a StateListAnimator which sets
            // an alpha on the whole view, so we don't need to apply disabledAlpha to
            // textColorSecondary
            disabledTextColor = ThemeUtils.getThemeAttrColor(context,
                    android.R.attr.textColorSecondary);
        

        mView.setTextColor(ThemeUtils.createDisabledStateList(
                textColors.getDefaultColor(), disabledTextColor));
    

       这里可以看到首先获取了TextView的ColorStateList值,final ColorStateList textColors = mView.getTextColors();之后当textColors不为空且isStatefull为false,isStatefull又是这么处理的?

 public boolean isStateful() 
    return mStateSpecs.length > 1;

       发现当mStateSpecs.length大于1就是true,但是这里我们只设置了一个颜色值,因此length为1,所有为false,会进入判断条件,可以发现系统先获取了一个disable的TextColor,默认读取的值为textColorSecondary属性,因此只要你Theme里面设置了该只,系统最后显示就是你设置的颜色,之后将默认颜色与disable颜色新创建了一个ColorStateList在设置回TextView, 这也就是为什么当他disable时会变黑,就算你没有设置该值,也会变黑。这不是坑爹嘛!!!这也就说明了为什么项目中会变黑。

23.1.1

       我们再来看看23.1.1的代码:

public AppCompatTextView(Context context, AttributeSet attrs, int defStyleAttr) 
    super(context, attrs, defStyleAttr);

    mTintManager = TintManager.get(getContext());
    mBackgroundTintHelper = new AppCompatBackgroundHelper(this, mTintManager);
    mBackgroundTintHelper.loadFromAttributes(attrs, defStyleAttr);

    mTextHelper = AppCompatTextHelper.create(this);
    mTextHelper.loadFromAttributes(attrs, defStyleAttr);
    mTextHelper.applyCompoundDrawablesTints();

       这里我们看到AppCompatTextHelper对TextView的某一些属性进行了处理,处理的代码在loadFromAttributes(attrs, defStyleAttr):

void loadFromAttributes(AttributeSet attrs, int defStyleAttr) 
        final Context context = mView.getContext();
        final TintManager tintManager = TintManager.get(context);

        // First read the TextAppearance style id
        TypedArray a = context.obtainStyledAttributes(attrs, VIEW_ATTRS, defStyleAttr, 0);
        final int ap = a.getResourceId(0, -1);

        // Now read the compound drawable and grab any tints
        if (a.hasValue(1)) 
            mDrawableLeftTint = createTintInfo(context, tintManager, a.getResourceId(1, 0));
        
        if (a.hasValue(2)) 
            mDrawableTopTint = createTintInfo(context, tintManager, a.getResourceId(2, 0));
        
        if (a.hasValue(3)) 
            mDrawableRightTint = createTintInfo(context, tintManager, a.getResourceId(3, 0));
        
        if (a.hasValue(4)) 
            mDrawableBottomTint = createTintInfo(context, tintManager, a.getResourceId(4, 0));
        
        a.recycle();

        // Now check TextAppearance's textAllCaps value
        if (ap != -1) 
            TypedArray appearance = context.obtainStyledAttributes(ap, R.styleable.TextAppearance);
            if (appearance.hasValue(R.styleable.TextAppearance_textAllCaps)) 
                setAllCaps(appearance.getBoolean(R.styleable.TextAppearance_textAllCaps, false));
            
            appearance.recycle();
        

        // Now read the style's value
        a = context.obtainStyledAttributes(attrs, TEXT_APPEARANCE_ATTRS, defStyleAttr, 0);
        if (a.getBoolean(0, false)) 
            setAllCaps(true);
        
        a.recycle();
    

       从代码我们可以知道,这里并没有对TextView添加disaleTextColor,估计是google官方也发现这种默认添加颜色的设置不太合理,因此后续版本给改掉了。只是处理了文本是否变成大写,和为了实现MD对四周的图片进行着色。这也是为什么第二个demo为什么是正确的了。

24.0.0

       这里我们再来看看24.0.0的代码:

public AppCompatTextView(Context context, AttributeSet attrs, int defStyleAttr) 
    super(TintContextWrapper.wrap(context), attrs, defStyleAttr);
    this.mDrawableManager = AppCompatDrawableManager.get();
    this.mBackgroundTintHelper = new AppCompatBackgroundHelper(this, this.mDrawableManager);
    this.mBackgroundTintHelper.loadFromAttributes(attrs, defStyleAttr);
    this.mTextHelper = AppCompatTextHelper.create(this);
    this.mTextHelper.loadFromAttributes(attrs, defStyleAttr);
    this.mTextHelper.applyCompoundDrawablesTints();

       套路都是一样的,这里调用了this.mTextHelper.loadFromAttributes(attrs, defStyleAttr); 代码如下:

void loadFromAttributes(AttributeSet attrs, int defStyleAttr) 
    final Context context = mView.getContext();
    final AppCompatDrawableManager drawableManager = AppCompatDrawableManager.get();

    // First read the TextAppearance style id
    TintTypedArray a = TintTypedArray.obtainStyledAttributes(context, attrs,
            VIEW_ATTRS, defStyleAttr, 0);
    final int ap = a.getResourceId(0, -1);
    // Now read the compound drawable and grab any tints
    if (a.hasValue(1)) 
        mDrawableLeftTint = createTintInfo(context, drawableManager, a.getResourceId(1, 0));
    
    if (a.hasValue(2)) 
        mDrawableTopTint = createTintInfo(context, drawableManager, a.getResourceId(2, 0));
    
    if (a.hasValue(3)) 
        mDrawableRightTint = createTintInfo(context, drawableManager, a.getResourceId(3, 0));
    
    if (a.hasValue(4)) 
        mDrawableBottomTint = createTintInfo(context, drawableManager, a.getResourceId(4, 0));
    
    a.recycle();

    // PasswordTransformationMethod wipes out all other TransformationMethod instances
    // in TextView's constructor, so we should only set a new transformation method
    // if we don't have a PasswordTransformationMethod currently...
    final boolean hasPwdTm =
            mView.getTransformationMethod() instanceof PasswordTransformationMethod;
    boolean allCaps = false;
    boolean allCapsSet = false;
    ColorStateList textColor = null;

    // First check TextAppearance's textAllCaps value
    if (ap != -1) 
        a = TintTypedArray.obtainStyledAttributes(context, ap, R.styleable.TextAppearance);
        if (!hasPwdTm && a.hasValue(R.styleable.TextAppearance_textAllCaps)) 
            allCapsSet = true;
            allCaps = a.getBoolean(R.styleable.TextAppearance_textAllCaps, false);
        
        if (Build.VERSION.SDK_INT < 23
                && a.hasValue(R.styleable.TextAppearance_android_textColor)) 
            // If we're running on < API 23, the text color may contain theme references
            // so let's re-set using our own inflater
            textColor = a.getColorStateList(R.styleable.TextAppearance_android_textColor);
        
        a.recycle();
    

    // Now read the style's values
    a = TintTypedArray.obtainStyledAttributes(context, attrs, R.styleable.TextAppearance,
            defStyleAttr, 0);
    if (!hasPwdTm && a.hasValue(R.styleable.TextAppearance_textAllCaps)) 
        allCapsSet = true;
        allCaps = a.getBoolean(R.styleable.TextAppearance_textAllCaps, false);
    
    if (Build.VERSION.SDK_INT < 23
            && a.hasValue(R.styleable.TextAppearance_android_textColor)) 
        // If we're running on < API 23, the text color may contain theme references
        // so let's re-set using our own inflater
        textColor = a.getColorStateList(R.styleable.TextAppearance_android_textColor);
    
    a.recycle();

    if (textColor != null) 
        mView.setTextColor(textColor);
    

    if (!hasPwdTm && allCapsSet) 
        setAllCaps(allCaps);
    

       这里也没有处理默认设置disable颜色,设置了大写等情况。所以这个版本也不会出问题。

总结

       问题是解决了,可以调整对应支持包的版本,或者对TextView设置一个selector的颜色值,这样就解决了问题,虽然正常这种文本如果需要disable状态,应该就设置好disable颜色,但是有的时候,视觉可能效果是没有变化的,只是不能点,所以也就没有设置,所以一个良好的编程习惯还是最重要的。

以上是关于Android TextView disable颜色坑的主要内容,如果未能解决你的问题,请参考以下文章

[修正] Firemonkey Android Edit 可输入 Emoji (颜文字)

Android中用TextView显示大量文字的方法

Android常见控件— — —TextView

android textview啥意思

Android从零单排系列六《Android视图控件——TextView》

Android从零单排系列六《Android视图控件——TextView》