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閱讀 229,117評論 6 537
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,860評論 3 423
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,128評論 0 381
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,291評論 1 315
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,025評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,421評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,477評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,642評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,177評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,970評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,157評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,717評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,410評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,821評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,053評論 1 289
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,896評論 3 395
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,157評論 2 375

推薦閱讀更多精彩內容