• Android 切换主题 (二)


    Android 切换主题 (二)

    背景

    我原来写过一篇文章关于 android 切换主题的文章 -- Android 切换主题以及换肤的实现 , 里面介绍了如何使用 setTheme() 来切换主题,只不过使用这个函数有个缺点那就是你必须得重新启动当前的 Activity 才能生效!那么问题来了,有没有方法保证我们既使用了 setTheme() 又能不重启当前的 Activity 呢?我告诉这是有的,下面我就是详细介绍这个方法 (参考 github 上的一个开源项目进行介绍!文末给出这个项目的地址)。

    ===

    原理

    众所周知,setTheme() 之后需要重启当前的 Activity 的原因是:它要重新渲染当前的 当前的 ViewTree 。所以现在我们的做法就是我们来自己给他渲染不就行了!那样的话,就不需要重启当前的 Activity 了!下面我们就来看代码吧!

    ===

    代码

    代码的实现核心就是:在用户调用 setTheme() 之后,我们获取当前的 Theme ,之后我们在从中获取到我们用的属性,之后在设置到对应的控件上,这样就ok了!

    第一步

    我们先定义个接口:

    public interface ColorUiInterface {
    	public View getView();
    	public void setTheme(Resources.Theme themeId);
    }
    

    这样的话我们就可以重写所有的控件,让他继承该接口,并实现对应的函数,那么在 setTheme() 之后就可以直接调用每个控件都有的 setTheme() 方法了!

    第二步

    现在我们就来实现一个自定义的view

    public class ColorTextView extends TextView implements ColorUiInterface {
     	private int attr_drawable = -1;
    	private int attr_textAppearance = -1;
    	private int attr_textColor = -1;
    	public ColorTextView(Context context) {
        	super(context);
    	}
    
    	public ColorTextView(Context context, AttributeSet attrs) {
        	super(context, attrs);
        	this.attr_drawable = ViewAttributeUtil.getBackgroundAttibute(attrs);
        	this.attr_textColor = ViewAttributeUtil.getTextColorAttribute(attrs);
    	}
    
    	public ColorTextView(Context context, AttributeSet attrs, int defStyleAttr) {
        	super(context, attrs, defStyleAttr);
        	this.attr_drawable = ViewAttributeUtil.getBackgroundAttibute(attrs);
        	this.attr_textColor = ViewAttributeUtil.getTextColorAttribute(attrs);
    	}
    
    	@Override
    	public View getView() {
        	return this;
    	}
    
    	@Override
    	public void setTheme(Resources.Theme themeId) {
        	if (attr_drawable != -1) {
            	ViewAttributeUtil.applyBackgroundDrawable(this, themeId, attr_drawable);
        	}
        	if (attr_textColor != -1) {
            	ViewAttributeUtil.applyTextColor(this, themeId, attr_textColor);
        	}
    	}
    }
    

    从以上代码中我们可以看到,我首先获取了一下一些常用的需要换肤的要素,比如:背景色,字体颜色,当让还有其他的,这个随用户定制!当然读者也可以实现其他的自定义 view 。

    第三步

    现在我们来看下 ViewAttributeUtil 这个类的具体实现!

    public class ViewAttributeUtil {
    
      public static int getAttributeValue(AttributeSet attr, int paramInt) {
        int value = -1;
        int count = attr.getAttributeCount();
        for(int i = 0; i <count;i++) {
            if(attr.getAttributeNameResource(i) == paramInt) {
                String str = attr.getAttributeValue(i);
                if(null != str && str.startsWith("?")) {
                    value = Integer.valueOf(str.substring(1,str.length())).intValue();
                    return value;
                }
            }
        }
        return value;
    }
    
    public static int getBackgroundAttibute(AttributeSet attr) {
        return getAttributeValue(attr , android.R.attr.background);
    }
    
    public static int getCheckMarkAttribute(AttributeSet attr) {
        return getAttributeValue(attr, android.R.attr.checkMark);
    }
    
    public static int getSrcAttribute(AttributeSet attr) {
        return getAttributeValue(attr, android.R.attr.src);
    }
    
    public static int getTextApperanceAttribute(AttributeSet attr) {
        return getAttributeValue(attr, android.R.attr.textAppearance);
    }
    
    public static int getDividerAttribute(AttributeSet attr) {
        return getAttributeValue(attr, android.R.attr.divider);
    }
    
    public static int getTextColorAttribute(AttributeSet attr) {
        return getAttributeValue(attr, android.R.attr.textColor);
    }
    
    public static void applyBackgroundDrawable(ColorUiInterface ci, Resources.Theme theme, int paramInt) {
        TypedArray ta = theme.obtainStyledAttributes(new int[]{paramInt});
        Drawable drawable = ta.getDrawable(0);
        if(null != ci) {
            (ci.getView()).setBackgroundDrawable(drawable);
        }
        ta.recycle();
    }
    
    public static void applyImageDrawable(ColorUiInterface ci, Resources.Theme theme, int paramInt) {
        TypedArray ta = theme.obtainStyledAttributes(new int[]{paramInt});
        Drawable drawable = ta.getDrawable(0);
        if(null != ci && ci instanceof ImageView) {
            ((ImageView)ci.getView()).setImageDrawable(drawable);
        }
        ta.recycle();
    }
    
    public static void applyTextAppearance(ColorUiInterface ci, Resources.Theme theme, int paramInt) {
        TypedArray ta = theme.obtainStyledAttributes(new int[]{paramInt});
        int resourceId = ta.getResourceId(0,0);
        if(null != ci && ci instanceof TextView) {
            ((TextView)ci.getView()).setTextAppearance(ci.getView().getContext(), resourceId);
        }
        ta.recycle();
    }
    
    public static void applyTextColor(ColorUiInterface ci, Resources.Theme theme, int paramInt) {
        TypedArray ta = theme.obtainStyledAttributes(new int[]{paramInt});
        int resourceId = ta.getColor(0,0);
        if(null != ci && ci instanceof TextView) {
            ((TextView)ci.getView()).setTextColor(resourceId);
        }
        ta.recycle();
    }
    }
    

    这个类比较简单,就是根据对应的 themeid 得到对应 themeid 的值!好了目前为止,我们还差一步就是,当我们调用了 Activity 的 setTheme() 方法之后,重新设置到对应的view就行了!

    第四步

    直接上代码

    public class ColorUiUtil {
    /**
     * 切换应用主题
     *
     * @param rootView
     */
    public static void changeTheme(View rootView, Resources.Theme theme) {
        if (rootView instanceof ColorUiInterface) {
            ((ColorUiInterface) rootView).setTheme(theme);
            if (rootView instanceof ViewGroup) {
                int count = ((ViewGroup) rootView).getChildCount();
                for (int i = 0; i < count; i++) {
                    changeTheme(((ViewGroup) rootView).getChildAt(i), theme);
                }
            }
            if (rootView instanceof AbsListView) {
                try {
                    Field localField = AbsListView.class.getDeclaredField("mRecycler");
                    localField.setAccessible(true);
                    Method localMethod = Class.forName("android.widget.AbsListView$RecycleBin").getDeclaredMethod("clear", new Class[0]);
                    localMethod.setAccessible(true);
                    localMethod.invoke(localField.get(rootView), new Object[0]);
                } catch (NoSuchFieldException e1) {
                    e1.printStackTrace();
                } catch (ClassNotFoundException e2) {
                    e2.printStackTrace();
                } catch (NoSuchMethodException e3) {
                    e3.printStackTrace();
                } catch (IllegalAccessException e4) {
                    e4.printStackTrace();
                } catch (InvocationTargetException e5) {
                    e5.printStackTrace();
                }
            }
        } else {
            if (rootView instanceof ViewGroup) {
                int count = ((ViewGroup) rootView).getChildCount();
                for (int i = 0; i < count; i++) {
                    changeTheme(((ViewGroup) rootView).getChildAt(i), theme);
                }
            }
            if (rootView instanceof AbsListView) {
                try {
                    Field localField = AbsListView.class.getDeclaredField("mRecycler");
                    localField.setAccessible(true);
                    Method localMethod = Class.forName("android.widget.AbsListView$RecycleBin").getDeclaredMethod("clear", new Class[0]);
                    localMethod.setAccessible(true);
                    localMethod.invoke(localField.get(rootView), new Object[0]);
                } catch (NoSuchFieldException e1) {
                    e1.printStackTrace();
                } catch (ClassNotFoundException e2) {
                    e2.printStackTrace();
                } catch (NoSuchMethodException e3) {
                    e3.printStackTrace();
                } catch (IllegalAccessException e4) {
                    e4.printStackTrace();
                } catch (InvocationTargetException e5) {
                    e5.printStackTrace();
                }
            }
        }
    }
    }
    

    代码很简单,就是用了递归,从当前的 ViewTree 中找到 继承了我们在第一步定义的那个接口,之后再调用其 setTheme() 方法就行了!!

    最后一步

    那么我们现在来看下如何在 Activity 中是如何调用的!

       setTheme(R.style.theme_1);
       ColorUiUtil.changeTheme(rootView, getTheme());	
    

    是不是很简单呀!

    总结

    这个方法我感觉蛮好的,要说唯一的缺点吧!就是使用了递归!有点耗性能!但是这个我估计是可以忽略不计的!!

    *** 现在给出这个开源项目的地址 MultipleTheme ***

  • 相关阅读:
    OpenGL——旋转的六边形(动画)
    OpenGL——三维多面体实现
    OpenGL——二维几何变换
    OpenGL——圆公式相关变化的绘制
    OpenGL——折线图柱状图饼图绘制
    图片相似原理--Java实现
    Service 保活法之二
    Service 保活法之一
    Android仿腾讯手机管家实现桌面悬浮窗小火箭发射的动画效果
    让应用在息屏后保活
  • 原文地址:https://www.cnblogs.com/likeandroid/p/4695675.html
Copyright © 2020-2023  润新知