换肤框架
来源:互联网 发布:淘宝天狼花苗是真的吗 编辑:程序博客网 时间:2024/06/05 19:41
在进行换肤框架讲解之前,我先把View创建过程说一下:
调用Context.getSystemService()方法
LayoutInflater inflater = LayoutInflater.from(context); LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); View rootView = inflater.inflate(R.layout.view_layout, null);
直接使用LayoutInflater.from()方法
View rootView = inflater.inflate(R.layout.view_layout, null);
在Activity下直接调用getLayoutInflater()方法
LayoutInflater inflater = getLayoutInflater(); View rootView = inflater.inflate(R.layout.view_layout, null);
使用View的静态方法View.inflate()
rootView = View.inflate(context, R.layout.view_layout, null);
这四种方式他们的源代码实质都是一样的,过程都是先获取LayoutInflater 对象context.getSystemService(Context.LAYOUT_INFLATER_SERVICE),然后调用LayoutInflater 对象的函数inflate(int resource, ViewGroup root, boolean attachToRoot),生成对应的View,我们来看看inflate函数:
public View inflate(int resource, ViewGroup root, boolean attachToRoot) { if (DEBUG) System.out.println("INFLATING from resource: " + resource); XmlResourceParser parser = getContext().getResources().getLayout(resource); try { return inflate(parser, root, attachToRoot); } finally { parser.close(); } }
可以看到上面的函数是先通过resourceId获取对应的XmlResourceParser对象,这个对象用于解析XML文件的。
然后在调用inflate()函数,接着往下看
public View inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot) { synchronized (mConstructorArgs) { final AttributeSet attrs = Xml.asAttributeSet(parser); Context lastContext = (Context)mConstructorArgs[0]; mConstructorArgs[0] = mContext; View result = root; try { // 寻找root节点 int type; while ((type = parser.next()) != XmlPullParser.START_TAG && type != XmlPullParser.END_DOCUMENT) { // Empty } if (type != XmlPullParser.START_TAG) { throw new InflateException(parser.getPositionDescription() + ": No start tag found!"); } final String name = parser.getName(); ........ if (TAG_MERGE.equals(name)) { ........ } else { // Temp is the root view that was found in the xml View temp; if (TAG_1995.equals(name)) { temp = new BlinkLayout(mContext, attrs); } else { temp = createViewFromTag(root, name, attrs); } ViewGroup.LayoutParams params = null; if (root != null) { if (DEBUG) { System.out.println("Creating params from root: " + root); } // 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); } } ........ // Inflate all children under temp rInflate(parser, temp, attrs, true); if (DEBUG) { System.out.println("-----> done inflating children"); } // We are supposed to attach all the views we found (int temp) // to root. Do that now. if (root != null && attachToRoot) { root.addView(temp, params); } // Decide whether to return the root that was passed in or the // top view found in xml. if (root == null || !attachToRoot) { result = temp; } } } catch (XmlPullParserException e) { .... ....} return result; } }
关键代码就是那两句红色代码,首先找到开始节点,然后调用CreateViewFromTag创建root布局,然后调用rinflate函数创建root布局的所有子View。
上面两个函数,我们先看CreateViewFromTag函数
View createViewFromTag(View parent, String name, AttributeSet attrs) { if (name.equals("view")) { name = attrs.getAttributeValue(null, "class"); } ..... try { View view; if (mFactory2 != null) view = mFactory2.onCreateView(parent, name, mContext, attrs); else if (mFactory != null) view = mFactory.onCreateView(name, mContext, attrs); else view = null; if (view == null && mPrivateFactory != null) { view = mPrivateFactory.onCreateView(parent, name, mContext, attrs); } if (view == null) { if (-1 == name.indexOf('.')) { view = onCreateView(parent, name, attrs); } else { view = createView(name, null, attrs); } } if (DEBUG) System.out.println("Created view is: " + view); return view; } catch (InflateException e) { .... .... } }
可以看到mFactory,Factory两个对象用户创建view视图,只有当两个对象都没有创建成功时才会依次调用mPrivateFactory.onCreateView,onCreateView,createView方法,直达view创建成功为止。
其中mFactory,Factory是LayoutInflater的两个可实现的接口,他们的作用是用于创建View,我们在进行皮肤替换的时候,就是对Factory接口进行实现然后替换系统自带的Factory接口,LayoutInflater的boolean类型的标志位mFactorySet代表着是否设置过factory接口。其中AppCompatActivity,在setContentView之前factory接口就设置过,mFactorySet就被设置成true,因此用户自己设置factory就会报错,提示已经被设置过的错误,因此在AppCompatActivity中,就需要通过反射将mFactorySet设置成false,然后把自己实现的factory设置给LayoutInflater,这些操作都必须在setContentView之前完成。
在生成view视图的时候,如果我们已经设置了factory,那么我们的view就一定是根据factory来生成的,我们可以在实现了的factory中来收集需要皮肤更新的view。
换肤框架的思路就是如下:
这里面最主要的两个部分就是,factory的实现类,异步加载其他apk资源的类,其他的类就是容器,接口,回调函数之类的。容器的关系是,一个view容器内部对应一个属性容器表,因为每一个view有多个属性,而属性也需要一个容器类来保存,因为不同的属性设置函数不一样,所以需要多个容器来保存不同的属性。下面来看看factory的onCreate函数(都有注释)
public class SkinFactory implements Factory { private static final String DEFAULT_SCHEMA_NAME = "http://schemas.android.com/apk/res-auto"; private static final String DEFAULT_ATTR_NAME = "enable"; private List<SkinView> mSkinViews = new ArrayList<SkinView>(); @Override public View onCreateView(String name, Context context, AttributeSet attrs) { View view = null; //判断view有没有skin:enable属性,自定义属性,设置了就表示该view需要换肤 final boolean skinEnable = attrs.getAttributeBooleanValue(DEFAULT_SCHEMA_NAME, DEFAULT_ATTR_NAME, false); if(skinEnable) { //有这个属性,那么就使用自定义函数来创建view view = createView(name, context, attrs); if(null != view) {//收集所有需要换肤的view加入到list中 parseAttrs(name, context, attrs, view); } } //如果返回的是null,那么就会调用系统的创建方法,前面已经说过了 return view; } public final View createView(String name, Context context, AttributeSet attrs) { View view = null; //给view设置前缀,这样系统才能识别是什么控件 if(-1 == name.indexOf('.')) { if("View".equalsIgnoreCase(name)) { view = createView(name, context, attrs, "android.view."); } if(null == view) { view = createView(name, context, attrs, "android.widget."); } if(null == view) { view = createView(name, context, attrs, "android.webkit."); } } else { view = createView(name, context, attrs, null); } return view; } View createView(String name, Context context, AttributeSet attrs, String prefix) { View view = null; try { view = LayoutInflater.from(context).createView(name, prefix, attrs); } catch (Exception e) { } return view; } private void parseAttrs(String name, Context context, AttributeSet attrs, View view) { int attrCount = attrs.getAttributeCount(); final Resources temp = context.getResources(); List<BaseAttr> viewAttrs = new ArrayList<BaseAttr>(); for(int i = 0; i < attrCount; i++) { String attrName = attrs.getAttributeName(i); String attrValue = attrs.getAttributeValue(i); if(isSupportedAttr(attrName)) { //由此可见,属性只有设置了@引用资源的才能换肤 if(attrValue.startsWith("@")) { int id = Integer.parseInt(attrValue.substring(1)); String entryName = temp.getResourceEntryName(id); String entryType = temp.getResourceTypeName(id); BaseAttr viewAttr = createAttr(attrName, attrValue, id, entryName, entryType); if(null != viewAttr) { viewAttrs.add(viewAttr); } } } } if(viewAttrs.size() > 0) { SkinView skinView = new SkinView(); skinView.view = view; skinView.viewAttrs = viewAttrs; mSkinViews.add(skinView); } } // attrName:textColor attrValue:2130968576 entryName:common_bg_color entryType:color private BaseAttr createAttr(String attrName, String attrValue, int id, String entryName, String entryType) { BaseAttr viewAttr = null; //目前只实现了background和textcolor属性设置 if("background".equalsIgnoreCase(attrName)) { viewAttr = new BackgroundAttr(); //背景属性容器 } else if("textColor".equalsIgnoreCase(attrName)) { viewAttr = new TextColorAttr(); //文字颜色属性容器 } if(null != viewAttr) { viewAttr.attrName = attrName; viewAttr.attrValue = id; viewAttr.entryName = entryName; viewAttr.entryType = entryType; } return viewAttr; } //是否支持该属性 private boolean isSupportedAttr(String attrName) { if("background".equalsIgnoreCase(attrName)) { return true; } else if("textColor".equalsIgnoreCase(attrName)) { return true; } return false; } //应用皮肤 public void applaySkin() { if(null != mSkinViews) { for(SkinView skinView : mSkinViews) { if(null != skinView.view) { skinView.apply(); } } } } }
public final class SkinManager { private static final Object mClock = new Object(); private static SkinManager mInstance; private Context mContext; //当前上下文 private Resources mResources; //该变量是保存对应皮肤资源的Resources 对象,不同的Resources 代表着不同的皮肤 private String mSkinPkgName; //Resources 对应的包名 private SkinManager() { } //懒汉加载单例 public static SkinManager getInstance() { if(null == mInstance) { synchronized (mClock) { if(null == mInstance) { mInstance = new SkinManager(); } } } return mInstance; } public void init(Context context) { enableContext(context); mContext = context.getApplicationContext(); } public void loadSkin(String skinPath) { loadSkin(skinPath, null); } public void loadSkin(final String skinPath, final ILoadListener listener) { enableContext(mContext); if(TextUtils.isEmpty(skinPath)) { return; } //异步加载其他apk的资源 ,给定一个apk包路径,返回一个资源Resources new AsyncTask<String, Void, Resources>() { @Override protected void onPreExecute() { if(null != listener) { listener.onStart(); } } @Override protected Resources doInBackground(String... params) { if(null != params && params.length == 1) { String skinPath = params[0]; File file = new File(skinPath); if(null != file && file.exists()) { PackageManager packageManager = mContext.getPackageManager(); PackageInfo packageInfo = packageManager.getPackageArchiveInfo(skinPath, 1); if(null != packageInfo) { mSkinPkgName = packageInfo.packageName; } return getResources(mContext, skinPath); } } return null; } @Override protected void onPostExecute(Resources result) { if(null != result) { mResources = result; if(null != listener) { // 如果返回的Resources 不是空的话,那么就回调listener,这里就回到了activity中 // 根据之前说factory是在setContentView()之前设置的,所以Activity中就可以使用factory// 然后这个回调就调用 factory.applyskin()函数,这样收集的view就会设置Resources 对应的属性// 下面的getColor(),getDrawer(),函数就是根据ID返回Resources中的属性值, 那么就可以达到换肤的效果了。 listener.onSuccess(); } } else { if(null != listener) { listener.onFailure(); } } } }.execute(skinPath); } public Resources getResources(Context context, String apkPath) { try { //下面四行是固定写法,反射加载静态资源。 AssetManager assetManager = AssetManager.class.newInstance(); Method addAssetPath = assetManager.getClass().getDeclaredMethod("addAssetPath", String.class); addAssetPath.setAccessible(true); addAssetPath.invoke(assetManager, apkPath); //生成Resources Resources r = context.getResources(); Resources skinResources = new Resources(assetManager, r.getDisplayMetrics(), r.getConfiguration()); return skinResources; } catch (Exception e) { } return null; } public void restoreDefaultSkin() { if(null != mResources) { mResources = null; mSkinPkgName = null; } } public int getColor(int id) { enableContext(mContext); Resources originResources = mContext.getResources(); int originColor = originResources.getColor(id); if(null == mResources || TextUtils.isEmpty(mSkinPkgName)) { return originColor; } String entryName = mResources.getResourceEntryName(id); int resourceId = mResources.getIdentifier(entryName, "color", mSkinPkgName); try { return mResources.getColor(resourceId); } catch (Exception e) { } return originColor; } public Drawable getDrawable(int id) { enableContext(mContext); Resources originResources = mContext.getResources(); Drawable originDrawable = originResources.getDrawable(id); if(null == mResources || TextUtils.isEmpty(mSkinPkgName)) { return originDrawable; } String entryName = mResources.getResourceEntryName(id); int resourceId = mResources.getIdentifier(entryName, "drawable", mSkinPkgName); try { return mResources.getDrawable(resourceId); } catch (Exception e) { } return originDrawable; } private void enableContext(Context context) { if(null == context) { throw new NullPointerException(); } } }
最后 附上源码地址:http://download.csdn.net/detail/llew2011/9518833
参考的博客地址:http://blog.csdn.net/llew2011/article/details/51252401
阅读全文
0 0
- 换肤框架
- Android换肤框架
- 换肤框架
- 换肤框架
- 换肤框架
- 换肤框架的资源
- 换肤框架学习总结
- 框架页面的换肤实现
- [Swift 开发] iOS换肤框架 SwiftTheme
- 换肤框架 skin-loader-lib 使用
- Android换肤功能实现与换肤框架QSkinLoader使用方式介绍
- Android换肤白天/夜间模式的框架
- android的资源混淆和压缩工具,换肤框架
- Andorid 换肤框架AndSkin源码解析及优缺点
- Android换肤原理和Android-Skin-Loader框架解析
- 换肤
- 换肤
- 换肤
- Docker搭建带有访问认证的私有仓库
- 文章标题
- 在 VMware 虚拟机中安装 open-vm-tools
- Unity自定义UI组件(七)渐变工具、渐变色图片、渐变遮罩
- JavaScript对DOM操作的若干优化
- 换肤框架
- android 闹钟
- Java 异常处理机制(异常链)
- Android TV APPs 的介绍与创建
- Eclipse中Maven工程缺少Maven Dependencies 的解决办法
- 《HBase权威指南》读书笔记 第四章:客户端API高级特性
- No matching distribution found for tensorflow 错误解决
- Java Concurrency代码实例之三原子变量
- 一份土豆丝的自白