LayoutInflater偷天換日,自定義屬性巧解析

近期有一個比較特殊的需求,就是給所有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);
    }
}
  1. 自定義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

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

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

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