- setFactory
- setFactory2
这两个方法的功能基本是一致的,setFactory2是在SDK>=11以后引入的,所以我们要根据SDK的版本去选择调用上述方法。
值得高兴的是,v4包下有个类LayoutInflaterCompat
帮我们完成了兼容性的操作,提供的方法为:
LayoutInflaterCompat
- setFactory(LayoutInflater inflater,
LayoutInflaterFactory factory)
我们新建一个Activity,在其onCreate中调用
public class MainActivity extends AppCompatActivity
{
private static final String TAG = "MainActivity";
@Override
protected void onCreate(Bundle savedInstanceState)
{
LayoutInflaterCompat.setFactory(LayoutInflater.from(this), new LayoutInflaterFactory()
{
@Override
public View onCreateView(View parent, 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;
}
});
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
现在开发App的时候,我们一般Activity都继承于AppCompatActivity
,而在AppCompatActivity
中,实际上也调用了setFactory
方法。
如果你自己还调用了setFactory
就可能带来一些问题,因为setFactory并不能重复调用。
4、解决方案
我们具体看下appcompat中onCreateView的全部代码:
@Override
public final View onCreateView(View parent, String name,
Context context, AttributeSet attrs) {
// First let the Activity's Factory try and inflate the view
final View view = callActivityOnCreateView(parent, name, context, attrs);
if (view != null) {
return view;
}
// If the Factory didn't handle it, let our createView() method try
return createView(parent, name, context, attrs);
}
可以看到其最终是调用:createView
方法完成view的创建,并且值得高兴的是该方法是public的。
也就是说,我们可以自己设置factory中,依然可以保证appcompat中创建View的代码的执行。
LayoutInflaterCompat.setFactory(LayoutInflater.from(this), new LayoutInflaterFactory()
{
@Override
public View onCreateView(View parent, String name, Context context, AttributeSet attrs)
{
//你可以在这里直接new自定义View
//你可以在这里将系统类替换为自定义View
//appcompat 创建view代码
AppCompatDelegate delegate = getDelegate();
View view = delegate.createView(parent, name, context, attrs);
return view;
}
});
5、高效统一设置app中所有字体
很多时候我们为了app更加个性,然后整体采用外部引入的字体。
很多开发者的实现是这样的,在BaseActivity的onCreate中去从跟布局去递归遍历所有的View,类似的代码如下:
public void setTypeface(ViewGroup root, Typeface typeface){
if(root==null || typeface==null){
return;
}
int count = root.getChildCount();
for(int i=0;i<count;++i){
View view = root.getChildAt(i);
if(View instanceof TextView){
((TextView)view).setTypeface(typeface);
}else if(View instanceof ViewGroup){
setTypeface((ViewGroup)view, typeface);
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
这种方式虽然方便,但是肯定会带来一定性能问题。
说到这,我估计你心理已经有全新的解决方案了,那就是利用setFactory,相关代码如下(在BaseActivity中):
public static Typeface typeface;
@Override
protected void onCreate(Bundle savedInstanceState)
{
if (typeface == null)
{
typeface = Typeface.createFromAsset(getAssets(), "hwxk.ttf");
}
LayoutInflaterCompat.setFactory(LayoutInflater.from(this), new LayoutInflaterFactory()
{
@Override
public View onCreateView(View parent, String name, Context context, AttributeSet attrs)
{
AppCompatDelegate delegate = getDelegate();
View view = delegate.createView(parent, name, context, attrs);
if ( view!= null && (view instanceof TextView))
{
((TextView) view).setTypeface(typeface);
}
return view;
}
});
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
6、扩展
setFactory还非常适合一个场景,就是换肤,换肤需要解决的核心问题有两个:
- 外部资源的加载
- 定位到需要换肤的View
第一个资源加载的问题可以通过构造AssetManager
,反射调用其addAssetPath
就可以完成。
第二个问题,就可以利用在onCreateView中,根据view的属性来定位,例如你可以让需要换肤的view添加一个自定义的属性skin_enabled=true
(最开始有打印属性),并且利用一些手段拿到构造到的view,就能在View构造阶段定位的需要换肤的View。