[
  {
    "path": ".gitignore",
    "content": "*.iml\n.gradle\n/local.properties\n/.idea/workspace.xml\n/.idea/libraries\n.DS_Store\n/build\n/captures\n.externalNativeBuild\n"
  },
  {
    "path": ".idea/compiler.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"CompilerConfiguration\">\n    <resourceExtensions />\n    <wildcardResourcePatterns>\n      <entry name=\"!?*.java\" />\n      <entry name=\"!?*.form\" />\n      <entry name=\"!?*.class\" />\n      <entry name=\"!?*.groovy\" />\n      <entry name=\"!?*.scala\" />\n      <entry name=\"!?*.flex\" />\n      <entry name=\"!?*.kt\" />\n      <entry name=\"!?*.clj\" />\n      <entry name=\"!?*.aj\" />\n    </wildcardResourcePatterns>\n    <annotationProcessing>\n      <profile default=\"true\" name=\"Default\" enabled=\"false\">\n        <processorPath useClasspath=\"true\" />\n      </profile>\n    </annotationProcessing>\n    <bytecodeTargetLevel target=\"1.6\" />\n  </component>\n</project>"
  },
  {
    "path": ".idea/copyright/profiles_settings.xml",
    "content": "<component name=\"CopyrightManager\">\n  <settings default=\"\" />\n</component>"
  },
  {
    "path": ".idea/encodings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"Encoding\">\n    <file url=\"PROJECT\" charset=\"UTF-8\" />\n  </component>\n</project>"
  },
  {
    "path": ".idea/gradle.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"GradleSettings\">\n    <option name=\"linkedExternalProjectsSettings\">\n      <GradleProjectSettings>\n        <option name=\"distributionType\" value=\"DEFAULT_WRAPPED\" />\n        <option name=\"externalProjectPath\" value=\"$PROJECT_DIR$\" />\n        <option name=\"modules\">\n          <set>\n            <option value=\"$PROJECT_DIR$\" />\n            <option value=\"$PROJECT_DIR$/QSkinLoaderlib\" />\n            <option value=\"$PROJECT_DIR$/app\" />\n          </set>\n        </option>\n        <option name=\"resolveModulePerSourceSet\" value=\"false\" />\n      </GradleProjectSettings>\n    </option>\n  </component>\n</project>"
  },
  {
    "path": ".idea/misc.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"ProjectLevelVcsManager\" settingsEditedManually=\"false\">\n    <OptionsSetting value=\"true\" id=\"Add\" />\n    <OptionsSetting value=\"true\" id=\"Remove\" />\n    <OptionsSetting value=\"true\" id=\"Checkout\" />\n    <OptionsSetting value=\"true\" id=\"Update\" />\n    <OptionsSetting value=\"true\" id=\"Status\" />\n    <OptionsSetting value=\"true\" id=\"Edit\" />\n    <ConfirmationsSetting value=\"0\" id=\"Add\" />\n    <ConfirmationsSetting value=\"0\" id=\"Remove\" />\n  </component>\n  <component name=\"ProjectRootManager\" version=\"2\" languageLevel=\"JDK_1_7\" default=\"true\" assert-keyword=\"true\" jdk-15=\"true\" project-jdk-name=\"1.8\" project-jdk-type=\"JavaSDK\">\n    <output url=\"file://$PROJECT_DIR$/build/classes\" />\n  </component>\n  <component name=\"ProjectType\">\n    <option name=\"id\" value=\"Android\" />\n  </component>\n</project>"
  },
  {
    "path": ".idea/modules.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"ProjectModuleManager\">\n    <modules>\n      <module fileurl=\"file://$PROJECT_DIR$/QSkinLoader.iml\" filepath=\"$PROJECT_DIR$/QSkinLoader.iml\" />\n      <module fileurl=\"file://$PROJECT_DIR$/QSkinLoaderlib/QSkinLoaderlib.iml\" filepath=\"$PROJECT_DIR$/QSkinLoaderlib/QSkinLoaderlib.iml\" />\n      <module fileurl=\"file://$PROJECT_DIR$/app/app.iml\" filepath=\"$PROJECT_DIR$/app/app.iml\" />\n    </modules>\n  </component>\n</project>"
  },
  {
    "path": ".idea/runConfigurations.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"RunConfigurationProducerService\">\n    <option name=\"ignoredProducers\">\n      <set>\n        <option value=\"org.jetbrains.plugins.gradle.execution.test.runner.AllInPackageGradleConfigurationProducer\" />\n        <option value=\"org.jetbrains.plugins.gradle.execution.test.runner.TestClassGradleConfigurationProducer\" />\n        <option value=\"org.jetbrains.plugins.gradle.execution.test.runner.TestMethodGradleConfigurationProducer\" />\n      </set>\n    </option>\n  </component>\n</project>"
  },
  {
    "path": "QSkinLoaderlib/.gitignore",
    "content": "/build\n"
  },
  {
    "path": "QSkinLoaderlib/build.gradle",
    "content": "apply plugin: 'com.android.library'\n\nandroid {\n    compileSdkVersion 23\n    buildToolsVersion \"23.0.1\"\n\n    defaultConfig {\n        minSdkVersion 14\n        targetSdkVersion 23\n    }\n}\n\ndependencies {\n    compile fileTree(include: ['*.jar'], dir: 'libs')\n    compile 'com.android.support:recyclerview-v7:23.2.1'\n}\n"
  },
  {
    "path": "QSkinLoaderlib/proguard-rules.pro",
    "content": "# Add project specific ProGuard rules here.\n# By default, the flags in this file are appended to flags specified\n# You can edit the include path and order by changing the proguardFiles\n# directive in build.gradle.\n#\n# For more details, see\n#   http://developer.android.com/guide/developing/tools/proguard.html\n\n# Add any project specific keep options here:\n\n# If your project uses WebView with JS, uncomment the following\n# and specify the fully qualified class name to the JavaScript interface\n# class:\n#-keepclassmembers class fqcn.of.javascript.interface.for.webview {\n#   public *;\n#}\n"
  },
  {
    "path": "QSkinLoaderlib/src/main/AndroidManifest.xml",
    "content": "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    package=\"org.qcode.qskinloader\">\n</manifest>\n"
  },
  {
    "path": "QSkinLoaderlib/src/main/java/org/qcode/qskinloader/IActivitySkinEventHandler.java",
    "content": "package org.qcode.qskinloader;\n\nimport android.app.Activity;\n\n/**\n * 与Activity相关的皮肤框架逻辑处理抽象接口\n *\n * a interface defines how to implement an activity skin event handler,\n * which handles activity's life events like onCreate/onResume...\n *\n * qqliu\n * 2016/9/25.\n */\npublic interface IActivitySkinEventHandler {\n    /***\n     * 应该在setContentView之前调用\n     *\n     * invoked in activity's onCreate method,\n     * and should be invoked before setContentView.\n     */\n    void onCreate(Activity activity);\n\n    /***\n     * 设置View创建的监听器；\n     * 可替代框架的View创建，或在框架创建了View后进行进一步处理；\n     *\n     * set an IViewCreateListener to LayoutInflater factory,\n     * to delegate view-creating or do further work after view created.\n     * @param viewCreateListener\n     */\n    void setViewCreateListener(IViewCreateListener viewCreateListener);\n\n    /***\n     * 在setContentView之后调用\n     *\n     * should be invoked after setContentView.\n     */\n    void onViewCreated();\n\n    /***\n     * 在onStart()回调内调用\n     * invoked in activity's onStart()\n     */\n    void onStart();\n\n    /***\n     * 在onResume()回调内调用\n     * invoked in activity's onResume()\n     */\n    void onResume();\n\n    /***\n     * 在onWindowFocusChanged()回调内调用\n     *\n     * invoked in activity's onWindowFocusChanged()\n     */\n    void onWindowFocusChanged(boolean hasFocus);\n\n    /***\n     * 在onPause()回调内调用\n     *\n     * invoked in activity's onPause()\n     */\n    void onPause();\n\n    /***\n     * 在onStop()回调内调用\n     * invoked in activity's onStop()\n     */\n    void onStop();\n\n    /***\n     * 在onDestroy()回调内调用\n     * invoked in activity's onDestroy()\n     */\n    void onDestroy();\n\n    /***\n     * 告知当前界面是否在换肤事件发生时立刻刷新皮肤，\n     * false表示Activity获取到focus时才会刷新，\n     * onCreate之前调用\n     *\n     * notify the handler whether the activity handles\n     * skin-change event immediately.\n     * invoked before onCreate();\n     *\n     * @param isImmediate\n     * @return\n     */\n    IActivitySkinEventHandler setSwitchSkinImmediately(boolean isImmediate);\n\n    /***\n     * 告知当前界面是否支持换肤;\n     * onCreate之前调用\n     *\n     * notify the handler whether the activity\n     * supports skin change.\n     * invoked before onCreate();\n     *\n     * @param supportChange\n     * @return\n     */\n    IActivitySkinEventHandler setSupportSkinChange(boolean supportChange);\n\n    /***\n     * 告知当前界面的Window的背景色，需要传入资源id;\n     * onCreate之前调用\n     *\n     * tell handler the activity's background color,\n     * refered as resource id.\n     * invoked before onCreate();\n     * @param resId\n     * @return\n     */\n    IActivitySkinEventHandler setWindowBackgroundResource(int resId);\n\n    /***\n     * 设置是否需要代理View创建过程；\n     * true表示框架创建View，\n     * false表示不需要创建View，由框架外其他模块创建View，\n     * 此时属性解析动作应在IViewCreateListener内完成。\n     *\n     * set whether framework need delegate view-creating.\n     * true indicates that the framework does view-creating;\n     * false indicates that the framework don't handle view-creating,\n     * otherwise, the view is created by outside,\n     * and the property-parsing process should be done in interface @ref{IViewCreateListener}.\n     *\n     * @param needDelegateViewCreate\n     * @return\n     */\n    IActivitySkinEventHandler setNeedDelegateViewCreate(boolean needDelegateViewCreate);\n\n    /***\n     * 当皮肤发生变化时，此方法会被调用，来完成Activity的皮肤切换工作\n     *\n     * when skin changes, @ref{handleSkinUpdate} will be\n     * called to refresh the activity's skin.\n     */\n    void handleSkinUpdate();\n\n    /***\n     * 获取皮肤属性解析帮助类\n     *\n     * get a skin attributes parser used when view is creating.\n     * @return\n     */\n    ISkinAttributeParser getSkinAttributeParser();\n}\n"
  },
  {
    "path": "QSkinLoaderlib/src/main/java/org/qcode/qskinloader/ILoadSkinListener.java",
    "content": "package org.qcode.qskinloader;\n\n/**\n * 加载皮肤过程的事件回调\n *\n * a interface defines the skin loading progress.\n *\n * qqliu\n * 2016/9/24.\n */\npublic interface ILoadSkinListener {\n    /***\n     * 加载皮肤开始\n     *\n     * notify skin-loading begin event\n     *\n     * @param skinIdentifier\n     */\n    void onLoadStart(String skinIdentifier);\n\n    /***\n     * 加载皮肤完成\n     *\n     * notify skin-loading success event\n     *\n     * @param skinIdentifier\n     */\n    void onLoadSuccess(String skinIdentifier);\n\n    /***\n     * 加载皮肤失败\n     *\n     * notify skin-loading fail event\n     *\n     * @param skinIdentifier\n     */\n    void onLoadFail(String skinIdentifier);\n}\n"
  },
  {
    "path": "QSkinLoaderlib/src/main/java/org/qcode/qskinloader/IResourceLoader.java",
    "content": "package org.qcode.qskinloader;\n\nimport org.qcode.qskinloader.resourceloader.ILoadResourceCallback;\n\n/**\n * 皮肤资源加载器接口\n *\n * A interface defines the load resource behaviour.\n * A resource loader loads resource and notify load behaviour by @ref{ILoadResourceCallback}\n * qqliu\n * 2016/9/25.\n */\npublic interface IResourceLoader {\n\n    /***\n     * 定义资源加载的行为接口，加载的皮肤以skinIdentifier标识，\n     * 加载结果以loadCallBack通知加载资源结果\n     *\n     * loads the skin identified by skinIdentifier,\n     * notifies load behaviour by loadCallBack\n     * @param skinIdentifier\n     * @param loadCallBack\n     */\n    void loadResource(String skinIdentifier,\n                      ILoadResourceCallback loadCallBack);\n}\n"
  },
  {
    "path": "QSkinLoaderlib/src/main/java/org/qcode/qskinloader/IResourceManager.java",
    "content": "package org.qcode.qskinloader;\n\nimport android.content.res.ColorStateList;\nimport android.content.res.Resources;\nimport android.graphics.drawable.Drawable;\n\n/**\n * 皮肤资源管理器接口\n *\n * the interface defines what a resource manager should do.\n * qqliu\n * 2016/9/25.\n */\npublic interface IResourceManager {\n\n    /***\n     * 为IResourceManager设置一个真正工作的资源管理器，IResourceManager是baseResource的封装\n     * set a resource manager as a base worker for the IResourceManager.\n     * @param skinIdentifier 皮肤的唯一标识;\n     *                       the skin identifier\n     * @param baseResource 真正读取资源的ResourceManager;\n     *                     the real resource manager which defines how to attach resources.\n     */\n    void setBaseResource(\n            String skinIdentifier,\n            IResourceManager baseResource);\n\n    /***\n     * get skin identifier\n     * @return\n     */\n    String getSkinIdentifier();\n\n    /***\n     * return whether current is default skin\n     * @return\n     */\n    boolean isDefault();\n\n    /***\n     * get drawable by resource id\n     * @param resId\n     * @return\n     * @throws Resources.NotFoundException\n     */\n    Drawable getDrawable(int resId) throws Resources.NotFoundException;\n\n    /***\n     * get drawable by resource id and name\n     * @param resId\n     * @param resName\n     * @return\n     * @throws Resources.NotFoundException\n     */\n    Drawable getDrawable(int resId, String resName) throws Resources.NotFoundException;\n\n    /***\n     * get color by resource id\n     * @param resId\n     * @return\n     * @throws Resources.NotFoundException\n     */\n    int getColor(int resId) throws Resources.NotFoundException;\n\n    /***\n     * get color by resource id and resource name\n     * @param resId\n     * @param resName\n     * @return\n     * @throws Resources.NotFoundException\n     */\n    int getColor(int resId, String resName) throws Resources.NotFoundException;\n\n    /***\n     * get ColorStateList by resource id\n     * @param resId\n     * @return\n     * @throws Resources.NotFoundException\n     */\n    ColorStateList getColorStateList(int resId) throws Resources.NotFoundException;\n\n    /***\n     * get ColorStateList by resource id and name\n     * @param resId\n     * @param resName\n     * @return\n     * @throws Resources.NotFoundException\n     */\n    ColorStateList getColorStateList(int resId, String resName) throws Resources.NotFoundException;\n\n    /***\n     * get ColorStateList by resource id and name\n     * @param resId\n     * @param typeName\n     * @param resName\n     * @return\n     * @throws Resources.NotFoundException\n     */\n    ColorStateList getColorStateList(int resId, String typeName, String resName) throws Resources.NotFoundException;\n}\n"
  },
  {
    "path": "QSkinLoaderlib/src/main/java/org/qcode/qskinloader/ISkinActivity.java",
    "content": "package org.qcode.qskinloader;\n\n/**\n * 支持换肤的Activity应实现的接口\n *\n * A interface indicates that this activity supports skin change.\n *\n * qqliu\n * 2016/9/25.\n */\npublic interface ISkinActivity {\n\n    /***\n     * 是否需要立刻刷新皮肤；默认不立刻换肤\n     * tells whether refresh skin immediately, if return false, the activity will\n     * be refreshed after focus obtained.\n     *\n     * @return\n     */\n    boolean isSwitchSkinImmediately();\n\n    /***\n     * 确定是否支持换肤\n     *tells whether the activity support skin change,\n     * if return false, the activity will not refresh when skin change.\n     * NOTICE: SkinManager.getInstance().applySkin() will ignore the setting\n     *\n     * @return\n     */\n    boolean isSupportSkinChange();\n\n    /***\n     * 刷新皮肤；\n     * 此处刷新的是皮肤框架管理之外的界面\n     *\n     * when skin changes, this method will be called,\n     * to notify activity doing something beyond the framework's ability.\n     */\n    void handleSkinChange();\n}\n"
  },
  {
    "path": "QSkinLoaderlib/src/main/java/org/qcode/qskinloader/ISkinAttrHandler.java",
    "content": "package org.qcode.qskinloader;\n\nimport android.view.View;\n\nimport org.qcode.qskinloader.entity.SkinAttr;\n\n/**\n * 皮肤属性处理器的接口\n *\n * an interface indicates how to apply special skin attributes for a view.\n *\n * qqliu\n * 2016/9/24.\n */\npublic interface ISkinAttrHandler {\n\n    /***\n     * 将属性应用到View上\n     *\n     * apply skin attribute to view\n     *\n     * @param view\n     * @param skinAttr\n     * @param resourceManager\n     */\n    void apply(View view,\n               SkinAttr skinAttr,\n               IResourceManager resourceManager);\n}\n"
  },
  {
    "path": "QSkinLoaderlib/src/main/java/org/qcode/qskinloader/ISkinAttributeParser.java",
    "content": "package org.qcode.qskinloader;\n\nimport android.content.Context;\nimport android.util.AttributeSet;\nimport android.view.View;\n\n/**\n * 框架解析皮肤属性的解析帮助类\n *\n * the skin attribute parser defines\n * how to parse attributes when view is created.\n *\n * qqliu\n * 2016/11/8.\n */\n\npublic interface ISkinAttributeParser {\n    /***\n     * 是否支持换肤\n     *\n     * return the parse result whether the view supports skin-change\n     * @param name\n     * @param context\n     * @param attrs\n     * @return\n     */\n    boolean isSupportSkin(String name, Context context, AttributeSet attrs);\n\n    /***\n     * 解析View的皮肤属性\n     *\n     * parse skin attributes from view-creating process\n     *\n     * @param view\n     * @param name\n     * @param context\n     * @param attrs\n     */\n    void parseAttribute(View view, String name, Context context, AttributeSet attrs);\n}\n"
  },
  {
    "path": "QSkinLoaderlib/src/main/java/org/qcode/qskinloader/ISkinManager.java",
    "content": "package org.qcode.qskinloader;\n\nimport android.content.Context;\nimport android.view.View;\n\nimport org.qcode.qskinloader.base.observable.IObservable;\n\n/**\n * 皮肤框架管理类接口\n *\n * the skin manager interface, supporting skin operations.\n * qqliu\n * 2016/9/24.\n */\npublic interface ISkinManager extends IObservable<IActivitySkinEventHandler> {\n\n    /***\n     * 初始化皮肤框架\n     * initing the skin framework\n     * @param context\n     */\n    void init(Context context);\n\n    /***\n     * 恢复到默认皮肤\n     *\n     * restore to default skin\n     * @param defaultSkinIdentifier: default skin identifier, a skin identifier\n     *                             identifies a special skin.\n     * @param loadListener\n     */\n    void restoreDefault(String defaultSkinIdentifier, ILoadSkinListener loadListener);\n\n    /***\n     * 从指定位置加载一个APK皮肤包；APK皮肤包是另一个未安装的APK应用（只包含资源）.\n     * 通过APK皮肤包可以支持动态下载换肤等功能；\n     *\n     * load an apk resources package from file(indicated by skinPath);\n     * an apk resources is an apk application, which only contains resources.\n     * APK supports can be applied when dynamically downloading the skin resouces.\n     *\n     * @param skinPath skinPath is a file path,\n     *                 and is also used as the skin identifier.\n     * @param loadListener the load result listener\n     */\n    void loadAPKSkin(String skinPath, ILoadSkinListener loadListener);\n\n    /***\n     * 加载指定的皮肤包，皮肤包以skinIdentifier标识，\n     * 依靠resourceLoader加载，并通过loadListener告知皮肤切换结果。\n     * 由外部指定皮肤加载方式，目前支持APK加载(APKResourceLoader)、后缀方式加载(SuffixResourceLoader)等。\n     *\n     * load skin for views, the skin is identified by skinIdentifier,\n     * loaded by resourceLoader(currently supports APKResourceLoader/SuffixResourceLoader),\n     * and the load result is notified by loadListener.\n     * @param skinIdentifier the skin identifier\n     * @param resourceLoader the resource loader(currently supports APKResourceLoader/SuffixResourceLoader)\n     * @param loadListener the skin load result listener\n     */\n    void loadSkin(String skinIdentifier,\n                  IResourceLoader resourceLoader,\n                  ILoadSkinListener loadListener);\n\n    /***\n     * 对View应用当前的皮肤设置，applyChild 表示对View的子元素设置皮肤\n     *\n     * apply current skin for the view\n     *\n     * @param view\n     * @param applyChild: true indicates apply skin for view's children\n     */\n    void applySkin(View view, boolean applyChild);\n\n    /***\n     * 注册指定属性的处理器，可以通过此方法覆盖默认的属性处理器，也可以定义自定义属性的属性处理器\n     *\n     * register a skin attributes handler for attribute(named as attrName)\n     *\n     * @param attrName : the attribute name\n     * @param skinAttrHandler : the attribute handler\n     */\n    void registerSkinAttrHandler(String attrName, ISkinAttrHandler skinAttrHandler);\n\n    /***\n     * 移除指定属性的处理器\n     *\n     * remove a skin attribute handler for a attribute\n     * @param attrName\n     */\n    void unregisterSkinAttrHandler(String attrName);\n\n    /***\n     * 设置一个IResourceManager对象，\n     * 可用来替换默认的ResourceManager实现，\n     * 在属性处理器内收到替换的ResourceManager实现。\n     *\n     * set a IResourceManager object to framework to replace the default ResourceManager.\n     * @param resourceManager\n     */\n    void setResourceManager(IResourceManager resourceManager);\n\n    /***\n     * 获取框架内的资源管理器对象\n     *\n     * return the resource manager object used in framework.\n     * @return\n     */\n    IResourceManager getResourceManager();\n}\n"
  },
  {
    "path": "QSkinLoaderlib/src/main/java/org/qcode/qskinloader/ISkinViewHelper.java",
    "content": "package org.qcode.qskinloader;\n\nimport org.qcode.qskinloader.entity.DynamicAttr;\nimport org.qcode.qskinloader.entity.SkinAttr;\n\nimport java.util.List;\n\n/**\n * 皮肤框架中View相关皮肤属性的管理器接口抽象\n *\n * The abstract interface for registering skin attributes dynamically.\n * qqliu\n * 2016/10/8.\n */\n\npublic interface ISkinViewHelper {\n    /***\n     * 给指定View注册一个属性名称为attrName，属性值为resId的皮肤属性；\n     * 此View之前注册的属性会被覆盖；\n     *\n     * apply a skin attribute(named as attrName, value is resId) to View,\n     * the skin attributes previously registered is removed.\n     * @param attrName\n     * @param resId\n     * @return\n     */\n    ISkinViewHelper setViewAttrs(String attrName, int resId);\n\n    /***\n     * 给指定View注册多个皮肤属性；\n     * 此View之前注册的属性会被覆盖；\n     *\n     * apply skin attributes(\n     * named as attrName, value is resId, indicated in DynamicAttr) to View,\n     * the skin attributes previously registered is removed.\n     *\n     * @param dynamicAttrs\n     * @return\n     */\n    ISkinViewHelper setViewAttrs(DynamicAttr... dynamicAttrs);\n\n    /***\n     * 给指定View注册多个皮肤属性；\n     * 此View之前注册的属性会被覆盖；\n     *\n     * apply skin attributes(\n     * named as attrName, value is resId, indicated in SkinAttr) to View,\n     * the skin attributes previously registered is removed.\n     *\n     * @param skinAttrs\n     * @return\n     */\n    ISkinViewHelper setViewAttrs(SkinAttr... skinAttrs);\n\n    /***\n     * 给指定View注册多个皮肤属性；\n     * 此View之前注册的属性会被覆盖；\n     *\n     * apply skin attributes(\n     * named as attrName, value is resId, indicated in DynamicAttr) to View,\n     * the skin attributes previously registered is removed.\n     *\n     * @param dynamicAttrs\n     * @return\n     */\n    ISkinViewHelper setViewAttrs(List<DynamicAttr> dynamicAttrs);\n\n    /***\n     * 给指定View添加一个属性名称为attrName，属性值为resId的皮肤属性；\n     * 此View之前注册的属性不会被覆盖；\n     *\n     * add a skin attribute(named as attrName, value is resId) to View,\n     * the skin attributes previously registered is maintained.\n     * @param attrName\n     * @param resId\n     * @return\n     */\n    ISkinViewHelper addViewAttrs(String attrName, int resId);\n\n    /***\n     * 给指定View添加多个皮肤属性；\n     * 此View之前注册的属性不会被覆盖；\n     *\n     * add skin attributes(\n     * named as attrName, value is resId, indicated in DynamicAttr) to View,\n     * the skin attributes previously registered is maintained.\n     *\n     * @param dynamicAttrs\n     * @return\n     */\n    ISkinViewHelper addViewAttrs(DynamicAttr... dynamicAttrs);\n\n    /***\n     * 给指定View添加多个皮肤属性；\n     * 此View之前注册的属性不会被覆盖；\n     *\n     * add skin attributes(\n     * named as attrName, value is resId, indicated in SkinAttr) to View,\n     * the skin attributes previously registered is maintained.\n     *\n     * @param skinAttrs\n     * @return\n     */\n    ISkinViewHelper addViewAttrs(SkinAttr... skinAttrs);\n\n    /***\n     * 给指定View添加多个皮肤属性；\n     * 此View之前注册的属性不会被覆盖；\n     *\n     * add skin attributes(\n     * named as attrName, value is resId, indicated in DynamicAttr) to View,\n     * the skin attributes previously registered is maintained.\n     *\n     * @param dynamicAttrs\n     * @return\n     */\n    ISkinViewHelper addViewAttrs(List<DynamicAttr> dynamicAttrs);\n\n    /***\n     * 移除View内注册的皮肤属性\n     *\n     * remove all skin attributes for a view\n     * @param clearChild true表示同时移除View的子元素的皮肤属性；\n     *                   false只移除View的皮肤属性；\n     *                   true indicates also removing skin attributes for the view's children,\n     *                   false means only remove skin attributes for the view itself;\n     * @return\n     */\n    ISkinViewHelper cleanAttrs(boolean clearChild);\n\n    /***\n     * 对View应用当前的皮肤设置；\n     *\n     * apply current skin for the view\n     * @param applyChild true表示对View子元素也应用皮肤；\n     *                   false表示只对View应用皮肤；\n     *                   true indicates apply skin for the view's children,\n     *                   false otherwise;\n     */\n    void applySkin(boolean applyChild);\n}\n"
  },
  {
    "path": "QSkinLoaderlib/src/main/java/org/qcode/qskinloader/IViewCreateListener.java",
    "content": "package org.qcode.qskinloader;\n\nimport android.content.Context;\nimport android.util.AttributeSet;\nimport android.view.View;\n\n/**\n * 创建View的过程中的回调\n * qqliu\n * 2016/10/17.\n */\n\npublic interface IViewCreateListener {\n    /***\n     * 创建View之前的回调; invoked before view create,\n     * should be used to create view outside the framework if needed\n     * @param name\n     * @param context\n     * @param attrs\n     * @return\n     */\n    View beforeCreate(String name, Context context, AttributeSet attrs);\n\n    /***\n     * 创建View之后的回调; invoked after view create,\n     * should be used to parse view attributes outside the framework if needed\n     * @param view\n     * @param name\n     * @param context\n     * @param attrs\n     */\n    void afterCreated(View view, String name, Context context, AttributeSet attrs);\n}\n"
  },
  {
    "path": "QSkinLoaderlib/src/main/java/org/qcode/qskinloader/IWindowViewManager.java",
    "content": "package org.qcode.qskinloader;\n\nimport android.view.View;\n\nimport java.util.List;\n\n/**\n * 皮肤框架对直接加载在WindowManager上的View的管理器；\n * 包含：悬浮窗、popWindow、Dialog等持有的View；\n * 一般是将Activity的View树以外的View加入框架内管理；\n * 建议成对调用{@ref{addWindowView}和{@ref{removeWindowView}；\n *\n * The manager of Views\n * (added to WindowManager, such as PopWindow/Dialog/Float View...);\n * Basically, the views not add to Activity's View tree\n * should be added to IWindowViewManager for skin changing purpose.\n * addWindowView should be used with removeWindowView in pairs.\n * qqliu\n * 2016/10/8.\n */\n\npublic interface IWindowViewManager {\n    /***\n     * 在框架内增加View的引用，刷新皮肤时会刷新此View及其所有子元素；\n     * ，应与{@ref{removeWindowView}成对使用\n     *\n     * add a view in framework, so that we can refresh\n     * the view(and its children)'s skin immediately.\n     * should be used with {@ref{removeWindowView} in pairs.\n     * @param view\n     * @return\n     */\n    IWindowViewManager addWindowView(View view);\n\n    /***\n     * 从框架内移除View的引用；\n     * 应与{@ref{addWindowView}成对使用;\n     *\n     * remove a view from framework, see {@ref{addWindowView}\n     * @param view\n     * @return\n     */\n    IWindowViewManager removeWindowView(View view);\n\n    /***\n     * 清空框架内持有的所有View的引用；\n     * clear all views maintained in framework\n     * @return\n     */\n    IWindowViewManager clear();\n\n    /***\n     * 对框架内持有的所有View刷新皮肤；\n     *\n     * refresh skin for views maintained in framework\n     * @param applyChild 表示刷新是否同时刷新View的子元素，一般传入true;\n     *                   true means we also refresh the views' children,\n     *                   most time true is needed.\n     */\n    void applySkinForViews(boolean applyChild);\n\n    /***\n     * 获取注册到框架内维护的所有View\n     *\n     * return all the views maintained in the framework.\n     * @return\n     */\n    List<View> getWindowViewList();\n}\n"
  },
  {
    "path": "QSkinLoaderlib/src/main/java/org/qcode/qskinloader/SkinManager.java",
    "content": "package org.qcode.qskinloader;\n\nimport android.view.View;\n\nimport org.qcode.qskinloader.impl.ActivitySkinEventHandlerImpl;\nimport org.qcode.qskinloader.impl.SkinManagerImpl;\nimport org.qcode.qskinloader.impl.SkinViewHelperImpl;\nimport org.qcode.qskinloader.impl.WindowViewManager;\n\n/**\n * 皮肤框架对外接口;\n *\n * the base entrance class for the QSkinLoader library\n * qqliu\n * 2016/10/8.\n */\n\npublic class SkinManager {\n\n    /***\n     * 获取皮肤管理类实例;\n     *\n     * return an ISkinManager object to deal with skin manager events\n     * such as init/skin change/apply skin for view ...\n     * @return\n     */\n    public static ISkinManager getInstance() {\n        return SkinManagerImpl.getInstance();\n    }\n\n    /***\n     * 获取View的皮肤属性管理类，支持链式编程，可动态操作View的皮肤属性;\n     *\n     * return an ISinViewHelper to add/remove skin attrs dynamically;\n     * ISkinViewHelper supports the chain programming style;\n     * @param view\n     * @return\n     */\n    public static ISkinViewHelper with(View view) {\n        return new SkinViewHelperImpl(view);\n    }\n\n    /***\n     * 获取Window View的管理类；\n     * 框架只能自动支持刷新Activity的ContentView，\n     * 对于PopupWindow/对话框/悬浮窗等View，\n     * 只能通过此方法注册到框架内来保证换肤效果；\n     *\n     * return an IWindowViewManager to add/remove view to the framework;\n     * the framework only supports apply skin for Activity's\n     * content view(by findViewById(android.R.id.content));\n     * so that other views(PopupWindow/Dialog/View directly added to WindowManager)\n     * should be add to framework for skin changing.\n     * @return\n     */\n    public static IWindowViewManager getWindowViewManager() {\n        return WindowViewManager.getInstance();\n    }\n\n    /***\n     * 创建一个新的Activity的皮肤事件处理器；\n     * IActivitySkinEventHandler用于代理完成\n     * Activity内各生命周期与皮肤相关的逻辑；\n     *\n     * return a new IActivitySkinEventHandler object for Activity;\n     * IActivitySkinEventHandler handles event for the skin framework,\n     * and should be notified when activity life state change(onCreate/onResume/onPause...)\n     * @return\n     */\n    public static IActivitySkinEventHandler newActivitySkinEventHandler() {\n        return new ActivitySkinEventHandlerImpl();\n    }\n}\n"
  },
  {
    "path": "QSkinLoaderlib/src/main/java/org/qcode/qskinloader/attrhandler/BackgroundAttrHandler.java",
    "content": "package org.qcode.qskinloader.attrhandler;\n\nimport android.graphics.drawable.Drawable;\nimport android.view.View;\n\nimport org.qcode.qskinloader.ISkinAttrHandler;\nimport org.qcode.qskinloader.entity.SkinAttr;\nimport org.qcode.qskinloader.entity.SkinAttrName;\nimport org.qcode.qskinloader.IResourceManager;\n\n/***\n * 背景属性的换肤支持（android:background）\n */\nclass BackgroundAttrHandler implements ISkinAttrHandler {\n\n    @Override\n    public void apply(View view, SkinAttr skinAttr, IResourceManager resourceManager) {\n        if(null == view\n                || null == skinAttr\n                || !(SkinAttrName.BACKGROUND.equals(skinAttr.mAttrName))) {\n            return;\n        }\n\n        Drawable drawable = SkinAttrUtils.getDrawable(\n                resourceManager, skinAttr.mAttrValueRefId,\n                skinAttr.mAttrValueTypeName, skinAttr.mAttrValueRefName);\n\n        if(null != drawable) {\n            view.setBackgroundDrawable(drawable);\n        }\n    }\n}\n"
  },
  {
    "path": "QSkinLoaderlib/src/main/java/org/qcode/qskinloader/attrhandler/DividerAttrHandler.java",
    "content": "package org.qcode.qskinloader.attrhandler;\n\nimport android.graphics.drawable.Drawable;\nimport android.view.View;\nimport android.widget.ListView;\n\nimport org.qcode.qskinloader.ISkinAttrHandler;\nimport org.qcode.qskinloader.entity.SkinAttr;\nimport org.qcode.qskinloader.entity.SkinAttrName;\nimport org.qcode.qskinloader.IResourceManager;\n\n/***\n * ListView divider属性的换肤支持（android:divider）\n */\nclass DividerAttrHandler implements ISkinAttrHandler {\n\n    @Override\n    public void apply(View view, SkinAttr skinAttr, IResourceManager resourceManager) {\n        if (null == view\n                || null == skinAttr\n                || !(SkinAttrName.DIVIDER.equals(skinAttr.mAttrName))) {\n            return;\n        }\n\n        if (!(view instanceof ListView)) {\n            return;\n        }\n\n        ListView tv = (ListView) view;\n        Drawable drawable = SkinAttrUtils.getDrawable(\n                resourceManager, skinAttr.mAttrValueRefId,\n                skinAttr.mAttrValueTypeName, skinAttr.mAttrValueRefName);\n\n        if (null != drawable) {\n            tv.setDivider(drawable);\n        }\n    }\n}\n"
  },
  {
    "path": "QSkinLoaderlib/src/main/java/org/qcode/qskinloader/attrhandler/DrawableLeftAttrHandler.java",
    "content": "package org.qcode.qskinloader.attrhandler;\n\nimport android.graphics.drawable.Drawable;\nimport android.view.View;\nimport android.widget.TextView;\n\nimport org.qcode.qskinloader.ISkinAttrHandler;\nimport org.qcode.qskinloader.entity.SkinAttr;\nimport org.qcode.qskinloader.entity.SkinAttrName;\nimport org.qcode.qskinloader.IResourceManager;\n\n/**\n * TextView的drawableLeft属性处理\n * qqliu\n * 2016/9/27.\n */\nclass DrawableLeftAttrHandler implements ISkinAttrHandler {\n\n    @Override\n    public void apply(View view, SkinAttr skinAttr, IResourceManager resourceManager) {\n        if(null == view\n                || null == skinAttr\n                || !(SkinAttrName.DRAWABLE_LEFT.equals(skinAttr.mAttrName))) {\n            return;\n        }\n\n        if (!(view instanceof TextView)) {\n            return;\n        }\n\n        Drawable drawable = SkinAttrUtils.getDrawable(resourceManager,\n                skinAttr.mAttrValueRefId,\n                skinAttr.mAttrValueTypeName,\n                skinAttr.mAttrValueRefName);\n\n        if(null != drawable) {\n            ((TextView)view).setCompoundDrawablesWithIntrinsicBounds(\n                    drawable, null, null, null);\n        }\n    }\n}\n"
  },
  {
    "path": "QSkinLoaderlib/src/main/java/org/qcode/qskinloader/attrhandler/ListSelectorAttrHandler.java",
    "content": "package org.qcode.qskinloader.attrhandler;\n\n/***\n * ListView selector属性的换肤支持（android:listSelector）\n */\n\nimport android.graphics.drawable.Drawable;\nimport android.view.View;\nimport android.widget.AbsListView;\n\nimport org.qcode.qskinloader.ISkinAttrHandler;\nimport org.qcode.qskinloader.entity.SkinAttr;\nimport org.qcode.qskinloader.entity.SkinAttrName;\nimport org.qcode.qskinloader.IResourceManager;\n\nclass ListSelectorAttrHandler implements ISkinAttrHandler {\n\n    @Override\n    public void apply(View view, SkinAttr skinAttr, IResourceManager resourceManager) {\n        if (null == view\n                || null == skinAttr\n                || !(SkinAttrName.LIST_SELECTOR.equals(skinAttr.mAttrName))) {\n            return;\n        }\n\n        if (!(view instanceof AbsListView)) {\n            return;\n        }\n\n        Drawable drawable = SkinAttrUtils.getDrawable(\n                resourceManager, skinAttr.mAttrValueRefId,\n                skinAttr.mAttrValueTypeName, skinAttr.mAttrValueRefName);\n\n        if (null != drawable) {\n            ((AbsListView) view).setSelector(drawable);\n        }\n    }\n}\n"
  },
  {
    "path": "QSkinLoaderlib/src/main/java/org/qcode/qskinloader/attrhandler/RecyclerViewClearSubAttrHandler.java",
    "content": "package org.qcode.qskinloader.attrhandler;\n\nimport android.support.v7.widget.RecyclerView;\nimport android.view.View;\n\nimport org.qcode.qskinloader.IResourceManager;\nimport org.qcode.qskinloader.ISkinAttrHandler;\nimport org.qcode.qskinloader.base.utils.Logging;\nimport org.qcode.qskinloader.entity.SkinAttr;\nimport org.qcode.qskinloader.entity.SkinAttrName;\n\nimport java.lang.reflect.Field;\nimport java.lang.reflect.Method;\n\n/**\n * 清除RecyclerView的缓存View池，保证不出现夜间模式与白天模式共存的问题\n * qqliu\n * 2016/9/27.\n */\nclass RecyclerViewClearSubAttrHandler implements ISkinAttrHandler {\n    private static final String TAG = \"RecyclerViewClearSubAttr\";\n\n    @Override\n    public void apply(View view, SkinAttr skinAttr, IResourceManager resourceManager) {\n        if(null == view\n                || null == skinAttr\n                || !(SkinAttrName.CLEAR_RECYCLER_VIEW.equals(skinAttr.mAttrName))) {\n            return;\n        }\n\n        if(!(view instanceof RecyclerView)) {\n            return;\n        }\n\n        refreshRecyclerView((RecyclerView) view);\n    }\n\n    private void refreshRecyclerView(RecyclerView recyclerView) {\n        Logging.d(TAG, \"refreshRecyclerView()| clear recycler view\");\n        Class<RecyclerView> recyclerViewClass = RecyclerView.class;\n        try {\n            Field declaredField = recyclerViewClass.getDeclaredField(\"mRecycler\" );\n            declaredField.setAccessible(true);\n            Method declaredMethod = Class.forName(RecyclerView.Recycler. class.getName()).getDeclaredMethod(\"clear\", (Class<?>[]) new Class[0]);\n            declaredMethod.setAccessible(true);\n            declaredMethod.invoke(declaredField.get(recyclerView), new Object[0]);\n            RecyclerView.RecycledViewPool recycledViewPool = recyclerView.getRecycledViewPool();\n            recycledViewPool.clear();\n\n        } catch (Exception ex) {\n            Logging.d(TAG, \"refreshRecyclerView()| error happened\", ex);\n        }\n    }\n}\n"
  },
  {
    "path": "QSkinLoaderlib/src/main/java/org/qcode/qskinloader/attrhandler/ShadowAttrHandler.java",
    "content": "package org.qcode.qskinloader.attrhandler;\n\nimport android.graphics.Color;\nimport android.graphics.PorterDuff;\nimport android.view.View;\nimport android.widget.ImageView;\n\nimport org.qcode.qskinloader.ISkinAttrHandler;\nimport org.qcode.qskinloader.entity.SkinAttr;\nimport org.qcode.qskinloader.entity.SkinAttrName;\nimport org.qcode.qskinloader.entity.SkinConstant;\nimport org.qcode.qskinloader.IResourceManager;\nimport org.qcode.qskinloader.view.ShadowImageView;\n\n/**\n * 蒙层阴影属性，仅支持ImageView，且蒙层只能是int型颜色\n * qqliu\n * 2016/9/25.\n */\nclass ShadowAttrHandler implements ISkinAttrHandler {\n\n    @Override\n    public void apply(View view, SkinAttr skinAttr, IResourceManager resourceManager) {\n        if (null == view\n                || null == skinAttr\n                || !(SkinAttrName.DRAW_SHADOW.equals(skinAttr.mAttrName))) {\n            return;\n        }\n\n        if (!(view instanceof ImageView)) {\n            return;\n        }\n\n        if (resourceManager.isDefault()) {\n            if (view instanceof ShadowImageView) {\n                ShadowImageView imageView = (ShadowImageView) view;\n                imageView.setShadowColor(Color.WHITE);\n            } else {\n                ((ImageView) view).setColorFilter(Color.WHITE, PorterDuff.Mode.MULTIPLY);\n            }\n        } else {\n            if (SkinConstant.RES_TYPE_NAME_COLOR.equals(skinAttr.mAttrValueTypeName)) {\n                int bgColor = resourceManager.getColor(\n                        skinAttr.mAttrValueRefId, skinAttr.mAttrValueRefName);\n                if (view instanceof ShadowImageView) {\n                    ShadowImageView imageView = (ShadowImageView) view;\n                    imageView.setShadowColor(bgColor);\n                } else {\n                    ((ImageView) view).setColorFilter(bgColor, PorterDuff.Mode.MULTIPLY);\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "QSkinLoaderlib/src/main/java/org/qcode/qskinloader/attrhandler/SkinAttrFactory.java",
    "content": "package org.qcode.qskinloader.attrhandler;\n\n\nimport android.text.TextUtils;\n\nimport org.qcode.qskinloader.ISkinAttrHandler;\nimport org.qcode.qskinloader.base.utils.StringUtils;\nimport org.qcode.qskinloader.entity.DynamicAttr;\nimport org.qcode.qskinloader.entity.SkinAttr;\nimport org.qcode.qskinloader.entity.SkinAttrName;\n\nimport java.util.HashMap;\nimport java.util.Map;\n\n/***\n * 获取支持的属性处理器工厂\n */\npublic class SkinAttrFactory {\n\n    //存放支持的换肤属性和对应的处理器\n    private static Map<String, ISkinAttrHandler> mSupportAttrHandler = new HashMap<String, ISkinAttrHandler>();\n\n    //静态注册支持的属性和处理器\n    static {\n        registerSkinAttrHandler(SkinAttrName.BACKGROUND, new BackgroundAttrHandler());\n        registerSkinAttrHandler(SkinAttrName.SRC, new SrcAttrHandler());\n        registerSkinAttrHandler(SkinAttrName.TEXT_COLOR, new TextColorAttrHandler());\n        registerSkinAttrHandler(SkinAttrName.TEXT_COLOR_HINT, new TextColorHintAttrHandler());\n        registerSkinAttrHandler(SkinAttrName.LIST_SELECTOR, new ListSelectorAttrHandler());\n        registerSkinAttrHandler(SkinAttrName.DIVIDER, new DividerAttrHandler());\n        registerSkinAttrHandler(SkinAttrName.DRAWABLE_LEFT, new DrawableLeftAttrHandler());\n        registerSkinAttrHandler(SkinAttrName.DRAW_SHADOW, new ShadowAttrHandler());\n        registerSkinAttrHandler(SkinAttrName.CLEAR_RECYCLER_VIEW, new RecyclerViewClearSubAttrHandler());\n    }\n\n    /***\n     * 创建一个新的皮肤对象\n     * @param attrName\n     * @param attrValueRefId\n     * @param attrValueRefName\n     * @param typeName\n     * @return\n     */\n    public static SkinAttr newSkinAttr(\n            String attrName, int attrValueRefId,\n            String attrValueRefName, String typeName) {\n        if (StringUtils.isEmpty(attrName)) {\n            return null;\n        }\n\n        SkinAttr skinAttr = new SkinAttr();\n\n        skinAttr.mAttrName = attrName;\n        skinAttr.mAttrValueRefId = attrValueRefId;\n        skinAttr.mAttrValueRefName = attrValueRefName;\n        skinAttr.mAttrValueTypeName = typeName;\n        return skinAttr;\n    }\n\n    /***\n     * 基于属性名称生成SkinAttr\n     * @param attrName\n     * @return\n     */\n    public static SkinAttr newSkinAttr(String attrName) {\n        if (StringUtils.isEmpty(attrName)) {\n            return null;\n        }\n\n        SkinAttr skinAttr = new SkinAttr();\n\n        skinAttr.mAttrName = attrName;\n        return skinAttr;\n    }\n\n    /***\n     * 获取特定属性的换肤处理器\n     *\n     * @param attrName\n     * @return\n     */\n    public static ISkinAttrHandler getSkinAttrHandler(String attrName) {\n        return mSupportAttrHandler.get(attrName);\n    }\n\n    /***\n     * 是否支持某属性换肤\n     *\n     * @param attrName\n     * @return\n     */\n    public static boolean isSupportedAttr(String attrName) {\n        return null != getSkinAttrHandler(attrName);\n    }\n\n    /****\n     * 注册对某个属性的换肤支持\n     *\n     * @param attrName\n     */\n    public static void registerSkinAttrHandler(String attrName, ISkinAttrHandler skinAttrHandler) {\n        if (TextUtils.isEmpty(attrName) || null == skinAttrHandler) {\n            return;\n        }\n\n        mSupportAttrHandler.put(attrName, skinAttrHandler);\n    }\n\n    /***\n     * 移除对某个属性的换肤支持\n     *\n     * @param attrName\n     */\n    public static void removeSkinAttrHandler(String attrName) {\n        if (TextUtils.isEmpty(attrName)) {\n            return;\n        }\n        mSupportAttrHandler.remove(attrName);\n    }\n}\n"
  },
  {
    "path": "QSkinLoaderlib/src/main/java/org/qcode/qskinloader/attrhandler/SkinAttrUtils.java",
    "content": "package org.qcode.qskinloader.attrhandler;\n\nimport android.graphics.drawable.ColorDrawable;\nimport android.graphics.drawable.Drawable;\nimport android.view.View;\n\nimport org.qcode.qskinloader.IResourceManager;\nimport org.qcode.qskinloader.ISkinAttrHandler;\nimport org.qcode.qskinloader.base.utils.CollectionUtils;\nimport org.qcode.qskinloader.entity.SkinAttr;\nimport org.qcode.qskinloader.entity.SkinAttrSet;\nimport org.qcode.qskinloader.entity.SkinConstant;\n\nimport java.util.List;\n\n/**\n * 皮肤属性的辅助帮助类\n * qqliu\n * 2016/9/25.\n */\npublic class SkinAttrUtils {\n\n    /***\n     * 获取指定资源的drawable，支持的resId为int型颜色和drawable\n     * @param resourceManager\n     * @param resId\n     * @param resTypeName\n     * @param resName\n     * @return\n     */\n    public static Drawable getDrawable(IResourceManager resourceManager,\n                                       int resId,\n                                       String resTypeName,\n                                       String resName) {\n        if (SkinConstant.RES_TYPE_NAME_COLOR.equals(resTypeName)) {\n            int bgColor = resourceManager.getColor(\n                    resId, resName);\n            return new ColorDrawable(bgColor);\n\n        } else if (SkinConstant.RES_TYPE_NAME_DRAWABLE.equals(resTypeName)) {\n            Drawable drawable = resourceManager.getDrawable(\n                    resId, resName);\n            return drawable;\n        } else if (SkinConstant.RES_TYPE_NAME_MIPMAP.equals(resTypeName)) {\n            Drawable drawable = resourceManager.getDrawable(\n                    resId, resName);\n            return drawable;\n        }\n\n        return null;\n    }\n\n    /***\n     * 对View应用指定的属性集合\n     * @param view\n     * @param skinAttrSet\n     * @param resourceManager\n     */\n    public static void applySkinAttrs(View view, SkinAttrSet skinAttrSet, IResourceManager resourceManager) {\n        if(null == view || null == skinAttrSet) {\n            return;\n        }\n\n        List<SkinAttr> attrArrayList = skinAttrSet.getAttrList();\n        if (CollectionUtils.isEmpty(attrArrayList)) {\n            return;\n        }\n\n        for (SkinAttr attr : attrArrayList) {\n            ISkinAttrHandler attrHandler = SkinAttrFactory.getSkinAttrHandler(attr.mAttrName);\n            if(null != attrHandler) {\n                attrHandler.apply(view, attr, resourceManager);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "QSkinLoaderlib/src/main/java/org/qcode/qskinloader/attrhandler/SrcAttrHandler.java",
    "content": "package org.qcode.qskinloader.attrhandler;\n\nimport android.graphics.drawable.AnimationDrawable;\nimport android.graphics.drawable.Drawable;\nimport android.view.View;\nimport android.widget.ImageView;\n\nimport org.qcode.qskinloader.IResourceManager;\nimport org.qcode.qskinloader.ISkinAttrHandler;\nimport org.qcode.qskinloader.entity.SkinAttr;\nimport org.qcode.qskinloader.entity.SkinAttrName;\n\n/***\n * src属性的换肤支持（android:src）\n */\nclass SrcAttrHandler implements ISkinAttrHandler {\n\n    @Override\n    public void apply(View view, SkinAttr skinAttr, IResourceManager resourceManager) {\n        if (null == view\n                || null == skinAttr\n                || !(SkinAttrName.SRC.equals(skinAttr.mAttrName))) {\n            return;\n        }\n\n        if (!(view instanceof ImageView)) {\n            return;\n        }\n\n        boolean isAnimationDrawable = false;\n        boolean isRunning = false;\n        Drawable originalDrawable = ((ImageView) view).getDrawable();\n        if (originalDrawable instanceof AnimationDrawable) {\n            AnimationDrawable animationDrawable = (AnimationDrawable) originalDrawable;\n            isAnimationDrawable = true;\n            isRunning = animationDrawable.isRunning();\n        }\n\n        Drawable drawable = SkinAttrUtils.getDrawable(\n                resourceManager, skinAttr.mAttrValueRefId,\n                skinAttr.mAttrValueTypeName, skinAttr.mAttrValueRefName);\n\n        if (null != drawable) {\n            ((ImageView) view).setImageDrawable(drawable);\n\n            if (isAnimationDrawable && drawable instanceof AnimationDrawable) {\n                AnimationDrawable animationDrawable = ((AnimationDrawable) drawable);\n                if (isRunning) {\n                    animationDrawable.start();\n                } else {\n                    animationDrawable.stop();\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "QSkinLoaderlib/src/main/java/org/qcode/qskinloader/attrhandler/TextColorAttrHandler.java",
    "content": "package org.qcode.qskinloader.attrhandler;\n\nimport android.content.res.ColorStateList;\nimport android.view.View;\nimport android.widget.TextView;\n\nimport org.qcode.qskinloader.IResourceManager;\nimport org.qcode.qskinloader.ISkinAttrHandler;\nimport org.qcode.qskinloader.entity.SkinAttr;\nimport org.qcode.qskinloader.entity.SkinAttrName;\nimport org.qcode.qskinloader.entity.SkinConstant;\n\n/***\n * 文字颜色属性的换肤支持（android:textColor）\n */\nclass TextColorAttrHandler implements ISkinAttrHandler {\n\n    @Override\n    public void apply(View view, SkinAttr skinAttr, IResourceManager resourceManager) {\n        if (null == view\n                || null == skinAttr\n                || !(SkinAttrName.TEXT_COLOR.equals(skinAttr.mAttrName))) {\n            return;\n        }\n\n        if (!(view instanceof TextView)) {\n            return;\n        }\n\n        TextView tv = (TextView) view;\n        if (SkinConstant.RES_TYPE_NAME_COLOR.equals(skinAttr.mAttrValueTypeName)\n                || SkinConstant.RES_TYPE_NAME_DRAWABLE.equals(skinAttr.mAttrValueTypeName)) {\n            //按照ColorStateList引用来解析；\n            //context.getResources().getColor()方法可以取纯颜色，也可以取ColorStateList引用内的颜色，\n            //如果取的是ColorStateList，则取其中默认颜色；\n            //同时，context.getResources().getColorStateList()方法也可以取纯颜色生成一个ColorStateList\n            ColorStateList textColor = resourceManager.getColorStateList(\n                    skinAttr.mAttrValueRefId, skinAttr.mAttrValueTypeName, skinAttr.mAttrValueRefName);\n            tv.setTextColor(textColor);\n        }\n\n    }\n}\n"
  },
  {
    "path": "QSkinLoaderlib/src/main/java/org/qcode/qskinloader/attrhandler/TextColorHintAttrHandler.java",
    "content": "package org.qcode.qskinloader.attrhandler;\n\nimport android.content.res.ColorStateList;\nimport android.view.View;\nimport android.widget.TextView;\n\nimport org.qcode.qskinloader.IResourceManager;\nimport org.qcode.qskinloader.ISkinAttrHandler;\nimport org.qcode.qskinloader.entity.SkinAttr;\nimport org.qcode.qskinloader.entity.SkinAttrName;\nimport org.qcode.qskinloader.entity.SkinConstant;\n\n/***\n * 文字提示颜色属性的换肤支持（android:textColorHint）\n */\nclass TextColorHintAttrHandler implements ISkinAttrHandler {\n\n    @Override\n    public void apply(View view, SkinAttr skinAttr, IResourceManager resourceManager) {\n        if (null == view\n                || null == skinAttr\n                || !(SkinAttrName.TEXT_COLOR_HINT.equals(skinAttr.mAttrName))) {\n            return;\n        }\n\n        if (!(view instanceof TextView)) {\n            return;\n        }\n\n        TextView tv = (TextView) view;\n        if (SkinConstant.RES_TYPE_NAME_COLOR.equals(skinAttr.mAttrValueTypeName)) {\n            //按照ColorStateList引用来解析；\n            //context.getResources().getColor()方法可以取纯颜色，也可以取ColorStateList引用内的颜色，\n            //如果取的是ColorStateList，则取其中默认颜色；\n            //同时，context.getResources().getColorStateList()方法也可以取纯颜色生成一个ColorStateList\n            ColorStateList textHintColor = resourceManager.getColorStateList(\n                    skinAttr.mAttrValueRefId, skinAttr.mAttrValueRefName);\n            tv.setHintTextColor(textHintColor);\n        }\n    }\n}\n"
  },
  {
    "path": "QSkinLoaderlib/src/main/java/org/qcode/qskinloader/base/observable/INotifyUpdate.java",
    "content": "package org.qcode.qskinloader.base.observable;\n\n/**\n * 通知观察者更新\n * qqliu\n * 2016/9/19.\n */\npublic interface INotifyUpdate<T> {\n\n    /***\n     * 通知观察者发生了标识为identifier的事件，事件参数是params\n     * @param callback 观察者\n     * @param identifier 事件标识\n     * @param params 事件参数\n     * @return 返回true截断事件传播，false继续事件传播\n     */\n    boolean notifyEvent(T callback, String identifier, Object... params);\n}\n"
  },
  {
    "path": "QSkinLoaderlib/src/main/java/org/qcode/qskinloader/base/observable/IObservable.java",
    "content": "package org.qcode.qskinloader.base.observable;\n\n/**\n * 可观察对象的抽象接口，T为观察者\n * qqliu\n * 2016/9/19.\n */\npublic interface IObservable<T> {\n    /***\n     * 增加新的观察者\n     *\n     * @param observer\n     */\n    void addObserver(T observer);\n\n    /***\n     * 删除观察者\n     *\n     * @param observer\n     */\n    void removeObserver(T observer);\n\n    /***\n     * 告知观察者发生了变化\n     * @param callback\n     */\n    void notifyUpdate(INotifyUpdate<T> callback, String identifier, Object... params);\n}\n"
  },
  {
    "path": "QSkinLoaderlib/src/main/java/org/qcode/qskinloader/base/observable/Observable.java",
    "content": "package org.qcode.qskinloader.base.observable;\n\nimport java.util.ArrayList;\n\n/**\n * 观察者通用逻辑\n * qqliu\n * 2016/9/19.\n */\npublic class Observable<T> implements IObservable<T> {\n\n    private ArrayList<T> mObservers;\n\n    @Override\n    public void addObserver(T observer) {\n        if (mObservers == null) {\n            mObservers = new ArrayList<T>();\n        }\n\n        if (!mObservers.contains(observer)) {\n            mObservers.add(observer);\n        }\n    }\n\n    @Override\n    public void removeObserver(T observer) {\n        if (mObservers == null) {\n            return;\n        }\n\n        if (mObservers.contains(observer)) {\n            mObservers.remove(observer);\n        }\n    }\n\n    @Override\n    public void notifyUpdate(INotifyUpdate<T> listener, String identifier, Object... params) {\n        if (mObservers == null || null == listener) {\n            return;\n        }\n\n        ArrayList<T> tmpListeners\n                = (ArrayList<T>) mObservers.clone();\n        for (T observer : tmpListeners) {\n            listener.notifyEvent(observer, identifier, params);\n        }\n    }\n}\n"
  },
  {
    "path": "QSkinLoaderlib/src/main/java/org/qcode/qskinloader/base/utils/CollectionUtils.java",
    "content": "package org.qcode.qskinloader.base.utils;\n\nimport java.util.Collection;\n\n/**\n * qqliu\n * 2016/9/25.\n */\npublic class CollectionUtils {\n    public static boolean isEmpty(Collection<?> collection){\n        return null == collection || collection.size() <= 0;\n    }\n\n    public static <T> boolean isEmpty(T... array){\n        return null == array || array.length <= 0;\n    }\n}\n"
  },
  {
    "path": "QSkinLoaderlib/src/main/java/org/qcode/qskinloader/base/utils/HashMapCache.java",
    "content": "package org.qcode.qskinloader.base.utils;\n\nimport java.lang.ref.WeakReference;\nimport java.util.HashMap;\n\n/**\n * 维护一个缓存Map，包括强引用和弱引用两种维护方式；\n * 两种方式中key都是强引用;\n * qqliu\n * 2016/9/19.\n */\npublic class HashMapCache<K, V> {\n    private HashMap<K, V> mCacheMap = null;\n    private HashMap<K, WeakReference<V>> mWeakCacheMap = null;\n\n    /***\n     * @param isStrongReference true 强引用，false弱引用\n     */\n    public HashMapCache(boolean isStrongReference) {\n        if (isStrongReference) {\n            mCacheMap = new HashMap<K, V>();\n        } else {\n            mWeakCacheMap = new HashMap<K, WeakReference<V>>();\n        }\n    }\n\n    public V getCache(K key) {\n        if(null == key) {\n            return null;\n        }\n\n        if(null != mCacheMap) {\n            return mCacheMap.get(key);\n        } else {\n            WeakReference<V> refValue = mWeakCacheMap.get(key);\n            if(null != refValue) {\n                return refValue.get();\n            }\n            return null;\n        }\n    }\n\n    public void addCache(K key, V value) {\n        if(null == key) {\n            return;\n        }\n\n        if(null != mCacheMap) {\n            mCacheMap.put(key, value);\n        } else {\n            WeakReference<V> refValue = new WeakReference<V>(value);\n            mWeakCacheMap.put(key, refValue);\n        }\n    }\n}\n"
  },
  {
    "path": "QSkinLoaderlib/src/main/java/org/qcode/qskinloader/base/utils/Logging.java",
    "content": "package org.qcode.qskinloader.base.utils;\n\nimport android.util.Log;\n\npublic class Logging {\n\n\tprotected static boolean mLoggingEnabled = true;\n\n\tprivate static final String PRE_TAG = \"SkinLoader_\";\n\n\tpublic static void setDebugLogging(boolean enabled) {\n\t\tmLoggingEnabled = enabled;\n\t}\n\t\n\tpublic static boolean isDebugLogging() {\n\t    return mLoggingEnabled;\n\t}\n\n\tpublic static int v(String tag, String msg) {\n\t\tint result = 0;\n\t\tif (mLoggingEnabled) {\n\t\t\tresult = Log.v(PRE_TAG + tag, msg);\n\t\t}\n\t\treturn result;\n\t}\n\n\tpublic static int v(String tag, String msg, Throwable tr) {\n\t\tint result = 0;\n\t\tif (mLoggingEnabled) {\n\t\t\tresult = Log.v(PRE_TAG + tag, msg, tr);\n\t\t}\n\t\treturn result;\n\t}\n\n\tpublic static int d(String tag, String msg) {\n\t\tint result = 0;\n\t\tif (mLoggingEnabled) {\n\t\t\tresult = Log.d(PRE_TAG + tag, msg);\n\t\t}\n\t\treturn result;\n\t}\n\n\tpublic static int d(String tag, String msg, Throwable tr) {\n\t\tint result = 0;\n\t\tif (mLoggingEnabled) {\n\t\t\tresult = Log.d(PRE_TAG + tag, msg, tr);\n\t\t}\n\t\treturn result;\n\t}\n\n\tpublic static int i(String tag, String msg) {\n\t\tint result = 0;\n\t\tif (mLoggingEnabled) {\n\t\t\tresult = Log.i(PRE_TAG + tag, msg);\n\t\t}\n\t\treturn result;\n\t}\n\n\tpublic static int i(String tag, String msg, Throwable tr) {\n\t\tint result = 0;\n\t\tif (mLoggingEnabled) {\n\t\t\tresult = Log.i(PRE_TAG + tag, msg, tr);\n\t\t}\n\t\treturn result;\n\t}\n\n\tpublic static int w(String tag, String msg) {\n\t\tint result = 0;\n\t\tif (mLoggingEnabled) {\n\t\t\tresult = Log.w(PRE_TAG + tag, msg);\n\t\t}\n\t\treturn result;\n\t}\n\n\tpublic static int w(String tag, String msg, Throwable tr) {\n\t\tint result = 0;\n\t\tif (mLoggingEnabled) {\n\t\t\tresult = Log.w(PRE_TAG + tag, msg, tr);\n\t\t}\n\t\treturn result;\n\t}\n\n\tpublic static int w(String tag, Throwable tr) {\n\t\tint result = 0;\n\t\tif (mLoggingEnabled) {\n\t\t\tresult = Log.w(PRE_TAG + tag, tr);\n\t\t}\n\t\treturn result;\n\t}\n\n\tpublic static int e(String tag, String msg) {\n\t\tint result = 0;\n\t\tif (mLoggingEnabled) {\n\t\t\tresult = Log.e(PRE_TAG + tag, msg);\n\t\t}\n\t\treturn result;\n\t}\n\n\tpublic static int e(String tag, String msg, Throwable tr) {\n\t\tint result = 0;\n\t\tif (mLoggingEnabled) {\n\t\t\tresult = Log.e(PRE_TAG + tag, msg, tr);\n\t\t}\n\t\treturn result;\n\t}\n\t\n}\n"
  },
  {
    "path": "QSkinLoaderlib/src/main/java/org/qcode/qskinloader/base/utils/ReflectUtils.java",
    "content": "package org.qcode.qskinloader.base.utils;\n\nimport java.lang.reflect.Field;\n\n/***\n * 反射帮助类\n * created at 2017/12/31\n */\npublic class ReflectUtils {\n\n    private static final String TAG = \"ReflectUtils\";\n\n    private static Field getDeclaredField(Object object, String fieldName) throws NoSuchFieldException {\n        for (Class clz = object.getClass(); Object.class != clz; clz = clz.getSuperclass()) {\n            try {\n                return clz.getDeclaredField(fieldName);\n            } catch (Exception ex) {\n                Logging.d(TAG, \"getDeclaredField()| field \" + fieldName + \" is not in class: \" + clz.getSimpleName());\n            }\n        }\n\n        throw new NoSuchFieldException(\"field \" + fieldName + \" NOT found\");\n    }\n\n    /***\n     * 获取指定field的value\n     *\n     * @param object\n     * @param fieldName\n     * @param <T>\n     * @return\n     */\n    public static <T> T getFieldValue(Object object, String fieldName)\n            throws NoSuchFieldException, IllegalAccessException {\n        if (null == object || StringUtils.isEmpty(fieldName)) {\n            return null;\n        }\n\n        Field field = getDeclaredField(object, fieldName);\n        field.setAccessible(true);\n        Object value = field.get(object);\n        return (T) value;\n    }\n\n    /***\n     * 获取指定field的value，无异常\n     *\n     * @param obj\n     * @param fieldName\n     * @param <T>\n     * @return\n     */\n    public static <T> T getFieldValueOpt(Object obj, String fieldName) {\n        try {\n            return getFieldValue(obj, fieldName);\n        } catch (Exception ex) {\n            Logging.d(TAG, \"getFieldValueOpt()| error happened\", ex);\n            return null;\n        }\n    }\n\n    /***\n     * 设置field的值\n     *\n     * @param object\n     * @param fieldName\n     * @param value\n     * @throws NoSuchFieldException\n     * @throws IllegalAccessException\n     */\n    public static void setFieldValue(Object object, String fieldName, Object value)\n            throws NoSuchFieldException, IllegalAccessException {\n        if (null == object || StringUtils.isEmpty(fieldName)) {\n            return;\n        }\n\n        Field field = getDeclaredField(object, fieldName);\n        field.setAccessible(true);\n        field.set(object, value);\n    }\n\n    /***\n     * 设置field的值，无异常\n     *\n     * @param object\n     * @param fieldName\n     * @param value\n     */\n    public static void setFieldValueOpt(Object object, String fieldName, Object value) {\n        try {\n            setFieldValue(object, fieldName, value);\n        } catch (Exception ex) {\n            Logging.d(TAG, \"setFieldValueOpt()| error happened\", ex);\n        }\n    }\n}\n"
  },
  {
    "path": "QSkinLoaderlib/src/main/java/org/qcode/qskinloader/base/utils/StringUtils.java",
    "content": "package org.qcode.qskinloader.base.utils;\n\n/**\n * qqliu\n * 2016/9/25.\n */\npublic class StringUtils {\n    public static boolean isEmpty(CharSequence sequence) {\n        return null == sequence || sequence.length() <= 0;\n    }\n}\n"
  },
  {
    "path": "QSkinLoaderlib/src/main/java/org/qcode/qskinloader/base/utils/WeakReferenceHelper.java",
    "content": "package com.iflytek.skin.manager.base.utils;\n\nimport java.lang.ref.WeakReference;\n\n/**\n * qqliu\n * 2016/10/19.\n */\n\npublic class WeakReferenceHelper<T> {\n\n    private WeakReference<T> mRef;\n\n    public WeakReferenceHelper(T t) {\n        setData(t);\n    }\n\n    public T getData() {\n        if(null == mRef) {\n            return null;\n        }\n\n        return mRef.get();\n    }\n\n    public void setData(T t) {\n        this.mRef = new WeakReference<T>(t);\n    }\n\n    @Override\n    public String toString() {\n        return \"WeakReferenceHelper{\" +\n                \"mData= \" + (null == mRef ? \"NULL\" : mRef.get()) +\n                '}';\n    }\n}\n"
  },
  {
    "path": "QSkinLoaderlib/src/main/java/org/qcode/qskinloader/entity/DynamicAttr.java",
    "content": "package org.qcode.qskinloader.entity;\n\n/**\n * 动态代码设置皮肤属性的实体类\n *\n * qqliu\n * 2016/9/25.\n */\npublic class DynamicAttr {\n\n    /***\n     * 对应View的属性\n     */\n    public String mAttrName;\n\n    /***\n     * 属性值对应的reference id值，类似R.color.XX\n     */\n    public int mAttrValueRefId = -1;\n\n    /**是否设置了属性值引用*/\n    public boolean hasSetValueRef = false;\n\n    /***\n     * 是否保留dynamicAttr；\n     * 子类继承DynamicAttr时可以改变此属性来保留自定义的属性值\n     */\n    public boolean keepInstance = false;\n\n    public DynamicAttr(String attrName) {\n        this.mAttrName = attrName;\n        hasSetValueRef = false;\n        keepInstance = false;\n    }\n\n    public DynamicAttr(String attrName, int attrValueRefId) {\n        this.mAttrName = attrName;\n        this.mAttrValueRefId = attrValueRefId;\n        hasSetValueRef = true;\n        keepInstance = false;\n    }\n\n    @Override\n    public String toString() {\n        return \"DynamicAttr{\" +\n                \"mAttrName='\" + mAttrName + '\\'' +\n                \", mAttrValueRefId=\" + mAttrValueRefId +\n                \", hasSetValueRef=\" + hasSetValueRef +\n                \", keepInstance=\" + keepInstance +\n                '}';\n    }\n}\n"
  },
  {
    "path": "QSkinLoaderlib/src/main/java/org/qcode/qskinloader/entity/SkinAttr.java",
    "content": "package org.qcode.qskinloader.entity;\n\n/**\n * 皮肤指定属性及其对应的值/类型的实体类封装\n * qqliu\n * 2016/9/24.\n */\npublic class SkinAttr {\n    /***\n     * 对应View的属性\n     */\n    public String mAttrName;\n\n    /***\n     * 属性值对应的reference id值，类似R.color.XX\n     */\n    public int mAttrValueRefId;\n\n    /***\n     * 属性值refrence id对应的名称，如R.color.XX，则此值为\"XX\"\n     */\n    public String mAttrValueRefName;\n\n    /***\n     * 属性值refrence id对应的类型，如R.color.XX，则此值为color\n     */\n    public String mAttrValueTypeName;\n\n    /***\n     * 直接存放自定义的属性\n     */\n    public DynamicAttr mDynamicAttr;\n\n    public SkinAttr() {\n        //empty\n    }\n\n    public SkinAttr(String attrName) {\n        mAttrName = attrName;\n        //others is empty\n    }\n\n    @Override\n    public String toString() {\n        return \"SkinAttr{\" +\n                \"mAttrName='\" + mAttrName + '\\'' +\n                \", mAttrValueRefId=\" + mAttrValueRefId +\n                \", mAttrValueRefName='\" + mAttrValueRefName + '\\'' +\n                \", mAttrValueTypeName='\" + mAttrValueTypeName + '\\'' +\n                \", mDynamicAttr='\" + mDynamicAttr + '\\'' +\n                '}';\n    }\n}\n"
  },
  {
    "path": "QSkinLoaderlib/src/main/java/org/qcode/qskinloader/entity/SkinAttrName.java",
    "content": "package org.qcode.qskinloader.entity;\n\n/**\n * 皮肤框架内置支持的属性\n * qqliu\n * 2016/9/27.\n */\npublic class SkinAttrName {\n    public static final String BACKGROUND = \"background\";\n    public static final String SRC = \"src\";\n    public static final String DRAWABLE_LEFT = \"drawableLeft\";\n    public static final String TEXT_COLOR = \"textColor\";\n    public static final String TEXT_COLOR_HINT = \"textColorHint\";\n    public static final String LIST_SELECTOR = \"listSelector\";\n    public static final String DIVIDER = \"divider\";\n    public static final String DRAW_SHADOW = \"drawShadow\";\n    public static final String CLEAR_RECYCLER_VIEW = \"clearRecyclerView\";\n}\n"
  },
  {
    "path": "QSkinLoaderlib/src/main/java/org/qcode/qskinloader/entity/SkinAttrSet.java",
    "content": "package org.qcode.qskinloader.entity;\n\nimport org.qcode.qskinloader.base.utils.CollectionUtils;\nimport org.qcode.qskinloader.base.utils.StringUtils;\n\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Collection;\nimport java.util.HashMap;\nimport java.util.List;\n\n/**\n * 皮肤框架支持的属性集合\n * qqliu\n * 2016/9/24.\n */\npublic class SkinAttrSet {\n    private HashMap<String, SkinAttr> mAttrMap = new HashMap<String, SkinAttr>();\n\n    public SkinAttrSet(SkinAttr... skinAttrs) {\n        this(Arrays.asList(skinAttrs));\n    }\n\n    public SkinAttrSet(List<SkinAttr> skinAttrs) {\n        saveAttrs(skinAttrs);\n    }\n\n    private void saveAttrs(List<SkinAttr> skinAttrs) {\n        if (CollectionUtils.isEmpty(skinAttrs)) {\n            return;\n        }\n\n        for (SkinAttr attr : skinAttrs) {\n            if (null == attr || StringUtils.isEmpty(attr.mAttrName)) {\n                continue;\n            }\n            mAttrMap.put(attr.mAttrName, attr);\n        }\n    }\n\n    public synchronized void addSkinAttrSet(SkinAttrSet skinAttrSet) {\n        if (null == skinAttrSet) {\n            return;\n        }\n\n        List<SkinAttr> setAttrList = skinAttrSet.getAttrList();\n        saveAttrs(setAttrList);\n    }\n\n    public synchronized void addSkinAttr(SkinAttr skinAttr) {\n        if (null == skinAttr || StringUtils.isEmpty(skinAttr.mAttrName)) {\n            return;\n        }\n\n        mAttrMap.put(skinAttr.mAttrName, skinAttr);\n    }\n\n    public synchronized List<SkinAttr> getAttrList() {\n        ArrayList<SkinAttr> resultList = new ArrayList<SkinAttr>();\n\n        Collection<SkinAttr> valueAttrList = mAttrMap.values();\n        if (!CollectionUtils.isEmpty(valueAttrList)) {\n            resultList.addAll(valueAttrList);\n        }\n\n        return resultList;\n    }\n}\n"
  },
  {
    "path": "QSkinLoaderlib/src/main/java/org/qcode/qskinloader/entity/SkinConstant.java",
    "content": "package org.qcode.qskinloader.entity;\n\n/**\n * 皮肤框架的常量定义\n * qqliu\n * 2016/9/25.\n */\npublic class SkinConstant {\n    /***\n     * 支持的命名空间\n     */\n    public static final String XML_NAMESPACE = \"http://schemas.android.com/android/skin\";\n\n    /**属性值对应的类型是color*/\n    public static final String RES_TYPE_NAME_COLOR = \"color\";\n\n    /**属性值对应的类型是drawable*/\n    public static final String RES_TYPE_NAME_DRAWABLE = \"drawable\";\n\n    /**属性值对应的类型是mipmap*/\n    public static final String RES_TYPE_NAME_MIPMAP = \"mipmap\";\n\n    /**界面元素支持换肤的属性*/\n    public static final String ATTR_SKIN_ENABLE = \"enable\";\n\n}\n"
  },
  {
    "path": "QSkinLoaderlib/src/main/java/org/qcode/qskinloader/impl/ActivitySkinEventHandlerImpl.java",
    "content": "package org.qcode.qskinloader.impl;\n\nimport android.app.Activity;\nimport android.content.res.Resources;\nimport android.graphics.drawable.ColorDrawable;\nimport android.graphics.drawable.Drawable;\nimport android.view.View;\n\nimport org.qcode.qskinloader.IActivitySkinEventHandler;\nimport org.qcode.qskinloader.ISkinActivity;\nimport org.qcode.qskinloader.ISkinAttributeParser;\nimport org.qcode.qskinloader.IViewCreateListener;\nimport org.qcode.qskinloader.SkinManager;\nimport org.qcode.qskinloader.base.utils.Logging;\n\nimport java.lang.ref.WeakReference;\n\n/***\n * 支持换肤界面的帮助类\n */\npublic class ActivitySkinEventHandlerImpl implements IActivitySkinEventHandler {\n\n    private static final String TAG = \"ActivityEventHandler\";\n\n    private final SkinManagerImpl mSkinManager;\n\n    private volatile boolean mNeedRefreshSkin = false;\n\n    //当前界面是否有Focus\n    private boolean mHasFocus;\n\n    //皮肤发生变化时，当前界面是否需要立刻刷新皮肤\n    private boolean mSwitchSkinImmediately;\n\n    //当前界面是否支持皮肤变化\n    private boolean mIsSupportSkinChange;\n\n    private WeakReference<Activity> mActivity = null;\n    private int mWindowBgResId = -1;\n    private SkinInflaterFactoryImpl mSkinInflaterFactoryImpl;\n    private IViewCreateListener mViewCreateListener;\n    private boolean mNeedDelegateViewCreate = true;\n\n    private SkinAttributeParser mSkinAttributeParser;\n\n    public ActivitySkinEventHandlerImpl() {\n        mSkinManager = SkinManagerImpl.getInstance();\n    }\n\n    @Override\n    public void onCreate(Activity activity) {\n        if (!mIsSupportSkinChange) {\n            return;\n        }\n\n        mActivity = new WeakReference<Activity>(activity);\n\n        if(mNeedDelegateViewCreate) {\n            mSkinInflaterFactoryImpl =\n                    new SkinInflaterFactoryImpl(getSkinAttributeParser());\n            mSkinInflaterFactoryImpl.setViewCreateListener(mViewCreateListener);\n            activity.getLayoutInflater().setFactory(mSkinInflaterFactoryImpl);\n        }\n\n        mSkinManager.addObserver(this);\n    }\n\n    @Override\n    public void setViewCreateListener(IViewCreateListener viewCreateListener) {\n        mViewCreateListener = viewCreateListener;\n        if(null != mSkinInflaterFactoryImpl) {\n            mSkinInflaterFactoryImpl.setViewCreateListener(viewCreateListener);\n        }\n    }\n\n    @Override\n    public void onViewCreated() {\n        if (!mIsSupportSkinChange) {\n            return;\n        }\n\n        Logging.d(TAG, \"onViewCreated()\");\n\n        if (!mSkinManager.getResourceManager().isDefault()) {\n            View contentView = getContentView();\n            mSkinManager.applySkin(contentView, true);\n            refreshWindowBg(contentView);\n        }\n\n        mSkinManager.addObserver(this);\n    }\n\n    @Override\n    public void onStart() {\n        //do nothing\n    }\n\n    @Override\n    public void onResume() {\n        //do nothing\n    }\n\n    @Override\n    public void onWindowFocusChanged(boolean hasFocus) {\n        if (!mIsSupportSkinChange) {\n            return;\n        }\n\n        mHasFocus = hasFocus;\n\n        if(mHasFocus) {\n            if (mNeedRefreshSkin) {\n                mNeedRefreshSkin = false;\n                //后台界面展示出来时再刷新\n                refreshSkin();\n            }\n        }\n    }\n\n    @Override\n    public void onPause() {\n        //do nothing\n    }\n\n    @Override\n    public void onStop() {\n        //do nothing\n    }\n\n    @Override\n    public void onDestroy() {\n        if (!mIsSupportSkinChange) {\n            return;\n        }\n\n        mSkinManager.removeObserver(this);\n        SkinManager\n                .with(getContentView())\n                .cleanAttrs(true);\n\n        mActivity.clear();\n    }\n\n    @Override\n    public IActivitySkinEventHandler setSwitchSkinImmediately(boolean isImmediate) {\n        mSwitchSkinImmediately = isImmediate;\n        return this;\n    }\n\n    @Override\n    public IActivitySkinEventHandler setSupportSkinChange(boolean supportChange) {\n        mIsSupportSkinChange = supportChange;\n        return this;\n    }\n\n    @Override\n    public void handleSkinUpdate() {\n        if (!mIsSupportSkinChange) {\n            Logging.d(TAG, \"onThemeUpdate()| not support theme change: \" + getClass().getSimpleName());\n            return;\n        }\n\n        if (mHasFocus || mSwitchSkinImmediately) {\n            mNeedRefreshSkin = false;\n            refreshSkin();\n        } else {\n            //仅置位，不立刻刷新\n            mNeedRefreshSkin = true;\n        }\n    }\n\n    @Override\n    public ISkinAttributeParser getSkinAttributeParser() {\n        if(null == mSkinAttributeParser) {\n            mSkinAttributeParser = new SkinAttributeParser();\n        }\n\n        return mSkinAttributeParser;\n    }\n\n    @Override\n    public IActivitySkinEventHandler setWindowBackgroundResource(int resId) {\n        mWindowBgResId = resId;\n        return this;\n    }\n\n    @Override\n    public IActivitySkinEventHandler setNeedDelegateViewCreate(boolean needDelegateViewCreate) {\n        mNeedDelegateViewCreate = needDelegateViewCreate;\n        return this;\n    }\n\n    private void refreshSkin() {\n        if (!mIsSupportSkinChange) {\n            return;\n        }\n\n        if (null == mActivity) {\n            return;\n        }\n\n        final Activity activity = mActivity.get();\n\n        activity.runOnUiThread(new Runnable() {\n            @Override\n            public void run() {\n                View contentView = getContentView();\n                mSkinManager.applySkin(contentView, true);\n                mSkinManager.applyWindowViewSkin();\n                refreshWindowBg(contentView);\n\n                //通知Activity做其他刷新操作\n                if (activity instanceof ISkinActivity) {\n                    ((ISkinActivity) activity).handleSkinChange();\n                }\n            }\n        });\n    }\n\n    private void refreshWindowBg(View contentView) {\n        if(!mIsSupportSkinChange) {\n            return;\n        }\n\n        if (mWindowBgResId <= 0) {\n            return;\n        }\n\n        if (null == mActivity) {\n            return;\n        }\n        Activity activity = mActivity.get();\n        if (null == activity) {\n            return;\n        }\n\n        Drawable bgDrawable;\n        try {\n            bgDrawable = new ColorDrawable(\n                    mSkinManager.getResourceManager().getColor(mWindowBgResId));\n\n        } catch (Resources.NotFoundException ex) {\n            try {\n                bgDrawable = mSkinManager.getResourceManager().getDrawable(mWindowBgResId);\n            } catch (Resources.NotFoundException e) {\n                return;\n            }\n        }\n//        contentView.setBackgroundDrawable(bgDrawable);\n        activity.getWindow().setBackgroundDrawable(bgDrawable);\n    }\n\n    public View getContentView() {\n        if (null == mActivity) {\n            return null;\n        }\n\n        Activity activity = mActivity.get();\n        if (null == activity) {\n            return null;\n        }\n\n        return activity.findViewById(android.R.id.content);\n    }\n}\n"
  },
  {
    "path": "QSkinLoaderlib/src/main/java/org/qcode/qskinloader/impl/SkinAttributeParser.java",
    "content": "package org.qcode.qskinloader.impl;\n\nimport android.content.Context;\nimport android.content.res.Resources.NotFoundException;\nimport android.support.v7.widget.RecyclerView;\nimport android.util.AttributeSet;\nimport android.view.View;\n\nimport org.qcode.qskinloader.ISkinAttributeParser;\nimport org.qcode.qskinloader.R;\nimport org.qcode.qskinloader.SkinManager;\nimport org.qcode.qskinloader.attrhandler.SkinAttrFactory;\nimport org.qcode.qskinloader.base.utils.CollectionUtils;\nimport org.qcode.qskinloader.base.utils.Logging;\nimport org.qcode.qskinloader.entity.SkinAttr;\nimport org.qcode.qskinloader.entity.SkinAttrName;\nimport org.qcode.qskinloader.entity.SkinAttrSet;\nimport org.qcode.qskinloader.entity.SkinConstant;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\n/***\n * 代理View的创建，解析与换肤相关的属性\n */\nclass SkinAttributeParser implements ISkinAttributeParser {\n\n    private static final String TAG = \"SkinAttributeParser\";\n\n    public boolean isSupportSkin(String name, Context context, AttributeSet attrs) {\n        //只有View设置了skin:enable，才解析属性\n        boolean isSkinEnable = attrs.getAttributeBooleanValue(\n                SkinConstant.XML_NAMESPACE,\n                SkinConstant.ATTR_SKIN_ENABLE,\n                false);\n        return isSkinEnable;\n    }\n\n    public void parseAttribute(View view, String name, Context context, AttributeSet attrs) {\n        if (view == null) {\n            return;\n        }\n\n        SkinAttrSet skinAttrSet = parseSkinAttr(context, attrs);\n\n        //recyclerview 增加处理\n        if(view instanceof RecyclerView) {\n            SkinAttr clearSubAttr = new SkinAttr(SkinAttrName.CLEAR_RECYCLER_VIEW);\n            SkinManager\n                    .with(view)\n                    .addViewAttrs(clearSubAttr);\n            if(null == skinAttrSet) {\n                skinAttrSet = new SkinAttrSet(clearSubAttr);\n            } else {\n                skinAttrSet.addSkinAttr(clearSubAttr);\n            }\n        }\n\n        if (null != skinAttrSet) {\n            //将SkinItem存储在View的tag内\n            view.setTag(R.id.tag_skin_attr, skinAttrSet);\n\n            //如果有drawShadow属性，则替换ImageView为其他View\n//            ShadowAttr2 skinAttrShadow = skinAttrSet.findSkinAttr(ShadowAttr2.class);\n//            if (null != skinAttrShadow) {\n//                if(view instanceof ImageView) {\n//                    //\n//                    view = createShadowImageView(context);\n//                    //将SkinItem存储在View的tag内\n//                    view.setTag(R.id.tag_skin_attr, skinAttrSet);\n//                } else {\n//                    view = createFrameWrapper(view, skinAttrShadow);\n//                }\n//            }\n        }\n    }\n\n//    private View createFrameWrapper(View view, ShadowAttr2 skinAttrShadow) {\n//        Context context = view.getContext();\n//        FrameLayout wrapperView = new FrameLayout(context);\n//        FrameLayout.LayoutParams paramView = new FrameLayout.LayoutParams(\n//                ViewGroup.LayoutParams.MATCH_PARENT,\n//                ViewGroup.LayoutParams.MATCH_PARENT);\n//        wrapperView.addView(view, paramView);\n//\n//        View shadowView = new View(context);\n//        shadowView.setVisibility(View.GONE);\n//        FrameLayout.LayoutParams paramShadow = new FrameLayout.LayoutParams(\n//                ViewGroup.LayoutParams.MATCH_PARENT,\n//                ViewGroup.LayoutParams.MATCH_PARENT);\n//        wrapperView.addView(shadowView, paramShadow);\n//\n//        skinAttrShadow.setShadowView(shadowView);\n//        return wrapperView;\n//    }\n\n//    private ShadowImageView createShadowImageView(Context context) {\n//        return new ShadowImageView(context);\n//    }\n\n    /***\n     * 收集与换肤相关的属性\n     *\n     * @param context\n     * @param attrs\n     */\n    private SkinAttrSet parseSkinAttr(Context context,\n                                      AttributeSet attrs) {\n        List<SkinAttr> viewAttrs = new ArrayList<SkinAttr>();\n\n        for (int i = 0; i < attrs.getAttributeCount(); i++) {\n            String attrName = attrs.getAttributeName(i);\n            String attrValue = attrs.getAttributeValue(i);\n\n            if (!SkinAttrFactory.isSupportedAttr(attrName)) {\n                continue;\n            }\n\n            if (!attrValue.startsWith(\"@\")) {\n                Logging.d(TAG, \"parseSkinAttr()| only support ref id\");\n                continue;\n            }\n\n            SkinAttr skinAttr = null;\n            try {\n                skinAttr = getSkinAttrFromId(context, attrName, attrValue);\n            } catch (NumberFormatException ex) {\n//                Logging.d(TAG, \"parseSkinAttr()| error happened\", ex);\n                skinAttr = getSkinAttrBySplit(context, attrName, attrValue);\n            } catch (NotFoundException ex) {\n                Logging.d(TAG, \"parseSkinAttr()| error happened\", ex);\n            }\n\n            if (skinAttr != null) {\n                viewAttrs.add(skinAttr);\n            }\n        }\n\n        if (CollectionUtils.isEmpty(viewAttrs)) {\n            return null;\n        }\n\n        return new SkinAttrSet(viewAttrs);\n    }\n\n    private SkinAttr getSkinAttrBySplit(Context context, String attrName, String attrValue) {\n        try {\n            int dividerIndex = attrValue.indexOf(\"/\");\n            String entryName = attrValue.substring(dividerIndex + 1, attrValue.length());\n            String typeName = attrValue.substring(1, dividerIndex);\n            int id = context.getResources().getIdentifier(entryName, typeName, context.getPackageName());\n            return SkinAttrFactory.newSkinAttr(attrName, id, entryName, typeName);\n        } catch (NotFoundException e) {\n            Logging.d(TAG, \"parseSkinAttr()| error happened\", e);\n        }\n        return null;\n    }\n\n    private SkinAttr getSkinAttrFromId(Context context, String attrName, String attrValue) {\n        int id = Integer.parseInt(attrValue.substring(1));\n        String entryName = context.getResources().getResourceEntryName(id);\n        String typeName = context.getResources().getResourceTypeName(id);\n        return SkinAttrFactory.newSkinAttr(attrName, id, entryName, typeName);\n    }\n}\n"
  },
  {
    "path": "QSkinLoaderlib/src/main/java/org/qcode/qskinloader/impl/SkinInflaterFactoryImpl.java",
    "content": "package org.qcode.qskinloader.impl;\n\nimport android.content.Context;\nimport android.util.AttributeSet;\nimport android.view.LayoutInflater;\nimport android.view.View;\n\nimport org.qcode.qskinloader.ISkinAttributeParser;\nimport org.qcode.qskinloader.IViewCreateListener;\nimport org.qcode.qskinloader.base.utils.Logging;\nimport org.qcode.qskinloader.base.utils.ReflectUtils;\n\n/***\n * 代理View的创建，解析与换肤相关的属性\n */\nclass SkinInflaterFactoryImpl implements LayoutInflater.Factory {\n\n    private static final String TAG = \"SkinInflaterFactoryImpl\";\n    private IViewCreateListener mViewCreateListener;\n\n    private ISkinAttributeParser mSkinAttributeParser;\n\n    public SkinInflaterFactoryImpl(ISkinAttributeParser parser) {\n        mSkinAttributeParser = parser;\n    }\n\n    public void setViewCreateListener(IViewCreateListener viewCreateListener) {\n        mViewCreateListener = viewCreateListener;\n    }\n\n    @Override\n    public View onCreateView(String name, Context context, AttributeSet attrs) {\n        View view = null;\n\n        //给框架外创建View的机会\n        if(null != mViewCreateListener) {\n            view = mViewCreateListener.beforeCreate(name, context, attrs);\n        }\n\n        //判断是否支持换肤\n        if(!mSkinAttributeParser.isSupportSkin(name, context, attrs)) {\n            return null;\n        }\n\n        if(null == view) {\n            //代理创建View\n            view = createView(context, name, attrs);\n        }\n\n        if (view == null) {\n            return null;\n        }\n\n        //解析属性\n        mSkinAttributeParser.parseAttribute(view, name, context, attrs);\n\n        //给框架外解析属性的机会\n        if(null != mViewCreateListener) {\n            mViewCreateListener.afterCreated(view, name, context, attrs);\n        }\n\n        return view;\n    }\n\n    /***\n     * 根据名称创建view\n     *\n     * @param context\n     * @param name\n     * @param attrs\n     * @return\n     */\n    private View createView(Context context,\n                            String name,\n                            AttributeSet attrs) {\n        View view = null;\n        try {\n            LayoutInflater inflater = LayoutInflater.from(context);\n            setupInflater(inflater, context);\n\n            if (-1 == name.indexOf('.')) {\n                if (\"View\".equals(name)\n                        || \"ViewStub\".equals(name)\n                        || \"ViewGroup\".equals(name)) {\n                    view = inflater.createView(\n                            name, \"android.view.\", attrs);\n                }\n                if (view == null) {\n                    view = inflater.createView(\n                            name, \"android.widget.\", attrs);\n                }\n                if (view == null) {\n                    view = inflater.createView(\n                            name, \"android.webkit.\", attrs);\n                }\n            } else {\n                view = inflater.createView(name, null, attrs);\n            }\n\n        } catch (Exception ex) {\n            Logging.d(TAG, \"createView()| create view failed\", ex);\n            view = null;\n        }\n\n        return view;\n    }\n\n    private void setupInflater(LayoutInflater inflater, Context context) {\n        //异常，处理context为空，一般不会发生\n        Context inflaterContext = inflater.getContext();\n        if (null == inflaterContext) {\n            ReflectUtils.setFieldValueOpt(inflater, \"mContext\", context);\n        }\n\n        //设置mConstructorArgs的第一个参数context\n        Object[] constructorArgs = ReflectUtils.getFieldValueOpt(inflater, \"mConstructorArgs\");\n        if (null == constructorArgs || constructorArgs.length < 2) {\n            //异常，一般不会发生\n            constructorArgs = new Object[2];\n            ReflectUtils.setFieldValueOpt(inflater, \"mConstructorArgs\", constructorArgs);\n        }\n\n        //如果mConstructorArgs的第一个参数为空，则设置为mContext\n        if (null == constructorArgs[0]) {\n            constructorArgs[0] = inflater.getContext();\n        }\n    }\n}\n"
  },
  {
    "path": "QSkinLoaderlib/src/main/java/org/qcode/qskinloader/impl/SkinManagerImpl.java",
    "content": "package org.qcode.qskinloader.impl;\n\nimport android.content.Context;\nimport android.os.AsyncTask;\nimport android.view.View;\nimport android.view.ViewGroup;\n\nimport org.qcode.qskinloader.IActivitySkinEventHandler;\nimport org.qcode.qskinloader.ILoadSkinListener;\nimport org.qcode.qskinloader.IResourceLoader;\nimport org.qcode.qskinloader.IResourceManager;\nimport org.qcode.qskinloader.ISkinAttrHandler;\nimport org.qcode.qskinloader.ISkinManager;\nimport org.qcode.qskinloader.attrhandler.SkinAttrFactory;\nimport org.qcode.qskinloader.attrhandler.SkinAttrUtils;\nimport org.qcode.qskinloader.base.observable.INotifyUpdate;\nimport org.qcode.qskinloader.base.observable.Observable;\nimport org.qcode.qskinloader.base.utils.CollectionUtils;\nimport org.qcode.qskinloader.base.utils.Logging;\nimport org.qcode.qskinloader.base.utils.StringUtils;\nimport org.qcode.qskinloader.entity.SkinAttrSet;\nimport org.qcode.qskinloader.resourceloader.ILoadResourceCallback;\nimport org.qcode.qskinloader.resourceloader.ResourceManager;\nimport org.qcode.qskinloader.resourceloader.impl.APKResourceLoader;\n\nimport java.util.List;\n\n/**\n * 皮肤加载管理类对外实现接口\n * qqliu\n * 2016/9/24.\n */\npublic class SkinManagerImpl implements ISkinManager {\n\n    private static final String TAG = \"SkinManager\";\n\n    //单例相关\n    private static volatile SkinManagerImpl mInstance;\n\n    private SkinManagerImpl() {\n    }\n\n    public static SkinManagerImpl getInstance() {\n        if (null == mInstance) {\n            synchronized (SkinManagerImpl.class) {\n                if (null == mInstance) {\n                    mInstance = new SkinManagerImpl();\n                }\n            }\n        }\n        return mInstance;\n    }\n\n    private Context mContext;\n    private IResourceManager mResourceManager;\n\n    private Observable<IActivitySkinEventHandler> mObservable;\n\n    @Override\n    public void init(Context context) {\n        mContext = context.getApplicationContext();\n        mResourceManager = new ResourceManager(mContext);\n        mObservable = new Observable<IActivitySkinEventHandler>();\n        new AsyncTask<String, Void, Void>() {\n\n            @Override\n            protected Void doInBackground(String... params) {\n                return null;\n            }\n\n        }.execute(\"\");\n    }\n\n    @Override\n    public void restoreDefault(String defaultSkinIdentifier, ILoadSkinListener loadListener) {\n        if (null != loadListener) {\n            loadListener.onLoadStart(defaultSkinIdentifier);\n        }\n\n        //恢复ResourceManager的行为\n        mResourceManager.setBaseResource(null, null);\n\n        refreshAllSkin();\n\n        if (loadListener != null) {\n            loadListener.onLoadSuccess(defaultSkinIdentifier);\n        }\n    }\n\n    private void refreshAllSkin() {\n        //刷新正常的Activity内View的皮肤\n        refreshSkin();\n\n        //刷新框架内维护的View的皮肤,包括Dialog/popWindow/悬浮窗等应用场景\n        applyWindowViewSkin();\n    }\n\n    @Override\n    public void loadAPKSkin(String skinPath, ILoadSkinListener loadListener) {\n        loadSkin(skinPath, new APKResourceLoader(mContext), loadListener);\n    }\n\n    @Override\n    public void loadSkin(final String skinIdentifier,\n                         final IResourceLoader resourceLoader,\n                         final ILoadSkinListener loadListener) {\n        if(StringUtils.isEmpty(skinIdentifier)\n                || null == resourceLoader) {\n            if(null != loadListener) {\n                loadListener.onLoadFail(skinIdentifier);\n            }\n            return;\n        }\n\n        //当前皮肤就是将要换肤的皮肤，则不执行后续行为\n        if (skinIdentifier.equals(mResourceManager.getSkinIdentifier())) {\n            Logging.d(TAG, \"load()| current skin matches target, do nothing\");\n            if(null != loadListener) {\n                loadListener.onLoadSuccess(skinIdentifier);\n            }\n            return;\n        }\n\n        resourceLoader.loadResource(skinIdentifier, new ILoadResourceCallback() {\n            @Override\n            public void onLoadStart(String identifier) {\n                if (loadListener != null) {\n                    loadListener.onLoadStart(skinIdentifier);\n                }\n            }\n\n            @Override\n            public void onLoadSuccess(String identifier, IResourceManager result) {\n                Logging.d(TAG, \"onLoadSuccess() | identifier= \" + identifier);\n                mResourceManager.setBaseResource(identifier, result);\n\n                refreshAllSkin();\n\n                Logging.d(TAG, \"onLoadSuccess()| notify update\");\n                if (loadListener != null) {\n                    loadListener.onLoadSuccess(skinIdentifier);\n                }\n            }\n\n            @Override\n            public void onLoadFail(String identifier, int errorCode) {\n                mResourceManager.setBaseResource(null, null);\n                if (loadListener != null) {\n                    loadListener.onLoadFail(skinIdentifier);\n                }\n            }\n        });\n    }\n\n    @Override\n    public void applySkin(View view, boolean applyChild) {\n        if (null == view) {\n            return;\n        }\n\n        SkinAttrSet skinAttrSet = ViewSkinTagHelper.getSkinAttrs(view);\n        SkinAttrUtils.applySkinAttrs(view, skinAttrSet, mResourceManager);\n\n        if (applyChild) {\n            if (view instanceof ViewGroup) {\n                //遍历子元素应用皮肤\n                ViewGroup viewGroup = (ViewGroup) view;\n                for (int i = 0; i < viewGroup.getChildCount(); i++) {\n                    applySkin(viewGroup.getChildAt(i), true);\n                }\n            }\n        }\n    }\n\n    @Override\n    public void registerSkinAttrHandler(String attrName, ISkinAttrHandler skinAttrHandler) {\n        SkinAttrFactory.registerSkinAttrHandler(attrName, skinAttrHandler);\n    }\n\n    @Override\n    public void unregisterSkinAttrHandler(String attrName) {\n        SkinAttrFactory.removeSkinAttrHandler(attrName);\n    }\n\n    @Override\n    public void setResourceManager(IResourceManager resourceManager) {\n        if(null == resourceManager) {\n            return;\n        }\n        mResourceManager = resourceManager;\n    }\n\n    @Override\n    public IResourceManager getResourceManager() {\n        return mResourceManager;\n    }\n\n    @Override\n    public void addObserver(IActivitySkinEventHandler observer) {\n        mObservable.addObserver(observer);\n    }\n\n    @Override\n    public void removeObserver(IActivitySkinEventHandler observer) {\n        mObservable.removeObserver(observer);\n    }\n\n    @Override\n    public void notifyUpdate(INotifyUpdate<IActivitySkinEventHandler> callback, String identifier, Object... params) {\n        mObservable.notifyUpdate(callback, identifier, params);\n    }\n\n    /**刷新Window上添加的View的显示模式*/\n    void applyWindowViewSkin() {\n        List<View> windowViewList = WindowViewManager.getInstance().getWindowViewList();\n        if(CollectionUtils.isEmpty(windowViewList)) {\n            return;\n        }\n\n        for(View view : windowViewList) {\n            applySkin(view, true);\n        }\n    }\n\n    /***\n     * 告知外部观察者当前皮肤发生了变化\n     */\n    private void refreshSkin() {\n        notifyUpdate(new INotifyUpdate<IActivitySkinEventHandler>() {\n            @Override\n            public boolean notifyEvent(\n                    IActivitySkinEventHandler handler,\n                    String identifier,\n                    Object... params) {\n                handler.handleSkinUpdate();\n                return false;\n            }\n        }, null);\n    }\n}\n"
  },
  {
    "path": "QSkinLoaderlib/src/main/java/org/qcode/qskinloader/impl/SkinViewHelperImpl.java",
    "content": "package org.qcode.qskinloader.impl;\n\nimport android.content.res.Resources;\nimport android.support.annotation.NonNull;\nimport android.view.View;\nimport android.view.ViewGroup;\n\nimport org.qcode.qskinloader.ISkinViewHelper;\nimport org.qcode.qskinloader.attrhandler.SkinAttrFactory;\nimport org.qcode.qskinloader.base.utils.CollectionUtils;\nimport org.qcode.qskinloader.base.utils.Logging;\nimport org.qcode.qskinloader.base.utils.StringUtils;\nimport org.qcode.qskinloader.entity.DynamicAttr;\nimport org.qcode.qskinloader.entity.SkinAttr;\nimport org.qcode.qskinloader.entity.SkinAttrSet;\n\nimport java.util.Arrays;\nimport java.util.List;\n\nimport static android.content.ContentValues.TAG;\n\n/**\n * View皮肤设置管理\n * qqliu\n * 2016/10/8.\n */\n\npublic class SkinViewHelperImpl implements ISkinViewHelper {\n\n    private View mView;\n\n    public SkinViewHelperImpl(View view) {\n        mView = view;\n    }\n\n    //=========================interfaces================================//\n\n    @Override\n    public ISkinViewHelper setViewAttrs(String attrName, int resId) {\n        if (StringUtils.isEmpty(attrName)) {\n            return this;\n        }\n\n        SkinAttr attr = parseSkinAttr(attrName, resId);\n        return setViewAttrs(attr);\n    }\n\n    @Override\n    public ISkinViewHelper setViewAttrs(DynamicAttr... dynamicAttrs) {\n        if (CollectionUtils.isEmpty(dynamicAttrs)) {\n            return this;\n        }\n\n        return setViewAttrs(Arrays.asList(dynamicAttrs));\n    }\n\n    @Override\n    public ISkinViewHelper setViewAttrs(SkinAttr... skinAttrs) {\n        if (CollectionUtils.isEmpty(skinAttrs)) {\n            return this;\n        }\n\n        ViewSkinTagHelper.setSkinAttrs(mView, new SkinAttrSet(skinAttrs));\n\n        return this;\n    }\n\n    @Override\n    public ISkinViewHelper setViewAttrs(List<DynamicAttr> dynamicAttrs) {\n        if (CollectionUtils.isEmpty(dynamicAttrs)) {\n            return this;\n        }\n\n        SkinAttrSet skinAttrSet = parseSkinAttrSet(dynamicAttrs);\n\n        ViewSkinTagHelper.setSkinAttrs(mView, skinAttrSet);\n\n        return this;\n    }\n\n    @Override\n    public ISkinViewHelper addViewAttrs(String attrName, int resId) {\n        if (StringUtils.isEmpty(attrName)) {\n            return this;\n        }\n\n        SkinAttr attr = parseSkinAttr(attrName, resId);\n        return addViewAttrs(attr);\n    }\n\n    @Override\n    public ISkinViewHelper addViewAttrs(DynamicAttr... dynamicAttrs) {\n        if (CollectionUtils.isEmpty(dynamicAttrs)) {\n            return this;\n        }\n\n        return addViewAttrs(Arrays.asList(dynamicAttrs));\n    }\n\n    @Override\n    public ISkinViewHelper addViewAttrs(SkinAttr... skinAttrs) {\n        if (CollectionUtils.isEmpty(skinAttrs)) {\n            return this;\n        }\n\n        ViewSkinTagHelper.addSkinAttrs(mView, new SkinAttrSet(skinAttrs));\n\n        return this;\n    }\n\n    @Override\n    public ISkinViewHelper addViewAttrs(List<DynamicAttr> dynamicAttrs) {\n        if (CollectionUtils.isEmpty(dynamicAttrs)) {\n            return this;\n        }\n\n        SkinAttrSet skinAttrSet = parseSkinAttrSet(dynamicAttrs);\n\n        ViewSkinTagHelper.addSkinAttrs(mView, skinAttrSet);\n\n        return this;\n    }\n\n    @Override\n    public ISkinViewHelper cleanAttrs(boolean clearChild) {\n        if (null == mView) {\n            return this;\n        }\n\n        cleanAttrs(mView, clearChild);\n\n        return this;\n    }\n\n    @Override\n    public void applySkin(boolean applyChild) {\n        SkinManagerImpl.getInstance().applySkin(mView, applyChild);\n    }\n\n    private static void cleanAttrs(View view, boolean clearChild) {\n        if (null == view) {\n            return;\n        }\n\n        ViewSkinTagHelper.setSkinAttrs(view, null);\n\n        if (clearChild) {\n            if (view instanceof ViewGroup) {\n                //遍历子元素清除皮肤\n                ViewGroup viewGroup = (ViewGroup) view;\n                for (int i = 0; i < viewGroup.getChildCount(); i++) {\n                    cleanAttrs(viewGroup.getChildAt(i), true);\n                }\n            }\n        }\n    }\n\n    private SkinAttr parseSkinAttr(String attrName, int resId) {\n        SkinAttr skinAttr = null;\n\n        Resources resources = mView.getResources();\n\n        try {\n            String attrValueName = resources.getResourceEntryName(resId);\n            String attrValueType = resources.getResourceTypeName(resId);\n            skinAttr = SkinAttrFactory.newSkinAttr(attrName, resId, attrValueName, attrValueType);\n        } catch (Exception ex) {\n            Logging.d(TAG, \"dynamicAddView()| error happened\", ex);\n        }\n\n        return skinAttr;\n    }\n\n    @NonNull\n    private SkinAttrSet parseSkinAttrSet(List<DynamicAttr> dynamicAttrs) {\n        SkinAttrSet skinAttrSet = new SkinAttrSet();\n\n        for (DynamicAttr dynamicAttr : dynamicAttrs) {\n            if (null == dynamicAttr) {\n                continue;\n            }\n\n            SkinAttr attr;\n            if (dynamicAttr.hasSetValueRef) {\n                //设置了value，则解析resId的名称和类型\n                attr = parseSkinAttr(dynamicAttr.mAttrName, dynamicAttr.mAttrValueRefId);\n            } else {\n                //没有value，直接解析名称\n                attr = SkinAttrFactory.newSkinAttr(dynamicAttr.mAttrName);\n            }\n\n            if (null == attr) {\n                continue;\n            }\n\n            if (dynamicAttr.keepInstance) {\n                attr.mDynamicAttr = dynamicAttr;\n            }\n\n            skinAttrSet.addSkinAttr(attr);\n        }\n\n        return skinAttrSet;\n    }\n}\n"
  },
  {
    "path": "QSkinLoaderlib/src/main/java/org/qcode/qskinloader/impl/ViewSkinTagHelper.java",
    "content": "package org.qcode.qskinloader.impl;\n\nimport android.view.View;\n\nimport org.qcode.qskinloader.R;\nimport org.qcode.qskinloader.entity.SkinAttrSet;\n\n/**\n * 存取View的皮肤属性的帮助类\n * qqliu\n * 2016/10/8.\n */\n\nclass ViewSkinTagHelper {\n\n    /***\n     * 设置View的皮肤属性\n     * @param view\n     * @param skinAttrSet\n     */\n    public static void setSkinAttrs(View view, SkinAttrSet skinAttrSet) {\n        if(null == view) {\n            return;\n        }\n        view.setTag(R.id.tag_skin_attr, skinAttrSet);\n    }\n\n    /***\n     * 添加View的皮肤属性\n     * @param view\n     * @param skinAttrSet\n     */\n    public static void addSkinAttrs(View view, SkinAttrSet skinAttrSet) {\n        if(null == view) {\n            return;\n        }\n\n        SkinAttrSet attrSet = getSkinAttrs(view);\n        if (null == attrSet) {\n            view.setTag(R.id.tag_skin_attr, skinAttrSet);\n        } else {\n            attrSet.addSkinAttrSet(skinAttrSet);\n        }\n    }\n\n    /***\n     * 获取View的皮肤属性\n     * @param view\n     * @return  skinAttrSet\n     */\n    public static SkinAttrSet getSkinAttrs(View view) {\n        if(null == view) {\n            return null;\n        }\n\n        return (SkinAttrSet) view.getTag(R.id.tag_skin_attr);\n    }\n}\n"
  },
  {
    "path": "QSkinLoaderlib/src/main/java/org/qcode/qskinloader/impl/WindowViewManager.java",
    "content": "package org.qcode.qskinloader.impl;\n\nimport android.view.View;\n\nimport org.qcode.qskinloader.IWindowViewManager;\nimport org.qcode.qskinloader.SkinManager;\nimport org.qcode.qskinloader.base.utils.CollectionUtils;\n\nimport java.lang.ref.WeakReference;\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * 直接加载在Window上的view的管理器\n * qqliu\n * 2016/10/8.\n */\n\npublic class WindowViewManager implements IWindowViewManager {\n    private ArrayList<WeakReference<View>> mSkinViewList = new ArrayList<WeakReference<View>>();\n\n    //==========================Singleton========================//\n    private static volatile WindowViewManager mInstance;\n\n    private WindowViewManager() {}\n\n    public static WindowViewManager getInstance() {\n        if(null == mInstance) {\n            synchronized (WindowViewManager.class) {\n                if(null == mInstance) {\n                    mInstance = new WindowViewManager();\n                }\n            }\n        }\n        return mInstance;\n    }\n\n    //==========================interfaces========================//\n    @Override\n    public IWindowViewManager addWindowView(View view) {\n        ArrayList<WeakReference<View>> tmpList\n                = (ArrayList<WeakReference<View>>) mSkinViewList.clone();\n        for (WeakReference<View> viewRef : tmpList) {\n            if (null == viewRef) {\n                continue;\n            }\n            //已添加此View，则不需要再添加\n            if (view == viewRef.get()) {\n                return this;\n            }\n        }\n\n        mSkinViewList.add(new WeakReference<View>(view));\n\n        return this;\n    }\n\n    @Override\n    public IWindowViewManager removeWindowView(View view) {\n        ArrayList<WeakReference<View>> tmpList\n                = (ArrayList<WeakReference<View>>) mSkinViewList.clone();\n        for (WeakReference<View> viewRef : tmpList) {\n            if (null == viewRef) {\n                continue;\n            }\n            //找到了指定View\n            if (view == viewRef.get()) {\n                mSkinViewList.remove(viewRef);\n                break;\n            }\n        }\n\n        return this;\n    }\n\n    @Override\n    public IWindowViewManager clear() {\n        if(CollectionUtils.isEmpty(mSkinViewList)) {\n            return this;\n        }\n\n        mSkinViewList.clear();\n        return this;\n    }\n\n    @Override\n    public void applySkinForViews(boolean applyChild) {\n        if(CollectionUtils.isEmpty(mSkinViewList)) {\n            return;\n        }\n\n        for(WeakReference<View> viewRef : mSkinViewList) {\n            if(null == viewRef || null == viewRef.get()) {\n                continue;\n            }\n\n            SkinManager.getInstance().applySkin(viewRef.get(), applyChild);\n        }\n    }\n\n    @Override\n    public List<View> getWindowViewList() {\n        ArrayList<View> resultList = new ArrayList<View>();\n\n        ArrayList<WeakReference<View>> tmpList\n                = (ArrayList<WeakReference<View>>) mSkinViewList.clone();\n        for (WeakReference<View> viewRef : tmpList) {\n            if (null == viewRef || null == viewRef.get()) {\n                continue;\n            }\n\n            resultList.add(viewRef.get());\n        }\n\n        return resultList;\n    }\n}\n"
  },
  {
    "path": "QSkinLoaderlib/src/main/java/org/qcode/qskinloader/resourceloader/ILoadResourceCallback.java",
    "content": "package org.qcode.qskinloader.resourceloader;\n\nimport org.qcode.qskinloader.IResourceManager;\n\n/**\n * 加载皮肤资源的回调\n * qqliu\n * 2016/9/25.\n */\npublic interface ILoadResourceCallback {\n    /***\n     * 加载皮肤资源开始\n     *\n     * @param identifier\n     */\n    void onLoadStart(String identifier);\n\n    /***\n     * 加载皮肤资源成功\n     *\n     * @param identifier\n     * @param result\n     */\n    void onLoadSuccess(String identifier, IResourceManager result);\n\n    /***\n     * 加载皮肤资源失败\n     *\n     * @param identifier\n     * @param errorCode\n     */\n    void onLoadFail(String identifier, int errorCode);\n}\n"
  },
  {
    "path": "QSkinLoaderlib/src/main/java/org/qcode/qskinloader/resourceloader/ResourceManager.java",
    "content": "package org.qcode.qskinloader.resourceloader;\n\nimport android.annotation.SuppressLint;\nimport android.content.Context;\nimport android.content.res.ColorStateList;\nimport android.content.res.Resources;\nimport android.graphics.drawable.Drawable;\n\nimport org.qcode.qskinloader.IResourceManager;\nimport org.qcode.qskinloader.base.utils.Logging;\n\n/**\n * 资源管理类的实现，获取资源的行为会代理到其内部的mBase实现\n * <p>\n * qqliu\n * 2016/9/18.\n */\npublic class ResourceManager implements IResourceManager {\n    private static final String TAG = \"ResourceManager\";\n\n    private Context mContext;\n    private Resources mDefaultResources;\n    private IResourceManager mBase;\n    private String mSkinIdentifier;\n\n    public ResourceManager(Context context) {\n        mContext = context;\n        mDefaultResources = mContext.getResources();\n    }\n\n    @Override\n    public void setBaseResource(String skinIdentifier,\n                                IResourceManager baseResource) {\n        mSkinIdentifier = skinIdentifier;\n        mBase = baseResource;\n    }\n\n    @Override\n    public boolean isDefault() {\n        if(null != mBase) {\n            return mBase.isDefault();\n        }\n\n        return true;\n    }\n\n    @Override\n    public int getColor(int resId) {\n        if (null != mBase) {\n            try {\n                return mBase.getColor(resId);\n            } catch (Exception ex) {\n                Logging.d(TAG, \"getColor()| error happened\", ex);\n            }\n        }\n\n        return mDefaultResources.getColor(resId);\n    }\n\n    @Override\n    public int getColor(int resId, String resName) {\n        if (null != mBase) {\n            try {\n                return mBase.getColor(resId, resName);\n            } catch (Exception ex) {\n                Logging.d(TAG, \"getColor()| error happened\", ex);\n            }\n        }\n\n        return mDefaultResources.getColor(resId);\n    }\n\n    @Override\n    public ColorStateList getColorStateList(int resId) {\n        if (null != mBase) {\n            try {\n                return mBase.getColorStateList(resId);\n            } catch (Exception ex) {\n                Logging.d(TAG, \"getColorStateList()| error happened\", ex);\n            }\n        }\n\n        return convertToColorStateList(resId);\n    }\n\n    @Override\n    public ColorStateList getColorStateList(int resId, String resName) {\n        if (null != mBase) {\n            try {\n                return mBase.getColorStateList(resId, resName);\n            } catch (Exception ex) {\n                Logging.d(TAG, \"getColorStateList()| error happened\", ex);\n            }\n        }\n\n        return convertToColorStateList(resId);\n    }\n\n    @Override\n    public ColorStateList getColorStateList(int resId, String typeName, String resName) {\n        if (null != mBase) {\n            try {\n                return mBase.getColorStateList(resId, typeName, resName);\n            } catch (Exception ex) {\n                Logging.d(TAG, \"getColorStateList()| error happened\", ex);\n            }\n        }\n\n        return convertToColorStateList(resId);\n    }\n\n    public Drawable getDrawable(int resId) {\n        if (null != mBase) {\n            try {\n                return mBase.getDrawable(resId);\n            } catch (Exception ex) {\n                Logging.d(TAG, \"getDrawable()| error happened\", ex);\n            }\n        }\n\n        return mDefaultResources.getDrawable(resId);\n    }\n\n    @SuppressLint(\"NewApi\")\n    public Drawable getDrawable(int resId, String resName) {\n        if (null != mBase) {\n            try {\n                return mBase.getDrawable(resId, resName);\n            } catch (Exception ex) {\n                Logging.d(TAG, \"getDrawable()| error happened\", ex);\n            }\n        }\n\n        return mDefaultResources.getDrawable(resId);\n    }\n\n    /**\n     * 加载指定资源颜色drawable,转化为ColorStateList，保证selector类型的Color也能被转换。\n     * 无皮肤包资源返回默认主题颜色\n     *\n     * @param resId\n     * @return\n     */\n    private ColorStateList convertToColorStateList(int resId) {\n        ColorStateList colorList = null;\n        try {\n            colorList = mDefaultResources.getColorStateList(resId);\n        } catch (Resources.NotFoundException ex) {\n            Logging.d(TAG, \"convertToColorStateList()| error happened\", ex);\n        }\n\n        if (colorList != null) {\n            return colorList;\n        }\n\n        int[][] states = new int[1][1];\n        colorList =\n                new ColorStateList(states, new int[]{\n                        mDefaultResources.getColor(resId)});\n        return colorList;\n    }\n\n    @Override\n    public String getSkinIdentifier() {\n        return mSkinIdentifier;\n    }\n}\n"
  },
  {
    "path": "QSkinLoaderlib/src/main/java/org/qcode/qskinloader/resourceloader/impl/APKResourceLoader.java",
    "content": "package org.qcode.qskinloader.resourceloader.impl;\n\nimport android.content.Context;\nimport android.content.pm.PackageInfo;\nimport android.content.pm.PackageManager;\nimport android.content.res.AssetManager;\nimport android.content.res.Resources;\nimport android.os.AsyncTask;\n\nimport org.qcode.qskinloader.resourceloader.ILoadResourceCallback;\nimport org.qcode.qskinloader.IResourceLoader;\nimport org.qcode.qskinloader.base.utils.Logging;\nimport org.qcode.qskinloader.base.utils.StringUtils;\n\nimport java.io.File;\nimport java.lang.reflect.Method;\n\n/**\n * 基于APK方式的资源加载器\n * qqliu\n * 2016/9/25.\n */\npublic class APKResourceLoader implements IResourceLoader {\n\n    private static final String TAG = \"APKResourceLoader\";\n\n    private Context mContext;\n\n    private String mPackageName;\n\n    private Resources mResources;\n\n    public APKResourceLoader(Context context) {\n        this.mContext = context;\n    }\n\n    @Override\n    public void loadResource(final String skinIdentifier,\n                             final ILoadResourceCallback loadCallBack) {\n        if (StringUtils.isEmpty(skinIdentifier)) {\n            return;\n        }\n\n        new AsyncTask<String, Void, APkLoadResult>() {\n\n            @Override\n            protected void onPreExecute() {\n                if (loadCallBack != null) {\n                    loadCallBack.onLoadStart(skinIdentifier);\n                }\n            }\n\n            @Override\n            protected APkLoadResult doInBackground(String... params) {\n                if (null == mContext || null == params || params.length <= 0) {\n                    return null;\n                }\n\n                try {\n                    String skinPkgPath = params[0];\n\n                    File file = new File(skinPkgPath);\n                    if (file == null || !file.exists()) {\n                        return null;\n                    }\n\n                    PackageManager packageManager = mContext.getPackageManager();\n                    PackageInfo packageInfo = packageManager.getPackageArchiveInfo(\n                            skinPkgPath, PackageManager.GET_ACTIVITIES);\n                    String skinPkgName = packageInfo.packageName;\n\n                    AssetManager assetManager = AssetManager.class.newInstance();\n                    Method addAssetPath = assetManager.getClass().getMethod(\"addAssetPath\", String.class);\n                    addAssetPath.invoke(assetManager, skinPkgPath);\n\n                    Resources superResources = mContext.getResources();\n                    Resources skinResource = new Resources(\n                            assetManager, superResources.getDisplayMetrics(),\n                            superResources.getConfiguration());\n                    return new APkLoadResult(skinPkgName, skinResource);\n                } catch (Exception ex) {\n                    Logging.d(TAG, \"doInBackground()| exception happened\", ex);\n                }\n\n                return null;\n            }\n\n            @Override\n            protected void onPostExecute(APkLoadResult result) {\n                if (null != result) {\n                    if (loadCallBack != null) {\n                        loadCallBack.onLoadSuccess(skinIdentifier,\n                                new APKResourceManager(\n                                        mContext, result.pkgName, result.resources));\n                    }\n                } else {\n                    if (loadCallBack != null) {\n                        loadCallBack.onLoadFail(skinIdentifier, -1);\n                    }\n                }\n            }\n\n        }.execute(skinIdentifier);\n    }\n\n    private static class APkLoadResult {\n        String pkgName;\n        Resources resources;\n\n        public APkLoadResult(String pkgName, Resources resources) {\n            this.pkgName = pkgName;\n            this.resources = resources;\n        }\n    }\n}\n"
  },
  {
    "path": "QSkinLoaderlib/src/main/java/org/qcode/qskinloader/resourceloader/impl/APKResourceManager.java",
    "content": "package org.qcode.qskinloader.resourceloader.impl;\n\nimport android.annotation.SuppressLint;\nimport android.content.Context;\nimport android.content.res.ColorStateList;\nimport android.content.res.Resources;\nimport android.graphics.drawable.Drawable;\n\nimport org.qcode.qskinloader.IResourceManager;\nimport org.qcode.qskinloader.base.utils.HashMapCache;\nimport org.qcode.qskinloader.entity.SkinConstant;\n\n/**\n * 基于APK方式的资源管理类\n * qqliu\n * 2016/9/18.\n */\npublic class APKResourceManager implements IResourceManager {\n    private static final String TAG = \"APKResourceManager\";\n\n    private Context mContext;\n    private Resources mDefaultResources;\n    private String mPackageName;\n    private Resources mResources;\n\n    private HashMapCache<String, Integer> mColorCache\n            = new HashMapCache<String, Integer>(true);\n\n    public APKResourceManager(Context context, String pkgName, Resources resources) {\n        mContext = context;\n        mDefaultResources = mContext.getResources();\n        mPackageName = pkgName;\n        mResources = resources;\n    }\n\n    @Override\n    public void setBaseResource(String skinIdentifier, IResourceManager baseResource) {\n        //do nothing\n    }\n\n    @Override\n    public String getSkinIdentifier() {\n        return null;\n    }\n\n    @Override\n    public boolean isDefault() {\n        return false;\n    }\n\n    @Override\n    public int getColor(int resId) {\n        String resName = mDefaultResources.getResourceEntryName(resId);\n        return getColor(resId, resName);\n    }\n\n    @Override\n    public int getColor(int resId, String resName) {\n        String resKey = getResKey(mPackageName, resName);\n\n        Integer color = mColorCache.getCache(resKey);\n        if (null != color) {\n            return color;\n        }\n\n        int trueResId = mResources.getIdentifier(\n                resName, SkinConstant.RES_TYPE_NAME_COLOR, mPackageName);\n        int trueColor = mResources.getColor(trueResId);\n        mColorCache.addCache(resKey, trueColor);\n        return trueColor;\n    }\n\n    public Drawable getDrawable(int resId) {\n        String resName = mDefaultResources.getResourceEntryName(resId);\n        return getDrawable(resId, resName);\n    }\n\n    @SuppressLint(\"NewApi\")\n    public Drawable getDrawable(int resId, String resName) {\n        int trueResId = mResources.getIdentifier(resName,\n                SkinConstant.RES_TYPE_NAME_DRAWABLE, mPackageName);\n\n        if (0 == trueResId) {\n            trueResId = mResources.getIdentifier(resName,\n                    SkinConstant.RES_TYPE_NAME_MIPMAP, mPackageName);\n            if (0 == trueResId) {\n                throw new Resources.NotFoundException(resName);\n            }\n        }\n\n        Drawable trueDrawable;\n        if (android.os.Build.VERSION.SDK_INT < 22) {\n            trueDrawable = mResources.getDrawable(trueResId);\n        } else {\n            trueDrawable = mResources.getDrawable(trueResId, null);\n        }\n\n        return trueDrawable;\n    }\n\n    @Override\n    public ColorStateList getColorStateList(int resId) {\n        String resName = mDefaultResources.getResourceEntryName(resId);\n        return getColorStateList(resId, resName);\n    }\n\n    /**\n     * 读取ColorStateList\n     * @param resId\n     * @return\n     */\n    @Override\n    public ColorStateList getColorStateList(int resId, String resName) {\n        return getColorStateList(resId, SkinConstant.RES_TYPE_NAME_COLOR, resName);\n    }\n\n    @Override\n    public ColorStateList getColorStateList(int resId, String typeName, String resName) {\n        int trueResId = mResources.getIdentifier(\n                resName, typeName, mPackageName);\n        ColorStateList colorList = mResources.getColorStateList(trueResId);\n\n        return colorList;\n    }\n\n    private String getResKey(String skinPackageName, String resName) {\n        return (null == skinPackageName ? \"\" : skinPackageName) + \"_\" + resName;\n    }\n}\n"
  },
  {
    "path": "QSkinLoaderlib/src/main/java/org/qcode/qskinloader/resourceloader/impl/ConfigChangeResourceLoader.java",
    "content": "package org.qcode.qskinloader.resourceloader.impl;\n\nimport android.content.Context;\nimport android.content.res.Configuration;\nimport android.content.res.Resources;\nimport android.util.DisplayMetrics;\n\nimport org.qcode.qskinloader.IResourceLoader;\nimport org.qcode.qskinloader.base.utils.StringUtils;\nimport org.qcode.qskinloader.resourceloader.ILoadResourceCallback;\n\n/**\n * 基于Android原生夜间模式的ConfigChange方式的资源加载器\n * qqliu\n * 2016/10/9.\n */\npublic class ConfigChangeResourceLoader implements IResourceLoader {\n\n    private static final String TAG = \"ConfigChangeResourceLoader\";\n\n    public static final String MODE_DAY = \"day\";\n    public static final String MODE_NIGHT = \"night\";\n\n    private Context mContext;\n\n    public ConfigChangeResourceLoader(Context context) {\n        this.mContext = context;\n    }\n\n    @Override\n    public void loadResource(final String skinIdentifier,\n                             final ILoadResourceCallback loadCallBack) {\n        if (StringUtils.isEmpty(skinIdentifier)) {\n            return;\n        }\n\n        if (loadCallBack != null) {\n            loadCallBack.onLoadStart(skinIdentifier);\n        }\n\n        switchMode(MODE_NIGHT.equals(skinIdentifier));\n\n        if (loadCallBack != null) {\n            loadCallBack.onLoadSuccess(skinIdentifier,\n                    new ConfigChangeResourceManager(mContext, skinIdentifier));\n        }\n    }\n\n    private void switchMode(boolean isNightMode) {\n        Resources resources = mContext.getResources();\n        DisplayMetrics dm = resources.getDisplayMetrics();\n        Configuration config = resources.getConfiguration();\n        config.uiMode &= ~Configuration.UI_MODE_NIGHT_MASK;\n        config.uiMode |= isNightMode ?\n                Configuration.UI_MODE_NIGHT_YES :\n                Configuration.UI_MODE_NIGHT_NO;\n        resources.updateConfiguration(config, dm);\n    }\n}\n"
  },
  {
    "path": "QSkinLoaderlib/src/main/java/org/qcode/qskinloader/resourceloader/impl/ConfigChangeResourceManager.java",
    "content": "package org.qcode.qskinloader.resourceloader.impl;\n\nimport android.annotation.SuppressLint;\nimport android.content.Context;\nimport android.content.res.ColorStateList;\nimport android.content.res.Resources;\nimport android.graphics.drawable.Drawable;\n\nimport org.qcode.qskinloader.IResourceManager;\n\n/**\n * 基于资Android原生夜间模式的ConfigChange方式的资源管理类\n * qqliu\n * 2016/10/9.\n */\npublic class ConfigChangeResourceManager implements IResourceManager {\n    private static final String TAG = \"ConfigChangeResourceManager\";\n\n    private Context mContext;\n    private String mSkinIdentifier;\n    private Resources mResources;\n\n    public ConfigChangeResourceManager(Context context, String skinIdentifier) {\n        mContext = context;\n        mResources = mContext.getResources();\n        mSkinIdentifier = skinIdentifier;\n    }\n\n    @Override\n    public void setBaseResource(String skinIdentifier, IResourceManager baseResource) {\n        //do nothing\n    }\n\n    @Override\n    public String getSkinIdentifier() {\n        return mSkinIdentifier;\n    }\n\n    @Override\n    public boolean isDefault() {\n        return ConfigChangeResourceLoader.MODE_DAY.equals(mSkinIdentifier);\n    }\n\n    @Override\n    public int getColor(int resId) {\n        return mResources.getColor(resId);\n    }\n\n    @Override\n    public int getColor(int resId, String resName) {\n        return mResources.getColor(resId);\n    }\n\n    public Drawable getDrawable(int resId) {\n        return mResources.getDrawable(resId);\n    }\n\n    @SuppressLint(\"NewApi\")\n    public Drawable getDrawable(int resId, String resName) {\n        return mResources.getDrawable(resId);\n    }\n\n    @Override\n    public ColorStateList getColorStateList(int resId) {\n        return mResources.getColorStateList(resId);\n    }\n\n    @Override\n    public ColorStateList getColorStateList(int resId, String resName) {\n        return mResources.getColorStateList(resId);\n    }\n\n    @Override\n    public ColorStateList getColorStateList(int resId, String typeName, String resName) {\n        return mResources.getColorStateList(resId);\n    }\n}\n"
  },
  {
    "path": "QSkinLoaderlib/src/main/java/org/qcode/qskinloader/resourceloader/impl/SuffixResourceLoader.java",
    "content": "package org.qcode.qskinloader.resourceloader.impl;\n\nimport android.content.Context;\n\nimport org.qcode.qskinloader.IResourceLoader;\nimport org.qcode.qskinloader.base.utils.StringUtils;\nimport org.qcode.qskinloader.resourceloader.ILoadResourceCallback;\n\n/**\n * 基于资源名称后缀拼接方式的资源加载器\n * qqliu\n * 2016/10/9.\n */\npublic class SuffixResourceLoader implements IResourceLoader {\n\n    private static final String TAG = \"SuffixResourceLoader\";\n\n    private Context mContext;\n\n    private String mSkinSuffix;\n\n    public SuffixResourceLoader(Context context) {\n        this.mContext = context;\n    }\n\n    @Override\n    public void loadResource(final String skinIdentifier,\n                             final ILoadResourceCallback loadCallBack) {\n        if (StringUtils.isEmpty(skinIdentifier)) {\n            return;\n        }\n\n        if (loadCallBack != null) {\n            loadCallBack.onLoadStart(skinIdentifier);\n        }\n\n        mSkinSuffix = skinIdentifier;\n\n        if (loadCallBack != null) {\n            loadCallBack.onLoadSuccess(skinIdentifier,\n                    new SuffixResourceManager(mContext, mSkinSuffix));\n        }\n    }\n}\n"
  },
  {
    "path": "QSkinLoaderlib/src/main/java/org/qcode/qskinloader/resourceloader/impl/SuffixResourceManager.java",
    "content": "package org.qcode.qskinloader.resourceloader.impl;\n\nimport android.annotation.SuppressLint;\nimport android.content.Context;\nimport android.content.res.ColorStateList;\nimport android.content.res.Resources;\nimport android.graphics.drawable.Drawable;\n\nimport org.qcode.qskinloader.IResourceManager;\nimport org.qcode.qskinloader.base.utils.HashMapCache;\nimport org.qcode.qskinloader.entity.SkinConstant;\n\n/**\n * 基于资源名称后缀拼接方式的资源管理类\n * qqliu\n * 2016/10/9.\n */\npublic class SuffixResourceManager implements IResourceManager {\n    private static final String TAG = \"SuffixResourceManager\";\n\n    private Context mContext;\n    private Resources mResources;\n    private String mSkinSuffix;\n    private String mPackageName;\n\n    private HashMapCache<String, Integer> mColorCache\n            = new HashMapCache<String, Integer>(true);\n\n    public SuffixResourceManager(Context context, String skinSuffix) {\n        mContext = context;\n        mPackageName = mContext.getPackageName();\n        mResources = mContext.getResources();\n        mSkinSuffix = skinSuffix;\n    }\n\n    @Override\n    public void setBaseResource(String skinIdentifier, IResourceManager baseResource) {\n        //do nothing\n    }\n\n    @Override\n    public String getSkinIdentifier() {\n        return mSkinSuffix;\n    }\n\n    @Override\n    public boolean isDefault() {\n        return false;\n    }\n\n    @Override\n    public int getColor(int resId) {\n        String resName = mResources.getResourceEntryName(resId);\n        return getColor(resId, resName);\n    }\n\n    @Override\n    public int getColor(int resId, String resName) {\n        Integer color = mColorCache.getCache(resName);\n        if (null != color) {\n            return color;\n        }\n\n        String trueResName = appendSuffix(resName);\n        int trueResId = mResources.getIdentifier(\n                trueResName,\n                SkinConstant.RES_TYPE_NAME_COLOR,\n                mPackageName);\n        int trueColor = mResources.getColor(trueResId);\n        mColorCache.addCache(trueResName, trueColor);\n        return trueColor;\n    }\n\n    public Drawable getDrawable(int resId) {\n        String resName = mResources.getResourceEntryName(resId);\n        return getDrawable(resId, resName);\n    }\n\n    @SuppressLint(\"NewApi\")\n    public Drawable getDrawable(int resId, String resName) {\n        String trueResName = appendSuffix(resName);\n        int trueResId = mResources.getIdentifier(\n                trueResName,\n                SkinConstant.RES_TYPE_NAME_DRAWABLE,\n                mPackageName);\n\n        if (0 == trueResId) {\n            trueResId = mResources.getIdentifier(\n                    trueResName,\n                    SkinConstant.RES_TYPE_NAME_MIPMAP,\n                    mPackageName);\n            if (0 == trueResId) {\n                throw new Resources.NotFoundException(resName);\n            }\n        }\n\n        Drawable trueDrawable;\n        if (android.os.Build.VERSION.SDK_INT < 22) {\n            trueDrawable = mResources.getDrawable(trueResId);\n        } else {\n            trueDrawable = mResources.getDrawable(trueResId, null);\n        }\n\n        return trueDrawable;\n    }\n\n    @Override\n    public ColorStateList getColorStateList(int resId) {\n        String resName = mResources.getResourceEntryName(resId);\n        return getColorStateList(resId, resName);\n    }\n\n    /**\n     * 加载指定资源颜色drawable,转化为ColorStateList，保证selector类型的Color也能被转换。\n     * 无皮肤包资源返回默认主题颜色\n     *\n     * @param resId\n     * @return\n     */\n    @Override\n    public ColorStateList getColorStateList(int resId, String resName) {\n        return getColorStateList(resId, SkinConstant.RES_TYPE_NAME_COLOR, resName);\n    }\n\n    @Override\n    public ColorStateList getColorStateList(int resId, String typeName, String resName) {\n        String trueResName = appendSuffix(resName);\n        int trueResId = mResources.getIdentifier(\n                trueResName,\n                typeName,\n                mPackageName);\n        ColorStateList colorList = mResources.getColorStateList(trueResId);\n\n        return colorList;\n    }\n\n    private String appendSuffix(String resName) {\n        return resName + mSkinSuffix;\n    }\n}\n"
  },
  {
    "path": "QSkinLoaderlib/src/main/java/org/qcode/qskinloader/view/ShadowImageView.java",
    "content": "package org.qcode.qskinloader.view;\n\nimport android.content.Context;\nimport android.graphics.Canvas;\nimport android.graphics.PorterDuff;\nimport android.graphics.PorterDuffColorFilter;\nimport android.graphics.drawable.Drawable;\nimport android.util.AttributeSet;\nimport android.widget.ImageView;\n\nimport java.lang.ref.WeakReference;\n\n/**\n * 支持蒙层的ImageView，建议使用此类作为蒙层处理的ImageView的替代，\n * 给ImageView设置ColorFilter可能会失败，此类为drawable设置蒙层，效果更好点。\n *\n * qqliu\n * 2016/9/28.\n */\npublic class ShadowImageView extends ImageView {\n\n    private PorterDuffColorFilter mColorFilter;\n\n    private boolean hasFilterSet = true;\n    private WeakReference<Drawable> mSetFilteredDrawable = null;\n\n    public ShadowImageView(Context context) {\n        this(context, null);\n    }\n\n    public ShadowImageView(Context context, AttributeSet attrs) {\n        this(context, attrs, 0);\n    }\n\n    public ShadowImageView(Context context, AttributeSet attrs, int defStyleAttr) {\n        super(context, attrs, defStyleAttr);\n    }\n\n    public void setShadowColor(int color) {\n        hasFilterSet = false;\n        mColorFilter = new PorterDuffColorFilter(color, PorterDuff.Mode.MULTIPLY);\n        invalidate();\n    }\n\n    @Override\n    protected void onDraw(Canvas canvas) {\n        Drawable drawable = getDrawable();\n        if(null != mSetFilteredDrawable && drawable != mSetFilteredDrawable.get()) {\n            //drawable 发生了变化，重新设置filter\n            hasFilterSet = false;\n        }\n\n        if(!hasFilterSet) {\n            if (null != drawable) {\n                drawable.setColorFilter(mColorFilter);\n                mSetFilteredDrawable = new WeakReference<Drawable>(drawable);\n                hasFilterSet = true;\n            }\n        }\n\n        super.onDraw(canvas);\n    }\n}\n"
  },
  {
    "path": "QSkinLoaderlib/src/main/res/values/skin_attrs.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <!-- 设置此属性标识View支持换肤;this tag indicates a view supports skin change-->\n    <attr name=\"enable\" format=\"boolean\" />\n    <!-- 设置此属性标识View需要绘制阴影; this tag indicates a view should draw a shadow when skin changing-->\n    <attr name=\"drawShadow\" format=\"color|reference\" />\n</resources>"
  },
  {
    "path": "QSkinLoaderlib/src/main/res/values/skin_ids.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <!--皮肤属性在View内存放时的Tag id; the tag id used to save skin attrs in view's tag-->\n    <item name=\"tag_skin_attr\" type=\"id\"/>\n</resources>"
  },
  {
    "path": "QSkinLoaderlib/src/main/res/values/strings.xml",
    "content": "<resources>\n    <string name=\"app_name\">QSkinLoaderLib</string>\n</resources>\n"
  },
  {
    "path": "README.md",
    "content": "# QSkinLoader换肤框架\n\n**如何在一个成熟的应用内换肤？** 请参见文章：[链接](http://blog.csdn.net/u013478336/article/details/78993969)。\n\n**README分三部分：基本简介、使用方法、框架由来与架构设计。\n如果不嫌麻烦，还可以去看文章[夜间模式方案调研](http://blog.csdn.net/u013478336/article/details/52484322)和[QSkinLoader框架介绍](http://blog.csdn.net/u013478336/article/details/53083054)**\n\n#**效果图**\n![QSkinLoader实现夜间模式效果图](https://github.com/qqliu10u/QSkinLoader/blob/master/skin-change-demo.gif)\n\n#**基本简介：**\nQSkinLoader是一个支持多种场景的Android换肤框架。基本原理是通过代理LayoutInflater的View创建过程解析皮肤相关属性（background/src/textColor等），将皮肤相关属性设置到View的Tag内，在切换皮肤时寻找对应的皮肤来完成实时刷新动作。此方案具有代码及XML侵入性小、功能完善（支持Activity/Dialog/悬浮窗/PopWindow等）、无需重启Activity、支持自定义属性换肤、同时支持资源内换肤和独立资源包（下载后换肤）等优点。\n\n#**使用方法**\n## 基本使用\n由于可以自定义皮肤资源加载过程，QSkinLoader框架内并未提供当前皮肤的保存逻辑（不能支持loadCurrentSkin之类的接口）。因此建议使用框架时封装两个类：一个负责保存当前皮肤（保存皮肤其实就是SharePreference持久化存储，此处略去），一个负责与框架交互，如下：\n```Java\npublic void init(Context context) {\n    SkinManager.getInstance().init(context);\n}\n\npublic void switchSkinMode(OnSkinChangeListener listener) {\n    mIsSwitching = true;\n    mIsDefaultMode = !mIsDefaultMode;\n    refreshSkin(listener);\n}\n\npublic void refreshSkin(OnSkinChangeListener listener) {\n    if (mIsDefaultMode) {\n        //恢复到默认皮肤\n        SkinManager.getInstance().restoreDefault(SkinConstant.DEFAULT_SKIN, new MyLoadSkinListener(listener));\n    } else {\n        changeSkin(listener);\n    }\n}\n\nprivate void changeSkin(OnSkinChangeListener listener) {\n    SkinManager.getInstance().loadSkin(\"_night\",\n            new SuffixResourceLoader(mContext),\n            new MyLoadSkinListener(listener));\n}\n```\n具体代码此处不完全贴出了，工程内有详细的代码。\n\n###1. 框架初始化\n在Application创建过程中执行框架的初始化：\n```Java\n// 初始化皮肤框架\nSkinChangeHelper.getInstance().init(this);\n//初始化上次缓存的皮肤\nSkinChangeHelper.getInstance().refreshSkin(null);\n```\n初始化了框架后需要调用refreshSkin来加载上一次的皮肤设置，refreshSkin加载完成皮肤后会通知各Activity界面刷新皮肤设置，由于此处在Application初始化时调用，可能加载完成皮肤设置后界面仍未初始化，这并不无影响，因为Activity初始化时会主动执行一次换肤操作，弥补此过程的缺失。\n\n###2. Activity初始化与各生命周期调用\n因为换肤一般是整个应用都需要执行的过程，此处建议实现一个基础类(BaseActivity)来封装换肤相关逻辑，此类建议实现接口ISkinActivity，告知是否支持换肤，以及在换肤操作触发后如果界面不在前台是否立刻换肤：\n```Java\n@Override\npublic boolean isSupportSkinChange() {\n    //告知当前界面是否支持换肤：true支持换肤，false不支持\n    return true;\n}\n\n@Override\npublic boolean isSwitchSkinImmediately() {\n    //告知当切换皮肤时，是否立刻刷新当前界面；true立刻刷新，false表示在界面onResume时刷新；\n    //减轻换肤时性能压力\n    return false;\n}\n\n@Override\npublic void handleSkinChange() {\n    //当前界面在换肤时收到的回调，可以在此回调内做一些其他事情；\n    //比如：通知WebView内的页面切换到夜间模式等\n}\n```\n然后在Activity的onCreate中执行IActivitySkinEventHandler的初始化与配置工作：\n```Java\n//初始化并配置IActivitySkinEventHandler，应在IActivitySkinEventHandler.onCreate之前执行\nmSkinEventHandler = SkinManager.newActivitySkinEventHandler()\n        .setSwitchSkinImmediately(isSwitchSkinImmediately())\n        .setSupportSkinChange(isSupportSkinChange())\n        .setWindowBackgroundResource(getWindowBackgroundResource())\n        .setNeedDelegateViewCreate(false);\n//通知框架onCreate事件\nmSkinEventHandler.onCreate(this);\n```\n其中，**setWindowBackgroundResource**用于设置Activity的背景色，在换肤时会寻找对应的背景色替换之，此处传入的不能是色值，只支持引用，类似R.color.xx。\n**setNeedDelegateViewCreate**用于设置是否需要代理View创建，因为LayoutInflater的代理View创建Factory只支持设置一次，如果外部已经设置了Factory，则框架内再次设置会引起崩溃，所以框架使用配置与回调来处理此问题。具体见高级使用部分。\n\n其他生命周期回调基本类似，挑两个做实例，如下：\n```Java\n@Override\nprotected void onResume() {\n    super.onResume();\n    //皮肤相关，此通知放在此处，尽量让子类的view都添加到view树内\n    if (mFirstTimeApplySkin) {\n        mSkinEventHandler.onViewCreated();\n        mFirstTimeApplySkin = false;\n    }\n    //皮肤相关\n    mSkinEventHandler.onResume();\n}\n\n@Override\npublic void onWindowFocusChanged(boolean hasFocus) {\n    super.onWindowFocusChanged(hasFocus);\n\n    //皮肤相关\n    mSkinEventHandler.onWindowFocusChanged(hasFocus);\n}\n```\n###3. XML配置\nQSkinLoader只支持引用型资源换肤，所有的颜色定义都应定义在colors.xml内，在使用时引用。\n对于一个布局，需要定义一个skin的命名空间：\n```XML\nxmlns:skin=\"http://schemas.android.com/android/skin\"\n```\n然后对所有需要换肤的View增加属性：\n```XML\nskin:enable=\"true\"\n```\n即可完成换肤配置。举例如下：\n```XML\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:skin=\"http://schemas.android.com/android/skin\"\n    android:orientation=\"horizontal\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    skin:enable=\"true\"\n    android:gravity=\"center_vertical\"\n    android:background=\"@color/color_background\">\n    <ImageView\n        android:layout_width=\"50dp\"\n        android:layout_height=\"50dp\"\n        android:src=\"@mipmap/ic_launcher\"/>\n\n    <TextView\n        android:id=\"@+id/list_item_text_view\"\n        android:layout_width=\"100dp\"\n        android:layout_height=\"50dp\"\n        android:textColor=\"@color/color_text\"\n        skin:enable=\"true\"/>\n</LinearLayout>\n```\n在这段布局内，框架代理创建Linearlayout时会解析其background属性，代理创建View时不解析任何属性，代理创建TextView时会解析textColor属性。\n\n###4. 图片蒙层\n对ImageView/ImageButton可以配置属性：\n```XML\nskin:drawShadow=\"@color/night_shadow_color\"\n```\n来支持图片蒙层，night_shadow_color是一个颜色引用，在默认情况下建议使用透明色，同时在皮肤包内定义此值为另一个色值（不必须是半透明色）。\n需要注意的是：蒙层的原理是ImageView的ColorFilter，有时候对ImageView设置ColorFilter会失效。但是对Drawable设置ColorFilter基本都会生效，所以如果是对ImageView的Src属性做蒙层，建议使用框架内的ShadowImageView替代ImageView。如下：\n```XML\n<org.qcode.qskinloader.view.ShadowImageView\n            android:id=\"@+id/logo\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_centerVertical=\"true\"\n            skin:enable=\"true\"\n            skin:drawShadow=\"@color/news_pic_night_shadow_color\"/>\n```\n\n###5. 换肤与恢复默认皮肤\n**先来看看换肤操作：**\n```Java\nSkinManager.getInstance().loadSkin(\"_night\",\n            new SuffixResourceLoader(mContext),\n            new MyLoadSkinListener(listener));\n```\n这是基于资源后缀的换肤方式，对于R.color.color_text，切换到夜间模式时，框架会去找R.color.color_text_night作为夜间模式的资源。\nQSkinLoader换肤框架还支持另一种默认的换肤方式——APK资源换肤，也就是将资源文件定义在独立的APK文件内，此文件可从服务端下载，从而真正实现动态换肤。此方式对现有工程的影响比较小，非常值得推荐。具体方式如下：\n```Java\nSkinUtils.copyAssetSkin(mContext);\nFile skin = new File(SkinUtils.getTotalSkinPath(mContext));\nSkinManager.getInstance().loadAPKSkin(\n        skin.getAbsolutePath(), new MyLoadSkinListener(listener));\n```\n当然也可以写成：\n```Java\nSkinManager.getInstance().loadSkin(skin.getAbsolutePath(),\n            new APKResourceLoader(mContext),\n            new MyLoadSkinListener(listener));\n```\n此时对于资源R.color.color_text，框架会去skin路径的APK文件内寻找对于的资源R.color.color_text，找不到就继续使用当前应用的color_text资源。\n自定义皮肤加载过程见高级使用部分。\n\n**那么怎么恢复默认皮肤呢？**\n```Java\n//恢复到默认皮肤\nSkinManager.getInstance().restoreDefault(SkinConstant.DEFAULT_SKIN,\n        new MyLoadSkinListener(listener));\n```\nDEFAULT_SKIN值对框架而言并无意义，框架只是把此值回调到ILoadSkinListener使外部知道当前加载的是默认皮肤，所以此值是在**框架外**定义的。\n\n###6. 动态创建View的皮肤设置\n上文中指出的使用方式是基于XML配置的，如果是在Java代码内如何使用呢？\nQSkinLoader框架提供了一个帮助类ISkinViewHelper来添加/删除View的皮肤属性。此类设计为链式编程方式，提供的接口有：\n```Java\nISkinViewHelper setViewAttrs(String attrName, int resId);\nISkinViewHelper setViewAttrs(DynamicAttr... dynamicAttrs);\nISkinViewHelper setViewAttrs(SkinAttr... skinAttrs);\nISkinViewHelper setViewAttrs(List<DynamicAttr> dynamicAttrs);\n\nISkinViewHelper addViewAttrs(String attrName, int resId);\nISkinViewHelper addViewAttrs(DynamicAttr... dynamicAttrs);\nISkinViewHelper addViewAttrs(SkinAttr... skinAttrs);\nISkinViewHelper addViewAttrs(List<DynamicAttr> dynamicAttrs);\n\nISkinViewHelper cleanAttrs(boolean clearChild);\nvoid applySkin(boolean applyChild);\n```\n如果View本身已经有了皮肤属性，setViewAttrs接口会替换已有的皮肤属性，而addViewAttrs不会覆盖已有属性，而是在已有的皮肤属性内添加新的属性。\ncleanAttrs会清除View的所有皮肤属性，如果传入clearChild为true则遍历所有子元素清除皮肤属性，false只清除自身属性。\napplySkin则对当前View应用皮肤设置，如果传入applyChild为true则遍历所有子元素应用皮肤，false只应用自身。\n假设对一个TextView，动态设置View的皮肤属性大致如下：\n```Java\nSkinManager\n    .with(textview)\n    .setViewAttrs(SkinAttrName.BACKGROUND, R.color.white)\n    .addViewAttrs(SkinAttrName.TEXT_COLOR, R.color.black)\n    .applySkin(false);\n```\n所有框架支持的属性名称都定义在SkinAttrName内，如果需要扩展属性支持，建议参考自定义View属性处理器部分。\n\n###7. 特定View的换肤管理\n上面的换肤过程都是对Activity的View树做遍历换肤操作的，树根是：\n```Java\nactivity.findViewById(android.R.id.content);\n```\n所有不在这颗树内的View都不能换肤，哪些View不在换肤范围呢？\nDialog的View、popWindow的View、悬浮窗(WindowManager上直接加View)，目前这三类View要换肤都应该使用特定View的换肤管理模块。\n需要注意的是：Dialog的交互具有排他型，通常在换肤操作时是不展示的，所以一般可以在show接口调用时做换肤，而不使用IWindowViewManager。\n**怎么对特定View进行换肤管理呢？**\n框架提供了IWindowViewManager接口来提供特定View的管理，支持链式编程，接口如下：\n```Java\nIWindowViewManager addWindowView(View view);\nIWindowViewManager removeWindowView(View view);\nIWindowViewManager clear();\nvoid applySkinForViews(boolean applyChild);\nList<View> getWindowViewList();\n```\n接口比较简单，主要是增加/删除/清空全局View列表和应用皮肤的操作。\n使用如下：\n```Java\nView popView = LayoutInflater.from(mContext).inflate(\n    R.layout.news_list_item_pop, null);\nSkinManager.getInstance().applySkin(popView, true);\nSkinManager\n        .getWindowViewManager()\n        .addWindowView(popView);\npopupWindow = new PopupWindow(popView, popWidth, popHeight);\n```\n通常不建议使用applySkinForViews接口，因为它会遍历所有全局View列表的View做遍历，所以替代方式是先对当前View做属性设置，再添加到框架内管理，从而在下次换肤时接口换肤事件。\nIWindowViewManager内的View是弱引用存储的，所以不会发生内存泄露，但建议在View无用的时候从框架内移除特定View。\n\n## 高级使用\n###1. 自定义View属性处理器\n当项目需要自定义View时，一般都会自定义一些属性，这些属性框架是不支持换肤的，此时需要自定义属性处理器并注册到框架内。自定义属性处理器实现接口ISkinAttrHandler，实现方法：\n```Java\nvoid apply(View view,\n        SkinAttr skinAttr,\n        IResourceManager resourceManager);\n```\n下面是一个示例：\n若有一个自定义CustomTextView，使用属性defTextColor来定义文字颜色，如下：\n```XML\n<org.qcode.demo.ui.customattr.CustomTextView\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"50dp\"\n        android:gravity=\"center\"\n        android:text=\"自定义的文字颜色和背景1\"\n        app:defBackground=\"@color/color_background\"\n        app:defTextColor=\"@color/color_text\"\n        skin:enable=\"true\" />\n```\n则其自定义属性处理器为：\n```Java\npublic class DefTextColorAttrHandler implements ISkinAttrHandler {\n    public static final String DEF_TEXT_COLOR = \"defTextColor\";\n\n    @Override\n    public void apply(View view, SkinAttr skinAttr, IResourceManager resourceManager) {\n        if(!(view instanceof CustomTextView)) {\n            //防止在错误的View上设置了此属性\n            return;\n        }\n        CustomTextView tv = (CustomTextView) view;\n        if (RES_TYPE_NAME_COLOR.equals(skinAttr.mAttrValueTypeName)) {\n            if (SkinConstant.RES_TYPE_NAME_COLOR.equals(\n            skinAttr.mAttrValueTypeName)) {\n                try {\n                    //先尝试按照int型颜色解析\n                    int textColor = resourceManager.getColor(\n                            skinAttr.mAttrValueRefId, \n                            skinAttr.mAttrValueRefName);\n                    tv.setTextColor(textColor);\n\n                } catch (Resources.NotFoundException ex) {\n                    //不是int型则按照ColorStateList引用来解析\n                    ColorStateList textColor = \n                    resourceManager.getColorStateList(\n                            skinAttr.mAttrValueRefId, \n                            skinAttr.mAttrValueRefName);\n                    tv.setTextColor(textColor);\n                }\n            }\n        }\n    }\n}\n```\n定义了属性处理器后，再注册到框架内，**注册需要在setContentView之前**：\n```Java\nSkinManager.getInstance().registerSkinAttrHandler(\n        DEF_TEXT_COLOR, new DefTextColorAttrHandler());\n```\n**注意：**自定义属性处理器不一定就是与皮肤相关的属性的处理，也可以是换肤过程中需要对View进行的特定处理。比如RecyclerView换肤的时候要清除内部View缓存（因为其onBindViewHolder不是每次子View显示时都回调），此时，可以定义如下的属性处理器：\n```Java\nclass RecyclerViewClearSubAttrHandler implements ISkinAttrHandler {\n    @Override\n    public void apply(View view, SkinAttr skinAttr, IResourceManager resourceManager) {\n        Field declaredField = view.getDeclaredField(\"mRecycler\");\n        ......\n        RecyclerView.RecycledViewPool recycledViewPool = recyclerView.getRecycledViewPool();\n        recycledViewPool.clear();\n}\n```\n此处代码有删减，具体见框架内的RecyclerViewClearSubAttrHandler处理器。使用时如下：\n```Java\nSkinAttr clearSubAttr = new SkinAttr(SkinAttrName.CLEAR_RECYCLER_VIEW);\nSkinManager\n        .with(view)\n        .addViewAttrs(clearSubAttr);\n```\n###2. 自定义皮肤资源加载\n框架默认支持**资源名称后缀加载、APK加载、Android UIMode Configuration变化**三种换肤方式。集成方式如下：\n```Java\n//后缀加载\nSkinManager.getInstance().loadSkin(\"_night\",\n                new SuffixResourceLoader(mContext),\n                new MyLoadSkinListener(listener));\n                \n//APK皮肤包\nSkinManager.getInstance().loadAPKSkin(\n                skin.getAbsolutePath(), \n                new MyLoadSkinListener(listener));\n                \n//Android UI Configuration变化\nSkinManager.getInstance().loadSkin(\n                ConfigChangeResourceLoader.MODE_NIGHT,\n                new ConfigChangeResourceLoader(mContext),\n                new MyLoadSkinListener(listener));\n```\n如果项目准备采用其他的加载方式，可以通过自定义皮肤资源加载过程来实现。自定义皮肤资源加载的核心是实现IResourceLoader接口，接口只有一个方法：\n```Java\nvoid loadResource(String skinIdentifier,\n                      ILoadResourceCallback loadCallBack);\n```\n也就是定义了从皮肤标识符skinIdentifier加载资源，并在加载过程中通过loadCallBack对外通知加载过程：\n```Java\npublic interface ILoadResourceCallback {\n    void onLoadStart(String identifier);\n    void onLoadSuccess(String identifier, IResourceManager result);\n    void onLoadFail(String identifier, int errorCode);\n}\n```\n加载开始和失败没啥可说的，主要是加载完成后，需要返回一个资源管理类IResourceManager。这个类定义了如何从指定的换肤流程中抽取对应的皮肤资源：\n```Java\npublic interface IResourceManager {\n    String getSkinIdentifier();\n    Drawable getDrawable(int resId, String resName) throws Resources.NotFoundException;\n    int getColor(int resId, String resName) throws Resources.NotFoundException;\n    ColorStateList getColorStateList(int resId, String resName) throws Resources.NotFoundException;\n}\n```\n整个过程比较简单，自定义一个加载过程，再返回一个资源管理类即可。下面以后缀资源加载的方式做个示例（摘录部分代码，具体见工程）：\n```Java\npublic class SuffixResourceLoader implements IResourceLoader {\n    private String mSkinSuffix;\n    \n    @Override\n    public void loadResource(final String skinIdentifier,\n                final ILoadResourceCallback loadCallBack) {\n        //通知加载开始\n        loadCallBack.onLoadStart(skinIdentifier);\n        //后缀存下，加载过程就结束了，不像apk加载，还需要操作AssetManager\n        mSkinSuffix = skinIdentifier;\n        //通知加载结束，返回一个资源管理类SuffixResourceManager\n        loadCallBack.onLoadSuccess(skinIdentifier,\n                    new SuffixResourceManager(mContext, mSkinSuffix));\n    }\n}\n```\n```Java\npublic class SuffixResourceManager implements IResourceManager {\n    private Context mContext;\n    private Resources mResources;\n    private String mSkinSuffix;\n    private String mPackageName;\n\n    private HashMapCache<String, Integer> mColorCache\n            = new HashMapCache<String, Integer>(true);\n\n    public SuffixResourceManager(Context context, String skinSuffix) {\n        mContext = context;\n        mPackageName = mContext.getPackageName();\n        mResources = mContext.getResources();\n        mSkinSuffix = skinSuffix;\n    }\n\n    @Override\n    public int getColor(int resId, String resName) {\n        String trueResName = resName + mSkinSuffix;\n        //找到名字+后缀的id，读取颜色\n        int trueResId = mResources.getIdentifier(\n                trueResName,\n                SkinConstant.RES_TYPE_NAME_COLOR,\n                mPackageName);\n        int trueColor = mResources.getColor(trueResId);\n        return trueColor;\n    }\n    ......\n}\n```\n###3.解决与其他代理View创建过程的冲突\n上文也简要的提到了此问题，对每个Activity的LayoutInflater的setFactory接口（代理View创建与属性解析）只能调用一次，而换肤框架是依赖此操作来完成皮肤属性解析的，因此我们需要设计一套方案在确保框架外已经代理了LayoutInflater后还能保证换肤功能的可用性。\n我们需要保证两点：\n- 如果框架外需要代理View创建，则框架应被告知不能代理View创建，并且提供一个帮助类在外部创建View创建时完成属性解析；\n- 如果框架外不需要代理View创建，但需要解析属性，则提供接口在View创建前后对外回调；\n对于第一点，可以通过IActivitySkinEventHandler.setNeedDelegateViewCreate来告知框架不代理View创建，解析属性的帮助类也可以从IActivitySkinEventHandler内取到，如下：\n```Java\nLayoutInflater.from(this).setFactory(new LayoutInflater.Factory() {\n    @Override\n    public View onCreateView(String name, Context context, AttributeSet attrs) {\n        View view = createView(name, context, attrs);\n        //创建View后通知框架解析属性\n        ISkinAttributeParser parser =\n            mSkinEventHandler.getSkinAttributeParser();\n        if (parser.isSupportSkin(name, context, attrs)) {\n            parser.parseAttribute(view, name, context, attrs);\n        }\n        return view;\n    }\n});\n```\n核心代码就是这段解析属性的逻辑。\n\n对于第二点，我们提供接口IViewCreateListener来监听View创建过程：\n```Java\npublic interface IViewCreateListener {\n        View beforeCreate(String name, Context context, AttributeSet attrs);\n        void afterCreated(View view, String name, Context context, AttributeSet attrs);\n}\n```\nbeforeCreate在View创建之前执行，可以拦截框架的View创建过程，自己创建View，afterCreated在框架创建View后执行，用于框架外进一步处理。\n此接口应通过IActivitySkinEventHandler.setViewCreateListener()设置到框架内使用。\n\n##- 各种View的换肤应用\n###1. ViewPager\nViewPager使用时，应在PagerAdapter的instantiateItem回调中对创建的View应用当前的皮肤。\n```Java\nmViewPager.setAdapter(new PagerAdapter() {\n    ......\n    @Override\n    public Object instantiateItem(ViewGroup container, int position) {\n        View view = onCreatePagerView(position);\n        container.addView(view);\n        //每次实例化某个View时都对其应用皮肤设置\n        SkinManager.with(view).applySkin(true);\n        return view;\n    }\n});\n```\n###2. ListView/GridView\nListView/GridView都继承AbsListView，并使用BaseAdapter作为适配器，其换肤方法为：\n```Java\nlistView.setAdapter(new BaseAdapter() {\n    @Override\n    public View getView(int position, View convertView, ViewGroup parent) {\n        if(null == convertView) {\n            convertView = onCreateContentView(position);\n        }\n        //每次某个子元素需要展示时，都应用当前皮肤设置\n        SkinManager.with(convertView).applySkin(true);\n        return convertView;\n    }\n});\n```\n需要注意的是，如果ListView存在HeaderView或FooterView时，只使用上面的方法是不完善的，如果换肤时HeaderView/FooterView不在ListView内展示，则换肤失效，此时应调用ListView.mRecycler.clear()方法清除View缓存，具体见[上一篇文章](http://blog.csdn.net/u013478336/article/details/52484322)。\n###3. RecyclerView\n上一章也大致讲了RecyclerView换肤的注意事项，由于RecyclerView滑动时，其子元素出现的过程不一定会伴有onBindViewHolder回调，导致我们有时出现两种皮肤并存的问题。因此，使用RecyclerView时换肤一定要清除RecyclerView的缓存。\n```Java\nrecyclerView.setAdapter(new RecyclerView.Adapter() {\n    ......\n    @Override\n    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {\n        //此回调非必执行，但是执行时还是要应用皮肤设置\n        SkinManager.with(holder.itemView).applySkin(true);\n    }\n});\n```\n为了应对RecyclerView清除缓存的问题，框架内定义了一个特殊的属性处理器RecyclerViewClearSubAttrHandler，其作用就是在换肤时，清除RecyclerView内的View缓存。具体使用方式如下：\n```Java\nSkinManager\n    .with(recyclerView)\n    .addViewAttrs(new SkinAttr(SkinAttrName.CLEAR_RECYCLER_VIEW));\n```\n\n#附录\n\n此框架脱胎于项目需要实现夜间模式的需求，在[文章](http://blog.csdn.net/u013478336/article/details/52484322)中，我们列举了常见的几种实现夜间模式切换的方案，并大致对比了一下各种方案的优缺点，此处不再一一列举。仅大致摘录夜间模式的需求分析如下：\n>夜间模式需要对屏幕上的文字/图片/视频三种表现形式做特殊处理，具体细化如下：\n>**1）对界面背景，**白色等浅色背景应该变成黑色/灰色之类的深色背景，以此降低屏幕亮度减少视觉刺激；\n>**2）对文字，**因背景色变深，文字颜色需变浅，以形成对比效果；\n>**3）对图片，**对图片加蒙层，避免加载浅色图片带来的视觉刺激；\n>**4）对视频，**通常在播放界面增加亮度变化功能，由用户来决定屏幕亮度。\n\n具体技术选型与框架设计可见文章：http://blog.csdn.net/u013478336/article/details/53083054"
  },
  {
    "path": "SkinProject/.gitignore",
    "content": "*.iml\n.gradle\n/local.properties\n/.idea/workspace.xml\n/.idea/libraries\n.DS_Store\n/build\n/captures\n.externalNativeBuild\n"
  },
  {
    "path": "SkinProject/app/.gitignore",
    "content": "/build\n"
  },
  {
    "path": "SkinProject/app/build.gradle",
    "content": "apply plugin: 'com.android.application'\n\nandroid {\n    compileSdkVersion 23\n    buildToolsVersion \"23.0.1\"\n    defaultConfig {\n        applicationId \"org.qcode.skinproject\"\n        minSdkVersion 14\n        targetSdkVersion 23\n        versionCode 1\n        versionName \"1.0\"\n    }\n    buildTypes {\n        release {\n            minifyEnabled false\n            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'\n        }\n    }\n}\n\ndependencies {\n    compile fileTree(dir: 'libs', include: ['*.jar'])\n    androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {\n        exclude group: 'com.android.support', module: 'support-annotations'\n    })\n    compile 'com.android.support:appcompat-v7:23.2.1'\n    testCompile 'junit:junit:4.12'\n}\n"
  },
  {
    "path": "SkinProject/app/proguard-rules.pro",
    "content": "# Add project specific ProGuard rules here.\n# By default, the flags in this file are appended to flags specified\n# in E:\\Program Files\\Android\\android-sdk/tools/proguard/proguard-android.txt\n# You can edit the include path and order by changing the proguardFiles\n# directive in build.gradle.\n#\n# For more details, see\n#   http://developer.android.com/guide/developing/tools/proguard.html\n\n# Add any project specific keep options here:\n\n# If your project uses WebView with JS, uncomment the following\n# and specify the fully qualified class name to the JavaScript interface\n# class:\n#-keepclassmembers class fqcn.of.javascript.interface.for.webview {\n#   public *;\n#}\n"
  },
  {
    "path": "SkinProject/app/src/main/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    package=\"org.qcode.sinproject\">\n\n    <application>\n        <activity android:name=\"org.qcode.skinproject.MainActivity\" >\n            <intent-filter>\n                <action android:name=\"android.intent.action.MAIN\"/>\n                <category android:name=\"android.intent.category.LAUNCHER\"/>\n            </intent-filter>\n            </activity>\n    </application>\n\n</manifest>\n"
  },
  {
    "path": "SkinProject/app/src/main/java/org/qcode/skinproject/MainActivity.java",
    "content": "package org.qcode.skinproject;\n\nimport android.app.Activity;\n\n/**\n * qqliu\n * 2016/11/9.\n */\n\npublic class MainActivity extends Activity {\n}\n"
  },
  {
    "path": "SkinProject/app/src/main/res/drawable/btn_bg.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:shape=\"rectangle\">\n    <corners android:radius=\"4dp\"/>\n\n    <solid android:color=\"@color/color_white\"/>\n\n    <stroke android:width=\"1dp\" android:color=\"@color/gray_50\"/>\n</shape>"
  },
  {
    "path": "SkinProject/app/src/main/res/drawable/drawable_float_view.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:shape=\"oval\">\n\n    <size android:width=\"50dp\"\n        android:height=\"50dp\"/>\n    <solid android:color=\"@color/color_red\"/>\n</shape>"
  },
  {
    "path": "SkinProject/app/src/main/res/drawable/news_item_selector.xml",
    "content": "<?xml version = \"1.0\" encoding = \"utf-8\"?>\n<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n    <item android:drawable=\"@color/color_item_press_bg\" android:state_pressed=\"true\" android:state_focused=\"true\"/>\n    <item android:drawable=\"@color/color_item_normal_bg\"/>\n\n</selector>"
  },
  {
    "path": "SkinProject/app/src/main/res/values/colors.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <color name=\"gray_50\">#84878c</color>\n\n    <color name=\"color_white\">#000000</color>\n\n    <color name=\"color_text\">#FFFFFF</color>\n    <color name=\"color_background\">#000000</color>\n    <color name=\"color_item_normal_bg\">#000000</color>\n    <color name=\"color_item_press_bg\">#333333</color>\n    <color name=\"activity_bg_color\">#1c1d20</color>\n\n\n    <color name=\"color_red\">#00FF00</color>\n\n    <color name=\"night_shadow_color\">#8e8e8e</color>\n</resources>\n"
  },
  {
    "path": "SkinProject/build.gradle",
    "content": "// Top-level build file where you can add configuration options common to all sub-projects/modules.\n\nbuildscript {\n    repositories {\n        jcenter()\n    }\n    dependencies {\n        classpath 'com.android.tools.build:gradle:2.2.0'\n\n        // NOTE: Do not place your application dependencies here; they belong\n        // in the individual module build.gradle files\n    }\n}\n\nallprojects {\n    repositories {\n        jcenter()\n    }\n}\n\ntask clean(type: Delete) {\n    delete rootProject.buildDir\n}\n"
  },
  {
    "path": "SkinProject/gradle.properties",
    "content": "## Project-wide Gradle settings.\n#\n# For more details on how to configure your build environment visit\n# http://www.gradle.org/docs/current/userguide/build_environment.html\n#\n# Specifies the JVM arguments used for the daemon process.\n# The setting is particularly useful for tweaking memory settings.\n# Default value: -Xmx1024m -XX:MaxPermSize=256m\n# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8\n#\n# When configured, Gradle will run in incubating parallel mode.\n# This option should only be used with decoupled projects. More details, visit\n# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects\n# org.gradle.parallel=true\n#Wed Nov 09 09:21:50 CST 2016\nsystemProp.http.proxyHost=127.0.0.1\norg.gradle.jvmargs=-Xmx1536m\nsystemProp.http.proxyPort=1080\n"
  },
  {
    "path": "SkinProject/gradlew",
    "content": "#!/usr/bin/env bash\n\n##############################################################################\n##\n##  Gradle start up script for UN*X\n##\n##############################################################################\n\n# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nDEFAULT_JVM_OPTS=\"\"\n\nAPP_NAME=\"Gradle\"\nAPP_BASE_NAME=`basename \"$0\"`\n\n# Use the maximum available, or set MAX_FD != -1 to use that value.\nMAX_FD=\"maximum\"\n\nwarn ( ) {\n    echo \"$*\"\n}\n\ndie ( ) {\n    echo\n    echo \"$*\"\n    echo\n    exit 1\n}\n\n# OS specific support (must be 'true' or 'false').\ncygwin=false\nmsys=false\ndarwin=false\ncase \"`uname`\" in\n  CYGWIN* )\n    cygwin=true\n    ;;\n  Darwin* )\n    darwin=true\n    ;;\n  MINGW* )\n    msys=true\n    ;;\nesac\n\n# Attempt to set APP_HOME\n# Resolve links: $0 may be a link\nPRG=\"$0\"\n# Need this for relative symlinks.\nwhile [ -h \"$PRG\" ] ; do\n    ls=`ls -ld \"$PRG\"`\n    link=`expr \"$ls\" : '.*-> \\(.*\\)$'`\n    if expr \"$link\" : '/.*' > /dev/null; then\n        PRG=\"$link\"\n    else\n        PRG=`dirname \"$PRG\"`\"/$link\"\n    fi\ndone\nSAVED=\"`pwd`\"\ncd \"`dirname \\\"$PRG\\\"`/\" >/dev/null\nAPP_HOME=\"`pwd -P`\"\ncd \"$SAVED\" >/dev/null\n\nCLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar\n\n# Determine the Java command to use to start the JVM.\nif [ -n \"$JAVA_HOME\" ] ; then\n    if [ -x \"$JAVA_HOME/jre/sh/java\" ] ; then\n        # IBM's JDK on AIX uses strange locations for the executables\n        JAVACMD=\"$JAVA_HOME/jre/sh/java\"\n    else\n        JAVACMD=\"$JAVA_HOME/bin/java\"\n    fi\n    if [ ! -x \"$JAVACMD\" ] ; then\n        die \"ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\n    fi\nelse\n    JAVACMD=\"java\"\n    which java >/dev/null 2>&1 || die \"ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\nfi\n\n# Increase the maximum file descriptors if we can.\nif [ \"$cygwin\" = \"false\" -a \"$darwin\" = \"false\" ] ; then\n    MAX_FD_LIMIT=`ulimit -H -n`\n    if [ $? -eq 0 ] ; then\n        if [ \"$MAX_FD\" = \"maximum\" -o \"$MAX_FD\" = \"max\" ] ; then\n            MAX_FD=\"$MAX_FD_LIMIT\"\n        fi\n        ulimit -n $MAX_FD\n        if [ $? -ne 0 ] ; then\n            warn \"Could not set maximum file descriptor limit: $MAX_FD\"\n        fi\n    else\n        warn \"Could not query maximum file descriptor limit: $MAX_FD_LIMIT\"\n    fi\nfi\n\n# For Darwin, add options to specify how the application appears in the dock\nif $darwin; then\n    GRADLE_OPTS=\"$GRADLE_OPTS \\\"-Xdock:name=$APP_NAME\\\" \\\"-Xdock:icon=$APP_HOME/media/gradle.icns\\\"\"\nfi\n\n# For Cygwin, switch paths to Windows format before running java\nif $cygwin ; then\n    APP_HOME=`cygpath --path --mixed \"$APP_HOME\"`\n    CLASSPATH=`cygpath --path --mixed \"$CLASSPATH\"`\n    JAVACMD=`cygpath --unix \"$JAVACMD\"`\n\n    # We build the pattern for arguments to be converted via cygpath\n    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`\n    SEP=\"\"\n    for dir in $ROOTDIRSRAW ; do\n        ROOTDIRS=\"$ROOTDIRS$SEP$dir\"\n        SEP=\"|\"\n    done\n    OURCYGPATTERN=\"(^($ROOTDIRS))\"\n    # Add a user-defined pattern to the cygpath arguments\n    if [ \"$GRADLE_CYGPATTERN\" != \"\" ] ; then\n        OURCYGPATTERN=\"$OURCYGPATTERN|($GRADLE_CYGPATTERN)\"\n    fi\n    # Now convert the arguments - kludge to limit ourselves to /bin/sh\n    i=0\n    for arg in \"$@\" ; do\n        CHECK=`echo \"$arg\"|egrep -c \"$OURCYGPATTERN\" -`\n        CHECK2=`echo \"$arg\"|egrep -c \"^-\"`                                 ### Determine if an option\n\n        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition\n            eval `echo args$i`=`cygpath --path --ignore --mixed \"$arg\"`\n        else\n            eval `echo args$i`=\"\\\"$arg\\\"\"\n        fi\n        i=$((i+1))\n    done\n    case $i in\n        (0) set -- ;;\n        (1) set -- \"$args0\" ;;\n        (2) set -- \"$args0\" \"$args1\" ;;\n        (3) set -- \"$args0\" \"$args1\" \"$args2\" ;;\n        (4) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" ;;\n        (5) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" ;;\n        (6) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" ;;\n        (7) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" ;;\n        (8) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" \"$args7\" ;;\n        (9) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" \"$args7\" \"$args8\" ;;\n    esac\nfi\n\n# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules\nfunction splitJvmOpts() {\n    JVM_OPTS=(\"$@\")\n}\neval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS\nJVM_OPTS[${#JVM_OPTS[*]}]=\"-Dorg.gradle.appname=$APP_BASE_NAME\"\n\nexec \"$JAVACMD\" \"${JVM_OPTS[@]}\" -classpath \"$CLASSPATH\" org.gradle.wrapper.GradleWrapperMain \"$@\"\n"
  },
  {
    "path": "SkinProject/gradlew.bat",
    "content": "@if \"%DEBUG%\" == \"\" @echo off\n@rem ##########################################################################\n@rem\n@rem  Gradle startup script for Windows\n@rem\n@rem ##########################################################################\n\n@rem Set local scope for the variables with windows NT shell\nif \"%OS%\"==\"Windows_NT\" setlocal\n\n@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nset DEFAULT_JVM_OPTS=\n\nset DIRNAME=%~dp0\nif \"%DIRNAME%\" == \"\" set DIRNAME=.\nset APP_BASE_NAME=%~n0\nset APP_HOME=%DIRNAME%\n\n@rem Find java.exe\nif defined JAVA_HOME goto findJavaFromJavaHome\n\nset JAVA_EXE=java.exe\n%JAVA_EXE% -version >NUL 2>&1\nif \"%ERRORLEVEL%\" == \"0\" goto init\n\necho.\necho ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\necho.\necho Please set the JAVA_HOME variable in your environment to match the\necho location of your Java installation.\n\ngoto fail\n\n:findJavaFromJavaHome\nset JAVA_HOME=%JAVA_HOME:\"=%\nset JAVA_EXE=%JAVA_HOME%/bin/java.exe\n\nif exist \"%JAVA_EXE%\" goto init\n\necho.\necho ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%\necho.\necho Please set the JAVA_HOME variable in your environment to match the\necho location of your Java installation.\n\ngoto fail\n\n:init\n@rem Get command-line arguments, handling Windowz variants\n\nif not \"%OS%\" == \"Windows_NT\" goto win9xME_args\nif \"%@eval[2+2]\" == \"4\" goto 4NT_args\n\n:win9xME_args\n@rem Slurp the command line arguments.\nset CMD_LINE_ARGS=\nset _SKIP=2\n\n:win9xME_args_slurp\nif \"x%~1\" == \"x\" goto execute\n\nset CMD_LINE_ARGS=%*\ngoto execute\n\n:4NT_args\n@rem Get arguments from the 4NT Shell from JP Software\nset CMD_LINE_ARGS=%$\n\n:execute\n@rem Setup the command line\n\nset CLASSPATH=%APP_HOME%\\gradle\\wrapper\\gradle-wrapper.jar\n\n@rem Execute Gradle\n\"%JAVA_EXE%\" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% \"-Dorg.gradle.appname=%APP_BASE_NAME%\" -classpath \"%CLASSPATH%\" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%\n\n:end\n@rem End local scope for the variables with windows NT shell\nif \"%ERRORLEVEL%\"==\"0\" goto mainEnd\n\n:fail\nrem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of\nrem the _cmd.exe /c_ return code!\nif  not \"\" == \"%GRADLE_EXIT_CONSOLE%\" exit 1\nexit /b 1\n\n:mainEnd\nif \"%OS%\"==\"Windows_NT\" endlocal\n\n:omega\n"
  },
  {
    "path": "SkinProject/settings.gradle",
    "content": "include ':app'\n"
  },
  {
    "path": "app/.gitignore",
    "content": "/build\n"
  },
  {
    "path": "app/build.gradle",
    "content": "apply plugin: 'com.android.application'\n\nandroid {\n    compileSdkVersion 23\n    buildToolsVersion \"23.0.1\"\n    defaultConfig {\n        applicationId \"org.qcode.qskinloader\"\n        minSdkVersion 14\n        targetSdkVersion 23\n        versionCode 1\n        versionName \"1.0\"\n        testInstrumentationRunner \"android.support.test.runner.AndroidJUnitRunner\"\n    }\n    buildTypes {\n        release {\n            minifyEnabled false\n            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'\n        }\n    }\n}\n\ndependencies {\n    compile fileTree(dir: 'libs', include: ['*.jar'])\n    androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {\n        exclude group: 'com.android.support', module: 'support-annotations'\n    })\n    compile 'com.android.support:appcompat-v7:23.2.1'\n    testCompile 'junit:junit:4.12'\n    compile project(':QSkinLoaderlib')\n}\n"
  },
  {
    "path": "app/proguard-rules.pro",
    "content": "# Add project specific ProGuard rules here.\n# By default, the flags in this file are appended to flags specified\n# in E:\\Program Files\\Android\\android-sdk/tools/proguard/proguard-android.txt\n# You can edit the include path and order by changing the proguardFiles\n# directive in build.gradle.\n#\n# For more details, see\n#   http://developer.android.com/guide/developing/tools/proguard.html\n\n# Add any project specific keep options here:\n\n# If your project uses WebView with JS, uncomment the following\n# and specify the fully qualified class name to the JavaScript interface\n# class:\n#-keepclassmembers class fqcn.of.javascript.interface.for.webview {\n#   public *;\n#}\n"
  },
  {
    "path": "app/src/main/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    package=\"org.qcode.skintestdemo\">\n\n    <uses-permission android:name=\"android.permission.SYSTEM_ALERT_WINDOW\"/>\n    <application\n        android:name=\"org.qcode.demo.SkinDemoApp\"\n        android:allowBackup=\"true\"\n        android:icon=\"@mipmap/ic_launcher\"\n        android:label=\"@string/app_name\"\n        android:supportsRtl=\"true\">\n        <activity android:name=\"org.qcode.demo.MainActivity\"\n            android:theme=\"@style/ActivityTheme\">\n            <intent-filter>\n                <action android:name=\"android.intent.action.MAIN\" />\n\n                <category android:name=\"android.intent.category.LAUNCHER\" />\n            </intent-filter>\n        </activity>\n\n        <activity\n            android:name=\"org.qcode.demo.ui.viewpageandlistview.ViewPagerAndListViewActivity\"\n            android:label=\"RecyclerView\" />\n\n        <activity\n            android:name=\"org.qcode.demo.ui.recyclerview.RecyclerViewActivity\"\n            android:label=\"RecyclerView\" />\n\n        <activity\n            android:name=\"org.qcode.demo.ui.dynamicaddview.DynamicAddViewActivity\"\n            android:label=\"DynamicAddView\" />\n\n        <activity\n            android:name=\"org.qcode.demo.ui.gridview.GridViewActivity\"\n            android:label=\"GridView\" />\n\n        <activity\n            android:name=\"org.qcode.demo.ui.customattr.CustomAttrViewActivity\"\n            android:label=\"SelfDefineView\" />\n\n        <activity\n            android:name=\"org.qcode.demo.ui.otherscene.OtherSceneActivity\"\n            android:label=\"OtherScene\"\n            android:theme=\"@style/TransparentTheme\"/>\n    </application>\n\n</manifest>\n"
  },
  {
    "path": "app/src/main/java/org/qcode/demo/BaseActivity.java",
    "content": "package org.qcode.demo;\n\nimport android.app.Activity;\nimport android.content.Context;\nimport android.graphics.PixelFormat;\nimport android.os.Bundle;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.view.Window;\nimport android.widget.FrameLayout;\nimport android.widget.LinearLayout;\n\nimport org.qcode.demo.utils.UIUtil;\nimport org.qcode.qskinloader.IActivitySkinEventHandler;\nimport org.qcode.qskinloader.ISkinActivity;\nimport org.qcode.qskinloader.SkinManager;\nimport org.qcode.skintestdemo.R;\n\n/**\n * 所有Activity的父类；需要实现ISkinActivity接口\n */\npublic abstract class BaseActivity extends Activity implements ISkinActivity {\n    private IActivitySkinEventHandler mSkinEventHandler;\n    private boolean mFirstTimeApplySkin = true;\n    private FrameLayout mContentContainer;\n    private SkinChangeSwitchView mSwitchView;\n\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n\n        requestWindowFeature(Window.FEATURE_NO_TITLE);\n        getWindow().setFormat(PixelFormat.RGBA_8888);\n\n        mSkinEventHandler = SkinManager.newActivitySkinEventHandler()\n                .setSwitchSkinImmediately(isSwitchSkinImmediately())\n                .setSupportSkinChange(isSupportSkinChange())\n                .setWindowBackgroundResource(getWindowBackgroundResource())\n                .setNeedDelegateViewCreate(true);\n        mSkinEventHandler.onCreate(this);\n\n        initView(this);\n    }\n\n    private void initView(Context context) {\n        LinearLayout root = new LinearLayout(context);\n        root.setOrientation(LinearLayout.VERTICAL);\n        super.setContentView(root);\n\n        LinearLayout.LayoutParams paramTitle = new LinearLayout.LayoutParams(\n                ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);\n        mSwitchView = new SkinChangeSwitchView(context);\n        int padding = UIUtil.dip2px(context, 15);\n        mSwitchView.setPadding(padding, padding, padding, padding);\n        root.addView(mSwitchView, paramTitle);\n\n        LinearLayout.LayoutParams paramContent = new LinearLayout.LayoutParams(\n                ViewGroup.LayoutParams.MATCH_PARENT, 0);\n        paramContent.weight = 1;\n        mContentContainer = new FrameLayout(context);\n        root.addView(mContentContainer, paramContent);\n    }\n\n    @Override\n    public void setContentView(int layoutResID) {\n        LayoutInflater.from(this).inflate(layoutResID, mContentContainer);\n    }\n\n    @Override\n    public void setContentView(View view) {\n        FrameLayout.LayoutParams param = new FrameLayout.LayoutParams(\n                ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);\n        mContentContainer.addView(view, param);\n    }\n\n    @Override\n    protected void onStart() {\n        super.onStart();\n        mSkinEventHandler.onStart();\n    }\n\n    @Override\n    protected void onResume() {\n        super.onResume();\n        //皮肤相关，此通知放在此处，尽量让子类的view都添加到view树内\n        if (mFirstTimeApplySkin) {\n            mSkinEventHandler.onViewCreated();\n            mFirstTimeApplySkin = false;\n        }\n\n        mSwitchView.refreshSwitch();\n\n        mSkinEventHandler.onResume();\n    }\n\n    @Override\n    public void onWindowFocusChanged(boolean hasFocus) {\n        super.onWindowFocusChanged(hasFocus);\n\n        //皮肤相关\n        mSkinEventHandler.onWindowFocusChanged(hasFocus);\n    }\n\n    @Override\n    protected void onPause() {\n        super.onPause();\n        mSkinEventHandler.onPause();\n    }\n\n    @Override\n    protected void onStop() {\n        super.onStop();\n        mSkinEventHandler.onStop();\n    }\n\n    @Override\n    protected void onDestroy() {\n        super.onDestroy();\n\n        //皮肤相关\n        mSkinEventHandler.onDestroy();\n    }\n\n    @Override\n    public boolean isSupportSkinChange() {\n        //告知当前界面是否支持换肤：true支持换肤，false不支持\n        return true;\n    }\n\n    @Override\n    public boolean isSwitchSkinImmediately() {\n        //告知当切换皮肤时，是否立刻刷新当前界面；true立刻刷新，false表示在界面onResume时刷新；\n        //减轻换肤时性能压力\n        return false;\n    }\n\n    @Override\n    public void handleSkinChange() {\n        //当前界面在换肤时收到的回调，可以在此回调内做一些其他事情；\n        //比如：通知WebView内的页面切换到夜间模式等\n    }\n\n    /**\n     * 告知当前界面Window的background资源，换肤时会寻找对应的资源替换\n     */\n    protected int getWindowBackgroundResource() {\n        return R.color.activity_bg_color;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/org/qcode/demo/MainActivity.java",
    "content": "package org.qcode.demo;\n\nimport android.content.Intent;\nimport android.os.Bundle;\nimport android.view.View;\n\nimport org.qcode.demo.ui.customattr.CustomAttrViewActivity;\nimport org.qcode.demo.ui.dynamicaddview.DynamicAddViewActivity;\nimport org.qcode.demo.ui.gridview.GridViewActivity;\nimport org.qcode.demo.ui.otherscene.OtherSceneActivity;\nimport org.qcode.demo.ui.recyclerview.RecyclerViewActivity;\nimport org.qcode.demo.ui.viewpageandlistview.ViewPagerAndListViewActivity;\nimport org.qcode.skintestdemo.R;\n\n/**\n * qqliu\n * 2016/10/10.\n */\n\npublic class MainActivity extends BaseActivity {\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.activity_main);\n    }\n\n    public void onClick(View view) {\n        Intent intent = new Intent();\n        switch (view.getId()) {\n            case R.id.btnCustomView:\n                intent.setClass(this, CustomAttrViewActivity.class);\n                break;\n            case R.id.btnDynamicAddView:\n                intent.setClass(this, DynamicAddViewActivity.class);\n                break;\n            case R.id.btnRecyclerView:\n                intent.setClass(this, RecyclerViewActivity.class);\n                break;\n            case R.id.btnViewPagerAndListView:\n                intent.setClass(this, ViewPagerAndListViewActivity.class);\n                break;\n            case R.id.btnGridView:\n                intent.setClass(this, GridViewActivity.class);\n                break;\n            case R.id.btnOtherScene:\n                intent.setClass(this, OtherSceneActivity.class);\n                break;\n        }\n        startActivity(intent);\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/org/qcode/demo/SkinChangeSwitchView.java",
    "content": "package org.qcode.demo;\n\nimport android.content.Context;\nimport android.view.Gravity;\nimport android.view.View;\nimport android.widget.ImageView;\nimport android.widget.LinearLayout;\nimport android.widget.RelativeLayout;\nimport android.widget.TextView;\n\nimport org.qcode.demo.skin.SkinChangeHelper;\nimport org.qcode.qskinloader.SkinManager;\nimport org.qcode.qskinloader.entity.SkinAttrName;\nimport org.qcode.skintestdemo.R;\n\n\npublic class SkinChangeSwitchView extends LinearLayout {\n\n    private static final String TAG = \"NightModeSettingView\";\n\n    private ImageView mImgBtnSwitch;\n    private TextView mTextViewTitle;\n\n    public SkinChangeSwitchView(Context context) {\n        super(context);\n        initView(context);\n    }\n\n    protected void initView(final Context context) {\n        setOrientation(LinearLayout.HORIZONTAL);\n        setGravity(Gravity.CENTER);\n\n        SkinManager.with(this)\n                .setViewAttrs(SkinAttrName.BACKGROUND, R.color.color_background)\n                .applySkin(true);\n\n        mTextViewTitle = new TextView(context);\n        SkinManager.with(mTextViewTitle)\n                .setViewAttrs(SkinAttrName.TEXT_COLOR, R.color.color_text)\n                .applySkin(false);\n        mTextViewTitle.setTextSize(16);\n        LinearLayout.LayoutParams paramTitlePart = new LinearLayout.LayoutParams(\n                0, RelativeLayout.LayoutParams.WRAP_CONTENT);\n        paramTitlePart.weight = 1;\n        addView(mTextViewTitle, paramTitlePart);\n        mTextViewTitle.setText(\"夜间模式\");\n\n        mImgBtnSwitch = new ImageView(context);\n        LinearLayout.LayoutParams paramEntryFlag = new LinearLayout.LayoutParams(\n                RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT);\n        addView(mImgBtnSwitch, paramEntryFlag);\n\n        refreshSwitch();\n\n        mImgBtnSwitch.setOnClickListener(new View.OnClickListener() {\n\n            @Override\n            public void onClick(View v) {\n                SkinChangeHelper.getInstance().switchSkinMode(\n                        new SkinChangeHelper.OnSkinChangeListener() {\n                            @Override\n                            public void onSuccess() {\n                                refreshSwitch();\n                            }\n\n                            @Override\n                            public void onError() {\n                                refreshSwitch();\n                            }\n                        });\n\n                refreshSwitch();\n            }\n        });\n    }\n\n    public void refreshSwitch() {\n        boolean isDefaultMode = SkinChangeHelper.getInstance().isDefaultMode();\n        boolean isSwitching = SkinChangeHelper.getInstance().isSwitching();\n        int drawableId;\n        if (isDefaultMode) {\n            //mImgBtnSwitch.setImageResource(R.drawable.news_switch_setting_off_nor);\n            drawableId = R.mipmap.news_switch_setting_off_nor;\n        } else {\n            //mImgBtnSwitch.setImageResource(R.drawable.news_switch_setting_on_nor);\n            drawableId = R.mipmap.news_switch_setting_on_nor;\n        }\n        SkinManager.with(mImgBtnSwitch)\n                .setViewAttrs(SkinAttrName.SRC, drawableId)\n                .applySkin(false);\n        mImgBtnSwitch.setEnabled(!isSwitching);\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/org/qcode/demo/SkinDemoApp.java",
    "content": "package org.qcode.demo;\n\nimport android.app.Application;\nimport android.content.Context;\n\nimport org.qcode.demo.base.Settings;\nimport org.qcode.demo.skin.SkinChangeHelper;\n\n/**\n * qqliu\n * 2016/9/9.\n */\npublic class SkinDemoApp extends Application {\n\n    private static Context mContext;\n\n    public void onCreate() {\n        super.onCreate();\n        mContext = this;\n\n        Settings.createInstance(this);\n\n        initSkinLoader();\n    }\n\n    /**\n     * Must call init first\n     */\n    private void initSkinLoader() {\n        // 初始化皮肤框架\n        SkinChangeHelper.getInstance().init(this);\n        //初始化上次缓存的皮肤\n        SkinChangeHelper.getInstance().refreshSkin(null);\n    }\n\n    public static Context getAppContext() {\n        return mContext;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/org/qcode/demo/base/Settings.java",
    "content": "package org.qcode.demo.base;\n\nimport android.content.Context;\nimport android.content.SharedPreferences;\nimport android.content.SharedPreferences.Editor;\nimport android.text.TextUtils;\n\nimport org.qcode.qskinloader.base.utils.Logging;\n\nimport java.io.File;\nimport java.io.FileInputStream;\nimport java.io.FileOutputStream;\nimport java.io.IOException;\nimport java.io.ObjectInputStream;\nimport java.io.ObjectOutputStream;\n\npublic class Settings {\n\n    private static final String TAG = \"SettingsImpl\";\n\n    private static final String NAME = \"SKinSettings\";\n\n    private SharedPreferences mSharedPref;\n\n    private Settings(Context context) {\n        int mode = Context.MODE_PRIVATE;\n        mSharedPref = context.getSharedPreferences(NAME, mode);\n    }\n\n    private static volatile Settings mInstance;\n\n    public static Settings createInstance(Context context) {\n        if(null == mInstance) {\n            synchronized (Settings.class) {\n                if(null == mInstance) {\n                    mInstance = new Settings(context);\n                }\n            }\n        }\n        return mInstance;\n    }\n\n    public static Settings getInstance() {\n        return mInstance;\n    }\n\n    public boolean isSetted(String key) {\n        return mSharedPref.contains(key);\n    }\n\n\n    public void setSetting(String key, boolean value) {\n        try {\n            Editor editor = mSharedPref.edit();\n            editor.putBoolean(key, value);\n            editor.commit();\n        } catch (Exception e) {\n            Logging.e(TAG, \"setSetting(\" + key + \", \" + value + \")\", e);\n        }\n    }\n\n\n    public void setSetting(String key, int value) {\n        try {\n            Editor editor = mSharedPref.edit();\n            editor.putInt(key, value);\n            editor.commit();\n        } catch (Exception e) {\n            Logging.e(TAG, \"setSetting(\" + key + \", \" + value + \")\", e);\n        }\n    }\n\n\n    public void setSetting(String key, float value) {\n        try {\n            Editor editor = mSharedPref.edit();\n            editor.putFloat(key, value);\n            editor.commit();\n        } catch (Exception e) {\n            Logging.e(TAG, \"setSetting(\" + key + \", \" + value + \")\", e);\n        }\n    }\n\n\n    public void setSetting(String key, long value) {\n        try {\n            Editor editor = mSharedPref.edit();\n            editor.putLong(key, value);\n            editor.commit();\n        } catch (Exception e) {\n            Logging.e(TAG, \"setSetting(\" + key + \", \" + value + \")\", e);\n        }\n    }\n\n\n    public void setSetting(String key, String value) {\n        if (null != value) {\n            //要过滤'\\0',否则会使XML读取异常\n            value = value.replace(\"\\0\", \"\");\n        }\n\n        try {\n            Editor editor = mSharedPref.edit();\n            editor.putString(key, value);\n            editor.commit();\n        } catch (Exception e) {\n            Logging.e(TAG, \"setSetting(\" + key + \", \" + value + \")\", e);\n        }\n    }\n\n\n    public boolean getBoolean(String key) {\n        return getBoolean(key, false);\n    }\n\n\n    public boolean getBoolean(String key, boolean defaultValue) {\n        boolean result = defaultValue;\n        try {\n            result = mSharedPref.getBoolean(key, result);\n        } catch (Exception e) {\n            Logging.e(TAG, \"getBoolean()\", e);\n        }\n        return result;\n    }\n\n\n    public int getInt(String key) {\n        return getInt(key, 0);\n    }\n\n\n    public int getInt(String key, int defaultValue) {\n        int value = defaultValue;\n        try {\n            value = mSharedPref.getInt(key, defaultValue);\n        } catch (Exception e) {\n            Logging.e(TAG, \"getSetting()\", e);\n        }\n        return value;\n    }\n\n\n    public float getFloat(String key) {\n        return getFloat(key, 0);\n    }\n\n\n    public float getFloat(String key, float defaultValue) {\n        float value = defaultValue;\n        try {\n            value = mSharedPref.getFloat(key, defaultValue);\n        } catch (Exception e) {\n            Logging.e(TAG, \"getLongSetting()\", e);\n        }\n        return value;\n    }\n\n\n    public long getLong(String key) {\n        return getLong(key, 0);\n    }\n\n\n    public long getLong(String key, long defaultValue) {\n        long value = defaultValue;\n        try {\n            value = mSharedPref.getLong(key, defaultValue);\n        } catch (Exception e) {\n            Logging.e(TAG, \"getLongSetting()\", e);\n        }\n        return value;\n    }\n\n\n    public String getString(String key) {\n        return getString(key, null);\n    }\n\n\n    public String getString(String key, String defaultValue) {\n        String value = defaultValue;\n        try {\n            value = mSharedPref.getString(key, defaultValue);\n        } catch (Exception e) {\n            Logging.e(TAG, \"getString()\", e);\n        }\n        return value;\n    }\n\n\n    public void saveObject(String fileName, Object object) {\n        ObjectOutputStream objectOutputStream = null;\n        try {\n            File file = new File(fileName);\n            if (file.exists()) {\n                file.delete();\n            }\n            file.createNewFile();\n\n            objectOutputStream = new ObjectOutputStream(new FileOutputStream(file));\n            objectOutputStream.writeObject(object);\n            objectOutputStream.flush();\n        } catch (Exception e) {\n            Logging.e(TAG, \"saveObject()\", e);\n        } finally {\n            if (objectOutputStream != null) {\n                try {\n                    objectOutputStream.close();\n                } catch (IOException e) {\n                    Logging.e(TAG, \"saveObject()\", e);\n                }\n            }\n        }\n    }\n\n\n    public Object readObject(String fileName) {\n\n        Object object = null;\n        ObjectInputStream objectInputStream = null;\n        try {\n\n            objectInputStream = new ObjectInputStream(new FileInputStream(fileName));\n            object = objectInputStream.readObject();\n        } catch (Exception e) {\n\n            Logging.e(TAG, \"readObject()\" + e);\n        } finally {\n            if (objectInputStream != null) {\n\n                try {\n                    objectInputStream.close();\n                } catch (IOException e) {\n\n                    Logging.e(TAG, \"readObject()\" + e);\n                }\n            }\n        }\n        return object;\n    }\n\n\n    public void clearObject(String fileName) {\n        try {\n            File file = new File(fileName);\n            if (file.exists()) {\n                file.delete();\n                Logging.d(TAG, \"delete file success\");\n            }\n        } catch (Exception e) {\n            Logging.e(TAG, \" clearObject()\", e);\n        }\n    }\n\n\n    public void removeSetting(String key) {\n        try {\n            //如果key不为空，把key删掉\n            if (!TextUtils.isEmpty(key)) {\n                Editor editor = mSharedPref.edit();\n                editor.remove(key);\n                editor.commit();\n            }\n        } catch (Exception e) {\n            Logging.e(TAG, \"removeSetting(\" + key + \")\", e);\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/org/qcode/demo/skin/SkinChangeHelper.java",
    "content": "package org.qcode.demo.skin;\n\nimport android.content.Context;\n\nimport org.qcode.demo.SkinDemoApp;\nimport org.qcode.demo.utils.UITaskRunner;\nimport org.qcode.demo.utils.UIUtil;\nimport org.qcode.qskinloader.ILoadSkinListener;\nimport org.qcode.qskinloader.SkinManager;\nimport org.qcode.qskinloader.resourceloader.impl.ConfigChangeResourceLoader;\nimport org.qcode.qskinloader.resourceloader.impl.SuffixResourceLoader;\n\nimport java.io.File;\n\n/**\n * qqliu\n * 2016/9/26.\n */\npublic class SkinChangeHelper {\n    private static final String TAG = \"SkinChangeHelper\";\n    //基于suffix换肤\n    private static final int TYPE_SUFFIX = 1;\n    //基于apk换肤\n    private static final int TYPE_APK = 2;\n    //基于UIMode换肤\n    private static final int TYPE_UIMODE = 3;\n\n    private static volatile SkinChangeHelper mInstance;\n    private final Context mContext;\n\n    //目前框架支持三种换肤方式，后缀换肤/APK资源包换肤/UIMode换肤\n    private int mSkinChangeType = TYPE_SUFFIX;\n\n\n    private SkinChangeHelper() {\n        mContext = SkinDemoApp.getAppContext();\n        mIsDefaultMode = SkinConfigHelper.isDefaultSkin();\n    }\n\n    public static SkinChangeHelper getInstance() {\n        if (null == mInstance) {\n            synchronized (SkinChangeHelper.class) {\n                if (null == mInstance) {\n                    mInstance = new SkinChangeHelper();\n                }\n            }\n        }\n        return mInstance;\n    }\n\n    private volatile boolean mIsDefaultMode = false;\n\n    private volatile boolean mIsSwitching = false;\n\n    public void init(Context context) {\n        SkinManager.getInstance().init(context);\n    }\n\n    public void switchSkinMode(OnSkinChangeListener listener) {\n        mIsSwitching = true;\n        mIsDefaultMode = !mIsDefaultMode;\n        refreshSkin(listener);\n    }\n\n    public void refreshSkin(OnSkinChangeListener listener) {\n        if (mIsDefaultMode) {\n            switch (mSkinChangeType) {\n                case TYPE_SUFFIX:\n                case TYPE_APK:\n                    //恢复到默认皮肤\n                    SkinManager.getInstance().restoreDefault(\n                            SkinConstant.DEFAULT_SKIN,\n                            new MyLoadSkinListener(listener));\n                    break;\n\n                case TYPE_UIMODE:\n                    //基于UIMode换肤只能通过改回配置才能换肤\n                    changeSkinByConfig(ConfigChangeResourceLoader.MODE_DAY, listener);\n                    break;\n            }\n\n        } else {\n            switch (mSkinChangeType) {\n                case TYPE_SUFFIX:\n                    changeSkinBySuffix(listener);\n                    break;\n\n                case TYPE_APK:\n                    changeSkinByApk(listener);\n                    break;\n\n                case TYPE_UIMODE:\n                    //基于UIMode换肤只能通过改回配置才能换肤\n                    changeSkinByConfig(ConfigChangeResourceLoader.MODE_NIGHT, listener);\n                    break;\n            }\n        }\n    }\n\n    public boolean isDefaultMode() {\n        return mIsDefaultMode;\n    }\n\n    public boolean isSwitching() {\n        return mIsSwitching;\n    }\n\n    private void changeSkinByApk(OnSkinChangeListener listener) {\n        SkinUtils.copyAssetSkin(mContext);\n\n        File skin = new File(\n                SkinUtils.getTotalSkinPath(mContext));\n\n        if (skin == null || !skin.exists()) {\n            UIUtil.showToast(mContext, \"皮肤初始化失败\");\n            return;\n        }\n\n        SkinManager.getInstance().loadAPKSkin(\n                skin.getAbsolutePath(), new MyLoadSkinListener(listener));\n    }\n\n    private void changeSkinBySuffix(OnSkinChangeListener listener) {\n                SkinManager.getInstance().loadSkin(\"_night\",\n                new SuffixResourceLoader(mContext),\n                new MyLoadSkinListener(listener));\n    }\n\n    private void changeSkinByConfig(String mode, OnSkinChangeListener listener) {\n        SkinManager.getInstance().loadSkin(mode,\n                new ConfigChangeResourceLoader(mContext),\n                new MyLoadSkinListener(listener));\n    }\n\n    private class MyLoadSkinListener implements ILoadSkinListener {\n\n        private final OnSkinChangeListener mListener;\n\n        public MyLoadSkinListener(OnSkinChangeListener listener) {\n            mListener = listener;\n        }\n\n        @Override\n        public void onLoadStart(String skinIdentifier) {\n        }\n\n        @Override\n        public void onLoadSuccess(String skinIdentifier) {\n            mIsSwitching = false;\n\n            //存储皮肤标识\n            SkinConfigHelper.saveSkinIdentifier(skinIdentifier);\n\n            UITaskRunner.getHandler().post(new Runnable() {\n                @Override\n                public void run() {\n                    if(null != mListener) {\n                        mListener.onSuccess();\n                    }\n                }\n            });\n        }\n\n        @Override\n        public void onLoadFail(String skinIdentifier) {\n            mIsSwitching = false;\n\n            UITaskRunner.getHandler().post(new Runnable() {\n                @Override\n                public void run() {\n                    if (null != mListener) {\n                        mListener.onError();\n                    }\n                }\n            });\n        }\n    };\n\n    public interface OnSkinChangeListener {\n        void onSuccess();\n\n        void onError();\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/org/qcode/demo/skin/SkinConfigHelper.java",
    "content": "package org.qcode.demo.skin;\n\nimport org.qcode.demo.base.Settings;\n\npublic class SkinConfigHelper {\n\n    /***\n     * 获取当前皮肤包的标识\n     */\n    public static String getSkinIdentifier() {\n        return Settings.getInstance().getString(\n                SkinConstant.CUSTOM_SKIN_IDENTIFIER,\n                SkinConstant.DEFAULT_SKIN);\n    }\n\n    /**\n     * 保存皮肤包的标识\n     */\n    public static void saveSkinIdentifier(String identifier) {\n        Settings.getInstance().setSetting(\n                SkinConstant.CUSTOM_SKIN_IDENTIFIER,\n                identifier);\n    }\n\n    /**\n     * 是否默认皮肤\n     */\n    public static boolean isDefaultSkin() {\n        return SkinConstant.DEFAULT_SKIN.equals(getSkinIdentifier());\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/org/qcode/demo/skin/SkinConstant.java",
    "content": "package org.qcode.demo.skin;\n\n/**\n * qqliu\n * 2016/10/8.\n */\n\npublic class SkinConstant {\n\n    public static final String PACKAGE_NAME = \"org.qcode.demo\";\n\n    /**皮肤标识存放*/\n    public static final String CUSTOM_SKIN_IDENTIFIER =\n            PACKAGE_NAME + \".CUSTOM_SKIN_IDENTIFIER\";\n\n    /**默认皮肤*/\n    public static final String DEFAULT_SKIN = \"default\";\n}\n"
  },
  {
    "path": "app/src/main/java/org/qcode/demo/skin/SkinUtils.java",
    "content": "package org.qcode.demo.skin;\n\nimport android.content.Context;\n\nimport org.qcode.demo.utils.FileUtils;\nimport org.qcode.qskinloader.base.utils.Logging;\n\nimport java.io.File;\n\n/**\n * qqliu\n * 2016/9/21.\n */\npublic class SkinUtils {\n    private static final String TAG = \"NightModeUtils\";\n\n    private static final String SKIN_NAME = \"nightMode.skin\";\n\n    public static String getTotalSkinPath(Context context) {\n        String SKIN_PATH = context.getCacheDir().getAbsolutePath();\n        String totalPath = SKIN_PATH + File.separator + SKIN_NAME;\n        return totalPath;\n    }\n\n    public static boolean copyAssetSkin(Context context) {\n        String totalPath = getTotalSkinPath(context);\n        File skin = new File(totalPath);\n\n        if (skin == null || !skin.exists() || needUpdateSkin()) {\n            long currTime = System.currentTimeMillis();\n\n            boolean isSuccess = FileUtils.copyAssetFile(context, SKIN_NAME,\n                    context.getCacheDir().getAbsolutePath(),\n                    SKIN_NAME);\n\n            long diff = System.currentTimeMillis() - currTime;\n            Logging.d(TAG, \"copyAssetSkin()| copy file time: \" + diff);\n\n            return isSuccess;\n        }\n\n        return false;\n    }\n\n    private static boolean needUpdateSkin() {\n        //每次都拷贝皮肤包\n        return true;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/org/qcode/demo/ui/customattr/CustomAttrViewActivity.java",
    "content": "package org.qcode.demo.ui.customattr;\n\nimport android.os.Bundle;\n\nimport org.qcode.demo.BaseActivity;\nimport org.qcode.qskinloader.SkinManager;\nimport org.qcode.skintestdemo.R;\n\nimport static org.qcode.demo.ui.customattr.DefBackgroundAttrHandler.DEF_BACKGROUND;\nimport static org.qcode.demo.ui.customattr.DefTextColorAttrHandler.DEF_TEXT_COLOR;\n\npublic class CustomAttrViewActivity extends BaseActivity {\n\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        registerHandler();\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.activity_custom_attr_test);\n    }\n\n    private void registerHandler() {\n        SkinManager.getInstance().registerSkinAttrHandler(\n                DEF_TEXT_COLOR, new DefTextColorAttrHandler());\n\n        SkinManager.getInstance().registerSkinAttrHandler(\n                DEF_BACKGROUND, new DefBackgroundAttrHandler());\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/org/qcode/demo/ui/customattr/CustomTextView.java",
    "content": "package org.qcode.demo.ui.customattr;\n\nimport android.content.Context;\nimport android.content.res.TypedArray;\nimport android.graphics.Color;\nimport android.util.AttributeSet;\nimport android.widget.TextView;\n\nimport org.qcode.skintestdemo.R;\n\n/**\n * qqliu\n * 2016/9/11.\n */\npublic class CustomTextView extends TextView {\n    public CustomTextView(Context context) {\n        this(context, null);\n    }\n\n    public CustomTextView(Context context, AttributeSet attrs) {\n        super(context, attrs);\n\n        TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.app);\n        int textColor = array.getColor(R.styleable.app_defTextColor, Color.BLACK);\n        int bgDrawableId = array.getResourceId(R.styleable.app_defBackground, 0);\n        array.recycle();\n\n        setTextColor(textColor);\n        setBackgroundResource(bgDrawableId);\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/org/qcode/demo/ui/customattr/DefBackgroundAttrHandler.java",
    "content": "package org.qcode.demo.ui.customattr;\n\nimport android.graphics.drawable.Drawable;\nimport android.view.View;\n\nimport org.qcode.qskinloader.IResourceManager;\nimport org.qcode.qskinloader.ISkinAttrHandler;\nimport org.qcode.qskinloader.attrhandler.SkinAttrUtils;\nimport org.qcode.qskinloader.entity.SkinAttr;\n\n/**\n * qqliu\n * 2016/10/9.\n */\n\npublic class DefBackgroundAttrHandler implements ISkinAttrHandler {\n    public static final String DEF_BACKGROUND = \"defBackground\";\n\n    @Override\n    public void apply(View view, SkinAttr skinAttr, IResourceManager resourceManager) {\n        if (null == view\n                || null == skinAttr\n                || !(DEF_BACKGROUND.equals(skinAttr.mAttrName))) {\n            //自定义属性处理时，需要明确当前处理器能处理的属性，此处是DEF_BACKGROUND\n            return;\n        }\n\n        if (!(view instanceof CustomTextView)) {\n            //防止在错误的View上设置了此属性\n            return;\n        }\n\n        //封装了取ColorDrawable和取普通Drawable的逻辑\n        Drawable drawable = SkinAttrUtils.getDrawable(\n                resourceManager, skinAttr.mAttrValueRefId,\n                skinAttr.mAttrValueTypeName, skinAttr.mAttrValueRefName);\n\n        if (null != drawable) {\n            view.setBackgroundDrawable(drawable);\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/org/qcode/demo/ui/customattr/DefTextColorAttrHandler.java",
    "content": "package org.qcode.demo.ui.customattr;\n\nimport android.content.res.ColorStateList;\nimport android.content.res.Resources;\nimport android.view.View;\n\nimport org.qcode.qskinloader.IResourceManager;\nimport org.qcode.qskinloader.ISkinAttrHandler;\nimport org.qcode.qskinloader.entity.SkinAttr;\nimport org.qcode.qskinloader.entity.SkinConstant;\n\nimport static org.qcode.qskinloader.entity.SkinConstant.RES_TYPE_NAME_COLOR;\n\n/**\n * qqliu\n * 2016/10/9.\n */\n\npublic class DefTextColorAttrHandler implements ISkinAttrHandler {\n    public static final String DEF_TEXT_COLOR = \"defTextColor\";\n\n    @Override\n    public void apply(View view, SkinAttr skinAttr, IResourceManager resourceManager) {\n        if(null == view\n                || null == skinAttr\n                || !(DEF_TEXT_COLOR.equals(skinAttr.mAttrName))) {\n            //自定义属性处理时，需要明确当前处理器能处理的属性，此处是DEF_TEXT_COLOR\n            return;\n        }\n\n        if(!(view instanceof CustomTextView)) {\n            //防止在错误的View上设置了此属性\n            return;\n        }\n\n        CustomTextView tv = (CustomTextView) view;\n\n        if (RES_TYPE_NAME_COLOR.equals(skinAttr.mAttrValueTypeName)) {\n            if (SkinConstant.RES_TYPE_NAME_COLOR.equals(skinAttr.mAttrValueTypeName)) {\n                try {\n                    //先尝试按照int型颜色解析\n                    int textColor = resourceManager.getColor(\n                            skinAttr.mAttrValueRefId, skinAttr.mAttrValueRefName);\n                    tv.setTextColor(textColor);\n\n                } catch (Resources.NotFoundException ex) {\n                    //不是int型则按照ColorStateList引用来解析\n                    ColorStateList textColor = resourceManager.getColorStateList(\n                            skinAttr.mAttrValueRefId, skinAttr.mAttrValueRefName);\n                    tv.setTextColor(textColor);\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/org/qcode/demo/ui/dynamicaddview/DynamicAddViewActivity.java",
    "content": "package org.qcode.demo.ui.dynamicaddview;\n\nimport android.os.Bundle;\nimport android.view.ViewGroup;\nimport android.widget.LinearLayout;\n\nimport org.qcode.demo.BaseActivity;\nimport org.qcode.demo.ui.viewpageandlistview.DataListAdapter;\nimport org.qcode.demo.utils.UIUtil;\nimport org.qcode.skintestdemo.R;\n\npublic class DynamicAddViewActivity extends BaseActivity {\n\n    public static final int SHOW_COUNT = 40;\n    private LinearLayout mContainer;\n    private DataListAdapter mAdapter;\n    private int mCount;\n    private boolean mIsDestroying;\n\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.activity_dynamic_add_view);\n        mContainer = (LinearLayout) findViewById(R.id.dynamic_container);\n\n        mAdapter = new DataListAdapter(this, \"\");\n\n        mContainer.postDelayed(new Runnable() {\n            @Override\n            public void run() {\n                if(mIsDestroying) {\n                    return;\n                }\n                if(mCount >= 25) {\n                    UIUtil.showToast(DynamicAddViewActivity.this, \"添加完毕\");\n                    return;\n                }\n\n                for (int i = SHOW_COUNT * mCount; i < SHOW_COUNT * (mCount + 1); i++) {\n                    LinearLayout.LayoutParams param = new LinearLayout.LayoutParams(\n                            ViewGroup.LayoutParams.MATCH_PARENT, UIUtil.dip2px(getApplicationContext(), 50)\n                    );\n                    mContainer.addView(mAdapter.getView(i, null, null), param);\n                }\n\n                mCount++;\n                mContainer.postDelayed(this, 200);\n            }\n        }, 200);\n    }\n\n    @Override\n    protected void onDestroy() {\n        mIsDestroying = true;\n        super.onDestroy();\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/org/qcode/demo/ui/gridview/GridViewActivity.java",
    "content": "package org.qcode.demo.ui.gridview;\n\nimport android.os.Bundle;\nimport android.widget.GridView;\n\nimport org.qcode.demo.BaseActivity;\nimport org.qcode.demo.ui.viewpageandlistview.DataListAdapter;\nimport org.qcode.skintestdemo.R;\n\npublic class GridViewActivity extends BaseActivity{\n\n    private GridView mGridView;\n\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.activity_grid_view);\n        mGridView = (GridView)findViewById(R.id.grid_view);\n        mGridView.setAdapter(new DataListAdapter(this, \"\"));\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/org/qcode/demo/ui/otherscene/CustomDialog.java",
    "content": "package org.qcode.demo.ui.otherscene;\n\nimport android.app.Dialog;\nimport android.content.Context;\nimport android.content.DialogInterface;\nimport android.view.View;\nimport android.view.Window;\n\nimport org.qcode.qskinloader.SkinManager;\nimport org.qcode.skintestdemo.R;\n\n/**\n * qqliu\n * 2016/10/14.\n */\n\npublic class CustomDialog extends Dialog implements View.OnClickListener {\n\n    private WrapperDismissListener mDismissListener;\n\n    public CustomDialog(Context context) {\n        super(context);\n\n        getWindow().requestFeature(Window.FEATURE_NO_TITLE);\n\n        setContentView(R.layout.layout_dialog_custom);\n\n        findViewById(R.id.btn_dialog_confirm).setOnClickListener(this);\n        findViewById(R.id.btn_dialog_cancel).setOnClickListener(this);\n\n//        //方案二：---------------------------------\n//        mDismissListener = new WrapperDismissListener();\n//        super.setOnDismissListener(mDismissListener);\n//        //如果换肤过程中对话框需要展示，则需要将对话框的View\n//        //添加到框架的WindowViewManager中存储;\n//        //建议addWindowView与removeWindowView成对使用\n//        SkinManager\n//                .getWindowViewManager()\n//                .addWindowView(findViewById(android.R.id.content))\n//                .applySkinForViews(true);\n    }\n\n    @Override\n    public void show() {\n        super.show();\n\n        //方案一：---------------------------------\n        //如果换肤的过程中对话框不会展示，则在Dialog初始化时\n        //按照当前皮肤设置应用一次皮肤即可\n        SkinManager.getInstance().applySkin(\n                findViewById(android.R.id.content), true);\n    }\n\n    @Override\n    public void setOnDismissListener(OnDismissListener listener) {\n        mDismissListener.setDismissListener(listener);\n    }\n\n    public void onClick(View view) {\n        switch (view.getId()) {\n            case R.id.btn_dialog_confirm:\n            case R.id.btn_dialog_cancel:\n                dismiss();\n                break;\n        }\n    }\n\n    //代理外部的OnDismissListener\n    private static class WrapperDismissListener implements OnDismissListener {\n\n        private OnDismissListener mOuterListener;\n\n        void setDismissListener(OnDismissListener listener) {\n            mOuterListener = listener;\n        }\n\n        @Override\n        public void onDismiss(DialogInterface dialogInterface) {\n            if(null != mOuterListener) {\n                mOuterListener.onDismiss(dialogInterface);\n            }\n\n            if(!(dialogInterface instanceof CustomDialog)) {\n                return;\n            }\n\n            CustomDialog dialog = (CustomDialog) dialogInterface;\n\n            //与addWindowView成对使用\n            SkinManager.getWindowViewManager()\n                    .removeWindowView(dialog.findViewById(android.R.id.content));\n        }\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/org/qcode/demo/ui/otherscene/FloatView.java",
    "content": "package org.qcode.demo.ui.otherscene;\n\nimport android.content.Context;\nimport android.widget.ImageView;\n\nimport org.qcode.qskinloader.SkinManager;\nimport org.qcode.qskinloader.entity.SkinAttrName;\nimport org.qcode.skintestdemo.R;\n\n/**\n * qqliu\n * 2016/10/14.\n */\n\npublic class FloatView extends ImageView {\n    public FloatView(Context context) {\n        super(context);\n\n        //动态设置皮肤\n        SkinManager\n                .with(this)\n                .setViewAttrs(SkinAttrName.SRC, R.drawable.drawable_float_view)\n                .applySkin(false);\n    }\n\n    public void dismiss() {\n        SkinManager\n                .getWindowViewManager()\n                .removeWindowView(this);\n    }\n\n    public void show() {\n        //因为悬浮窗直接加载在WindowManager上,我们需要将View添加到框架内维护\n        SkinManager\n                .getWindowViewManager()\n                .addWindowView(this);\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/org/qcode/demo/ui/otherscene/OtherSceneActivity.java",
    "content": "package org.qcode.demo.ui.otherscene;\n\nimport android.app.Dialog;\nimport android.content.Context;\nimport android.graphics.PixelFormat;\nimport android.os.Bundle;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.WindowManager;\nimport android.widget.PopupWindow;\nimport android.widget.TextView;\nimport android.widget.Toast;\n\nimport org.qcode.demo.BaseActivity;\nimport org.qcode.demo.ui.customattr.DefBackgroundAttrHandler;\nimport org.qcode.demo.utils.UIUtil;\nimport org.qcode.qskinloader.SkinManager;\nimport org.qcode.qskinloader.entity.DynamicAttr;\nimport org.qcode.skintestdemo.R;\n\nimport static org.qcode.demo.ui.customattr.DefBackgroundAttrHandler.DEF_BACKGROUND;\n\n/**\n * qqliu\n * 2016/10/13.\n */\n\npublic class OtherSceneActivity extends BaseActivity {\n    private PopupWindow mPopWindow;\n    private Dialog mDialog;\n    private FloatView mFloatView;\n    private boolean mIsShowingFloatView;\n\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n\n        SkinManager.getInstance().registerSkinAttrHandler(\n                SpannableSkinAttr.HIGHLIGHT_SPANNABLE, new SpannableSkinAttrHandler());\n\n        setContentView(R.layout.activity_other_scene);\n\n        TextView textView = (TextView) findViewById(R.id.textviewSpannableSkin);\n\n        DynamicAttr dynamicAttr = new SpannableSkinAttr(\n                textView.getText().toString(), R.color.color_red);\n        SkinManager.with(textView).addViewAttrs(dynamicAttr);\n    }\n\n    public void onClick(View view) {\n        switch (view.getId()) {\n            case R.id.btnPopWindow:\n                View contentView = LayoutInflater.from(this).inflate(R.layout.layout_popwindow, null);\n                //对所有不在Activity的Content下的View（PopWindow/Dialog/Float View等）都可以应用\n                //此方法将View添加到SkinManager内维护\n                //注意，虽然框架内对View采用了弱引用，但是建议此方法配合remove或clear方法一起使用，释放对View的静态引用\n                SkinManager.getWindowViewManager()\n                        .addWindowView(contentView)\n                        .applySkinForViews(true);\n\n                if (null == mPopWindow) {\n                    mPopWindow = new PopupWindow(\n                            contentView,\n                            WindowManager.LayoutParams.WRAP_CONTENT,\n                            WindowManager.LayoutParams.WRAP_CONTENT);\n                }\n\n                if (!mPopWindow.isShowing()) {\n                    mPopWindow.showAsDropDown(view);\n                } else {\n                    mPopWindow.dismiss();\n                }\n\n                break;\n\n\n            case R.id.btnDialog:\n                if (null == mDialog) {\n                    mDialog = new CustomDialog(this);\n                }\n\n                if (!mDialog.isShowing()) {\n                    mDialog.show();\n                } else {\n                    mDialog.dismiss();\n                }\n                break;\n\n\n            case R.id.btnFloatWindow:\n                if (null == mFloatView) {\n                    mFloatView = new FloatView(this);\n                    mFloatView.setOnClickListener(new View.OnClickListener() {\n                        @Override\n                        public void onClick(View v) {\n                            mIsShowingFloatView = false;\n                            hideFloatView();\n                        }\n                    });\n                }\n\n                mIsShowingFloatView = !mIsShowingFloatView;\n                if (mIsShowingFloatView) {\n                    showFloatView();\n                } else {\n                    hideFloatView();\n                }\n                break;\n\n            case R.id.btnPopWindowClick:\n                findViewById(R.id.btnPopWindow).performClick();\n                Toast.makeText(this, \"popWindow点击\", Toast.LENGTH_SHORT).show();\n                break;\n        }\n    }\n\n    private void showFloatView() {\n        WindowManager windowManager = (WindowManager) getApplicationContext()\n                .getSystemService(Context.WINDOW_SERVICE);\n        WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams(\n                WindowManager.LayoutParams.WRAP_CONTENT,\n                WindowManager.LayoutParams.WRAP_CONTENT);\n        int width = UIUtil.dip2px(this, 50);\n        layoutParams.width = width;\n        layoutParams.height = width;\n        layoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;\n        layoutParams.type = WindowManager.LayoutParams.TYPE_SYSTEM_ERROR;\n        layoutParams.format = PixelFormat.TRANSLUCENT; // 设置图片格式，效果为背景透明\n        windowManager.addView(mFloatView, layoutParams);\n        mFloatView.show();\n    }\n\n    private void hideFloatView() {\n        WindowManager windowManager = getWindowManager();\n        windowManager.removeView(mFloatView);\n        mFloatView.dismiss();\n    }\n\n    @Override\n    protected int getWindowBackgroundResource() {\n        return R.color.color_transprent;\n    }\n\n    @Override\n    public boolean isSwitchSkinImmediately() {\n        //悬浮窗会导致Activity失去Focus,但是悬浮窗又是半透明的,所以此处建议立刻切换皮肤\n        return true;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/org/qcode/demo/ui/otherscene/SpannableSkinAttr.java",
    "content": "package org.qcode.demo.ui.otherscene;\r\n\r\nimport org.qcode.qskinloader.entity.DynamicAttr;\r\n\r\n/***\r\n * author: qqliu\r\n * created at 2018/1/5\r\n */\r\npublic class SpannableSkinAttr extends DynamicAttr {\r\n\r\n    public static final String HIGHLIGHT_SPANNABLE = \"highlightSpannable\";\r\n    public final String mText;\r\n\r\n    public SpannableSkinAttr(String text, int attrValueRefId) {\r\n        super(HIGHLIGHT_SPANNABLE, attrValueRefId);\r\n        mText = text;\r\n\r\n        //这里一定要设置，mText就丢弃了\r\n        keepInstance = true;\r\n    }\r\n}\r\n"
  },
  {
    "path": "app/src/main/java/org/qcode/demo/ui/otherscene/SpannableSkinAttrHandler.java",
    "content": "package org.qcode.demo.ui.otherscene;\n\nimport android.content.res.ColorStateList;\nimport android.content.res.Resources;\nimport android.text.Spannable;\nimport android.text.SpannableString;\nimport android.text.Spanned;\nimport android.text.style.ForegroundColorSpan;\nimport android.view.View;\nimport android.widget.TextView;\n\nimport org.qcode.demo.ui.customattr.CustomTextView;\nimport org.qcode.qskinloader.IResourceManager;\nimport org.qcode.qskinloader.ISkinAttrHandler;\nimport org.qcode.qskinloader.entity.SkinAttr;\nimport org.qcode.qskinloader.entity.SkinConstant;\n\nimport static org.qcode.qskinloader.entity.SkinConstant.RES_TYPE_NAME_COLOR;\n\n/***\n * author: qqliu\n * created at 2018/1/5\n */\npublic class SpannableSkinAttrHandler implements ISkinAttrHandler {\n    @Override\n    public void apply(View view, SkinAttr skinAttr, IResourceManager resourceManager) {\n        if(null == view\n                || null == skinAttr\n                || !(SpannableSkinAttr.HIGHLIGHT_SPANNABLE.equals(skinAttr.mAttrName))) {\n            return;\n        }\n\n        if(!(view instanceof TextView)) {\n            return;\n        }\n\n        SpannableSkinAttr spannableSkinAttr = (SpannableSkinAttr) skinAttr.mDynamicAttr;\n        if (null == spannableSkinAttr) {\n            return;\n        }\n\n        TextView tv = (TextView) view;\n\n        if (RES_TYPE_NAME_COLOR.equals(skinAttr.mAttrValueTypeName)) {\n            int textColor = resourceManager.getColor(\n                    skinAttr.mAttrValueRefId, skinAttr.mAttrValueRefName);\n            SpannableString spannableString = new SpannableString(spannableSkinAttr.mText);\n            spannableString.setSpan(new ForegroundColorSpan(textColor), 0, 11, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);\n            tv.setText(spannableString);\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/org/qcode/demo/ui/recyclerview/DataRecyclerViewAdapter.java",
    "content": "package org.qcode.demo.ui.recyclerview;\n\nimport android.content.Context;\nimport android.support.v7.widget.RecyclerView;\nimport android.view.Gravity;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.ImageView;\nimport android.widget.LinearLayout;\nimport android.widget.TextView;\n\nimport org.qcode.demo.utils.UIUtil;\nimport org.qcode.qskinloader.SkinManager;\nimport org.qcode.qskinloader.entity.SkinAttrName;\nimport org.qcode.skintestdemo.R;\n\nimport java.util.ArrayList;\n\n/**\n * qqliu\n * 2016/9/11.\n */\npublic class DataRecyclerViewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {\n    private static final int TEXT_ID = 0x5f000031;\n    private final Context mContext;\n\n    private ArrayList<String> mList = new ArrayList<String>();\n\n    public DataRecyclerViewAdapter(Context context) {\n        mContext = context;\n\n        for (int i = 0; i < 1000; i++) {\n            mList.add(\"测试\" + i);\n        }\n    }\n\n    @Override\n    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {\n        View view = newItem();\n        return new RecyclerView.ViewHolder(view) {\n        };\n    }\n\n    @Override\n    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {\n        TextView textView = (TextView) holder.itemView.findViewById(TEXT_ID);\n        textView.setText(mList.get(position));\n        SkinManager.getInstance().applySkin(holder.itemView, true);\n    }\n\n    @Override\n    public int getItemCount() {\n        return mList.size();\n    }\n\n    public View newItem() {\n        LinearLayout linearLayout = new LinearLayout(mContext);\n        linearLayout.setOrientation(LinearLayout.HORIZONTAL);\n        linearLayout.setBackgroundResource(R.color.color_background);\n        linearLayout.setGravity(Gravity.CENTER_VERTICAL);\n\n        ImageView imageView = new ImageView(mContext);\n        LinearLayout.LayoutParams paramImg =\n                new LinearLayout.LayoutParams(\n                        UIUtil.dip2px(mContext, 50), UIUtil.dip2px(mContext, 50));\n        imageView.setImageResource(R.mipmap.ic_launcher);\n        linearLayout.addView(imageView, paramImg);\n\n        TextView textView = new TextView(mContext);\n        textView.setId(TEXT_ID);\n        LinearLayout.LayoutParams paramText =\n                new LinearLayout.LayoutParams(\n                        UIUtil.dip2px(mContext, 100), UIUtil.dip2px(mContext, 50));\n        textView.setTextColor(mContext.getResources().getColor(R.color.color_text));\n        linearLayout.addView(textView, paramText);\n\n        SkinManager\n                .with(linearLayout)\n                .setViewAttrs(SkinAttrName.BACKGROUND, R.color.color_background);\n\n        SkinManager\n                .with(textView)\n                .setViewAttrs(SkinAttrName.TEXT_COLOR, R.color.color_text);\n\n        return linearLayout;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/org/qcode/demo/ui/recyclerview/RecyclerViewActivity.java",
    "content": "package org.qcode.demo.ui.recyclerview;\n\nimport android.os.Bundle;\nimport android.support.v7.widget.LinearLayoutManager;\nimport android.support.v7.widget.RecyclerView;\n\nimport org.qcode.demo.BaseActivity;\nimport org.qcode.skintestdemo.R;\n\npublic class RecyclerViewActivity extends BaseActivity {\n\n    private RecyclerView mRecyclerView;\n\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.activity_recycler_view);\n        mRecyclerView = (RecyclerView) findViewById(R.id.recycler_view);\n        mRecyclerView.setAdapter(new DataRecyclerViewAdapter(this));\n        LinearLayoutManager layoutManager = new LinearLayoutManager(this);\n        mRecyclerView.setLayoutManager(layoutManager);\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/org/qcode/demo/ui/viewpageandlistview/DataListAdapter.java",
    "content": "package org.qcode.demo.ui.viewpageandlistview;\n\nimport android.content.Context;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.BaseAdapter;\nimport android.widget.TextView;\n\nimport org.qcode.qskinloader.SkinManager;\nimport org.qcode.skintestdemo.R;\n\nimport java.util.ArrayList;\n\n/**\n * qqliu\n * 2016/9/9.\n */\npublic class DataListAdapter extends BaseAdapter {\n    private static final String TAG = \"MyListAdapter\";\n    private final String mOutterIdentifier;\n    private Context mContext;\n\n    private ArrayList<String> mList = new ArrayList<String>();\n\n    public DataListAdapter(Context context, String outterIdentifier) {\n        mContext = context;\n        mOutterIdentifier = outterIdentifier;\n        for (int i = 0; i < 1000; i++) {\n            mList.add(\"测试\" + i);\n        }\n    }\n\n    public String getIdentifier() {\n        return mOutterIdentifier;\n    }\n\n    @Override\n    public int getCount() {\n        return mList.size();\n    }\n\n    @Override\n    public Object getItem(int position) {\n        return mList.get(position);\n    }\n\n    @Override\n    public long getItemId(int position) {\n        return position;\n    }\n\n    @Override\n    public View getView(int position, View convertView, ViewGroup parent) {\n        if (null == convertView) {\n            convertView = LayoutInflater.from(mContext).inflate(R.layout.list_item_view, null);\n        }\n        //在Adapter getView处应用处理,保证动态添加的View刷新了皮肤\n        SkinManager.getInstance().applySkin(convertView, true);\n\n        TextView txtView = (TextView) convertView.findViewById(R.id.list_item_text_view);\n        txtView.setText(mOutterIdentifier + \" \" + mList.get(position));\n\n        return convertView;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/org/qcode/demo/ui/viewpageandlistview/NewsPageAdapter.java",
    "content": "package org.qcode.demo.ui.viewpageandlistview;\n\nimport android.content.Context;\nimport android.view.View;\nimport android.widget.ListView;\n\nimport org.qcode.qskinloader.base.utils.Logging;\n\nimport java.util.ArrayList;\n\npublic class NewsPageAdapter extends RecyclablePageAdapter<ListView> {\n    private static final String TAG = \"NewsPageAdapter\";\n\n    private Context mContext;\n\n    private ArrayList<String> mList = new ArrayList<String>();\n\n    public NewsPageAdapter(Context context) {\n        mContext = context;\n        for (int i = 0; i < 15; i++) {\n            mList.add(\"测试\" + i);\n        }\n    }\n\n    @Override\n    public int getCount() {\n        return mList.size();\n    }\n\n    @Override\n    public boolean isViewFromObject(View view, Object obj) {\n        if (view instanceof ListView && null != ((ListView) view).getAdapter()) {\n            return obj.equals(((DataListAdapter) ((ListView) view).getAdapter()).getIdentifier());\n        }\n        return false;\n    }\n\n    @Override\n    public int getItemPosition(Object object) {\n        super.getItemPosition(object);\n\n        for (int i = 0; i < mList.size(); i++) {\n            if (mList.get(i).equals(object)) {\n                return i;\n            }\n        }\n\n        return -1;\n    }\n\n    @Override\n    protected Object getItemObject(int position) {\n        return mList.get(position);\n    }\n\n    @Override\n    protected ListView createItemView(int itemViewType) {\n        ListView listView = new ListView(mContext);\n        return listView;\n    }\n\n    @Override\n    protected void onBindView(ListView itemView, int position, int itemViewType) {\n        Logging.d(TAG, \"onBindView()| position= \" + position);\n\n        String str = mList.get(position);\n        DataListAdapter adapter = new DataListAdapter(mContext, str);\n        itemView.setAdapter(adapter);\n    }\n\n    @Override\n    protected void destroyItemView(ListView view) {\n\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/org/qcode/demo/ui/viewpageandlistview/RecyclablePageAdapter.java",
    "content": "package org.qcode.demo.ui.viewpageandlistview;\n\nimport android.os.Parcelable;\nimport android.support.v4.view.PagerAdapter;\nimport android.util.SparseArray;\nimport android.view.View;\nimport android.view.ViewGroup;\n\nimport org.qcode.qskinloader.base.utils.Logging;\n\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.Iterator;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\n\n/**\n * 可回收利用的PageAdapter\n * T代表界面展示的View\n * qqliu\n * 2016/5/6.\n */\npublic abstract class RecyclablePageAdapter<T extends View> extends PagerAdapter {\n    protected static final int TYPE_DEFAULT = 0;\n\n    private static final String TAG = \"RecyclablePageAdapter\";\n\n    private SparseArray<List<T>> mRecycledViews = new SparseArray<List<T>>();\n    private HashMap<Object, ViewBundle> mUsingViewsMap = new HashMap<Object, ViewBundle>();\n    //上一次展示的位置\n    private int mLastItemPosition = -1;\n\n    @Override\n    public void startUpdate(ViewGroup container) {\n//        Logging.d(TAG, \"startUpdate()\");\n        super.startUpdate(container);\n    }\n\n    @Override\n    public final Object instantiateItem(ViewGroup container, int position) {\n        Logging.d(TAG, \"instantiateItem()| position= \" + position);\n\n        Object dataObject = getItemObject(position);\n\n        //如果要实例化的元素就是当前位置，则直接绑定数据\n//        Logging.d(TAG, \"instantiateItem()| get view for \" + dataObject);\n        ViewBundle currentUsingView = mUsingViewsMap.get(dataObject);\n        if (null != currentUsingView) {\n            onBindView(currentUsingView.view, position, currentUsingView.viewType);\n            return dataObject;\n        }\n\n        int itemViewType = getItemViewType(position);\n\n        //获取新的View\n        T itemView = null;\n        List<T> recyclableViewList = mRecycledViews.get(itemViewType);\n        if (null != recyclableViewList && recyclableViewList.size() > 0) {\n            itemView = recyclableViewList.remove(0);\n        } else {\n            itemView = createItemView(itemViewType);\n        }\n\n        //绑定数据\n        onBindView(itemView, position, itemViewType);\n        container.addView(itemView);\n//        Logging.d(TAG, \"instantiateItem()| position= \" + position + \" itemView= \" + itemView.hashCode() + \" dataObject= \" + dataObject);\n        mUsingViewsMap.put(dataObject, new ViewBundle(itemView, itemViewType));\n\n        return dataObject;\n    }\n\n    protected int getItemViewType(int position) {\n        return TYPE_DEFAULT;\n    }\n\n    @Override\n    public final void destroyItem(ViewGroup container, int position, Object object) {\n        Logging.d(TAG, \"destroyItem()| position= \" + position);\n\n//        Logging.d(TAG, \"destroyItem()| object= \" + object);\n        ViewBundle itemViewForDestroy = mUsingViewsMap.get(object);\n\n        if (null != itemViewForDestroy) {\n            //销毁item view\n            destroyItemView(itemViewForDestroy.view);\n            //移除item view, 并挪到待处理列表内\n            container.removeView(itemViewForDestroy.view);\n//            Logging.d(TAG, \"destroyItem()| remove item for \" + position + \" object= \" + object);\n            mUsingViewsMap.remove(object);\n\n            ensureListNotEmpty(itemViewForDestroy.viewType);\n            List<T> recyclableViewList = mRecycledViews.get(itemViewForDestroy.viewType);\n            recyclableViewList.add(itemViewForDestroy.view);\n        } else {\n            Logging.d(TAG, \"destroyItem()| but item view is null for position: \" + position);\n        }\n    }\n\n    @Override\n    public final void setPrimaryItem(ViewGroup container, int position, Object object) {\n        super.setPrimaryItem(container, position, object);\n        onItemPositionChange(mLastItemPosition, position);\n        mLastItemPosition = position;\n    }\n\n    @Override\n    public void finishUpdate(ViewGroup container) {\n//        Logging.d(TAG, \"finishUpdate()\");\n        super.finishUpdate(container);\n    }\n\n    @Override\n    public Parcelable saveState() {\n//        Logging.d(TAG, \"saveState()\");\n        return super.saveState();\n    }\n\n    @Override\n    public void restoreState(Parcelable state, ClassLoader loader) {\n//        Logging.d(TAG, \"restoreState()\");\n        super.restoreState(state, loader);\n    }\n\n    @Override\n    public int getItemPosition(Object object) {\n//        Logging.d(TAG, \"getItemPosition() object= \" + object);\n        return super.getItemPosition(object);\n    }\n\n    protected void onItemPositionChange(int oldPosition, int newPosition) {\n        //hook\n    }\n\n    protected T getCreatedView(int position) {\n        Object dataObject = getItemObject(position);\n        ViewBundle viewBundle = mUsingViewsMap.get(dataObject);\n        if (null == viewBundle) {\n            return null;\n        }\n        return viewBundle.view;\n    }\n\n    protected List<T> getUsingViews() {\n        List<T> resultList = new ArrayList<T>();\n        Set<Map.Entry<Object, ViewBundle>> entrySet = mUsingViewsMap.entrySet();\n        Iterator<Map.Entry<Object, ViewBundle>> iterator = entrySet.iterator();\n        while (iterator.hasNext()) {\n            Map.Entry<Object, ViewBundle> entry = iterator.next();\n            ViewBundle viewBundle = entry.getValue();\n            resultList.add(viewBundle.view);\n        }\n        return resultList;\n    }\n\n    protected abstract Object getItemObject(int position);\n\n    protected abstract T createItemView(int itemViewType);\n\n    protected abstract void onBindView(T itemView, int position, int itemViewType);\n\n    protected abstract void destroyItemView(T view);\n\n\n    private void ensureListNotEmpty(int viewType) {\n        List<T> viewList = mRecycledViews.get(viewType);\n        if (null == viewList) {\n            mRecycledViews.put(viewType, new ArrayList<T>());\n        }\n    }\n\n    private class ViewBundle {\n        T view;\n        int viewType;\n\n        public ViewBundle(T view, int viewType) {\n            this.view = view;\n            this.viewType = viewType;\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/org/qcode/demo/ui/viewpageandlistview/ViewPagerAndListViewActivity.java",
    "content": "package org.qcode.demo.ui.viewpageandlistview;\n\nimport android.os.Bundle;\nimport android.support.v4.view.ViewPager;\n\nimport org.qcode.demo.BaseActivity;\nimport org.qcode.skintestdemo.R;\n\npublic class ViewPagerAndListViewActivity extends BaseActivity {\n\n    private ViewPager mViewPager;\n\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.activity_viewpager_listview);\n        mViewPager = (ViewPager) findViewById(R.id.home_news_viewpager);\n        mViewPager.setAdapter(new NewsPageAdapter(this));\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/org/qcode/demo/utils/FileUtils.java",
    "content": "package org.qcode.demo.utils;\n\nimport android.content.Context;\nimport android.text.TextUtils;\n\nimport java.io.BufferedOutputStream;\nimport java.io.File;\nimport java.io.FileOutputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.OutputStream;\n\n/**\n * qqliu\n * 2016/9/12.\n */\npublic class FileUtils {\n    public static String getDirectory(Context context) {\n        return context.getExternalCacheDir() + File.separator + \"NightMode\";\n    }\n\n    public static String getFileName() {\n        return \"resultFile.txt\";\n    }\n\n    public static OutputStream openFileStream(Context context) {\n        return openFileStream(getDirectory(context), getFileName());\n    }\n\n    public static OutputStream openFileStream(String directory, String filePath) {\n        if (TextUtils.isEmpty(directory) || TextUtils.isEmpty(filePath)) {\n            return null;\n        }\n\n        File directoryFile = new File(directory);\n        if (!directoryFile.exists()) {\n            directoryFile.mkdirs();\n        }\n\n        File file = new File(directory + File.separator + filePath);\n        try {\n            if (!file.exists()) {\n                file.createNewFile();\n            }\n            return new FileOutputStream(file, true);\n        } catch (Exception ex) {\n            ex.printStackTrace();\n            return null;\n        }\n    }\n\n    public static void writeData(OutputStream stream, String str) {\n        if (null == stream || TextUtils.isEmpty(str)) {\n            return;\n        }\n\n        str = str + \"\\r\\n\";\n\n        try {\n            BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(stream);\n            bufferedOutputStream.write(str.getBytes());\n            bufferedOutputStream.flush();\n        } catch (Exception ex) {\n            ex.printStackTrace();\n        } finally {\n\n        }\n    }\n\n    public static void closeStream(OutputStream stream) {\n        if (null != stream) {\n            try {\n                stream.close();\n            } catch (IOException e) {\n                e.printStackTrace();\n            }\n        }\n    }\n\n    public static boolean copyAssetFile(Context context, String originFileName,\n                                     String destFilePath, String destFileName) {\n        InputStream is = null;\n        BufferedOutputStream bos = null;\n        try {\n            is = context.getAssets().open(originFileName);\n            File destPathFile = new File(destFilePath);\n            if(!destPathFile.exists()) {\n                destPathFile.mkdirs();\n            }\n\n            File destFile = new File(destFilePath + File.separator + destFileName);\n            if(!destFile.exists()) {\n                destFile.createNewFile();\n            }\n\n            FileOutputStream fos = new FileOutputStream(destFile);\n            bos = new BufferedOutputStream(fos);\n\n            byte[] buffer = new byte[256];\n            int length = 0;\n            while ((length = is.read(buffer)) > 0) {\n                bos.write(buffer, 0, length);\n            }\n            bos.flush();\n\n            return true;\n        } catch (Exception ex) {\n            ex.printStackTrace();\n        } finally {\n            if(null != is) {\n                try {\n                    is.close();\n                } catch (IOException e) {\n                    e.printStackTrace();\n                }\n            }\n\n            if(null != bos) {\n                try {\n                    bos.close();\n                } catch (IOException e) {\n                    e.printStackTrace();\n                }\n            }\n        }\n\n        return false;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/org/qcode/demo/utils/UITaskRunner.java",
    "content": "package org.qcode.demo.utils;\n\nimport android.os.Handler;\nimport android.os.Looper;\n\n/**\n * 在主线程中执行runnable\n */\npublic class UITaskRunner {\n    private Handler mHandler;\n\n    private UITaskRunner() {\n        mHandler = new Handler(Looper.getMainLooper());\n    }\n\n    private static class SingletonHolder {\n        static UITaskRunner sRunner = new UITaskRunner();\n    }\n\n    public static Handler getHandler() {\n        return SingletonHolder.sRunner.mHandler;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/org/qcode/demo/utils/UIUtil.java",
    "content": "package org.qcode.demo.utils;\n\nimport android.content.Context;\nimport android.os.Looper;\nimport android.widget.Toast;\n\n/**\n * 与UI相关的帮助类\n */\npublic final class UIUtil {\n\n\tprivate static final String TAG = \"UIUtil\";\n\n\tprivate UIUtil() { }\n\n\t/**\n\t* 根据手机的分辨率从 dip 的单位 转成为 px(像素)\n\t*/\n\tpublic static int dip2px(Context context, double dpValue) {\n\t\tfinal float scale = context.getResources().getDisplayMetrics().density;\n\t\treturn (int) (dpValue * scale + 0.5);\n\t}\n\n    /**\n     * 显示Toast\n     */\n    public static void showToast(Context context, String toast) {\n        toast(context, toast, false);\n    }\n\n\tprivate static Toast mStaticToastImpl = null;\n    public static void toast(final Context context, final String msg, final boolean isLong) {\n\t\tif (null == mStaticToastImpl) {\n\t\t\t//需要在主线程创建toast实例\n\t\t\tif (Looper.myLooper() != Looper.getMainLooper()) {\n\t\t\t\tUITaskRunner.getHandler().post(new Runnable() {\n\t\t\t\t\t@Override\n\t\t\t\t\tpublic void run() {\n\t\t\t\t\t\ttoast(context, msg, isLong);\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\n\t\tif(null == mStaticToastImpl) {\n\t\t\tsynchronized (UIUtil.class) {\n\t\t\t\tif(null == mStaticToastImpl) {\n\t\t\t\t\tmStaticToastImpl = Toast.makeText(\n\t\t\t\t\t\t\tcontext.getApplicationContext(), \"\", Toast.LENGTH_SHORT);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif(Looper.myLooper() == Looper.getMainLooper()) {\n\t\t\tmStaticToastImpl.setText(msg);\n\t\t\tmStaticToastImpl.setDuration(isLong ? Toast.LENGTH_LONG : Toast.LENGTH_SHORT);\n\t\t\t//当前在主线程\n\t\t\tmStaticToastImpl.show();\n\t\t} else {\n\t\t\tUITaskRunner.getHandler().post(new Runnable() {\n\t\t\t\t@Override\n\t\t\t\tpublic void run() {\n\t\t\t\t\tmStaticToastImpl.setText(msg);\n\t\t\t\t\tmStaticToastImpl.setDuration(isLong ? Toast.LENGTH_LONG : Toast.LENGTH_SHORT);\n\t\t\t\t\tmStaticToastImpl.show();\n\t\t\t\t}\n\t\t\t});\n\t\t}\n    }\n\n    public static void toast(Context context, String msg) {\n        toast(context, msg, true);\n    }\n}\n"
  },
  {
    "path": "app/src/main/res/drawable/btn_bg.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:shape=\"rectangle\">\n    <corners android:radius=\"4dp\"/>\n\n    <solid android:color=\"@color/color_white\"/>\n\n    <stroke android:width=\"1dp\" android:color=\"@color/gray_50\"/>\n</shape>"
  },
  {
    "path": "app/src/main/res/drawable/btn_bg_night.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:shape=\"rectangle\">\n    <corners android:radius=\"4dp\"/>\n\n    <solid android:color=\"@color/color_white_night\"/>\n\n    <stroke android:width=\"1dp\" android:color=\"@color/gray_50_night\"/>\n</shape>"
  },
  {
    "path": "app/src/main/res/drawable/drawable_float_view.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:shape=\"oval\">\n\n    <size android:width=\"50dp\"\n        android:height=\"50dp\"/>\n    <solid android:color=\"@color/color_red\"/>\n</shape>"
  },
  {
    "path": "app/src/main/res/drawable/drawable_float_view_night.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:shape=\"oval\">\n\n    <size android:width=\"50dp\"\n        android:height=\"50dp\"/>\n    <solid android:color=\"@color/color_red_night\"/>\n</shape>"
  },
  {
    "path": "app/src/main/res/drawable/news_item_selector.xml",
    "content": "<?xml version = \"1.0\" encoding = \"utf-8\"?>\n<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n    <item android:drawable=\"@color/color_item_press_bg\" android:state_pressed=\"true\" android:state_focused=\"true\"/>\n    <item android:drawable=\"@color/color_item_normal_bg\"/>\n\n</selector>"
  },
  {
    "path": "app/src/main/res/drawable/news_item_selector_night.xml",
    "content": "<?xml version = \"1.0\" encoding = \"utf-8\"?>\n<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n    <item android:drawable=\"@color/color_item_press_bg_night\" android:state_pressed=\"true\" android:state_focused=\"true\"/>\n    <item android:drawable=\"@color/color_item_normal_bg_night\"/>\n\n</selector>"
  },
  {
    "path": "app/src/main/res/drawable-night/btn_bg.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:shape=\"rectangle\">\n    <corners android:radius=\"4dp\"/>\n\n    <solid android:color=\"@color/color_white\"/>\n\n    <stroke android:width=\"1dp\" android:color=\"@color/gray_50\"/>\n</shape>"
  },
  {
    "path": "app/src/main/res/drawable-night/drawable_float_view.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:shape=\"oval\">\n\n    <size android:width=\"50dp\"\n        android:height=\"50dp\"/>\n    <solid android:color=\"@color/color_red\"/>\n</shape>"
  },
  {
    "path": "app/src/main/res/drawable-night/news_item_selector.xml",
    "content": "<?xml version = \"1.0\" encoding = \"utf-8\"?>\n<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n    <item android:drawable=\"@color/color_item_press_bg\" android:state_pressed=\"true\" android:state_focused=\"true\"/>\n    <item android:drawable=\"@color/color_item_normal_bg\"/>\n\n</selector>"
  },
  {
    "path": "app/src/main/res/layout/activity_base_activity.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:skin=\"http://schemas.android.com/android/skin\"\n    android:orientation=\"vertical\" android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\">\n\n    <Button\n        android:id=\"@+id/setting_btn\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"50dp\"\n        android:textColor=\"@color/color_text\"\n        android:background=\"@color/color_background\"\n        android:text=\"设置\"\n        skin:enable=\"true\"/>\n\n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"50dp\"\n        android:orientation=\"horizontal\">\n\n        <Button\n            android:id=\"@+id/btn_recyclerview\"\n            android:layout_width=\"0dp\"\n            android:layout_height=\"match_parent\"\n            android:layout_weight=\"1\"\n            android:background=\"@color/color_background\"\n            android:text=\"RecyclerView\"\n            android:textColor=\"@color/color_text\"\n            skin:enable=\"true\" />\n\n        <Button\n            android:id=\"@+id/btn_gridview\"\n            android:layout_width=\"0dp\"\n            android:layout_height=\"match_parent\"\n            android:layout_weight=\"1\"\n            android:background=\"@color/color_background\"\n            android:text=\"GridView\"\n            android:textColor=\"@color/color_text\"\n            skin:enable=\"true\" />\n\n        <Button\n            android:id=\"@+id/btn_dynamic_add_view\"\n            android:layout_width=\"0dp\"\n            android:layout_height=\"match_parent\"\n            android:layout_weight=\"1\"\n            android:background=\"@color/color_background\"\n            android:text=\"DynamicAddView\"\n            android:textColor=\"@color/color_text\"\n            skin:enable=\"true\" />\n\n        <Button\n            android:id=\"@+id/btn_self_def_view\"\n            android:layout_width=\"0dp\"\n            android:layout_height=\"match_parent\"\n            android:layout_weight=\"1\"\n            android:background=\"@color/color_background\"\n            android:text=\"SelfDefineView\"\n            android:textColor=\"@color/color_text\"\n            skin:enable=\"true\" />\n    </LinearLayout>\n\n    <FrameLayout\n        android:id=\"@+id/content_layout\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"0dp\"\n        android:layout_weight=\"1\"/>\n\n</LinearLayout>"
  },
  {
    "path": "app/src/main/res/layout/activity_custom_attr_test.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/android/app\"\n    xmlns:skin=\"http://schemas.android.com/android/skin\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:background=\"@color/color_background\"\n    android:orientation=\"vertical\"\n    android:paddingBottom=\"@dimen/activity_vertical_margin\"\n    android:paddingLeft=\"@dimen/activity_horizontal_margin\"\n    android:paddingRight=\"@dimen/activity_horizontal_margin\"\n    android:paddingTop=\"@dimen/activity_vertical_margin\"\n    skin:enable=\"true\"\n    tools:context=\"org.qcode.skintestdemo.org.qcode.demo.ui.ViewPagerAndListViewActivity\">\n\n    <ScrollView\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"0dp\"\n        android:layout_weight=\"1\">\n\n        <LinearLayout\n            android:id=\"@+id/dynamic_container\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:orientation=\"vertical\"\n            skin:enable=\"true\">\n\n            <org.qcode.demo.ui.customattr.CustomTextView\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"50dp\"\n                android:gravity=\"center\"\n                android:text=\"自定义的文字颜色和背景1\"\n                app:defBackground=\"@color/color_background\"\n                app:defTextColor=\"@color/color_text\"\n                skin:enable=\"true\" />\n\n            <org.qcode.demo.ui.customattr.CustomTextView\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"50dp\"\n                android:gravity=\"center\"\n                android:text=\"自定义的文字颜色和背景2\"\n                app:defBackground=\"@color/color_background\"\n                app:defTextColor=\"@color/color_text\"\n                skin:enable=\"true\" />\n        </LinearLayout>\n    </ScrollView>\n</LinearLayout>\n"
  },
  {
    "path": "app/src/main/res/layout/activity_dynamic_add_view.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:skin=\"http://schemas.android.com/android/skin\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:background=\"@color/color_background\"\n    android:orientation=\"vertical\"\n    android:paddingBottom=\"@dimen/activity_vertical_margin\"\n    android:paddingLeft=\"@dimen/activity_horizontal_margin\"\n    android:paddingRight=\"@dimen/activity_horizontal_margin\"\n    android:paddingTop=\"@dimen/activity_vertical_margin\"\n    skin:enable=\"true\"\n    tools:context=\"org.qcode.skintestdemo.org.qcode.demo.ui.ViewPagerAndListViewActivity\">\n\n    <ScrollView\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"0dp\"\n        android:layout_weight=\"1\">\n\n        <LinearLayout\n            android:id=\"@+id/dynamic_container\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:orientation=\"vertical\"\n            skin:enable=\"true\" />\n    </ScrollView>\n</LinearLayout>\n"
  },
  {
    "path": "app/src/main/res/layout/activity_grid_view.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:skin=\"http://schemas.android.com/android/skin\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:background=\"@color/color_background\"\n    android:orientation=\"vertical\"\n    android:paddingBottom=\"@dimen/activity_vertical_margin\"\n    android:paddingLeft=\"@dimen/activity_horizontal_margin\"\n    android:paddingRight=\"@dimen/activity_horizontal_margin\"\n    android:paddingTop=\"@dimen/activity_vertical_margin\"\n    skin:enable=\"true\"\n    tools:context=\"org.qcode.skintestdemo.org.qcode.demo.ui.ViewPagerAndListViewActivity\">\n\n    <GridView\n        android:id=\"@+id/grid_view\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"0dp\"\n        android:layout_weight=\"1\"\n        android:numColumns=\"4\"\n        skin:enable=\"true\" />\n</LinearLayout>\n"
  },
  {
    "path": "app/src/main/res/layout/activity_main.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<ScrollView xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:skin=\"http://schemas.android.com/android/skin\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\">\n\n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:orientation=\"vertical\"\n        android:padding=\"15dp\">\n\n        <Button\n            android:id=\"@+id/btnViewPagerAndListView\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:background=\"@drawable/btn_bg\"\n            android:onClick=\"onClick\"\n            android:text=\"ViewPager嵌套ListView示例\"\n            android:textColor=\"@color/color_text\"\n            android:textSize=\"16sp\"\n            skin:enable=\"true\" />\n\n        <Button\n            android:id=\"@+id/btnRecyclerView\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginTop=\"5dp\"\n            android:background=\"@drawable/btn_bg\"\n            android:onClick=\"onClick\"\n            android:text=\"RecyclerView示例\"\n            android:textColor=\"@color/color_text\"\n            android:textSize=\"16sp\"\n            skin:enable=\"true\" />\n\n        <Button\n            android:id=\"@+id/btnGridView\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginTop=\"5dp\"\n            android:background=\"@drawable/btn_bg\"\n            android:onClick=\"onClick\"\n            android:text=\"GridView示例\"\n            android:textColor=\"@color/color_text\"\n            android:textSize=\"16sp\"\n            skin:enable=\"true\" />\n\n        <Button\n            android:id=\"@+id/btnDynamicAddView\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginTop=\"5dp\"\n            android:background=\"@drawable/btn_bg\"\n            android:onClick=\"onClick\"\n            android:text=\"动态添加View示例\"\n            android:textColor=\"@color/color_text\"\n            android:textSize=\"16sp\"\n            skin:enable=\"true\" />\n\n        <Button\n            android:id=\"@+id/btnCustomView\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginTop=\"5dp\"\n            android:background=\"@drawable/btn_bg\"\n            android:onClick=\"onClick\"\n            android:text=\"自定义View属性示例\"\n            android:textColor=\"@color/color_text\"\n            android:textSize=\"16sp\"\n            skin:enable=\"true\" />\n\n        <Button\n            android:id=\"@+id/btnOtherScene\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginTop=\"5dp\"\n            android:background=\"@drawable/btn_bg\"\n            android:onClick=\"onClick\"\n            android:text=\"其他场景示例\"\n            android:textColor=\"@color/color_text\"\n            android:textSize=\"16sp\"\n            skin:enable=\"true\" />\n\n    </LinearLayout>\n</ScrollView>"
  },
  {
    "path": "app/src/main/res/layout/activity_other_scene.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:skin=\"http://schemas.android.com/android/skin\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:orientation=\"vertical\"\n    android:background=\"@color/color_background\"\n    skin:enable=\"true\"\n    android:gravity=\"center_horizontal\"\n    android:padding=\"15dp\">\n\n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:orientation=\"horizontal\"\n        android:gravity=\"center_vertical\">\n\n        <org.qcode.qskinloader.view.ShadowImageView\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            skin:enable=\"true\"\n            skin:drawShadow=\"@color/night_shadow_color\"\n            android:src=\"@mipmap/ic_launcher\"/>\n\n        <TextView\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:text=\"ImageView加蒙层\"\n            android:textColor=\"@color/color_text\"\n            android:textSize=\"16sp\"\n            skin:enable=\"true\" />\n    </LinearLayout>\n\n    <Button\n        android:id=\"@+id/btnPopWindow\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginTop=\"5dp\"\n        android:background=\"@drawable/btn_bg\"\n        android:onClick=\"onClick\"\n        android:text=\"PopWindow示例\"\n        android:textColor=\"@color/color_text\"\n        android:textSize=\"16sp\"\n        skin:enable=\"true\" />\n\n    <Button\n        android:id=\"@+id/btnDialog\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginTop=\"5dp\"\n        android:background=\"@drawable/btn_bg\"\n        android:onClick=\"onClick\"\n        android:text=\"Dialog示例\"\n        android:textColor=\"@color/color_text\"\n        android:textSize=\"16sp\"\n        skin:enable=\"true\" />\n\n    <Button\n        android:id=\"@+id/btnFloatWindow\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginTop=\"5dp\"\n        android:background=\"@drawable/btn_bg\"\n        android:onClick=\"onClick\"\n        android:text=\"悬浮窗示例\"\n        android:textColor=\"@color/color_text\"\n        android:textSize=\"16sp\"\n        skin:enable=\"true\" />\n\n    <TextView\n        android:id=\"@+id/textviewSpannableSkin\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"48dp\"\n        android:layout_marginTop=\"5dp\"\n        android:gravity=\"center\"\n        android:background=\"@drawable/btn_bg\"\n        android:text=\"DynamicAttr属性扩展\"\n        android:textColor=\"@color/color_text\"\n        android:textSize=\"16sp\"\n        skin:enable=\"true\" />\n</LinearLayout>"
  },
  {
    "path": "app/src/main/res/layout/activity_recycler_view.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:skin=\"http://schemas.android.com/android/skin\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:background=\"@color/color_background\"\n    android:orientation=\"vertical\"\n    android:paddingBottom=\"@dimen/activity_vertical_margin\"\n    android:paddingLeft=\"@dimen/activity_horizontal_margin\"\n    android:paddingRight=\"@dimen/activity_horizontal_margin\"\n    android:paddingTop=\"@dimen/activity_vertical_margin\"\n    skin:enable=\"true\">\n\n    <android.support.v7.widget.RecyclerView\n        android:id=\"@+id/recycler_view\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\" />\n</LinearLayout>\n"
  },
  {
    "path": "app/src/main/res/layout/activity_viewpager_listview.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:skin=\"http://schemas.android.com/android/skin\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:background=\"@color/color_background\"\n    android:orientation=\"vertical\"\n    android:paddingBottom=\"@dimen/activity_vertical_margin\"\n    android:paddingLeft=\"@dimen/activity_horizontal_margin\"\n    android:paddingRight=\"@dimen/activity_horizontal_margin\"\n    android:paddingTop=\"@dimen/activity_vertical_margin\"\n    skin:enable=\"true\"\n    tools:context=\"org.qcode.skintestdemo.org.qcode.demo.ui.ViewPagerAndListViewActivity\">\n\n    <TextView\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:text=\"测试文本\"\n        android:textColor=\"@color/color_text\"\n        skin:enable=\"true\" />\n\n    <android.support.v4.view.ViewPager\n        android:id=\"@+id/home_news_viewpager\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"0dp\"\n        android:layout_weight=\"1\"\n        skin:enable=\"true\" />\n</LinearLayout>\n"
  },
  {
    "path": "app/src/main/res/layout/grid_item_view.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:skin=\"http://schemas.android.com/android/skin\"\n    android:orientation=\"vertical\" android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    skin:enable=\"true\"\n    android:gravity=\"center\"\n    android:background=\"@color/color_background\">\n\n    <ImageView\n        android:layout_width=\"50dp\"\n        android:layout_height=\"50dp\"\n        android:src=\"@mipmap/ic_launcher\"/>\n\n    <TextView\n        android:id=\"@+id/list_item_text_view\"\n        android:layout_width=\"100dp\"\n        android:layout_height=\"50dp\"\n        android:textColor=\"@color/color_text\"\n        skin:enable=\"true\"/>\n\n</LinearLayout>"
  },
  {
    "path": "app/src/main/res/layout/layout_dialog_custom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:skin=\"http://schemas.android.com/android/skin\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:orientation=\"vertical\"\n    android:background=\"@color/color_background\"\n    skin:enable=\"true\"\n    android:padding=\"15dp\">\n\n    <TextView\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:padding=\"5dp\"\n        android:background=\"@drawable/btn_bg\"\n        android:onClick=\"onClick\"\n        android:text=\"这是一个Dialog示例\"\n        android:textColor=\"@color/color_text\"\n        android:textSize=\"22sp\"\n        skin:enable=\"true\" />\n\n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:orientation=\"horizontal\">\n        <Button\n            android:id=\"@+id/btn_dialog_confirm\"\n            android:layout_width=\"0dp\"\n            android:layout_weight=\"1\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginTop=\"5dp\"\n            android:background=\"@drawable/btn_bg\"\n            android:text=\"确认\"\n            android:textColor=\"@color/color_text\"\n            android:textSize=\"16sp\"\n            skin:enable=\"true\" />\n\n        <Button\n            android:id=\"@+id/btn_dialog_cancel\"\n            android:layout_width=\"0dp\"\n            android:layout_weight=\"1\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginTop=\"5dp\"\n            android:background=\"@drawable/btn_bg\"\n            android:text=\"取消\"\n            android:textColor=\"@color/color_text\"\n            android:textSize=\"16sp\"\n            skin:enable=\"true\" />\n    </LinearLayout>\n\n</LinearLayout>"
  },
  {
    "path": "app/src/main/res/layout/layout_popwindow.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:skin=\"http://schemas.android.com/android/skin\"\n    android:orientation=\"vertical\" android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    skin:enable=\"true\"\n    android:background=\"@color/color_white\"\n    android:padding=\"5dp\">\n\n    <TextView\n        android:id=\"@+id/btnPopWindowClick\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:background=\"@drawable/btn_bg\"\n        android:onClick=\"onClick\"\n        android:text=\"这是一个PopWindow\"\n        android:textColor=\"@color/color_text\"\n        android:textSize=\"16sp\"\n        skin:enable=\"true\" />\n\n</LinearLayout>"
  },
  {
    "path": "app/src/main/res/layout/list_item_view.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:skin=\"http://schemas.android.com/android/skin\"\n    android:orientation=\"horizontal\" android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    skin:enable=\"true\"\n    android:gravity=\"center_vertical\"\n    android:background=\"@color/color_background\">\n\n    <ImageView\n        android:layout_width=\"50dp\"\n        android:layout_height=\"50dp\"\n        skin:drawShadow=\"@color/night_shadow_color\"\n        android:src=\"@mipmap/ic_launcher\"/>\n\n    <TextView\n        android:id=\"@+id/list_item_text_view\"\n        android:layout_width=\"100dp\"\n        android:layout_height=\"50dp\"\n        android:textColor=\"@color/color_text\"\n        skin:enable=\"true\"/>\n\n</LinearLayout>"
  },
  {
    "path": "app/src/main/res/values/colors.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <color name=\"gray_50\">#505050</color>\n\n    <color name=\"color_white\">#FFFFFF</color>\n\n    <color name=\"color_text\">#000000</color>\n    <color name=\"color_background\">#FFFFFF</color>\n    <color name=\"color_item_normal_bg\">@color/color_white</color>\n    <color name=\"color_item_press_bg\">#EEEEEE</color>\n    <color name=\"activity_bg_color\">#f3f4f5</color>\n\n    <color name=\"color_red\">#FF0000</color>\n\n    <color name=\"color_transprent\">#00000000</color>\n\n    <color name=\"night_shadow_color\">@color/color_transprent</color>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values/colors_night.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <color name=\"gray_50_night\">#84878c</color>\n\n    <color name=\"color_white_night\">#000000</color>\n\n    <color name=\"color_text_night\">#FFFFFF</color>\n    <color name=\"color_background_night\">#000000</color>\n    <color name=\"color_item_normal_bg_night\">#000000</color>\n    <color name=\"color_item_press_bg_night\">#333333</color>\n    <color name=\"activity_bg_color_night\">#1c1d20</color>\n\n\n    <color name=\"color_red_night\">#00FF00</color>\n\n    <color name=\"night_shadow_color_night\">#8e8e8e</color>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values/dimens.xml",
    "content": "<resources>\n    <!-- Default screen margins, per the Android Design guidelines. -->\n    <dimen name=\"activity_horizontal_margin\">16dp</dimen>\n    <dimen name=\"activity_vertical_margin\">16dp</dimen>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values/news_attr.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <declare-styleable name=\"app\">\n        <attr name=\"defTextColor\" format=\"color|reference\" />\n        <attr name=\"defBackground\" format=\"reference\" />\n    </declare-styleable>\n</resources>"
  },
  {
    "path": "app/src/main/res/values/strings.xml",
    "content": "<resources>\n    <string name=\"app_name\">NightModeDemo_QSkinLoader</string>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values/styles.xml",
    "content": "<resources>\n\n    <style name=\"ActivityTheme\">\n        <item name=\"android:windowBackground\">@color/color_white</item>\n    </style>\n\n    <style name=\"TransparentTheme\">\n        <item name=\"android:windowBackground\">@color/color_transprent</item>\n        <item name=\"android:colorBackgroundCacheHint\">@null</item>\n        <item name=\"android:windowIsTranslucent\">true</item>\n    </style>\n\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-night/colors_night.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <color name=\"gray_50\">#84878c</color>\n\n    <color name=\"color_white\">#000000</color>\n\n    <color name=\"color_text\">#FFFFFF</color>\n    <color name=\"color_background\">#000000</color>\n    <color name=\"color_item_normal_bg\">#000000</color>\n    <color name=\"color_item_press_bg\">#333333</color>\n    <color name=\"activity_bg_color\">#1c1d20</color>\n\n\n    <color name=\"color_red\">#00FF00</color>\n\n    <color name=\"night_shadow_color\">#8e8e8e</color>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-w820dp/dimens.xml",
    "content": "<resources>\n    <!-- Example customization of dimensions originally defined in res/values/dimens.xml\n         (such as screen margins) for screens with more than 820dp of available width. This\n         would include 7\" and 10\" devices in landscape (~960dp and ~1280dp respectively). -->\n    <dimen name=\"activity_horizontal_margin\">64dp</dimen>\n</resources>\n"
  },
  {
    "path": "build.gradle",
    "content": "// Top-level build file where you can add configuration options common to all sub-projects/modules.\n\nbuildscript {\n    repositories {\n        jcenter()\n    }\n    dependencies {\n        classpath 'com.android.tools.build:gradle:2.2.0'\n\n        // NOTE: Do not place your application dependencies here; they belong\n        // in the individual module build.gradle files\n    }\n}\n\nallprojects {\n    repositories {\n        jcenter()\n    }\n}\n\ntask clean(type: Delete) {\n    delete rootProject.buildDir\n}\n"
  },
  {
    "path": "gradle/wrapper/gradle-wrapper.properties",
    "content": "#Mon Dec 28 10:00:20 PST 2015\ndistributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\nzipStoreBase=GRADLE_USER_HOME\nzipStorePath=wrapper/dists\ndistributionUrl=https\\://services.gradle.org/distributions/gradle-2.14.1-all.zip\n"
  },
  {
    "path": "gradle.properties",
    "content": "## Project-wide Gradle settings.\n#\n# For more details on how to configure your build environment visit\n# http://www.gradle.org/docs/current/userguide/build_environment.html\n#\n# Specifies the JVM arguments used for the daemon process.\n# The setting is particularly useful for tweaking memory settings.\n# Default value: -Xmx1024m -XX:MaxPermSize=256m\n# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8\n#\n# When configured, Gradle will run in incubating parallel mode.\n# This option should only be used with decoupled projects. More details, visit\n# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects\n# org.gradle.parallel=true\n#Sun Oct 09 14:51:38 CST 2016\nsystemProp.http.proxyHost=127.0.0.1\norg.gradle.jvmargs=-Xmx1536m\nsystemProp.http.proxyPort=1080\n"
  },
  {
    "path": "gradlew",
    "content": "#!/usr/bin/env bash\n\n##############################################################################\n##\n##  Gradle start up script for UN*X\n##\n##############################################################################\n\n# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nDEFAULT_JVM_OPTS=\"\"\n\nAPP_NAME=\"Gradle\"\nAPP_BASE_NAME=`basename \"$0\"`\n\n# Use the maximum available, or set MAX_FD != -1 to use that value.\nMAX_FD=\"maximum\"\n\nwarn ( ) {\n    echo \"$*\"\n}\n\ndie ( ) {\n    echo\n    echo \"$*\"\n    echo\n    exit 1\n}\n\n# OS specific support (must be 'true' or 'false').\ncygwin=false\nmsys=false\ndarwin=false\ncase \"`uname`\" in\n  CYGWIN* )\n    cygwin=true\n    ;;\n  Darwin* )\n    darwin=true\n    ;;\n  MINGW* )\n    msys=true\n    ;;\nesac\n\n# Attempt to set APP_HOME\n# Resolve links: $0 may be a link\nPRG=\"$0\"\n# Need this for relative symlinks.\nwhile [ -h \"$PRG\" ] ; do\n    ls=`ls -ld \"$PRG\"`\n    link=`expr \"$ls\" : '.*-> \\(.*\\)$'`\n    if expr \"$link\" : '/.*' > /dev/null; then\n        PRG=\"$link\"\n    else\n        PRG=`dirname \"$PRG\"`\"/$link\"\n    fi\ndone\nSAVED=\"`pwd`\"\ncd \"`dirname \\\"$PRG\\\"`/\" >/dev/null\nAPP_HOME=\"`pwd -P`\"\ncd \"$SAVED\" >/dev/null\n\nCLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar\n\n# Determine the Java command to use to start the JVM.\nif [ -n \"$JAVA_HOME\" ] ; then\n    if [ -x \"$JAVA_HOME/jre/sh/java\" ] ; then\n        # IBM's JDK on AIX uses strange locations for the executables\n        JAVACMD=\"$JAVA_HOME/jre/sh/java\"\n    else\n        JAVACMD=\"$JAVA_HOME/bin/java\"\n    fi\n    if [ ! -x \"$JAVACMD\" ] ; then\n        die \"ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\n    fi\nelse\n    JAVACMD=\"java\"\n    which java >/dev/null 2>&1 || die \"ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\nfi\n\n# Increase the maximum file descriptors if we can.\nif [ \"$cygwin\" = \"false\" -a \"$darwin\" = \"false\" ] ; then\n    MAX_FD_LIMIT=`ulimit -H -n`\n    if [ $? -eq 0 ] ; then\n        if [ \"$MAX_FD\" = \"maximum\" -o \"$MAX_FD\" = \"max\" ] ; then\n            MAX_FD=\"$MAX_FD_LIMIT\"\n        fi\n        ulimit -n $MAX_FD\n        if [ $? -ne 0 ] ; then\n            warn \"Could not set maximum file descriptor limit: $MAX_FD\"\n        fi\n    else\n        warn \"Could not query maximum file descriptor limit: $MAX_FD_LIMIT\"\n    fi\nfi\n\n# For Darwin, add options to specify how the application appears in the dock\nif $darwin; then\n    GRADLE_OPTS=\"$GRADLE_OPTS \\\"-Xdock:name=$APP_NAME\\\" \\\"-Xdock:icon=$APP_HOME/media/gradle.icns\\\"\"\nfi\n\n# For Cygwin, switch paths to Windows format before running java\nif $cygwin ; then\n    APP_HOME=`cygpath --path --mixed \"$APP_HOME\"`\n    CLASSPATH=`cygpath --path --mixed \"$CLASSPATH\"`\n    JAVACMD=`cygpath --unix \"$JAVACMD\"`\n\n    # We build the pattern for arguments to be converted via cygpath\n    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`\n    SEP=\"\"\n    for dir in $ROOTDIRSRAW ; do\n        ROOTDIRS=\"$ROOTDIRS$SEP$dir\"\n        SEP=\"|\"\n    done\n    OURCYGPATTERN=\"(^($ROOTDIRS))\"\n    # Add a user-defined pattern to the cygpath arguments\n    if [ \"$GRADLE_CYGPATTERN\" != \"\" ] ; then\n        OURCYGPATTERN=\"$OURCYGPATTERN|($GRADLE_CYGPATTERN)\"\n    fi\n    # Now convert the arguments - kludge to limit ourselves to /bin/sh\n    i=0\n    for arg in \"$@\" ; do\n        CHECK=`echo \"$arg\"|egrep -c \"$OURCYGPATTERN\" -`\n        CHECK2=`echo \"$arg\"|egrep -c \"^-\"`                                 ### Determine if an option\n\n        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition\n            eval `echo args$i`=`cygpath --path --ignore --mixed \"$arg\"`\n        else\n            eval `echo args$i`=\"\\\"$arg\\\"\"\n        fi\n        i=$((i+1))\n    done\n    case $i in\n        (0) set -- ;;\n        (1) set -- \"$args0\" ;;\n        (2) set -- \"$args0\" \"$args1\" ;;\n        (3) set -- \"$args0\" \"$args1\" \"$args2\" ;;\n        (4) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" ;;\n        (5) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" ;;\n        (6) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" ;;\n        (7) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" ;;\n        (8) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" \"$args7\" ;;\n        (9) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" \"$args7\" \"$args8\" ;;\n    esac\nfi\n\n# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules\nfunction splitJvmOpts() {\n    JVM_OPTS=(\"$@\")\n}\neval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS\nJVM_OPTS[${#JVM_OPTS[*]}]=\"-Dorg.gradle.appname=$APP_BASE_NAME\"\n\nexec \"$JAVACMD\" \"${JVM_OPTS[@]}\" -classpath \"$CLASSPATH\" org.gradle.wrapper.GradleWrapperMain \"$@\"\n"
  },
  {
    "path": "gradlew.bat",
    "content": "@if \"%DEBUG%\" == \"\" @echo off\n@rem ##########################################################################\n@rem\n@rem  Gradle startup script for Windows\n@rem\n@rem ##########################################################################\n\n@rem Set local scope for the variables with windows NT shell\nif \"%OS%\"==\"Windows_NT\" setlocal\n\n@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nset DEFAULT_JVM_OPTS=\n\nset DIRNAME=%~dp0\nif \"%DIRNAME%\" == \"\" set DIRNAME=.\nset APP_BASE_NAME=%~n0\nset APP_HOME=%DIRNAME%\n\n@rem Find java.exe\nif defined JAVA_HOME goto findJavaFromJavaHome\n\nset JAVA_EXE=java.exe\n%JAVA_EXE% -version >NUL 2>&1\nif \"%ERRORLEVEL%\" == \"0\" goto init\n\necho.\necho ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\necho.\necho Please set the JAVA_HOME variable in your environment to match the\necho location of your Java installation.\n\ngoto fail\n\n:findJavaFromJavaHome\nset JAVA_HOME=%JAVA_HOME:\"=%\nset JAVA_EXE=%JAVA_HOME%/bin/java.exe\n\nif exist \"%JAVA_EXE%\" goto init\n\necho.\necho ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%\necho.\necho Please set the JAVA_HOME variable in your environment to match the\necho location of your Java installation.\n\ngoto fail\n\n:init\n@rem Get command-line arguments, handling Windowz variants\n\nif not \"%OS%\" == \"Windows_NT\" goto win9xME_args\nif \"%@eval[2+2]\" == \"4\" goto 4NT_args\n\n:win9xME_args\n@rem Slurp the command line arguments.\nset CMD_LINE_ARGS=\nset _SKIP=2\n\n:win9xME_args_slurp\nif \"x%~1\" == \"x\" goto execute\n\nset CMD_LINE_ARGS=%*\ngoto execute\n\n:4NT_args\n@rem Get arguments from the 4NT Shell from JP Software\nset CMD_LINE_ARGS=%$\n\n:execute\n@rem Setup the command line\n\nset CLASSPATH=%APP_HOME%\\gradle\\wrapper\\gradle-wrapper.jar\n\n@rem Execute Gradle\n\"%JAVA_EXE%\" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% \"-Dorg.gradle.appname=%APP_BASE_NAME%\" -classpath \"%CLASSPATH%\" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%\n\n:end\n@rem End local scope for the variables with windows NT shell\nif \"%ERRORLEVEL%\"==\"0\" goto mainEnd\n\n:fail\nrem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of\nrem the _cmd.exe /c_ return code!\nif  not \"\" == \"%GRADLE_EXIT_CONSOLE%\" exit 1\nexit /b 1\n\n:mainEnd\nif \"%OS%\"==\"Windows_NT\" endlocal\n\n:omega\n"
  },
  {
    "path": "settings.gradle",
    "content": "include ':app', ':QSkinLoaderlib'\n"
  }
]