换肤框架

来源:互联网 发布:淘宝天狼花苗是真的吗 编辑:程序博客网 时间: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();                  }              }          }      }  }  


再来看看SkinManager

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


原创粉丝点击