Git Product home page Git Product logo

themeskinning's Introduction

Download API

Android 主题换肤的开源库(插件化换肤)

新增夜间模式的简洁实现方式,不需要再去单独创建一个皮肤包(目前处于beta版本)

夜间模式实现方式

  1. 前提条件还是每个使用到的资源必须是引用的,不能是具体的值
  2. 开启全局换肤配置 SkinConfig.enableGlobalSkinApply()
  3. 复制一份 color.xml 文件并重命名为 color_night.xml ,然后修改 color_night.xml,在每一个 color 的 name 后面加上一个 _night后缀
  4. 对于 drawable 文件的处理也差不多,复制需要夜间模式切换的 drawable 文件,然后在其文件名上加上一个 _night 后缀,xml 文件中的颜色值修改成夜间模式需要的即可
  5. 最后在适当的地方调用 SkinManager.getInstance().nightMode() 即可实现夜间模式
  6. 具体实现可以参考示例项目

效果图如下:

Demo

1. 集成步骤:

  1. 添加依赖 compile 'com.solid.skin:skinlibrary:latestVersion'

  2. 让你的 Application 继承于 SkinBaseApplication

  3. 让你的 Activity 继承于 SkinBaseActivity,如果使用了 Fragment 则继承于 SkinBaseFragment

  4. 在需要换肤的根布局上添加 xmlns:skin="http://schemas.android.com/android/skin" ,然后在需要换肤的View上加上 skin:enable="true"

  5. 新建一个项目模块(只包含有资源文件,例如本项目的 skinpackage 模块),其中包含的资源文件的 name 一定要和原项目中有换肤需求的 View 所使用的资源name一致。

  6. 拿到上一步生成的文件( ×××.apk ),改名为 ×××.skin,放入 assets 中的 skin 目录下( skin 目录是自己新建的)

  7. 调用换肤

      SkinManager.getInstance().loadSkin("Your skin file name in assets(eg:theme.skin)",
                                    new ILoaderListener() {
                                        @Override
                                        public void onStart() {
                                            Toast.makeText(getApplicationContext(), "正在切换中", Toast.LENGTH_SHORT).show();
                                        }
    
                                        @Override
                                        public void onSuccess() {
                                            Toast.makeText(getApplicationContext(), "切换成功", Toast.LENGTH_SHORT).show();
                                        }
    
                                        @Override
                                        public void onFailed() {
                                            Toast.makeText(getApplicationContext(), "切换失败", Toast.LENGTH_SHORT).show();
                                        }
                                    }
    
                            );

详细的使用,请到示例项目中查看

2.换肤属性的扩展

本开源库默认支持 textColor 和 background 的换肤。如果你还需要对其他属性进行换肤,那么就需要去自定义了。

那么如何自定义呢?看下面这个例子:

TabLayout大家应该都用过吧。它下面会有一个指示器,当我们换肤的时候也希望这个指示器的颜色也跟着更改。

  • 新建一个 TabLayoutIndicatorAttr 继承于 SkinAttr,然后重写 apply 方法。apply 方法在换肤的时候就会被调用

  • 代码的详细实现

public class TabLayoutIndicatorAttr extends SkinAttr {
    @Override
    public void apply(View view) {
        if (view instanceof TabLayout) {
            TabLayout tl = (TabLayout) view;
            if (RES_TYPE_NAME_COLOR.equals(attrValueTypeName)) {
                int color = SkinResourcesUtils.getColor(attrValueRefId);
                tl.setSelectedTabIndicatorColor(color);
            }
        }
    }
}

注:attrValueRefId:就是资源 id。SkinResourcesUtils 是用来获取皮肤包里的资源,这里设置color或者drawable一定要使用本工具类。

  • 当上面的工作完成之后,就到我们自己的 Application 的 onCreate 方法中加入 SkinConfig.addSupportAttr("tabLayoutIndicator", new TabLayoutIndicatorAttr());

  • 最后我们就可以正常使用了,dynamicAddSkinEnableView(tablayout, "tabLayoutIndicator", R.color.colorPrimaryDark);

3. 关于字体切换

还是遵守本项目的约定大于配置的原则,所有的字体都放到 assets/fonts 文件夹下

如何切换字体: SkinManager.getInstance().loadFont("xx.ttf")

关于切换字体需要配置的东西: 如果只是单纯的想要字体切换这个功能。只需集成步骤中的前三步就行了。

注:字体切换功能默认不开启,需要字体切换功能请在你的Application中加入SkinConfig.setCanChangeFont(true);

4. 其他一些重要的api

  1. SkinConfig.isDefaultSkin(context):判断当前皮肤是否是默认皮肤

  2. SkinManager.getInstance().restoreDefaultTheme(): 重置默认皮肤

  3. dynamicAddView:当动态创建的View也需要换肤的时候,就可以调用 dynamicAddView


5. 使用注意事项:

  1. 换肤默认只支持 android 的常用控件,对于支持库的控件和自定义控件的换肤需要动态添加(例如: dynamicAddSkinEnableView(toolbar, "background", R.color.colorPrimaryDark);),在布局文件中使用skin:enable="true"是无效的。

  2. 默认不支持状态栏颜色的更改,如果需要换肤的同时也要更改状态栏颜色,请到您的Application文件中加入SkinConfig.setCanChangeStatusColor(true);,状态栏的颜色值来源于colorPrimaryDark

  3. 本开源库使用的 Activity 是 AppCompatActivity,使用的 Fragment 是 android.support.v4.app.Fragment

  4. 有换肤需求 View 所使用的资源一定要是引用值,例如:@color/red,而不是 #ff0000

6.项目依赖:

  1. 'com.android.support:appcompat-v7:26.1.0'

致谢:

本项目是基于 Android-Skin-Loader 这个开源库改进而来,再次对原作者表示感谢 Android-Skin-Loader

LICENSE

Copyright [2016] [_SOLID]

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.

themeskinning's People

Contributors

burgessjp avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

themeskinning's Issues

getDrawable方法的改进建议

SkinManager类的getDrawable方法,目前只能从drawable目录下加载。
在自己扩展属性的时候,有可能需要从其他目录(比如anim)下加载drawable,因此希望能够加一个新的方法,可以自己指定目录,就像这样 getDrawable(String defType, int resId)。

getDrawable方法的改进建议(二)

image
在int trueResId = mResources.getIdentifier(resName, "drawable", skinPackageName);这句里得到了trueResId,这时候应该先判断trueResId是否为0,等于0的话直接返回originDrawable即可。

如果不判断的话,虽然try catch可以捕获到异常,但是不是很好的写法,可以用判断处理的就不要通过抛异常处理。在应用提交到一些自动化测试平台(比如阿里)的时候,会检测应用抛出异常的次数,目前我在应用里集成这个库,自动化测试平台报出调用这个库发生了100多个异常,这样会认为应用的健壮性差。库里getColor方法也一样有这个问题,所以希望能够改进下。

研究了一天问几个问题

image

1.我圈起来的这部分是做什么替换的呢?
2. 我想给imageview 换图片该怎么弄呢?
3. 可不可以换布局文件呢,把整个布局文件都替换掉
4. 布局文件里的skin:enable="true" 属性是怎么加载的呢,我只要写了这个属性就可以换颜色了,这个属性在xml 里不认识 studio 一直报红色,如何解决这个问题呢

希望作者看看我的问题

代码逻辑错误

image
SkinFileUtils类的第61行存在逻辑错误,应该是if (!skinDir.exists())。

不能切肤问题

之前切肤时可以的,今天启动APP后,突然间不行了。用的时1.3.1版本

imageView setBackgroundColor not work

public class ImageViewSrcAttr extends SkinAttr {
    @Override
    protected void applySkin(View view) {
        if (view instanceof ImageView) {
            ImageView iv = (ImageView) view;
            if (isDrawable()) {
                iv.setImageDrawable(SkinResourcesUtils.getDrawable(attrValueRefId));
            } else if (isColor()) {
                iv.setBackgroundColor(SkinResourcesUtils.getColor(attrValueRefId));
            }
        }
    }
}

在ImageViewSrcAttr中这样写的话,如果xml中这样设置:
android:src="@color/main"
实际就没有换肤效果了。是否应该将setBackgroundColor改成
setImageDrawable(new ColorDrawable(SkinResourcesUtils.getColorattrValueRefId))) 我测试了一下,这样就正确换肤了。

style内的drawable为对drawable的引用时在4.几的机子上会报错崩溃

@drawable/selector_item
在style内的background为针对drawable资源的引用时,SkinInflaterFactory.parseSkinAttr的TypedArray.getColor()在4.几的机子上会抛出错误,6.0的机子没问题,5.0的没试过
Caused by: android.content.res.Resources$NotFoundException: android.content.res.Resources.loadColorStateList(Resources.java:2088)
android.content.res.TypedArray.getColor(TypedArray.java:326)
solid.ren.skinlibrary.loader.SkinInflaterFactory.parseSkinAttr(SkinInflaterFactory.java:93)

PS:所有引用资源写成@null时,getResourceEntryName()方法也会抛出异常导致崩溃

RadioButton设置的background无法动态更换皮肤

当前使用版本:1.4.6

在background中设置selecter,其select中引用的资源名和插件中的资源名完全一致,通过 skin:enable="true" 无法动态更换,
随后我使用dynamicAddView函数也无法更换

动态创建一个ImageView,如何允许更换图片?

我是按照下面方式创建的,但是没有更换。
ImageView imb = new ImageView(getContext());
imb.setImageDrawable(getResources().getDrawable(R.drawable.pi12));
ViewGroup.MarginLayoutParams paramsimg = new ViewGroup.MarginLayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
imb.setLayoutParams(paramsimg);
dynamicAddView(imb, "background", R.drawable.pi12);

当一个MainActivity中包含一个ViewPager存在的问题

当MainActivity(应用主界面,一般是和应用的生命周期是一样的)中包含一个ViewPager,ViewPager包含有很多的Fragment,当Fragment已经销毁的时候,然而其中的View还依然保存在SkinInflaterFactory的mSkinItems中,所以一直保存着其引用,不会释放,当Fragement重新创建的时候,又会有新的相同的View重新创建,由于MainActivity和应用的生命周期是一样的,mSkinItems一直不会被销毁,所以当多次滑动ViewPager就很容易发生内存溢出。目前正在寻求比较好的解决方案

一个关于drawable的问题

此Issue主要目的是讨论

小弟不才 因为想写一个换icon的demo 发现按步骤配置之后不生效 于是排查了一下问题所在
最终定位在SkinManager类的312行
int trueResId = mResources.getIdentifier(resName, "drawable", skinPackageName);

原因是第二个参数写死为drawable 而我想换的资源文件在mipmap中
所以此时 trueResId必然为0
期望替换的图片也不会被替换

虽说是约定优于配置 但还是感觉对于一些场景不太灵活
比如 有个项目已经做完了 版本迭代时提出要添加换肤功能
而此时项目中大部分图片都放置在mipmap中
如果把mipmap中所有图片搬到drawable 显然工作量不小 而且容易出错

个人建议是 getDrawable() 函数中
if (trueResId == 0) {
trueDrawable = originDrawable;
}
修改成
if(trueResId==0) {
trueResId = mResources.getIdentifier(resName, "mipmap", skinPackageName);
if (trueResId == 0) {
trueDrawable = originDrawable;
}
else{
//do something
}
}

未通读所有原代码 不能保证其正确与合理性
仅供查验与参考 有误望指出

Apk第一次安装的皮肤问题

Apk第一次安装的时候,在Application中加载其他皮肤的时候,在我的引导页中会闪烁一下原先的皮肤,然后才变成正确的皮肤,这个是加载来不及吗?

对于Fix Issues9解决方案的疑惑

最新的代码中,在SkinBaseFragment类的onDestroyView()方法里,移除了Fragment相关的View,但是在SkinInflaterFactory类的成员变量mSkinItemMap里只是移除了Fragment本身的View,那些子View并未从 mSkinItemMap里移除,那样的话子View还是不能被释放内存。不知道我的理解是否有误,希望作者能解答一下!

activity不实现Theme.AppCompat 的话,会报错

Caused by: java.lang.IllegalStateException: You need to use a Theme.AppCompat theme (or descendant) with this activity.
at android.support.v7.app.AppCompatDelegateImplV9.createSubDecor(AppCompatDelegateImplV9.java:359)

dialog换肤

如何在服务(service)中换dialog的皮肤呢?

对于Fix Issues9解决方案的疑惑

最新的代码中,在SkinBaseFragment类的onDestroyView()方法里,移除了Fragment相关的View,但是在SkinInflaterFactory类的成员变量mSkinItemMap里只是移除了Fragment本身的View,那些子View并未从mSkinItemMap里移除,那样的话子View还是不能被释放内存。不知道我的理解是否有误,希望作者能解答一下!

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.