近期有一個(gè)比較特殊的需求裂问,就是給所有View上添加一個(gè)特殊屬性侧啼,可以在XML中賦值(例如:tag = "https://static.byr.cn/files/imgupload/2011-07-22-00-24-26.jpg"),在View創(chuàng)建時(shí)根據(jù)屬性做一些操作堪簿。如載入U(xiǎn)RL等痊乾。
這個(gè)需求有些限制:
1.因?yàn)橄M峭ㄓ玫募軜?gòu),各種View如ImageView椭更、各種Layout哪审、還有自定義View等都可以使用。
所以沒(méi)法去繼承某個(gè)單獨(dú)的View虑瀑。
2.盡量不修改現(xiàn)有的代碼湿滓。對(duì)使用者透明。
問(wèn)題最難的地方還是在于缺少一個(gè)合適的切入點(diǎn)來(lái)讀取并且應(yīng)用屬性舌狗。后來(lái)使用了一些比較tricky的方式來(lái)解決這個(gè)問(wèn)題:
1.已知LayoutInflater 中有一個(gè)onCreateView的方法叽奥。這個(gè)方法是public可以被復(fù)寫(xiě),而且可以獲取到讀取xml獲取到的各種屬性值把夸。因此而线,這是一個(gè)理想的切入點(diǎn)。
插一句話,雖然LayoutInflater 可以通過(guò)setFactory
之類(lèi)的方式來(lái)定制View的生產(chǎn)膀篮,但是在這里并不合適嘹狞,因?yàn)槲覀儾⒉幌肴リP(guān)注 View是如何構(gòu)建出來(lái)的,只是希望在View被構(gòu)建出來(lái)之后做一些額外工作誓竿。
因此希望可以替換掉系統(tǒng)的LayoutInfalter磅网。
2.LayoutInflater的日常使用方式多為:
LayoutInfater.from(context)
或者Activit#getLayoutInflater
等,到最后的調(diào)用都會(huì)走到context.getSystemService(LAYOUT_INFLATER)
上來(lái)。
// 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());
}});
我們?cè)贏ctivity中就可以通過(guò)重寫(xiě)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的實(shí)現(xiàn)
這個(gè)實(shí)現(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來(lái)做個(gè)實(shí)驗(yàn)
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中解析出來(lái)的屬性
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);
}
一個(gè)簡(jiǎn)單的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
其實(shí)關(guān)于替換SystemService的類(lèi)似的小tricky也有高人專門(mén)研究過(guò):
https://github.com/square/mortar