PaintedSkin
A solution to Android App skin framework, very low invasion and learning costs.Copy the code
Frame effect video
The latest version
The module | instructions | version |
---|---|---|
PaintedSkin | Skin core pack | 3.1.6 |
StandardPlugin | Plug-in package to reduce code intrusion | 3.1.6 |
AutoPlugin | Fully automatic plug-in package | 3.1.6 |
ConstraintLayoutCompat | ConstraintLayout Skin compatible package | 3.1.6 |
TypefacePlugin | Font replacement plugin | 3.1.6 |
Give the project address to Star if you like!
Framework Implementation Principle
TODO
Function is introduced
- Supports XML full View peels
- Support for XML specified View peels
- Support code to create View peels
- Support custom View, View provided by the tripartite library, custom attribute skin
- Supports most basic View peels
- Support differentiated skin change (suitable for partial View festival skin change)
- [support global dynamic font replacement](#TypefacePlugin used)
- Support for intercepting View creation through interceptors
- Support Androidx and support
- Support custom extensions
- Does not conflict with other libraries that rely on LayoutInflater.Factory
use
Add the dependent
- In the engineering of
build.gradle
Add:
buildscript {
repositories {
maven { url "https://jitpack.io" } // Must be added
}
dependencies{...classpath 'com. Hujiang. Aspectjx: gradle - android plugin - aspectjx: 2.0.10' // If you do not use AutoPlugin, do not add it
}
allprojects {
maven { url "https://jitpack.io" } // Must be added}}Copy the code
- If you want to use
AutoPlugin
In the project,app
thebuild.gradle
Add:
apply plugin: 'android-aspectjx'
android {
...
}
Copy the code
- In the project
app
thebuild.gradle
Add :: to file
dependencies {
// Dependent reflection library
implementation 'com. Making. CoderAlee: Reflex: 1.2.0'
/ / core library
implementation 'com.github.CoderAlee.PaintedSkin:PaintedSkin:TAG'
implementation 'com.github.CoderAlee.PaintedSkin:StandardPlugin:TAG'
// StandardPlugin and AutoPlugin only need to be added
annotationProcessor 'com.github.CoderAlee.PaintedSkin:AopPlugin:TAG'
implementation 'com.github.CoderAlee.PaintedSkin:AopPlugin:TAG'
// Import if the project ConstraintLayout needs to be skinned
implementation 'com.github.CoderAlee.PaintedSkin:ConstraintLayoutCompat:TAG'
// when the font library needs to be replaced
implementation 'com.github.CoderAlee.PaintedSkin:TypefacePlugin:TAG'. }Copy the code
Run the configure
PaintedSkin supports three peels:
Skinmode. REPLACE_ALL All views participate in skinmode. View with skin:enable=”false” tag will not participate in skinmode. skinmode. REPLACE_ALL
Skinmode. REPLACE_MARKED Only views with **skin:enable=”true”** tagged participated in skinmode. REPLACE_MARKED
Skinmode. DO_NOT_REPLACE None of the views are involved in peels
API:
public final class App extends Application {
static{ Config.getInstance().setSkinMode(Config.SkinMode.REPLACE_ALL); }}Copy the code
PaintedSkin supports debug mode and strict mode:
In debugging mode, logs of some key nodes within the framework and the execution time of skin changing task will be output;
In strict mode, an exception is thrown if an error occurs within the framework.
API:
public final class App extends Application {
static {
Config.getInstance().setEnableDebugMode(false);
Config.getInstance().setEnableStrictMode(false); }}Copy the code
The plug-in USES
StandardPlugin use:
public final class App extends Application {
@Override
public void onCreate(a) {
super.onCreate();
WindowManager.getInstance().init(this.newOptionFactory()); }}Copy the code
final class OptionFactory implements IOptionFactory {
@Override
public int defaultTheme(a) {
return 0;
}
@Override
public IThemeSkinOption requireOption(int theme) {
switch (theme) {
case 1:
return new NightOption();
default:
return null; }}}Copy the code
The AutoPlugin no longer requires the developer to invoke the initialization code and simply adds the annotation ** @skin ** to the implementation class that implements the IOptionFactory interface:
@Skin
public final class OptionFactory implements IOptionFactory {
@Override
public int defaultTheme(a) {
return 0;
}
@Override
public IThemeSkinOption requireOption(int theme) {
switch (theme) {
case 1:
return new NightOption();
default:
return null; }}}Copy the code
The topic configuration
class NightOption implements IThemeSkinOption {
@Override
public LinkedHashSet<String> getStandardSkinPackPath(a) {
LinkedHashSet<String> pathSet = new LinkedHashSet<>();
pathSet.add("/sdcard/night.skin");
returnpathSet; }}Copy the code
In the skin
ThemeSkinService.getInstance().switchThemeSkin(int theme);
Copy the code
Skin pack construction
-
Create an Android Application project
-
The skin project package name cannot be the same as the host application package name
-
Place the resources that need to be skinned in the corresponding directory of RES
For example, Button text color
APK in the res/values/colors. XML
<color name="textColor">#FFFFFFFF</color> Copy the code
Res /values/colors.xml in the skin package
<color name="textColor">#FF000000</color> Copy the code
For example, the Button background image
APK in the res/mipmap bg_button. PNG
Res /mipmap/bg_button.png in skin pack
-
Add to the build.gradle file of the skin pack project:
applicationVariants.all { variant -> variant.outputs.all { output -> outputFileName = "xxx.skin"}}Copy the code
Dynamically create View peels
The core interface WindowManager. GetInstance (). GetWindowProxy (getContext ()). AddEnabledThemeSkinView (View, SkinElement);
TextView textView = new TextView(getContext());
textView.setTextColor(getResources().getColor(R.color.textColor));
textView.setText("Dynamically create View to participate in skin change");
WindowManager.getInstance().getWindowProxy(getContext()).addEnabledThemeSkinView(textView, new SkinElement("textColor", R.color.textColor));
layout.addView(textView);
Copy the code
Advanced usage
Intercept the View creation process
ThemeSkinService.getInstance().getCreateViewInterceptor().add(new LayoutInflater.Factory2() {
@Nullable
@Override
public View onCreateView(@Nullable View parent, @NonNull String name, @NonNull Context context, @NonNull AttributeSet attrs) {
return onCreateView(name, context, attrs);
}
@Nullable
@Override
public View onCreateView(@NonNull String name, @NonNull Context context, @NonNull AttributeSet attrs) {
if (TextUtils.equals(name,"TextView")) {return new Button(context, attrs);
}
return null; }});Copy the code
You can do a lot of things by intercepting the View creation process. For example, this code can replace a global TextView with a Button. This is much faster and more convenient than making changes one by one in XML. This is how Google can replace a Button with an AppCompatButton. AppCompatDelegate is the same technical solution.
Custom View, tripartite library View skin
When a custom View or a tripartite library View has a custom property that needs to be skinned:
- Implement IThemeSkinExecutorBuilder interfaces for parsing support for a skin and create the corresponding attribute for skin actuators. See the DefaultExecutorBuilder that comes with the framework:
@RestrictTo(RestrictTo.Scope.LIBRARY)
public final class DefaultExecutorBuilder implements IThemeSkinExecutorBuilder {
/** ** Attribute background */ supported by peels
@RestrictTo(RestrictTo.Scope.LIBRARY)
public static final String ATTRIBUTE_BACKGROUND = "background";
/** ** Peels the supported properties of the foreground color */
@RestrictTo(RestrictTo.Scope.LIBRARY)
public static final String ATTRIBUTE_FOREGROUND = "foreground";
/** ** The supported properties of the skin font color */
@RestrictTo(RestrictTo.Scope.LIBRARY)
public static final String ATTRIBUTE_TEXT_COLOR = "textColor";
/** ** Attributes supported by peels indicate font colors */
@RestrictTo(RestrictTo.Scope.LIBRARY)
public static final String ATTRIBUTE_TEXT_COLOR_HINT = "textColorHint";
/** * The attribute supported by peels highlights the background color */ when selected
@RestrictTo(RestrictTo.Scope.LIBRARY)
public static final String ATTRIBUTE_TEXT_COLOR_HIGH_LIGHT = "textColorHighlight";
/** ** The color of the attribute link supported by peels */
@RestrictTo(RestrictTo.Scope.LIBRARY)
public static final String ATTRIBUTE_TEXT_COLOR_LINK = "textColorLink";
/** ** Properties progress bar background */
@RestrictTo(RestrictTo.Scope.LIBRARY)
public static final String ATTRIBUTE_PROGRESS_DRAWABLE = "progressDrawable";
/** ** The property supported by peels is ListView split line */
@RestrictTo(RestrictTo.Scope.LIBRARY)
public static final String ATTRIBUTE_LIST_VIEW_DIVIDER = "divider";
/** * The properties supported by peels are padded */
@RestrictTo(RestrictTo.Scope.LIBRARY)
public static final String ATTRIBUTE_SRC = "src";
/** ** Properties button background */ supported by peels
@RestrictTo(RestrictTo.Scope.LIBRARY)
public static final String ATTRIBUTE_BUTTON = "button";
private static final Map<Integer, String> SUPPORT_ATTR = new HashMap<>();
static {
SUPPORT_ATTR.put(R.styleable.BasicSupportAttr_android_background, ATTRIBUTE_BACKGROUND);
SUPPORT_ATTR.put(R.styleable.BasicSupportAttr_android_foreground, ATTRIBUTE_FOREGROUND);
SUPPORT_ATTR.put(R.styleable.BasicSupportAttr_android_textColor, ATTRIBUTE_TEXT_COLOR);
SUPPORT_ATTR.put(R.styleable.BasicSupportAttr_android_textColorHint, ATTRIBUTE_TEXT_COLOR_HINT);
SUPPORT_ATTR.put(R.styleable.BasicSupportAttr_android_textColorHighlight, ATTRIBUTE_TEXT_COLOR_HIGH_LIGHT);
SUPPORT_ATTR.put(R.styleable.BasicSupportAttr_android_textColorLink, ATTRIBUTE_TEXT_COLOR_LINK);
SUPPORT_ATTR.put(R.styleable.BasicSupportAttr_android_progressDrawable, ATTRIBUTE_PROGRESS_DRAWABLE);
SUPPORT_ATTR.put(R.styleable.BasicSupportAttr_android_divider, ATTRIBUTE_LIST_VIEW_DIVIDER);
SUPPORT_ATTR.put(R.styleable.BasicSupportAttr_android_src, ATTRIBUTE_SRC);
SUPPORT_ATTR.put(R.styleable.BasicSupportAttr_android_button, ATTRIBUTE_BUTTON);
}
/** * Parse the attribute ** that supports peels@param context {@link Context}
* @param attributeSet {@link AttributeSet}
* @return {@link SkinElement}
*/
@RestrictTo(RestrictTo.Scope.LIBRARY)
@Override
public Set<SkinElement> parse(@NonNull Context context, @NonNull AttributeSet attributeSet) {
TypedArray typedArray = context.obtainStyledAttributes(attributeSet, R.styleable.BasicSupportAttr);
if (null == typedArray) {
return null;
}
Set<SkinElement> elementSet = new HashSet<>();
try {
for (Integer key : SUPPORT_ATTR.keySet()) {
try {
if (typedArray.hasValue(key)) {
elementSet.add(new SkinElement(SUPPORT_ATTR.get(key), typedArray.getResourceId(key, -1))); }}catch (Throwable ignored) {
}
}
} catch (Throwable ignored) {
} finally {
typedArray.recycle();
}
return elementSet;
}
/** ** Requires a skin changer **@paramView needs to be skinned view *@paramElement Specifies the element to execute@return {@link ISkinExecutor}
*/
@Override
@RestrictTo(RestrictTo.Scope.LIBRARY)
public ISkinExecutor requireSkinExecutor(@NonNull View view, @NonNull SkinElement element) {
return BasicViewSkinExecutorFactory.requireSkinExecutor(view, element);
}
/** * Whether the attribute ** is supported@param view View
* @paramAttrName Attribute name *@returnTrue: * / support
@Override
@RestrictTo(RestrictTo.Scope.LIBRARY)
public boolean isSupportAttr(@NonNull View view, @NonNull String attrName) {
returnSUPPORT_ATTR.containsValue(attrName); }}Copy the code
- Inherit from BaseSkinExecutor to provide the corresponding peel executor:
public class ViewSkinExecutor<T extends View> extends BaseSkinExecutor<T> {
public ViewSkinExecutor(@NonNull SkinElement fullElement) {
super(fullElement);
}
@Override
protected void applyColor(@NonNull T view, @NonNull ColorStateList colorStateList, @NonNull String attrName) {
switch (attrName) {
case ATTRIBUTE_BACKGROUND:
case ATTRIBUTE_FOREGROUND:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
applyDrawable(view, new ColorStateListDrawable(colorStateList), attrName);
} else {
applyColor(view, colorStateList.getDefaultColor(), attrName);
}
break;
default:
break; }}@Override
protected void applyColor(@NonNull T view, int color, @NonNull String attrName) {
switch (attrName) {
case ATTRIBUTE_BACKGROUND:
view.setBackgroundColor(color);
break;
case ATTRIBUTE_FOREGROUND:
applyDrawable(view, new ColorDrawable(color), attrName);
break;
default:
break; }}@Override
protected void applyDrawable(@NonNull T view, @NonNull Drawable drawable, @NonNull String attrName) {
switch (attrName) {
case ATTRIBUTE_BACKGROUND:
view.setBackground(drawable);
break;
case ATTRIBUTE_FOREGROUND:
view.setForeground(drawable);
break;
default:
break; }}}Copy the code
- Add the custom ThemeSkinExecutorBuilder to the framework:
ThemeSkinService.getInstance().addThemeSkinExecutorBuilder(xxx);
Copy the code
ConstraintLayout Skin compatible package to use
public final class App extends Application {
static{ ConstraintLayoutCompat.init(); }}Copy the code
TypefacePlugin use
public final class App extends Application {
static {
TypefacePlugin.init();
}
@Override
public void onCreate(a) {
super.onCreate();
TypefacePlugin.getInstance().setEnable(true).switchTypeface(Typeface); }}Copy the code
License Apache-2.0
Licensed under the Apache License, Version 2.0 (the "License"); Copyright [2018] [mingyu. Liu] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.Copy the code