近期有一個比較特殊的需求,就是給所有View上添加一個特殊屬性,可以在XML中賦值(例如:tag = "https://static.byr.cn/files/imgupload/2011-07-22-00-24-26.jpg"),在View創(chuàng)建時根據(jù)屬性做一些操作。如載入URL等。
這個需求有些限制:
1.因為希望是通用的架構(gòu),各種View如ImageView、各種Layout、還有自定義View等都可以使用。
所以沒法去繼承某個單獨的View。
2.盡量不修改現(xiàn)有的代碼。對使用者透明。
問題最難的地方還是在于缺少一個合適的切入點來讀取并且應(yīng)用屬性。后來使用了一些比較tricky的方式來解決這個問題:
1.已知LayoutInflater 中有一個onCreateView的方法。這個方法是public可以被復(fù)寫,而且可以獲取到讀取xml獲取到的各種屬性值。因此,這是一個理想的切入點。
插一句話,雖然LayoutInflater 可以通過setFactory之類的方式來定制View的生產(chǎn),但是在這里并不合適,因為我們并不想去關(guān)注 View是如何構(gòu)建出來的,只是希望在View被構(gòu)建出來之后做一些額外工作。
因此希望可以替換掉系統(tǒng)的LayoutInfalter。
2.LayoutInflater的日常使用方式多為:
LayoutInfater.from(context)或者Activit#getLayoutInflater等,到最后的調(diào)用都會走到context.getSystemService(LAYOUT_INFLATER)上來。
// LayoutInflater.java
public static LayoutInflater from(Context context) {
LayoutInflater LayoutInflater =
(LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
if (LayoutInflater == null) {
throw new AssertionError("LayoutInflater not found.");
}
return LayoutInflater;
}
而系統(tǒng)中的“正牌”LayoutInflate是使用的PhoneLayoutInflater
//ContextImpl.java
@Override
public Object getSystemService(String name) {
return SystemServiceRegistry.getSystemService(this, name);
}
//SystemServiceRegistry
registerService(Context.LAYOUT_INFLATER_SERVICE, LayoutInflater.class,
new CachedServiceFetcher<LayoutInflater>() {
@Override
public LayoutInflater createService(ContextImpl ctx) {
return new PhoneLayoutInflater(ctx.getOuterContext());
}});
我們在Activity中就可以通過重寫getSystemService就可以替換成我們的LayoutInflater。
public class MainActivity extends Activity {
LayoutInflater layoutInflater;
@Override
public Object getSystemService(String name) {
if (name.equals(LAYOUT_INFLATER_SERVICE)) {
if (layoutInflater == null) {
LayoutInflater origin = (LayoutInflater) super.getSystemService(name);
//使用這種 ni
layoutInflater = new PPLayoutInflater(origin, this);
}
return layoutInflater;
}
return super.getSystemService(name);
}
@Override
public LayoutInflater getLayoutInflater() {
return layoutInflater;
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
}
- 自定義InflaterLayout的實現(xiàn)
這個實現(xiàn)參考了PhoneLayoutInflater
核心代碼:
@Override protected View onCreateView(String name, AttributeSet attrs) throws ClassNotFoundException {
Log.e("shitshit", "【" + this.getClass().getSimpleName() + "】 onCreateView() called with: " + "name = [" + name + "], attrs = [" + attrs + "]");
for (String prefix : sClassPrefixList) {
try {
// 使用默認(rèn)構(gòu)造方法 這一步不是我們所關(guān)心的
View view = createView(name, prefix, attrs);
if (view != null) {
//do custom here...先用ImageView來做個實驗
if(view instanceof ImageView) {
if (attrs != null) {
for (int i = 0; i < attrs.getAttributeCount(); i++) {
String attributeName = attrs.getAttributeName(i);
String attributeValue = attrs.getAttributeValue(i);
//擁有了View和其各種從xml中解析出來的屬性
processAttrs(attributeName, attributeValue, view);
}
}
}
return view;
}
} catch (ClassNotFoundException e) {
// In this case we want to let the base class take a crack
// at it.
}
}
return super.onCreateView(name, attrs);
}
一個簡單的processAttrs的栗子:
public void processAttrs(String attrName, String attrValue, View view){
if(attrName.equals("tag")){
view.setBackgroundColor(Color.parseColor(attrValue));
}
}
本文完整代碼已經(jīng)上傳至git
https://github.com/BFridge/CustomLayoutInflater
其實關(guān)于替換SystemService的類似的小tricky也有高人專門研究過:
https://github.com/square/mortar