今天整理了一下項目中經常使用的組合控件,對屬性進行了提取,擴展,儘量使這個組合控件更靈活、通用。
但過程當中,遇到一個問題:設置字體大小比預計的要大很多。
我先列出來自定義屬性基本的使用步驟(很熟悉這個過程的朋友可以忽略下面這幾個步驟,直接看問題分析部分)
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()三者的區別,這裏,我推薦大家自己去看看源碼,源碼比較簡單,比較容易分析出來究竟是怎麼個區別,而不是直接從網上搜出來三者區別的結論來。
結語:
本文提到的問題,並不是什麼大問題,貼出來,其實就是想跟大家分享一下解決一些基本問題的思路:遇到問題之後,儘量的深入源碼去分析問題、提煉出來解決方法。在時間允許的情況下,養成閱讀源碼的好習慣,並要有足夠的耐心,這會對我們提高編碼能力大有益處的。