Android 關於組合控件(自定義控件等)中自定義屬性,通過getDimension後 設置字體大小比實際要大很多的問題分析

今天整理了一下項目中經常使用的組合控件,對屬性進行了提取,擴展,儘量使這個組合控件更靈活、通用。

但過程當中,遇到一個問題:設置字體大小比預計的要大很多。

我先列出來自定義屬性基本的使用步驟(很熟悉這個過程的朋友可以忽略下面這幾個步驟,直接看問題分析部分)

1、抽取自定義屬性tsc_TextSize到attr.xml中

<declare-styleable name="MTopSelectorControl">
   <attr name="tsc_TextSize" format="dimension" />
   //省略其它的屬性...僅以tsc_TextSize一個作爲示例
</declare-styleable>

2、在佈局文件中設置自定義屬性tsc_TextSize的值

<com.xxx.view.MTopSelectorControl
        android:id="@+id/layout_topSelector"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@color/white"      
        app:tsc_TextSize="@dimen/sp_14" />

3、在組合控件編碼過程中,取出設定的tsc_TextSize的值(app:tsc_TextSize="@dimen/sp_14"),並給具體的控件設置字體大小

private float tsc_TextSize;//文本字體大小
 /**
  * 取出定義好的屬性值
  * @param attrs
 */
private void initTypedArray(AttributeSet attrs) {
  //加載自定義的屬性 
  tsc_TextSize = a.getDimension(R.styleable.MTopSelectorControl_tsc_TextSize, 12);
  //回收資源,這一句必須調用
  a.recycle();
 }
/**
  * 初始化頂部選擇的控件
 */
 private void initTextView(String strName) {
            TextView textView = percentRelativeLayout.findViewById(R.id.tv_typename);
            textView.setText(strName);
            textView.setTextSize(tsc_TextSize);
    }

通過上面這些操作步驟,基本上就能達到自定義屬性的定義、賦值和使用了。但是這樣一來,會發現字體大小比預計的要大很多。

下面進行問題分析

1、首先看獲取自定義屬性值的代碼

tsc_TextSize = a.getDimension(R.styleable.MTopSelectorControl_tsc_TextSize, 12); 點擊去看getDimension源碼:

   public float getDimension(@StyleableRes int index, float defValue) {
        if (mRecycled) {
            throw new RuntimeException("Cannot make calls to a recycled instance!");
        }

        final int attrIndex = index;
        index *= AssetManager.STYLE_NUM_ENTRIES;

        final int[] data = mData;
        final int type = data[index+AssetManager.STYLE_TYPE];
        if (type == TypedValue.TYPE_NULL) {
            return defValue;
        } else if (type == TypedValue.TYPE_DIMENSION) {
            //重點看這裏,重點看這裏,重點看這裏
            return TypedValue.complexToDimension(
                    data[index + AssetManager.STYLE_DATA], mMetrics);
        } else if (type == TypedValue.TYPE_ATTRIBUTE) {
            final TypedValue value = mValue;
            getValueAt(index, value);
            throw new UnsupportedOperationException(
                    "Failed to resolve attribute at index " + attrIndex + ": " + value);
        }

        throw new UnsupportedOperationException("Can't convert value at index " + attrIndex
                + " to dimension: type=0x" + Integer.toHexString(type));
    }

再看 return TypedValue.complexToDimension(data[index + AssetManager.STYLE_DATA], mMetrics);

complexToDimension源碼

  public static float complexToDimension(int data, DisplayMetrics metrics)
    {
        //再接着點進去applyDimension 看源碼
        return applyDimension(
            (data>>COMPLEX_UNIT_SHIFT)&COMPLEX_UNIT_MASK,
            complexToFloat(data),
            metrics);
    }

再點進去看applyDimension 源碼:

  //這裏是applyDimension源碼
 public static float applyDimension(int unit, float value, DisplayMetrics metrics)
    {
        switch (unit) {
        case COMPLEX_UNIT_PX:
            return value;
        case COMPLEX_UNIT_DIP:
            return value * metrics.density;
        //添加一個註釋:我們使用的sp作爲的單位,應該走下面這個case
        case COMPLEX_UNIT_SP:
            return value * metrics.scaledDensity;
        case COMPLEX_UNIT_PT:
            return value * metrics.xdpi * (1.0f/72);
        case COMPLEX_UNIT_IN:
            return value * metrics.xdpi;
        case COMPLEX_UNIT_MM:
            return value * metrics.xdpi * (1.0f/25.4f);
        }
        return 0;
    }

通過上面源碼,不難發現一個關鍵現象:即將取到的自定義屬性的值,在不同類型下,通過乘以既定的係數,進行PX轉換,

那麼最終得到的值,肯定是變化了的。具體到我們的例子,設定的是14sp,那麼獲取到的實際的數值是

 return value * metrics.scaledDensity; 即,14*3(當前測試機的值)=42(px).

好,現在知道了 tsc_TextSize = a.getDimension(R.styleable.MTopSelectorControl_tsc_TextSize, 12);獲取到的實際值是42(px)

然後再看賦值過程: textView.setTextSize(tsc_TextSize); 正如本文上面提到的,看到的實際效果是文本大了很多。爲什麼呢?

那就讓我們繼續看看textView.setTextSize(tsc_TextSize);這個setTextSize方法的源碼吧

   @android.view.RemotableViewMethod
    public void setTextSize(float size) {
        //這裏添加個註釋:問題就出在這裏,下面這個方法,直接給了一個默認值TypedValue.COMPLEX_UNIT_SP
        setTextSize(TypedValue.COMPLEX_UNIT_SP, size);
    }

問題就出現在上面這個方法裏了,給了默認值TypedValue.COMPLEX_UNIT_SP,而上面我們獲取到的自定義屬性的值,已經是給轉換成px的值了,這裏再按照sp爲單位進行setTextSize ,肯定是要變大了。

再繼續看 setTextSize(TypedValue.COMPLEX_UNIT_SP, size);的源碼

    public void setTextSize(int unit, float size) {
        if (!isAutoSizeEnabled()) {
            setTextSizeInternal(unit, size, true /* shouldRequestLayout */);
        }
    }
    //這裏添加個註釋:setTextSizeInternal方法的源碼就不貼出來了,感興趣的朋友可以繼續點進去查看。

發現這個方法,其實是public的,我們也是可以調用的.那麼,結合我們的實際場景,我們應該採用TypedValue.COMPLEX_UNIT_PX,也就是要用px作爲單位來進行setTextSize ,即:textView.setTextSize(TypedValue.COMPLEX_UNIT_PX,tsc_TextSize);

改變了之後,運行會發現,是理想效果了。

另外啊,a.getDimension()\a.getDimensionPixelOffset()\a.getDimensionPixelSize()三者的區別,這裏,我推薦大家自己去看看源碼,源碼比較簡單,比較容易分析出來究竟是怎麼個區別,而不是直接從網上搜出來三者區別的結論來。

結語:

本文提到的問題,並不是什麼大問題,貼出來,其實就是想跟大家分享一下解決一些基本問題的思路:遇到問題之後,儘量的深入源碼去分析問題、提煉出來解決方法。在時間允許的情況下,養成閱讀源碼的好習慣,並要有足夠的耐心,這會對我們提高編碼能力大有益處的。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章