Android之旅

LayoutInflaterFactory app内全局更换字体

2018-03-07  本文已影响80人  h2coder

LayoutInflaterFactory app内全局更换字体

效果图.png

LayoutInflaterFactory,它的作用是什么?简单来讲就是,我们在布局中写的控件,在反射构造完后在设置到View树之前,先过一把我们写的工厂类。我们可以对它一些操作,甚至替换掉~

Activity...

@Override
   protected void onCreate(Bundle savedInstanceState) {
       createTextTypeface();
       LayoutInflaterCompat.setFactory(LayoutInflater.from(this), new LayoutInflaterFactory() {
           @Override
           public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
               //动态替换成带字体的TextView
               if (name.equals("TextView")) {
                   return new Button(MainActivity.this, attrs);
               }
               return view;
           }
       });
       super.onCreate(savedInstanceState);
       setContentView(R.layout.activity_main);
   }

其实还有没有问题呢?

@Override
   protected void onCreate(@Nullable Bundle savedInstanceState) {
       //创建代理
       final AppCompatDelegate delegate = getDelegate();
       //这里开始替换
       delegate.installViewFactory();
       delegate.onCreate(savedInstanceState);
       if (delegate.applyDayNight() && mThemeId != 0) {
           // If DayNight has been applied, we need to re-apply the theme for
           // the changes to take effect. On API 23+, we should bypass
           // setTheme(), which will no-op if the theme ID is identical to the
           // current theme ID.
           if (Build.VERSION.SDK_INT >= 23) {
               onApplyThemeResource(getTheme(), mThemeId, false);
           } else {
               setTheme(mThemeId);
           }
       }
       super.onCreate(savedInstanceState);
   }

   @Override
   public void installViewFactory() {
       LayoutInflater layoutInflater = LayoutInflater.from(mContext);
       if (layoutInflater.getFactory() == null) {
           LayoutInflaterCompat.setFactory2(layoutInflater, this);
       } else {
           if (!(layoutInflater.getFactory2() instanceof AppCompatDelegateImplV9)) {
               Log.i(TAG, "The Activity's LayoutInflater already has a Factory installed"
                       + " so we can not install AppCompat's");
           }
       }
   }
@NonNull
   public AppCompatDelegate getDelegate() {
       if (mDelegate == null) {
           mDelegate = AppCompatDelegate.create(this, this);
       }
       return mDelegate;
   }
private static AppCompatDelegate create(Context context, Window window,
           AppCompatCallback callback) {
       if (Build.VERSION.SDK_INT >= 24) {
           return new AppCompatDelegateImplN(context, window, callback);
       } else if (Build.VERSION.SDK_INT >= 23) {
           return new AppCompatDelegateImplV23(context, window, callback);
       } else if (Build.VERSION.SDK_INT >= 14) {
           return new AppCompatDelegateImplV14(context, window, callback);
       } else if (Build.VERSION.SDK_INT >= 11) {
           return new AppCompatDelegateImplV11(context, window, callback);
       } else {
           return new AppCompatDelegateImplV9(context, window, callback);
       }
   }
class AppCompatDelegateImplV11 extends AppCompatDelegateImplV9 {
}
@Override
   public View createView(View parent, final String name, @NonNull Context context,
           @NonNull AttributeSet attrs) {
       if (mAppCompatViewInflater == null) {
           mAppCompatViewInflater = new AppCompatViewInflater();
       }

       boolean inheritContext = false;
       if (IS_PRE_LOLLIPOP) {
           inheritContext = (attrs instanceof XmlPullParser)
                   // If we have a XmlPullParser, we can detect where we are in the layout
                   ? ((XmlPullParser) attrs).getDepth() > 1
                   // Otherwise we have to use the old heuristic
                   : shouldInheritContext((ViewParent) parent);
       }

       //替换并返回
       return mAppCompatViewInflater.createView(parent, name, context, attrs, inheritContext,
               IS_PRE_LOLLIPOP, /* Only read android:theme pre-L (L+ handles this anyway) */
               true, /* Read read app:theme as a fallback at all times for legacy reasons */
               VectorEnabledTintResources.shouldBeUsed() /* Only tint wrap the context if enabled */
       );
   }
public final View createView(View parent, final String name, @NonNull Context context,
           @NonNull AttributeSet attrs, boolean inheritContext,
           boolean readAndroidTheme, boolean readAppTheme, boolean wrapContext) {
       ...省略部分代码

       View view = null;

       // We need to 'inject' our tint aware Views in place of the standard framework versions
       switch (name) {
           case "TextView":
               view = new AppCompatTextView(context, attrs);
               break;
           case "ImageView":
               view = new AppCompatImageView(context, attrs);
               break;
           case "Button":
               view = new AppCompatButton(context, attrs);
               break;
           case "EditText":
               view = new AppCompatEditText(context, attrs);
               break;
           case "Spinner":
               view = new AppCompatSpinner(context, attrs);
               break;
           case "ImageButton":
               view = new AppCompatImageButton(context, attrs);
               break;
           case "CheckBox":
               view = new AppCompatCheckBox(context, attrs);
               break;
           case "RadioButton":
               view = new AppCompatRadioButton(context, attrs);
               break;
           case "CheckedTextView":
               view = new AppCompatCheckedTextView(context, attrs);
               break;
           case "AutoCompleteTextView":
               view = new AppCompatAutoCompleteTextView(context, attrs);
               break;
           case "MultiAutoCompleteTextView":
               view = new AppCompatMultiAutoCompleteTextView(context, attrs);
               break;
           case "RatingBar":
               view = new AppCompatRatingBar(context, attrs);
               break;
           case "SeekBar":
               view = new AppCompatSeekBar(context, attrs);
               break;
       }
       省略部分代码...
       return view;
   }
LayoutInflaterCompat.setFactory(LayoutInflater.from(this), new LayoutInflaterFactory() {
           @Override
           public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
               //这里先做自己的替换操作
               if (name.equals("TextView")) {
                   return new xxx();
               }
               //为了让AppCompat的控件替换到,所以调用AppCompat的替换进行替换
               View view = getDelegate().createView(parent, name, context, attrs);
               return view;
           }
       });

用途

  1. 先将我们的字体放在src-main-assets文件夹,没有则需要自己新建,这个大家都懂

  2. 创建Typeface对象,给TextView设置setTypeface

public class MainActivity extends AppCompatActivity {
   private Typeface mTypeface;

   @Override
   protected void onCreate(Bundle savedInstanceState) {
       createTextTypeface();
       LayoutInflaterCompat.setFactory(LayoutInflater.from(this), new LayoutInflaterFactory() {
           @Override
           public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
               //为了让AppCompat的控件替换到,所以调用AppCompat的替换进行替换
               View view = getDelegate().createView(parent, name, context, attrs);
               if (view != null && view instanceof TextView) {
                   //这里给每个TextView都设置上字体
                   ((TextView) view).setTypeface(mTypeface);
               }
               return view;
           }
       });
       super.onCreate(savedInstanceState);
       setContentView(R.layout.activity_main);
   }

   //创建字体
   private void createTextTypeface() {
       mTypeface = Typeface.createFromAsset(getAssets(), "QingXinKaiTi.ttf");
   }
}
  1. 也可以自定义TextView去调用字体设置,替换TextView喔
LayoutInflaterCompat.setFactory(LayoutInflater.from(this), new LayoutInflaterFactory() {
           @Override
           public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
               //动态替换成带字体的TextView
               if (name.equals("TextView")) {
                   return new FontTextView(MainActivity.this, attrs);
               }
               //为了让AppCompat的控件替换到,所以调用AppCompat的替换进行替换
               View view = getDelegate().createView(parent, name, context, attrs);
               if (view != null && view instanceof TextView) {
                   ((TextView) view).setTypeface(mTypeface);
               }
               return view;
           }
       });
  1. 最后将这句话放到Activity的基类的onCreate(),这样就大功告成啦~

有些继承了官方控件,在项目中不好替换时,就可以这样动态替换啦,例如我们的通用滚动库,就是给每个滚动控件都写一个子类,实现接口,再用工厂进行偷梁换柱~

  1. github地址
上一篇 下一篇

猜你喜欢

热点阅读