Android对View进行全局拦截处理

前言

当我们继承AppCompatActivity时,会发现一些系统控件会被替换成v4包扩展过后的View,它是如何做到全局拦截替换的呢,有时候我们也有一些需求,需要对某一类型的View进行统一操作。

LayoutInflater 源码分析

先来看看inflate函数:

//Layoutinflater.java

  public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
        final Resources res = getContext().getResources();
        final XmlResourceParser parser = res.getLayout(resource);
        try {
            return inflate(parser, root, attachToRoot);
        } finally {
            parser.close();
        }
    }
    
  public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
            ...
            // Temp is the root view that was found in the xml
            final View temp = createViewFromTag(root, name, inflaterContext, attrs);
            ViewGroup.LayoutParams params = null;
            if (root != null) {
                // Create layout params that match root, if supplied
                params = root.generateLayoutParams(attrs);
                if (!attachToRoot) {
                    // Set the layout params for temp if we are not
                    // attaching. (If we are, we use addView, below)
                    temp.setLayoutParams(params);
                }
            }
            ...
        }
    }
    
    View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,boolean ignoreThemeAttr) {
            ...
            View view;
            if (mFactory2 != null) {
                view = mFactory2.onCreateView(parent, name, context, attrs);
            } else if (mFactory != null) {
                view = mFactory.onCreateView(name, context, attrs);
            } else {
                view = null;
            }

            if (view == null && mPrivateFactory != null) {
                view = mPrivateFactory.onCreateView(parent, name, context, attrs);
            }

            if (view == null) {
                final Object lastContext = mConstructorArgs[0];
                mConstructorArgs[0] = context;
                try {
                    if (-1 == name.indexOf('.')) {
                        view = onCreateView(parent, name, attrs);
                    } else {
                        view = createView(name, null, attrs);
                    }
                } finally {
                    mConstructorArgs[0] = lastContext;
                }
            }
            return view;
    }

可以看到最终创建View调用的是createViewFromTag函数,这个函数会依次调用FactoryFactory2进行创建View,因此我们就找到了介入View加载的点,也就是换掉factory成我们自己的,在LayoutInflater中找到了两个Factory,源码如下。

//Layoutinflater.java

    public interface Factory {
        /**
         * 你可以在LayoutInflater调用inflating之前处理它。
         * 并且可以在Layout文件中使用自定义Tag并且在这里处理它。
         * 
         * <p>
         * 提示:在这些自定义标签前面加包名的前缀是一个不错的选择(例如: com.coolcompany.apps) 来避免与系统的标签产生冲突。
         * 
         * @param 要解析的标签名
         * @param 创建View的Context
         * @param XML解析出来的属性
         * 
         * @return 返回创建的View,如果返回空则使用默认创建行为
         */
        public View onCreateView(String name, Context context, AttributeSet attrs);
    }

    public interface Factory2 extends Factory {
        /**
         * 跟Factory功能差不多,这个只是多了一个parent属性。
         */
        public View onCreateView(View parent, String name, Context context, AttributeSet attrs);
    }

从源码中可以看到Factory的作用是在系统解析View之前调用者可以先接管解析逻辑,如果不解析才使用默认的行为去解析,也就是我们只需要给LayoutInflater设置自己的处理逻辑即可。接下来我们看看V4包是怎么处理的。

//AppCompatActiviyt.java
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        final AppCompatDelegate delegate = getDelegate();
        delegate.installViewFactory();
        ...
    }

接下来我们又看看installViewFactory函数干了什么?

//AppCompatDelegateImplV9.java

    @Override
    public void installViewFactory() {
        LayoutInflater layoutInflater = LayoutInflater.from(mContext);
        if (layoutInflater.getFactory() == null) {
            LayoutInflaterCompat.setFactory(layoutInflater, this);
        } else {
            if (!(LayoutInflaterCompat.getFactory(layoutInflater)
                    instanceof AppCompatDelegateImplV9)) {
                Log.i(TAG, "The Activity's LayoutInflater already has a Factory installed"
                        + " so we can not install AppCompat's");
            }
        }
    }

显然就是给LayoutInflater设置Factory的,然后再创建View的时候把一些控件替换成了AppCompat打头的控件。有了上面的思路我们就可以依葫芦画瓢来拦截处理。

拦截处理View

我这里的需求是对所有输入框进行特殊字符处理,比如英文分号,SQL关键字等。


import android.content.Context;
import android.support.v7.app.AppCompatDelegate;
import android.util.AttributeSet;
import android.view.InflateException;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.EditText;

/**
 * @version 1.0
 * @auth venusic
 * @date 2018/2/5 14:44
 * @copyRight 四川金信石信息技术有限公司
 * @since 1.0
 */

public class XSSLayoutFactory implements LayoutInflater.Factory2 {

    private static final String TAG = "LayoutFactory";
    private AppCompatDelegate appCompatDelegate;
    private LayoutInflater inflater;

    XSSLayoutFactory(AppCompatDelegate appCompatDelegate, LayoutInflater inflater) {
        this.appCompatDelegate = appCompatDelegate;
        this.inflater = inflater;
        if (inflater.getFactory2() == null) {
            inflater.setFactory2(this);
        } else {
            throw new InflateException("inflater has a LayoutFactory!!!");
        }
    }

    public static XSSLayoutFactory installViewFactory(AppCompatDelegate appCompatDelegate, LayoutInflater inflater) {
        return new XSSLayoutFactory(appCompatDelegate, inflater);
    }

    @Override
    public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
        //先调用AppCompat的处理逻辑
        View result = appCompatDelegate.createView(parent, name, context, attrs);
        //如果是系统控件则直接New一个出来
        if (result == null) {
            if ("EditText".equals(name)) {
                result = new EditText(context, attrs);
            }
        }
        //如果是自定义控件 则先检查是否是我们要处理的子类,如果是子类则调用inflate加载出来。
        if (result == null && name.indexOf(".") != -1) {
            try {
                Class clz = Class.forName(name);
                if (EditText.class.isAssignableFrom(clz)) {
                    result = inflater.createView(name, null, attrs);
                }
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
        }
        //判断属于我们要处理的类型 然后处理
        if (EditText.class.isInstance(result)) {
            //doSomeThing 
        }
        //如果上面都没有处理到 则返回null 这样就表明是交给系统加载。
        return result;
    }

    @Override
    public View onCreateView(String name, Context context, AttributeSet attrs) {
        return onCreateView(null, name, context, attrs);
    }
}

然后在BaseActivity的onCreate里调用一下就可以看到效果了。

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        //必须要在super.onCreate()之前调用,因为LayoutInflater.setFactory()只能调用一次。
        //因此要赶在Appcompat调用之前设置。我们的Factory代理了AppCompatDelegate,因此不会影响到兼容性问题。
        XSSLayoutFactory.installViewFactory(getDelegate(), getLayoutInflater());
        super.onCreate(savedInstanceState);
    }

结语

Java是一门非常强大的语言,尤其是其的多态性,可以让你在处理问题的时候轻松的覆盖原有逻辑。对于很多时候我们去拦截或处理一些操作的时候,都是寻找一个可以介入操作的点,然后利用偷天换日的手段来换成自己的逻辑。而寻找点的过程就是分析源码的过程。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值