android多主题之坑

声明:本文已授权微信公众号Android程序员 (Android Trending) 在微信公众号平台原创首发。

年后重构了一版多主题框架,在重构过程中遇到了不少的坑,特此记录下与君共勉。(Tips: 多主题框架也将于6月初开源啦.)

  • 多彩主题和夜间主题
    在写多主题框架时,首先一个概念要分清就是多彩主题和夜间模式。

    • 多彩主题其实是白天模式的衍生,与夜间模式是对立的。
    • 虽然夜间和多彩是对立,但还是建议多彩主题应该与夜间模式解偶,因为有时夜间模式的颜色变化并不是简单的颜色取反,受产品设计的影响较大,有时甚至一个tag在夜间和多彩中的取色完全不一样的,这时如果还在强求通过一次编码“通吃“多彩和夜间,这样的做法完全是不明智的,同时也会导致框架易用性变差。
      当然如果某些控件在夜间模式下的需求只是简单的颜色取反,对于这种情况,框架是应当给予适配支持的(不能一棒子打死嘛),因为这种特性支持很简单,所以可以在基本不增加框架学习使用成本的前提下,大大减少程序员的重复编码,提高了开发效率。
    • 关于夜间模式的具体实现方式有很多,在这里推荐一篇文章 Android夜间模式最佳实践,文中一共概述了三种实现方式,其中第三种通过修改uiMode来切换夜间模式 其实就是Google在support库23.2.0版本(新增支持夜间模式,其实早就支持了0,0)中采用的方式,只不过在AppcompatDeleglate中进行了封装,使用起来更加简单了。
  • 关于ColorDrawable
    API21以下是不支持染色的,所以从兼容性上考虑,一般地对ColorDrawable直接new而不是染色。
    源码如下(API19):

/**
* Setting a color filter on a ColorDrawable has no effect.
*
* @param colorFilter Ignore.
*/
public void setColorFilter(ColorFilter colorFilter) {
}


- 关于GradientDrawable
 比较特殊,API22以下是不支持直接tint的,这点在support库中有很清楚的说明(DrawableCompatLollipop.java$setTintList):
 ```java
 public static void setTintList(Drawable drawable, ColorStateList tint) {
      if (drawable instanceof DrawableWrapperLollipop) {
          // GradientDrawable on Lollipop does not support tinting, so we'll use our compatible
          // functionality instead
          DrawableCompatBase.setTintList(drawable, tint);
      } else {
          // Else, we'll use the framework API
          drawable.setTintList(tint);
      }
  }

另外值得注意的是,GradientDrawable不支持tint的原因有两点,1).在API21以下它并没有实现onStateChange方法,而onStateChange在view中的默认实现是直接返回false,所以它就不会随着状态的变化刷新UI了。2). 在API21的GradientDrawable源码中并没有支持setTint,这有点奇怪,因为其他Drawable基本都支持了,有时间要仔细对比下源码。

    protected boolean onStateChange(int[] state) { 
        return false; 
    }

  • 关于setPressed(boolean)
    setPressed 方法不同于setSelected方法,虽然它在执行过程中会更新Drawable的state状态,但是不会调用invalidate函数(备注:并不是针对所有Drawable,stateDrawableList会在setPressed执行过程中调用invalidate())。
    附上API23部分源码:

    //View.java
    public void setPressed(boolean pressed) {
        ... ...
        if (needsRefresh) {
            refreshDrawableState();
        }
         ... ...
     }
    protected void drawableStateChanged() {
        ... ...
        final Drawable bg = mBackground;
        if (bg != null && bg.isStateful()) {
            bg.setState(state);
        }
        ... ...
    }
    
    //BitmapDrawable
    @Override
    protected boolean onStateChange(int[] stateSet) {
        ... ...
            mTintFilter = updateTintFilter(mTintFilter, state.mTint, state.mTintMode);
        ... ...
    }
    
    //Drawable
    /**
     * Ensures the tint filter is consistent with the current tint color and
     * mode.
     */
    PorterDuffColorFilter updateTintFilter(PorterDuffColorFilter tintFilter, ColorStateList tint,
            PorterDuff.Mode tintMode) {
            ... ...
        if (tintFilter == null) {
            return new PorterDuffColorFilter(color, tintMode);
        }
        tintFilter.setColor(color);
        tintFilter.setMode(tintMode);
        return tintFilter;
    }
    

    特别地,当一个textview设置了一张png为background并对该background设置了normaltint和pressed tint,然后你会发现background在按下时背景色并没有tint。
    解决的方法:1). 在view的drawstateChanged()中手动调用invalidate方法。2). 在view的drawstateChanged()中apply新的drawable state。3). 等待你来补充。

  • 关于.9png
    .9png在绘制时如果.9png内含有padding值,则5.0以下时view的padding会消失。如果想要view的padding保留,目前比较好的做法就是在set前先将view的padding值保存下来,然后等set之后再重新setPadding回去(首先要明确的一点是drawable和view的padding是有区别的)。

  • 关于StateListDrawable对child tint 无效
    这是一个5.0以下的bug,现在比较好的解决方案就是继承StateListDrawable,重写它的selectDrawable方法,每次在状态切换获取对应的drawable时,手动进行setColorFilter设置。附上链接

  • 关于setButtonDrawable方法
    setButtonDrawable方法在API21以下存在一个 非常隐蔽的bug。
    在API21以下,如果CompoundButton已设置了一个buttonDrawable(非空),然后在调用setButtonDrawable(null),你会发现之前设置的buttonDrawable仍然存在!根本没有被置空。
    至于原因非常简单,对比一下源码就一目了然了。下面附上API23 和API19的相关源码

    //API19
    /**
       * Set the background to a given Drawable
       *
       * @param d The Drawable to use as the background
       */
      public void setButtonDrawable(Drawable d) {
          if (d != null) {
              if (mButtonDrawable != null) {
                  mButtonDrawable.setCallback(null);
                  unscheduleDrawable(mButtonDrawable);
              }
              ... ...
      }
    
    //API21
      /**
       * Sets a drawable as the compound button image.
       *
       * @param drawable the drawable to set
       * @attr ref android.R.styleable#CompoundButton_button
       */
      @Nullable
      public void setButtonDrawable(@Nullable Drawable drawable) {
          if (mButtonDrawable != drawable) {
              if (mButtonDrawable != null) {
                  mButtonDrawable.setCallback(null);
                  unscheduleDrawable(mButtonDrawable);
              }
              
              mButtonDrawable = drawable;
              
              if (drawable != null) {
                  drawable.setCallback(this);
                  ... ...
              }
          }
      }
    
  • 关于obtain属性
    好吧,这个obtain属性非常怪,有时候会出些莫名其妙的bug。

    • 在API21以下,如果在int [] ATTRS数组中将android属性放在自定义属性之后读取,则你会发现android属性的值将无法取到,-,-是不是很奇葩。
    • 在API19上,如果将drawableLeft之类的android属性放在一个int [] ATTRS中通过TypeArray读取时,除了第一个android属性能取到resourceId,之后的drawableXxx的resourceId解析的值都为0。
    • 目前的解决方案是针对每个attr都单独obtain一次,如果有更好的解决方案,欢迎支持。

拖沓了两个月终于踩着五月份的尾巴把文章发了,唏嘘...(拖延症害死人--|||)


欢迎查看 个人博客.

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

推荐阅读更多精彩内容