安卓app作为一个有界面的程序,谷歌给我们提供了很多UI控件。而控件是功能性的,具体的控件样式却是需要我们自己去控制的。这也就引出了今天话题,安卓的theme与style。说到这个,大家可能都知道theme是主题,也就是整个app的样式。而style是样式,控制的是单个View。是的,希望我们在应用的时候也要铭记这点。
也就是说theme是针对application和activity的,当然也不是所有都是这样的,比如toolbar就可以设置theme。一开始toolbar是特例,但后来谷歌推出了ThemeOverlay使得一般view也可以使用theme。而且会传递给子控件。theme原理是什么呢,我们来看看View的构造方法。
public View(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
this(context);
final TypedArray a = context.obtainStyledAttributes(
attrs, com.android.internal.R.styleable.View, defStyleAttr, defStyleRes);
可以看到是使用的context对象得到的属性数组,这也是所有的View构造方法要包含context的原因。具体参数意义及作用顺序,你也可以直接看里面结论就好了。
其实控制theme不难,难的是控制一个view的style,因为它跟theme还有关系。比如现在我改变了一个activity的theme,我如何让里面的View用原theme的样式呢?为了回答这个问题,我们先来看Dialog源码。因为Dialog是可以设置Theme的,而且不影响原来的activity。
Dialog(@NonNull Context context, @StyleRes int themeResId, boolean createContextThemeWrapper) {
if (createContextThemeWrapper) {
if (themeResId == 0) {
final TypedValue outValue = new TypedValue();
context.getTheme().resolveAttribute(R.attr.dialogTheme, outValue, true);
themeResId = outValue.resourceId;
}
mContext = new ContextThemeWrapper(context, themeResId); //可以看到true时,会创建一个这种对象
} else {
mContext = context;
}
那么,不是有一个context,为什么要重新生成一个ContextThemeWrapper呢。关于context点这里,我们来看看这个ContextThemeWrapper。
/**
* A ContextWrapper that allows you to modify the theme from what is in the
* wrapped context.
*/
public class ContextThemeWrapper extends ContextWrapper {
private int mThemeResource;
private Resources.Theme mTheme;
private LayoutInflater mInflater;
private Configuration mOverrideConfiguration;
private Resources mResources;
public ContextThemeWrapper() {
super(null);
}
public ContextThemeWrapper(Context base, @StyleRes int themeResId) {
super(base);
mThemeResource = themeResId;
}
public ContextThemeWrapper(Context base, Resources.Theme theme) {
super(base);
mTheme = theme;
}..............
可以看到重写了theme相关的方法和属性,而且从类的介绍也可以看出。回到原来的问题,我们可以通过new一个这个类的对象给View传过去。