前言
这一篇我们完成沙发页和启动页,以及沉浸式状态栏
实现沙发页
看下UI,主要就是两部分组成,上面是个TabLayout
,下面是个ViewPager
我们在做首页底部Tab
的时候用的是json
配置的,我们这个上面的TabLayout
也用一个json
来配置
{
"activeSize": 16,
"normalSize": 14,
"activeColor": "#ED7282",
"normalColor": "#666666",
"select": 0,
"tabGravity": 0,
"tabs": [
{
"title": "图片",
"index": 0,
"tag": "pics",
"enable": true
},
{
"title": "视频",
"index": 1,
"tag": "video",
"enable": true
},
{
"title": "文本",
"index": 1,
"tag": "text",
"enable": true
}
]
}
简单解释下,分别是选中字体大小,正常字体大小,选中字体颜色,正常字体颜色,选中第几个,位置,标签,然后我们定义一个对应的JavaBean
public class SofaTab {
public int activeSize;
public int normalSize;
public String activeColor;
public String normalColor;
public int select;
public int tabGravity;
public List<Tabs> tabs;
public static class Tabs {
public String title;
public int index;
public String tag;
public boolean enable;
}
}
我们之前解析BottomBar
是在AppConfig
中做的,我们这个也在这里面获取沙发相关的Tab
public static SofaTab getSofaTabConfig() {
if (sSofaTab == null) {
String content = parseFile("sofa_tabs_config.json");
sSofaTab = JSON.parseObject(content, SofaTab.class);
Collections.sort(sSofaTab.tabs, new Comparator<SofaTab.Tabs>() {
@Override
public int compare(SofaTab.Tabs o1, SofaTab.Tabs o2) {
return o1.index < o2.index ? -1 : 1;
}
});
}
return sSofaTab;
}
接着我们就改造下我们的fragment_sofa
这个布局文件,里面应该是有两个控件的
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
</data>
<LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:fitsSystemWindows="true" android:orientation="vertical">
<com.google.android.material.tabs.TabLayout android:id="@+id/tab_layout" android:layout_width="match_parent" android:layout_height="@dimen/dp_45" app:tabGravity="center" app:tabIndicatorColor="@color/color_theme" app:tabIndicatorFullWidth="false" app:tabIndicatorHeight="@dimen/dp_2" app:tabInlineLabel="true" app:tabMode="scrollable" app:tabSelectedTextColor="@color/color_theme" app:tabTextColor="@color/color_333" app:tabUnboundedRipple="true" />
<androidx.viewpager2.widget.ViewPager2 android:id="@+id/view_pager" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="horizontal" />
</LinearLayout>
</layout>
TabLayout
的用法这里就不在多说了,这里就稍微说下这个ViewPager2
,这个可以算是 ViewPager
的升级版,内部使用的是RecycleView
实现类似ViewPager
的效果,同时也解决了ViewPager
预加载的问题,使用起来也很简单
//限制页面预加载
viewPager2.setOffscreenPageLimit(ViewPager2.OFFSCREEN_PAGE_LIMIT_DEFAULT);
//viewPager2默认只有一种类型的Adapter。FragmentStateAdapter
//并且在页面切换的时候 不会调用子Fragment的setUserVisibleHint ,取而代之的是onPause(),onResume()、
viewPager2.setAdapter(new FragmentStateAdapter(getChildFragmentManager(), this.getLifecycle()) {
@NonNull
@Override
public Fragment createFragment(int position) {
//这里不需要自己保管了,FragmentStateAdapter内部自己会管理已实例化的fragment对象。
return getTabFragment(position);
}
@Override
public int getItemCount() {
return tabs.size();
}
});
tabLayout.setTabGravity(tabConfig.tabGravity);
//viewPager2 就不能和再用TabLayout.setUpWithViewPager()了
//取而代之的是TabLayoutMediator。我们可以在onConfigureTab()方法的回调里面 做tab标签的配置
//其中autoRefresh的意思是:如果viewPager2 中child的数量发生了变化,也即我们调用了adapter#notifyItemChanged()前后getItemCount不同。
//要不要 重新刷野tabLayout的tab标签。视情况而定,像咱们sofaFragment的tab数量一旦固定了是不会变的,传true/false 都问题不大
mediator = new TabLayoutMediator(tabLayout, viewPager2, true, new TabLayoutMediator.TabConfigurationStrategy() {
@Override
public void onConfigureTab(@NonNull TabLayout.Tab tab, int position) {
tab.setCustomView(makeTabView(position));
}
});
mediator.attach();
viewPager2.registerOnPageChangeCallback(mPageChangeCallback);
//切换到默认选择项,那当然要等待初始化完成之后才有效
viewPager2.post(() -> viewPager2.setCurrentItem(tabConfig.select, false));
ViewPager2
里面默认的适配器只有一种FragmentStateAdapter
,相对于ViewPager
来说配合TabLayout
的使用方法有些不同,这里需要一个TabLayoutMediator
,对应的就是attach()
和detach()
方法,监听页面变化改为registerOnPageChangeCallback
,具体的详细使用可以参考下其他的文章,这里就不在赘述了
public class SofaFragment extends Fragment {
private FragmentSofaBinding binding;
protected ViewPager2 viewPager2;
protected TabLayout tabLayout;
private SofaTab tabConfig;
private ArrayList<SofaTab.Tabs> tabs;
private TabLayoutMediator mediator;
@Override
public View onCreateView(@NonNull LayoutInflater inflater,
ViewGroup container, Bundle savedInstanceState) {
binding = FragmentSofaBinding.inflate(inflater, container, false);
return binding.getRoot();
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
viewPager2 = binding.viewPager;
tabLayout = binding.tabLayout;
tabConfig = getTabConfig();
tabs = new ArrayList<>();
for (SofaTab.Tabs tab : tabConfig.tabs) {
if (tab.enable) {
tabs.add(tab);
}
}
//限制页面预加载
viewPager2.setOffscreenPageLimit(ViewPager2.OFFSCREEN_PAGE_LIMIT_DEFAULT);
//viewPager2默认只有一种类型的Adapter。FragmentStateAdapter
//并且在页面切换的时候 不会调用子Fragment的setUserVisibleHint ,取而代之的是onPause(),onResume()、
viewPager2.setAdapter(new FragmentStateAdapter(getChildFragmentManager(), this.getLifecycle()) {
@NonNull
@Override
public Fragment createFragment(int position) {
//这里不需要自己保管了,FragmentStateAdapter内部自己会管理已实例化的fragment对象。
return getTabFragment(position);
}
@Override
public int getItemCount() {
return tabs.size();
}
});
tabLayout.setTabGravity(tabConfig.tabGravity);
//viewPager2 就不能和再用TabLayout.setUpWithViewPager()了
//取而代之的是TabLayoutMediator。我们可以在onConfigureTab()方法的回调里面 做tab标签的配置
//其中autoRefresh的意思是:如果viewPager2 中child的数量发生了变化,也即我们调用了adapter#notifyItemChanged()前后getItemCount不同。
//要不要 重新刷野tabLayout的tab标签。视情况而定,像咱们sofaFragment的tab数量一旦固定了是不会变的,传true/false 都问题不大
mediator = new TabLayoutMediator(tabLayout, viewPager2, true, new TabLayoutMediator.TabConfigurationStrategy() {
@Override
public void onConfigureTab(@NonNull TabLayout.Tab tab, int position) {
tab.setCustomView(makeTabView(position));
}
});
mediator.attach();
viewPager2.registerOnPageChangeCallback(mPageChangeCallback);
//切换到默认选择项,那当然要等待初始化完成之后才有效
viewPager2.post(() -> viewPager2.setCurrentItem(tabConfig.select, false));
}
ViewPager2.OnPageChangeCallback mPageChangeCallback = new ViewPager2.OnPageChangeCallback() {
@Override
public void onPageSelected(int position) {
int tabCount = tabLayout.getTabCount();
for (int i = 0; i < tabCount; i++) {
TabLayout.Tab tab = tabLayout.getTabAt(i);
TextView customView = (TextView) tab.getCustomView();
if (tab.getPosition() == position) {
customView.setTextSize(tabConfig.activeSize);
customView.setTypeface(Typeface.DEFAULT_BOLD);
} else {
customView.setTextSize(tabConfig.normalSize);
customView.setTypeface(Typeface.DEFAULT);
}
}
}
};
private View makeTabView(int position) {
TextView tabView = new TextView(getContext());
int[][] states = new int[2][];
states[0] = new int[]{android.R.attr.state_selected};
states[1] = new int[]{};
int[] colors = new int[]{Color.parseColor(tabConfig.activeColor), Color.parseColor(tabConfig.normalColor)};
ColorStateList stateList = new ColorStateList(states, colors);
tabView.setTextColor(stateList);
tabView.setText(tabs.get(position).title);
tabView.setTextSize(tabConfig.normalSize);
return tabView;
}
public Fragment getTabFragment(int position) {
return HomeFragment.newInstance(tabs.get(position).tag);
}
public SofaTab getTabConfig() {
return AppConfig.getSofaTabConfig();
}
@Override
public void onHiddenChanged(boolean hidden) {
super.onHiddenChanged(hidden);
List<Fragment> fragments = getChildFragmentManager().getFragments();
for (Fragment fragment : fragments) {
if (fragment.isAdded() && fragment.isVisible()) {
fragment.onHiddenChanged(hidden);
break;
}
}
}
@Override
public void onDestroy() {
mediator.detach();
viewPager2.unregisterOnPageChangeCallback(mPageChangeCallback);
super.onDestroy();
}
}
实现启动页,消除白屏
我们如果不配置启动页的话,我们的App
启动的时候可能会有一会白屏,然后才会显示我们的内容,启动分冷启动
和热启动
,这种出现白屏一般都是冷启动
发生的,解决办法网上有很多,我们这里采用一种简单高效的方式
我们首先给我们的入口类Activity
配置一个特殊的主题,不使用默认的主题
<style name="launcher" parent="AppTheme"> <item name="android:windowFullscreen">true</item> <item name="android:windowBackground">@drawable/splash</item> </style>
然后给我们的MainActivity
设置主题
<activity android:name=".MainActivity" android:label="@string/app_name" android:theme="@style/launcher">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
接着我们在MainActivity
的onCreate
方法中设置回正常的主题即可
@Override
protected void onCreate(Bundle savedInstanceState) {
//由于 启动时设置了 R.style.launcher 的windowBackground属性
//势必要在进入主页后,把窗口背景清理掉
setTheme(R.style.AppTheme);
super.onCreate(savedInstanceState);
...
}
实现沉浸式状态栏
public class StatusBar {
public static void fitSystemBar(Activity activity) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M)
return;
Window window = activity.getWindow();
View decorView = window.getDecorView();
//View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN--能够使得我们的页面布局延伸到状态栏之下,但不会隐藏状态栏。也就相当于状态栏是遮盖在布局之上的
//View.SYSTEM_UI_FLAG_FULLSCREEN -- 能够使得我们的页面布局延伸到状态栏,但是会隐藏状态栏。
//WindowManager.LayoutParams.FLAG_FULLSCREEN
decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
| View.SYSTEM_UI_FLAG_LAYOUT_STABLE
| View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
window.setStatusBarColor(Color.TRANSPARENT);
}
public static void lightStatusBar(Activity activity, boolean light) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M)
return;
Window window = activity.getWindow();
View decorView = window.getDecorView();
int visibility = decorView.getSystemUiVisibility();
if (light) {
visibility |= View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR;
} else {
visibility &= ~View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR;
}
decorView.setSystemUiVisibility(visibility);
}
}
OK,最后我们看下效果