android多主題之坑

聲明:本文已授權微信公眾號Android程序員 (Android Trending) 在微信公眾號平臺原創(chuàng)首發(fā)。

年后重構了一版多主題框架,在重構過程中遇到了不少的坑,特此記錄下與君共勉。(Tips: 多主題框架也將于6月初開源啦.)

  • 多彩主題和夜間主題
    在寫多主題框架時,首先一個概念要分清就是多彩主題和夜間模式。

    • 多彩主題其實是白天模式的衍生,與夜間模式是對立的。
    • 雖然夜間和多彩是對立,但還是建議多彩主題應該與夜間模式解偶,因為有時夜間模式的顏色變化并不是簡單的顏色取反,受產(chǎn)品設計的影響較大,有時甚至一個tag在夜間和多彩中的取色完全不一樣的,這時如果還在強求通過一次編碼“通吃“多彩和夜間,這樣的做法完全是不明智的,同時也會導致框架易用性變差。
      當然如果某些控件在夜間模式下的需求只是簡單的顏色取反,對于這種情況,框架是應當給予適配支持的(不能一棒子打死嘛),因為這種特性支持很簡單,所以可以在基本不增加框架學習使用成本的前提下,大大減少程序員的重復編碼,提高了開發(fā)效率。
    • 關于夜間模式的具體實現(xiàn)方式有很多,在這里推薦一篇文章 Android夜間模式最佳實踐,文中一共概述了三種實現(xiàn)方式,其中第三種通過修改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以下它并沒有實現(xiàn)onStateChange方法,而onStateChange在view中的默認實現(xiàn)是直接返回false,所以它就不會隨著狀態(tài)的變化刷新UI了。2). 在API21的GradientDrawable源碼中并沒有支持setTint,這有點奇怪,因為其他Drawable基本都支持了,有時間要仔細對比下源碼。

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

  • 關于setPressed(boolean)
    setPressed 方法不同于setSelected方法,雖然它在執(zhí)行過程中會更新Drawable的state狀態(tài),但是不會調(diào)用invalidate函數(shù)(備注:并不是針對所有Drawable,stateDrawableList會在setPressed執(zhí)行過程中調(diào)用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,然后你會發(fā)現(xiàn)background在按下時背景色并沒有tint。
    解決的方法:1). 在view的drawstateChanged()中手動調(diào)用invalidate方法。2). 在view的drawstateChanged()中apply新的drawable state。3). 等待你來補充。

  • 關于.9png
    .9png在繪制時如果.9png內(nèi)含有padding值,則5.0以下時view的padding會消失。如果想要view的padding保留,目前比較好的做法就是在set前先將view的padding值保存下來,然后等set之后再重新setPadding回去(首先要明確的一點是drawable和view的padding是有區(qū)別的)。

  • 關于StateListDrawable對child tint 無效
    這是一個5.0以下的bug,現(xiàn)在比較好的解決方案就是繼承StateListDrawable,重寫它的selectDrawable方法,每次在狀態(tài)切換獲取對應的drawable時,手動進行setColorFilter設置。附上鏈接

  • 關于setButtonDrawable方法
    setButtonDrawable方法在API21以下存在一個 非常隱蔽的bug。
    在API21以下,如果CompoundButton已設置了一個buttonDrawable(非空),然后在調(diào)用setButtonDrawable(null),你會發(fā)現(xiàn)之前設置的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數(shù)組中將android屬性放在自定義屬性之后讀取,則你會發(fā)現(xiàn)android屬性的值將無法取到,-,-是不是很奇葩。
    • 在API19上,如果將drawableLeft之類的android屬性放在一個int [] ATTRS中通過TypeArray讀取時,除了第一個android屬性能取到resourceId,之后的drawableXxx的resourceId解析的值都為0。
    • 目前的解決方案是針對每個attr都單獨obtain一次,如果有更好的解決方案,歡迎支持。

拖沓了兩個月終于踩著五月份的尾巴把文章發(fā)了,唏噓...(拖延癥害死人--|||)


歡迎查看 個人博客.

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容