不只是切换多语言Android(一)
来源:互联网 发布:网络装修平台有哪些 编辑:程序博客网 时间:2024/05/20 14:15
前言:前几天在地铁上看到一哥们的一篇app换肤方案解决,觉得很nice,于是就研究了一下,收获蛮多,还想到以前我做过的app,当时是需要中英文切换的,于是打算把这哥们的换肤方案运用在切换语言上去,捣腾了一会,感觉还可以,但是自我感觉还是不太理想,不管咋样,就当涨涨见识呗,于是打算把我所学到的知识分享给大家,大家有什么好的切换语言方案记得告知一下哈,先拜谢啦!!
先附上大牛的换肤demo地址:
https://github.com/ximsfei/Android-skin-support
先看一下换肤效果:
然后根据换肤思路,看看我们实现的切换语言框架效果:
其中主要的思路还是来自v7的AppCompatActivity,AppCompatActivity做的一件事就是把我们的基本组件转换成了material效果组件,我们简单的看一下AppCompatActivity是怎么处理的。
public class AppCompatActivity extends FragmentActivity implements AppCompatCallback, TaskStackBuilder.SupportParentable, ActionBarDrawerToggle.DelegateProvider { 。。。。 @Override protected void onCreate(@Nullable Bundle savedInstanceState) { final AppCompatDelegate delegate = getDelegate(); delegate.installViewFactory(); delegate.onCreate(savedInstanceState); } 。。。。}
首先创建了一个AppCompatDelegate对象delegate:
/** * @return The {@link AppCompatDelegate} being used by this Activity. */ @NonNull public AppCompatDelegate getDelegate() { if (mDelegate == null) { mDelegate = AppCompatDelegate.create(this, this); } return mDelegate; }
然后我们看看它是怎么创建mDelegate对象的:
private static AppCompatDelegate create(Context context, Window window, AppCompatCallback callback) { final int sdk = Build.VERSION.SDK_INT; if (BuildCompat.isAtLeastN()) { return new AppCompatDelegateImplN(context, window, callback); } else if (sdk >= 23) { return new AppCompatDelegateImplV23(context, window, callback); } else if (sdk >= 14) { return new AppCompatDelegateImplV14(context, window, callback); } else if (sdk >= 11) { return new AppCompatDelegateImplV11(context, window, callback); } else { return new AppCompatDelegateImplV9(context, window, callback); } }
找到了delegate对象,我们反过来看看AppCompatActivity最初的oncreate方法:
@Override protected void onCreate(@Nullable Bundle savedInstanceState) { final AppCompatDelegate delegate = getDelegate(); delegate.installViewFactory(); delegate.onCreate(savedInstanceState);
我们找到installViewFactory方法:
@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"); } } }
可以看到在delegate的内部,获取到当前app的 layoutInflater对象,然后给layoutInflater设置了一个factory:
LayoutInflater layoutInflater = LayoutInflater.from(mContext); if (layoutInflater.getFactory() == null) { LayoutInflaterCompat.setFactory(layoutInflater, this); }
LayoutInflater我们都不陌生,当我们去加载一个xml布局的时候用到:
inflater.inflate(xxxx)
这个方法:
我们顺着这个方法往下走:
getLayoutInflater().inflate()
ublic View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {。。省略很多代码 rInflate(parser, root, inflaterContext, attrs, false);。。省略很多代码}
我们看看rInflate方法:
void rInflate(XmlPullParser parser, View parent, Context context, AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException { final int depth = parser.getDepth(); int type; while (((type = parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) { if (type != XmlPullParser.START_TAG) { continue; } final String name = parser.getName(); if (TAG_REQUEST_FOCUS.equals(name)) { parseRequestFocus(parser, parent); } else if (TAG_TAG.equals(name)) { parseViewTag(parser, parent, attrs); } else if (TAG_INCLUDE.equals(name)) { if (parser.getDepth() == 0) { throw new InflateException("<include /> cannot be the root element"); } parseInclude(parser, context, parent, attrs); } else if (TAG_MERGE.equals(name)) { throw new InflateException("<merge /> must be the root element"); } else { final View view = createViewFromTag(parent, name, context, attrs); final ViewGroup viewGroup = (ViewGroup) parent; final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs); rInflateChildren(parser, view, attrs, true); viewGroup.addView(view, params); } } if (finishInflate) { parent.onFinishInflate(); } }
我们继续看到其中的createViewFromTag方法:
View createViewFromTag(View parent, String name, Context context, AttributeSet attrs, boolean ignoreThemeAttr) { 。。。。 if (view == null && mPrivateFactory != null) { view = mPrivateFactory.onCreateView(parent, name, context, attrs); } 。。。。 }
这个mPrivateFactory就是我们在AppCompatActivity的onCreate方法中调用的:
delegate.installViewFactory();
@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"); } } }
所以这个factory即为AppCompatActivity中的delegate,所以当我们调用setContentView的时候,其中在PhoneWindow中会去获取app的inflator对象,然后调用inflate方法,把通过我们传递的layoutid加载layout文件,在inflater方法中,当inflator设置了factory的话就会执行:
if (view == null && mPrivateFactory != null) { view = mPrivateFactory.onCreateView(parent, name, context, attrs); }
所以会触发factory的onCreateView方法,把需要创建view的父类、名字、attrs属性传递过去:
比如下面的xml中view,就会传递name=TextView、attrs为text等属性。
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/change_succ" />
我们看看AppCompatActivity中的delegate对象(factory)怎么处理的:
@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); }
@Override public View createView(View parent, final String name, @NonNull Context context, @NonNull AttributeSet attrs) { final boolean isPre21 = Build.VERSION.SDK_INT < 21; if (mAppCompatViewInflater == null) { mAppCompatViewInflater = new AppCompatViewInflater(); } // We only want the View to inherit its context if we're running pre-v21 final boolean inheritContext = isPre21 && shouldInheritContext((ViewParent) parent); return mAppCompatViewInflater.createView(parent, name, context, attrs, inheritContext, isPre21, /* 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 */ ); }
最后看看
mAppCompatViewInflater.createView:
@NonNull AttributeSet attrs, boolean inheritContext, boolean readAndroidTheme, boolean readAppTheme, boolean wrapContext) { final Context originalContext = context; // We can emulate Lollipop's android:theme attribute propagating down the view hierarchy // by using the parent's context if (inheritContext && parent != null) { context = parent.getContext(); } if (readAndroidTheme || readAppTheme) { // We then apply the theme on the context, if specified context = themifyContext(context, attrs, readAndroidTheme, readAppTheme); } if (wrapContext) { context = TintContextWrapper.wrap(context); } 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; } if (view == null && originalContext != context) { // If the original context does not equal our themed context, then we need to manually // inflate it using the name so that android:theme takes effect. view = createViewFromTag(context, name, attrs); } if (view != null) { // If we have created a view, check it's android:onClick checkOnClickListener(view, attrs); } return view; }
好吧,看到这里是不是有点兴奋呢??我们看到,当我们在xml布局中创建一些基本的组件的时候,都会AppCompatActivity转换成了AppCompatTextView等等这类的组件了,好累啊~AppCompatTextView我就不看了,AppCompatTextView等就是实现material属性的关键组件。
好啦~~也不知道小伙伴看懂了没有,简单总结一下:
比如我们在xml中创建一个TextView—>我们调用activity的setcontentview—>inflater的inflate方法--->在AppCompatActivity方法中给inflater设置factory--->调用inflater的factory的oncreateview方法—>然后在oncreateview方法中通过传递的组件name创建对应的组件AppCompatTextView-->根据activity的主题修改AppCompatTextView样式。
换肤方案的实现:
跟AppCompatActivity一样,给inflater设置一个factory--->>调用inflater的factory的oncreateview方法--->然后在oncreateview方法中通过传递的组件name创建对应的组件SkinAbleTextView--->然后把创建的SkinAbleTextView组件用一个集合装起来—>最后通过观察者模式,当变皮肤的时候,遍历所有的组件,然后调用对应的换肤方法--->改变样式
我们的切换语言框架也是一样哈~~~我就不啰嗦了,下节直接撸代码了~
不只是切换多语言Android(二)
本节先到这里!!
欢迎小伙伴入群: qq群号:511276976
- 不只是切换多语言Android(一)
- 不只是切换多语言Android(二)
- 未定义行为:不只是编程语言
- android多语言切换
- android多语言切换
- Android多语言切换
- Android多语言切换
- Android多语言切换
- 不只是休闲:关于体感游戏的一些思考(一)--- 开篇和“随身”物件
- android源码分析(一) - 语言切换机制
- android源码分析(一) - 语言切换机制
- android源码分析(一) - 语言切换机制
- android源码分析(一) - 语言切换机制
- Android源码分析一:语言切换机制
- 27. 不只是学语言,更要领悟其文化
- New 不只是用来创建对象的(一)
- Android App 多语言切换
- Android App 多语言切换
- 插入数据时返回主键值
- 机器学习-scikit learn学习笔记
- 调用手机相册和照相机
- Longest Common Subsequence
- Java学习日记13:MyBatis
- 不只是切换多语言Android(一)
- js函数 作用域
- DecimalFormat在一道笔试题中的应用
- Mybatis通用Mapper
- Scrapy学习笔记VI--Item Loaders
- 调用手机相册和文档
- Nginx+Tomcat负载均衡配置
- c++实验三作业
- mysql:存储过程样例