换肤框架

作者: zhuguohui | 来源:发表于2016-08-22 09:13 被阅读363次

    序言

    现在说是换肤框架还有点夸大其词,因为目前只实现了颜色的替换,目前网上已有的换肤框架我都研究过,主要感觉给每个View设置样式,还要保存每个需要换肤的View,实在是太繁琐,而且目前我的项目中不需要皮肤功能,开发这个框架也仅仅是为了实现夜间模式,而又不用过多的改造原有的代码,比如给每个颜色替换成引用等等。从目前实现的效果来看,基本能达到简单方便的目的,而且也能实现WebView的换肤,且不会重启Activity,还有过渡动画,我相信这个框架已经能满足大多数的项目了。

    效果

    这里写图片描述

    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

    相关文章

      网友评论

        本文标题:换肤框架

        本文链接:https://www.haomeiwen.com/subject/aciwsttx.html