├── .gitignore ├── LICENSE ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── net │ │ └── arvin │ │ └── changeskinhelper │ │ └── sample │ │ ├── CustomView.java │ │ ├── MainActivity.java │ │ ├── MainFragment.java │ │ ├── SecondActivity.java │ │ └── StatusBarUtil.java │ ├── res-dark │ ├── drawable │ │ └── bg_item_dark.xml │ ├── mipmap-xxhdpi │ │ └── img_avatar_dark.png │ └── values │ │ └── colors_dark.xml │ ├── res-light │ ├── drawable │ │ └── bg_item_light.xml │ ├── mipmap-xxhdpi │ │ └── img_avatar_light.png │ └── values │ │ └── colors_light.xml │ └── res │ ├── drawable-v24 │ └── ic_launcher_foreground.xml │ ├── drawable │ ├── bg_item.xml │ └── ic_launcher_background.xml │ ├── layout │ ├── activity_main.xml │ ├── activity_second.xml │ ├── fragment_main.xml │ ├── item_test.xml │ └── layout_header.xml │ ├── mipmap-anydpi-v26 │ ├── ic_launcher.xml │ └── ic_launcher_round.xml │ ├── mipmap-hdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-mdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xxhdpi │ ├── ic_launcher.png │ ├── ic_launcher_round.png │ └── img_avatar.png │ ├── mipmap-xxxhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ └── values │ ├── colors.xml │ ├── ids.xml │ ├── strings.xml │ └── styles.xml ├── build.gradle ├── changeskinhelper ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── net │ │ └── arvin │ │ └── changeskinhelper │ │ ├── ChangeSkinHelper.java │ │ └── core │ │ ├── ChangeCustomSkinListener.java │ │ ├── ChangeSkinActivity.java │ │ ├── ChangeSkinListener.java │ │ ├── ChangeSkinPreferenceUtil.java │ │ ├── SkinCache.java │ │ ├── SkinResId.java │ │ └── SkinResourceProcessor.java │ └── res │ └── values │ ├── attrs.xml │ ├── ids.xml │ └── strings.xml ├── config.gradle ├── doc ├── RecyclerView换肤.md ├── 手动创建View换肤.md └── 自定义View换肤.md ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle └── skinpackage ├── .gitignore ├── build.gradle ├── debug ├── output.json └── skinpackage-debug.apk ├── proguard-rules.pro └── src └── main ├── AndroidManifest.xml ├── assets └── font │ └── yizhiqingshu.ttf └── res ├── drawable └── bg_item_blue.xml ├── mipmap-xxhdpi └── img_avatar_blue.png └── values ├── colors_blue.xml └── strings.xml /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .idea 3 | .gradle 4 | local.properties 5 | /.idea/workspace.xml 6 | /.idea/libraries 7 | .DS_Store 8 | /build 9 | /captures 10 | .externalNativeBuild 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 arvinljw 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## ChangeSkinHelper 2 | 3 | 这是一个Android换肤的库,提供一下功能: 4 | 5 | * **无需重启,一键换肤效率高** 6 | * **支持App内多套皮肤换肤** 7 | * **支持插件式动态换肤** 8 | * **支持Activity,Fragment,以及使用LayoutInflater创建的View换肤** 9 | * **支持手动创建的View换肤** 10 | * **支持RecyclerView换肤** 11 | * **支持自定义View换肤** 12 | 13 | ### 使用 14 | 15 | #### 依赖 16 | 17 | **1、在根目录的build.gradle中加入如下配置** 18 | 19 | ``` 20 | allprojects { 21 | repositories { 22 | ... 23 | maven { url 'https://jitpack.io' } 24 | } 25 | } 26 | ``` 27 | 28 | **2、在要是用的module中增加如下引用** 29 | 30 | ``` 31 | dependencies { 32 | ... 33 | implementation 'com.android.support:appcompat-v7:28.0.0' 34 | implementation 'com.github.arvinljw:ChangeSkinHelper:v1.0.2' 35 | } 36 | ``` 37 | 38 | #### 资源定义 39 | 40 | 换肤,就像字面意思换的大多无非就是颜色,颜色里又可以包含文字**背景颜色和不同颜色图片资源**,当然除此之外还可以换字体等。本库支持的资源类型包含: 41 | 42 | * color 43 | * drawable和mipmap 44 | * string 45 | 46 | *其中字体资源目前只能放在assets目录中,路径需要在string中定义*下文中详细介绍。 47 | 48 | 其实**app内和插件皮肤包**中的**资源定义**方式都是一致的,只是位置不一样,插件皮肤包需要先加载,app内的可以直接使用而已。 49 | 50 | 本库,定义不同皮肤的标准是通过**后缀**,例如默认颜色资源`colorAccent`,皮肤颜色资源就需要使用原资源名字加后缀,以后缀`_light`为例,light皮肤就是`colorAccent_light`,也就是资源名字对应为: 51 | 52 | * color资源 53 | 54 | 默认:`colorAccent` 55 | 56 | light皮肤:`colorAccent_light` 57 | 58 | * drawable资源 59 | 60 | 默认:`img_avatar` 61 | 62 | light皮肤:`img_avatar_light` 63 | 64 | * string资源 65 | 66 | 默认:`custom_typeface` 67 | 68 | light皮肤:`custom_typeface_light` 69 | 70 | 其中字体的设置比较特殊,建议在app的style中加入`csh_typeface`属性进行全局设置: 71 | 72 | ``` 73 | @string/custom_typeface 74 | ``` 75 | 76 | 这里的`custom_typeface`资源就是指字体在assets目录中的路径,没有填写目录时表示使用默认字体。 77 | 78 | **皮肤资源的定义**:在默认资源后加后缀定义某类皮肤,例如各类xml写的drawable也是一样,当然xml里边写的资源就需要自己手动换了。 79 | 80 | *需要注意的是,如果皮肤里没有找到对应资源就会使用默认资源* 81 | 82 | #### 换肤 83 | 84 | 资源的定义都完成了,那么换肤的工作就基本完成了百分之99。剩下的功能就是简单的三步: 85 | 86 | * 在需要换肤的Activity继承`ChangeSkinActivity`, 87 | * 重写`isChangeSkin`方法并返回true即可,默认是不开启换肤的 88 | * 需要换肤时,调用父类的`dynamicSkin`方法 89 | 90 | `dynamicSkin`该方法有两个重载方法: 91 | 92 | * `protected void dynamicSkin(String skinSuffix)` 该方法是换app内的皮肤的,传入皮肤后缀就可以了。 93 | * `protected void dynamicSkin(String skinPath, String skinSuffix)` 该方法是插件式动态换肤,第一个参数就是**皮肤包的具体路径**,第二个就是**皮肤后缀**。 94 | 95 | 当然还原成默认皮肤可以直接调用`defaultSkin`方法就能实现默认皮换替换。 96 | 97 | **换肤:**`dynamicSkin` 98 | 99 | **还原:**`defaultSkin ` 100 | 101 | ##### 补充说明: 102 | 103 | View只要是通过`LayoutInflater.from(context).inflate()`来创建的,其中context是继承自`ChangeSkinActivity`的activity,当然这些view的文字背景或者图片资源是通过引用资源的方式设置的,那么就可以实现换肤。 104 | 105 | 这样的情况包括Fragment,或者通过inflate引用布局文件创建View。 106 | 107 | 其实RecyclerView这类的也可以,但是滑动一下就会发现不对劲,这是因为它存在item复用,所以需要优化处理,后文中会介绍如何处理。 108 | 109 | ##### 状态栏、导航栏、ActionBar颜色设置 110 | 111 | 这部分就比较简单,状态栏的颜色设置主要是通过[laobie/StatusBarUtil](https://github.com/laobie/StatusBarUtil)设置的,主要是颜色获取通过`ChangeSkinHelper.getColor`,导航栏只支持5.0以后,actionBar主要就是获取到actionBar设置颜色即可,不使用actionBar不管也行,具体代码: 112 | 113 | ``` 114 | private void setBarsColor() { 115 | StatusBarUtil.setColorNoTranslucent(this, ChangeSkinHelper.getColor(R.color.colorPrimary)); 116 | ChangeSkinHelper.setNavigation(this, R.color.colorPrimary); 117 | ChangeSkinHelper.setActionBar(this, R.color.colorPrimary); 118 | } 119 | ``` 120 | 121 | 这个方法需要在activity的onCreate方法中调用和在换肤的回调changeSkin方法中调用即可,使用方法见app下的MainActivity。 122 | 123 | ##### 插件皮肤包 124 | 125 | 插件皮肤包其实就是一个Android apk,只是里边可以只包含资源文件即可。 126 | 127 | 皮肤包只需要创建好项目,加入对应的资源,然后build成apk,存放到手机目录中即可。 128 | 129 | *可以注意的点就是皮肤包最好不加任何第三方依赖包括google的support包这样会让皮肤包小很多。* 130 | 131 | #### [手动创建View换肤](https://github.com/arvinljw/ChangeSkinHelper/blob/master/doc/手动创建View换肤.md) 132 | 133 | #### [RecyclerView换肤](https://github.com/arvinljw/ChangeSkinHelper/blob/master/doc/RecyclerView换肤.md) 134 | 135 | #### [自定义View换肤](https://github.com/arvinljw/ChangeSkinHelper/blob/master/doc/自定义View换肤.md) 136 | 137 | ### 感谢 138 | 139 | 换肤功能最开始是通过《网易云课程-安卓高级开发工程师微专业》学习,后来又看到了[hongyangAndroid/ChangeSkin](https://github.com/hongyangAndroid/ChangeSkin)库,吸取了一些技巧,对我帮助都很大,在此表示特别感谢~ 140 | 141 | 如果有任何对本库需要改进和优化的建议都可以通过issues提交给我,我会定期维护优化,感谢支持。 -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | def androidId = rootProject.ext.androidId 4 | def appId = rootProject.ext.appId 5 | 6 | android { 7 | compileSdkVersion androidId.compileSdkVersion 8 | defaultConfig { 9 | applicationId appId.app 10 | minSdkVersion androidId.minSdkVersion 11 | targetSdkVersion androidId.targetSdkVersion 12 | versionCode androidId.versionCode 13 | versionName androidId.versionName 14 | 15 | } 16 | buildTypes { 17 | release { 18 | minifyEnabled false 19 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 20 | } 21 | } 22 | sourceSets { 23 | main { 24 | res.srcDirs += ['src/main/res-dark', 'src/main/res-light'] 25 | } 26 | } 27 | } 28 | 29 | def thirdLibrary = rootProject.ext.thirdLibrary 30 | 31 | dependencies { 32 | implementation fileTree(dir: 'libs', include: ['*.jar']) 33 | thirdLibrary.each { k, v -> implementation v } 34 | implementation project(':changeskinhelper') 35 | // implementation 'com.github.arvinljw:ChangeSkinHelper:v1.0.2' 36 | } 37 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 16 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /app/src/main/java/net/arvin/changeskinhelper/sample/CustomView.java: -------------------------------------------------------------------------------- 1 | package net.arvin.changeskinhelper.sample; 2 | 3 | import android.content.Context; 4 | import android.graphics.Canvas; 5 | import android.graphics.Paint; 6 | import android.support.annotation.Nullable; 7 | import android.util.AttributeSet; 8 | import android.view.View; 9 | 10 | import net.arvin.changeskinhelper.ChangeSkinHelper; 11 | import net.arvin.changeskinhelper.core.ChangeCustomSkinListener; 12 | 13 | /** 14 | * Created by arvinljw on 2019-08-10 17:40 15 | * Function: 16 | * Desc: 17 | */ 18 | public class CustomView extends View implements ChangeCustomSkinListener { 19 | private int circleColor; 20 | private Paint paint; 21 | private int radius; 22 | 23 | public CustomView(Context context) { 24 | this(context, null); 25 | } 26 | 27 | public CustomView(Context context, @Nullable AttributeSet attrs) { 28 | this(context, attrs, 0); 29 | } 30 | 31 | public CustomView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { 32 | super(context, attrs, defStyleAttr); 33 | 34 | circleColor = R.color.colorPrimary; 35 | paint = new Paint(Paint.ANTI_ALIAS_FLAG); 36 | paint.setColor(ChangeSkinHelper.getColor(circleColor)); 37 | radius = 100; 38 | } 39 | 40 | @Override 41 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 42 | setMeasuredDimension(getSize(radius * 2, widthMeasureSpec), getSize(radius * 2, heightMeasureSpec)); 43 | } 44 | 45 | public int getSize(int size, int measureSpec) { 46 | int result = size; 47 | int specMode = MeasureSpec.getMode(measureSpec); 48 | int specSize = MeasureSpec.getSize(measureSpec); 49 | 50 | switch (specMode) { 51 | case MeasureSpec.UNSPECIFIED: 52 | case MeasureSpec.AT_MOST: 53 | result = size; 54 | break; 55 | case MeasureSpec.EXACTLY: 56 | result = specSize; 57 | break; 58 | } 59 | return result; 60 | } 61 | 62 | @Override 63 | protected void onDraw(Canvas canvas) { 64 | super.onDraw(canvas); 65 | canvas.drawCircle(getWidth() / 2, getHeight() / 2, 100, paint); 66 | } 67 | 68 | @Override 69 | public void setCustomTag() { 70 | ChangeSkinHelper.setCustomResId(this, circleColor); 71 | // ChangeSkinHelper.setCustomResIds(this, circleColor); 72 | } 73 | 74 | @Override 75 | public void changeCustomSkin() { 76 | int skinCustomResId = ChangeSkinHelper.getSkinCustomResId(this); 77 | // int skinCustomResId = ChangeSkinHelper.getSkinCustomResIds(this).get(0); 78 | paint.setColor(ChangeSkinHelper.getColor(skinCustomResId)); 79 | invalidate(); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /app/src/main/java/net/arvin/changeskinhelper/sample/MainActivity.java: -------------------------------------------------------------------------------- 1 | package net.arvin.changeskinhelper.sample; 2 | 3 | import android.Manifest; 4 | import android.app.Activity; 5 | import android.content.Intent; 6 | import android.os.Bundle; 7 | import android.os.Environment; 8 | import android.util.Log; 9 | import android.view.View; 10 | import android.view.ViewGroup; 11 | import android.widget.FrameLayout; 12 | import android.widget.TextView; 13 | 14 | import net.arvin.changeskinhelper.ChangeSkinHelper; 15 | import net.arvin.changeskinhelper.core.ChangeSkinActivity; 16 | import net.arvin.changeskinhelper.core.ChangeSkinPreferenceUtil; 17 | import net.arvin.permissionhelper.PermissionUtil; 18 | 19 | import java.io.File; 20 | 21 | public class MainActivity extends ChangeSkinActivity { 22 | //默认是0,1是dark,2是light,3是blue,来自动态加载的皮肤包 23 | private int currSkinIndex = 0; 24 | 25 | private PermissionUtil permissionUtil; 26 | private TextView tvTest; 27 | 28 | protected boolean isChangeSkin() { 29 | return true; 30 | } 31 | 32 | @Override 33 | protected void onCreate(Bundle savedInstanceState) { 34 | super.onCreate(savedInstanceState); 35 | setContentView(R.layout.activity_main); 36 | 37 | setBarsColor(); 38 | 39 | FrameLayout frameLayout = findViewById(R.id.layout_main); 40 | frameLayout.setBackgroundColor(ChangeSkinHelper.getColor(R.color.colorPrimary)); 41 | ChangeSkinHelper.setViewTag(frameLayout, R.color.colorPrimary); 42 | 43 | TextView textView = new TextView(this); 44 | textView.setText("我是手动创建的view"); 45 | textView.setTextColor(ChangeSkinHelper.getColor(R.color.colorAccent)); 46 | textView.setTypeface(ChangeSkinHelper.getTypeface(R.string.custom_typeface)); 47 | ChangeSkinHelper.setViewTag(textView, -1, -1, R.color.colorAccent, R.string.custom_typeface); 48 | frameLayout.addView(textView, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)); 49 | 50 | String suffix = ChangeSkinPreferenceUtil.getString(getApplicationContext(), ChangeSkinHelper.KEY_SKIN_SUFFIX); 51 | currSkinIndex = getSkinIndexBySuffix(suffix); 52 | 53 | tvTest = findViewById(R.id.tv_test); 54 | tvTest.setOnClickListener(new View.OnClickListener() { 55 | @Override 56 | public void onClick(View v) { 57 | tvTest.setSelected(!tvTest.isSelected()); 58 | } 59 | }); 60 | 61 | } 62 | 63 | public static int getSkinIndexBySuffix(String suffix) { 64 | int skinIndex = 0; 65 | switch (suffix) { 66 | case "": 67 | skinIndex = 0; 68 | break; 69 | case "_dark": 70 | skinIndex = 1; 71 | break; 72 | case "_light": 73 | skinIndex = 2; 74 | break; 75 | case "_blue": 76 | skinIndex = 3; 77 | break; 78 | } 79 | return skinIndex; 80 | } 81 | 82 | public static String getSuffixBySkinIndex(int skinIndex) { 83 | String suffix; 84 | switch (skinIndex) { 85 | case 0: 86 | default: 87 | suffix = ""; 88 | break; 89 | case 1: 90 | suffix = "_dark"; 91 | break; 92 | case 2: 93 | suffix = "_light"; 94 | break; 95 | case 3: 96 | suffix = "_blue"; 97 | break; 98 | } 99 | return suffix; 100 | } 101 | 102 | public void changeSkin(View view) { 103 | currSkinIndex = (currSkinIndex + 1) % 4; 104 | String suffix = getSuffixBySkinIndex(currSkinIndex); 105 | if (currSkinIndex == 3) { 106 | final String skinPath = Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + "blue.skin"; 107 | if (permissionUtil == null) { 108 | permissionUtil = new PermissionUtil.Builder().with(this).build(); 109 | } 110 | final String tempSuffix = suffix; 111 | permissionUtil.request("需要读取文件权限", Manifest.permission.READ_EXTERNAL_STORAGE, 112 | new PermissionUtil.RequestPermissionListener() { 113 | @Override 114 | public void callback(boolean granted, boolean isAlwaysDenied) { 115 | dynamicSkin(skinPath, tempSuffix); 116 | } 117 | }); 118 | } else { 119 | dynamicSkin(suffix); 120 | } 121 | } 122 | 123 | public void defaultSkin(View view) { 124 | defaultSkin(); 125 | currSkinIndex = 0; 126 | } 127 | 128 | public void toSecond(View view) { 129 | startActivity(new Intent(this, SecondActivity.class)); 130 | } 131 | 132 | @Override 133 | public void changeSkin() { 134 | super.changeSkin(); 135 | setBarsColor(); 136 | } 137 | 138 | private void setBarsColor() { 139 | StatusBarUtil.setColorNoTranslucent(this, ChangeSkinHelper.getColor(R.color.colorPrimary)); 140 | ChangeSkinHelper.setNavigation(this, R.color.colorPrimary); 141 | ChangeSkinHelper.setActionBar(this, R.color.colorPrimary); 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /app/src/main/java/net/arvin/changeskinhelper/sample/MainFragment.java: -------------------------------------------------------------------------------- 1 | package net.arvin.changeskinhelper.sample; 2 | 3 | import android.content.Context; 4 | import android.os.Bundle; 5 | import android.support.annotation.NonNull; 6 | import android.support.annotation.Nullable; 7 | import android.support.v4.app.Fragment; 8 | import android.support.v7.widget.LinearLayoutManager; 9 | import android.support.v7.widget.RecyclerView; 10 | import android.util.Log; 11 | import android.view.LayoutInflater; 12 | import android.view.View; 13 | import android.view.ViewGroup; 14 | import android.widget.TextView; 15 | 16 | import net.arvin.changeskinhelper.ChangeSkinHelper; 17 | import net.arvin.changeskinhelper.core.ChangeSkinListener; 18 | 19 | import java.util.ArrayList; 20 | import java.util.List; 21 | 22 | /** 23 | * Created by arvinljw on 2019-08-09 20:15 24 | * Function: 25 | * Desc: 26 | */ 27 | public class MainFragment extends Fragment implements ChangeSkinListener { 28 | private List items; 29 | private SkinAdapter adapter; 30 | 31 | @Nullable 32 | @Override 33 | public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { 34 | return inflater.inflate(R.layout.fragment_main, null); 35 | } 36 | 37 | @Override 38 | public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { 39 | super.onViewCreated(view, savedInstanceState); 40 | ChangeSkinHelper.addListener(this); 41 | items = new ArrayList<>(); 42 | for (int i = 1; i <= 100; i++) { 43 | items.add("item" + i); 44 | } 45 | RecyclerView recyclerView = view.findViewById(R.id.recycler_view); 46 | recyclerView.setLayoutManager(new LinearLayoutManager(getActivity())); 47 | adapter = new SkinAdapter(getActivity(), items); 48 | recyclerView.setAdapter(adapter); 49 | 50 | } 51 | 52 | @Override 53 | public void changeSkin() { 54 | adapter.notifyDataSetChanged(); 55 | } 56 | 57 | @Override 58 | public void onDestroyView() { 59 | super.onDestroyView(); 60 | ChangeSkinHelper.removeListener(this); 61 | } 62 | 63 | public static class SkinAdapter extends RecyclerView.Adapter { 64 | private Context context; 65 | private List items; 66 | 67 | public SkinAdapter(Context context, List items) { 68 | this.context = context; 69 | this.items = items; 70 | } 71 | 72 | @NonNull 73 | @Override 74 | public SkinViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int i) { 75 | LayoutInflater layoutInflater = LayoutInflater.from(context); 76 | return new SkinViewHolder(layoutInflater.inflate(R.layout.item_test, null)); 77 | } 78 | 79 | @Override 80 | public void onBindViewHolder(@NonNull SkinViewHolder skinViewHolder, int i) { 81 | skinViewHolder.setData(items.get(i)); 82 | } 83 | 84 | @Override 85 | public int getItemCount() { 86 | return items.size(); 87 | } 88 | } 89 | 90 | public static class SkinViewHolder extends RecyclerView.ViewHolder { 91 | private TextView textView; 92 | 93 | public SkinViewHolder(@NonNull View itemView) { 94 | super(itemView); 95 | textView = itemView.findViewById(R.id.tv_item); 96 | ChangeSkinHelper.setSkin(textView); 97 | } 98 | 99 | public void setData(String item) { 100 | ChangeSkinHelper.applyViews(itemView); 101 | textView.setText(item); 102 | } 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /app/src/main/java/net/arvin/changeskinhelper/sample/SecondActivity.java: -------------------------------------------------------------------------------- 1 | package net.arvin.changeskinhelper.sample; 2 | 3 | import android.Manifest; 4 | import android.os.Environment; 5 | import android.support.v7.app.AppCompatActivity; 6 | import android.os.Bundle; 7 | import android.view.View; 8 | 9 | import net.arvin.changeskinhelper.ChangeSkinHelper; 10 | import net.arvin.changeskinhelper.core.ChangeSkinActivity; 11 | import net.arvin.changeskinhelper.core.ChangeSkinPreferenceUtil; 12 | import net.arvin.permissionhelper.PermissionUtil; 13 | 14 | import java.io.File; 15 | 16 | public class SecondActivity extends ChangeSkinActivity { 17 | 18 | private int currSkinIndex; 19 | private PermissionUtil permissionUtil; 20 | 21 | @Override 22 | protected boolean isChangeSkin() { 23 | return true; 24 | } 25 | 26 | @Override 27 | protected void onCreate(Bundle savedInstanceState) { 28 | super.onCreate(savedInstanceState); 29 | setContentView(R.layout.activity_second); 30 | setBarsColor(); 31 | getSupportFragmentManager().beginTransaction().add(R.id.layout_main, new MainFragment()).commit(); 32 | String suffix = ChangeSkinPreferenceUtil.getString(getApplicationContext(), ChangeSkinHelper.KEY_SKIN_SUFFIX); 33 | currSkinIndex = MainActivity.getSkinIndexBySuffix(suffix); 34 | } 35 | 36 | public void changeSkin(View view) { 37 | currSkinIndex = (currSkinIndex + 1) % 4; 38 | String suffix = MainActivity.getSuffixBySkinIndex(currSkinIndex); 39 | if (currSkinIndex == 3) { 40 | final String skinPath = Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + "blue.skin"; 41 | if (permissionUtil == null) { 42 | permissionUtil = new PermissionUtil.Builder().with(this).build(); 43 | } 44 | final String tempSuffix = suffix; 45 | permissionUtil.request("需要读取文件权限", Manifest.permission.READ_EXTERNAL_STORAGE, 46 | new PermissionUtil.RequestPermissionListener() { 47 | @Override 48 | public void callback(boolean granted, boolean isAlwaysDenied) { 49 | dynamicSkin(skinPath, tempSuffix); 50 | } 51 | }); 52 | } else { 53 | dynamicSkin(suffix); 54 | } 55 | } 56 | 57 | public void defaultSkin(View view) { 58 | defaultSkin(); 59 | currSkinIndex = 0; 60 | } 61 | 62 | @Override 63 | public void changeSkin() { 64 | super.changeSkin(); 65 | setBarsColor(); 66 | } 67 | 68 | private void setBarsColor() { 69 | StatusBarUtil.setColorNoTranslucent(this, ChangeSkinHelper.getColor(R.color.colorPrimary)); 70 | ChangeSkinHelper.setNavigation(this, R.color.colorPrimary); 71 | ChangeSkinHelper.setActionBar(this, R.color.colorPrimary); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /app/src/main/java/net/arvin/changeskinhelper/sample/StatusBarUtil.java: -------------------------------------------------------------------------------- 1 | package net.arvin.changeskinhelper.sample; 2 | 3 | import android.annotation.TargetApi; 4 | import android.app.Activity; 5 | import android.content.Context; 6 | import android.content.res.TypedArray; 7 | import android.graphics.Color; 8 | import android.os.Build; 9 | import android.support.annotation.ColorInt; 10 | import android.support.annotation.IntRange; 11 | import android.support.annotation.NonNull; 12 | import android.support.design.widget.CoordinatorLayout; 13 | import android.support.v4.widget.DrawerLayout; 14 | import android.view.View; 15 | import android.view.ViewGroup; 16 | import android.view.Window; 17 | import android.view.WindowManager; 18 | import android.widget.LinearLayout; 19 | 20 | import java.lang.reflect.Field; 21 | import java.lang.reflect.Method; 22 | 23 | /** 24 | * Created by Jaeger on 16/2/14. 25 | *

26 | * Email: chjie.jaeger@gmail.com 27 | * GitHub: https://github.com/laobie 28 | */ 29 | public class StatusBarUtil { 30 | 31 | public static final int DEFAULT_STATUS_BAR_ALPHA = 112; 32 | private static final int FAKE_STATUS_BAR_VIEW_ID = R.id.statusbarutil_fake_status_bar_view; 33 | private static final int FAKE_TRANSLUCENT_VIEW_ID = R.id.statusbarutil_translucent_view; 34 | private static final int TAG_KEY_HAVE_SET_OFFSET = -123; 35 | 36 | public static void forNightStatusBar(Activity activity, int colorFor4_4) { 37 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { 38 | TypedArray a = activity.getTheme().obtainStyledAttributes(0, new int[]{ 39 | android.R.attr.statusBarColor 40 | }); 41 | int color = a.getColor(0, 0); 42 | activity.getWindow().setStatusBarColor(color); 43 | a.recycle(); 44 | } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { 45 | activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); 46 | ViewGroup decorView = (ViewGroup) activity.getWindow().getDecorView(); 47 | View fakeStatusBarView = decorView.findViewById(FAKE_STATUS_BAR_VIEW_ID); 48 | if (fakeStatusBarView != null) { 49 | if (fakeStatusBarView.getVisibility() == View.GONE) { 50 | fakeStatusBarView.setVisibility(View.VISIBLE); 51 | } 52 | fakeStatusBarView.setBackgroundColor(calculateStatusColor(colorFor4_4, 0)); 53 | } else { 54 | decorView.addView(createStatusBarView(activity, colorFor4_4, 0)); 55 | } 56 | setRootView(activity); 57 | } 58 | } 59 | 60 | /** 61 | * 设置状态栏颜色 62 | * 63 | * @param activity 需要设置的 activity 64 | * @param color 状态栏颜色值 65 | */ 66 | public static void setColor(Activity activity, @ColorInt int color) { 67 | setColor(activity, color, DEFAULT_STATUS_BAR_ALPHA); 68 | } 69 | 70 | /** 71 | * 设置状态栏颜色 72 | * 73 | * @param activity 需要设置的activity 74 | * @param color 状态栏颜色值 75 | * @param statusBarAlpha 状态栏透明度 76 | */ 77 | public static void setColor(Activity activity, @ColorInt int color, @IntRange(from = 0, to = 255) int statusBarAlpha) { 78 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { 79 | activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); 80 | activity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); 81 | activity.getWindow().setStatusBarColor(calculateStatusColor(color, statusBarAlpha)); 82 | } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { 83 | activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); 84 | ViewGroup decorView = (ViewGroup) activity.getWindow().getDecorView(); 85 | View fakeStatusBarView = decorView.findViewById(FAKE_STATUS_BAR_VIEW_ID); 86 | if (fakeStatusBarView != null) { 87 | if (fakeStatusBarView.getVisibility() == View.GONE) { 88 | fakeStatusBarView.setVisibility(View.VISIBLE); 89 | } 90 | fakeStatusBarView.setBackgroundColor(calculateStatusColor(color, statusBarAlpha)); 91 | } else { 92 | decorView.addView(createStatusBarView(activity, color, statusBarAlpha)); 93 | } 94 | setRootView(activity); 95 | } 96 | } 97 | 98 | /** 99 | * 为滑动返回界面设置状态栏颜色 100 | * 101 | * @param activity 需要设置的activity 102 | * @param color 状态栏颜色值 103 | */ 104 | public static void setColorForSwipeBack(Activity activity, int color) { 105 | setColorForSwipeBack(activity, color, DEFAULT_STATUS_BAR_ALPHA); 106 | } 107 | 108 | /** 109 | * 为滑动返回界面设置状态栏颜色 110 | * 111 | * @param activity 需要设置的activity 112 | * @param color 状态栏颜色值 113 | * @param statusBarAlpha 状态栏透明度 114 | */ 115 | public static void setColorForSwipeBack(Activity activity, @ColorInt int color, 116 | @IntRange(from = 0, to = 255) int statusBarAlpha) { 117 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { 118 | 119 | ViewGroup contentView = ((ViewGroup) activity.findViewById(android.R.id.content)); 120 | View rootView = contentView.getChildAt(0); 121 | int statusBarHeight = getStatusBarHeight(activity); 122 | if (rootView != null && rootView instanceof CoordinatorLayout) { 123 | final CoordinatorLayout coordinatorLayout = (CoordinatorLayout) rootView; 124 | if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { 125 | coordinatorLayout.setFitsSystemWindows(false); 126 | contentView.setBackgroundColor(calculateStatusColor(color, statusBarAlpha)); 127 | boolean isNeedRequestLayout = contentView.getPaddingTop() < statusBarHeight; 128 | if (isNeedRequestLayout) { 129 | contentView.setPadding(0, statusBarHeight, 0, 0); 130 | coordinatorLayout.post(new Runnable() { 131 | @Override 132 | public void run() { 133 | coordinatorLayout.requestLayout(); 134 | } 135 | }); 136 | } 137 | } else { 138 | coordinatorLayout.setStatusBarBackgroundColor(calculateStatusColor(color, statusBarAlpha)); 139 | } 140 | } else { 141 | contentView.setPadding(0, statusBarHeight, 0, 0); 142 | contentView.setBackgroundColor(calculateStatusColor(color, statusBarAlpha)); 143 | } 144 | setTransparentForWindow(activity); 145 | } 146 | } 147 | 148 | /** 149 | * 设置状态栏纯色 不加半透明效果 150 | * 151 | * @param activity 需要设置的 activity 152 | * @param color 状态栏颜色值 153 | */ 154 | public static void setColorNoTranslucent(Activity activity, @ColorInt int color) { 155 | setColor(activity, color, 0); 156 | } 157 | 158 | /** 159 | * 设置状态栏颜色(5.0以下无半透明效果,不建议使用) 160 | * 161 | * @param activity 需要设置的 activity 162 | * @param color 状态栏颜色值 163 | */ 164 | @Deprecated 165 | public static void setColorDiff(Activity activity, @ColorInt int color) { 166 | if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) { 167 | return; 168 | } 169 | transparentStatusBar(activity); 170 | ViewGroup contentView = (ViewGroup) activity.findViewById(android.R.id.content); 171 | // 移除半透明矩形,以免叠加 172 | View fakeStatusBarView = contentView.findViewById(FAKE_STATUS_BAR_VIEW_ID); 173 | if (fakeStatusBarView != null) { 174 | if (fakeStatusBarView.getVisibility() == View.GONE) { 175 | fakeStatusBarView.setVisibility(View.VISIBLE); 176 | } 177 | fakeStatusBarView.setBackgroundColor(color); 178 | } else { 179 | contentView.addView(createStatusBarView(activity, color)); 180 | } 181 | setRootView(activity); 182 | } 183 | 184 | /** 185 | * 使状态栏半透明 186 | *

187 | * 适用于图片作为背景的界面,此时需要图片填充到状态栏 188 | * 189 | * @param activity 需要设置的activity 190 | */ 191 | public static void setTranslucent(Activity activity) { 192 | setTranslucent(activity, DEFAULT_STATUS_BAR_ALPHA); 193 | } 194 | 195 | /** 196 | * 使状态栏半透明 197 | *

198 | * 适用于图片作为背景的界面,此时需要图片填充到状态栏 199 | * 200 | * @param activity 需要设置的activity 201 | * @param statusBarAlpha 状态栏透明度 202 | */ 203 | public static void setTranslucent(Activity activity, @IntRange(from = 0, to = 255) int statusBarAlpha) { 204 | if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) { 205 | return; 206 | } 207 | setTransparent(activity); 208 | addTranslucentView(activity, statusBarAlpha); 209 | } 210 | 211 | /** 212 | * 针对根布局是 CoordinatorLayout, 使状态栏半透明 213 | *

214 | * 适用于图片作为背景的界面,此时需要图片填充到状态栏 215 | * 216 | * @param activity 需要设置的activity 217 | * @param statusBarAlpha 状态栏透明度 218 | */ 219 | public static void setTranslucentForCoordinatorLayout(Activity activity, @IntRange(from = 0, to = 255) int statusBarAlpha) { 220 | if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) { 221 | return; 222 | } 223 | transparentStatusBar(activity); 224 | addTranslucentView(activity, statusBarAlpha); 225 | } 226 | 227 | /** 228 | * 设置状态栏全透明 229 | * 230 | * @param activity 需要设置的activity 231 | */ 232 | public static void setTransparent(Activity activity) { 233 | if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) { 234 | return; 235 | } 236 | transparentStatusBar(activity); 237 | setRootView(activity); 238 | } 239 | 240 | /** 241 | * 使状态栏透明(5.0以上半透明效果,不建议使用) 242 | *

243 | * 适用于图片作为背景的界面,此时需要图片填充到状态栏 244 | * 245 | * @param activity 需要设置的activity 246 | */ 247 | @Deprecated 248 | public static void setTranslucentDiff(Activity activity) { 249 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { 250 | // 设置状态栏透明 251 | activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); 252 | setRootView(activity); 253 | } 254 | } 255 | 256 | /** 257 | * 为DrawerLayout 布局设置状态栏变色 258 | * 259 | * @param activity 需要设置的activity 260 | * @param drawerLayout DrawerLayout 261 | * @param color 状态栏颜色值 262 | */ 263 | public static void setColorForDrawerLayout(Activity activity, DrawerLayout drawerLayout, @ColorInt int color) { 264 | setColorForDrawerLayout(activity, drawerLayout, color, DEFAULT_STATUS_BAR_ALPHA); 265 | } 266 | 267 | /** 268 | * 为DrawerLayout 布局设置状态栏颜色,纯色 269 | * 270 | * @param activity 需要设置的activity 271 | * @param drawerLayout DrawerLayout 272 | * @param color 状态栏颜色值 273 | */ 274 | public static void setColorNoTranslucentForDrawerLayout(Activity activity, DrawerLayout drawerLayout, @ColorInt int color) { 275 | setColorForDrawerLayout(activity, drawerLayout, color, 0); 276 | } 277 | 278 | /** 279 | * 为DrawerLayout 布局设置状态栏变色 280 | * 281 | * @param activity 需要设置的activity 282 | * @param drawerLayout DrawerLayout 283 | * @param color 状态栏颜色值 284 | * @param statusBarAlpha 状态栏透明度 285 | */ 286 | public static void setColorForDrawerLayout(Activity activity, DrawerLayout drawerLayout, @ColorInt int color, 287 | @IntRange(from = 0, to = 255) int statusBarAlpha) { 288 | if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) { 289 | return; 290 | } 291 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { 292 | activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); 293 | activity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); 294 | activity.getWindow().setStatusBarColor(Color.TRANSPARENT); 295 | } else { 296 | activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); 297 | } 298 | // 生成一个状态栏大小的矩形 299 | // 添加 statusBarView 到布局中 300 | ViewGroup contentLayout = (ViewGroup) drawerLayout.getChildAt(0); 301 | View fakeStatusBarView = contentLayout.findViewById(FAKE_STATUS_BAR_VIEW_ID); 302 | if (fakeStatusBarView != null) { 303 | if (fakeStatusBarView.getVisibility() == View.GONE) { 304 | fakeStatusBarView.setVisibility(View.VISIBLE); 305 | } 306 | fakeStatusBarView.setBackgroundColor(color); 307 | } else { 308 | contentLayout.addView(createStatusBarView(activity, color), 0); 309 | } 310 | // 内容布局不是 LinearLayout 时,设置padding top 311 | if (!(contentLayout instanceof LinearLayout) && contentLayout.getChildAt(1) != null) { 312 | contentLayout.getChildAt(1) 313 | .setPadding(contentLayout.getPaddingLeft(), getStatusBarHeight(activity) + contentLayout.getPaddingTop(), 314 | contentLayout.getPaddingRight(), contentLayout.getPaddingBottom()); 315 | } 316 | // 设置属性 317 | setDrawerLayoutProperty(drawerLayout, contentLayout); 318 | addTranslucentView(activity, statusBarAlpha); 319 | } 320 | 321 | /** 322 | * 设置 DrawerLayout 属性 323 | * 324 | * @param drawerLayout DrawerLayout 325 | * @param drawerLayoutContentLayout DrawerLayout 的内容布局 326 | */ 327 | private static void setDrawerLayoutProperty(DrawerLayout drawerLayout, ViewGroup drawerLayoutContentLayout) { 328 | ViewGroup drawer = (ViewGroup) drawerLayout.getChildAt(1); 329 | drawerLayout.setFitsSystemWindows(false); 330 | drawerLayoutContentLayout.setFitsSystemWindows(false); 331 | drawerLayoutContentLayout.setClipToPadding(true); 332 | drawer.setFitsSystemWindows(false); 333 | } 334 | 335 | /** 336 | * 为DrawerLayout 布局设置状态栏变色(5.0以下无半透明效果,不建议使用) 337 | * 338 | * @param activity 需要设置的activity 339 | * @param drawerLayout DrawerLayout 340 | * @param color 状态栏颜色值 341 | */ 342 | @Deprecated 343 | public static void setColorForDrawerLayoutDiff(Activity activity, DrawerLayout drawerLayout, @ColorInt int color) { 344 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { 345 | activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); 346 | // 生成一个状态栏大小的矩形 347 | ViewGroup contentLayout = (ViewGroup) drawerLayout.getChildAt(0); 348 | View fakeStatusBarView = contentLayout.findViewById(FAKE_STATUS_BAR_VIEW_ID); 349 | if (fakeStatusBarView != null) { 350 | if (fakeStatusBarView.getVisibility() == View.GONE) { 351 | fakeStatusBarView.setVisibility(View.VISIBLE); 352 | } 353 | fakeStatusBarView.setBackgroundColor(calculateStatusColor(color, DEFAULT_STATUS_BAR_ALPHA)); 354 | } else { 355 | // 添加 statusBarView 到布局中 356 | contentLayout.addView(createStatusBarView(activity, color), 0); 357 | } 358 | // 内容布局不是 LinearLayout 时,设置padding top 359 | if (!(contentLayout instanceof LinearLayout) && contentLayout.getChildAt(1) != null) { 360 | contentLayout.getChildAt(1).setPadding(0, getStatusBarHeight(activity), 0, 0); 361 | } 362 | // 设置属性 363 | setDrawerLayoutProperty(drawerLayout, contentLayout); 364 | } 365 | } 366 | 367 | /** 368 | * 为 DrawerLayout 布局设置状态栏透明 369 | * 370 | * @param activity 需要设置的activity 371 | * @param drawerLayout DrawerLayout 372 | */ 373 | public static void setTranslucentForDrawerLayout(Activity activity, DrawerLayout drawerLayout) { 374 | setTranslucentForDrawerLayout(activity, drawerLayout, DEFAULT_STATUS_BAR_ALPHA); 375 | } 376 | 377 | /** 378 | * 为 DrawerLayout 布局设置状态栏透明 379 | * 380 | * @param activity 需要设置的activity 381 | * @param drawerLayout DrawerLayout 382 | */ 383 | public static void setTranslucentForDrawerLayout(Activity activity, DrawerLayout drawerLayout, 384 | @IntRange(from = 0, to = 255) int statusBarAlpha) { 385 | if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) { 386 | return; 387 | } 388 | setTransparentForDrawerLayout(activity, drawerLayout); 389 | addTranslucentView(activity, statusBarAlpha); 390 | } 391 | 392 | /** 393 | * 为 DrawerLayout 布局设置状态栏透明 394 | * 395 | * @param activity 需要设置的activity 396 | * @param drawerLayout DrawerLayout 397 | */ 398 | public static void setTransparentForDrawerLayout(Activity activity, DrawerLayout drawerLayout) { 399 | if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) { 400 | return; 401 | } 402 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { 403 | activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); 404 | activity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); 405 | activity.getWindow().setStatusBarColor(Color.TRANSPARENT); 406 | } else { 407 | activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); 408 | } 409 | 410 | ViewGroup contentLayout = (ViewGroup) drawerLayout.getChildAt(0); 411 | // 内容布局不是 LinearLayout 时,设置padding top 412 | if (!(contentLayout instanceof LinearLayout) && contentLayout.getChildAt(1) != null) { 413 | contentLayout.getChildAt(1).setPadding(0, getStatusBarHeight(activity), 0, 0); 414 | } 415 | 416 | // 设置属性 417 | setDrawerLayoutProperty(drawerLayout, contentLayout); 418 | } 419 | 420 | /** 421 | * 为 DrawerLayout 布局设置状态栏透明(5.0以上半透明效果,不建议使用) 422 | * 423 | * @param activity 需要设置的activity 424 | * @param drawerLayout DrawerLayout 425 | */ 426 | @Deprecated 427 | public static void setTranslucentForDrawerLayoutDiff(Activity activity, DrawerLayout drawerLayout) { 428 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { 429 | // 设置状态栏透明 430 | activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); 431 | // 设置内容布局属性 432 | ViewGroup contentLayout = (ViewGroup) drawerLayout.getChildAt(0); 433 | contentLayout.setFitsSystemWindows(true); 434 | contentLayout.setClipToPadding(true); 435 | // 设置抽屉布局属性 436 | ViewGroup vg = (ViewGroup) drawerLayout.getChildAt(1); 437 | vg.setFitsSystemWindows(false); 438 | // 设置 DrawerLayout 属性 439 | drawerLayout.setFitsSystemWindows(false); 440 | } 441 | } 442 | 443 | /** 444 | * 为头部是 ImageView 的界面设置状态栏全透明 445 | * 446 | * @param activity 需要设置的activity 447 | * @param needOffsetView 需要向下偏移的 View 448 | */ 449 | public static void setTransparentForImageView(Activity activity, View needOffsetView) { 450 | setTranslucentForImageView(activity, 0, needOffsetView); 451 | } 452 | 453 | /** 454 | * 为头部是 ImageView 的界面设置状态栏透明(使用默认透明度) 455 | * 456 | * @param activity 需要设置的activity 457 | * @param needOffsetView 需要向下偏移的 View 458 | */ 459 | public static void setTranslucentForImageView(Activity activity, View needOffsetView) { 460 | setTranslucentForImageView(activity, DEFAULT_STATUS_BAR_ALPHA, needOffsetView); 461 | } 462 | 463 | /** 464 | * 为头部是 ImageView 的界面设置状态栏透明 465 | * 466 | * @param activity 需要设置的activity 467 | * @param statusBarAlpha 状态栏透明度 468 | * @param needOffsetView 需要向下偏移的 View 469 | */ 470 | public static void setTranslucentForImageView(Activity activity, @IntRange(from = 0, to = 255) int statusBarAlpha, 471 | View needOffsetView) { 472 | if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) { 473 | return; 474 | } 475 | setTransparentForWindow(activity); 476 | addTranslucentView(activity, statusBarAlpha); 477 | if (needOffsetView != null) { 478 | Object haveSetOffset = needOffsetView.getTag(TAG_KEY_HAVE_SET_OFFSET); 479 | if (haveSetOffset != null && (Boolean) haveSetOffset) { 480 | return; 481 | } 482 | ViewGroup.MarginLayoutParams layoutParams = (ViewGroup.MarginLayoutParams) needOffsetView.getLayoutParams(); 483 | layoutParams.setMargins(layoutParams.leftMargin, layoutParams.topMargin + getStatusBarHeight(activity), 484 | layoutParams.rightMargin, layoutParams.bottomMargin); 485 | needOffsetView.setTag(TAG_KEY_HAVE_SET_OFFSET, true); 486 | } 487 | } 488 | 489 | /** 490 | * 为 fragment 头部是 ImageView 的设置状态栏透明 491 | * 492 | * @param activity fragment 对应的 activity 493 | * @param needOffsetView 需要向下偏移的 View 494 | */ 495 | public static void setTranslucentForImageViewInFragment(Activity activity, View needOffsetView) { 496 | setTranslucentForImageViewInFragment(activity, DEFAULT_STATUS_BAR_ALPHA, needOffsetView); 497 | } 498 | 499 | /** 500 | * 为 fragment 头部是 ImageView 的设置状态栏透明 501 | * 502 | * @param activity fragment 对应的 activity 503 | * @param needOffsetView 需要向下偏移的 View 504 | */ 505 | public static void setTransparentForImageViewInFragment(Activity activity, View needOffsetView) { 506 | setTranslucentForImageViewInFragment(activity, 0, needOffsetView); 507 | } 508 | 509 | /** 510 | * 为 fragment 头部是 ImageView 的设置状态栏透明 511 | * 512 | * @param activity fragment 对应的 activity 513 | * @param statusBarAlpha 状态栏透明度 514 | * @param needOffsetView 需要向下偏移的 View 515 | */ 516 | public static void setTranslucentForImageViewInFragment(Activity activity, @IntRange(from = 0, to = 255) int statusBarAlpha, 517 | View needOffsetView) { 518 | setTranslucentForImageView(activity, statusBarAlpha, needOffsetView); 519 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT && Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { 520 | clearPreviousSetting(activity); 521 | } 522 | } 523 | 524 | /** 525 | * 隐藏伪状态栏 View 526 | * 527 | * @param activity 调用的 Activity 528 | */ 529 | public static void hideFakeStatusBarView(Activity activity) { 530 | ViewGroup decorView = (ViewGroup) activity.getWindow().getDecorView(); 531 | View fakeStatusBarView = decorView.findViewById(FAKE_STATUS_BAR_VIEW_ID); 532 | if (fakeStatusBarView != null) { 533 | fakeStatusBarView.setVisibility(View.GONE); 534 | } 535 | View fakeTranslucentView = decorView.findViewById(FAKE_TRANSLUCENT_VIEW_ID); 536 | if (fakeTranslucentView != null) { 537 | fakeTranslucentView.setVisibility(View.GONE); 538 | } 539 | } 540 | 541 | @TargetApi(Build.VERSION_CODES.M) 542 | public static void setLightMode(Activity activity) { 543 | setMIUIStatusBarDarkIcon(activity, true); 544 | setMeizuStatusBarDarkIcon(activity, true); 545 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { 546 | activity.getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN); 547 | } 548 | } 549 | 550 | @TargetApi(Build.VERSION_CODES.M) 551 | public static void setDarkMode(Activity activity) { 552 | setMIUIStatusBarDarkIcon(activity, false); 553 | setMeizuStatusBarDarkIcon(activity, false); 554 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { 555 | activity.getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN); 556 | } 557 | } 558 | 559 | /** 560 | * 修改 MIUI V6 以上状态栏颜色 561 | */ 562 | private static void setMIUIStatusBarDarkIcon(@NonNull Activity activity, boolean darkIcon) { 563 | Class clazz = activity.getWindow().getClass(); 564 | try { 565 | Class layoutParams = Class.forName("android.view.MiuiWindowManager$LayoutParams"); 566 | Field field = layoutParams.getField("EXTRA_FLAG_STATUS_BAR_DARK_MODE"); 567 | int darkModeFlag = field.getInt(layoutParams); 568 | Method extraFlagField = clazz.getMethod("setExtraFlags", int.class, int.class); 569 | extraFlagField.invoke(activity.getWindow(), darkIcon ? darkModeFlag : 0, darkModeFlag); 570 | } catch (Exception e) { 571 | //e.printStackTrace(); 572 | } 573 | } 574 | 575 | /** 576 | * 修改魅族状态栏字体颜色 Flyme 4.0 577 | */ 578 | private static void setMeizuStatusBarDarkIcon(@NonNull Activity activity, boolean darkIcon) { 579 | try { 580 | WindowManager.LayoutParams lp = activity.getWindow().getAttributes(); 581 | Field darkFlag = WindowManager.LayoutParams.class.getDeclaredField("MEIZU_FLAG_DARK_STATUS_BAR_ICON"); 582 | Field meizuFlags = WindowManager.LayoutParams.class.getDeclaredField("meizuFlags"); 583 | darkFlag.setAccessible(true); 584 | meizuFlags.setAccessible(true); 585 | int bit = darkFlag.getInt(null); 586 | int value = meizuFlags.getInt(lp); 587 | if (darkIcon) { 588 | value |= bit; 589 | } else { 590 | value &= ~bit; 591 | } 592 | meizuFlags.setInt(lp, value); 593 | activity.getWindow().setAttributes(lp); 594 | } catch (Exception e) { 595 | //e.printStackTrace(); 596 | } 597 | } 598 | 599 | /////////////////////////////////////////////////////////////////////////////////// 600 | 601 | @TargetApi(Build.VERSION_CODES.KITKAT) 602 | private static void clearPreviousSetting(Activity activity) { 603 | ViewGroup decorView = (ViewGroup) activity.getWindow().getDecorView(); 604 | View fakeStatusBarView = decorView.findViewById(FAKE_STATUS_BAR_VIEW_ID); 605 | if (fakeStatusBarView != null) { 606 | decorView.removeView(fakeStatusBarView); 607 | ViewGroup rootView = (ViewGroup) ((ViewGroup) activity.findViewById(android.R.id.content)).getChildAt(0); 608 | rootView.setPadding(0, 0, 0, 0); 609 | } 610 | } 611 | 612 | /** 613 | * 添加半透明矩形条 614 | * 615 | * @param activity 需要设置的 activity 616 | * @param statusBarAlpha 透明值 617 | */ 618 | private static void addTranslucentView(Activity activity, @IntRange(from = 0, to = 255) int statusBarAlpha) { 619 | ViewGroup contentView = (ViewGroup) activity.findViewById(android.R.id.content); 620 | View fakeTranslucentView = contentView.findViewById(FAKE_TRANSLUCENT_VIEW_ID); 621 | if (fakeTranslucentView != null) { 622 | if (fakeTranslucentView.getVisibility() == View.GONE) { 623 | fakeTranslucentView.setVisibility(View.VISIBLE); 624 | } 625 | fakeTranslucentView.setBackgroundColor(Color.argb(statusBarAlpha, 0, 0, 0)); 626 | } else { 627 | contentView.addView(createTranslucentStatusBarView(activity, statusBarAlpha)); 628 | } 629 | } 630 | 631 | /** 632 | * 生成一个和状态栏大小相同的彩色矩形条 633 | * 634 | * @param activity 需要设置的 activity 635 | * @param color 状态栏颜色值 636 | * @return 状态栏矩形条 637 | */ 638 | private static View createStatusBarView(Activity activity, @ColorInt int color) { 639 | return createStatusBarView(activity, color, 0); 640 | } 641 | 642 | /** 643 | * 生成一个和状态栏大小相同的半透明矩形条 644 | * 645 | * @param activity 需要设置的activity 646 | * @param color 状态栏颜色值 647 | * @param alpha 透明值 648 | * @return 状态栏矩形条 649 | */ 650 | private static View createStatusBarView(Activity activity, @ColorInt int color, int alpha) { 651 | // 绘制一个和状态栏一样高的矩形 652 | View statusBarView = new View(activity); 653 | LinearLayout.LayoutParams params = 654 | new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, getStatusBarHeight(activity)); 655 | statusBarView.setLayoutParams(params); 656 | statusBarView.setBackgroundColor(calculateStatusColor(color, alpha)); 657 | statusBarView.setId(FAKE_STATUS_BAR_VIEW_ID); 658 | return statusBarView; 659 | } 660 | 661 | /** 662 | * 设置根布局参数 663 | */ 664 | private static void setRootView(Activity activity) { 665 | ViewGroup parent = (ViewGroup) activity.findViewById(android.R.id.content); 666 | for (int i = 0, count = parent.getChildCount(); i < count; i++) { 667 | View childView = parent.getChildAt(i); 668 | if (childView instanceof ViewGroup) { 669 | childView.setFitsSystemWindows(true); 670 | ((ViewGroup) childView).setClipToPadding(true); 671 | } 672 | } 673 | } 674 | 675 | /** 676 | * 设置透明 677 | */ 678 | private static void setTransparentForWindow(Activity activity) { 679 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { 680 | activity.getWindow().setStatusBarColor(Color.TRANSPARENT); 681 | activity.getWindow() 682 | .getDecorView() 683 | .setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN); 684 | } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { 685 | activity.getWindow() 686 | .setFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS, WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); 687 | } 688 | } 689 | 690 | /** 691 | * 使状态栏透明 692 | */ 693 | @TargetApi(Build.VERSION_CODES.KITKAT) 694 | private static void transparentStatusBar(Activity activity) { 695 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { 696 | activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); 697 | activity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); 698 | activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION); 699 | activity.getWindow().setStatusBarColor(Color.TRANSPARENT); 700 | } else { 701 | activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); 702 | } 703 | } 704 | 705 | /** 706 | * 创建半透明矩形 View 707 | * 708 | * @param alpha 透明值 709 | * @return 半透明 View 710 | */ 711 | private static View createTranslucentStatusBarView(Activity activity, int alpha) { 712 | // 绘制一个和状态栏一样高的矩形 713 | View statusBarView = new View(activity); 714 | LinearLayout.LayoutParams params = 715 | new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, getStatusBarHeight(activity)); 716 | statusBarView.setLayoutParams(params); 717 | statusBarView.setBackgroundColor(Color.argb(alpha, 0, 0, 0)); 718 | statusBarView.setId(FAKE_TRANSLUCENT_VIEW_ID); 719 | return statusBarView; 720 | } 721 | 722 | /** 723 | * 获取状态栏高度 724 | * 725 | * @param context context 726 | * @return 状态栏高度 727 | */ 728 | private static int getStatusBarHeight(Context context) { 729 | // 获得状态栏高度 730 | int resourceId = context.getResources().getIdentifier("status_bar_height", "dimen", "android"); 731 | return context.getResources().getDimensionPixelSize(resourceId); 732 | } 733 | 734 | /** 735 | * 计算状态栏颜色 736 | * 737 | * @param color color值 738 | * @param alpha alpha值 739 | * @return 最终的状态栏颜色 740 | */ 741 | private static int calculateStatusColor(@ColorInt int color, int alpha) { 742 | if (alpha == 0) { 743 | return color; 744 | } 745 | float a = 1 - alpha / 255f; 746 | int red = color >> 16 & 0xff; 747 | int green = color >> 8 & 0xff; 748 | int blue = color & 0xff; 749 | red = (int) (red * a + 0.5); 750 | green = (int) (green * a + 0.5); 751 | blue = (int) (blue * a + 0.5); 752 | return 0xff << 24 | red << 16 | green << 8 | blue; 753 | } 754 | } 755 | -------------------------------------------------------------------------------- /app/src/main/res-dark/drawable/bg_item_dark.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res-dark/mipmap-xxhdpi/img_avatar_dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arvinljw/ChangeSkinHelper/c18bc537af5b6b65c9c79dcad843ebf4cd24a1c3/app/src/main/res-dark/mipmap-xxhdpi/img_avatar_dark.png -------------------------------------------------------------------------------- /app/src/main/res-dark/values/colors_dark.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #00574B 4 | #88D81B60 5 | #888888 6 | #444444 7 | -------------------------------------------------------------------------------- /app/src/main/res-light/drawable/bg_item_light.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res-light/mipmap-xxhdpi/img_avatar_light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arvinljw/ChangeSkinHelper/c18bc537af5b6b65c9c79dcad843ebf4cd24a1c3/app/src/main/res-light/mipmap-xxhdpi/img_avatar_light.png -------------------------------------------------------------------------------- /app/src/main/res-light/values/colors_light.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #89BFC7 4 | #10D81B60 5 | #333333 6 | #bbbbbb 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | 19 | 22 | 25 | 26 | 27 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/bg_item.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 11 | 16 | 21 | 26 | 31 | 36 | 41 | 46 | 51 | 56 | 61 | 66 | 71 | 76 | 81 | 86 | 91 | 96 | 101 | 106 | 111 | 116 | 121 | 126 | 131 | 136 | 141 | 146 | 151 | 156 | 161 | 166 | 171 | 172 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 12 | 13 |