Android对View进行全局拦截处理
Posted Wastrel_xyz
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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
函数,这个函数会依次调用Factory
、Factory2
进行创建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是一门非常强大的语言,尤其是其的多态性,可以让你在处理问题的时候轻松的覆盖原有逻辑。对于很多时候我们去拦截或处理一些操作的时候,都是寻找一个可以介入操作的点,然后利用偷天换日的手段来换成自己的逻辑。而寻找点的过程就是分析源码的过程。
以上是关于Android对View进行全局拦截处理的主要内容,如果未能解决你的问题,请参考以下文章
Android-View的事件分发及拦截-父控件和子控件都处理触摸事件的方式
Android 逆向函数拦截原理 ( 通过修改 GOT 全局偏移表拦截函数 | 通过在实际被调用的函数中添加跳转代码实现函数拦截 )
Android的事件分发(dispatchTouchEvent),拦截(onInterceptTouchEvent)与处理(onTouchEvent)