换肤框架
来源:互联网 发布:淘宝店铺装修的意义 编辑:程序博客网 时间:2024/06/01 09:20
序言
现在说是换肤框架还有点夸大其词,因为目前只实现了颜色的替换,目前网上已有的换肤框架我都研究过,主要感觉给每个View设置样式,还要保存每个需要换肤的View,实在是太繁琐,而且目前我的项目中不需要皮肤功能,开发这个框架也仅仅是为了实现夜间模式,而又不用过多的改造原有的代码,比如给每个颜色替换成引用等等。从目前实现的效果来看,基本能达到简单方便的目的,而且也能实现WebView的换肤,且不会重启Activity,还有过渡动画,我相信这个框架已经能满足大多数的项目了。
效果
功能
1.支持通过配置文件设置全局样式
2.配置文件实现继承功能,编写更灵活
3.不需要重启Activity,并带有过渡动画。
4.对原有项目的改造可以说很小,基本只需要改造XML文件。
5.可在多种样式中切换
6.实现了对WebView的支持。
使用
1.配置样式文件
目前的Library中已经集成了几种样式,一种是白天的,一种是网易新闻的夜间模式,一种是百度贴吧的夜间模式,如果你觉得不满意,可以在你自己项目的raw目录项新建自己的,注意:配置文件的格式是JSON格式。
1.Library中自带的style文件
2.style文件的定义,具体的请大家看源文件吧。
2.在Application中初始化
//将需要使用的Style文件名传入即可,注意不需要后缀 StyleHelper.init(this,"wangyi","baidu", "day");
3.在BaseActivity中初始化
其实此处的注册就是将acitivty添加到StyleHelper的acitiviy栈中,这样方便遍历所有的view,为了防止可能发生的内容泄漏,在activity栈中我使用的是弱引用。
/** * Created by zhuguohui on 2016/7/18. */public abstract class BaseActivity extends AppCompatActivity { @Override public final void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); childOnCreate(savedInstanceState); //注意,只有到所有view都创建完成以后再初始化 StyleHelper.initActivity(this); } public abstract void childOnCreate(Bundle savedInstanceState); public void changeMode(){ StyleHelper.changeStyle(0, 1); } @Override protected void onDestroy() { super.onDestroy(); //销毁Activity StyleHelper.destroyActivity(); }}
4.样式切换
在需要切换的地方,调用StyleHelper.changeStyle传入需要切换的StyleId就行了,这个id是在配置文件中注册的,注意id必需唯一。
findViewById(R.id.btn_baidu).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { StyleHelper.changeStyle(0,1); } }); findViewById(R.id.btn_wangyi).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { StyleHelper.changeStyle(0,2); } });
5.个性化定制
通过以上的配置已经能使用了,但是字体颜色背景色都会统一,对于需要不同字体颜色的需要,还需要我们自定义type。
1.自定义type实现不同的字体颜色
在我的Demo中每个Item中有两个TextView,一个用来显示标题,一个用来显示内容。而且两个字体颜色并不同。
我的实现方式是,将内容的字体颜色定义在type_base中,然后再定义一个type_title继承自type_base重写字体颜色,如下:
最后再XML中设置type
2.给子View设置type
对于一些系统的View我们不能直接给其内部的view设置type,我们可以通过这种方式设置,关于怎么实现的我们后面再讲。
3.不需要换肤
有一些控件我们不需要换肤,直接设置为type_no就行了,这个type已经定义在StyleHelper中了。
4.WebView换肤
WebView换肤我主要通过JavaScript实现的,所以需要在每个页面加载完成以后调用 StyleHelper.setupWebView();
webview.setWebViewClient(new WebViewClient() { @Override public void onPageFinished(WebView view, String url) { StyleHelper.setupWebView(view); } });
但是这里面还有很多细节需要注意,为了实现在夜间模式进入webview的时候,webview的背景不是白色,此处需要将webview的背景设置为透明,且需要在外侧包裹一层layout,通过对layout背景的变色实现webview背景的变色,布局文件可以参考这样。
<?xml version="1.0" encoding="utf-8"?><RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context="com.zgh.mvpdemo.WebViewActivity"> <WebView android:id="@+id/webview" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@null" /></RelativeLayout>
在Android4.4以上还必须关闭硬件加速才能实现webview背景透明
在Java代码中调用,这个方法的调用我已经写在框架中了。
webView.setBackgroundColor(0);
另外还有一点需要注意,由于我们要在页面加载完成以后才使用JavaScript动态改变背景色,所以网页中的body不能设置背景设,如果原来body的背景色为白色,等我们设置成黑色的时候就会有闪烁的感觉,用户体验很不好。我的Demo中跳转到网易新闻的手机版,就是很好的实现,大家可以看看他们的网页源码实现。
实现
1.遍历View的实现
主要通过方法递归调用实现,主要的难点在于一些有缓存池功能的View不然AbsListView要通过反射修改缓存池中的View的样式.
private static void setColor(View view, boolean IsRecursion) { int newColor = 0; if (view == null) { return; } //获取tag_style,此处的作用是将view与styleId进行绑定,防止重复设置。 Object tag = view.getTag(R.id.tag_style); if (tag == null && sCurrentStyleId == 0) { view.setTag(R.id.tag_style, sCurrentStyleId); return; } if (tag != null) { int viewStyle = (int) tag; if (viewStyle == sCurrentStyleId) { return; } } view.setTag(R.id.tag_style, sCurrentStyleId); //获取需要显示的样式 Object viewTag = view.getTag(); String typeName = ""; if (viewTag != null && viewTag instanceof String) { typeName = (String) viewTag; } else { //判断是否设置了默认的type,如果有则使用默认的 if (sHaveSetDefaluType) { typeName = sDefalutTypeName; } } //为了实现给子view设置type,我们每一次调用就去掉一个:,然后再设置给其子view //比如:tag,在这里会被变成tag,然后设置给子view,最后递归。当递归到子view的时候就不含有: //就会进入正常的设置过程了。 boolean needInherit = typeName.startsWith(":"); if (needInherit) { String inheritName = typeName.substring(1); if (view instanceof ViewGroup) { ViewGroup group = (ViewGroup) view; int count = group.getChildCount(); for (int i = 0; i < count; i++) { View childView = group.getChildAt(i); childView.setTag(inheritName); setColor(childView, IsRecursion); } return; } } //表明此view不需要换肤,直接return if (DISABLE_TYPE_NAME.equals(typeName)) { return; } //webview需要单独处理 if (view instanceof WebView) { setupWebView((WebView) view); return; } //只有在view没有设置背景色或者只使用颜色而不是其他 if (view.getBackground() == null || (view.getBackground() instanceof ColorDrawable)) { newColor = getFSColor(sCurrentStyleId, typeName, Attribute.TYPE_BACKGROUND_COLOR); if (newColor != 0) { view.setBackgroundColor(newColor); } } if (view instanceof ViewGroup) { //只有在listview设置了diver的情况下才换肤 if (view instanceof ListView && ((ListView) view).getDivider() instanceof ColorDrawable) { ListView lv = (ListView) view; newColor = getFSColor(sCurrentStyleId, typeName, Attribute.TYPE_DIVER_COLOR); if (newColor != 0) { int height = lv.getDividerHeight(); lv.setDivider(new ColorDrawable(newColor)); lv.setDividerHeight(height); } } if (IsRecursion) { ViewGroup group = (ViewGroup) view; int count = group.getChildCount(); for (int i = 0; i < count; i++) { setColor(group.getChildAt(i), true); } if (view instanceof AbsListView) { //如果是ListView则要通过反射将缓存池中的view换肤 AbsListView absListView = (AbsListView) view; try { Field field = AbsListView.class.getDeclaredField("mRecycler"); field.setAccessible(true); Object o = field.get(absListView); Class<?> cls = Class.forName("android.widget.AbsListView$RecycleBin"); Field scrapViews = cls.getDeclaredField("mScrapViews"); scrapViews.setAccessible(true); ArrayList<View>[] views = (ArrayList<View>[]) scrapViews.get(o); for (int i = 0; i < views.length; i++) { ArrayList<View> vs = views[i]; for (View v : vs) { setColor(v, true); } } } catch (Exception e) { e.printStackTrace(); } } } } else { if (view instanceof TextView) { TextView tv = (TextView) view; newColor = getFSColor(sCurrentStyleId, typeName, Attribute.TYPE_FONT_COLOR); if (newColor != 0) { tv.setTextColor(newColor); } } } }
2.对新添加的View的处理
通过以上的代码,我们已经能使已有的view改变样式了,但是如果用户new了一个View,添加进入其样式还是原来的。所以我们需要对给ViewGroup添加监听事件,主要的代码如下:
public static void initActivity(Activity activity) { mActivityStack.push(new WeakReference<Activity>(activity)); View view = activity.findViewById(android.R.id.content); setColor(view, true); //给ViewGrou设置监听器,当有新的view添加时也会设置样式。 setOnHierarchyChangeListener(view); } private static void setOnHierarchyChangeListener(View view) { if (view == null) { return; } if (view instanceof ViewGroup) { ViewGroup viewGroup = (ViewGroup) view; viewGroup.setOnHierarchyChangeListener(mOnHierarchyChangeListener); int childCount = viewGroup.getChildCount(); for (int i = 0; i < childCount; i++) { //递归调用 setOnHierarchyChangeListener(viewGroup.getChildAt(i)); } } } private static ViewGroup.OnHierarchyChangeListener mOnHierarchyChangeListener = new ViewGroup.OnHierarchyChangeListener() { @Override public void onChildViewAdded(View parent, View child) { //如果parent被标记为type_no,则child也会被标记 if (DISABLE_TYPE_NAME.equals(checkTag(child))) { child.setTag(DISABLE_TYPE_NAME); } setColor(child, true); setOnHierarchyChangeListener(child); } @Override public void onChildViewRemoved(View parent, View child) { } }; private static String checkTag(View child) { String tag = ""; if (child == null) { return tag; } try { View parent = null; if (child.getParent() instanceof View) { parent = (View) child.getParent(); } String parentTag = (String) parent.getTag(); while (parent != null && !DISABLE_TYPE_NAME.equals(parentTag)) { child = parent; parent = null; if (child.getParent() instanceof View) { parent = (View) child.getParent(); parentTag = (String) parent.getTag(); } } return parentTag; } catch (Exception e) { e.printStackTrace(); } return tag; }
3.WebView实现夜间模式
核心还是JavaScript,其他的内容已经在使用的时候说了。
document.body.style.backgroundColor="#FFFFFF";//改变背景色 //改变字体颜色 document.body.style.color="#000000"; //改变A标签颜色 var as = document.getElementsByTagName("a"); for(var i=0;i<as.length;i++){ as[i].style.color = ""; } //改变所以DIV的颜色,使其与背景色统一 var divs = document.getElementsByTagName("div"); for(var i=0;i<divs.length;i++){ divs[i].style.backgroundColor = "#FFFFFF"; }
过渡动画
大家看注释吧,没什么难度
//生成动画,原理:获取当前界面截图,生成一个imageview,添加到decorview中,即覆盖在原来 //界面的上层,并在一定时间内改变imageview的alpha值,当alpha值为0的时候,将imageview从decorview中移除出去 private static void createAnimator(Activity activity) { if (activity == null) { return; } //获取decorview View decorView = activity.getWindow().getDecorView(); final ImageView imageView = new ImageView(activity); int width = activity.getResources().getDisplayMetrics().widthPixels; int height = activity.getResources().getDisplayMetrics().heightPixels; Bitmap b = Bitmap.createBitmap(width, height, Bitmap.Config.RGB_565); //生成截图 decorView.draw(new Canvas(b)); imageView.setImageBitmap(b); final ViewGroup group = (ViewGroup) decorView; imageView.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); group.addView(imageView); ValueAnimator animator = ValueAnimator.ofFloat(1, 0); animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { float alph = (float) animation.getAnimatedValue(); imageView.setAlpha(alph); } }); animator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { group.removeView(imageView); } }); animator.setDuration(1000); animator.start(); }
项目
欢迎大家star哈,演示的Demo是我上一篇博客的使用MVP打造项目框架,大家主要看StyleLib吧。
StyleDemo
- 换肤框架
- Android换肤框架
- 换肤框架
- 换肤框架
- 换肤框架
- 换肤框架的资源
- 换肤框架学习总结
- 框架页面的换肤实现
- [Swift 开发] iOS换肤框架 SwiftTheme
- 换肤框架 skin-loader-lib 使用
- Android换肤功能实现与换肤框架QSkinLoader使用方式介绍
- Android换肤白天/夜间模式的框架
- android的资源混淆和压缩工具,换肤框架
- Andorid 换肤框架AndSkin源码解析及优缺点
- Android换肤原理和Android-Skin-Loader框架解析
- 换肤
- 换肤
- 换肤
- 终端修改 ~/.bashrc, ~/.profile, ~/.zshrc
- <JAVA模式>之装饰模式
- C++ string类的简单实现
- LeetCode题解-113-Path Sum II
- 运用预制件添加人物(让程序顺序执行 , 待修改)
- 换肤框架
- 纽摄(上)
- android-UniversalMusicPlayer(Google出品)
- android.content.res.Resources$NotFoundException: String resource ID #0x1
- AngularJS实现动态添加输入控件功能
- 浅谈cookie
- github应用
- Java实现对List去重
- Grunt个人学习