Showing preview only (300K chars total). Download the full file or copy to clipboard to get everything.
Repository: qqliu10u/QSkinLoader
Branch: master
Commit: 983ba3cc32aa
Files: 154
Total size: 242.6 KB
Directory structure:
gitextract_gfdlk8qj/
├── .gitignore
├── .idea/
│ ├── compiler.xml
│ ├── copyright/
│ │ └── profiles_settings.xml
│ ├── encodings.xml
│ ├── gradle.xml
│ ├── misc.xml
│ ├── modules.xml
│ └── runConfigurations.xml
├── QSkinLoaderlib/
│ ├── .gitignore
│ ├── build.gradle
│ ├── proguard-rules.pro
│ └── src/
│ └── main/
│ ├── AndroidManifest.xml
│ ├── java/
│ │ └── org/
│ │ └── qcode/
│ │ └── qskinloader/
│ │ ├── IActivitySkinEventHandler.java
│ │ ├── ILoadSkinListener.java
│ │ ├── IResourceLoader.java
│ │ ├── IResourceManager.java
│ │ ├── ISkinActivity.java
│ │ ├── ISkinAttrHandler.java
│ │ ├── ISkinAttributeParser.java
│ │ ├── ISkinManager.java
│ │ ├── ISkinViewHelper.java
│ │ ├── IViewCreateListener.java
│ │ ├── IWindowViewManager.java
│ │ ├── SkinManager.java
│ │ ├── attrhandler/
│ │ │ ├── BackgroundAttrHandler.java
│ │ │ ├── DividerAttrHandler.java
│ │ │ ├── DrawableLeftAttrHandler.java
│ │ │ ├── ListSelectorAttrHandler.java
│ │ │ ├── RecyclerViewClearSubAttrHandler.java
│ │ │ ├── ShadowAttrHandler.java
│ │ │ ├── SkinAttrFactory.java
│ │ │ ├── SkinAttrUtils.java
│ │ │ ├── SrcAttrHandler.java
│ │ │ ├── TextColorAttrHandler.java
│ │ │ └── TextColorHintAttrHandler.java
│ │ ├── base/
│ │ │ ├── observable/
│ │ │ │ ├── INotifyUpdate.java
│ │ │ │ ├── IObservable.java
│ │ │ │ └── Observable.java
│ │ │ └── utils/
│ │ │ ├── CollectionUtils.java
│ │ │ ├── HashMapCache.java
│ │ │ ├── Logging.java
│ │ │ ├── ReflectUtils.java
│ │ │ ├── StringUtils.java
│ │ │ └── WeakReferenceHelper.java
│ │ ├── entity/
│ │ │ ├── DynamicAttr.java
│ │ │ ├── SkinAttr.java
│ │ │ ├── SkinAttrName.java
│ │ │ ├── SkinAttrSet.java
│ │ │ └── SkinConstant.java
│ │ ├── impl/
│ │ │ ├── ActivitySkinEventHandlerImpl.java
│ │ │ ├── SkinAttributeParser.java
│ │ │ ├── SkinInflaterFactoryImpl.java
│ │ │ ├── SkinManagerImpl.java
│ │ │ ├── SkinViewHelperImpl.java
│ │ │ ├── ViewSkinTagHelper.java
│ │ │ └── WindowViewManager.java
│ │ ├── resourceloader/
│ │ │ ├── ILoadResourceCallback.java
│ │ │ ├── ResourceManager.java
│ │ │ └── impl/
│ │ │ ├── APKResourceLoader.java
│ │ │ ├── APKResourceManager.java
│ │ │ ├── ConfigChangeResourceLoader.java
│ │ │ ├── ConfigChangeResourceManager.java
│ │ │ ├── SuffixResourceLoader.java
│ │ │ └── SuffixResourceManager.java
│ │ └── view/
│ │ └── ShadowImageView.java
│ └── res/
│ └── values/
│ ├── skin_attrs.xml
│ ├── skin_ids.xml
│ └── strings.xml
├── README.md
├── SkinProject/
│ ├── .gitignore
│ ├── app/
│ │ ├── .gitignore
│ │ ├── build.gradle
│ │ ├── proguard-rules.pro
│ │ └── src/
│ │ └── main/
│ │ ├── AndroidManifest.xml
│ │ ├── java/
│ │ │ └── org/
│ │ │ └── qcode/
│ │ │ └── skinproject/
│ │ │ └── MainActivity.java
│ │ └── res/
│ │ ├── drawable/
│ │ │ ├── btn_bg.xml
│ │ │ ├── drawable_float_view.xml
│ │ │ └── news_item_selector.xml
│ │ └── values/
│ │ └── colors.xml
│ ├── build.gradle
│ ├── gradle.properties
│ ├── gradlew
│ ├── gradlew.bat
│ └── settings.gradle
├── app/
│ ├── .gitignore
│ ├── build.gradle
│ ├── proguard-rules.pro
│ └── src/
│ └── main/
│ ├── AndroidManifest.xml
│ ├── assets/
│ │ └── nightMode.skin
│ ├── java/
│ │ └── org/
│ │ └── qcode/
│ │ └── demo/
│ │ ├── BaseActivity.java
│ │ ├── MainActivity.java
│ │ ├── SkinChangeSwitchView.java
│ │ ├── SkinDemoApp.java
│ │ ├── base/
│ │ │ └── Settings.java
│ │ ├── skin/
│ │ │ ├── SkinChangeHelper.java
│ │ │ ├── SkinConfigHelper.java
│ │ │ ├── SkinConstant.java
│ │ │ └── SkinUtils.java
│ │ ├── ui/
│ │ │ ├── customattr/
│ │ │ │ ├── CustomAttrViewActivity.java
│ │ │ │ ├── CustomTextView.java
│ │ │ │ ├── DefBackgroundAttrHandler.java
│ │ │ │ └── DefTextColorAttrHandler.java
│ │ │ ├── dynamicaddview/
│ │ │ │ └── DynamicAddViewActivity.java
│ │ │ ├── gridview/
│ │ │ │ └── GridViewActivity.java
│ │ │ ├── otherscene/
│ │ │ │ ├── CustomDialog.java
│ │ │ │ ├── FloatView.java
│ │ │ │ ├── OtherSceneActivity.java
│ │ │ │ ├── SpannableSkinAttr.java
│ │ │ │ └── SpannableSkinAttrHandler.java
│ │ │ ├── recyclerview/
│ │ │ │ ├── DataRecyclerViewAdapter.java
│ │ │ │ └── RecyclerViewActivity.java
│ │ │ └── viewpageandlistview/
│ │ │ ├── DataListAdapter.java
│ │ │ ├── NewsPageAdapter.java
│ │ │ ├── RecyclablePageAdapter.java
│ │ │ └── ViewPagerAndListViewActivity.java
│ │ └── utils/
│ │ ├── FileUtils.java
│ │ ├── UITaskRunner.java
│ │ └── UIUtil.java
│ └── res/
│ ├── drawable/
│ │ ├── btn_bg.xml
│ │ ├── btn_bg_night.xml
│ │ ├── drawable_float_view.xml
│ │ ├── drawable_float_view_night.xml
│ │ ├── news_item_selector.xml
│ │ └── news_item_selector_night.xml
│ ├── drawable-night/
│ │ ├── btn_bg.xml
│ │ ├── drawable_float_view.xml
│ │ └── news_item_selector.xml
│ ├── layout/
│ │ ├── activity_base_activity.xml
│ │ ├── activity_custom_attr_test.xml
│ │ ├── activity_dynamic_add_view.xml
│ │ ├── activity_grid_view.xml
│ │ ├── activity_main.xml
│ │ ├── activity_other_scene.xml
│ │ ├── activity_recycler_view.xml
│ │ ├── activity_viewpager_listview.xml
│ │ ├── grid_item_view.xml
│ │ ├── layout_dialog_custom.xml
│ │ ├── layout_popwindow.xml
│ │ └── list_item_view.xml
│ ├── values/
│ │ ├── colors.xml
│ │ ├── colors_night.xml
│ │ ├── dimens.xml
│ │ ├── news_attr.xml
│ │ ├── strings.xml
│ │ └── styles.xml
│ ├── values-night/
│ │ └── colors_night.xml
│ └── values-w820dp/
│ └── dimens.xml
├── build.gradle
├── gradle/
│ └── wrapper/
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradle.properties
├── gradlew
├── gradlew.bat
└── settings.gradle
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitignore
================================================
*.iml
.gradle
/local.properties
/.idea/workspace.xml
/.idea/libraries
.DS_Store
/build
/captures
.externalNativeBuild
================================================
FILE: .idea/compiler.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<resourceExtensions />
<wildcardResourcePatterns>
<entry name="!?*.java" />
<entry name="!?*.form" />
<entry name="!?*.class" />
<entry name="!?*.groovy" />
<entry name="!?*.scala" />
<entry name="!?*.flex" />
<entry name="!?*.kt" />
<entry name="!?*.clj" />
<entry name="!?*.aj" />
</wildcardResourcePatterns>
<annotationProcessing>
<profile default="true" name="Default" enabled="false">
<processorPath useClasspath="true" />
</profile>
</annotationProcessing>
<bytecodeTargetLevel target="1.6" />
</component>
</project>
================================================
FILE: .idea/copyright/profiles_settings.xml
================================================
<component name="CopyrightManager">
<settings default="" />
</component>
================================================
FILE: .idea/encodings.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Encoding">
<file url="PROJECT" charset="UTF-8" />
</component>
</project>
================================================
FILE: .idea/gradle.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="GradleSettings">
<option name="linkedExternalProjectsSettings">
<GradleProjectSettings>
<option name="distributionType" value="DEFAULT_WRAPPED" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="modules">
<set>
<option value="$PROJECT_DIR$" />
<option value="$PROJECT_DIR$/QSkinLoaderlib" />
<option value="$PROJECT_DIR$/app" />
</set>
</option>
<option name="resolveModulePerSourceSet" value="false" />
</GradleProjectSettings>
</option>
</component>
</project>
================================================
FILE: .idea/misc.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectLevelVcsManager" settingsEditedManually="false">
<OptionsSetting value="true" id="Add" />
<OptionsSetting value="true" id="Remove" />
<OptionsSetting value="true" id="Checkout" />
<OptionsSetting value="true" id="Update" />
<OptionsSetting value="true" id="Status" />
<OptionsSetting value="true" id="Edit" />
<ConfirmationsSetting value="0" id="Add" />
<ConfirmationsSetting value="0" id="Remove" />
</component>
<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">
<output url="file://$PROJECT_DIR$/build/classes" />
</component>
<component name="ProjectType">
<option name="id" value="Android" />
</component>
</project>
================================================
FILE: .idea/modules.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/QSkinLoader.iml" filepath="$PROJECT_DIR$/QSkinLoader.iml" />
<module fileurl="file://$PROJECT_DIR$/QSkinLoaderlib/QSkinLoaderlib.iml" filepath="$PROJECT_DIR$/QSkinLoaderlib/QSkinLoaderlib.iml" />
<module fileurl="file://$PROJECT_DIR$/app/app.iml" filepath="$PROJECT_DIR$/app/app.iml" />
</modules>
</component>
</project>
================================================
FILE: .idea/runConfigurations.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="RunConfigurationProducerService">
<option name="ignoredProducers">
<set>
<option value="org.jetbrains.plugins.gradle.execution.test.runner.AllInPackageGradleConfigurationProducer" />
<option value="org.jetbrains.plugins.gradle.execution.test.runner.TestClassGradleConfigurationProducer" />
<option value="org.jetbrains.plugins.gradle.execution.test.runner.TestMethodGradleConfigurationProducer" />
</set>
</option>
</component>
</project>
================================================
FILE: QSkinLoaderlib/.gitignore
================================================
/build
================================================
FILE: QSkinLoaderlib/build.gradle
================================================
apply plugin: 'com.android.library'
android {
compileSdkVersion 23
buildToolsVersion "23.0.1"
defaultConfig {
minSdkVersion 14
targetSdkVersion 23
}
}
dependencies {
compile fileTree(include: ['*.jar'], dir: 'libs')
compile 'com.android.support:recyclerview-v7:23.2.1'
}
================================================
FILE: QSkinLoaderlib/proguard-rules.pro
================================================
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# You can edit the include path and order by changing the proguardFiles
# directive in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# Add any project specific keep options here:
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
================================================
FILE: QSkinLoaderlib/src/main/AndroidManifest.xml
================================================
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.qcode.qskinloader">
</manifest>
================================================
FILE: QSkinLoaderlib/src/main/java/org/qcode/qskinloader/IActivitySkinEventHandler.java
================================================
package org.qcode.qskinloader;
import android.app.Activity;
/**
* 与Activity相关的皮肤框架逻辑处理抽象接口
*
* a interface defines how to implement an activity skin event handler,
* which handles activity's life events like onCreate/onResume...
*
* qqliu
* 2016/9/25.
*/
public interface IActivitySkinEventHandler {
/***
* 应该在setContentView之前调用
*
* invoked in activity's onCreate method,
* and should be invoked before setContentView.
*/
void onCreate(Activity activity);
/***
* 设置View创建的监听器;
* 可替代框架的View创建,或在框架创建了View后进行进一步处理;
*
* set an IViewCreateListener to LayoutInflater factory,
* to delegate view-creating or do further work after view created.
* @param viewCreateListener
*/
void setViewCreateListener(IViewCreateListener viewCreateListener);
/***
* 在setContentView之后调用
*
* should be invoked after setContentView.
*/
void onViewCreated();
/***
* 在onStart()回调内调用
* invoked in activity's onStart()
*/
void onStart();
/***
* 在onResume()回调内调用
* invoked in activity's onResume()
*/
void onResume();
/***
* 在onWindowFocusChanged()回调内调用
*
* invoked in activity's onWindowFocusChanged()
*/
void onWindowFocusChanged(boolean hasFocus);
/***
* 在onPause()回调内调用
*
* invoked in activity's onPause()
*/
void onPause();
/***
* 在onStop()回调内调用
* invoked in activity's onStop()
*/
void onStop();
/***
* 在onDestroy()回调内调用
* invoked in activity's onDestroy()
*/
void onDestroy();
/***
* 告知当前界面是否在换肤事件发生时立刻刷新皮肤,
* false表示Activity获取到focus时才会刷新,
* onCreate之前调用
*
* notify the handler whether the activity handles
* skin-change event immediately.
* invoked before onCreate();
*
* @param isImmediate
* @return
*/
IActivitySkinEventHandler setSwitchSkinImmediately(boolean isImmediate);
/***
* 告知当前界面是否支持换肤;
* onCreate之前调用
*
* notify the handler whether the activity
* supports skin change.
* invoked before onCreate();
*
* @param supportChange
* @return
*/
IActivitySkinEventHandler setSupportSkinChange(boolean supportChange);
/***
* 告知当前界面的Window的背景色,需要传入资源id;
* onCreate之前调用
*
* tell handler the activity's background color,
* refered as resource id.
* invoked before onCreate();
* @param resId
* @return
*/
IActivitySkinEventHandler setWindowBackgroundResource(int resId);
/***
* 设置是否需要代理View创建过程;
* true表示框架创建View,
* false表示不需要创建View,由框架外其他模块创建View,
* 此时属性解析动作应在IViewCreateListener内完成。
*
* set whether framework need delegate view-creating.
* true indicates that the framework does view-creating;
* false indicates that the framework don't handle view-creating,
* otherwise, the view is created by outside,
* and the property-parsing process should be done in interface @ref{IViewCreateListener}.
*
* @param needDelegateViewCreate
* @return
*/
IActivitySkinEventHandler setNeedDelegateViewCreate(boolean needDelegateViewCreate);
/***
* 当皮肤发生变化时,此方法会被调用,来完成Activity的皮肤切换工作
*
* when skin changes, @ref{handleSkinUpdate} will be
* called to refresh the activity's skin.
*/
void handleSkinUpdate();
/***
* 获取皮肤属性解析帮助类
*
* get a skin attributes parser used when view is creating.
* @return
*/
ISkinAttributeParser getSkinAttributeParser();
}
================================================
FILE: QSkinLoaderlib/src/main/java/org/qcode/qskinloader/ILoadSkinListener.java
================================================
package org.qcode.qskinloader;
/**
* 加载皮肤过程的事件回调
*
* a interface defines the skin loading progress.
*
* qqliu
* 2016/9/24.
*/
public interface ILoadSkinListener {
/***
* 加载皮肤开始
*
* notify skin-loading begin event
*
* @param skinIdentifier
*/
void onLoadStart(String skinIdentifier);
/***
* 加载皮肤完成
*
* notify skin-loading success event
*
* @param skinIdentifier
*/
void onLoadSuccess(String skinIdentifier);
/***
* 加载皮肤失败
*
* notify skin-loading fail event
*
* @param skinIdentifier
*/
void onLoadFail(String skinIdentifier);
}
================================================
FILE: QSkinLoaderlib/src/main/java/org/qcode/qskinloader/IResourceLoader.java
================================================
package org.qcode.qskinloader;
import org.qcode.qskinloader.resourceloader.ILoadResourceCallback;
/**
* 皮肤资源加载器接口
*
* A interface defines the load resource behaviour.
* A resource loader loads resource and notify load behaviour by @ref{ILoadResourceCallback}
* qqliu
* 2016/9/25.
*/
public interface IResourceLoader {
/***
* 定义资源加载的行为接口,加载的皮肤以skinIdentifier标识,
* 加载结果以loadCallBack通知加载资源结果
*
* loads the skin identified by skinIdentifier,
* notifies load behaviour by loadCallBack
* @param skinIdentifier
* @param loadCallBack
*/
void loadResource(String skinIdentifier,
ILoadResourceCallback loadCallBack);
}
================================================
FILE: QSkinLoaderlib/src/main/java/org/qcode/qskinloader/IResourceManager.java
================================================
package org.qcode.qskinloader;
import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.graphics.drawable.Drawable;
/**
* 皮肤资源管理器接口
*
* the interface defines what a resource manager should do.
* qqliu
* 2016/9/25.
*/
public interface IResourceManager {
/***
* 为IResourceManager设置一个真正工作的资源管理器,IResourceManager是baseResource的封装
* set a resource manager as a base worker for the IResourceManager.
* @param skinIdentifier 皮肤的唯一标识;
* the skin identifier
* @param baseResource 真正读取资源的ResourceManager;
* the real resource manager which defines how to attach resources.
*/
void setBaseResource(
String skinIdentifier,
IResourceManager baseResource);
/***
* get skin identifier
* @return
*/
String getSkinIdentifier();
/***
* return whether current is default skin
* @return
*/
boolean isDefault();
/***
* get drawable by resource id
* @param resId
* @return
* @throws Resources.NotFoundException
*/
Drawable getDrawable(int resId) throws Resources.NotFoundException;
/***
* get drawable by resource id and name
* @param resId
* @param resName
* @return
* @throws Resources.NotFoundException
*/
Drawable getDrawable(int resId, String resName) throws Resources.NotFoundException;
/***
* get color by resource id
* @param resId
* @return
* @throws Resources.NotFoundException
*/
int getColor(int resId) throws Resources.NotFoundException;
/***
* get color by resource id and resource name
* @param resId
* @param resName
* @return
* @throws Resources.NotFoundException
*/
int getColor(int resId, String resName) throws Resources.NotFoundException;
/***
* get ColorStateList by resource id
* @param resId
* @return
* @throws Resources.NotFoundException
*/
ColorStateList getColorStateList(int resId) throws Resources.NotFoundException;
/***
* get ColorStateList by resource id and name
* @param resId
* @param resName
* @return
* @throws Resources.NotFoundException
*/
ColorStateList getColorStateList(int resId, String resName) throws Resources.NotFoundException;
/***
* get ColorStateList by resource id and name
* @param resId
* @param typeName
* @param resName
* @return
* @throws Resources.NotFoundException
*/
ColorStateList getColorStateList(int resId, String typeName, String resName) throws Resources.NotFoundException;
}
================================================
FILE: QSkinLoaderlib/src/main/java/org/qcode/qskinloader/ISkinActivity.java
================================================
package org.qcode.qskinloader;
/**
* 支持换肤的Activity应实现的接口
*
* A interface indicates that this activity supports skin change.
*
* qqliu
* 2016/9/25.
*/
public interface ISkinActivity {
/***
* 是否需要立刻刷新皮肤;默认不立刻换肤
* tells whether refresh skin immediately, if return false, the activity will
* be refreshed after focus obtained.
*
* @return
*/
boolean isSwitchSkinImmediately();
/***
* 确定是否支持换肤
*tells whether the activity support skin change,
* if return false, the activity will not refresh when skin change.
* NOTICE: SkinManager.getInstance().applySkin() will ignore the setting
*
* @return
*/
boolean isSupportSkinChange();
/***
* 刷新皮肤;
* 此处刷新的是皮肤框架管理之外的界面
*
* when skin changes, this method will be called,
* to notify activity doing something beyond the framework's ability.
*/
void handleSkinChange();
}
================================================
FILE: QSkinLoaderlib/src/main/java/org/qcode/qskinloader/ISkinAttrHandler.java
================================================
package org.qcode.qskinloader;
import android.view.View;
import org.qcode.qskinloader.entity.SkinAttr;
/**
* 皮肤属性处理器的接口
*
* an interface indicates how to apply special skin attributes for a view.
*
* qqliu
* 2016/9/24.
*/
public interface ISkinAttrHandler {
/***
* 将属性应用到View上
*
* apply skin attribute to view
*
* @param view
* @param skinAttr
* @param resourceManager
*/
void apply(View view,
SkinAttr skinAttr,
IResourceManager resourceManager);
}
================================================
FILE: QSkinLoaderlib/src/main/java/org/qcode/qskinloader/ISkinAttributeParser.java
================================================
package org.qcode.qskinloader;
import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
/**
* 框架解析皮肤属性的解析帮助类
*
* the skin attribute parser defines
* how to parse attributes when view is created.
*
* qqliu
* 2016/11/8.
*/
public interface ISkinAttributeParser {
/***
* 是否支持换肤
*
* return the parse result whether the view supports skin-change
* @param name
* @param context
* @param attrs
* @return
*/
boolean isSupportSkin(String name, Context context, AttributeSet attrs);
/***
* 解析View的皮肤属性
*
* parse skin attributes from view-creating process
*
* @param view
* @param name
* @param context
* @param attrs
*/
void parseAttribute(View view, String name, Context context, AttributeSet attrs);
}
================================================
FILE: QSkinLoaderlib/src/main/java/org/qcode/qskinloader/ISkinManager.java
================================================
package org.qcode.qskinloader;
import android.content.Context;
import android.view.View;
import org.qcode.qskinloader.base.observable.IObservable;
/**
* 皮肤框架管理类接口
*
* the skin manager interface, supporting skin operations.
* qqliu
* 2016/9/24.
*/
public interface ISkinManager extends IObservable<IActivitySkinEventHandler> {
/***
* 初始化皮肤框架
* initing the skin framework
* @param context
*/
void init(Context context);
/***
* 恢复到默认皮肤
*
* restore to default skin
* @param defaultSkinIdentifier: default skin identifier, a skin identifier
* identifies a special skin.
* @param loadListener
*/
void restoreDefault(String defaultSkinIdentifier, ILoadSkinListener loadListener);
/***
* 从指定位置加载一个APK皮肤包;APK皮肤包是另一个未安装的APK应用(只包含资源).
* 通过APK皮肤包可以支持动态下载换肤等功能;
*
* load an apk resources package from file(indicated by skinPath);
* an apk resources is an apk application, which only contains resources.
* APK supports can be applied when dynamically downloading the skin resouces.
*
* @param skinPath skinPath is a file path,
* and is also used as the skin identifier.
* @param loadListener the load result listener
*/
void loadAPKSkin(String skinPath, ILoadSkinListener loadListener);
/***
* 加载指定的皮肤包,皮肤包以skinIdentifier标识,
* 依靠resourceLoader加载,并通过loadListener告知皮肤切换结果。
* 由外部指定皮肤加载方式,目前支持APK加载(APKResourceLoader)、后缀方式加载(SuffixResourceLoader)等。
*
* load skin for views, the skin is identified by skinIdentifier,
* loaded by resourceLoader(currently supports APKResourceLoader/SuffixResourceLoader),
* and the load result is notified by loadListener.
* @param skinIdentifier the skin identifier
* @param resourceLoader the resource loader(currently supports APKResourceLoader/SuffixResourceLoader)
* @param loadListener the skin load result listener
*/
void loadSkin(String skinIdentifier,
IResourceLoader resourceLoader,
ILoadSkinListener loadListener);
/***
* 对View应用当前的皮肤设置,applyChild 表示对View的子元素设置皮肤
*
* apply current skin for the view
*
* @param view
* @param applyChild: true indicates apply skin for view's children
*/
void applySkin(View view, boolean applyChild);
/***
* 注册指定属性的处理器,可以通过此方法覆盖默认的属性处理器,也可以定义自定义属性的属性处理器
*
* register a skin attributes handler for attribute(named as attrName)
*
* @param attrName : the attribute name
* @param skinAttrHandler : the attribute handler
*/
void registerSkinAttrHandler(String attrName, ISkinAttrHandler skinAttrHandler);
/***
* 移除指定属性的处理器
*
* remove a skin attribute handler for a attribute
* @param attrName
*/
void unregisterSkinAttrHandler(String attrName);
/***
* 设置一个IResourceManager对象,
* 可用来替换默认的ResourceManager实现,
* 在属性处理器内收到替换的ResourceManager实现。
*
* set a IResourceManager object to framework to replace the default ResourceManager.
* @param resourceManager
*/
void setResourceManager(IResourceManager resourceManager);
/***
* 获取框架内的资源管理器对象
*
* return the resource manager object used in framework.
* @return
*/
IResourceManager getResourceManager();
}
================================================
FILE: QSkinLoaderlib/src/main/java/org/qcode/qskinloader/ISkinViewHelper.java
================================================
package org.qcode.qskinloader;
import org.qcode.qskinloader.entity.DynamicAttr;
import org.qcode.qskinloader.entity.SkinAttr;
import java.util.List;
/**
* 皮肤框架中View相关皮肤属性的管理器接口抽象
*
* The abstract interface for registering skin attributes dynamically.
* qqliu
* 2016/10/8.
*/
public interface ISkinViewHelper {
/***
* 给指定View注册一个属性名称为attrName,属性值为resId的皮肤属性;
* 此View之前注册的属性会被覆盖;
*
* apply a skin attribute(named as attrName, value is resId) to View,
* the skin attributes previously registered is removed.
* @param attrName
* @param resId
* @return
*/
ISkinViewHelper setViewAttrs(String attrName, int resId);
/***
* 给指定View注册多个皮肤属性;
* 此View之前注册的属性会被覆盖;
*
* apply skin attributes(
* named as attrName, value is resId, indicated in DynamicAttr) to View,
* the skin attributes previously registered is removed.
*
* @param dynamicAttrs
* @return
*/
ISkinViewHelper setViewAttrs(DynamicAttr... dynamicAttrs);
/***
* 给指定View注册多个皮肤属性;
* 此View之前注册的属性会被覆盖;
*
* apply skin attributes(
* named as attrName, value is resId, indicated in SkinAttr) to View,
* the skin attributes previously registered is removed.
*
* @param skinAttrs
* @return
*/
ISkinViewHelper setViewAttrs(SkinAttr... skinAttrs);
/***
* 给指定View注册多个皮肤属性;
* 此View之前注册的属性会被覆盖;
*
* apply skin attributes(
* named as attrName, value is resId, indicated in DynamicAttr) to View,
* the skin attributes previously registered is removed.
*
* @param dynamicAttrs
* @return
*/
ISkinViewHelper setViewAttrs(List<DynamicAttr> dynamicAttrs);
/***
* 给指定View添加一个属性名称为attrName,属性值为resId的皮肤属性;
* 此View之前注册的属性不会被覆盖;
*
* add a skin attribute(named as attrName, value is resId) to View,
* the skin attributes previously registered is maintained.
* @param attrName
* @param resId
* @return
*/
ISkinViewHelper addViewAttrs(String attrName, int resId);
/***
* 给指定View添加多个皮肤属性;
* 此View之前注册的属性不会被覆盖;
*
* add skin attributes(
* named as attrName, value is resId, indicated in DynamicAttr) to View,
* the skin attributes previously registered is maintained.
*
* @param dynamicAttrs
* @return
*/
ISkinViewHelper addViewAttrs(DynamicAttr... dynamicAttrs);
/***
* 给指定View添加多个皮肤属性;
* 此View之前注册的属性不会被覆盖;
*
* add skin attributes(
* named as attrName, value is resId, indicated in SkinAttr) to View,
* the skin attributes previously registered is maintained.
*
* @param skinAttrs
* @return
*/
ISkinViewHelper addViewAttrs(SkinAttr... skinAttrs);
/***
* 给指定View添加多个皮肤属性;
* 此View之前注册的属性不会被覆盖;
*
* add skin attributes(
* named as attrName, value is resId, indicated in DynamicAttr) to View,
* the skin attributes previously registered is maintained.
*
* @param dynamicAttrs
* @return
*/
ISkinViewHelper addViewAttrs(List<DynamicAttr> dynamicAttrs);
/***
* 移除View内注册的皮肤属性
*
* remove all skin attributes for a view
* @param clearChild true表示同时移除View的子元素的皮肤属性;
* false只移除View的皮肤属性;
* true indicates also removing skin attributes for the view's children,
* false means only remove skin attributes for the view itself;
* @return
*/
ISkinViewHelper cleanAttrs(boolean clearChild);
/***
* 对View应用当前的皮肤设置;
*
* apply current skin for the view
* @param applyChild true表示对View子元素也应用皮肤;
* false表示只对View应用皮肤;
* true indicates apply skin for the view's children,
* false otherwise;
*/
void applySkin(boolean applyChild);
}
================================================
FILE: QSkinLoaderlib/src/main/java/org/qcode/qskinloader/IViewCreateListener.java
================================================
package org.qcode.qskinloader;
import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
/**
* 创建View的过程中的回调
* qqliu
* 2016/10/17.
*/
public interface IViewCreateListener {
/***
* 创建View之前的回调; invoked before view create,
* should be used to create view outside the framework if needed
* @param name
* @param context
* @param attrs
* @return
*/
View beforeCreate(String name, Context context, AttributeSet attrs);
/***
* 创建View之后的回调; invoked after view create,
* should be used to parse view attributes outside the framework if needed
* @param view
* @param name
* @param context
* @param attrs
*/
void afterCreated(View view, String name, Context context, AttributeSet attrs);
}
================================================
FILE: QSkinLoaderlib/src/main/java/org/qcode/qskinloader/IWindowViewManager.java
================================================
package org.qcode.qskinloader;
import android.view.View;
import java.util.List;
/**
* 皮肤框架对直接加载在WindowManager上的View的管理器;
* 包含:悬浮窗、popWindow、Dialog等持有的View;
* 一般是将Activity的View树以外的View加入框架内管理;
* 建议成对调用{@ref{addWindowView}和{@ref{removeWindowView};
*
* The manager of Views
* (added to WindowManager, such as PopWindow/Dialog/Float View...);
* Basically, the views not add to Activity's View tree
* should be added to IWindowViewManager for skin changing purpose.
* addWindowView should be used with removeWindowView in pairs.
* qqliu
* 2016/10/8.
*/
public interface IWindowViewManager {
/***
* 在框架内增加View的引用,刷新皮肤时会刷新此View及其所有子元素;
* ,应与{@ref{removeWindowView}成对使用
*
* add a view in framework, so that we can refresh
* the view(and its children)'s skin immediately.
* should be used with {@ref{removeWindowView} in pairs.
* @param view
* @return
*/
IWindowViewManager addWindowView(View view);
/***
* 从框架内移除View的引用;
* 应与{@ref{addWindowView}成对使用;
*
* remove a view from framework, see {@ref{addWindowView}
* @param view
* @return
*/
IWindowViewManager removeWindowView(View view);
/***
* 清空框架内持有的所有View的引用;
* clear all views maintained in framework
* @return
*/
IWindowViewManager clear();
/***
* 对框架内持有的所有View刷新皮肤;
*
* refresh skin for views maintained in framework
* @param applyChild 表示刷新是否同时刷新View的子元素,一般传入true;
* true means we also refresh the views' children,
* most time true is needed.
*/
void applySkinForViews(boolean applyChild);
/***
* 获取注册到框架内维护的所有View
*
* return all the views maintained in the framework.
* @return
*/
List<View> getWindowViewList();
}
================================================
FILE: QSkinLoaderlib/src/main/java/org/qcode/qskinloader/SkinManager.java
================================================
package org.qcode.qskinloader;
import android.view.View;
import org.qcode.qskinloader.impl.ActivitySkinEventHandlerImpl;
import org.qcode.qskinloader.impl.SkinManagerImpl;
import org.qcode.qskinloader.impl.SkinViewHelperImpl;
import org.qcode.qskinloader.impl.WindowViewManager;
/**
* 皮肤框架对外接口;
*
* the base entrance class for the QSkinLoader library
* qqliu
* 2016/10/8.
*/
public class SkinManager {
/***
* 获取皮肤管理类实例;
*
* return an ISkinManager object to deal with skin manager events
* such as init/skin change/apply skin for view ...
* @return
*/
public static ISkinManager getInstance() {
return SkinManagerImpl.getInstance();
}
/***
* 获取View的皮肤属性管理类,支持链式编程,可动态操作View的皮肤属性;
*
* return an ISinViewHelper to add/remove skin attrs dynamically;
* ISkinViewHelper supports the chain programming style;
* @param view
* @return
*/
public static ISkinViewHelper with(View view) {
return new SkinViewHelperImpl(view);
}
/***
* 获取Window View的管理类;
* 框架只能自动支持刷新Activity的ContentView,
* 对于PopupWindow/对话框/悬浮窗等View,
* 只能通过此方法注册到框架内来保证换肤效果;
*
* return an IWindowViewManager to add/remove view to the framework;
* the framework only supports apply skin for Activity's
* content view(by findViewById(android.R.id.content));
* so that other views(PopupWindow/Dialog/View directly added to WindowManager)
* should be add to framework for skin changing.
* @return
*/
public static IWindowViewManager getWindowViewManager() {
return WindowViewManager.getInstance();
}
/***
* 创建一个新的Activity的皮肤事件处理器;
* IActivitySkinEventHandler用于代理完成
* Activity内各生命周期与皮肤相关的逻辑;
*
* return a new IActivitySkinEventHandler object for Activity;
* IActivitySkinEventHandler handles event for the skin framework,
* and should be notified when activity life state change(onCreate/onResume/onPause...)
* @return
*/
public static IActivitySkinEventHandler newActivitySkinEventHandler() {
return new ActivitySkinEventHandlerImpl();
}
}
================================================
FILE: QSkinLoaderlib/src/main/java/org/qcode/qskinloader/attrhandler/BackgroundAttrHandler.java
================================================
package org.qcode.qskinloader.attrhandler;
import android.graphics.drawable.Drawable;
import android.view.View;
import org.qcode.qskinloader.ISkinAttrHandler;
import org.qcode.qskinloader.entity.SkinAttr;
import org.qcode.qskinloader.entity.SkinAttrName;
import org.qcode.qskinloader.IResourceManager;
/***
* 背景属性的换肤支持(android:background)
*/
class BackgroundAttrHandler implements ISkinAttrHandler {
@Override
public void apply(View view, SkinAttr skinAttr, IResourceManager resourceManager) {
if(null == view
|| null == skinAttr
|| !(SkinAttrName.BACKGROUND.equals(skinAttr.mAttrName))) {
return;
}
Drawable drawable = SkinAttrUtils.getDrawable(
resourceManager, skinAttr.mAttrValueRefId,
skinAttr.mAttrValueTypeName, skinAttr.mAttrValueRefName);
if(null != drawable) {
view.setBackgroundDrawable(drawable);
}
}
}
================================================
FILE: QSkinLoaderlib/src/main/java/org/qcode/qskinloader/attrhandler/DividerAttrHandler.java
================================================
package org.qcode.qskinloader.attrhandler;
import android.graphics.drawable.Drawable;
import android.view.View;
import android.widget.ListView;
import org.qcode.qskinloader.ISkinAttrHandler;
import org.qcode.qskinloader.entity.SkinAttr;
import org.qcode.qskinloader.entity.SkinAttrName;
import org.qcode.qskinloader.IResourceManager;
/***
* ListView divider属性的换肤支持(android:divider)
*/
class DividerAttrHandler implements ISkinAttrHandler {
@Override
public void apply(View view, SkinAttr skinAttr, IResourceManager resourceManager) {
if (null == view
|| null == skinAttr
|| !(SkinAttrName.DIVIDER.equals(skinAttr.mAttrName))) {
return;
}
if (!(view instanceof ListView)) {
return;
}
ListView tv = (ListView) view;
Drawable drawable = SkinAttrUtils.getDrawable(
resourceManager, skinAttr.mAttrValueRefId,
skinAttr.mAttrValueTypeName, skinAttr.mAttrValueRefName);
if (null != drawable) {
tv.setDivider(drawable);
}
}
}
================================================
FILE: QSkinLoaderlib/src/main/java/org/qcode/qskinloader/attrhandler/DrawableLeftAttrHandler.java
================================================
package org.qcode.qskinloader.attrhandler;
import android.graphics.drawable.Drawable;
import android.view.View;
import android.widget.TextView;
import org.qcode.qskinloader.ISkinAttrHandler;
import org.qcode.qskinloader.entity.SkinAttr;
import org.qcode.qskinloader.entity.SkinAttrName;
import org.qcode.qskinloader.IResourceManager;
/**
* TextView的drawableLeft属性处理
* qqliu
* 2016/9/27.
*/
class DrawableLeftAttrHandler implements ISkinAttrHandler {
@Override
public void apply(View view, SkinAttr skinAttr, IResourceManager resourceManager) {
if(null == view
|| null == skinAttr
|| !(SkinAttrName.DRAWABLE_LEFT.equals(skinAttr.mAttrName))) {
return;
}
if (!(view instanceof TextView)) {
return;
}
Drawable drawable = SkinAttrUtils.getDrawable(resourceManager,
skinAttr.mAttrValueRefId,
skinAttr.mAttrValueTypeName,
skinAttr.mAttrValueRefName);
if(null != drawable) {
((TextView)view).setCompoundDrawablesWithIntrinsicBounds(
drawable, null, null, null);
}
}
}
================================================
FILE: QSkinLoaderlib/src/main/java/org/qcode/qskinloader/attrhandler/ListSelectorAttrHandler.java
================================================
package org.qcode.qskinloader.attrhandler;
/***
* ListView selector属性的换肤支持(android:listSelector)
*/
import android.graphics.drawable.Drawable;
import android.view.View;
import android.widget.AbsListView;
import org.qcode.qskinloader.ISkinAttrHandler;
import org.qcode.qskinloader.entity.SkinAttr;
import org.qcode.qskinloader.entity.SkinAttrName;
import org.qcode.qskinloader.IResourceManager;
class ListSelectorAttrHandler implements ISkinAttrHandler {
@Override
public void apply(View view, SkinAttr skinAttr, IResourceManager resourceManager) {
if (null == view
|| null == skinAttr
|| !(SkinAttrName.LIST_SELECTOR.equals(skinAttr.mAttrName))) {
return;
}
if (!(view instanceof AbsListView)) {
return;
}
Drawable drawable = SkinAttrUtils.getDrawable(
resourceManager, skinAttr.mAttrValueRefId,
skinAttr.mAttrValueTypeName, skinAttr.mAttrValueRefName);
if (null != drawable) {
((AbsListView) view).setSelector(drawable);
}
}
}
================================================
FILE: QSkinLoaderlib/src/main/java/org/qcode/qskinloader/attrhandler/RecyclerViewClearSubAttrHandler.java
================================================
package org.qcode.qskinloader.attrhandler;
import android.support.v7.widget.RecyclerView;
import android.view.View;
import org.qcode.qskinloader.IResourceManager;
import org.qcode.qskinloader.ISkinAttrHandler;
import org.qcode.qskinloader.base.utils.Logging;
import org.qcode.qskinloader.entity.SkinAttr;
import org.qcode.qskinloader.entity.SkinAttrName;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
/**
* 清除RecyclerView的缓存View池,保证不出现夜间模式与白天模式共存的问题
* qqliu
* 2016/9/27.
*/
class RecyclerViewClearSubAttrHandler implements ISkinAttrHandler {
private static final String TAG = "RecyclerViewClearSubAttr";
@Override
public void apply(View view, SkinAttr skinAttr, IResourceManager resourceManager) {
if(null == view
|| null == skinAttr
|| !(SkinAttrName.CLEAR_RECYCLER_VIEW.equals(skinAttr.mAttrName))) {
return;
}
if(!(view instanceof RecyclerView)) {
return;
}
refreshRecyclerView((RecyclerView) view);
}
private void refreshRecyclerView(RecyclerView recyclerView) {
Logging.d(TAG, "refreshRecyclerView()| clear recycler view");
Class<RecyclerView> recyclerViewClass = RecyclerView.class;
try {
Field declaredField = recyclerViewClass.getDeclaredField("mRecycler" );
declaredField.setAccessible(true);
Method declaredMethod = Class.forName(RecyclerView.Recycler. class.getName()).getDeclaredMethod("clear", (Class<?>[]) new Class[0]);
declaredMethod.setAccessible(true);
declaredMethod.invoke(declaredField.get(recyclerView), new Object[0]);
RecyclerView.RecycledViewPool recycledViewPool = recyclerView.getRecycledViewPool();
recycledViewPool.clear();
} catch (Exception ex) {
Logging.d(TAG, "refreshRecyclerView()| error happened", ex);
}
}
}
================================================
FILE: QSkinLoaderlib/src/main/java/org/qcode/qskinloader/attrhandler/ShadowAttrHandler.java
================================================
package org.qcode.qskinloader.attrhandler;
import android.graphics.Color;
import android.graphics.PorterDuff;
import android.view.View;
import android.widget.ImageView;
import org.qcode.qskinloader.ISkinAttrHandler;
import org.qcode.qskinloader.entity.SkinAttr;
import org.qcode.qskinloader.entity.SkinAttrName;
import org.qcode.qskinloader.entity.SkinConstant;
import org.qcode.qskinloader.IResourceManager;
import org.qcode.qskinloader.view.ShadowImageView;
/**
* 蒙层阴影属性,仅支持ImageView,且蒙层只能是int型颜色
* qqliu
* 2016/9/25.
*/
class ShadowAttrHandler implements ISkinAttrHandler {
@Override
public void apply(View view, SkinAttr skinAttr, IResourceManager resourceManager) {
if (null == view
|| null == skinAttr
|| !(SkinAttrName.DRAW_SHADOW.equals(skinAttr.mAttrName))) {
return;
}
if (!(view instanceof ImageView)) {
return;
}
if (resourceManager.isDefault()) {
if (view instanceof ShadowImageView) {
ShadowImageView imageView = (ShadowImageView) view;
imageView.setShadowColor(Color.WHITE);
} else {
((ImageView) view).setColorFilter(Color.WHITE, PorterDuff.Mode.MULTIPLY);
}
} else {
if (SkinConstant.RES_TYPE_NAME_COLOR.equals(skinAttr.mAttrValueTypeName)) {
int bgColor = resourceManager.getColor(
skinAttr.mAttrValueRefId, skinAttr.mAttrValueRefName);
if (view instanceof ShadowImageView) {
ShadowImageView imageView = (ShadowImageView) view;
imageView.setShadowColor(bgColor);
} else {
((ImageView) view).setColorFilter(bgColor, PorterDuff.Mode.MULTIPLY);
}
}
}
}
}
================================================
FILE: QSkinLoaderlib/src/main/java/org/qcode/qskinloader/attrhandler/SkinAttrFactory.java
================================================
package org.qcode.qskinloader.attrhandler;
import android.text.TextUtils;
import org.qcode.qskinloader.ISkinAttrHandler;
import org.qcode.qskinloader.base.utils.StringUtils;
import org.qcode.qskinloader.entity.DynamicAttr;
import org.qcode.qskinloader.entity.SkinAttr;
import org.qcode.qskinloader.entity.SkinAttrName;
import java.util.HashMap;
import java.util.Map;
/***
* 获取支持的属性处理器工厂
*/
public class SkinAttrFactory {
//存放支持的换肤属性和对应的处理器
private static Map<String, ISkinAttrHandler> mSupportAttrHandler = new HashMap<String, ISkinAttrHandler>();
//静态注册支持的属性和处理器
static {
registerSkinAttrHandler(SkinAttrName.BACKGROUND, new BackgroundAttrHandler());
registerSkinAttrHandler(SkinAttrName.SRC, new SrcAttrHandler());
registerSkinAttrHandler(SkinAttrName.TEXT_COLOR, new TextColorAttrHandler());
registerSkinAttrHandler(SkinAttrName.TEXT_COLOR_HINT, new TextColorHintAttrHandler());
registerSkinAttrHandler(SkinAttrName.LIST_SELECTOR, new ListSelectorAttrHandler());
registerSkinAttrHandler(SkinAttrName.DIVIDER, new DividerAttrHandler());
registerSkinAttrHandler(SkinAttrName.DRAWABLE_LEFT, new DrawableLeftAttrHandler());
registerSkinAttrHandler(SkinAttrName.DRAW_SHADOW, new ShadowAttrHandler());
registerSkinAttrHandler(SkinAttrName.CLEAR_RECYCLER_VIEW, new RecyclerViewClearSubAttrHandler());
}
/***
* 创建一个新的皮肤对象
* @param attrName
* @param attrValueRefId
* @param attrValueRefName
* @param typeName
* @return
*/
public static SkinAttr newSkinAttr(
String attrName, int attrValueRefId,
String attrValueRefName, String typeName) {
if (StringUtils.isEmpty(attrName)) {
return null;
}
SkinAttr skinAttr = new SkinAttr();
skinAttr.mAttrName = attrName;
skinAttr.mAttrValueRefId = attrValueRefId;
skinAttr.mAttrValueRefName = attrValueRefName;
skinAttr.mAttrValueTypeName = typeName;
return skinAttr;
}
/***
* 基于属性名称生成SkinAttr
* @param attrName
* @return
*/
public static SkinAttr newSkinAttr(String attrName) {
if (StringUtils.isEmpty(attrName)) {
return null;
}
SkinAttr skinAttr = new SkinAttr();
skinAttr.mAttrName = attrName;
return skinAttr;
}
/***
* 获取特定属性的换肤处理器
*
* @param attrName
* @return
*/
public static ISkinAttrHandler getSkinAttrHandler(String attrName) {
return mSupportAttrHandler.get(attrName);
}
/***
* 是否支持某属性换肤
*
* @param attrName
* @return
*/
public static boolean isSupportedAttr(String attrName) {
return null != getSkinAttrHandler(attrName);
}
/****
* 注册对某个属性的换肤支持
*
* @param attrName
*/
public static void registerSkinAttrHandler(String attrName, ISkinAttrHandler skinAttrHandler) {
if (TextUtils.isEmpty(attrName) || null == skinAttrHandler) {
return;
}
mSupportAttrHandler.put(attrName, skinAttrHandler);
}
/***
* 移除对某个属性的换肤支持
*
* @param attrName
*/
public static void removeSkinAttrHandler(String attrName) {
if (TextUtils.isEmpty(attrName)) {
return;
}
mSupportAttrHandler.remove(attrName);
}
}
================================================
FILE: QSkinLoaderlib/src/main/java/org/qcode/qskinloader/attrhandler/SkinAttrUtils.java
================================================
package org.qcode.qskinloader.attrhandler;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.view.View;
import org.qcode.qskinloader.IResourceManager;
import org.qcode.qskinloader.ISkinAttrHandler;
import org.qcode.qskinloader.base.utils.CollectionUtils;
import org.qcode.qskinloader.entity.SkinAttr;
import org.qcode.qskinloader.entity.SkinAttrSet;
import org.qcode.qskinloader.entity.SkinConstant;
import java.util.List;
/**
* 皮肤属性的辅助帮助类
* qqliu
* 2016/9/25.
*/
public class SkinAttrUtils {
/***
* 获取指定资源的drawable,支持的resId为int型颜色和drawable
* @param resourceManager
* @param resId
* @param resTypeName
* @param resName
* @return
*/
public static Drawable getDrawable(IResourceManager resourceManager,
int resId,
String resTypeName,
String resName) {
if (SkinConstant.RES_TYPE_NAME_COLOR.equals(resTypeName)) {
int bgColor = resourceManager.getColor(
resId, resName);
return new ColorDrawable(bgColor);
} else if (SkinConstant.RES_TYPE_NAME_DRAWABLE.equals(resTypeName)) {
Drawable drawable = resourceManager.getDrawable(
resId, resName);
return drawable;
} else if (SkinConstant.RES_TYPE_NAME_MIPMAP.equals(resTypeName)) {
Drawable drawable = resourceManager.getDrawable(
resId, resName);
return drawable;
}
return null;
}
/***
* 对View应用指定的属性集合
* @param view
* @param skinAttrSet
* @param resourceManager
*/
public static void applySkinAttrs(View view, SkinAttrSet skinAttrSet, IResourceManager resourceManager) {
if(null == view || null == skinAttrSet) {
return;
}
List<SkinAttr> attrArrayList = skinAttrSet.getAttrList();
if (CollectionUtils.isEmpty(attrArrayList)) {
return;
}
for (SkinAttr attr : attrArrayList) {
ISkinAttrHandler attrHandler = SkinAttrFactory.getSkinAttrHandler(attr.mAttrName);
if(null != attrHandler) {
attrHandler.apply(view, attr, resourceManager);
}
}
}
}
================================================
FILE: QSkinLoaderlib/src/main/java/org/qcode/qskinloader/attrhandler/SrcAttrHandler.java
================================================
package org.qcode.qskinloader.attrhandler;
import android.graphics.drawable.AnimationDrawable;
import android.graphics.drawable.Drawable;
import android.view.View;
import android.widget.ImageView;
import org.qcode.qskinloader.IResourceManager;
import org.qcode.qskinloader.ISkinAttrHandler;
import org.qcode.qskinloader.entity.SkinAttr;
import org.qcode.qskinloader.entity.SkinAttrName;
/***
* src属性的换肤支持(android:src)
*/
class SrcAttrHandler implements ISkinAttrHandler {
@Override
public void apply(View view, SkinAttr skinAttr, IResourceManager resourceManager) {
if (null == view
|| null == skinAttr
|| !(SkinAttrName.SRC.equals(skinAttr.mAttrName))) {
return;
}
if (!(view instanceof ImageView)) {
return;
}
boolean isAnimationDrawable = false;
boolean isRunning = false;
Drawable originalDrawable = ((ImageView) view).getDrawable();
if (originalDrawable instanceof AnimationDrawable) {
AnimationDrawable animationDrawable = (AnimationDrawable) originalDrawable;
isAnimationDrawable = true;
isRunning = animationDrawable.isRunning();
}
Drawable drawable = SkinAttrUtils.getDrawable(
resourceManager, skinAttr.mAttrValueRefId,
skinAttr.mAttrValueTypeName, skinAttr.mAttrValueRefName);
if (null != drawable) {
((ImageView) view).setImageDrawable(drawable);
if (isAnimationDrawable && drawable instanceof AnimationDrawable) {
AnimationDrawable animationDrawable = ((AnimationDrawable) drawable);
if (isRunning) {
animationDrawable.start();
} else {
animationDrawable.stop();
}
}
}
}
}
================================================
FILE: QSkinLoaderlib/src/main/java/org/qcode/qskinloader/attrhandler/TextColorAttrHandler.java
================================================
package org.qcode.qskinloader.attrhandler;
import android.content.res.ColorStateList;
import android.view.View;
import android.widget.TextView;
import org.qcode.qskinloader.IResourceManager;
import org.qcode.qskinloader.ISkinAttrHandler;
import org.qcode.qskinloader.entity.SkinAttr;
import org.qcode.qskinloader.entity.SkinAttrName;
import org.qcode.qskinloader.entity.SkinConstant;
/***
* 文字颜色属性的换肤支持(android:textColor)
*/
class TextColorAttrHandler implements ISkinAttrHandler {
@Override
public void apply(View view, SkinAttr skinAttr, IResourceManager resourceManager) {
if (null == view
|| null == skinAttr
|| !(SkinAttrName.TEXT_COLOR.equals(skinAttr.mAttrName))) {
return;
}
if (!(view instanceof TextView)) {
return;
}
TextView tv = (TextView) view;
if (SkinConstant.RES_TYPE_NAME_COLOR.equals(skinAttr.mAttrValueTypeName)
|| SkinConstant.RES_TYPE_NAME_DRAWABLE.equals(skinAttr.mAttrValueTypeName)) {
//按照ColorStateList引用来解析;
//context.getResources().getColor()方法可以取纯颜色,也可以取ColorStateList引用内的颜色,
//如果取的是ColorStateList,则取其中默认颜色;
//同时,context.getResources().getColorStateList()方法也可以取纯颜色生成一个ColorStateList
ColorStateList textColor = resourceManager.getColorStateList(
skinAttr.mAttrValueRefId, skinAttr.mAttrValueTypeName, skinAttr.mAttrValueRefName);
tv.setTextColor(textColor);
}
}
}
================================================
FILE: QSkinLoaderlib/src/main/java/org/qcode/qskinloader/attrhandler/TextColorHintAttrHandler.java
================================================
package org.qcode.qskinloader.attrhandler;
import android.content.res.ColorStateList;
import android.view.View;
import android.widget.TextView;
import org.qcode.qskinloader.IResourceManager;
import org.qcode.qskinloader.ISkinAttrHandler;
import org.qcode.qskinloader.entity.SkinAttr;
import org.qcode.qskinloader.entity.SkinAttrName;
import org.qcode.qskinloader.entity.SkinConstant;
/***
* 文字提示颜色属性的换肤支持(android:textColorHint)
*/
class TextColorHintAttrHandler implements ISkinAttrHandler {
@Override
public void apply(View view, SkinAttr skinAttr, IResourceManager resourceManager) {
if (null == view
|| null == skinAttr
|| !(SkinAttrName.TEXT_COLOR_HINT.equals(skinAttr.mAttrName))) {
return;
}
if (!(view instanceof TextView)) {
return;
}
TextView tv = (TextView) view;
if (SkinConstant.RES_TYPE_NAME_COLOR.equals(skinAttr.mAttrValueTypeName)) {
//按照ColorStateList引用来解析;
//context.getResources().getColor()方法可以取纯颜色,也可以取ColorStateList引用内的颜色,
//如果取的是ColorStateList,则取其中默认颜色;
//同时,context.getResources().getColorStateList()方法也可以取纯颜色生成一个ColorStateList
ColorStateList textHintColor = resourceManager.getColorStateList(
skinAttr.mAttrValueRefId, skinAttr.mAttrValueRefName);
tv.setHintTextColor(textHintColor);
}
}
}
================================================
FILE: QSkinLoaderlib/src/main/java/org/qcode/qskinloader/base/observable/INotifyUpdate.java
================================================
package org.qcode.qskinloader.base.observable;
/**
* 通知观察者更新
* qqliu
* 2016/9/19.
*/
public interface INotifyUpdate<T> {
/***
* 通知观察者发生了标识为identifier的事件,事件参数是params
* @param callback 观察者
* @param identifier 事件标识
* @param params 事件参数
* @return 返回true截断事件传播,false继续事件传播
*/
boolean notifyEvent(T callback, String identifier, Object... params);
}
================================================
FILE: QSkinLoaderlib/src/main/java/org/qcode/qskinloader/base/observable/IObservable.java
================================================
package org.qcode.qskinloader.base.observable;
/**
* 可观察对象的抽象接口,T为观察者
* qqliu
* 2016/9/19.
*/
public interface IObservable<T> {
/***
* 增加新的观察者
*
* @param observer
*/
void addObserver(T observer);
/***
* 删除观察者
*
* @param observer
*/
void removeObserver(T observer);
/***
* 告知观察者发生了变化
* @param callback
*/
void notifyUpdate(INotifyUpdate<T> callback, String identifier, Object... params);
}
================================================
FILE: QSkinLoaderlib/src/main/java/org/qcode/qskinloader/base/observable/Observable.java
================================================
package org.qcode.qskinloader.base.observable;
import java.util.ArrayList;
/**
* 观察者通用逻辑
* qqliu
* 2016/9/19.
*/
public class Observable<T> implements IObservable<T> {
private ArrayList<T> mObservers;
@Override
public void addObserver(T observer) {
if (mObservers == null) {
mObservers = new ArrayList<T>();
}
if (!mObservers.contains(observer)) {
mObservers.add(observer);
}
}
@Override
public void removeObserver(T observer) {
if (mObservers == null) {
return;
}
if (mObservers.contains(observer)) {
mObservers.remove(observer);
}
}
@Override
public void notifyUpdate(INotifyUpdate<T> listener, String identifier, Object... params) {
if (mObservers == null || null == listener) {
return;
}
ArrayList<T> tmpListeners
= (ArrayList<T>) mObservers.clone();
for (T observer : tmpListeners) {
listener.notifyEvent(observer, identifier, params);
}
}
}
================================================
FILE: QSkinLoaderlib/src/main/java/org/qcode/qskinloader/base/utils/CollectionUtils.java
================================================
package org.qcode.qskinloader.base.utils;
import java.util.Collection;
/**
* qqliu
* 2016/9/25.
*/
public class CollectionUtils {
public static boolean isEmpty(Collection<?> collection){
return null == collection || collection.size() <= 0;
}
public static <T> boolean isEmpty(T... array){
return null == array || array.length <= 0;
}
}
================================================
FILE: QSkinLoaderlib/src/main/java/org/qcode/qskinloader/base/utils/HashMapCache.java
================================================
package org.qcode.qskinloader.base.utils;
import java.lang.ref.WeakReference;
import java.util.HashMap;
/**
* 维护一个缓存Map,包括强引用和弱引用两种维护方式;
* 两种方式中key都是强引用;
* qqliu
* 2016/9/19.
*/
public class HashMapCache<K, V> {
private HashMap<K, V> mCacheMap = null;
private HashMap<K, WeakReference<V>> mWeakCacheMap = null;
/***
* @param isStrongReference true 强引用,false弱引用
*/
public HashMapCache(boolean isStrongReference) {
if (isStrongReference) {
mCacheMap = new HashMap<K, V>();
} else {
mWeakCacheMap = new HashMap<K, WeakReference<V>>();
}
}
public V getCache(K key) {
if(null == key) {
return null;
}
if(null != mCacheMap) {
return mCacheMap.get(key);
} else {
WeakReference<V> refValue = mWeakCacheMap.get(key);
if(null != refValue) {
return refValue.get();
}
return null;
}
}
public void addCache(K key, V value) {
if(null == key) {
return;
}
if(null != mCacheMap) {
mCacheMap.put(key, value);
} else {
WeakReference<V> refValue = new WeakReference<V>(value);
mWeakCacheMap.put(key, refValue);
}
}
}
================================================
FILE: QSkinLoaderlib/src/main/java/org/qcode/qskinloader/base/utils/Logging.java
================================================
package org.qcode.qskinloader.base.utils;
import android.util.Log;
public class Logging {
protected static boolean mLoggingEnabled = true;
private static final String PRE_TAG = "SkinLoader_";
public static void setDebugLogging(boolean enabled) {
mLoggingEnabled = enabled;
}
public static boolean isDebugLogging() {
return mLoggingEnabled;
}
public static int v(String tag, String msg) {
int result = 0;
if (mLoggingEnabled) {
result = Log.v(PRE_TAG + tag, msg);
}
return result;
}
public static int v(String tag, String msg, Throwable tr) {
int result = 0;
if (mLoggingEnabled) {
result = Log.v(PRE_TAG + tag, msg, tr);
}
return result;
}
public static int d(String tag, String msg) {
int result = 0;
if (mLoggingEnabled) {
result = Log.d(PRE_TAG + tag, msg);
}
return result;
}
public static int d(String tag, String msg, Throwable tr) {
int result = 0;
if (mLoggingEnabled) {
result = Log.d(PRE_TAG + tag, msg, tr);
}
return result;
}
public static int i(String tag, String msg) {
int result = 0;
if (mLoggingEnabled) {
result = Log.i(PRE_TAG + tag, msg);
}
return result;
}
public static int i(String tag, String msg, Throwable tr) {
int result = 0;
if (mLoggingEnabled) {
result = Log.i(PRE_TAG + tag, msg, tr);
}
return result;
}
public static int w(String tag, String msg) {
int result = 0;
if (mLoggingEnabled) {
result = Log.w(PRE_TAG + tag, msg);
}
return result;
}
public static int w(String tag, String msg, Throwable tr) {
int result = 0;
if (mLoggingEnabled) {
result = Log.w(PRE_TAG + tag, msg, tr);
}
return result;
}
public static int w(String tag, Throwable tr) {
int result = 0;
if (mLoggingEnabled) {
result = Log.w(PRE_TAG + tag, tr);
}
return result;
}
public static int e(String tag, String msg) {
int result = 0;
if (mLoggingEnabled) {
result = Log.e(PRE_TAG + tag, msg);
}
return result;
}
public static int e(String tag, String msg, Throwable tr) {
int result = 0;
if (mLoggingEnabled) {
result = Log.e(PRE_TAG + tag, msg, tr);
}
return result;
}
}
================================================
FILE: QSkinLoaderlib/src/main/java/org/qcode/qskinloader/base/utils/ReflectUtils.java
================================================
package org.qcode.qskinloader.base.utils;
import java.lang.reflect.Field;
/***
* 反射帮助类
* created at 2017/12/31
*/
public class ReflectUtils {
private static final String TAG = "ReflectUtils";
private static Field getDeclaredField(Object object, String fieldName) throws NoSuchFieldException {
for (Class clz = object.getClass(); Object.class != clz; clz = clz.getSuperclass()) {
try {
return clz.getDeclaredField(fieldName);
} catch (Exception ex) {
Logging.d(TAG, "getDeclaredField()| field " + fieldName + " is not in class: " + clz.getSimpleName());
}
}
throw new NoSuchFieldException("field " + fieldName + " NOT found");
}
/***
* 获取指定field的value
*
* @param object
* @param fieldName
* @param <T>
* @return
*/
public static <T> T getFieldValue(Object object, String fieldName)
throws NoSuchFieldException, IllegalAccessException {
if (null == object || StringUtils.isEmpty(fieldName)) {
return null;
}
Field field = getDeclaredField(object, fieldName);
field.setAccessible(true);
Object value = field.get(object);
return (T) value;
}
/***
* 获取指定field的value,无异常
*
* @param obj
* @param fieldName
* @param <T>
* @return
*/
public static <T> T getFieldValueOpt(Object obj, String fieldName) {
try {
return getFieldValue(obj, fieldName);
} catch (Exception ex) {
Logging.d(TAG, "getFieldValueOpt()| error happened", ex);
return null;
}
}
/***
* 设置field的值
*
* @param object
* @param fieldName
* @param value
* @throws NoSuchFieldException
* @throws IllegalAccessException
*/
public static void setFieldValue(Object object, String fieldName, Object value)
throws NoSuchFieldException, IllegalAccessException {
if (null == object || StringUtils.isEmpty(fieldName)) {
return;
}
Field field = getDeclaredField(object, fieldName);
field.setAccessible(true);
field.set(object, value);
}
/***
* 设置field的值,无异常
*
* @param object
* @param fieldName
* @param value
*/
public static void setFieldValueOpt(Object object, String fieldName, Object value) {
try {
setFieldValue(object, fieldName, value);
} catch (Exception ex) {
Logging.d(TAG, "setFieldValueOpt()| error happened", ex);
}
}
}
================================================
FILE: QSkinLoaderlib/src/main/java/org/qcode/qskinloader/base/utils/StringUtils.java
================================================
package org.qcode.qskinloader.base.utils;
/**
* qqliu
* 2016/9/25.
*/
public class StringUtils {
public static boolean isEmpty(CharSequence sequence) {
return null == sequence || sequence.length() <= 0;
}
}
================================================
FILE: QSkinLoaderlib/src/main/java/org/qcode/qskinloader/base/utils/WeakReferenceHelper.java
================================================
package com.iflytek.skin.manager.base.utils;
import java.lang.ref.WeakReference;
/**
* qqliu
* 2016/10/19.
*/
public class WeakReferenceHelper<T> {
private WeakReference<T> mRef;
public WeakReferenceHelper(T t) {
setData(t);
}
public T getData() {
if(null == mRef) {
return null;
}
return mRef.get();
}
public void setData(T t) {
this.mRef = new WeakReference<T>(t);
}
@Override
public String toString() {
return "WeakReferenceHelper{" +
"mData= " + (null == mRef ? "NULL" : mRef.get()) +
'}';
}
}
================================================
FILE: QSkinLoaderlib/src/main/java/org/qcode/qskinloader/entity/DynamicAttr.java
================================================
package org.qcode.qskinloader.entity;
/**
* 动态代码设置皮肤属性的实体类
*
* qqliu
* 2016/9/25.
*/
public class DynamicAttr {
/***
* 对应View的属性
*/
public String mAttrName;
/***
* 属性值对应的reference id值,类似R.color.XX
*/
public int mAttrValueRefId = -1;
/**是否设置了属性值引用*/
public boolean hasSetValueRef = false;
/***
* 是否保留dynamicAttr;
* 子类继承DynamicAttr时可以改变此属性来保留自定义的属性值
*/
public boolean keepInstance = false;
public DynamicAttr(String attrName) {
this.mAttrName = attrName;
hasSetValueRef = false;
keepInstance = false;
}
public DynamicAttr(String attrName, int attrValueRefId) {
this.mAttrName = attrName;
this.mAttrValueRefId = attrValueRefId;
hasSetValueRef = true;
keepInstance = false;
}
@Override
public String toString() {
return "DynamicAttr{" +
"mAttrName='" + mAttrName + '\'' +
", mAttrValueRefId=" + mAttrValueRefId +
", hasSetValueRef=" + hasSetValueRef +
", keepInstance=" + keepInstance +
'}';
}
}
================================================
FILE: QSkinLoaderlib/src/main/java/org/qcode/qskinloader/entity/SkinAttr.java
================================================
package org.qcode.qskinloader.entity;
/**
* 皮肤指定属性及其对应的值/类型的实体类封装
* qqliu
* 2016/9/24.
*/
public class SkinAttr {
/***
* 对应View的属性
*/
public String mAttrName;
/***
* 属性值对应的reference id值,类似R.color.XX
*/
public int mAttrValueRefId;
/***
* 属性值refrence id对应的名称,如R.color.XX,则此值为"XX"
*/
public String mAttrValueRefName;
/***
* 属性值refrence id对应的类型,如R.color.XX,则此值为color
*/
public String mAttrValueTypeName;
/***
* 直接存放自定义的属性
*/
public DynamicAttr mDynamicAttr;
public SkinAttr() {
//empty
}
public SkinAttr(String attrName) {
mAttrName = attrName;
//others is empty
}
@Override
public String toString() {
return "SkinAttr{" +
"mAttrName='" + mAttrName + '\'' +
", mAttrValueRefId=" + mAttrValueRefId +
", mAttrValueRefName='" + mAttrValueRefName + '\'' +
", mAttrValueTypeName='" + mAttrValueTypeName + '\'' +
", mDynamicAttr='" + mDynamicAttr + '\'' +
'}';
}
}
================================================
FILE: QSkinLoaderlib/src/main/java/org/qcode/qskinloader/entity/SkinAttrName.java
================================================
package org.qcode.qskinloader.entity;
/**
* 皮肤框架内置支持的属性
* qqliu
* 2016/9/27.
*/
public class SkinAttrName {
public static final String BACKGROUND = "background";
public static final String SRC = "src";
public static final String DRAWABLE_LEFT = "drawableLeft";
public static final String TEXT_COLOR = "textColor";
public static final String TEXT_COLOR_HINT = "textColorHint";
public static final String LIST_SELECTOR = "listSelector";
public static final String DIVIDER = "divider";
public static final String DRAW_SHADOW = "drawShadow";
public static final String CLEAR_RECYCLER_VIEW = "clearRecyclerView";
}
================================================
FILE: QSkinLoaderlib/src/main/java/org/qcode/qskinloader/entity/SkinAttrSet.java
================================================
package org.qcode.qskinloader.entity;
import org.qcode.qskinloader.base.utils.CollectionUtils;
import org.qcode.qskinloader.base.utils.StringUtils;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
/**
* 皮肤框架支持的属性集合
* qqliu
* 2016/9/24.
*/
public class SkinAttrSet {
private HashMap<String, SkinAttr> mAttrMap = new HashMap<String, SkinAttr>();
public SkinAttrSet(SkinAttr... skinAttrs) {
this(Arrays.asList(skinAttrs));
}
public SkinAttrSet(List<SkinAttr> skinAttrs) {
saveAttrs(skinAttrs);
}
private void saveAttrs(List<SkinAttr> skinAttrs) {
if (CollectionUtils.isEmpty(skinAttrs)) {
return;
}
for (SkinAttr attr : skinAttrs) {
if (null == attr || StringUtils.isEmpty(attr.mAttrName)) {
continue;
}
mAttrMap.put(attr.mAttrName, attr);
}
}
public synchronized void addSkinAttrSet(SkinAttrSet skinAttrSet) {
if (null == skinAttrSet) {
return;
}
List<SkinAttr> setAttrList = skinAttrSet.getAttrList();
saveAttrs(setAttrList);
}
public synchronized void addSkinAttr(SkinAttr skinAttr) {
if (null == skinAttr || StringUtils.isEmpty(skinAttr.mAttrName)) {
return;
}
mAttrMap.put(skinAttr.mAttrName, skinAttr);
}
public synchronized List<SkinAttr> getAttrList() {
ArrayList<SkinAttr> resultList = new ArrayList<SkinAttr>();
Collection<SkinAttr> valueAttrList = mAttrMap.values();
if (!CollectionUtils.isEmpty(valueAttrList)) {
resultList.addAll(valueAttrList);
}
return resultList;
}
}
================================================
FILE: QSkinLoaderlib/src/main/java/org/qcode/qskinloader/entity/SkinConstant.java
================================================
package org.qcode.qskinloader.entity;
/**
* 皮肤框架的常量定义
* qqliu
* 2016/9/25.
*/
public class SkinConstant {
/***
* 支持的命名空间
*/
public static final String XML_NAMESPACE = "http://schemas.android.com/android/skin";
/**属性值对应的类型是color*/
public static final String RES_TYPE_NAME_COLOR = "color";
/**属性值对应的类型是drawable*/
public static final String RES_TYPE_NAME_DRAWABLE = "drawable";
/**属性值对应的类型是mipmap*/
public static final String RES_TYPE_NAME_MIPMAP = "mipmap";
/**界面元素支持换肤的属性*/
public static final String ATTR_SKIN_ENABLE = "enable";
}
================================================
FILE: QSkinLoaderlib/src/main/java/org/qcode/qskinloader/impl/ActivitySkinEventHandlerImpl.java
================================================
package org.qcode.qskinloader.impl;
import android.app.Activity;
import android.content.res.Resources;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.view.View;
import org.qcode.qskinloader.IActivitySkinEventHandler;
import org.qcode.qskinloader.ISkinActivity;
import org.qcode.qskinloader.ISkinAttributeParser;
import org.qcode.qskinloader.IViewCreateListener;
import org.qcode.qskinloader.SkinManager;
import org.qcode.qskinloader.base.utils.Logging;
import java.lang.ref.WeakReference;
/***
* 支持换肤界面的帮助类
*/
public class ActivitySkinEventHandlerImpl implements IActivitySkinEventHandler {
private static final String TAG = "ActivityEventHandler";
private final SkinManagerImpl mSkinManager;
private volatile boolean mNeedRefreshSkin = false;
//当前界面是否有Focus
private boolean mHasFocus;
//皮肤发生变化时,当前界面是否需要立刻刷新皮肤
private boolean mSwitchSkinImmediately;
//当前界面是否支持皮肤变化
private boolean mIsSupportSkinChange;
private WeakReference<Activity> mActivity = null;
private int mWindowBgResId = -1;
private SkinInflaterFactoryImpl mSkinInflaterFactoryImpl;
private IViewCreateListener mViewCreateListener;
private boolean mNeedDelegateViewCreate = true;
private SkinAttributeParser mSkinAttributeParser;
public ActivitySkinEventHandlerImpl() {
mSkinManager = SkinManagerImpl.getInstance();
}
@Override
public void onCreate(Activity activity) {
if (!mIsSupportSkinChange) {
return;
}
mActivity = new WeakReference<Activity>(activity);
if(mNeedDelegateViewCreate) {
mSkinInflaterFactoryImpl =
new SkinInflaterFactoryImpl(getSkinAttributeParser());
mSkinInflaterFactoryImpl.setViewCreateListener(mViewCreateListener);
activity.getLayoutInflater().setFactory(mSkinInflaterFactoryImpl);
}
mSkinManager.addObserver(this);
}
@Override
public void setViewCreateListener(IViewCreateListener viewCreateListener) {
mViewCreateListener = viewCreateListener;
if(null != mSkinInflaterFactoryImpl) {
mSkinInflaterFactoryImpl.setViewCreateListener(viewCreateListener);
}
}
@Override
public void onViewCreated() {
if (!mIsSupportSkinChange) {
return;
}
Logging.d(TAG, "onViewCreated()");
if (!mSkinManager.getResourceManager().isDefault()) {
View contentView = getContentView();
mSkinManager.applySkin(contentView, true);
refreshWindowBg(contentView);
}
mSkinManager.addObserver(this);
}
@Override
public void onStart() {
//do nothing
}
@Override
public void onResume() {
//do nothing
}
@Override
public void onWindowFocusChanged(boolean hasFocus) {
if (!mIsSupportSkinChange) {
return;
}
mHasFocus = hasFocus;
if(mHasFocus) {
if (mNeedRefreshSkin) {
mNeedRefreshSkin = false;
//后台界面展示出来时再刷新
refreshSkin();
}
}
}
@Override
public void onPause() {
//do nothing
}
@Override
public void onStop() {
//do nothing
}
@Override
public void onDestroy() {
if (!mIsSupportSkinChange) {
return;
}
mSkinManager.removeObserver(this);
SkinManager
.with(getContentView())
.cleanAttrs(true);
mActivity.clear();
}
@Override
public IActivitySkinEventHandler setSwitchSkinImmediately(boolean isImmediate) {
mSwitchSkinImmediately = isImmediate;
return this;
}
@Override
public IActivitySkinEventHandler setSupportSkinChange(boolean supportChange) {
mIsSupportSkinChange = supportChange;
return this;
}
@Override
public void handleSkinUpdate() {
if (!mIsSupportSkinChange) {
Logging.d(TAG, "onThemeUpdate()| not support theme change: " + getClass().getSimpleName());
return;
}
if (mHasFocus || mSwitchSkinImmediately) {
mNeedRefreshSkin = false;
refreshSkin();
} else {
//仅置位,不立刻刷新
mNeedRefreshSkin = true;
}
}
@Override
public ISkinAttributeParser getSkinAttributeParser() {
if(null == mSkinAttributeParser) {
mSkinAttributeParser = new SkinAttributeParser();
}
return mSkinAttributeParser;
}
@Override
public IActivitySkinEventHandler setWindowBackgroundResource(int resId) {
mWindowBgResId = resId;
return this;
}
@Override
public IActivitySkinEventHandler setNeedDelegateViewCreate(boolean needDelegateViewCreate) {
mNeedDelegateViewCreate = needDelegateViewCreate;
return this;
}
private void refreshSkin() {
if (!mIsSupportSkinChange) {
return;
}
if (null == mActivity) {
return;
}
final Activity activity = mActivity.get();
activity.runOnUiThread(new Runnable() {
@Override
public void run() {
View contentView = getContentView();
mSkinManager.applySkin(contentView, true);
mSkinManager.applyWindowViewSkin();
refreshWindowBg(contentView);
//通知Activity做其他刷新操作
if (activity instanceof ISkinActivity) {
((ISkinActivity) activity).handleSkinChange();
}
}
});
}
private void refreshWindowBg(View contentView) {
if(!mIsSupportSkinChange) {
return;
}
if (mWindowBgResId <= 0) {
return;
}
if (null == mActivity) {
return;
}
Activity activity = mActivity.get();
if (null == activity) {
return;
}
Drawable bgDrawable;
try {
bgDrawable = new ColorDrawable(
mSkinManager.getResourceManager().getColor(mWindowBgResId));
} catch (Resources.NotFoundException ex) {
try {
bgDrawable = mSkinManager.getResourceManager().getDrawable(mWindowBgResId);
} catch (Resources.NotFoundException e) {
return;
}
}
// contentView.setBackgroundDrawable(bgDrawable);
activity.getWindow().setBackgroundDrawable(bgDrawable);
}
public View getContentView() {
if (null == mActivity) {
return null;
}
Activity activity = mActivity.get();
if (null == activity) {
return null;
}
return activity.findViewById(android.R.id.content);
}
}
================================================
FILE: QSkinLoaderlib/src/main/java/org/qcode/qskinloader/impl/SkinAttributeParser.java
================================================
package org.qcode.qskinloader.impl;
import android.content.Context;
import android.content.res.Resources.NotFoundException;
import android.support.v7.widget.RecyclerView;
import android.util.AttributeSet;
import android.view.View;
import org.qcode.qskinloader.ISkinAttributeParser;
import org.qcode.qskinloader.R;
import org.qcode.qskinloader.SkinManager;
import org.qcode.qskinloader.attrhandler.SkinAttrFactory;
import org.qcode.qskinloader.base.utils.CollectionUtils;
import org.qcode.qskinloader.base.utils.Logging;
import org.qcode.qskinloader.entity.SkinAttr;
import org.qcode.qskinloader.entity.SkinAttrName;
import org.qcode.qskinloader.entity.SkinAttrSet;
import org.qcode.qskinloader.entity.SkinConstant;
import java.util.ArrayList;
import java.util.List;
/***
* 代理View的创建,解析与换肤相关的属性
*/
class SkinAttributeParser implements ISkinAttributeParser {
private static final String TAG = "SkinAttributeParser";
public boolean isSupportSkin(String name, Context context, AttributeSet attrs) {
//只有View设置了skin:enable,才解析属性
boolean isSkinEnable = attrs.getAttributeBooleanValue(
SkinConstant.XML_NAMESPACE,
SkinConstant.ATTR_SKIN_ENABLE,
false);
return isSkinEnable;
}
public void parseAttribute(View view, String name, Context context, AttributeSet attrs) {
if (view == null) {
return;
}
SkinAttrSet skinAttrSet = parseSkinAttr(context, attrs);
//recyclerview 增加处理
if(view instanceof RecyclerView) {
SkinAttr clearSubAttr = new SkinAttr(SkinAttrName.CLEAR_RECYCLER_VIEW);
SkinManager
.with(view)
.addViewAttrs(clearSubAttr);
if(null == skinAttrSet) {
skinAttrSet = new SkinAttrSet(clearSubAttr);
} else {
skinAttrSet.addSkinAttr(clearSubAttr);
}
}
if (null != skinAttrSet) {
//将SkinItem存储在View的tag内
view.setTag(R.id.tag_skin_attr, skinAttrSet);
//如果有drawShadow属性,则替换ImageView为其他View
// ShadowAttr2 skinAttrShadow = skinAttrSet.findSkinAttr(ShadowAttr2.class);
// if (null != skinAttrShadow) {
// if(view instanceof ImageView) {
// //
// view = createShadowImageView(context);
// //将SkinItem存储在View的tag内
// view.setTag(R.id.tag_skin_attr, skinAttrSet);
// } else {
// view = createFrameWrapper(view, skinAttrShadow);
// }
// }
}
}
// private View createFrameWrapper(View view, ShadowAttr2 skinAttrShadow) {
// Context context = view.getContext();
// FrameLayout wrapperView = new FrameLayout(context);
// FrameLayout.LayoutParams paramView = new FrameLayout.LayoutParams(
// ViewGroup.LayoutParams.MATCH_PARENT,
// ViewGroup.LayoutParams.MATCH_PARENT);
// wrapperView.addView(view, paramView);
//
// View shadowView = new View(context);
// shadowView.setVisibility(View.GONE);
// FrameLayout.LayoutParams paramShadow = new FrameLayout.LayoutParams(
// ViewGroup.LayoutParams.MATCH_PARENT,
// ViewGroup.LayoutParams.MATCH_PARENT);
// wrapperView.addView(shadowView, paramShadow);
//
// skinAttrShadow.setShadowView(shadowView);
// return wrapperView;
// }
// private ShadowImageView createShadowImageView(Context context) {
// return new ShadowImageView(context);
// }
/***
* 收集与换肤相关的属性
*
* @param context
* @param attrs
*/
private SkinAttrSet parseSkinAttr(Context context,
AttributeSet attrs) {
List<SkinAttr> viewAttrs = new ArrayList<SkinAttr>();
for (int i = 0; i < attrs.getAttributeCount(); i++) {
String attrName = attrs.getAttributeName(i);
String attrValue = attrs.getAttributeValue(i);
if (!SkinAttrFactory.isSupportedAttr(attrName)) {
continue;
}
if (!attrValue.startsWith("@")) {
Logging.d(TAG, "parseSkinAttr()| only support ref id");
continue;
}
SkinAttr skinAttr = null;
try {
skinAttr = getSkinAttrFromId(context, attrName, attrValue);
} catch (NumberFormatException ex) {
// Logging.d(TAG, "parseSkinAttr()| error happened", ex);
skinAttr = getSkinAttrBySplit(context, attrName, attrValue);
} catch (NotFoundException ex) {
Logging.d(TAG, "parseSkinAttr()| error happened", ex);
}
if (skinAttr != null) {
viewAttrs.add(skinAttr);
}
}
if (CollectionUtils.isEmpty(viewAttrs)) {
return null;
}
return new SkinAttrSet(viewAttrs);
}
private SkinAttr getSkinAttrBySplit(Context context, String attrName, String attrValue) {
try {
int dividerIndex = attrValue.indexOf("/");
String entryName = attrValue.substring(dividerIndex + 1, attrValue.length());
String typeName = attrValue.substring(1, dividerIndex);
int id = context.getResources().getIdentifier(entryName, typeName, context.getPackageName());
return SkinAttrFactory.newSkinAttr(attrName, id, entryName, typeName);
} catch (NotFoundException e) {
Logging.d(TAG, "parseSkinAttr()| error happened", e);
}
return null;
}
private SkinAttr getSkinAttrFromId(Context context, String attrName, String attrValue) {
int id = Integer.parseInt(attrValue.substring(1));
String entryName = context.getResources().getResourceEntryName(id);
String typeName = context.getResources().getResourceTypeName(id);
return SkinAttrFactory.newSkinAttr(attrName, id, entryName, typeName);
}
}
================================================
FILE: QSkinLoaderlib/src/main/java/org/qcode/qskinloader/impl/SkinInflaterFactoryImpl.java
================================================
package org.qcode.qskinloader.impl;
import android.content.Context;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import org.qcode.qskinloader.ISkinAttributeParser;
import org.qcode.qskinloader.IViewCreateListener;
import org.qcode.qskinloader.base.utils.Logging;
import org.qcode.qskinloader.base.utils.ReflectUtils;
/***
* 代理View的创建,解析与换肤相关的属性
*/
class SkinInflaterFactoryImpl implements LayoutInflater.Factory {
private static final String TAG = "SkinInflaterFactoryImpl";
private IViewCreateListener mViewCreateListener;
private ISkinAttributeParser mSkinAttributeParser;
public SkinInflaterFactoryImpl(ISkinAttributeParser parser) {
mSkinAttributeParser = parser;
}
public void setViewCreateListener(IViewCreateListener viewCreateListener) {
mViewCreateListener = viewCreateListener;
}
@Override
public View onCreateView(String name, Context context, AttributeSet attrs) {
View view = null;
//给框架外创建View的机会
if(null != mViewCreateListener) {
view = mViewCreateListener.beforeCreate(name, context, attrs);
}
//判断是否支持换肤
if(!mSkinAttributeParser.isSupportSkin(name, context, attrs)) {
return null;
}
if(null == view) {
//代理创建View
view = createView(context, name, attrs);
}
if (view == null) {
return null;
}
//解析属性
mSkinAttributeParser.parseAttribute(view, name, context, attrs);
//给框架外解析属性的机会
if(null != mViewCreateListener) {
mViewCreateListener.afterCreated(view, name, context, attrs);
}
return view;
}
/***
* 根据名称创建view
*
* @param context
* @param name
* @param attrs
* @return
*/
private View createView(Context context,
String name,
AttributeSet attrs) {
View view = null;
try {
LayoutInflater inflater = LayoutInflater.from(context);
setupInflater(inflater, context);
if (-1 == name.indexOf('.')) {
if ("View".equals(name)
|| "ViewStub".equals(name)
|| "ViewGroup".equals(name)) {
view = inflater.createView(
name, "android.view.", attrs);
}
if (view == null) {
view = inflater.createView(
name, "android.widget.", attrs);
}
if (view == null) {
view = inflater.createView(
name, "android.webkit.", attrs);
}
} else {
view = inflater.createView(name, null, attrs);
}
} catch (Exception ex) {
Logging.d(TAG, "createView()| create view failed", ex);
view = null;
}
return view;
}
private void setupInflater(LayoutInflater inflater, Context context) {
//异常,处理context为空,一般不会发生
Context inflaterContext = inflater.getContext();
if (null == inflaterContext) {
ReflectUtils.setFieldValueOpt(inflater, "mContext", context);
}
//设置mConstructorArgs的第一个参数context
Object[] constructorArgs = ReflectUtils.getFieldValueOpt(inflater, "mConstructorArgs");
if (null == constructorArgs || constructorArgs.length < 2) {
//异常,一般不会发生
constructorArgs = new Object[2];
ReflectUtils.setFieldValueOpt(inflater, "mConstructorArgs", constructorArgs);
}
//如果mConstructorArgs的第一个参数为空,则设置为mContext
if (null == constructorArgs[0]) {
constructorArgs[0] = inflater.getContext();
}
}
}
================================================
FILE: QSkinLoaderlib/src/main/java/org/qcode/qskinloader/impl/SkinManagerImpl.java
================================================
package org.qcode.qskinloader.impl;
import android.content.Context;
import android.os.AsyncTask;
import android.view.View;
import android.view.ViewGroup;
import org.qcode.qskinloader.IActivitySkinEventHandler;
import org.qcode.qskinloader.ILoadSkinListener;
import org.qcode.qskinloader.IResourceLoader;
import org.qcode.qskinloader.IResourceManager;
import org.qcode.qskinloader.ISkinAttrHandler;
import org.qcode.qskinloader.ISkinManager;
import org.qcode.qskinloader.attrhandler.SkinAttrFactory;
import org.qcode.qskinloader.attrhandler.SkinAttrUtils;
import org.qcode.qskinloader.base.observable.INotifyUpdate;
import org.qcode.qskinloader.base.observable.Observable;
import org.qcode.qskinloader.base.utils.CollectionUtils;
import org.qcode.qskinloader.base.utils.Logging;
import org.qcode.qskinloader.base.utils.StringUtils;
import org.qcode.qskinloader.entity.SkinAttrSet;
import org.qcode.qskinloader.resourceloader.ILoadResourceCallback;
import org.qcode.qskinloader.resourceloader.ResourceManager;
import org.qcode.qskinloader.resourceloader.impl.APKResourceLoader;
import java.util.List;
/**
* 皮肤加载管理类对外实现接口
* qqliu
* 2016/9/24.
*/
public class SkinManagerImpl implements ISkinManager {
private static final String TAG = "SkinManager";
//单例相关
private static volatile SkinManagerImpl mInstance;
private SkinManagerImpl() {
}
public static SkinManagerImpl getInstance() {
if (null == mInstance) {
synchronized (SkinManagerImpl.class) {
if (null == mInstance) {
mInstance = new SkinManagerImpl();
}
}
}
return mInstance;
}
private Context mContext;
private IResourceManager mResourceManager;
private Observable<IActivitySkinEventHandler> mObservable;
@Override
public void init(Context context) {
mContext = context.getApplicationContext();
mResourceManager = new ResourceManager(mContext);
mObservable = new Observable<IActivitySkinEventHandler>();
new AsyncTask<String, Void, Void>() {
@Override
protected Void doInBackground(String... params) {
return null;
}
}.execute("");
}
@Override
public void restoreDefault(String defaultSkinIdentifier, ILoadSkinListener loadListener) {
if (null != loadListener) {
loadListener.onLoadStart(defaultSkinIdentifier);
}
//恢复ResourceManager的行为
mResourceManager.setBaseResource(null, null);
refreshAllSkin();
if (loadListener != null) {
loadListener.onLoadSuccess(defaultSkinIdentifier);
}
}
private void refreshAllSkin() {
//刷新正常的Activity内View的皮肤
refreshSkin();
//刷新框架内维护的View的皮肤,包括Dialog/popWindow/悬浮窗等应用场景
applyWindowViewSkin();
}
@Override
public void loadAPKSkin(String skinPath, ILoadSkinListener loadListener) {
loadSkin(skinPath, new APKResourceLoader(mContext), loadListener);
}
@Override
public void loadSkin(final String skinIdentifier,
final IResourceLoader resourceLoader,
final ILoadSkinListener loadListener) {
if(StringUtils.isEmpty(skinIdentifier)
|| null == resourceLoader) {
if(null != loadListener) {
loadListener.onLoadFail(skinIdentifier);
}
return;
}
//当前皮肤就是将要换肤的皮肤,则不执行后续行为
if (skinIdentifier.equals(mResourceManager.getSkinIdentifier())) {
Logging.d(TAG, "load()| current skin matches target, do nothing");
if(null != loadListener) {
loadListener.onLoadSuccess(skinIdentifier);
}
return;
}
resourceLoader.loadResource(skinIdentifier, new ILoadResourceCallback() {
@Override
public void onLoadStart(String identifier) {
if (loadListener != null) {
loadListener.onLoadStart(skinIdentifier);
}
}
@Override
public void onLoadSuccess(String identifier, IResourceManager result) {
Logging.d(TAG, "onLoadSuccess() | identifier= " + identifier);
mResourceManager.setBaseResource(identifier, result);
refreshAllSkin();
Logging.d(TAG, "onLoadSuccess()| notify update");
if (loadListener != null) {
loadListener.onLoadSuccess(skinIdentifier);
}
}
@Override
public void onLoadFail(String identifier, int errorCode) {
mResourceManager.setBaseResource(null, null);
if (loadListener != null) {
loadListener.onLoadFail(skinIdentifier);
}
}
});
}
@Override
public void applySkin(View view, boolean applyChild) {
if (null == view) {
return;
}
SkinAttrSet skinAttrSet = ViewSkinTagHelper.getSkinAttrs(view);
SkinAttrUtils.applySkinAttrs(view, skinAttrSet, mResourceManager);
if (applyChild) {
if (view instanceof ViewGroup) {
//遍历子元素应用皮肤
ViewGroup viewGroup = (ViewGroup) view;
for (int i = 0; i < viewGroup.getChildCount(); i++) {
applySkin(viewGroup.getChildAt(i), true);
}
}
}
}
@Override
public void registerSkinAttrHandler(String attrName, ISkinAttrHandler skinAttrHandler) {
SkinAttrFactory.registerSkinAttrHandler(attrName, skinAttrHandler);
}
@Override
public void unregisterSkinAttrHandler(String attrName) {
SkinAttrFactory.removeSkinAttrHandler(attrName);
}
@Override
public void setResourceManager(IResourceManager resourceManager) {
if(null == resourceManager) {
return;
}
mResourceManager = resourceManager;
}
@Override
public IResourceManager getResourceManager() {
return mResourceManager;
}
@Override
public void addObserver(IActivitySkinEventHandler observer) {
mObservable.addObserver(observer);
}
@Override
public void removeObserver(IActivitySkinEventHandler observer) {
mObservable.removeObserver(observer);
}
@Override
public void notifyUpdate(INotifyUpdate<IActivitySkinEventHandler> callback, String identifier, Object... params) {
mObservable.notifyUpdate(callback, identifier, params);
}
/**刷新Window上添加的View的显示模式*/
void applyWindowViewSkin() {
List<View> windowViewList = WindowViewManager.getInstance().getWindowViewList();
if(CollectionUtils.isEmpty(windowViewList)) {
return;
}
for(View view : windowViewList) {
applySkin(view, true);
}
}
/***
* 告知外部观察者当前皮肤发生了变化
*/
private void refreshSkin() {
notifyUpdate(new INotifyUpdate<IActivitySkinEventHandler>() {
@Override
public boolean notifyEvent(
IActivitySkinEventHandler handler,
String identifier,
Object... params) {
handler.handleSkinUpdate();
return false;
}
}, null);
}
}
================================================
FILE: QSkinLoaderlib/src/main/java/org/qcode/qskinloader/impl/SkinViewHelperImpl.java
================================================
package org.qcode.qskinloader.impl;
import android.content.res.Resources;
import android.support.annotation.NonNull;
import android.view.View;
import android.view.ViewGroup;
import org.qcode.qskinloader.ISkinViewHelper;
import org.qcode.qskinloader.attrhandler.SkinAttrFactory;
import org.qcode.qskinloader.base.utils.CollectionUtils;
import org.qcode.qskinloader.base.utils.Logging;
import org.qcode.qskinloader.base.utils.StringUtils;
import org.qcode.qskinloader.entity.DynamicAttr;
import org.qcode.qskinloader.entity.SkinAttr;
import org.qcode.qskinloader.entity.SkinAttrSet;
import java.util.Arrays;
import java.util.List;
import static android.content.ContentValues.TAG;
/**
* View皮肤设置管理
* qqliu
* 2016/10/8.
*/
public class SkinViewHelperImpl implements ISkinViewHelper {
private View mView;
public SkinViewHelperImpl(View view) {
mView = view;
}
//=========================interfaces================================//
@Override
public ISkinViewHelper setViewAttrs(String attrName, int resId) {
if (StringUtils.isEmpty(attrName)) {
return this;
}
SkinAttr attr = parseSkinAttr(attrName, resId);
return setViewAttrs(attr);
}
@Override
public ISkinViewHelper setViewAttrs(DynamicAttr... dynamicAttrs) {
if (CollectionUtils.isEmpty(dynamicAttrs)) {
return this;
}
return setViewAttrs(Arrays.asList(dynamicAttrs));
}
@Override
public ISkinViewHelper setViewAttrs(SkinAttr... skinAttrs) {
if (CollectionUtils.isEmpty(skinAttrs)) {
return this;
}
ViewSkinTagHelper.setSkinAttrs(mView, new SkinAttrSet(skinAttrs));
return this;
}
@Override
public ISkinViewHelper setViewAttrs(List<DynamicAttr> dynamicAttrs) {
if (CollectionUtils.isEmpty(dynamicAttrs)) {
return this;
}
SkinAttrSet skinAttrSet = parseSkinAttrSet(dynamicAttrs);
ViewSkinTagHelper.setSkinAttrs(mView, skinAttrSet);
return this;
}
@Override
public ISkinViewHelper addViewAttrs(String attrName, int resId) {
if (StringUtils.isEmpty(attrName)) {
return this;
}
SkinAttr attr = parseSkinAttr(attrName, resId);
return addViewAttrs(attr);
}
@Override
public ISkinViewHelper addViewAttrs(DynamicAttr... dynamicAttrs) {
if (CollectionUtils.isEmpty(dynamicAttrs)) {
return this;
}
return addViewAttrs(Arrays.asList(dynamicAttrs));
}
@Override
public ISkinViewHelper addViewAttrs(SkinAttr... skinAttrs) {
if (CollectionUtils.isEmpty(skinAttrs)) {
return this;
}
ViewSkinTagHelper.addSkinAttrs(mView, new SkinAttrSet(skinAttrs));
return this;
}
@Override
public ISkinViewHelper addViewAttrs(List<DynamicAttr> dynamicAttrs) {
if (CollectionUtils.isEmpty(dynamicAttrs)) {
return this;
}
SkinAttrSet skinAttrSet = parseSkinAttrSet(dynamicAttrs);
ViewSkinTagHelper.addSkinAttrs(mView, skinAttrSet);
return this;
}
@Override
public ISkinViewHelper cleanAttrs(boolean clearChild) {
if (null == mView) {
return this;
}
cleanAttrs(mView, clearChild);
return this;
}
@Override
public void applySkin(boolean applyChild) {
SkinManagerImpl.getInstance().applySkin(mView, applyChild);
}
private static void cleanAttrs(View view, boolean clearChild) {
if (null == view) {
return;
}
ViewSkinTagHelper.setSkinAttrs(view, null);
if (clearChild) {
if (view instanceof ViewGroup) {
//遍历子元素清除皮肤
ViewGroup viewGroup = (ViewGroup) view;
for (int i = 0; i < viewGroup.getChildCount(); i++) {
cleanAttrs(viewGroup.getChildAt(i), true);
}
}
}
}
private SkinAttr parseSkinAttr(String attrName, int resId) {
SkinAttr skinAttr = null;
Resources resources = mView.getResources();
try {
String attrValueName = resources.getResourceEntryName(resId);
String attrValueType = resources.getResourceTypeName(resId);
skinAttr = SkinAttrFactory.newSkinAttr(attrName, resId, attrValueName, attrValueType);
} catch (Exception ex) {
Logging.d(TAG, "dynamicAddView()| error happened", ex);
}
return skinAttr;
}
@NonNull
private SkinAttrSet parseSkinAttrSet(List<DynamicAttr> dynamicAttrs) {
SkinAttrSet skinAttrSet = new SkinAttrSet();
for (DynamicAttr dynamicAttr : dynamicAttrs) {
if (null == dynamicAttr) {
continue;
}
SkinAttr attr;
if (dynamicAttr.hasSetValueRef) {
//设置了value,则解析resId的名称和类型
attr = parseSkinAttr(dynamicAttr.mAttrName, dynamicAttr.mAttrValueRefId);
} else {
//没有value,直接解析名称
attr = SkinAttrFactory.newSkinAttr(dynamicAttr.mAttrName);
}
if (null == attr) {
continue;
}
if (dynamicAttr.keepInstance) {
attr.mDynamicAttr = dynamicAttr;
}
skinAttrSet.addSkinAttr(attr);
}
return skinAttrSet;
}
}
================================================
FILE: QSkinLoaderlib/src/main/java/org/qcode/qskinloader/impl/ViewSkinTagHelper.java
================================================
package org.qcode.qskinloader.impl;
import android.view.View;
import org.qcode.qskinloader.R;
import org.qcode.qskinloader.entity.SkinAttrSet;
/**
* 存取View的皮肤属性的帮助类
* qqliu
* 2016/10/8.
*/
class ViewSkinTagHelper {
/***
* 设置View的皮肤属性
* @param view
* @param skinAttrSet
*/
public static void setSkinAttrs(View view, SkinAttrSet skinAttrSet) {
if(null == view) {
return;
}
view.setTag(R.id.tag_skin_attr, skinAttrSet);
}
/***
* 添加View的皮肤属性
* @param view
* @param skinAttrSet
*/
public static void addSkinAttrs(View view, SkinAttrSet skinAttrSet) {
if(null == view) {
return;
}
SkinAttrSet attrSet = getSkinAttrs(view);
if (null == attrSet) {
view.setTag(R.id.tag_skin_attr, skinAttrSet);
} else {
attrSet.addSkinAttrSet(skinAttrSet);
}
}
/***
* 获取View的皮肤属性
* @param view
* @return skinAttrSet
*/
public static SkinAttrSet getSkinAttrs(View view) {
if(null == view) {
return null;
}
return (SkinAttrSet) view.getTag(R.id.tag_skin_attr);
}
}
================================================
FILE: QSkinLoaderlib/src/main/java/org/qcode/qskinloader/impl/WindowViewManager.java
================================================
package org.qcode.qskinloader.impl;
import android.view.View;
import org.qcode.qskinloader.IWindowViewManager;
import org.qcode.qskinloader.SkinManager;
import org.qcode.qskinloader.base.utils.CollectionUtils;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;
/**
* 直接加载在Window上的view的管理器
* qqliu
* 2016/10/8.
*/
public class WindowViewManager implements IWindowViewManager {
private ArrayList<WeakReference<View>> mSkinViewList = new ArrayList<WeakReference<View>>();
//==========================Singleton========================//
private static volatile WindowViewManager mInstance;
private WindowViewManager() {}
public static WindowViewManager getInstance() {
if(null == mInstance) {
synchronized (WindowViewManager.class) {
if(null == mInstance) {
mInstance = new WindowViewManager();
}
}
}
return mInstance;
}
//==========================interfaces========================//
@Override
public IWindowViewManager addWindowView(View view) {
ArrayList<WeakReference<View>> tmpList
= (ArrayList<WeakReference<View>>) mSkinViewList.clone();
for (WeakReference<View> viewRef : tmpList) {
if (null == viewRef) {
continue;
}
//已添加此View,则不需要再添加
if (view == viewRef.get()) {
return this;
}
}
mSkinViewList.add(new WeakReference<View>(view));
return this;
}
@Override
public IWindowViewManager removeWindowView(View view) {
ArrayList<WeakReference<View>> tmpList
= (ArrayList<WeakReference<View>>) mSkinViewList.clone();
for (WeakReference<View> viewRef : tmpList) {
if (null == viewRef) {
continue;
}
//找到了指定View
if (view == viewRef.get()) {
mSkinViewList.remove(viewRef);
break;
}
}
return this;
}
@Override
public IWindowViewManager clear() {
if(CollectionUtils.isEmpty(mSkinViewList)) {
return this;
}
mSkinViewList.clear();
return this;
}
@Override
public void applySkinForViews(boolean applyChild) {
if(CollectionUtils.isEmpty(mSkinViewList)) {
return;
}
for(WeakReference<View> viewRef : mSkinViewList) {
if(null == viewRef || null == viewRef.get()) {
continue;
}
SkinManager.getInstance().applySkin(viewRef.get(), applyChild);
}
}
@Override
public List<View> getWindowViewList() {
ArrayList<View> resultList = new ArrayList<View>();
ArrayList<WeakReference<View>> tmpList
= (ArrayList<WeakReference<View>>) mSkinViewList.clone();
for (WeakReference<View> viewRef : tmpList) {
if (null == viewRef || null == viewRef.get()) {
continue;
}
resultList.add(viewRef.get());
}
return resultList;
}
}
================================================
FILE: QSkinLoaderlib/src/main/java/org/qcode/qskinloader/resourceloader/ILoadResourceCallback.java
================================================
package org.qcode.qskinloader.resourceloader;
import org.qcode.qskinloader.IResourceManager;
/**
* 加载皮肤资源的回调
* qqliu
* 2016/9/25.
*/
public interface ILoadResourceCallback {
/***
* 加载皮肤资源开始
*
* @param identifier
*/
void onLoadStart(String identifier);
/***
* 加载皮肤资源成功
*
* @param identifier
* @param result
*/
void onLoadSuccess(String identifier, IResourceManager result);
/***
* 加载皮肤资源失败
*
* @param identifier
* @param errorCode
*/
void onLoadFail(String identifier, int errorCode);
}
================================================
FILE: QSkinLoaderlib/src/main/java/org/qcode/qskinloader/resourceloader/ResourceManager.java
================================================
package org.qcode.qskinloader.resourceloader;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.graphics.drawable.Drawable;
import org.qcode.qskinloader.IResourceManager;
import org.qcode.qskinloader.base.utils.Logging;
/**
* 资源管理类的实现,获取资源的行为会代理到其内部的mBase实现
* <p>
* qqliu
* 2016/9/18.
*/
public class ResourceManager implements IResourceManager {
private static final String TAG = "ResourceManager";
private Context mContext;
private Resources mDefaultResources;
private IResourceManager mBase;
private String mSkinIdentifier;
public ResourceManager(Context context) {
mContext = context;
mDefaultResources = mContext.getResources();
}
@Override
public void setBaseResource(String skinIdentifier,
IResourceManager baseResource) {
mSkinIdentifier = skinIdentifier;
mBase = baseResource;
}
@Override
public boolean isDefault() {
if(null != mBase) {
return mBase.isDefault();
}
return true;
}
@Override
public int getColor(int resId) {
if (null != mBase) {
try {
return mBase.getColor(resId);
} catch (Exception ex) {
Logging.d(TAG, "getColor()| error happened", ex);
}
}
return mDefaultResources.getColor(resId);
}
@Override
public int getColor(int resId, String resName) {
if (null != mBase) {
try {
return mBase.getColor(resId, resName);
} catch (Exception ex) {
Logging.d(TAG, "getColor()| error happened", ex);
}
}
return mDefaultResources.getColor(resId);
}
@Override
public ColorStateList getColorStateList(int resId) {
if (null != mBase) {
try {
return mBase.getColorStateList(resId);
} catch (Exception ex) {
Logging.d(TAG, "getColorStateList()| error happened", ex);
}
}
return convertToColorStateList(resId);
}
@Override
public ColorStateList getColorStateList(int resId, String resName) {
if (null != mBase) {
try {
return mBase.getColorStateList(resId, resName);
} catch (Exception ex) {
Logging.d(TAG, "getColorStateList()| error happened", ex);
}
}
return convertToColorStateList(resId);
}
@Override
public ColorStateList getColorStateList(int resId, String typeName, String resName) {
if (null != mBase) {
try {
return mBase.getColorStateList(resId, typeName, resName);
} catch (Exception ex) {
Logging.d(TAG, "getColorStateList()| error happened", ex);
}
}
return convertToColorStateList(resId);
}
public Drawable getDrawable(int resId) {
if (null != mBase) {
try {
return mBase.getDrawable(resId);
} catch (Exception ex) {
Logging.d(TAG, "getDrawable()| error happened", ex);
}
}
return mDefaultResources.getDrawable(resId);
}
@SuppressLint("NewApi")
public Drawable getDrawable(int resId, String resName) {
if (null != mBase) {
try {
return mBase.getDrawable(resId, resName);
} catch (Exception ex) {
Logging.d(TAG, "getDrawable()| error happened", ex);
}
}
return mDefaultResources.getDrawable(resId);
}
/**
* 加载指定资源颜色drawable,转化为ColorStateList,保证selector类型的Color也能被转换。
* 无皮肤包资源返回默认主题颜色
*
* @param resId
* @return
*/
private ColorStateList convertToColorStateList(int resId) {
ColorStateList colorList = null;
try {
colorList = mDefaultResources.getColorStateList(resId);
} catch (Resources.NotFoundException ex) {
Logging.d(TAG, "convertToColorStateList()| error happened", ex);
}
if (colorList != null) {
return colorList;
}
int[][] states = new int[1][1];
colorList =
new ColorStateList(states, new int[]{
mDefaultResources.getColor(resId)});
return colorList;
}
@Override
public String getSkinIdentifier() {
return mSkinIdentifier;
}
}
================================================
FILE: QSkinLoaderlib/src/main/java/org/qcode/qskinloader/resourceloader/impl/APKResourceLoader.java
================================================
package org.qcode.qskinloader.resourceloader.impl;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.res.AssetManager;
import android.content.res.Resources;
import android.os.AsyncTask;
import org.qcode.qskinloader.resourceloader.ILoadResourceCallback;
import org.qcode.qskinloader.IResourceLoader;
import org.qcode.qskinloader.base.utils.Logging;
import org.qcode.qskinloader.base.utils.StringUtils;
import java.io.File;
import java.lang.reflect.Method;
/**
* 基于APK方式的资源加载器
* qqliu
* 2016/9/25.
*/
public class APKResourceLoader implements IResourceLoader {
private static final String TAG = "APKResourceLoader";
private Context mContext;
private String mPackageName;
private Resources mResources;
public APKResourceLoader(Context context) {
this.mContext = context;
}
@Override
public void loadResource(final String skinIdentifier,
final ILoadResourceCallback loadCallBack) {
if (StringUtils.isEmpty(skinIdentifier)) {
return;
}
new AsyncTask<String, Void, APkLoadResult>() {
@Override
protected void onPreExecute() {
if (loadCallBack != null) {
loadCallBack.onLoadStart(skinIdentifier);
}
}
@Override
protected APkLoadResult doInBackground(String... params) {
if (null == mContext || null == params || params.length <= 0) {
return null;
}
try {
String skinPkgPath = params[0];
File file = new File(skinPkgPath);
if (file == null || !file.exists()) {
return null;
}
PackageManager packageManager = mContext.getPackageManager();
PackageInfo packageInfo = packageManager.getPackageArchiveInfo(
skinPkgPath, PackageManager.GET_ACTIVITIES);
String skinPkgName = packageInfo.packageName;
AssetManager assetManager = AssetManager.class.newInstance();
Method addAssetPath = assetManager.getClass().getMethod("addAssetPath", String.class);
addAssetPath.invoke(assetManager, skinPkgPath);
Resources superResources = mContext.getResources();
Resources skinResource = new Resources(
assetManager, superResources.getDisplayMetrics(),
superResources.getConfiguration());
return new APkLoadResult(skinPkgName, skinResource);
} catch (Exception ex) {
Logging.d(TAG, "doInBackground()| exception happened", ex);
}
return null;
}
@Override
protected void onPostExecute(APkLoadResult result) {
if (null != result) {
if (loadCallBack != null) {
loadCallBack.onLoadSuccess(skinIdentifier,
new APKResourceManager(
mContext, result.pkgName, result.resources));
}
} else {
if (loadCallBack != null) {
loadCallBack.onLoadFail(skinIdentifier, -1);
}
}
}
}.execute(skinIdentifier);
}
private static class APkLoadResult {
String pkgName;
Resources resources;
public APkLoadResult(String pkgName, Resources resources) {
this.pkgName = pkgName;
this.resources = resources;
}
}
}
================================================
FILE: QSkinLoaderlib/src/main/java/org/qcode/qskinloader/resourceloader/impl/APKResourceManager.java
================================================
package org.qcode.qskinloader.resourceloader.impl;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.graphics.drawable.Drawable;
import org.qcode.qskinloader.IResourceManager;
import org.qcode.qskinloader.base.utils.HashMapCache;
import org.qcode.qskinloader.entity.SkinConstant;
/**
* 基于APK方式的资源管理类
* qqliu
* 2016/9/18.
*/
public class APKResourceManager implements IResourceManager {
private static final String TAG = "APKResourceManager";
private Context mContext;
private Resources mDefaultResources;
private String mPackageName;
private Resources mResources;
private HashMapCache<String, Integer> mColorCache
= new HashMapCache<String, Integer>(true);
public APKResourceManager(Context context, String pkgName, Resources resources) {
mContext = context;
mDefaultResources = mContext.getResources();
mPackageName = pkgName;
mResources = resources;
}
@Override
public void setBaseResource(String skinIdentifier, IResourceManager baseResource) {
//do nothing
}
@Override
public String getSkinIdentifier() {
return null;
}
@Override
public boolean isDefault() {
return false;
}
@Override
public int getColor(int resId) {
String resName = mDefaultResources.getResourceEntryName(resId);
return getColor(resId, resName);
}
@Override
public int getColor(int resId, String resName) {
String resKey = getResKey(mPackageName, resName);
Integer color = mColorCache.getCache(resKey);
if (null != color) {
return color;
}
int trueResId = mResources.getIdentifier(
resName, SkinConstant.RES_TYPE_NAME_COLOR, mPackageName);
int trueColor = mResources.getColor(trueResId);
mColorCache.addCache(resKey, trueColor);
return trueColor;
}
public Drawable getDrawable(int resId) {
String resName = mDefaultResources.getResourceEntryName(resId);
return getDrawable(resId, resName);
}
@SuppressLint("NewApi")
public Drawable getDrawable(int resId, String resName) {
int trueResId = mResources.getIdentifier(resName,
SkinConstant.RES_TYPE_NAME_DRAWABLE, mPackageName);
if (0 == trueResId) {
trueResId = mResources.getIdentifier(resName,
SkinConstant.RES_TYPE_NAME_MIPMAP, mPackageName);
if (0 == trueResId) {
throw new Resources.NotFoundException(resName);
}
}
Drawable trueDrawable;
if (android.os.Build.VERSION.SDK_INT < 22) {
trueDrawable = mResources.getDrawable(trueResId);
} else {
trueDrawable = mResources.getDrawable(trueResId, null);
}
return trueDrawable;
}
@Override
public ColorStateList getColorStateList(int resId) {
String resName = mDefaultResources.getResourceEntryName(resId);
return getColorStateList(resId, resName);
}
/**
* 读取ColorStateList
* @param resId
* @return
*/
@Override
public ColorStateList getColorStateList(int resId, String resName) {
return getColorStateList(resId, SkinConstant.RES_TYPE_NAME_COLOR, resName);
}
@Override
public ColorStateList getColorStateList(int resId, String typeName, String resName) {
int trueResId = mResources.getIdentifier(
resName, typeName, mPackageName);
ColorStateList colorList = mResources.getColorStateList(trueResId);
return colorList;
}
private String getResKey(String skinPackageName, String resName) {
return (null == skinPackageName ? "" : skinPackageName) + "_" + resName;
}
}
================================================
FILE: QSkinLoaderlib/src/main/java/org/qcode/qskinloader/resourceloader/impl/ConfigChangeResourceLoader.java
================================================
package org.qcode.qskinloader.resourceloader.impl;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.util.DisplayMetrics;
import org.qcode.qskinloader.IResourceLoader;
import org.qcode.qskinloader.base.utils.StringUtils;
import org.qcode.qskinloader.resourceloader.ILoadResourceCallback;
/**
* 基于Android原生夜间模式的ConfigChange方式的资源加载器
* qqliu
* 2016/10/9.
*/
public class ConfigChangeResourceLoader implements IResourceLoader {
private static final String TAG = "ConfigChangeResourceLoader";
public static final String MODE_DAY = "day";
public static final String MODE_NIGHT = "night";
private Context mContext;
public ConfigChangeResourceLoader(Context context) {
this.mContext = context;
}
@Override
public void loadResource(final String skinIdentifier,
final ILoadResourceCallback loadCallBack) {
if (StringUtils.isEmpty(skinIdentifier)) {
return;
}
if (loadCallBack != null) {
loadCallBack.onLoadStart(skinIdentifier);
}
switchMode(MODE_NIGHT.equals(skinIdentifier));
if (loadCallBack != null) {
loadCallBack.onLoadSuccess(skinIdentifier,
new ConfigChangeResourceManager(mContext, skinIdentifier));
}
}
private void switchMode(boolean isNightMode) {
Resources resources = mContext.getResources();
DisplayMetrics dm = resources.getDisplayMetrics();
Configuration config = resources.getConfiguration();
config.uiMode &= ~Configuration.UI_MODE_NIGHT_MASK;
config.uiMode |= isNightMode ?
Configuration.UI_MODE_NIGHT_YES :
Configuration.UI_MODE_NIGHT_NO;
resources.updateConfiguration(config, dm);
}
}
================================================
FILE: QSkinLoaderlib/src/main/java/org/qcode/qskinloader/resourceloader/impl/ConfigChangeResourceManager.java
================================================
package org.qcode.qskinloader.resourceloader.impl;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.graphics.drawable.Drawable;
import org.qcode.qskinloader.IResourceManager;
/**
* 基于资Android原生夜间模式的ConfigChange方式的资源管理类
* qqliu
* 2016/10/9.
*/
public class ConfigChangeResourceManager implements IResourceManager {
private static final String TAG = "ConfigChangeResourceManager";
private Context mContext;
private String mSkinIdentifier;
private Resources mResources;
public ConfigChangeResourceManager(Context context, String skinIdentifier) {
mContext = context;
mResources = mContext.getResources();
mSkinIdentifier = skinIdentifier;
}
@Override
public void setBaseResource(String skinIdentifier, IResourceManager baseResource) {
//do nothing
}
@Override
public String getSkinIdentifier() {
return mSkinIdentifier;
}
@Override
public boolean isDefault() {
return ConfigChangeResourceLoader.MODE_DAY.equals(mSkinIdentifier);
}
@Override
public int getColor(int resId) {
return mResources.getColor(resId);
}
@Override
public int getColor(int resId, String resName) {
return mResources.getColor(resId);
}
public Drawable getDrawable(int resId) {
return mResources.getDrawable(resId);
}
@SuppressLint("NewApi")
public Drawable getDrawable(int resId, String resName) {
return mResources.getDrawable(resId);
}
@Override
public ColorStateList getColorStateList(int resId) {
return mResources.getColorStateList(resId);
}
@Override
public ColorStateList getColorStateList(int resId, String resName) {
return mResources.getColorStateList(resId);
}
@Override
public ColorStateList getColorStateList(int resId, String typeName, String resName) {
return mResources.getColorStateList(resId);
}
}
================================================
FILE: QSkinLoaderlib/src/main/java/org/qcode/qskinloader/resourceloader/impl/SuffixResourceLoader.java
================================================
package org.qcode.qskinloader.resourceloader.impl;
import android.content.Context;
import org.qcode.qskinloader.IResourceLoader;
import org.qcode.qskinloader.base.utils.StringUtils;
import org.qcode.qskinloader.resourceloader.ILoadResourceCallback;
/**
* 基于资源名称后缀拼接方式的资源加载器
* qqliu
* 2016/10/9.
*/
public class SuffixResourceLoader implements IResourceLoader {
private static final String TAG = "SuffixResourceLoader";
private Context mContext;
private String mSkinSuffix;
public SuffixResourceLoader(Context context) {
this.mContext = context;
}
@Override
public void loadResource(final String skinIdentifier,
final ILoadResourceCallback loadCallBack) {
if (StringUtils.isEmpty(skinIdentifier)) {
return;
}
if (loadCallBack != null) {
loadCallBack.onLoadStart(skinIdentifier);
}
mSkinSuffix = skinIdentifier;
if (loadCallBack != null) {
loadCallBack.onLoadSuccess(skinIdentifier,
new SuffixResourceManager(mContext, mSkinSuffix));
}
}
}
================================================
FILE: QSkinLoaderlib/src/main/java/org/qcode/qskinloader/resourceloader/impl/SuffixResourceManager.java
================================================
package org.qcode.qskinloader.resourceloader.impl;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.graphics.drawable.Drawable;
import org.qcode.qskinloader.IResourceManager;
import org.qcode.qskinloader.base.utils.HashMapCache;
import org.qcode.qskinloader.entity.SkinConstant;
/**
* 基于资源名称后缀拼接方式的资源管理类
* qqliu
* 2016/10/9.
*/
public class SuffixResourceManager implements IResourceManager {
private static final String TAG = "SuffixResourceManager";
private Context mContext;
private Resources mResources;
private String mSkinSuffix;
private String mPackageName;
private HashMapCache<String, Integer> mColorCache
= new HashMapCache<String, Integer>(true);
public SuffixResourceManager(Context context, String skinSuffix) {
mContext = context;
mPackageName = mContext.getPackageName();
mResources = mContext.getResources();
mSkinSuffix = skinSuffix;
}
@Override
public void setBaseResource(String skinIdentifier, IResourceManager baseResource) {
//do nothing
}
@Override
public String getSkinIdentifier() {
return mSkinSuffix;
}
@Override
public boolean isDefault() {
return false;
}
@Override
public int getColor(int resId) {
String resName = mResources.getResourceEntryName(resId);
return getColor(resId, resName);
}
@Override
public int getColor(int resId, String resName) {
Integer color = mColorCache.getCache(resName);
if (null != color) {
return color;
}
String trueResName = appendSuffix(resName);
int trueResId = mResources.getIdentifier(
trueResName,
SkinConstant.RES_TYPE_NAME_COLOR,
mPackageName);
int trueColor = mResources.getColor(trueResId);
mColorCache.addCache(trueResName, trueColor);
return trueColor;
}
public Drawable getDrawable(int resId) {
String resName = mResources.getResourceEntryName(resId);
return getDrawable(resId, resName);
}
@SuppressLint("NewApi")
public Drawable getDrawable(int resId, String resName) {
String trueResName = appendSuffix(resName);
int trueResId = mResources.getIdentifier(
trueResName,
SkinConstant.RES_TYPE_NAME_DRAWABLE,
mPackageName);
if (0 == trueResId) {
trueResId = mResources.getIdentifier(
trueResName,
SkinConstant.RES_TYPE_NAME_MIPMAP,
mPackageName);
if (0 == trueResId) {
throw new Resources.NotFoundException(resName);
}
}
Drawable trueDrawable;
if (android.os.Build.VERSION.SDK_INT < 22) {
trueDrawable = mResources.getDrawable(trueResId);
} else {
trueDrawable = mResources.getDrawable(trueResId, null);
}
return trueDrawable;
}
@Override
public ColorStateList getColorStateList(int resId) {
String resName = mResources.getResourceEntryName(resId);
return getColorStateList(resId, resName);
}
/**
* 加载指定资源颜色drawable,转化为ColorStateList,保证selector类型的Color也能被转换。
* 无皮肤包资源返回默认主题颜色
*
* @param resId
* @return
*/
@Override
public ColorStateList getColorStateList(int resId, String resName) {
return getColorStateList(resId, SkinConstant.RES_TYPE_NAME_COLOR, resName);
}
@Override
public ColorStateList getColorStateList(int resId, String typeName, String resName) {
String trueResName = appendSuffix(resName);
int trueResId = mResources.getIdentifier(
trueResName,
typeName,
mPackageName);
ColorStateList colorList = mResources.getColorStateList(trueResId);
return colorList;
}
private String appendSuffix(String resName) {
return resName + mSkinSuffix;
}
}
================================================
FILE: QSkinLoaderlib/src/main/java/org/qcode/qskinloader/view/ShadowImageView.java
================================================
package org.qcode.qskinloader.view;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffColorFilter;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.widget.ImageView;
import java.lang.ref.WeakReference;
/**
* 支持蒙层的ImageView,建议使用此类作为蒙层处理的ImageView的替代,
* 给ImageView设置ColorFilter可能会失败,此类为drawable设置蒙层,效果更好点。
*
* qqliu
* 2016/9/28.
*/
public class ShadowImageView extends ImageView {
private PorterDuffColorFilter mColorFilter;
private boolean hasFilterSet = true;
private WeakReference<Drawable> mSetFilteredDrawable = null;
public ShadowImageView(Context context) {
this(context, null);
}
public ShadowImageView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public ShadowImageView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public void setShadowColor(int color) {
hasFilterSet = false;
mColorFilter = new PorterDuffColorFilter(color, PorterDuff.Mode.MULTIPLY);
invalidate();
}
@Override
protected void onDraw(Canvas canvas) {
Drawable drawable = getDrawable();
if(null != mSetFilteredDrawable && drawable != mSetFilteredDrawable.get()) {
//drawable 发生了变化,重新设置filter
hasFilterSet = false;
}
if(!hasFilterSet) {
if (null != drawable) {
drawable.setColorFilter(mColorFilter);
mSetFilteredDrawable = new WeakReference<Drawable>(drawable);
hasFilterSet = true;
}
}
super.onDraw(canvas);
}
}
================================================
FILE: QSkinLoaderlib/src/main/res/values/skin_attrs.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- 设置此属性标识View支持换肤;this tag indicates a view supports skin change-->
<attr name="enable" format="boolean" />
<!-- 设置此属性标识View需要绘制阴影; this tag indicates a view should draw a shadow when skin changing-->
<attr name="drawShadow" format="color|reference" />
</resources>
================================================
FILE: QSkinLoaderlib/src/main/res/values/skin_ids.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!--皮肤属性在View内存放时的Tag id; the tag id used to save skin attrs in view's tag-->
<item name="tag_skin_attr" type="id"/>
</resources>
================================================
FILE: QSkinLoaderlib/src/main/res/values/strings.xml
================================================
<resources>
<string name="app_name">QSkinLoaderLib</string>
</resources>
================================================
FILE: README.md
================================================
# QSkinLoader换肤框架
**如何在一个成熟的应用内换肤?** 请参见文章:[链接](http://blog.csdn.net/u013478336/article/details/78993969)。
**README分三部分:基本简介、使用方法、框架由来与架构设计。
如果不嫌麻烦,还可以去看文章[夜间模式方案调研](http://blog.csdn.net/u013478336/article/details/52484322)和[QSkinLoader框架介绍](http://blog.csdn.net/u013478336/article/details/53083054)**
#**效果图**

#**基本简介:**
QSkinLoader是一个支持多种场景的Android换肤框架。基本原理是通过代理LayoutInflater的View创建过程解析皮肤相关属性(background/src/textColor等),将皮肤相关属性设置到View的Tag内,在切换皮肤时寻找对应的皮肤来完成实时刷新动作。此方案具有代码及XML侵入性小、功能完善(支持Activity/Dialog/悬浮窗/PopWindow等)、无需重启Activity、支持自定义属性换肤、同时支持资源内换肤和独立资源包(下载后换肤)等优点。
#**使用方法**
## 基本使用
由于可以自定义皮肤资源加载过程,QSkinLoader框架内并未提供当前皮肤的保存逻辑(不能支持loadCurrentSkin之类的接口)。因此建议使用框架时封装两个类:一个负责保存当前皮肤(保存皮肤其实就是SharePreference持久化存储,此处略去),一个负责与框架交互,如下:
```Java
public void init(Context context) {
SkinManager.getInstance().init(context);
}
public void switchSkinMode(OnSkinChangeListener listener) {
mIsSwitching = true;
mIsDefaultMode = !mIsDefaultMode;
refreshSkin(listener);
}
public void refreshSkin(OnSkinChangeListener listener) {
if (mIsDefaultMode) {
//恢复到默认皮肤
SkinManager.getInstance().restoreDefault(SkinConstant.DEFAULT_SKIN, new MyLoadSkinListener(listener));
} else {
changeSkin(listener);
}
}
private void changeSkin(OnSkinChangeListener listener) {
SkinManager.getInstance().loadSkin("_night",
new SuffixResourceLoader(mContext),
new MyLoadSkinListener(listener));
}
```
具体代码此处不完全贴出了,工程内有详细的代码。
###1. 框架初始化
在Application创建过程中执行框架的初始化:
```Java
// 初始化皮肤框架
SkinChangeHelper.getInstance().init(this);
//初始化上次缓存的皮肤
SkinChangeHelper.getInstance().refreshSkin(null);
```
初始化了框架后需要调用refreshSkin来加载上一次的皮肤设置,refreshSkin加载完成皮肤后会通知各Activity界面刷新皮肤设置,由于此处在Application初始化时调用,可能加载完成皮肤设置后界面仍未初始化,这并不无影响,因为Activity初始化时会主动执行一次换肤操作,弥补此过程的缺失。
###2. Activity初始化与各生命周期调用
因为换肤一般是整个应用都需要执行的过程,此处建议实现一个基础类(BaseActivity)来封装换肤相关逻辑,此类建议实现接口ISkinActivity,告知是否支持换肤,以及在换肤操作触发后如果界面不在前台是否立刻换肤:
```Java
@Override
public boolean isSupportSkinChange() {
//告知当前界面是否支持换肤:true支持换肤,false不支持
return true;
}
@Override
public boolean isSwitchSkinImmediately() {
//告知当切换皮肤时,是否立刻刷新当前界面;true立刻刷新,false表示在界面onResume时刷新;
//减轻换肤时性能压力
return false;
}
@Override
public void handleSkinChange() {
//当前界面在换肤时收到的回调,可以在此回调内做一些其他事情;
//比如:通知WebView内的页面切换到夜间模式等
}
```
然后在Activity的onCreate中执行IActivitySkinEventHandler的初始化与配置工作:
```Java
//初始化并配置IActivitySkinEventHandler,应在IActivitySkinEventHandler.onCreate之前执行
mSkinEventHandler = SkinManager.newActivitySkinEventHandler()
.setSwitchSkinImmediately(isSwitchSkinImmediately())
.setSupportSkinChange(isSupportSkinChange())
.setWindowBackgroundResource(getWindowBackgroundResource())
.setNeedDelegateViewCreate(false);
//通知框架onCreate事件
mSkinEventHandler.onCreate(this);
```
其中,**setWindowBackgroundResource**用于设置Activity的背景色,在换肤时会寻找对应的背景色替换之,此处传入的不能是色值,只支持引用,类似R.color.xx。
**setNeedDelegateViewCreate**用于设置是否需要代理View创建,因为LayoutInflater的代理View创建Factory只支持设置一次,如果外部已经设置了Factory,则框架内再次设置会引起崩溃,所以框架使用配置与回调来处理此问题。具体见高级使用部分。
其他生命周期回调基本类似,挑两个做实例,如下:
```Java
@Override
protected void onResume() {
super.onResume();
//皮肤相关,此通知放在此处,尽量让子类的view都添加到view树内
if (mFirstTimeApplySkin) {
mSkinEventHandler.onViewCreated();
mFirstTimeApplySkin = false;
}
//皮肤相关
mSkinEventHandler.onResume();
}
@Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
//皮肤相关
mSkinEventHandler.onWindowFocusChanged(hasFocus);
}
```
###3. XML配置
QSkinLoader只支持引用型资源换肤,所有的颜色定义都应定义在colors.xml内,在使用时引用。
对于一个布局,需要定义一个skin的命名空间:
```XML
xmlns:skin="http://schemas.android.com/android/skin"
```
然后对所有需要换肤的View增加属性:
```XML
skin:enable="true"
```
即可完成换肤配置。举例如下:
```XML
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:skin="http://schemas.android.com/android/skin"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="match_parent"
skin:enable="true"
android:gravity="center_vertical"
android:background="@color/color_background">
<ImageView
android:layout_width="50dp"
android:layout_height="50dp"
android:src="@mipmap/ic_launcher"/>
<TextView
android:id="@+id/list_item_text_view"
android:layout_width="100dp"
android:layout_height="50dp"
android:textColor="@color/color_text"
skin:enable="true"/>
</LinearLayout>
```
在这段布局内,框架代理创建Linearlayout时会解析其background属性,代理创建View时不解析任何属性,代理创建TextView时会解析textColor属性。
###4. 图片蒙层
对ImageView/ImageButton可以配置属性:
```XML
skin:drawShadow="@color/night_shadow_color"
```
来支持图片蒙层,night_shadow_color是一个颜色引用,在默认情况下建议使用透明色,同时在皮肤包内定义此值为另一个色值(不必须是半透明色)。
需要注意的是:蒙层的原理是ImageView的ColorFilter,有时候对ImageView设置ColorFilter会失效。但是对Drawable设置ColorFilter基本都会生效,所以如果是对ImageView的Src属性做蒙层,建议使用框架内的ShadowImageView替代ImageView。如下:
```XML
<org.qcode.qskinloader.view.ShadowImageView
android:id="@+id/logo"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
skin:enable="true"
skin:drawShadow="@color/news_pic_night_shadow_color"/>
```
###5. 换肤与恢复默认皮肤
**先来看看换肤操作:**
```Java
SkinManager.getInstance().loadSkin("_night",
new SuffixResourceLoader(mContext),
new MyLoadSkinListener(listener));
```
这是基于资源后缀的换肤方式,对于R.color.color_text,切换到夜间模式时,框架会去找R.color.color_text_night作为夜间模式的资源。
QSkinLoader换肤框架还支持另一种默认的换肤方式——APK资源换肤,也就是将资源文件定义在独立的APK文件内,此文件可从服务端下载,从而真正实现动态换肤。此方式对现有工程的影响比较小,非常值得推荐。具体方式如下:
```Java
SkinUtils.copyAssetSkin(mContext);
File skin = new File(SkinUtils.getTotalSkinPath(mContext));
SkinManager.getInstance().loadAPKSkin(
skin.getAbsolutePath(), new MyLoadSkinListener(listener));
```
当然也可以写成:
```Java
SkinManager.getInstance().loadSkin(skin.getAbsolutePath(),
new APKResourceLoader(mContext),
new MyLoadSkinListener(listener));
```
此时对于资源R.color.color_text,框架会去skin路径的APK文件内寻找对于的资源R.color.color_text,找不到就继续使用当前应用的color_text资源。
自定义皮肤加载过程见高级使用部分。
**那么怎么恢复默认皮肤呢?**
```Java
//恢复到默认皮肤
SkinManager.getInstance().restoreDefault(SkinConstant.DEFAULT_SKIN,
new MyLoadSkinListener(listener));
```
DEFAULT_SKIN值对框架而言并无意义,框架只是把此值回调到ILoadSkinListener使外部知道当前加载的是默认皮肤,所以此值是在**框架外**定义的。
###6. 动态创建View的皮肤设置
上文中指出的使用方式是基于XML配置的,如果是在Java代码内如何使用呢?
QSkinLoader框架提供了一个帮助类ISkinViewHelper来添加/删除View的皮肤属性。此类设计为链式编程方式,提供的接口有:
```Java
ISkinViewHelper setViewAttrs(String attrName, int resId);
ISkinViewHelper setViewAttrs(DynamicAttr... dynamicAttrs);
ISkinViewHelper setViewAttrs(SkinAttr... skinAttrs);
ISkinViewHelper setViewAttrs(List<DynamicAttr> dynamicAttrs);
ISkinViewHelper addViewAttrs(String attrName, int resId);
ISkinViewHelper addViewAttrs(DynamicAttr... dynamicAttrs);
ISkinViewHelper addViewAttrs(SkinAttr... skinAttrs);
ISkinViewHelper addViewAttrs(List<DynamicAttr> dynamicAttrs);
ISkinViewHelper cleanAttrs(boolean clearChild);
void applySkin(boolean applyChild);
```
如果View本身已经有了皮肤属性,setViewAttrs接口会替换已有的皮肤属性,而addViewAttrs不会覆盖已有属性,而是在已有的皮肤属性内添加新的属性。
cleanAttrs会清除View的所有皮肤属性,如果传入clearChild为true则遍历所有子元素清除皮肤属性,false只清除自身属性。
applySkin则对当前View应用皮肤设置,如果传入applyChild为true则遍历所有子元素应用皮肤,false只应用自身。
假设对一个TextView,动态设置View的皮肤属性大致如下:
```Java
SkinManager
.with(textview)
.setViewAttrs(SkinAttrName.BACKGROUND, R.color.white)
.addViewAttrs(SkinAttrName.TEXT_COLOR, R.color.black)
.applySkin(false);
```
所有框架支持的属性名称都定义在SkinAttrName内,如果需要扩展属性支持,建议参考自定义View属性处理器部分。
###7. 特定View的换肤管理
上面的换肤过程都是对Activity的View树做遍历换肤操作的,树根是:
```Java
activity.findViewById(android.R.id.content);
```
所有不在这颗树内的View都不能换肤,哪些View不在换肤范围呢?
Dialog的View、popWindow的View、悬浮窗(WindowManager上直接加View),目前这三类View要换肤都应该使用特定View的换肤管理模块。
需要注意的是:Dialog的交互具有排他型,通常在换肤操作时是不展示的,所以一般可以在show接口调用时做换肤,而不使用IWindowViewManager。
**怎么对特定View进行换肤管理呢?**
框架提供了IWindowViewManager接口来提供特定View的管理,支持链式编程,接口如下:
```Java
IWindowViewManager addWindowView(View view);
IWindowViewManager removeWindowView(View view);
IWindowViewManager clear();
void applySkinForViews(boolean applyChild);
List<View> getWindowViewList();
```
接口比较简单,主要是增加/删除/清空全局View列表和应用皮肤的操作。
使用如下:
```Java
View popView = LayoutInflater.from(mContext).inflate(
R.layout.news_list_item_pop, null);
SkinManager.getInstance().applySkin(popView, true);
SkinManager
.getWindowViewManager()
.addWindowView(popView);
popupWindow = new PopupWindow(popView, popWidth, popHeight);
```
通常不建议使用applySkinForViews接口,因为它会遍历所有全局View列表的View做遍历,所以替代方式是先对当前View做属性设置,再添加到框架内管理,从而在下次换肤时接口换肤事件。
IWindowViewManager内的View是弱引用存储的,所以不会发生内存泄露,但建议在View无用的时候从框架内移除特定View。
## 高级使用
###1. 自定义View属性处理器
当项目需要自定义View时,一般都会自定义一些属性,这些属性框架是不支持换肤的,此时需要自定义属性处理器并注册到框架内。自定义属性处理器实现接口ISkinAttrHandler,实现方法:
```Java
void apply(View view,
SkinAttr skinAttr,
IResourceManager resourceManager);
```
下面是一个示例:
若有一个自定义CustomTextView,使用属性defTextColor来定义文字颜色,如下:
```XML
<org.qcode.demo.ui.customattr.CustomTextView
android:layout_width="match_parent"
android:layout_height="50dp"
android:gravity="center"
android:text="自定义的文字颜色和背景1"
app:defBackground="@color/color_background"
app:defTextColor="@color/color_text"
skin:enable="true" />
```
则其自定义属性处理器为:
```Java
public class DefTextColorAttrHandler implements ISkinAttrHandler {
public static final String DEF_TEXT_COLOR = "defTextColor";
@Override
public void apply(View view, SkinAttr skinAttr, IResourceManager resourceManager) {
if(!(view instanceof CustomTextView)) {
//防止在错误的View上设置了此属性
return;
}
CustomTextView tv = (CustomTextView) view;
if (RES_TYPE_NAME_COLOR.equals(skinAttr.mAttrValueTypeName)) {
if (SkinConstant.RES_TYPE_NAME_COLOR.equals(
skinAttr.mAttrValueTypeName)) {
try {
//先尝试按照int型颜色解析
int textColor = resourceManager.getColor(
skinAttr.mAttrValueRefId,
skinAttr.mAttrValueRefName);
tv.setTextColor(textColor);
} catch (Resources.NotFoundException ex) {
//不是int型则按照ColorStateList引用来解析
ColorStateList textColor =
resourceManager.getColorStateList(
skinAttr.mAttrValueRefId,
skinAttr.mAttrValueRefName);
tv.setTextColor(textColor);
}
}
}
}
}
```
定义了属性处理器后,再注册到框架内,**注册需要在setContentView之前**:
```Java
SkinManager.getInstance().registerSkinAttrHandler(
DEF_TEXT_COLOR, new DefTextColorAttrHandler());
```
**注意:**自定义属性处理器不一定就是与皮肤相关的属性的处理,也可以是换肤过程中需要对View进行的特定处理。比如RecyclerView换肤的时候要清除内部View缓存(因为其onBindViewHolder不是每次子View显示时都回调),此时,可以定义如下的属性处理器:
```Java
class RecyclerViewClearSubAttrHandler implements ISkinAttrHandler {
@Override
public void apply(View view, SkinAttr skinAttr, IResourceManager resourceManager) {
Field declaredField = view.getDeclaredField("mRecycler");
......
RecyclerView.RecycledViewPool recycledViewPool = recyclerView.getRecycledViewPool();
recycledViewPool.clear();
}
```
此处代码有删减,具体见框架内的RecyclerViewClearSubAttrHandler处理器。使用时如下:
```Java
SkinAttr clearSubAttr = new SkinAttr(SkinAttrName.CLEAR_RECYCLER_VIEW);
SkinManager
.with(view)
.addViewAttrs(clearSubAttr);
```
###2. 自定义皮肤资源加载
框架默认支持**资源名称后缀加载、APK加载、Android UIMode Configuration变化**三种换肤方式。集成方式如下:
```Java
//后缀加载
SkinManager.getInstance().loadSkin("_night",
new SuffixResourceLoader(mContext),
new MyLoadSkinListener(listener));
//APK皮肤包
SkinManager.getInstance().loadAPKSkin(
skin.getAbsolutePath(),
new MyLoadSkinListener(listener));
//Android UI Configuration变化
SkinManager.getInstance().loadSkin(
ConfigChangeResourceLoader.MODE_NIGHT,
new ConfigChangeResourceLoader(mContext),
new MyLoadSkinListener(listener));
```
如果项目准备采用其他的加载方式,可以通过自定义皮肤资源加载过程来实现。自定义皮肤资源加载的核心是实现IResourceLoader接口,接口只有一个方法:
```Java
void loadResource(String skinIdentifier,
ILoadResourceCallback loadCallBack);
```
也就是定义了从皮肤标识符skinIdentifier加载资源,并在加载过程中通过loadCallBack对外通知加载过程:
```Java
public interface ILoadResourceCallback {
void onLoadStart(String identifier);
void onLoadSuccess(String identifier, IResourceManager result);
void onLoadFail(String identifier, int errorCode);
}
```
加载开始和失败没啥可说的,主要是加载完成后,需要返回一个资源管理类IResourceManager。这个类定义了如何从指定的换肤流程中抽取对应的皮肤资源:
```Java
public interface IResourceManager {
String getSkinIdentifier();
Drawable getDrawable(int resId, String resName) throws Resources.NotFoundException;
int getColor(int resId, String resName) throws Resources.NotFoundException;
ColorStateList getColorStateList(int resId, String resName) throws Resources.NotFoundException;
}
```
整个过程比较简单,自定义一个加载过程,再返回一个资源管理类即可。下面以后缀资源加载的方式做个示例(摘录部分代码,具体见工程):
```Java
public class SuffixResourceLoader implements IResourceLoader {
private String mSkinSuffix;
@Override
public void loadResource(final String skinIdentifier,
final ILoadResourceCallback loadCallBack) {
//通知加载开始
loadCallBack.onLoadStart(skinIdentifier);
//后缀存下,加载过程就结束了,不像apk加载,还需要操作AssetManager
mSkinSuffix = skinIdentifier;
//通知加载结束,返回一个资源管理类SuffixResourceManager
loadCallBack.onLoadSuccess(skinIdentifier,
new SuffixResourceManager(mContext, mSkinSuffix));
}
}
```
```Java
public class SuffixResourceManager implements IResourceManager {
private Context mContext;
private Resources mResources;
private String mSkinSuffix;
private String mPackageName;
private HashMapCache<String, Integer> mColorCache
= new HashMapCache<String, Integer>(true);
public SuffixResourceManager(Context context, String skinSuffix) {
mContext = context;
mPackageName = mContext.getPackageName();
mResources = mContext.getResources();
mSkinSuffix = skinSuffix;
}
@Override
public int getColor(int resId, String resName) {
String trueResName = resName + mSkinSuffix;
//找到名字+后缀的id,读取颜色
int trueResId = mResources.getIdentifier(
trueResName,
SkinConstant.RES_TYPE_NAME_COLOR,
mPackageName);
int trueColor = mResources.getColor(trueResId);
return trueColor;
}
......
}
```
###3.解决与其他代理View创建过程的冲突
上文也简要的提到了此问题,对每个Activity的LayoutInflater的setFactory接口(代理View创建与属性解析)只能调用一次,而换肤框架是依赖此操作来完成皮肤属性解析的,因此我们需要设计一套方案在确保框架外已经代理了LayoutInflater后还能保证换肤功能的可用性。
我们需要保证两点:
- 如果框架外需要代理View创建,则框架应被告知不能代理View创建,并且提供一个帮助类在外部创建View创建时完成属性解析;
- 如果框架外不需要代理View创建,但需要解析属性,则提供接口在View创建前后对外回调;
对于第一点,可以通过IActivitySkinEventHandler.setNeedDelegateViewCreate来告知框架不代理View创建,解析属性的帮助类也可以从IActivitySkinEventHandler内取到,如下:
```Java
LayoutInflater.from(this).setFactory(new LayoutInflater.Factory() {
@Override
public View onCreateView(String name, Context context, AttributeSet attrs) {
View view = createView(name, context, attrs);
//创建View后通知框架解析属性
ISkinAttributeParser parser =
mSkinEventHandler.getSkinAttributeParser();
if (parser.isSupportSkin(name, context, attrs)) {
parser.parseAttribute(view, name, context, attrs);
}
return view;
}
});
```
核心代码就是这段解析属性的逻辑。
对于第二点,我们提供接口IViewCreateListener来监听View创建过程:
```Java
public interface IViewCreateListener {
View beforeCreate(String name, Context context, AttributeSet attrs);
void afterCreated(View view, String name, Context context, AttributeSet attrs);
}
```
beforeCreate在View创建之前执行,可以拦截框架的View创建过程,自己创建View,afterCreated在框架创建View后执行,用于框架外进一步处理。
此接口应通过IActivitySkinEventHandler.setViewCreateListener()设置到框架内使用。
##- 各种View的换肤应用
###1. ViewPager
ViewPager使用时,应在PagerAdapter的instantiateItem回调中对创建的View应用当前的皮肤。
```Java
mViewPager.setAdapter(new PagerAdapter() {
......
@Override
public Object instantiateItem(ViewGroup container, int position) {
View view = onCreatePagerView(position);
container.addView(view);
//每次实例化某个View时都对其应用皮肤设置
SkinManager.with(view).applySkin(true);
return view;
}
});
```
###2. ListView/GridView
ListView/GridView都继承AbsListView,并使用BaseAdapter作为适配器,其换肤方法为:
```Java
listView.setAdapter(new BaseAdapter() {
@Override
public View getView(int position, View convertView, ViewGroup parent) {
if(null == convertView) {
convertView = onCreateContentView(position);
}
//每次某个子元素需要展示时,都应用当前皮肤设置
SkinManager.with(convertView).applySkin(true);
return convertView;
}
});
```
需要注意的是,如果ListView存在HeaderView或FooterView时,只使用上面的方法是不完善的,如果换肤时HeaderView/FooterView不在ListView内展示,则换肤失效,此时应调用ListView.mRecycler.clear()方法清除View缓存,具体见[上一篇文章](http://blog.csdn.net/u013478336/article/details/52484322)。
###3. RecyclerView
上一章也大致讲了RecyclerView换肤的注意事项,由于RecyclerView滑动时,其子元素出现的过程不一定会伴有onBindViewHolder回调,导致我们有时出现两种皮肤并存的问题。因此,使用RecyclerView时换肤一定要清除RecyclerView的缓存。
```Java
recyclerView.setAdapter(new RecyclerView.Adapter() {
......
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
//此回调非必执行,但是执行时还是要应用皮肤设置
SkinManager.with(holder.itemView).applySkin(true);
}
});
```
为了应对RecyclerView清除缓存的问题,框架内定义了一个特殊的属性处理器RecyclerViewClearSubAttrHandler,其作用就是在换肤时,清除RecyclerView内的View缓存。具体使用方式如下:
```Java
SkinManager
.with(recyclerView)
.addViewAttrs(new SkinAttr(SkinAttrName.CLEAR_RECYCLER_VIEW));
```
#附录
此框架脱胎于项目需要实现夜间模式的需求,在[文章](http://blog.csdn.net/u013478336/article/details/52484322)中,我们列举了常见的几种实现夜间模式切换的方案,并大致对比了一下各种方案的优缺点,此处不再一一列举。仅大致摘录夜间模式的需求分析如下:
>夜间模式需要对屏幕上的文字/图片/视频三种表现形式做特殊处理,具体细化如下:
>**1)对界面背景,**白色等浅色背景应该变成黑色/灰色之类的深色背景,以此降低屏幕亮度减少视觉刺激;
>**2)对文字,**因背景色变深,文字颜色需变浅,以形成对比效果;
>**3)对图片,**对图片加蒙层,避免加载浅色图片带来的视觉刺激;
>**4)对视频,**通常在播放界面增加亮度变化功能,由用户来决定屏幕亮度。
具体技术选型与框架设计可见文章:http://blog.csdn.net/u013478336/article/details/53083054
================================================
FILE: SkinProject/.gitignore
================================================
*.iml
.gradle
/local.properties
/.idea/workspace.xml
/.idea/libraries
.DS_Store
/build
/captures
.externalNativeBuild
================================================
FILE: SkinProject/app/.gitignore
================================================
/build
================================================
FILE: SkinProject/app/build.gradle
================================================
apply plugin: 'com.android.application'
android {
compileSdkVersion 23
buildToolsVersion "23.0.1"
defaultConfig {
applicationId "org.qcode.skinproject"
minSdkVersion 14
targetSdkVersion 23
versionCode 1
versionName "1.0"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
exclude group: 'com.android.support', module: 'support-annotations'
})
compile 'com.android.support:appcompat-v7:23.2.1'
testCompile 'junit:junit:4.12'
}
================================================
FILE: SkinProject/app/proguard-rules.pro
================================================
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in E:\Program Files\Android\android-sdk/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the proguardFiles
# directive in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# Add any project specific keep options here:
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
================================================
FILE: SkinProject/app/src/main/AndroidManifest.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.qcode.sinproject">
<application>
<activity android:name="org.qcode.skinproject.MainActivity" >
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
</application>
</manifest>
================================================
FILE: SkinProject/app/src/main/java/org/qcode/skinproject/MainActivity.java
================================================
package org.qcode.skinproject;
import android.app.Activity;
/**
* qqliu
* 2016/11/9.
*/
public class MainActivity extends Activity {
}
================================================
FILE: SkinProject/app/src/main/res/drawable/btn_bg.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<corners android:radius="4dp"/>
<solid android:color="@color/color_white"/>
<stroke android:width="1dp" android:color="@color/gray_50"/>
</shape>
================================================
FILE: SkinProject/app/src/main/res/drawable/drawable_float_view.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<size android:width="50dp"
android:height="50dp"/>
<solid android:color="@color/color_red"/>
</shape>
================================================
FILE: SkinProject/app/src/main/res/drawable/news_item_selector.xml
================================================
<?xml version = "1.0" encoding = "utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@color/color_item_press_bg" android:state_pressed="true" android:state_focused="true"/>
<item android:drawable="@color/color_item_normal_bg"/>
</selector>
================================================
FILE: SkinProject/app/src/main/res/values/colors.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="gray_50">#84878c</color>
<color name="color_white">#000000</color>
<color name="color_text">#FFFFFF</color>
<color name="color_background">#000000</color>
<color name="color_item_normal_bg">#000000</color>
<color name="color_item_press_bg">#333333</color>
<color name="activity_bg_color">#1c1d20</color>
<color name="color_red">#00FF00</color>
<color name="night_shadow_color">#8e8e8e</color>
</resources>
================================================
FILE: SkinProject/build.gradle
================================================
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:2.2.0'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
allprojects {
repositories {
jcenter()
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}
================================================
FILE: SkinProject/gradle.properties
================================================
## Project-wide Gradle settings.
#
# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html
#
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
# Default value: -Xmx1024m -XX:MaxPermSize=256m
# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
#
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true
#Wed Nov 09 09:21:50 CST 2016
systemProp.http.proxyHost=127.0.0.1
org.gradle.jvmargs=-Xmx1536m
systemProp.http.proxyPort=1080
================================================
FILE: SkinProject/gradlew
================================================
#!/usr/bin/env bash
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn ( ) {
echo "$*"
}
die ( ) {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
esac
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=$((i+1))
done
case $i in
(0) set -- ;;
(1) set -- "$args0" ;;
(2) set -- "$args0" "$args1" ;;
(3) set -- "$args0" "$args1" "$args2" ;;
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
function splitJvmOpts() {
JVM_OPTS=("$@")
}
eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
================================================
FILE: SkinProject/gradlew.bat
================================================
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windowz variants
if not "%OS%" == "Windows_NT" goto win9xME_args
if "%@eval[2+2]" == "4" goto 4NT_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
goto execute
:4NT_args
@rem Get arguments from the 4NT Shell from JP Software
set CMD_LINE_ARGS=%$
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega
================================================
FILE: SkinProject/settings.gradle
================================================
include ':app'
================================================
FILE: app/.gitignore
================================================
/build
================================================
FILE: app/build.gradle
================================================
apply plugin: 'com.android.application'
android {
compileSdkVersion 23
buildToolsVersion "23.0.1"
defaultConfig {
applicationId "org.qcode.qskinloader"
minSdkVersion 14
targetSdkVersion 23
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
exclude group: 'com.android.support', module: 'support-annotations'
})
compile 'com.android.support:appcompat-v7:23.2.1'
testCompile 'junit:junit:4.12'
compile project(':QSkinLoaderlib')
}
================================================
FILE: app/proguard-rules.pro
================================================
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in E:\Program Files\Android\android-sdk/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the proguardFiles
# directive in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# Add any project specific keep options here:
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
================================================
FILE: app/src/main/AndroidManifest.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.qcode.skintestdemo">
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
<application
android:name="org.qcode.demo.SkinDemoApp"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true">
<activity android:name="org.qcode.demo.MainActivity"
android:theme="@style/ActivityTheme">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name="org.qcode.demo.ui.viewpageandlistview.ViewPagerAndListViewActivity"
android:label="RecyclerView" />
<activity
android:name="org.qcode.demo.ui.recyclerview.RecyclerViewActivity"
android:label="RecyclerView" />
<activity
android:name="org.qcode.demo.ui.dynamicaddview.DynamicAddViewActivity"
android:label="DynamicAddView" />
<activity
android:name="org.qcode.demo.ui.gridview.GridViewActivity"
android:label="GridView" />
<activity
android:name="org.qcode.demo.ui.customattr.CustomAttrViewActivity"
android:label="SelfDefineView" />
<activity
android:name="org.qcode.demo.ui.otherscene.OtherSceneActivity"
android:label="OtherScene"
android:theme="@style/TransparentTheme"/>
</application>
</manifest>
================================================
FILE: app/src/main/java/org/qcode/demo/BaseActivity.java
================================================
package org.qcode.demo;
import android.app.Activity;
import android.content.Context;
import android.graphics.PixelFormat;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import org.qcode.demo.utils.UIUtil;
import org.qcode.qskinloader.IActivitySkinEventHandler;
import org.qcode.qskinloader.ISkinActivity;
import org.qcode.qskinloader.SkinManager;
import org.qcode.skintestdemo.R;
/**
* 所有Activity的父类;需要实现ISkinActivity接口
*/
public abstract class BaseActivity extends Activity implements ISkinActivity {
private IActivitySkinEventHandler mSkinEventHandler;
private boolean mFirstTimeApplySkin = true;
private FrameLayout mContentContainer;
private SkinChangeSwitchView mSwitchView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
getWindow().setFormat(PixelFormat.RGBA_8888);
mSkinEventHandler = SkinManager.newActivitySkinEventHandler()
.setSwitchSkinImmediately(isSwitchSkinImmediately())
.setSupportSkinChange(isSupportSkinChange())
.setWindowBackgroundResource(getWindowBackgroundResource())
.setNeedDelegateViewCreate(true);
mSkinEventHandler.onCreate(this);
initView(this);
}
private void initView(Context context) {
LinearLayout root = new LinearLayout(context);
root.setOrientation(LinearLayout.VERTICAL);
super.setContentView(root);
LinearLayout.LayoutParams paramTitle = new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
mSwitchView = new SkinChangeSwitchView(context);
int padding = UIUtil.dip2px(context, 15);
mSwitchView.setPadding(padding, padding, padding, padding);
root.addView(mSwitchView, paramTitle);
LinearLayout.LayoutParams paramContent = new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, 0);
paramContent.weight = 1;
mContentContainer = new FrameLayout(context);
root.addView(mContentContainer, paramContent);
}
@Override
public void setContentView(int layoutResID) {
LayoutInflater.from(this).inflate(layoutResID, mContentContainer);
}
@Override
public void setContentView(View view) {
FrameLayout.LayoutParams param = new FrameLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
mContentContainer.addView(view, param);
}
@Override
protected void onStart() {
super.onStart();
mSkinEventHandler.onStart();
}
@Override
protected void onResume() {
super.onResume();
//皮肤相关,此通知放在此处,尽量让子类的view都添加到view树内
if (mFirstTimeApplySkin) {
mSkinEventHandler.onViewCreated();
mFirstTimeApplySkin = false;
}
mSwitchView.refreshSwitch();
mSkinEventHandler.onResume();
}
@Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
//皮肤相关
mSkinEventHandler.onWindowFocusChanged(hasFocus);
}
@Override
protected void onPause() {
super.onPause();
mSkinEventHandler.onPause();
}
@Override
protected void onStop() {
super.onStop();
mSkinEventHandler.onStop();
}
@Override
protected void onDestroy() {
super.onDestroy();
//皮肤相关
mSkinEventHandler.onDestroy();
}
@Override
public boolean isSupportSkinChange() {
//告知当前界面是否支持换肤:true支持换肤,false不支持
return true;
}
@Override
public boolean isSwitchSkinImmediately() {
//告知当切换皮肤时,是否立刻刷新当前界面;true立刻刷新,false表示在界面onResume时刷新;
//减轻换肤时性能压力
return false;
}
@Override
public void handleSkinChange() {
//当前界面在换肤时收到的回调,可以在此回调内做一些其他事情;
//比如:通知WebView内的页面切换到夜间模式等
}
/**
* 告知当前界面Window的background资源,换肤时会寻找对应的资源替换
*/
protected int getWindowBackgroundResource() {
return R.color.activity_bg_color;
}
}
================================================
FILE: app/src/main/java/org/qcode/demo/MainActivity.java
================================================
package org.qcode.demo;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import org.qcode.demo.ui.customattr.CustomAttrViewActivity;
import org.qcode.demo.ui.dynamicaddview.DynamicAddViewActivity;
import org.qcode.demo.ui.gridview.GridViewActivity;
import org.qcode.demo.ui.otherscene.OtherSceneActivity;
import org.qcode.demo.ui.recyclerview.RecyclerViewActivity;
import org.qcode.demo.ui.viewpageandlistview.ViewPagerAndListViewActivity;
import org.qcode.skintestdemo.R;
/**
* qqliu
* 2016/10/10.
*/
public class MainActivity extends BaseActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void onClick(View view) {
Intent intent = new Intent();
switch (view.getId()) {
case R.id.btnCustomView:
intent.setClass(this, CustomAttrViewActivity.class);
break;
case R.id.btnDynamicAddView:
intent.setClass(this, DynamicAddViewActivity.class);
break;
case R.id.btnRecyclerView:
intent.setClass(this, RecyclerViewActivity.class);
break;
case R.id.btnViewPagerAndListView:
intent.setClass(this, ViewPagerAndListViewActivity.class);
break;
case R.id.btnGridView:
intent.setClass(this, GridViewActivity.class);
break;
case R.id.btnOtherScene:
intent.setClass(this, OtherSceneActivity.class);
break;
}
startActivity(intent);
}
}
================================================
FILE: app/src/main/java/org/qcode/demo/SkinChangeSwitchView.java
================================================
package org.qcode.demo;
import android.content.Context;
import android.view.Gravity;
import android.view.View;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.TextView;
import org.qcode.demo.skin.SkinChangeHelper;
import org.qcode.qskinloader.SkinManager;
import org.qcode.qskinloader.entity.SkinAttrName;
import org.qcode.skintestdemo.R;
public class SkinChangeSwitchView extends LinearLayout {
private static final String TAG = "NightModeSettingView";
private ImageView mImgBtnSwitch;
private TextView mTextViewTitle;
public SkinChangeSwitchView(Context context) {
super(context);
initView(context);
}
protected void initView(final Context context) {
setOrientation(LinearLayout.HORIZONTAL);
setGravity(Gravity.CENTER);
SkinManager.with(this)
.setViewAttrs(SkinAttrName.BACKGROUND, R.color.color_background)
.applySkin(true);
mTextViewTitle = new TextView(context);
SkinManager.with(mTextViewTitle)
.setViewAttrs(SkinAttrName.TEXT_COLOR, R.color.color_text)
.applySkin(false);
mTextViewTitle.setTextSize(16);
LinearLayout.LayoutParams paramTitlePart = new LinearLayout.LayoutParams(
0, RelativeLayout.LayoutParams.WRAP_CONTENT);
paramTitlePart.weight = 1;
addView(mTextViewTitle, paramTitlePart);
mTextViewTitle.setText("夜间模式");
mImgBtnSwitch = new ImageView(context);
LinearLayout.LayoutParams paramEntryFlag = new LinearLayout.LayoutParams(
RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT);
addView(mImgBtnSwitch, paramEntryFlag);
refreshSwitch();
mImgBtnSwitch.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
SkinChangeHelper.getInstance().switchSkinMode(
new SkinChangeHelper.OnSkinChangeListener() {
@Override
public void onSuccess() {
refreshSwitch();
}
@Override
public void onError() {
refreshSwitch();
}
});
refreshSwitch();
}
});
}
public void refreshSwitch() {
boolean isDefaultMode = SkinChangeHelper.getInstance().isDefaultMode();
boolean isSwitching = SkinChangeHelper.getInstance().isSwitching();
int drawableId;
if (isDefaultMode) {
//mImgBtnSwitch.setImageResource(R.drawable.news_switch_setting_off_nor);
drawableId = R.mipmap.news_switch_setting_off_nor;
} else {
//mImgBtnSwitch.setImageResource(R.drawable.news_switch_setting_on_nor);
drawableId = R.mipmap.news_switch_setting_on_nor;
}
SkinManager.with(mImgBtnSwitch)
.setViewAttrs(SkinAttrName.SRC, drawableId)
.applySkin(false);
mImgBtnSwitch.setEnabled(!isSwitching);
}
}
================================================
FILE: app/src/main/java/org/qcode/demo/SkinDemoApp.java
================================================
package org.qcode.demo;
import android.app.Application;
import android.content.Context;
import org.qcode.demo.base.Settings;
import org.qcode.demo.skin.SkinChangeHelper;
/**
* qqliu
* 2016/9/9.
*/
public class SkinDemoApp extends Application {
private static Context mContext;
public void onCreate() {
super.onCreate();
mContext = this;
Settings.createInstance(this);
initSkinLoader();
}
/**
* Must call init first
*/
private void initSkinLoader() {
// 初始化皮肤框架
SkinChangeHelper.getInstance().init(this);
//初始化上次缓存的皮肤
SkinChangeHelper.getInstance().refreshSkin(null);
}
public static Context getAppContext() {
return mContext;
}
}
================================================
FILE: app/src/main/java/org/qcode/demo/base/Settings.java
================================================
package org.qcode.demo.base;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.text.TextUtils;
import org.qcode.qskinloader.base.utils.Logging;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
public class Settings {
private static final String TAG = "SettingsImpl";
private static final String NAME = "SKinSettings";
private SharedPreferences mSharedPref;
private Settings(Context context) {
int mode = Context.MODE_PRIVATE;
mSharedPref = context.getSharedPreferences(NAME, mode);
}
private static volatile Settings mInstance;
public static Settings createInstance(Context context) {
if(null == mInstance) {
synchronized (Settings.class) {
if(null == mInstance) {
mInstance = new Settings(context);
}
}
}
return mInstance;
}
public static Settings getInstance() {
return mInstance;
}
public boolean isSetted(String key) {
return mSharedPref.contains(key);
}
public void setSetting(String key, boolean value) {
try {
Editor editor = mSharedPref.edit();
editor.putBoolean(key, value);
editor.commit();
} catch (Exception e) {
Logging.e(TAG, "setSetting(" + key + ", " + value + ")", e);
}
}
public void setSetting(String key, int value) {
try {
Editor editor = mSharedPref.edit();
editor.putInt(key, value);
editor.commit();
} catch (Exception e) {
Logging.e(TAG, "setSetting(" + key + ", " + value + ")", e);
}
}
public void setSetting(String key, float value) {
try {
Editor editor = mSharedPref.edit();
editor.putFloat(key, value);
editor.commit();
} catch (Exception e) {
Logging.e(TAG, "setSetting(" + key + ", " + value + ")", e);
}
}
public void setSetting(String key, long value) {
try {
Editor editor = mSharedPref.edit();
editor.putLong(key, value);
editor.commit();
} catch (Exception e) {
Logging.e(TAG, "setSetting(" + key + ", " + value + ")", e);
}
}
public void setSetting(String key, String value) {
if (null != value) {
//要过滤'\0',否则会使XML读取异常
value = value.replace("\0", "");
}
try {
Editor editor = mSharedPref.edit();
editor.putString(key, value);
editor.commit();
} catch (Exception e) {
Logging.e(TAG, "setSetting(" + key + ", " + value + ")", e);
}
}
public boolean getBoolean(String key) {
return getBoolean(key, false);
}
public boolean getBoolean(String key, boolean defaultValue) {
boolean result = defaultValue;
try {
result = mSharedPref.getBoolean(key, result);
} catch (Exception e) {
Logging.e(TAG, "getBoolean()", e);
}
return result;
}
public int getInt(String key) {
return getInt(key, 0);
}
public int getInt(String key, int defaultValue) {
int value = defaultValue;
try {
value = mSharedPref.getInt(key, defaultValue);
} catch (Exception e) {
Logging.e(TAG, "getSetting()", e);
}
return value;
}
public float getFloat(String key) {
return getFloat(key, 0);
}
public float getFloat(String key, float defaultValue) {
float value = defaultValue;
try {
value = mSharedPref.getFloat(key, defaultValue);
} catch (Exception e) {
Logging.e(TAG, "getLongSetting()", e);
}
return value;
}
public long getLong(String key) {
return getLong(key, 0);
}
public long getLong(String key, long defaultValue) {
long value = defaultValue;
try {
value = mSharedPref.getLong(key, defaultValue);
} catch (Exception e) {
Logging.e(TAG, "getLongSetting()", e);
}
return value;
}
public String getString(String key) {
return getString(key, null);
}
public String getString(String key, String defaultValue) {
String value = defaultValue;
try {
value = mSharedPref.getString(key, defaultValue);
} catch (Exception e) {
Logging.e(TAG, "getString()", e);
}
return value;
}
public void saveObject(String fileName, Object object) {
ObjectOutputStream objectOutputStream = null;
try {
File file = new File(fileName);
if (file.exists()) {
file.delete();
}
file.createNewFile();
objectOutputStream = new ObjectOutputStream(new FileOutputStream(file));
objectOutputStream.writeObject(object);
objectOutputStream.flush();
} catch (Exception e) {
Logging.e(TAG, "saveObject()", e);
} finally {
if (objectOutputStream != null) {
try {
objectOutputStream.close();
} catch (IOException e) {
Logging.e(TAG, "saveObject()", e);
}
}
}
}
public Object readObject(String fileName) {
Object object = null;
ObjectInputStream objectInputStream = null;
try {
objectInputStream = new ObjectInputStream(new FileInputStream(fileName));
object = objectInputStream.readObject();
} catch (Exception e) {
Logging.e(TAG, "readObject()" + e);
} finally {
if (objectInputStream != null) {
try {
objectInputStream.close();
} catch (IOException e) {
Logging.e(TAG, "readObject()" + e);
}
}
}
return object;
}
public void clearObject(String fileName) {
try {
File file = new File(fileName);
if (file.exists()) {
file.delete();
Logging.d(TAG, "delete file success");
}
} catch (Exception e) {
Logging.e(TAG, " clearObject()", e);
}
}
public void removeSetting(String key) {
try {
//如果key不为空,把key删掉
if (!TextUtils.isEmpty(key)) {
Editor editor = mSharedPref.edit();
editor.remove(key);
editor.commit();
}
} catch (Exception e) {
Logging.e(TAG, "removeSetting(" + key + ")", e);
}
}
}
================================================
FILE: app/src/main/java/org/qcode/demo/skin/SkinChangeHelper.java
================================================
package org.qcode.demo.skin;
import android.content.Context;
import org.qcode.demo.SkinDemoApp;
import org.qcode.demo.utils.UITaskRunner;
import org.qcode.demo.utils.UIUtil;
import org.qcode.qskinloader.ILoadSkinListener;
import org.qcode.qskinloader.SkinManager;
import org.qcode.qskinloader.resourceloader.impl.ConfigChangeResourceLoader;
import org.qcode.qskinloader.resourceloader.impl.SuffixResourceLoader;
import java.io.File;
/**
* qqliu
* 2016/9/26.
*/
public class SkinChangeHelper {
private static final String TAG = "SkinChangeHelper";
//基于suffix换肤
private static final int TYPE_SUFFIX = 1;
//基于apk换肤
private static final int TYPE_APK = 2;
//基于UIMode换肤
private static final int TYPE_UIMODE = 3;
private static volatile SkinChangeHelper mInstance;
private final Context mContext;
//目前框架支持三种换肤方式,后缀换肤/APK资源包换肤/UIMode换肤
private int mSkinChangeType = TYPE_SUFFIX;
private SkinChangeHelper() {
mContext = SkinDemoApp.getAppContext();
mIsDefaultMode = SkinConfigHelper.isDefaultSkin();
}
public static SkinChangeHelper getInstance() {
if (null == mInstance) {
synchronized (SkinChangeHelper.class) {
if (null == mInstance) {
mInstance = new SkinChangeHelper();
}
}
}
return mInstance;
}
private volatile boolean mIsDefaultMode = false;
private volatile boolean mIsSwitching = false;
public void init(Context context) {
SkinManager.getInstance().init(context);
}
public void switchSkinMode(OnSkinChangeListener listener) {
mIsSwitching = true;
mIsDefaultMode = !mIsDefaultMode;
refreshSkin(listener);
}
public void refreshSkin(OnSkinChangeListener listener) {
if (mIsDefaultMode) {
switch (mSkinChangeType) {
case TYPE_SUFFIX:
case TYPE_APK:
//恢复到默认皮肤
SkinManager.getInstance().restoreDefault(
SkinConstant.DEFAULT_SKIN,
new MyLoadSkinListener(listener));
break;
case TYPE_UIMODE:
//基于UIMode换肤只能通过改回配置才能换肤
changeSkinByConfig(ConfigChangeResourceLoader.MODE_DAY, listener);
break;
}
} else {
switch (mSkinChangeType) {
case TYPE_SUFFIX:
changeSkinBySuffix(listener);
break;
case TYPE_APK:
changeSkinByApk(listener);
break;
case TYPE_UIMODE:
//基于UIMode换肤只能通过改回配置才能换肤
changeSkinByConfig(ConfigChangeResourceLoader.MODE_NIGHT, listener);
break;
}
}
}
public boolean isDefaultMode() {
return mIsDefaultMode;
}
public boolean isSwitching() {
return mIsSwitching;
}
private void changeSkinByApk(OnSkinChangeListener listener) {
SkinUtils.copyAssetSkin(mContext);
File skin = new File(
SkinUtils.getTotalSkinPath(mContext));
if (skin == null || !skin.exists()) {
UIUtil.showToast(mContext, "皮肤初始化失败");
return;
}
SkinManager.getInstance().loadAPKSkin(
skin.getAbsolutePath(), new MyLoadSkinListener(listener));
}
private void changeSkinBySuffix(OnSkinChangeListener listener) {
SkinManager.getInstance().loadSkin("_night",
new SuffixResourceLoader(mContext),
new MyLoadSkinListener(listener));
}
private void changeSkinByConfig(String mode, OnSkinChangeListener listener) {
SkinManager.getInstance().loadSkin(mode,
new ConfigChangeResourceLoader(mContext),
new MyLoadSkinListener(listener));
}
private class MyLoadSkinListener implements ILoadSkinListener {
private final OnSkinChangeListener mListener;
public MyLoadSkinListener(OnSkinChangeListener listener) {
mListener = listener;
}
@Override
public void onLoadStart(String skinIdentifier) {
}
@Override
public void onLoadSuccess(String skinIdentifier) {
mIsSwitching = false;
//存储皮肤标识
SkinConfigHelper.saveSkinIdentifier(skinIdentifier);
UITaskRunner.getHandler().post(new Runnable() {
@Override
public void run() {
if(null != mListener) {
mListener.onSuccess();
}
}
});
}
@Override
public void onLoadFail(String skinIdentifier) {
mIsSwitching = false;
UITaskRunner.getHandler().post(new Runnable() {
@Override
public void run() {
if (null != mListener) {
mListener.onError();
}
}
});
}
};
public interface OnSkinChangeListener {
void onSuccess();
void onError();
}
}
================================================
FILE: app/src/main/java/org/qcode/demo/skin/SkinConfigHelper.java
================================================
package org.qcode.demo.skin;
import org.qcode.demo.base.Settings;
public class SkinConfigHelper {
/***
* 获取当前皮肤包的标识
*/
public static String getSkinIdentifier() {
return Settings.getInstance().getString(
SkinConstant.CUSTOM_SKIN_IDENTIFIER,
SkinConstant.DEFAULT_SKIN);
}
/**
* 保存皮肤包的标识
*/
public static void saveSkinIdentifier(String identifier) {
Settings.getInstance().setSetting(
SkinConstant.CUSTOM_SKIN_IDENTIFIER,
identifier);
}
/**
* 是否默认皮肤
*/
public static boolean isDefaultSkin() {
return SkinConstant.DEFAULT_SKIN.equals(getSkinIdentifier());
}
}
================================================
FILE: app/src/main/java/org/qcode/demo/skin/SkinConstant.java
================================================
package org.qcode.demo.skin;
/**
* qqliu
* 2016/10/8.
*/
public class SkinConstant {
public static final String PACKAGE_NAME = "org.qcode.demo";
/**皮肤标识存放*/
public static final String CUSTOM_SKIN_IDENTIFIER =
PACKAGE_NAME + ".CUSTOM_SKIN_IDENTIFIER";
/**默认皮肤*/
public static final String DEFAULT_SKIN = "default";
}
================================================
FILE: app/src/main/java/org/qcode/demo/skin/SkinUtils.java
================================================
package org.qcode.demo.skin;
import android.content.Context;
import org.qcode.demo.utils.FileUtils;
import org.qcode.qskinloader.base.utils.Logging;
import java.io.File;
/**
* qqliu
* 2016/9/21.
*/
public class SkinUtils {
private static final String TAG = "NightModeUtils";
private static final String SKIN_NAME = "nightMode.skin";
public static String getTotalSkinPath(Context context) {
String SKIN_PATH = context.getCacheDir().getAbsolutePath();
String totalPath = SKIN_PATH + File.separator + SKIN_NAME;
return totalPath;
}
public static boolean copyAssetSkin(Context context) {
String totalPath = getTotalSkinPath(context);
File skin = new File(totalPath);
if (skin == null || !skin.exists() || needUpdateSkin()) {
long currTime = System.currentTimeMillis();
boolean isSuccess = FileUtils.copyAssetFile(context, SKIN_NAME,
context.getCacheDir().getAbsolutePath(),
SKIN_NAME);
long diff = System.currentTimeMillis() - currTime;
Logging.d(TAG, "copyAssetSkin()| copy file time: " + diff);
return isSuccess;
}
return false;
}
private static boolean needUpdateSkin() {
//每次都拷贝皮肤包
return true;
}
}
================================================
FILE: app/src/main/java/org/qcode/demo/ui/customattr/CustomAttrViewActivity.java
================================================
package org.qcode.demo.ui.customattr;
import android.os.Bundle;
import org.qcode.demo.BaseActivity;
import org.qcode.qskinloader.SkinManager;
import org.qcode.skintestdemo.R;
import static org.qcode.demo.ui.customattr.DefBackgroundAttrHandler.DEF_BACKGROUND;
import static org.qcode.demo.ui.customattr.DefTextColorAttrHandler.DEF_TEXT_COLOR;
public class CustomAttrViewActivity extends BaseActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
registerHandler();
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_custom_attr_test);
}
private void registerHandler() {
SkinManager.getInstance().registerSkinAttrHandler(
DEF_TEXT_COLOR, new DefTextColorAttrHandler());
SkinManager.getInstance().registerSkinAttrHandler(
DEF_BACKGROUND, new DefBackgroundAttrHandler());
}
}
================================================
FILE: app/src/main/java/org/qcode/demo/ui/customattr/CustomTextView.java
================================================
package org.qcode.demo.ui.customattr;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.util.AttributeSet;
import android.widget.TextView;
import org.qcode.skintestdemo.R;
/**
* qqliu
* 2016/9/11.
*/
public class CustomTextView extends TextView {
public CustomTextView(Context context) {
this(context, null);
}
public CustomTextView(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.app);
int textColor = array.getColor(R.styleable.app_defTextColor, Color.BLACK);
int bgDrawableId = array.getResourceId(R.styleable.app_defBackground, 0);
array.recycle();
setTextColor(textColor);
setBackgroundResource(bgDrawableId);
}
}
================================================
FILE: app/src/main/java/org/qcode/demo/ui/customattr/DefBackgroundAttrHandler.java
================================================
package org.qcode.demo.ui.customattr;
import android.graphics.drawable.Drawable;
import android.view.View;
import org.qcode.qskinloader.IResourceManager;
import org.qcode.qskinloader.ISkinAttrHandler;
import org.qcode.qskinloader.attrhandler.SkinAttrUtils;
import org.qcode.qskinloader.entity.SkinAttr;
/**
* qqliu
* 2016/10/9.
*/
public class DefBackgroundAttrHandler implements ISkinAttrHandler {
public static final String DEF_BACKGROUND = "defBackground";
@Override
public void apply(View view, SkinAttr skinAttr, IResourceManager resourceManager) {
if (null == view
|| null == skinAttr
|| !(DEF_BACKGROUND.equals(skinAttr.mAttrName))) {
//自定义属性处理时,需要明确当前处理器能处理的属性,此处是DEF_BACKGROUND
return;
}
if (!(view instanceof CustomTextView)) {
//防止在错误的View上设置了此属性
return;
}
//封装了取ColorDrawable和取普通Drawable的逻辑
Drawable drawable = SkinAttrUtils.getDrawable(
resourceManager, skinAttr.mAttrValueRefId,
skinAttr.mAttrValueTypeName, skinAttr.mAttrValueRefName);
if (null != drawable) {
view.setBackgroundDrawable(drawable);
}
}
}
================================================
FILE: app/src/main/java/org/qcode/demo/ui/customattr/DefTextColorAttrHandler.java
================================================
package org.qcode.demo.ui.customattr;
import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.view.View;
import org.qcode.qskinloader.IResourceManager;
import org.qcode.qskinloader.ISkinAttrHandler;
import org.qcode.qskinloader.entity.SkinAttr;
import org.qcode.qskinloader.entity.SkinConstant;
import static org.qcode.qskinloader.entity.SkinConstant.RES_TYPE_NAME_COLOR;
/**
* qqliu
* 2016/10/9.
*/
public class DefTextColorAttrHandler implements ISkinAttrHandler {
public static final String DEF_TEXT_COLOR = "defTextColor";
@Override
public void apply(View view, SkinAttr skinAttr, IResourceManager resourceManager) {
if(null == view
|| null == skinAttr
|| !(DEF_TEXT_COLOR.equals(skinAttr.mAttrName))) {
//自定义属性处理时,需要明确当前处理器能处理的属性,此处是DEF_TEXT_COLOR
return;
}
if(!(view instanceof CustomTextView)) {
//防止在错误的View上设置了此属性
return;
}
CustomTextView tv = (CustomTextView) view;
if (RES_TYPE_NAME_COLOR.equals(skinAttr.mAttrValueTypeName)) {
if (SkinConstant.RES_TYPE_NAME_COLOR.equals(skinAttr.mAttrValueTypeName)) {
try {
//先尝试按照int型颜色解析
int textColor = resourceManager.getColor(
skinAttr.mAttrValueRefId, skinAttr.mAttrValueRefName);
tv.setTextColor(textColor);
} catch (Resources.NotFoundException ex) {
//不是int型则按照ColorStateList引用来解析
ColorStateList textColor = resourceManager.getColorStateList(
skinAttr.mAttrValueRefId, skinAttr.mAttrValueRefName);
tv.setTextColor(textColor);
}
}
}
}
}
================================================
FILE: app/src/main/java/org/qcode/demo/ui/dynamicaddview/DynamicAddViewActivity.java
================================================
package org.qcode.demo.ui.dynamicaddview;
import android.os.Bundle;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import org.qcode.demo.BaseActivity;
import org.qcode.demo.ui.viewpageandlistview.DataListAdapter;
import org.qcode.demo.utils.UIUtil;
import org.qcode.skintestdemo.R;
public class DynamicAddViewActivity extends BaseActivity {
public static final int SHOW_COUNT = 40;
private LinearLayout mContainer;
private DataListAdapter mAdapter;
private int mCount;
private boolean mIsDestroying;
@Override
protec
gitextract_gfdlk8qj/ ├── .gitignore ├── .idea/ │ ├── compiler.xml │ ├── copyright/ │ │ └── profiles_settings.xml │ ├── encodings.xml │ ├── gradle.xml │ ├── misc.xml │ ├── modules.xml │ └── runConfigurations.xml ├── QSkinLoaderlib/ │ ├── .gitignore │ ├── build.gradle │ ├── proguard-rules.pro │ └── src/ │ └── main/ │ ├── AndroidManifest.xml │ ├── java/ │ │ └── org/ │ │ └── qcode/ │ │ └── qskinloader/ │ │ ├── IActivitySkinEventHandler.java │ │ ├── ILoadSkinListener.java │ │ ├── IResourceLoader.java │ │ ├── IResourceManager.java │ │ ├── ISkinActivity.java │ │ ├── ISkinAttrHandler.java │ │ ├── ISkinAttributeParser.java │ │ ├── ISkinManager.java │ │ ├── ISkinViewHelper.java │ │ ├── IViewCreateListener.java │ │ ├── IWindowViewManager.java │ │ ├── SkinManager.java │ │ ├── attrhandler/ │ │ │ ├── BackgroundAttrHandler.java │ │ │ ├── DividerAttrHandler.java │ │ │ ├── DrawableLeftAttrHandler.java │ │ │ ├── ListSelectorAttrHandler.java │ │ │ ├── RecyclerViewClearSubAttrHandler.java │ │ │ ├── ShadowAttrHandler.java │ │ │ ├── SkinAttrFactory.java │ │ │ ├── SkinAttrUtils.java │ │ │ ├── SrcAttrHandler.java │ │ │ ├── TextColorAttrHandler.java │ │ │ └── TextColorHintAttrHandler.java │ │ ├── base/ │ │ │ ├── observable/ │ │ │ │ ├── INotifyUpdate.java │ │ │ │ ├── IObservable.java │ │ │ │ └── Observable.java │ │ │ └── utils/ │ │ │ ├── CollectionUtils.java │ │ │ ├── HashMapCache.java │ │ │ ├── Logging.java │ │ │ ├── ReflectUtils.java │ │ │ ├── StringUtils.java │ │ │ └── WeakReferenceHelper.java │ │ ├── entity/ │ │ │ ├── DynamicAttr.java │ │ │ ├── SkinAttr.java │ │ │ ├── SkinAttrName.java │ │ │ ├── SkinAttrSet.java │ │ │ └── SkinConstant.java │ │ ├── impl/ │ │ │ ├── ActivitySkinEventHandlerImpl.java │ │ │ ├── SkinAttributeParser.java │ │ │ ├── SkinInflaterFactoryImpl.java │ │ │ ├── SkinManagerImpl.java │ │ │ ├── SkinViewHelperImpl.java │ │ │ ├── ViewSkinTagHelper.java │ │ │ └── WindowViewManager.java │ │ ├── resourceloader/ │ │ │ ├── ILoadResourceCallback.java │ │ │ ├── ResourceManager.java │ │ │ └── impl/ │ │ │ ├── APKResourceLoader.java │ │ │ ├── APKResourceManager.java │ │ │ ├── ConfigChangeResourceLoader.java │ │ │ ├── ConfigChangeResourceManager.java │ │ │ ├── SuffixResourceLoader.java │ │ │ └── SuffixResourceManager.java │ │ └── view/ │ │ └── ShadowImageView.java │ └── res/ │ └── values/ │ ├── skin_attrs.xml │ ├── skin_ids.xml │ └── strings.xml ├── README.md ├── SkinProject/ │ ├── .gitignore │ ├── app/ │ │ ├── .gitignore │ │ ├── build.gradle │ │ ├── proguard-rules.pro │ │ └── src/ │ │ └── main/ │ │ ├── AndroidManifest.xml │ │ ├── java/ │ │ │ └── org/ │ │ │ └── qcode/ │ │ │ └── skinproject/ │ │ │ └── MainActivity.java │ │ └── res/ │ │ ├── drawable/ │ │ │ ├── btn_bg.xml │ │ │ ├── drawable_float_view.xml │ │ │ └── news_item_selector.xml │ │ └── values/ │ │ └── colors.xml │ ├── build.gradle │ ├── gradle.properties │ ├── gradlew │ ├── gradlew.bat │ └── settings.gradle ├── app/ │ ├── .gitignore │ ├── build.gradle │ ├── proguard-rules.pro │ └── src/ │ └── main/ │ ├── AndroidManifest.xml │ ├── assets/ │ │ └── nightMode.skin │ ├── java/ │ │ └── org/ │ │ └── qcode/ │ │ └── demo/ │ │ ├── BaseActivity.java │ │ ├── MainActivity.java │ │ ├── SkinChangeSwitchView.java │ │ ├── SkinDemoApp.java │ │ ├── base/ │ │ │ └── Settings.java │ │ ├── skin/ │ │ │ ├── SkinChangeHelper.java │ │ │ ├── SkinConfigHelper.java │ │ │ ├── SkinConstant.java │ │ │ └── SkinUtils.java │ │ ├── ui/ │ │ │ ├── customattr/ │ │ │ │ ├── CustomAttrViewActivity.java │ │ │ │ ├── CustomTextView.java │ │ │ │ ├── DefBackgroundAttrHandler.java │ │ │ │ └── DefTextColorAttrHandler.java │ │ │ ├── dynamicaddview/ │ │ │ │ └── DynamicAddViewActivity.java │ │ │ ├── gridview/ │ │ │ │ └── GridViewActivity.java │ │ │ ├── otherscene/ │ │ │ │ ├── CustomDialog.java │ │ │ │ ├── FloatView.java │ │ │ │ ├── OtherSceneActivity.java │ │ │ │ ├── SpannableSkinAttr.java │ │ │ │ └── SpannableSkinAttrHandler.java │ │ │ ├── recyclerview/ │ │ │ │ ├── DataRecyclerViewAdapter.java │ │ │ │ └── RecyclerViewActivity.java │ │ │ └── viewpageandlistview/ │ │ │ ├── DataListAdapter.java │ │ │ ├── NewsPageAdapter.java │ │ │ ├── RecyclablePageAdapter.java │ │ │ └── ViewPagerAndListViewActivity.java │ │ └── utils/ │ │ ├── FileUtils.java │ │ ├── UITaskRunner.java │ │ └── UIUtil.java │ └── res/ │ ├── drawable/ │ │ ├── btn_bg.xml │ │ ├── btn_bg_night.xml │ │ ├── drawable_float_view.xml │ │ ├── drawable_float_view_night.xml │ │ ├── news_item_selector.xml │ │ └── news_item_selector_night.xml │ ├── drawable-night/ │ │ ├── btn_bg.xml │ │ ├── drawable_float_view.xml │ │ └── news_item_selector.xml │ ├── layout/ │ │ ├── activity_base_activity.xml │ │ ├── activity_custom_attr_test.xml │ │ ├── activity_dynamic_add_view.xml │ │ ├── activity_grid_view.xml │ │ ├── activity_main.xml │ │ ├── activity_other_scene.xml │ │ ├── activity_recycler_view.xml │ │ ├── activity_viewpager_listview.xml │ │ ├── grid_item_view.xml │ │ ├── layout_dialog_custom.xml │ │ ├── layout_popwindow.xml │ │ └── list_item_view.xml │ ├── values/ │ │ ├── colors.xml │ │ ├── colors_night.xml │ │ ├── dimens.xml │ │ ├── news_attr.xml │ │ ├── strings.xml │ │ └── styles.xml │ ├── values-night/ │ │ └── colors_night.xml │ └── values-w820dp/ │ └── dimens.xml ├── build.gradle ├── gradle/ │ └── wrapper/ │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradle.properties ├── gradlew ├── gradlew.bat └── settings.gradle
SYMBOL INDEX (498 symbols across 83 files)
FILE: QSkinLoaderlib/src/main/java/org/qcode/qskinloader/IActivitySkinEventHandler.java
type IActivitySkinEventHandler (line 14) | public interface IActivitySkinEventHandler {
method onCreate (line 21) | void onCreate(Activity activity);
method setViewCreateListener (line 31) | void setViewCreateListener(IViewCreateListener viewCreateListener);
method onViewCreated (line 38) | void onViewCreated();
method onStart (line 44) | void onStart();
method onResume (line 50) | void onResume();
method onWindowFocusChanged (line 57) | void onWindowFocusChanged(boolean hasFocus);
method onPause (line 64) | void onPause();
method onStop (line 70) | void onStop();
method onDestroy (line 76) | void onDestroy();
method setSwitchSkinImmediately (line 90) | IActivitySkinEventHandler setSwitchSkinImmediately(boolean isImmediate);
method setSupportSkinChange (line 103) | IActivitySkinEventHandler setSupportSkinChange(boolean supportChange);
method setWindowBackgroundResource (line 115) | IActivitySkinEventHandler setWindowBackgroundResource(int resId);
method setNeedDelegateViewCreate (line 132) | IActivitySkinEventHandler setNeedDelegateViewCreate(boolean needDelega...
method handleSkinUpdate (line 140) | void handleSkinUpdate();
method getSkinAttributeParser (line 148) | ISkinAttributeParser getSkinAttributeParser();
FILE: QSkinLoaderlib/src/main/java/org/qcode/qskinloader/ILoadSkinListener.java
type ILoadSkinListener (line 11) | public interface ILoadSkinListener {
method onLoadStart (line 19) | void onLoadStart(String skinIdentifier);
method onLoadSuccess (line 28) | void onLoadSuccess(String skinIdentifier);
method onLoadFail (line 37) | void onLoadFail(String skinIdentifier);
FILE: QSkinLoaderlib/src/main/java/org/qcode/qskinloader/IResourceLoader.java
type IResourceLoader (line 13) | public interface IResourceLoader {
method loadResource (line 24) | void loadResource(String skinIdentifier,
FILE: QSkinLoaderlib/src/main/java/org/qcode/qskinloader/IResourceManager.java
type IResourceManager (line 14) | public interface IResourceManager {
method setBaseResource (line 24) | void setBaseResource(
method getSkinIdentifier (line 32) | String getSkinIdentifier();
method isDefault (line 38) | boolean isDefault();
method getDrawable (line 46) | Drawable getDrawable(int resId) throws Resources.NotFoundException;
method getDrawable (line 55) | Drawable getDrawable(int resId, String resName) throws Resources.NotFo...
method getColor (line 63) | int getColor(int resId) throws Resources.NotFoundException;
method getColor (line 72) | int getColor(int resId, String resName) throws Resources.NotFoundExcep...
method getColorStateList (line 80) | ColorStateList getColorStateList(int resId) throws Resources.NotFoundE...
method getColorStateList (line 89) | ColorStateList getColorStateList(int resId, String resName) throws Res...
method getColorStateList (line 99) | ColorStateList getColorStateList(int resId, String typeName, String re...
FILE: QSkinLoaderlib/src/main/java/org/qcode/qskinloader/ISkinActivity.java
type ISkinActivity (line 11) | public interface ISkinActivity {
method isSwitchSkinImmediately (line 20) | boolean isSwitchSkinImmediately();
method isSupportSkinChange (line 30) | boolean isSupportSkinChange();
method handleSkinChange (line 39) | void handleSkinChange();
FILE: QSkinLoaderlib/src/main/java/org/qcode/qskinloader/ISkinAttrHandler.java
type ISkinAttrHandler (line 15) | public interface ISkinAttrHandler {
method apply (line 26) | void apply(View view,
FILE: QSkinLoaderlib/src/main/java/org/qcode/qskinloader/ISkinAttributeParser.java
type ISkinAttributeParser (line 17) | public interface ISkinAttributeParser {
method isSupportSkin (line 27) | boolean isSupportSkin(String name, Context context, AttributeSet attrs);
method parseAttribute (line 39) | void parseAttribute(View view, String name, Context context, Attribute...
FILE: QSkinLoaderlib/src/main/java/org/qcode/qskinloader/ISkinManager.java
type ISkinManager (line 15) | public interface ISkinManager extends IObservable<IActivitySkinEventHand...
method init (line 22) | void init(Context context);
method restoreDefault (line 32) | void restoreDefault(String defaultSkinIdentifier, ILoadSkinListener lo...
method loadAPKSkin (line 46) | void loadAPKSkin(String skinPath, ILoadSkinListener loadListener);
method loadSkin (line 60) | void loadSkin(String skinIdentifier,
method applySkin (line 72) | void applySkin(View view, boolean applyChild);
method registerSkinAttrHandler (line 82) | void registerSkinAttrHandler(String attrName, ISkinAttrHandler skinAtt...
method unregisterSkinAttrHandler (line 90) | void unregisterSkinAttrHandler(String attrName);
method setResourceManager (line 100) | void setResourceManager(IResourceManager resourceManager);
method getResourceManager (line 108) | IResourceManager getResourceManager();
FILE: QSkinLoaderlib/src/main/java/org/qcode/qskinloader/ISkinViewHelper.java
type ISkinViewHelper (line 16) | public interface ISkinViewHelper {
method setViewAttrs (line 27) | ISkinViewHelper setViewAttrs(String attrName, int resId);
method setViewAttrs (line 40) | ISkinViewHelper setViewAttrs(DynamicAttr... dynamicAttrs);
method setViewAttrs (line 53) | ISkinViewHelper setViewAttrs(SkinAttr... skinAttrs);
method setViewAttrs (line 66) | ISkinViewHelper setViewAttrs(List<DynamicAttr> dynamicAttrs);
method addViewAttrs (line 78) | ISkinViewHelper addViewAttrs(String attrName, int resId);
method addViewAttrs (line 91) | ISkinViewHelper addViewAttrs(DynamicAttr... dynamicAttrs);
method addViewAttrs (line 104) | ISkinViewHelper addViewAttrs(SkinAttr... skinAttrs);
method addViewAttrs (line 117) | ISkinViewHelper addViewAttrs(List<DynamicAttr> dynamicAttrs);
method cleanAttrs (line 129) | ISkinViewHelper cleanAttrs(boolean clearChild);
method applySkin (line 140) | void applySkin(boolean applyChild);
FILE: QSkinLoaderlib/src/main/java/org/qcode/qskinloader/IViewCreateListener.java
type IViewCreateListener (line 13) | public interface IViewCreateListener {
method beforeCreate (line 22) | View beforeCreate(String name, Context context, AttributeSet attrs);
method afterCreated (line 32) | void afterCreated(View view, String name, Context context, AttributeSe...
FILE: QSkinLoaderlib/src/main/java/org/qcode/qskinloader/IWindowViewManager.java
type IWindowViewManager (line 22) | public interface IWindowViewManager {
method addWindowView (line 33) | IWindowViewManager addWindowView(View view);
method removeWindowView (line 43) | IWindowViewManager removeWindowView(View view);
method clear (line 50) | IWindowViewManager clear();
method applySkinForViews (line 60) | void applySkinForViews(boolean applyChild);
method getWindowViewList (line 68) | List<View> getWindowViewList();
FILE: QSkinLoaderlib/src/main/java/org/qcode/qskinloader/SkinManager.java
class SkinManager (line 18) | public class SkinManager {
method getInstance (line 27) | public static ISkinManager getInstance() {
method with (line 39) | public static ISkinViewHelper with(View view) {
method getWindowViewManager (line 56) | public static IWindowViewManager getWindowViewManager() {
method newActivitySkinEventHandler (line 70) | public static IActivitySkinEventHandler newActivitySkinEventHandler() {
FILE: QSkinLoaderlib/src/main/java/org/qcode/qskinloader/attrhandler/BackgroundAttrHandler.java
class BackgroundAttrHandler (line 14) | class BackgroundAttrHandler implements ISkinAttrHandler {
method apply (line 16) | @Override
FILE: QSkinLoaderlib/src/main/java/org/qcode/qskinloader/attrhandler/DividerAttrHandler.java
class DividerAttrHandler (line 15) | class DividerAttrHandler implements ISkinAttrHandler {
method apply (line 17) | @Override
FILE: QSkinLoaderlib/src/main/java/org/qcode/qskinloader/attrhandler/DrawableLeftAttrHandler.java
class DrawableLeftAttrHandler (line 17) | class DrawableLeftAttrHandler implements ISkinAttrHandler {
method apply (line 19) | @Override
FILE: QSkinLoaderlib/src/main/java/org/qcode/qskinloader/attrhandler/ListSelectorAttrHandler.java
class ListSelectorAttrHandler (line 16) | class ListSelectorAttrHandler implements ISkinAttrHandler {
method apply (line 18) | @Override
FILE: QSkinLoaderlib/src/main/java/org/qcode/qskinloader/attrhandler/RecyclerViewClearSubAttrHandler.java
class RecyclerViewClearSubAttrHandler (line 20) | class RecyclerViewClearSubAttrHandler implements ISkinAttrHandler {
method apply (line 23) | @Override
method refreshRecyclerView (line 38) | private void refreshRecyclerView(RecyclerView recyclerView) {
FILE: QSkinLoaderlib/src/main/java/org/qcode/qskinloader/attrhandler/ShadowAttrHandler.java
class ShadowAttrHandler (line 20) | class ShadowAttrHandler implements ISkinAttrHandler {
method apply (line 22) | @Override
FILE: QSkinLoaderlib/src/main/java/org/qcode/qskinloader/attrhandler/SkinAttrFactory.java
class SkinAttrFactory (line 18) | public class SkinAttrFactory {
method newSkinAttr (line 44) | public static SkinAttr newSkinAttr(
method newSkinAttr (line 65) | public static SkinAttr newSkinAttr(String attrName) {
method getSkinAttrHandler (line 82) | public static ISkinAttrHandler getSkinAttrHandler(String attrName) {
method isSupportedAttr (line 92) | public static boolean isSupportedAttr(String attrName) {
method registerSkinAttrHandler (line 101) | public static void registerSkinAttrHandler(String attrName, ISkinAttrH...
method removeSkinAttrHandler (line 114) | public static void removeSkinAttrHandler(String attrName) {
FILE: QSkinLoaderlib/src/main/java/org/qcode/qskinloader/attrhandler/SkinAttrUtils.java
class SkinAttrUtils (line 21) | public class SkinAttrUtils {
method getDrawable (line 31) | public static Drawable getDrawable(IResourceManager resourceManager,
method applySkinAttrs (line 59) | public static void applySkinAttrs(View view, SkinAttrSet skinAttrSet, ...
FILE: QSkinLoaderlib/src/main/java/org/qcode/qskinloader/attrhandler/SrcAttrHandler.java
class SrcAttrHandler (line 16) | class SrcAttrHandler implements ISkinAttrHandler {
method apply (line 18) | @Override
FILE: QSkinLoaderlib/src/main/java/org/qcode/qskinloader/attrhandler/TextColorAttrHandler.java
class TextColorAttrHandler (line 16) | class TextColorAttrHandler implements ISkinAttrHandler {
method apply (line 18) | @Override
FILE: QSkinLoaderlib/src/main/java/org/qcode/qskinloader/attrhandler/TextColorHintAttrHandler.java
class TextColorHintAttrHandler (line 16) | class TextColorHintAttrHandler implements ISkinAttrHandler {
method apply (line 18) | @Override
FILE: QSkinLoaderlib/src/main/java/org/qcode/qskinloader/base/observable/INotifyUpdate.java
type INotifyUpdate (line 8) | public interface INotifyUpdate<T> {
method notifyEvent (line 17) | boolean notifyEvent(T callback, String identifier, Object... params);
FILE: QSkinLoaderlib/src/main/java/org/qcode/qskinloader/base/observable/IObservable.java
type IObservable (line 8) | public interface IObservable<T> {
method addObserver (line 14) | void addObserver(T observer);
method removeObserver (line 21) | void removeObserver(T observer);
method notifyUpdate (line 27) | void notifyUpdate(INotifyUpdate<T> callback, String identifier, Object...
FILE: QSkinLoaderlib/src/main/java/org/qcode/qskinloader/base/observable/Observable.java
class Observable (line 10) | public class Observable<T> implements IObservable<T> {
method addObserver (line 14) | @Override
method removeObserver (line 25) | @Override
method notifyUpdate (line 36) | @Override
FILE: QSkinLoaderlib/src/main/java/org/qcode/qskinloader/base/utils/CollectionUtils.java
class CollectionUtils (line 9) | public class CollectionUtils {
method isEmpty (line 10) | public static boolean isEmpty(Collection<?> collection){
method isEmpty (line 14) | public static <T> boolean isEmpty(T... array){
FILE: QSkinLoaderlib/src/main/java/org/qcode/qskinloader/base/utils/HashMapCache.java
class HashMapCache (line 12) | public class HashMapCache<K, V> {
method HashMapCache (line 19) | public HashMapCache(boolean isStrongReference) {
method getCache (line 27) | public V getCache(K key) {
method addCache (line 43) | public void addCache(K key, V value) {
FILE: QSkinLoaderlib/src/main/java/org/qcode/qskinloader/base/utils/Logging.java
class Logging (line 5) | public class Logging {
method setDebugLogging (line 11) | public static void setDebugLogging(boolean enabled) {
method isDebugLogging (line 15) | public static boolean isDebugLogging() {
method v (line 19) | public static int v(String tag, String msg) {
method v (line 27) | public static int v(String tag, String msg, Throwable tr) {
method d (line 35) | public static int d(String tag, String msg) {
method d (line 43) | public static int d(String tag, String msg, Throwable tr) {
method i (line 51) | public static int i(String tag, String msg) {
method i (line 59) | public static int i(String tag, String msg, Throwable tr) {
method w (line 67) | public static int w(String tag, String msg) {
method w (line 75) | public static int w(String tag, String msg, Throwable tr) {
method w (line 83) | public static int w(String tag, Throwable tr) {
method e (line 91) | public static int e(String tag, String msg) {
method e (line 99) | public static int e(String tag, String msg, Throwable tr) {
FILE: QSkinLoaderlib/src/main/java/org/qcode/qskinloader/base/utils/ReflectUtils.java
class ReflectUtils (line 9) | public class ReflectUtils {
method getDeclaredField (line 13) | private static Field getDeclaredField(Object object, String fieldName)...
method getFieldValue (line 33) | public static <T> T getFieldValue(Object object, String fieldName)
method getFieldValueOpt (line 53) | public static <T> T getFieldValueOpt(Object obj, String fieldName) {
method setFieldValue (line 71) | public static void setFieldValue(Object object, String fieldName, Obje...
method setFieldValueOpt (line 89) | public static void setFieldValueOpt(Object object, String fieldName, O...
FILE: QSkinLoaderlib/src/main/java/org/qcode/qskinloader/base/utils/StringUtils.java
class StringUtils (line 7) | public class StringUtils {
method isEmpty (line 8) | public static boolean isEmpty(CharSequence sequence) {
FILE: QSkinLoaderlib/src/main/java/org/qcode/qskinloader/base/utils/WeakReferenceHelper.java
class WeakReferenceHelper (line 10) | public class WeakReferenceHelper<T> {
method WeakReferenceHelper (line 14) | public WeakReferenceHelper(T t) {
method getData (line 18) | public T getData() {
method setData (line 26) | public void setData(T t) {
method toString (line 30) | @Override
FILE: QSkinLoaderlib/src/main/java/org/qcode/qskinloader/entity/DynamicAttr.java
class DynamicAttr (line 9) | public class DynamicAttr {
method DynamicAttr (line 30) | public DynamicAttr(String attrName) {
method DynamicAttr (line 36) | public DynamicAttr(String attrName, int attrValueRefId) {
method toString (line 43) | @Override
FILE: QSkinLoaderlib/src/main/java/org/qcode/qskinloader/entity/SkinAttr.java
class SkinAttr (line 8) | public class SkinAttr {
method SkinAttr (line 34) | public SkinAttr() {
method SkinAttr (line 38) | public SkinAttr(String attrName) {
method toString (line 43) | @Override
FILE: QSkinLoaderlib/src/main/java/org/qcode/qskinloader/entity/SkinAttrName.java
class SkinAttrName (line 8) | public class SkinAttrName {
FILE: QSkinLoaderlib/src/main/java/org/qcode/qskinloader/entity/SkinAttrSet.java
class SkinAttrSet (line 17) | public class SkinAttrSet {
method SkinAttrSet (line 20) | public SkinAttrSet(SkinAttr... skinAttrs) {
method SkinAttrSet (line 24) | public SkinAttrSet(List<SkinAttr> skinAttrs) {
method saveAttrs (line 28) | private void saveAttrs(List<SkinAttr> skinAttrs) {
method addSkinAttrSet (line 41) | public synchronized void addSkinAttrSet(SkinAttrSet skinAttrSet) {
method addSkinAttr (line 50) | public synchronized void addSkinAttr(SkinAttr skinAttr) {
method getAttrList (line 58) | public synchronized List<SkinAttr> getAttrList() {
FILE: QSkinLoaderlib/src/main/java/org/qcode/qskinloader/entity/SkinConstant.java
class SkinConstant (line 8) | public class SkinConstant {
FILE: QSkinLoaderlib/src/main/java/org/qcode/qskinloader/impl/ActivitySkinEventHandlerImpl.java
class ActivitySkinEventHandlerImpl (line 21) | public class ActivitySkinEventHandlerImpl implements IActivitySkinEventH...
method ActivitySkinEventHandlerImpl (line 46) | public ActivitySkinEventHandlerImpl() {
method onCreate (line 50) | @Override
method setViewCreateListener (line 68) | @Override
method onViewCreated (line 76) | @Override
method onStart (line 93) | @Override
method onResume (line 98) | @Override
method onWindowFocusChanged (line 103) | @Override
method onPause (line 120) | @Override
method onStop (line 125) | @Override
method onDestroy (line 130) | @Override
method setSwitchSkinImmediately (line 144) | @Override
method setSupportSkinChange (line 150) | @Override
method handleSkinUpdate (line 156) | @Override
method getSkinAttributeParser (line 172) | @Override
method setWindowBackgroundResource (line 181) | @Override
method setNeedDelegateViewCreate (line 187) | @Override
method refreshSkin (line 193) | private void refreshSkin() {
method refreshWindowBg (line 220) | private void refreshWindowBg(View contentView) {
method getContentView (line 253) | public View getContentView() {
FILE: QSkinLoaderlib/src/main/java/org/qcode/qskinloader/impl/SkinAttributeParser.java
class SkinAttributeParser (line 26) | class SkinAttributeParser implements ISkinAttributeParser {
method isSupportSkin (line 30) | public boolean isSupportSkin(String name, Context context, AttributeSe...
method parseAttribute (line 39) | public void parseAttribute(View view, String name, Context context, At...
method parseSkinAttr (line 107) | private SkinAttrSet parseSkinAttr(Context context,
method getSkinAttrBySplit (line 146) | private SkinAttr getSkinAttrBySplit(Context context, String attrName, ...
method getSkinAttrFromId (line 159) | private SkinAttr getSkinAttrFromId(Context context, String attrName, S...
FILE: QSkinLoaderlib/src/main/java/org/qcode/qskinloader/impl/SkinInflaterFactoryImpl.java
class SkinInflaterFactoryImpl (line 16) | class SkinInflaterFactoryImpl implements LayoutInflater.Factory {
method SkinInflaterFactoryImpl (line 23) | public SkinInflaterFactoryImpl(ISkinAttributeParser parser) {
method setViewCreateListener (line 27) | public void setViewCreateListener(IViewCreateListener viewCreateListen...
method onCreateView (line 31) | @Override
method createView (line 73) | private View createView(Context context,
method setupInflater (line 108) | private void setupInflater(LayoutInflater inflater, Context context) {
FILE: QSkinLoaderlib/src/main/java/org/qcode/qskinloader/impl/SkinManagerImpl.java
class SkinManagerImpl (line 33) | public class SkinManagerImpl implements ISkinManager {
method SkinManagerImpl (line 40) | private SkinManagerImpl() {
method getInstance (line 43) | public static SkinManagerImpl getInstance() {
method init (line 59) | @Override
method restoreDefault (line 74) | @Override
method refreshAllSkin (line 90) | private void refreshAllSkin() {
method loadAPKSkin (line 98) | @Override
method loadSkin (line 103) | @Override
method applySkin (line 155) | @Override
method registerSkinAttrHandler (line 175) | @Override
method unregisterSkinAttrHandler (line 180) | @Override
method setResourceManager (line 185) | @Override
method getResourceManager (line 193) | @Override
method addObserver (line 198) | @Override
method removeObserver (line 203) | @Override
method notifyUpdate (line 208) | @Override
method applyWindowViewSkin (line 214) | void applyWindowViewSkin() {
method refreshSkin (line 228) | private void refreshSkin() {
FILE: QSkinLoaderlib/src/main/java/org/qcode/qskinloader/impl/SkinViewHelperImpl.java
class SkinViewHelperImpl (line 28) | public class SkinViewHelperImpl implements ISkinViewHelper {
method SkinViewHelperImpl (line 32) | public SkinViewHelperImpl(View view) {
method setViewAttrs (line 38) | @Override
method setViewAttrs (line 48) | @Override
method setViewAttrs (line 57) | @Override
method setViewAttrs (line 68) | @Override
method addViewAttrs (line 81) | @Override
method addViewAttrs (line 91) | @Override
method addViewAttrs (line 100) | @Override
method addViewAttrs (line 111) | @Override
method cleanAttrs (line 124) | @Override
method applySkin (line 135) | @Override
method cleanAttrs (line 140) | private static void cleanAttrs(View view, boolean clearChild) {
method parseSkinAttr (line 158) | private SkinAttr parseSkinAttr(String attrName, int resId) {
method parseSkinAttrSet (line 174) | @NonNull
FILE: QSkinLoaderlib/src/main/java/org/qcode/qskinloader/impl/ViewSkinTagHelper.java
class ViewSkinTagHelper (line 14) | class ViewSkinTagHelper {
method setSkinAttrs (line 21) | public static void setSkinAttrs(View view, SkinAttrSet skinAttrSet) {
method addSkinAttrs (line 33) | public static void addSkinAttrs(View view, SkinAttrSet skinAttrSet) {
method getSkinAttrs (line 51) | public static SkinAttrSet getSkinAttrs(View view) {
FILE: QSkinLoaderlib/src/main/java/org/qcode/qskinloader/impl/WindowViewManager.java
class WindowViewManager (line 19) | public class WindowViewManager implements IWindowViewManager {
method WindowViewManager (line 25) | private WindowViewManager() {}
method getInstance (line 27) | public static WindowViewManager getInstance() {
method addWindowView (line 39) | @Override
method removeWindowView (line 58) | @Override
method clear (line 76) | @Override
method applySkinForViews (line 86) | @Override
method getWindowViewList (line 101) | @Override
FILE: QSkinLoaderlib/src/main/java/org/qcode/qskinloader/resourceloader/ILoadResourceCallback.java
type ILoadResourceCallback (line 10) | public interface ILoadResourceCallback {
method onLoadStart (line 16) | void onLoadStart(String identifier);
method onLoadSuccess (line 24) | void onLoadSuccess(String identifier, IResourceManager result);
method onLoadFail (line 32) | void onLoadFail(String identifier, int errorCode);
FILE: QSkinLoaderlib/src/main/java/org/qcode/qskinloader/resourceloader/ResourceManager.java
class ResourceManager (line 18) | public class ResourceManager implements IResourceManager {
method ResourceManager (line 26) | public ResourceManager(Context context) {
method setBaseResource (line 31) | @Override
method isDefault (line 38) | @Override
method getColor (line 47) | @Override
method getColor (line 60) | @Override
method getColorStateList (line 73) | @Override
method getColorStateList (line 86) | @Override
method getColorStateList (line 99) | @Override
method getDrawable (line 112) | public Drawable getDrawable(int resId) {
method getDrawable (line 124) | @SuppressLint("NewApi")
method convertToColorStateList (line 144) | private ColorStateList convertToColorStateList(int resId) {
method getSkinIdentifier (line 163) | @Override
FILE: QSkinLoaderlib/src/main/java/org/qcode/qskinloader/resourceloader/impl/APKResourceLoader.java
class APKResourceLoader (line 23) | public class APKResourceLoader implements IResourceLoader {
method APKResourceLoader (line 33) | public APKResourceLoader(Context context) {
method loadResource (line 37) | @Override
class APkLoadResult (line 106) | private static class APkLoadResult {
method APkLoadResult (line 110) | public APkLoadResult(String pkgName, Resources resources) {
FILE: QSkinLoaderlib/src/main/java/org/qcode/qskinloader/resourceloader/impl/APKResourceManager.java
class APKResourceManager (line 18) | public class APKResourceManager implements IResourceManager {
method APKResourceManager (line 29) | public APKResourceManager(Context context, String pkgName, Resources r...
method setBaseResource (line 36) | @Override
method getSkinIdentifier (line 41) | @Override
method isDefault (line 46) | @Override
method getColor (line 51) | @Override
method getColor (line 57) | @Override
method getDrawable (line 73) | public Drawable getDrawable(int resId) {
method getDrawable (line 78) | @SuppressLint("NewApi")
method getColorStateList (line 101) | @Override
method getColorStateList (line 112) | @Override
method getColorStateList (line 117) | @Override
method getResKey (line 126) | private String getResKey(String skinPackageName, String resName) {
FILE: QSkinLoaderlib/src/main/java/org/qcode/qskinloader/resourceloader/impl/ConfigChangeResourceLoader.java
class ConfigChangeResourceLoader (line 17) | public class ConfigChangeResourceLoader implements IResourceLoader {
method ConfigChangeResourceLoader (line 26) | public ConfigChangeResourceLoader(Context context) {
method loadResource (line 30) | @Override
method switchMode (line 49) | private void switchMode(boolean isNightMode) {
FILE: QSkinLoaderlib/src/main/java/org/qcode/qskinloader/resourceloader/impl/ConfigChangeResourceManager.java
class ConfigChangeResourceManager (line 16) | public class ConfigChangeResourceManager implements IResourceManager {
method ConfigChangeResourceManager (line 23) | public ConfigChangeResourceManager(Context context, String skinIdentif...
method setBaseResource (line 29) | @Override
method getSkinIdentifier (line 34) | @Override
method isDefault (line 39) | @Override
method getColor (line 44) | @Override
method getColor (line 49) | @Override
method getDrawable (line 54) | public Drawable getDrawable(int resId) {
method getDrawable (line 58) | @SuppressLint("NewApi")
method getColorStateList (line 63) | @Override
method getColorStateList (line 68) | @Override
method getColorStateList (line 73) | @Override
FILE: QSkinLoaderlib/src/main/java/org/qcode/qskinloader/resourceloader/impl/SuffixResourceLoader.java
class SuffixResourceLoader (line 14) | public class SuffixResourceLoader implements IResourceLoader {
method SuffixResourceLoader (line 22) | public SuffixResourceLoader(Context context) {
method loadResource (line 26) | @Override
FILE: QSkinLoaderlib/src/main/java/org/qcode/qskinloader/resourceloader/impl/SuffixResourceManager.java
class SuffixResourceManager (line 18) | public class SuffixResourceManager implements IResourceManager {
method SuffixResourceManager (line 29) | public SuffixResourceManager(Context context, String skinSuffix) {
method setBaseResource (line 36) | @Override
method getSkinIdentifier (line 41) | @Override
method isDefault (line 46) | @Override
method getColor (line 51) | @Override
method getColor (line 57) | @Override
method getDrawable (line 74) | public Drawable getDrawable(int resId) {
method getDrawable (line 79) | @SuppressLint("NewApi")
method getColorStateList (line 107) | @Override
method getColorStateList (line 120) | @Override
method getColorStateList (line 125) | @Override
method appendSuffix (line 137) | private String appendSuffix(String resName) {
FILE: QSkinLoaderlib/src/main/java/org/qcode/qskinloader/view/ShadowImageView.java
class ShadowImageView (line 20) | public class ShadowImageView extends ImageView {
method ShadowImageView (line 27) | public ShadowImageView(Context context) {
method ShadowImageView (line 31) | public ShadowImageView(Context context, AttributeSet attrs) {
method ShadowImageView (line 35) | public ShadowImageView(Context context, AttributeSet attrs, int defSty...
method setShadowColor (line 39) | public void setShadowColor(int color) {
method onDraw (line 45) | @Override
FILE: SkinProject/app/src/main/java/org/qcode/skinproject/MainActivity.java
class MainActivity (line 10) | public class MainActivity extends Activity {
FILE: app/src/main/java/org/qcode/demo/BaseActivity.java
class BaseActivity (line 23) | public abstract class BaseActivity extends Activity implements ISkinActi...
method onCreate (line 29) | @Override
method initView (line 46) | private void initView(Context context) {
method setContentView (line 65) | @Override
method setContentView (line 70) | @Override
method onStart (line 77) | @Override
method onResume (line 83) | @Override
method onWindowFocusChanged (line 97) | @Override
method onPause (line 105) | @Override
method onStop (line 111) | @Override
method onDestroy (line 117) | @Override
method isSupportSkinChange (line 125) | @Override
method isSwitchSkinImmediately (line 131) | @Override
method handleSkinChange (line 138) | @Override
method getWindowBackgroundResource (line 147) | protected int getWindowBackgroundResource() {
FILE: app/src/main/java/org/qcode/demo/MainActivity.java
class MainActivity (line 20) | public class MainActivity extends BaseActivity {
method onCreate (line 21) | @Override
method onClick (line 27) | public void onClick(View view) {
FILE: app/src/main/java/org/qcode/demo/SkinChangeSwitchView.java
class SkinChangeSwitchView (line 17) | public class SkinChangeSwitchView extends LinearLayout {
method SkinChangeSwitchView (line 24) | public SkinChangeSwitchView(Context context) {
method initView (line 29) | protected void initView(final Context context) {
method refreshSwitch (line 77) | public void refreshSwitch() {
FILE: app/src/main/java/org/qcode/demo/SkinDemoApp.java
class SkinDemoApp (line 13) | public class SkinDemoApp extends Application {
method onCreate (line 17) | public void onCreate() {
method initSkinLoader (line 29) | private void initSkinLoader() {
method getAppContext (line 36) | public static Context getAppContext() {
FILE: app/src/main/java/org/qcode/demo/base/Settings.java
class Settings (line 17) | public class Settings {
method Settings (line 25) | private Settings(Context context) {
method createInstance (line 32) | public static Settings createInstance(Context context) {
method getInstance (line 43) | public static Settings getInstance() {
method isSetted (line 47) | public boolean isSetted(String key) {
method setSetting (line 52) | public void setSetting(String key, boolean value) {
method setSetting (line 63) | public void setSetting(String key, int value) {
method setSetting (line 74) | public void setSetting(String key, float value) {
method setSetting (line 85) | public void setSetting(String key, long value) {
method setSetting (line 96) | public void setSetting(String key, String value) {
method getBoolean (line 112) | public boolean getBoolean(String key) {
method getBoolean (line 117) | public boolean getBoolean(String key, boolean defaultValue) {
method getInt (line 128) | public int getInt(String key) {
method getInt (line 133) | public int getInt(String key, int defaultValue) {
method getFloat (line 144) | public float getFloat(String key) {
method getFloat (line 149) | public float getFloat(String key, float defaultValue) {
method getLong (line 160) | public long getLong(String key) {
method getLong (line 165) | public long getLong(String key, long defaultValue) {
method getString (line 176) | public String getString(String key) {
method getString (line 181) | public String getString(String key, String defaultValue) {
method saveObject (line 192) | public void saveObject(String fileName, Object object) {
method readObject (line 218) | public Object readObject(String fileName) {
method clearObject (line 244) | public void clearObject(String fileName) {
method removeSetting (line 257) | public void removeSetting(String key) {
FILE: app/src/main/java/org/qcode/demo/skin/SkinChangeHelper.java
class SkinChangeHelper (line 19) | public class SkinChangeHelper {
method SkinChangeHelper (line 35) | private SkinChangeHelper() {
method getInstance (line 40) | public static SkinChangeHelper getInstance() {
method init (line 55) | public void init(Context context) {
method switchSkinMode (line 59) | public void switchSkinMode(OnSkinChangeListener listener) {
method refreshSkin (line 65) | public void refreshSkin(OnSkinChangeListener listener) {
method isDefaultMode (line 100) | public boolean isDefaultMode() {
method isSwitching (line 104) | public boolean isSwitching() {
method changeSkinByApk (line 108) | private void changeSkinByApk(OnSkinChangeListener listener) {
method changeSkinBySuffix (line 123) | private void changeSkinBySuffix(OnSkinChangeListener listener) {
method changeSkinByConfig (line 129) | private void changeSkinByConfig(String mode, OnSkinChangeListener list...
class MyLoadSkinListener (line 135) | private class MyLoadSkinListener implements ILoadSkinListener {
method MyLoadSkinListener (line 139) | public MyLoadSkinListener(OnSkinChangeListener listener) {
method onLoadStart (line 143) | @Override
method onLoadSuccess (line 147) | @Override
method onLoadFail (line 164) | @Override
type OnSkinChangeListener (line 179) | public interface OnSkinChangeListener {
method onSuccess (line 180) | void onSuccess();
method onError (line 182) | void onError();
FILE: app/src/main/java/org/qcode/demo/skin/SkinConfigHelper.java
class SkinConfigHelper (line 5) | public class SkinConfigHelper {
method getSkinIdentifier (line 10) | public static String getSkinIdentifier() {
method saveSkinIdentifier (line 19) | public static void saveSkinIdentifier(String identifier) {
method isDefaultSkin (line 28) | public static boolean isDefaultSkin() {
FILE: app/src/main/java/org/qcode/demo/skin/SkinConstant.java
class SkinConstant (line 8) | public class SkinConstant {
FILE: app/src/main/java/org/qcode/demo/skin/SkinUtils.java
class SkinUtils (line 14) | public class SkinUtils {
method getTotalSkinPath (line 19) | public static String getTotalSkinPath(Context context) {
method copyAssetSkin (line 25) | public static boolean copyAssetSkin(Context context) {
method needUpdateSkin (line 45) | private static boolean needUpdateSkin() {
FILE: app/src/main/java/org/qcode/demo/ui/customattr/CustomAttrViewActivity.java
class CustomAttrViewActivity (line 12) | public class CustomAttrViewActivity extends BaseActivity {
method onCreate (line 14) | @Override
method registerHandler (line 21) | private void registerHandler() {
FILE: app/src/main/java/org/qcode/demo/ui/customattr/CustomTextView.java
class CustomTextView (line 15) | public class CustomTextView extends TextView {
method CustomTextView (line 16) | public CustomTextView(Context context) {
method CustomTextView (line 20) | public CustomTextView(Context context, AttributeSet attrs) {
FILE: app/src/main/java/org/qcode/demo/ui/customattr/DefBackgroundAttrHandler.java
class DefBackgroundAttrHandler (line 16) | public class DefBackgroundAttrHandler implements ISkinAttrHandler {
method apply (line 19) | @Override
FILE: app/src/main/java/org/qcode/demo/ui/customattr/DefTextColorAttrHandler.java
class DefTextColorAttrHandler (line 19) | public class DefTextColorAttrHandler implements ISkinAttrHandler {
method apply (line 22) | @Override
FILE: app/src/main/java/org/qcode/demo/ui/dynamicaddview/DynamicAddViewActivity.java
class DynamicAddViewActivity (line 12) | public class DynamicAddViewActivity extends BaseActivity {
method onCreate (line 20) | @Override
method onDestroy (line 52) | @Override
FILE: app/src/main/java/org/qcode/demo/ui/gridview/GridViewActivity.java
class GridViewActivity (line 10) | public class GridViewActivity extends BaseActivity{
method onCreate (line 14) | @Override
FILE: app/src/main/java/org/qcode/demo/ui/otherscene/CustomDialog.java
class CustomDialog (line 17) | public class CustomDialog extends Dialog implements View.OnClickListener {
method CustomDialog (line 21) | public CustomDialog(Context context) {
method show (line 43) | @Override
method setOnDismissListener (line 54) | @Override
method onClick (line 59) | public void onClick(View view) {
class WrapperDismissListener (line 69) | private static class WrapperDismissListener implements OnDismissListen...
method setDismissListener (line 73) | void setDismissListener(OnDismissListener listener) {
method onDismiss (line 77) | @Override
FILE: app/src/main/java/org/qcode/demo/ui/otherscene/FloatView.java
class FloatView (line 15) | public class FloatView extends ImageView {
method FloatView (line 16) | public FloatView(Context context) {
method dismiss (line 26) | public void dismiss() {
method show (line 32) | public void show() {
FILE: app/src/main/java/org/qcode/demo/ui/otherscene/OtherSceneActivity.java
class OtherSceneActivity (line 28) | public class OtherSceneActivity extends BaseActivity {
method onCreate (line 34) | @Override
method onClick (line 50) | public void onClick(View view) {
method showFloatView (line 117) | private void showFloatView() {
method hideFloatView (line 133) | private void hideFloatView() {
method getWindowBackgroundResource (line 139) | @Override
method isSwitchSkinImmediately (line 144) | @Override
FILE: app/src/main/java/org/qcode/demo/ui/otherscene/SpannableSkinAttr.java
class SpannableSkinAttr (line 9) | public class SpannableSkinAttr extends DynamicAttr {
method SpannableSkinAttr (line 14) | public SpannableSkinAttr(String text, int attrValueRefId) {
FILE: app/src/main/java/org/qcode/demo/ui/otherscene/SpannableSkinAttrHandler.java
class SpannableSkinAttrHandler (line 24) | public class SpannableSkinAttrHandler implements ISkinAttrHandler {
method apply (line 25) | @Override
FILE: app/src/main/java/org/qcode/demo/ui/recyclerview/DataRecyclerViewAdapter.java
class DataRecyclerViewAdapter (line 23) | public class DataRecyclerViewAdapter extends RecyclerView.Adapter<Recycl...
method DataRecyclerViewAdapter (line 29) | public DataRecyclerViewAdapter(Context context) {
method onCreateViewHolder (line 37) | @Override
method onBindViewHolder (line 44) | @Override
method getItemCount (line 51) | @Override
method newItem (line 56) | public View newItem() {
FILE: app/src/main/java/org/qcode/demo/ui/recyclerview/RecyclerViewActivity.java
class RecyclerViewActivity (line 10) | public class RecyclerViewActivity extends BaseActivity {
method onCreate (line 14) | @Override
FILE: app/src/main/java/org/qcode/demo/ui/viewpageandlistview/DataListAdapter.java
class DataListAdapter (line 19) | public class DataListAdapter extends BaseAdapter {
method DataListAdapter (line 26) | public DataListAdapter(Context context, String outterIdentifier) {
method getIdentifier (line 34) | public String getIdentifier() {
method getCount (line 38) | @Override
method getItem (line 43) | @Override
method getItemId (line 48) | @Override
method getView (line 53) | @Override
FILE: app/src/main/java/org/qcode/demo/ui/viewpageandlistview/NewsPageAdapter.java
class NewsPageAdapter (line 11) | public class NewsPageAdapter extends RecyclablePageAdapter<ListView> {
method NewsPageAdapter (line 18) | public NewsPageAdapter(Context context) {
method getCount (line 25) | @Override
method isViewFromObject (line 30) | @Override
method getItemPosition (line 38) | @Override
method getItemObject (line 51) | @Override
method createItemView (line 56) | @Override
method onBindView (line 62) | @Override
method destroyItemView (line 71) | @Override
FILE: app/src/main/java/org/qcode/demo/ui/viewpageandlistview/RecyclablePageAdapter.java
class RecyclablePageAdapter (line 24) | public abstract class RecyclablePageAdapter<T extends View> extends Page...
method startUpdate (line 34) | @Override
method instantiateItem (line 40) | @Override
method getItemViewType (line 74) | protected int getItemViewType(int position) {
method destroyItem (line 78) | @Override
method setPrimaryItem (line 101) | @Override
method finishUpdate (line 108) | @Override
method saveState (line 114) | @Override
method restoreState (line 120) | @Override
method getItemPosition (line 126) | @Override
method onItemPositionChange (line 132) | protected void onItemPositionChange(int oldPosition, int newPosition) {
method getCreatedView (line 136) | protected T getCreatedView(int position) {
method getUsingViews (line 145) | protected List<T> getUsingViews() {
method getItemObject (line 157) | protected abstract Object getItemObject(int position);
method createItemView (line 159) | protected abstract T createItemView(int itemViewType);
method onBindView (line 161) | protected abstract void onBindView(T itemView, int position, int itemV...
method destroyItemView (line 163) | protected abstract void destroyItemView(T view);
method ensureListNotEmpty (line 166) | private void ensureListNotEmpty(int viewType) {
class ViewBundle (line 173) | private class ViewBundle {
method ViewBundle (line 177) | public ViewBundle(T view, int viewType) {
FILE: app/src/main/java/org/qcode/demo/ui/viewpageandlistview/ViewPagerAndListViewActivity.java
class ViewPagerAndListViewActivity (line 9) | public class ViewPagerAndListViewActivity extends BaseActivity {
method onCreate (line 13) | @Override
FILE: app/src/main/java/org/qcode/demo/utils/FileUtils.java
class FileUtils (line 17) | public class FileUtils {
method getDirectory (line 18) | public static String getDirectory(Context context) {
method getFileName (line 22) | public static String getFileName() {
method openFileStream (line 26) | public static OutputStream openFileStream(Context context) {
method openFileStream (line 30) | public static OutputStream openFileStream(String directory, String fil...
method writeData (line 52) | public static void writeData(OutputStream stream, String str) {
method closeStream (line 70) | public static void closeStream(OutputStream stream) {
method copyAssetFile (line 80) | public static boolean copyAssetFile(Context context, String originFile...
FILE: app/src/main/java/org/qcode/demo/utils/UITaskRunner.java
class UITaskRunner (line 9) | public class UITaskRunner {
method UITaskRunner (line 12) | private UITaskRunner() {
class SingletonHolder (line 16) | private static class SingletonHolder {
method getHandler (line 20) | public static Handler getHandler() {
FILE: app/src/main/java/org/qcode/demo/utils/UIUtil.java
class UIUtil (line 10) | public final class UIUtil {
method UIUtil (line 14) | private UIUtil() { }
method dip2px (line 19) | public static int dip2px(Context context, double dpValue) {
method showToast (line 27) | public static void showToast(Context context, String toast) {
method toast (line 32) | public static void toast(final Context context, final String msg, fina...
method toast (line 72) | public static void toast(Context context, String msg) {
Condensed preview — 154 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (288K chars).
[
{
"path": ".gitignore",
"chars": 118,
"preview": "*.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",
"chars": 727,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n <component name=\"CompilerConfiguration\">\n <resourceExt"
},
{
"path": ".idea/copyright/profiles_settings.xml",
"chars": 74,
"preview": "<component name=\"CopyrightManager\">\n <settings default=\"\" />\n</component>"
},
{
"path": ".idea/encodings.xml",
"chars": 159,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n <component name=\"Encoding\">\n <file url=\"PROJECT\" chars"
},
{
"path": ".idea/gradle.xml",
"chars": 686,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n <component name=\"GradleSettings\">\n <option name=\"linke"
},
{
"path": ".idea/misc.xml",
"chars": 882,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n <component name=\"ProjectLevelVcsManager\" settingsEditedMa"
},
{
"path": ".idea/modules.xml",
"chars": 500,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n <component name=\"ProjectModuleManager\">\n <modules>\n "
},
{
"path": ".idea/runConfigurations.xml",
"chars": 564,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n <component name=\"RunConfigurationProducerService\">\n <o"
},
{
"path": "QSkinLoaderlib/.gitignore",
"chars": 7,
"preview": "/build\n"
},
{
"path": "QSkinLoaderlib/build.gradle",
"chars": 314,
"preview": "apply plugin: 'com.android.library'\n\nandroid {\n compileSdkVersion 23\n buildToolsVersion \"23.0.1\"\n\n defaultConfi"
},
{
"path": "QSkinLoaderlib/proguard-rules.pro",
"chars": 589,
"preview": "# Add project specific ProGuard rules here.\n# By default, the flags in this file are appended to flags specified\n# You c"
},
{
"path": "QSkinLoaderlib/src/main/AndroidManifest.xml",
"chars": 118,
"preview": "<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",
"chars": 3605,
"preview": "package org.qcode.qskinloader;\n\nimport android.app.Activity;\n\n/**\n * 与Activity相关的皮肤框架逻辑处理抽象接口\n *\n * a interface defines "
},
{
"path": "QSkinLoaderlib/src/main/java/org/qcode/qskinloader/ILoadSkinListener.java",
"chars": 651,
"preview": "package org.qcode.qskinloader;\n\n/**\n * 加载皮肤过程的事件回调\n *\n * a interface defines the skin loading progress.\n *\n * qqliu\n * 2"
},
{
"path": "QSkinLoaderlib/src/main/java/org/qcode/qskinloader/IResourceLoader.java",
"chars": 689,
"preview": "package org.qcode.qskinloader;\n\nimport org.qcode.qskinloader.resourceloader.ILoadResourceCallback;\n\n/**\n * 皮肤资源加载器接口\n *\n"
},
{
"path": "QSkinLoaderlib/src/main/java/org/qcode/qskinloader/IResourceManager.java",
"chars": 2686,
"preview": "package org.qcode.qskinloader;\n\nimport android.content.res.ColorStateList;\nimport android.content.res.Resources;\nimport "
},
{
"path": "QSkinLoaderlib/src/main/java/org/qcode/qskinloader/ISkinActivity.java",
"chars": 936,
"preview": "package org.qcode.qskinloader;\n\n/**\n * 支持换肤的Activity应实现的接口\n *\n * A interface indicates that this activity supports skin "
},
{
"path": "QSkinLoaderlib/src/main/java/org/qcode/qskinloader/ISkinAttrHandler.java",
"chars": 539,
"preview": "package org.qcode.qskinloader;\n\nimport android.view.View;\n\nimport org.qcode.qskinloader.entity.SkinAttr;\n\n/**\n * 皮肤属性处理器"
},
{
"path": "QSkinLoaderlib/src/main/java/org/qcode/qskinloader/ISkinAttributeParser.java",
"chars": 842,
"preview": "package org.qcode.qskinloader;\n\nimport android.content.Context;\nimport android.util.AttributeSet;\nimport android.view.Vi"
},
{
"path": "QSkinLoaderlib/src/main/java/org/qcode/qskinloader/ISkinManager.java",
"chars": 3387,
"preview": "package org.qcode.qskinloader;\n\nimport android.content.Context;\nimport android.view.View;\n\nimport org.qcode.qskinloader."
},
{
"path": "QSkinLoaderlib/src/main/java/org/qcode/qskinloader/ISkinViewHelper.java",
"chars": 3910,
"preview": "package org.qcode.qskinloader;\n\nimport org.qcode.qskinloader.entity.DynamicAttr;\nimport org.qcode.qskinloader.entity.Ski"
},
{
"path": "QSkinLoaderlib/src/main/java/org/qcode/qskinloader/IViewCreateListener.java",
"chars": 807,
"preview": "package org.qcode.qskinloader;\n\nimport android.content.Context;\nimport android.util.AttributeSet;\nimport android.view.Vi"
},
{
"path": "QSkinLoaderlib/src/main/java/org/qcode/qskinloader/IWindowViewManager.java",
"chars": 1820,
"preview": "package org.qcode.qskinloader;\n\nimport android.view.View;\n\nimport java.util.List;\n\n/**\n * 皮肤框架对直接加载在WindowManager上的View的"
},
{
"path": "QSkinLoaderlib/src/main/java/org/qcode/qskinloader/SkinManager.java",
"chars": 2155,
"preview": "package org.qcode.qskinloader;\n\nimport android.view.View;\n\nimport org.qcode.qskinloader.impl.ActivitySkinEventHandlerImp"
},
{
"path": "QSkinLoaderlib/src/main/java/org/qcode/qskinloader/attrhandler/BackgroundAttrHandler.java",
"chars": 963,
"preview": "package org.qcode.qskinloader.attrhandler;\n\nimport android.graphics.drawable.Drawable;\nimport android.view.View;\n\nimport"
},
{
"path": "QSkinLoaderlib/src/main/java/org/qcode/qskinloader/attrhandler/DividerAttrHandler.java",
"chars": 1102,
"preview": "package org.qcode.qskinloader.attrhandler;\n\nimport android.graphics.drawable.Drawable;\nimport android.view.View;\nimport "
},
{
"path": "QSkinLoaderlib/src/main/java/org/qcode/qskinloader/attrhandler/DrawableLeftAttrHandler.java",
"chars": 1176,
"preview": "package org.qcode.qskinloader.attrhandler;\n\nimport android.graphics.drawable.Drawable;\nimport android.view.View;\nimport "
},
{
"path": "QSkinLoaderlib/src/main/java/org/qcode/qskinloader/attrhandler/ListSelectorAttrHandler.java",
"chars": 1106,
"preview": "package org.qcode.qskinloader.attrhandler;\n\n/***\n * ListView selector属性的换肤支持(android:listSelector)\n */\n\nimport android.g"
},
{
"path": "QSkinLoaderlib/src/main/java/org/qcode/qskinloader/attrhandler/RecyclerViewClearSubAttrHandler.java",
"chars": 1933,
"preview": "package org.qcode.qskinloader.attrhandler;\n\nimport android.support.v7.widget.RecyclerView;\nimport android.view.View;\n\nim"
},
{
"path": "QSkinLoaderlib/src/main/java/org/qcode/qskinloader/attrhandler/ShadowAttrHandler.java",
"chars": 1860,
"preview": "package org.qcode.qskinloader.attrhandler;\n\nimport android.graphics.Color;\nimport android.graphics.PorterDuff;\nimport an"
},
{
"path": "QSkinLoaderlib/src/main/java/org/qcode/qskinloader/attrhandler/SkinAttrFactory.java",
"chars": 3393,
"preview": "package org.qcode.qskinloader.attrhandler;\n\n\nimport android.text.TextUtils;\n\nimport org.qcode.qskinloader.ISkinAttrHandl"
},
{
"path": "QSkinLoaderlib/src/main/java/org/qcode/qskinloader/attrhandler/SkinAttrUtils.java",
"chars": 2358,
"preview": "package org.qcode.qskinloader.attrhandler;\n\nimport android.graphics.drawable.ColorDrawable;\nimport android.graphics.draw"
},
{
"path": "QSkinLoaderlib/src/main/java/org/qcode/qskinloader/attrhandler/SrcAttrHandler.java",
"chars": 1869,
"preview": "package org.qcode.qskinloader.attrhandler;\n\nimport android.graphics.drawable.AnimationDrawable;\nimport android.graphics."
},
{
"path": "QSkinLoaderlib/src/main/java/org/qcode/qskinloader/attrhandler/TextColorAttrHandler.java",
"chars": 1533,
"preview": "package org.qcode.qskinloader.attrhandler;\n\nimport android.content.res.ColorStateList;\nimport android.view.View;\nimport "
},
{
"path": "QSkinLoaderlib/src/main/java/org/qcode/qskinloader/attrhandler/TextColorHintAttrHandler.java",
"chars": 1439,
"preview": "package org.qcode.qskinloader.attrhandler;\n\nimport android.content.res.ColorStateList;\nimport android.view.View;\nimport "
},
{
"path": "QSkinLoaderlib/src/main/java/org/qcode/qskinloader/base/observable/INotifyUpdate.java",
"chars": 387,
"preview": "package org.qcode.qskinloader.base.observable;\n\n/**\n * 通知观察者更新\n * qqliu\n * 2016/9/19.\n */\npublic interface INotifyUpdate"
},
{
"path": "QSkinLoaderlib/src/main/java/org/qcode/qskinloader/base/observable/IObservable.java",
"chars": 475,
"preview": "package org.qcode.qskinloader.base.observable;\n\n/**\n * 可观察对象的抽象接口,T为观察者\n * qqliu\n * 2016/9/19.\n */\npublic interface IObs"
},
{
"path": "QSkinLoaderlib/src/main/java/org/qcode/qskinloader/base/observable/Observable.java",
"chars": 1092,
"preview": "package org.qcode.qskinloader.base.observable;\n\nimport java.util.ArrayList;\n\n/**\n * 观察者通用逻辑\n * qqliu\n * 2016/9/19.\n */\np"
},
{
"path": "QSkinLoaderlib/src/main/java/org/qcode/qskinloader/base/utils/CollectionUtils.java",
"chars": 374,
"preview": "package org.qcode.qskinloader.base.utils;\n\nimport java.util.Collection;\n\n/**\n * qqliu\n * 2016/9/25.\n */\npublic class Col"
},
{
"path": "QSkinLoaderlib/src/main/java/org/qcode/qskinloader/base/utils/HashMapCache.java",
"chars": 1319,
"preview": "package org.qcode.qskinloader.base.utils;\n\nimport java.lang.ref.WeakReference;\nimport java.util.HashMap;\n\n/**\n * 维护一个缓存M"
},
{
"path": "QSkinLoaderlib/src/main/java/org/qcode/qskinloader/base/utils/Logging.java",
"chars": 2151,
"preview": "package org.qcode.qskinloader.base.utils;\n\nimport android.util.Log;\n\npublic class Logging {\n\n\tprotected static boolean m"
},
{
"path": "QSkinLoaderlib/src/main/java/org/qcode/qskinloader/base/utils/ReflectUtils.java",
"chars": 2627,
"preview": "package org.qcode.qskinloader.base.utils;\n\nimport java.lang.reflect.Field;\n\n/***\n * 反射帮助类\n * created at 2017/12/31\n */\np"
},
{
"path": "QSkinLoaderlib/src/main/java/org/qcode/qskinloader/base/utils/StringUtils.java",
"chars": 227,
"preview": "package org.qcode.qskinloader.base.utils;\n\n/**\n * qqliu\n * 2016/9/25.\n */\npublic class StringUtils {\n public static b"
},
{
"path": "QSkinLoaderlib/src/main/java/org/qcode/qskinloader/base/utils/WeakReferenceHelper.java",
"chars": 642,
"preview": "package com.iflytek.skin.manager.base.utils;\n\nimport java.lang.ref.WeakReference;\n\n/**\n * qqliu\n * 2016/10/19.\n */\n\npubl"
},
{
"path": "QSkinLoaderlib/src/main/java/org/qcode/qskinloader/entity/DynamicAttr.java",
"chars": 1144,
"preview": "package org.qcode.qskinloader.entity;\n\n/**\n * 动态代码设置皮肤属性的实体类\n *\n * qqliu\n * 2016/9/25.\n */\npublic class DynamicAttr {\n\n "
},
{
"path": "QSkinLoaderlib/src/main/java/org/qcode/qskinloader/entity/SkinAttr.java",
"chars": 1113,
"preview": "package org.qcode.qskinloader.entity;\n\n/**\n * 皮肤指定属性及其对应的值/类型的实体类封装\n * qqliu\n * 2016/9/24.\n */\npublic class SkinAttr {\n "
},
{
"path": "QSkinLoaderlib/src/main/java/org/qcode/qskinloader/entity/SkinAttrName.java",
"chars": 651,
"preview": "package org.qcode.qskinloader.entity;\n\n/**\n * 皮肤框架内置支持的属性\n * qqliu\n * 2016/9/27.\n */\npublic class SkinAttrName {\n pub"
},
{
"path": "QSkinLoaderlib/src/main/java/org/qcode/qskinloader/entity/SkinAttrSet.java",
"chars": 1777,
"preview": "package org.qcode.qskinloader.entity;\n\nimport org.qcode.qskinloader.base.utils.CollectionUtils;\nimport org.qcode.qskinlo"
},
{
"path": "QSkinLoaderlib/src/main/java/org/qcode/qskinloader/entity/SkinConstant.java",
"chars": 591,
"preview": "package org.qcode.qskinloader.entity;\n\n/**\n * 皮肤框架的常量定义\n * qqliu\n * 2016/9/25.\n */\npublic class SkinConstant {\n /***\n"
},
{
"path": "QSkinLoaderlib/src/main/java/org/qcode/qskinloader/impl/ActivitySkinEventHandlerImpl.java",
"chars": 6936,
"preview": "package org.qcode.qskinloader.impl;\n\nimport android.app.Activity;\nimport android.content.res.Resources;\nimport android.g"
},
{
"path": "QSkinLoaderlib/src/main/java/org/qcode/qskinloader/impl/SkinAttributeParser.java",
"chars": 6088,
"preview": "package org.qcode.qskinloader.impl;\n\nimport android.content.Context;\nimport android.content.res.Resources.NotFoundExcept"
},
{
"path": "QSkinLoaderlib/src/main/java/org/qcode/qskinloader/impl/SkinInflaterFactoryImpl.java",
"chars": 3884,
"preview": "package org.qcode.qskinloader.impl;\n\nimport android.content.Context;\nimport android.util.AttributeSet;\nimport android.vi"
},
{
"path": "QSkinLoaderlib/src/main/java/org/qcode/qskinloader/impl/SkinManagerImpl.java",
"chars": 7434,
"preview": "package org.qcode.qskinloader.impl;\n\nimport android.content.Context;\nimport android.os.AsyncTask;\nimport android.view.Vi"
},
{
"path": "QSkinLoaderlib/src/main/java/org/qcode/qskinloader/impl/SkinViewHelperImpl.java",
"chars": 5488,
"preview": "package org.qcode.qskinloader.impl;\n\nimport android.content.res.Resources;\nimport android.support.annotation.NonNull;\nim"
},
{
"path": "QSkinLoaderlib/src/main/java/org/qcode/qskinloader/impl/ViewSkinTagHelper.java",
"chars": 1204,
"preview": "package org.qcode.qskinloader.impl;\n\nimport android.view.View;\n\nimport org.qcode.qskinloader.R;\nimport org.qcode.qskinlo"
},
{
"path": "QSkinLoaderlib/src/main/java/org/qcode/qskinloader/impl/WindowViewManager.java",
"chars": 3197,
"preview": "package org.qcode.qskinloader.impl;\n\nimport android.view.View;\n\nimport org.qcode.qskinloader.IWindowViewManager;\nimport "
},
{
"path": "QSkinLoaderlib/src/main/java/org/qcode/qskinloader/resourceloader/ILoadResourceCallback.java",
"chars": 588,
"preview": "package org.qcode.qskinloader.resourceloader;\n\nimport org.qcode.qskinloader.IResourceManager;\n\n/**\n * 加载皮肤资源的回调\n * qqliu"
},
{
"path": "QSkinLoaderlib/src/main/java/org/qcode/qskinloader/resourceloader/ResourceManager.java",
"chars": 4592,
"preview": "package org.qcode.qskinloader.resourceloader;\n\nimport android.annotation.SuppressLint;\nimport android.content.Context;\ni"
},
{
"path": "QSkinLoaderlib/src/main/java/org/qcode/qskinloader/resourceloader/impl/APKResourceLoader.java",
"chars": 3858,
"preview": "package org.qcode.qskinloader.resourceloader.impl;\n\nimport android.content.Context;\nimport android.content.pm.PackageInf"
},
{
"path": "QSkinLoaderlib/src/main/java/org/qcode/qskinloader/resourceloader/impl/APKResourceManager.java",
"chars": 3896,
"preview": "package org.qcode.qskinloader.resourceloader.impl;\n\nimport android.annotation.SuppressLint;\nimport android.content.Conte"
},
{
"path": "QSkinLoaderlib/src/main/java/org/qcode/qskinloader/resourceloader/impl/ConfigChangeResourceLoader.java",
"chars": 1863,
"preview": "package org.qcode.qskinloader.resourceloader.impl;\n\nimport android.content.Context;\nimport android.content.res.Configura"
},
{
"path": "QSkinLoaderlib/src/main/java/org/qcode/qskinloader/resourceloader/impl/ConfigChangeResourceManager.java",
"chars": 2066,
"preview": "package org.qcode.qskinloader.resourceloader.impl;\n\nimport android.annotation.SuppressLint;\nimport android.content.Conte"
},
{
"path": "QSkinLoaderlib/src/main/java/org/qcode/qskinloader/resourceloader/impl/SuffixResourceLoader.java",
"chars": 1134,
"preview": "package org.qcode.qskinloader.resourceloader.impl;\n\nimport android.content.Context;\n\nimport org.qcode.qskinloader.IResou"
},
{
"path": "QSkinLoaderlib/src/main/java/org/qcode/qskinloader/resourceloader/impl/SuffixResourceManager.java",
"chars": 4147,
"preview": "package org.qcode.qskinloader.resourceloader.impl;\n\nimport android.annotation.SuppressLint;\nimport android.content.Conte"
},
{
"path": "QSkinLoaderlib/src/main/java/org/qcode/qskinloader/view/ShadowImageView.java",
"chars": 1749,
"preview": "package org.qcode.qskinloader.view;\n\nimport android.content.Context;\nimport android.graphics.Canvas;\nimport android.grap"
},
{
"path": "QSkinLoaderlib/src/main/res/values/skin_attrs.xml",
"chars": 335,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <!-- 设置此属性标识View支持换肤;this tag indicates a view supports skin chan"
},
{
"path": "QSkinLoaderlib/src/main/res/values/skin_ids.xml",
"chars": 188,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <!--皮肤属性在View内存放时的Tag id; the tag id used to save skin attrs in v"
},
{
"path": "QSkinLoaderlib/src/main/res/values/strings.xml",
"chars": 77,
"preview": "<resources>\n <string name=\"app_name\">QSkinLoaderLib</string>\n</resources>\n"
},
{
"path": "README.md",
"chars": 18270,
"preview": "# QSkinLoader换肤框架\n\n**如何在一个成熟的应用内换肤?** 请参见文章:[链接](http://blog.csdn.net/u013478336/article/details/78993969)。\n\n**README分三部"
},
{
"path": "SkinProject/.gitignore",
"chars": 118,
"preview": "*.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",
"chars": 7,
"preview": "/build\n"
},
{
"path": "SkinProject/app/build.gradle",
"chars": 786,
"preview": "apply plugin: 'com.android.application'\n\nandroid {\n compileSdkVersion 23\n buildToolsVersion \"23.0.1\"\n defaultCo"
},
{
"path": "SkinProject/app/proguard-rules.pro",
"chars": 667,
"preview": "# Add project specific ProGuard rules here.\n# By default, the flags in this file are appended to flags specified\n# in E:"
},
{
"path": "SkinProject/app/src/main/AndroidManifest.xml",
"chars": 490,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n package="
},
{
"path": "SkinProject/app/src/main/java/org/qcode/skinproject/MainActivity.java",
"chars": 141,
"preview": "package org.qcode.skinproject;\n\nimport android.app.Activity;\n\n/**\n * qqliu\n * 2016/11/9.\n */\n\npublic class MainActivity "
},
{
"path": "SkinProject/app/src/main/res/drawable/btn_bg.xml",
"chars": 295,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:sha"
},
{
"path": "SkinProject/app/src/main/res/drawable/drawable_float_view.xml",
"chars": 249,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:sha"
},
{
"path": "SkinProject/app/src/main/res/drawable/news_item_selector.xml",
"chars": 301,
"preview": "<?xml version = \"1.0\" encoding = \"utf-8\"?>\n<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n <i"
},
{
"path": "SkinProject/app/src/main/res/values/colors.xml",
"chars": 511,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <color name=\"gray_50\">#84878c</color>\n\n <color name=\"color_whi"
},
{
"path": "SkinProject/build.gradle",
"chars": 498,
"preview": "// Top-level build file where you can add configuration options common to all sub-projects/modules.\n\nbuildscript {\n r"
},
{
"path": "SkinProject/gradle.properties",
"chars": 849,
"preview": "## Project-wide Gradle settings.\n#\n# For more details on how to configure your build environment visit\n# http://www.grad"
},
{
"path": "SkinProject/gradlew",
"chars": 4971,
"preview": "#!/usr/bin/env bash\n\n##############################################################################\n##\n## Gradle start "
},
{
"path": "SkinProject/gradlew.bat",
"chars": 2314,
"preview": "@if \"%DEBUG%\" == \"\" @echo off\n@rem ##########################################################################\n@rem\n@rem "
},
{
"path": "SkinProject/settings.gradle",
"chars": 15,
"preview": "include ':app'\n"
},
{
"path": "app/.gitignore",
"chars": 7,
"preview": "/build\n"
},
{
"path": "app/build.gradle",
"chars": 908,
"preview": "apply plugin: 'com.android.application'\n\nandroid {\n compileSdkVersion 23\n buildToolsVersion \"23.0.1\"\n defaultCo"
},
{
"path": "app/proguard-rules.pro",
"chars": 667,
"preview": "# Add project specific ProGuard rules here.\n# By default, the flags in this file are appended to flags specified\n# in E:"
},
{
"path": "app/src/main/AndroidManifest.xml",
"chars": 1721,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n package="
},
{
"path": "app/src/main/java/org/qcode/demo/BaseActivity.java",
"chars": 4387,
"preview": "package org.qcode.demo;\n\nimport android.app.Activity;\nimport android.content.Context;\nimport android.graphics.PixelForma"
},
{
"path": "app/src/main/java/org/qcode/demo/MainActivity.java",
"chars": 1700,
"preview": "package org.qcode.demo;\n\nimport android.content.Intent;\nimport android.os.Bundle;\nimport android.view.View;\n\nimport org."
},
{
"path": "app/src/main/java/org/qcode/demo/SkinChangeSwitchView.java",
"chars": 3290,
"preview": "package org.qcode.demo;\n\nimport android.content.Context;\nimport android.view.Gravity;\nimport android.view.View;\nimport a"
},
{
"path": "app/src/main/java/org/qcode/demo/SkinDemoApp.java",
"chars": 756,
"preview": "package org.qcode.demo;\n\nimport android.app.Application;\nimport android.content.Context;\n\nimport org.qcode.demo.base.Set"
},
{
"path": "app/src/main/java/org/qcode/demo/base/Settings.java",
"chars": 7017,
"preview": "package org.qcode.demo.base;\n\nimport android.content.Context;\nimport android.content.SharedPreferences;\nimport android.c"
},
{
"path": "app/src/main/java/org/qcode/demo/skin/SkinChangeHelper.java",
"chars": 5282,
"preview": "package org.qcode.demo.skin;\n\nimport android.content.Context;\n\nimport org.qcode.demo.SkinDemoApp;\nimport org.qcode.demo."
},
{
"path": "app/src/main/java/org/qcode/demo/skin/SkinConfigHelper.java",
"chars": 715,
"preview": "package org.qcode.demo.skin;\n\nimport org.qcode.demo.base.Settings;\n\npublic class SkinConfigHelper {\n\n /***\n * 获取当"
},
{
"path": "app/src/main/java/org/qcode/demo/skin/SkinConstant.java",
"chars": 356,
"preview": "package org.qcode.demo.skin;\n\n/**\n * qqliu\n * 2016/10/8.\n */\n\npublic class SkinConstant {\n\n public static final Strin"
},
{
"path": "app/src/main/java/org/qcode/demo/skin/SkinUtils.java",
"chars": 1327,
"preview": "package org.qcode.demo.skin;\n\nimport android.content.Context;\n\nimport org.qcode.demo.utils.FileUtils;\nimport org.qcode.q"
},
{
"path": "app/src/main/java/org/qcode/demo/ui/customattr/CustomAttrViewActivity.java",
"chars": 908,
"preview": "package org.qcode.demo.ui.customattr;\n\nimport android.os.Bundle;\n\nimport org.qcode.demo.BaseActivity;\nimport org.qcode.q"
},
{
"path": "app/src/main/java/org/qcode/demo/ui/customattr/CustomTextView.java",
"chars": 858,
"preview": "package org.qcode.demo.ui.customattr;\n\nimport android.content.Context;\nimport android.content.res.TypedArray;\nimport and"
},
{
"path": "app/src/main/java/org/qcode/demo/ui/customattr/DefBackgroundAttrHandler.java",
"chars": 1234,
"preview": "package org.qcode.demo.ui.customattr;\n\nimport android.graphics.drawable.Drawable;\nimport android.view.View;\n\nimport org."
},
{
"path": "app/src/main/java/org/qcode/demo/ui/customattr/DefTextColorAttrHandler.java",
"chars": 1844,
"preview": "package org.qcode.demo.ui.customattr;\n\nimport android.content.res.ColorStateList;\nimport android.content.res.Resources;\n"
},
{
"path": "app/src/main/java/org/qcode/demo/ui/dynamicaddview/DynamicAddViewActivity.java",
"chars": 1817,
"preview": "package org.qcode.demo.ui.dynamicaddview;\n\nimport android.os.Bundle;\nimport android.view.ViewGroup;\nimport android.widge"
},
{
"path": "app/src/main/java/org/qcode/demo/ui/gridview/GridViewActivity.java",
"chars": 611,
"preview": "package org.qcode.demo.ui.gridview;\n\nimport android.os.Bundle;\nimport android.widget.GridView;\n\nimport org.qcode.demo.Ba"
},
{
"path": "app/src/main/java/org/qcode/demo/ui/otherscene/CustomDialog.java",
"chars": 2707,
"preview": "package org.qcode.demo.ui.otherscene;\n\nimport android.app.Dialog;\nimport android.content.Context;\nimport android.content"
},
{
"path": "app/src/main/java/org/qcode/demo/ui/otherscene/FloatView.java",
"chars": 879,
"preview": "package org.qcode.demo.ui.otherscene;\n\nimport android.content.Context;\nimport android.widget.ImageView;\n\nimport org.qcod"
},
{
"path": "app/src/main/java/org/qcode/demo/ui/otherscene/OtherSceneActivity.java",
"chars": 5101,
"preview": "package org.qcode.demo.ui.otherscene;\n\nimport android.app.Dialog;\nimport android.content.Context;\nimport android.graphic"
},
{
"path": "app/src/main/java/org/qcode/demo/ui/otherscene/SpannableSkinAttr.java",
"chars": 524,
"preview": "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 * "
},
{
"path": "app/src/main/java/org/qcode/demo/ui/otherscene/SpannableSkinAttrHandler.java",
"chars": 1783,
"preview": "package org.qcode.demo.ui.otherscene;\n\nimport android.content.res.ColorStateList;\nimport android.content.res.Resources;\n"
},
{
"path": "app/src/main/java/org/qcode/demo/ui/recyclerview/DataRecyclerViewAdapter.java",
"chars": 2855,
"preview": "package org.qcode.demo.ui.recyclerview;\n\nimport android.content.Context;\nimport android.support.v7.widget.RecyclerView;\n"
},
{
"path": "app/src/main/java/org/qcode/demo/ui/recyclerview/RecyclerViewActivity.java",
"chars": 790,
"preview": "package org.qcode.demo.ui.recyclerview;\n\nimport android.os.Bundle;\nimport android.support.v7.widget.LinearLayoutManager;"
},
{
"path": "app/src/main/java/org/qcode/demo/ui/viewpageandlistview/DataListAdapter.java",
"chars": 1743,
"preview": "package org.qcode.demo.ui.viewpageandlistview;\n\nimport android.content.Context;\nimport android.view.LayoutInflater;\nimpo"
},
{
"path": "app/src/main/java/org/qcode/demo/ui/viewpageandlistview/NewsPageAdapter.java",
"chars": 1910,
"preview": "package org.qcode.demo.ui.viewpageandlistview;\n\nimport android.content.Context;\nimport android.view.View;\nimport android"
},
{
"path": "app/src/main/java/org/qcode/demo/ui/viewpageandlistview/RecyclablePageAdapter.java",
"chars": 5903,
"preview": "package org.qcode.demo.ui.viewpageandlistview;\n\nimport android.os.Parcelable;\nimport android.support.v4.view.PagerAdapte"
},
{
"path": "app/src/main/java/org/qcode/demo/ui/viewpageandlistview/ViewPagerAndListViewActivity.java",
"chars": 604,
"preview": "package org.qcode.demo.ui.viewpageandlistview;\n\nimport android.os.Bundle;\nimport android.support.v4.view.ViewPager;\n\nimp"
},
{
"path": "app/src/main/java/org/qcode/demo/utils/FileUtils.java",
"chars": 3601,
"preview": "package org.qcode.demo.utils;\n\nimport android.content.Context;\nimport android.text.TextUtils;\n\nimport java.io.BufferedOu"
},
{
"path": "app/src/main/java/org/qcode/demo/utils/UITaskRunner.java",
"chars": 469,
"preview": "package org.qcode.demo.utils;\n\nimport android.os.Handler;\nimport android.os.Looper;\n\n/**\n * 在主线程中执行runnable\n */\npublic c"
},
{
"path": "app/src/main/java/org/qcode/demo/utils/UIUtil.java",
"chars": 1821,
"preview": "package org.qcode.demo.utils;\n\nimport android.content.Context;\nimport android.os.Looper;\nimport android.widget.Toast;\n\n/"
},
{
"path": "app/src/main/res/drawable/btn_bg.xml",
"chars": 295,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:sha"
},
{
"path": "app/src/main/res/drawable/btn_bg_night.xml",
"chars": 307,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:sha"
},
{
"path": "app/src/main/res/drawable/drawable_float_view.xml",
"chars": 249,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:sha"
},
{
"path": "app/src/main/res/drawable/drawable_float_view_night.xml",
"chars": 255,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:sha"
},
{
"path": "app/src/main/res/drawable/news_item_selector.xml",
"chars": 301,
"preview": "<?xml version = \"1.0\" encoding = \"utf-8\"?>\n<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n <i"
},
{
"path": "app/src/main/res/drawable/news_item_selector_night.xml",
"chars": 313,
"preview": "<?xml version = \"1.0\" encoding = \"utf-8\"?>\n<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n <i"
},
{
"path": "app/src/main/res/drawable-night/btn_bg.xml",
"chars": 295,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:sha"
},
{
"path": "app/src/main/res/drawable-night/drawable_float_view.xml",
"chars": 249,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:sha"
},
{
"path": "app/src/main/res/drawable-night/news_item_selector.xml",
"chars": 301,
"preview": "<?xml version = \"1.0\" encoding = \"utf-8\"?>\n<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n <i"
},
{
"path": "app/src/main/res/layout/activity_base_activity.xml",
"chars": 2406,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n xmln"
},
{
"path": "app/src/main/res/layout/activity_custom_attr_test.xml",
"chars": 2007,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n xmln"
},
{
"path": "app/src/main/res/layout/activity_dynamic_add_view.xml",
"chars": 1148,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n xmln"
},
{
"path": "app/src/main/res/layout/activity_grid_view.xml",
"chars": 979,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n xmln"
},
{
"path": "app/src/main/res/layout/activity_main.xml",
"chars": 3127,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<ScrollView xmlns:android=\"http://schemas.android.com/apk/res/android\"\n xmlns:"
},
{
"path": "app/src/main/res/layout/activity_other_scene.xml",
"chars": 2821,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n xmln"
},
{
"path": "app/src/main/res/layout/activity_recycler_view.xml",
"chars": 840,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n xmln"
},
{
"path": "app/src/main/res/layout/activity_viewpager_listview.xml",
"chars": 1191,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n xmln"
},
{
"path": "app/src/main/res/layout/grid_item_view.xml",
"chars": 742,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n xmln"
},
{
"path": "app/src/main/res/layout/layout_dialog_custom.xml",
"chars": 1797,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n xmln"
},
{
"path": "app/src/main/res/layout/layout_popwindow.xml",
"chars": 764,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n xmln"
},
{
"path": "app/src/main/res/layout/list_item_view.xml",
"chars": 805,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n xmln"
},
{
"path": "app/src/main/res/values/colors.xml",
"chars": 591,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <color name=\"gray_50\">#505050</color>\n\n <color name=\"color_whi"
},
{
"path": "app/src/main/res/values/colors_night.xml",
"chars": 565,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <color name=\"gray_50_night\">#84878c</color>\n\n <color name=\"col"
},
{
"path": "app/src/main/res/values/dimens.xml",
"chars": 211,
"preview": "<resources>\n <!-- Default screen margins, per the Android Design guidelines. -->\n <dimen name=\"activity_horizontal"
},
{
"path": "app/src/main/res/values/news_attr.xml",
"chars": 242,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <declare-styleable name=\"app\">\n <attr name=\"defTextColor\" "
},
{
"path": "app/src/main/res/values/strings.xml",
"chars": 88,
"preview": "<resources>\n <string name=\"app_name\">NightModeDemo_QSkinLoader</string>\n</resources>\n"
},
{
"path": "app/src/main/res/values/styles.xml",
"chars": 400,
"preview": "<resources>\n\n <style name=\"ActivityTheme\">\n <item name=\"android:windowBackground\">@color/color_white</item>\n "
},
{
"path": "app/src/main/res/values-night/colors_night.xml",
"chars": 511,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <color name=\"gray_50\">#84878c</color>\n\n <color name=\"color_whi"
},
{
"path": "app/src/main/res/values-w820dp/dimens.xml",
"chars": 358,
"preview": "<resources>\n <!-- Example customization of dimensions originally defined in res/values/dimens.xml\n (such as s"
},
{
"path": "build.gradle",
"chars": 498,
"preview": "// Top-level build file where you can add configuration options common to all sub-projects/modules.\n\nbuildscript {\n r"
},
{
"path": "gradle/wrapper/gradle-wrapper.properties",
"chars": 233,
"preview": "#Mon Dec 28 10:00:20 PST 2015\ndistributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\nzipStoreBase=GRADLE_USER_"
},
{
"path": "gradle.properties",
"chars": 849,
"preview": "## Project-wide Gradle settings.\n#\n# For more details on how to configure your build environment visit\n# http://www.grad"
},
{
"path": "gradlew",
"chars": 4971,
"preview": "#!/usr/bin/env bash\n\n##############################################################################\n##\n## Gradle start "
},
{
"path": "gradlew.bat",
"chars": 2314,
"preview": "@if \"%DEBUG%\" == \"\" @echo off\n@rem ##########################################################################\n@rem\n@rem "
},
{
"path": "settings.gradle",
"chars": 34,
"preview": "include ':app', ':QSkinLoaderlib'\n"
}
]
// ... and 2 more files (download for full content)
About this extraction
This page contains the full source code of the qqliu10u/QSkinLoader GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 154 files (242.6 KB), approximately 65.8k tokens, and a symbol index with 498 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.