1.Test
测试布局文件:
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</FrameLayout>
1.1 当MainActivity继承普通的Activity:
public class MainActivity extends Activity {
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//为当前Activity的LayoutInflater设置Factory(或Factory2)
LayoutInflater.from(this).setFactory((String name, Context context, AttributeSet attrs) -> {
Log.e("TAG", "name = " + name);
int n = attrs.getAttributeCount();
for (int i = 0; i < n; i++) {
Log.e("TAG", attrs.getAttributeName(i) + " , " + attrs.getAttributeValue(i));
}
return null;
});
//为当前Activity的LayoutInflater设置完Factory之后再去设置布局文件
setContentView(R.layout.activity_main);
//打印发现这是一个TextView类型的对象
Log.e("Tag", findViewById(R.id.text).toString());
}
}
现象:
发现name打印的是控件的全名,例如:TextView,android.support.v7.widget.RecyclerView等
attrs里面保存了这个控件在xml文件定义的所有属性,例如layout_width,background,text等
1.2 当MainActivity继承AppCompatActivity:
现在的Actvity大都被AppCompatActivity取代,如果想要在AppCompatActivity中为LayoutInflater设置Factory:
public class MainActivity extends AppCompatActivity {
protected void onCreate(Bundle savedInstanceState) {
//onCreate会为当前Activity对应的LayoutInflater设置Factory
super.onCreate(savedInstanceState);
//为当前Activity的LayoutInflater设置Factory(或Factory2)会报错,因为LayoutInflater已经设置过Factory了
LayoutInflater.from(this).setFactory((String name, Context context, AttributeSet attrs) -> {
...
return null;
});
//为Activity的LayoutInflater设置Factory的另一种方式,setFactory已废弃,以后用setFactory2好了
LayoutInflaterCompat.setFactory2(LayoutInflater.from(this), new LayoutInflater.Factory2() {
@Override
public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
return null;
}
@Override
public View onCreateView(String name, Context context, AttributeSet attrs) {
return null;
}
});
setContentView(R.layout.activity_main);
//打印发现这是一只android.support.v7.widget.AppCompatTextView类型的对象
Log.e("Tag", findViewById(R.id.text).toString());
}
}
现象:
抛出异常A factory has already been set on this LayoutInflater,说明AppCompatActivity已经设置过了Factory,无法重复设置
去掉setFactory的代码,检查TextView对象的类型,发现它的类型已经被替换为AppCompatTextView
1.3 如何为AppCompatActivity添加自定义的Factory:
如果想要在AppCompatActivity中为LayoutInflater添加自己的Factory,需要在onCreate之前添加:
public class MainActivity extends AppCompatActivity {
protected void onCreate(Bundle savedInstanceState) {
//在onCreate之前设置Factory
LayoutInflater.from(this).setFactory((String name, Context context, AttributeSet attrs) -> {
...
return null;
});
//系统设置的Factory会失效
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//打印发现这是一只TextView, 说明系统为AppCompatActivity添加的Factory被我们屏蔽掉了
Log.e("Tag", findViewById(R.id.text).toString());
}
}
1.4 如果想在AppCompatActivity自定义Factory,又想保留AppCompat控件的替换怎么办?
用AppCompatDelegate创建控件可以创建AppCompat控件,例如:
public class MainActivity extends AppCompatActivity {
protected void onCreate(Bundle savedInstanceState) {
LayoutInflaterCompat.setFactory2(LayoutInflater.from(this), new LayoutInflater.Factory2() {
@Override
public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
//用AppCompatDelegate创建View
AppCompatDelegate delegate = getDelegate();
view = delegate.createView(parent, name, context, attrs);
return view;
}
@Override
public View onCreateView(String name, Context context, AttributeSet attrs) {
return null;
}
});
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//打印发现这是一只android.support.v7.widget.AppCompatTextView
Log.e("Tag", findViewById(R.id.text).toString());
}
}
2.AppCompatActivity源码:
//className = AppCompatActivity
protected void onCreate(@Nullable Bundle savedInstanceState) {
getDelegate().installViewFactory();
}
//className = AppCompatDelegateImplV9
public void installViewFactory() {
LayoutInflater layoutInflater = LayoutInflater.from(mContext);
if (layoutInflater.getFactory() == null) {
LayoutInflaterCompat.setFactory2(layoutInflater, this);
}
}
@override //override Factory2.createView
public View createView(View parent, final String name, Context context, AttributeSet attrs) {
return mAppCompatViewInflater.createView(parent, name, context, attrs, ...)
}
//className = AppCompatViewInflater
final View createView(View parent, final String name,Context context, AttributeSet attrs,....) {
switch (name) {
case "TextView":
view = new AppCompatTextView(context, attrs);
verifyNotNull(view, name);
break;
case "ImageView":
view = new AppCompatImageView(context, attrs);
verifyNotNull(view, name);
break;
case "Button":
view = new AppCompatButton(context, attrs);
verifyNotNull(view, name);
break;
case "EditText":
view = new AppCompatEditText(context, attrs);
verifyNotNull(view, name);
break;
case ...
}
}
可以看到AppCompatActivity确实在onCreate方法中为当前Activity的LayoutInflater设置了Factory,并利用这个Factory自动地把许多控件替换成兼容性更强的控件;
3.LayoutInflater.Factory
先来看看LayoutInflater中的Factory:
public abstract class LayoutInflater {
private Factory mFactory;
private Factory2 mFactory2;
private Factory2 mPrivateFactory;
private boolean mFactorySet; //mFactory和mFactory2有一个被赋值就会置为true;
public interface Factory {
public View onCreateView(String name, Context context, AttributeSet attrs);
}
public interface Factory2 extends Factory {
public View onCreateView(View parent, String name, Context context, AttributeSet attrs);
}
public void setFactory(@NotNull Factory factory) {
if (mFactorySet) {
throw new IllegalStateException("A factory has already been set on this LayoutInflater");
}
mFactorySet = true;
if (mFactory == null) {
mFactory = factory;
} else {
mFactory = new FactoryMerger(factory, null, mFactory, mFactory2);
}
}
public void setFactory2(@NotNull Factory2 factory) {
if (mFactorySet) {
throw new IllegalStateException("A factory has already been set on this LayoutInflater");
}
mFactorySet = true;
if (mFactory == null) {
mFactory = mFactory2 = factory;
} else {
mFactory = mFactory2 = new FactoryMerger(factory, factory, mFactory, mFactory2);
}
}
}
Factory2比Factory的onCreateView方法多了一个parent参数,其他没有区别,而且Factory2可以完全替代Factory的所有功能;
Factory2和Factory只能选择一个来添加,且一个Inflater只能添加一次Factory
4.用Factory为某类型的控件统一设置属性:
例如把当前页面所有TextView的字体都统一修改:
public class MainActivity extends AppCompatActivity {
protected void onCreate(Bundle savedInstanceState) {
//在onCreate之前设置Factory
LayoutInflaterCompat.setFactory2(LayoutInflater.from(this), new LayoutInflater.Factory2() {
@Override
public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
View view = null;
if (name.equals("TextView")) {
//用AppCompatDelegate的方法可以创建
AppCompatDelegate delegate = getDelegate();
View view = delegate.createView(parent, name, context, attrs);
((TextView) view).setTypeface(typeface);
}
//返回null将用其他方式创建View,不影响其他控件的正常创建
return null;
}
@Override
public View onCreateView(String name, Context context, AttributeSet attrs) {
return null;
}
});
//系统设置的Factory会失效
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
}