可以先看前两篇文章:
Android 动态式换肤框架1-setContentView源码分析:
https://blog.csdn.net/hongxue8888/article/details/95494195
Android 动态式换肤框架2-实现背景替换:
https://blog.csdn.net/hongxue8888/article/details/95390639
上篇文章实现了Activity的背景换肤,接下来实现以下的换肤:
- Fragment换肤
- 状态栏换肤
1 Fragment 换肤
1.1 Fragment 换肤实现
继续使用上篇文章中的代码,在MainActivity中添加三个Fragment。
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="skinSelect"
android:text="个性换肤" />
<!--测试换肤==> viewPager 与 fragment-->
<com.hongx.hxskin.widget.MyTabLayout
android:id="@+id/tabLayout"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:tabIndicatorColor="@color/tabSelectedTextColor"
app:tabTextColor="@color/tab_selector"/>
<android.support.v4.view.ViewPager
android:id="@+id/viewPager"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</LinearLayout>
MainActivity的onCreate方法中
MyTabLayout tabLayout = findViewById(R.id.tabLayout);
ViewPager viewPager = findViewById(R.id.viewPager);
List<Fragment> list = new ArrayList<>();
list.add(new MusicFragment());
list.add(new VideoFragment());
list.add(new RadioFragment());
List<String> listTitle = new ArrayList<>();
listTitle.add("音乐");
listTitle.add("视频");
listTitle.add("电台");
MyFragmentPagerAdapter myFragmentPagerAdapter = new MyFragmentPagerAdapter
(getSupportFragmentManager(), list, listTitle);
viewPager.setAdapter(myFragmentPagerAdapter);
tabLayout.setupWithViewPager(viewPager);
在MusicFragment中添加一个和MainActivity一样的背景:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:background="@drawable/t_window_bg"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="音乐"
android:textColor="@color/colorAccent"
android:textSize="22sp" />
</LinearLayout>
查看效果:
什么也没做,同样实现了Fragment的换肤,为什么?
1.2 源码分析
打开 /android.support.v4.app.Fragment.java
搜索LayoutInflater会发现onGetLayoutInflater方法中创建了LayoutInflater
public LayoutInflater onGetLayoutInflater(@Nullable Bundle savedInstanceState) {
return this.getLayoutInflater(savedInstanceState);
}
FragmentHostCallback mHost;
...
public LayoutInflater getLayoutInflater(@Nullable Bundle savedFragmentState) {
if (this.mHost == null) {
throw new IllegalStateException("onGetLayoutInflater() cannot be executed until the Fragment is attached to the FragmentManager.");
} else {
LayoutInflater result = this.mHost.onGetLayoutInflater();//1
this.getChildFragmentManager();
LayoutInflaterCompat.setFactory2(result, this.mChildFragmentManager.getLayoutInflaterFactory());//2
return result;
}
}
注释1:通过mHost.onGetLayoutInflater()创建了LayoutInflater对象result
注释2:设置Factory2
/android.support.v4.app.FragmentHostCallback.java
public LayoutInflater onGetLayoutInflater() {
return LayoutInflater.from(this.mContext);
}
/android.support.v4.view.LayoutInflaterCompat.java
public static void setFactory2(@NonNull LayoutInflater inflater, @NonNull Factory2 factory) {
inflater.setFactory2(factory);
if (VERSION.SDK_INT < 21) {
Factory f = inflater.getFactory();
if (f instanceof Factory2) {
forceSetFactory2(inflater, (Factory2)f);
} else {
forceSetFactory2(inflater, factory);
}
}
}
/android.view.LayoutInflater.java
public void setFactory2(Factory2 factory) {
if (mFactorySet) {
throw new IllegalStateException("A factory has already been set on this LayoutInflater");
}
if (factory == null) {
throw new NullPointerException("Given factory can not be null");
}
mFactorySet = true;
if (mFactory == null) {
mFactory = mFactory2 = factory;
} else {
mFactory = mFactory2 = new FactoryMerger(factory, factory, mFactory, mFactory2);
}
}
Fragment 会走
mFactory = mFactory2 = new FactoryMerger(factory, factory, mFactory, mFactory2);
private static class FactoryMerger implements Factory2 {
private final Factory mF1, mF2;
private final Factory2 mF12, mF22;
FactoryMerger(Factory f1, Factory2 f12, Factory f2, Factory2 f22) {
mF1 = f1;
mF2 = f2;
mF12 = f12;
mF22 = f22;
}
public View onCreateView(String name, Context context, AttributeSet attrs) {
View v = mF1.onCreateView(name, context, attrs);
if (v != null) return v;
return mF2.onCreateView(name, context, attrs);
}
public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
View v = mF12 != null ? mF12.onCreateView(parent, name, context, attrs)
: mF1.onCreateView(name, context, attrs);
if (v != null) return v;
return mF22 != null ? mF22.onCreateView(parent, name, context, attrs)
: mF2.onCreateView(name, context, attrs);
}
}
FactoryMerger实现了工厂合并。
2 状态栏换肤
首先需要将app和app_skin中分别设置colorPrimaryDark为两种不同的颜色。
<color name="colorPrimaryDark">#00574B</color>
<color name="colorPrimaryDark">#ffce3d3a</color>
在SkinThemeUtils.java中添加如下代码:
private static int[] APPCOMPAT_COLOR_PRIMARY_DARK_ATTRS = {
android.support.v7.appcompat.R.attr.colorPrimaryDark
};
private static int[] STATUSBAR_COLOR_ATTRS = {android.R.attr.statusBarColor, android.R.attr
.navigationBarColor};
public static void updateStatusBarColor(Activity activity) {
//5.0以上才能修改
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
return;
}
//获得 statusBarColor 与 nanavigationBarColor (状态栏颜色)
//当与 colorPrimaryDark 不同时 以statusBarColor为准
int[] statusBarColorResId = getResId(activity, STATUSBAR_COLOR_ATTRS);
//如果直接在style中写入固定颜色值(而不是 @color/XXX ) 获得0
if (statusBarColorResId[0] != 0) {
activity.getWindow().setStatusBarColor(SkinResources.getInstance().getColor
(statusBarColorResId[0]));
} else {
//获得 colorPrimaryDark
int colorPrimaryDarkResId = getResId(activity, APPCOMPAT_COLOR_PRIMARY_DARK_ATTRS)[0];
if (colorPrimaryDarkResId != 0) {
activity.getWindow().setStatusBarColor(SkinResources.getInstance().getColor
(colorPrimaryDarkResId));
}
}
if (statusBarColorResId[1] != 0) {
activity.getWindow().setNavigationBarColor(SkinResources.getInstance().getColor
(statusBarColorResId[1]));
}
}
在SkinLayoutInflaterFactory的update方法中去执行更换颜色
@Override
public void update(Observable o, Object arg) {
SkinThemeUtils.updateStatusBarColor(activity);
}
为了使下次进入后显示换肤后的颜色,需要在SkinActivityLifecycle的onActivityCreated方法中添加换肤操作。
@Override
public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
/**
* 更新状态栏
*/
SkinThemeUtils.updateStatusBarColor(activity);
}
看状态栏换肤效果: