Android下的沉浸式状态栏+折叠TitleBar(CoordinatorLayout+CollapsingToolbarLayout)+ViewPager切换实现

来源:互联网 发布:安全课 防火知多少 编辑:程序博客网 时间:2024/05/01 01:28

Android下的沉浸式状态栏+折叠TitleBar(CoordinatorLayout+CollapsingToolbarLayout)+ViewPager切换实现


最终效果以及过程中出现的问题如下:(简单解决在ViewPager+Fragment的组合中实现该效果时出现有页面视图偏移一个状态栏高度问题)

                                                                                                                 
           
 
一:实现沉浸式的状态栏
    沉浸式状态栏的实现是从android版本KITKAT开始,从KITKAT开始谷歌开始支持状态栏的背景色设置为透明,但在Lollipop版本(5.0)之后概念有所不同。
 设置状态栏透明的步骤:

1.实现当前activity的theme样式为无Tilte的,否则会有系统的titlebar显示在屏幕上方影响效果。
    即在Style.xml文件里面自定义一个主题样式:AppNoTitleTheme
    主要添加条目属性字段:windowNoTitletrue。并把要实现沉浸样式的Activity或整个应用的theme设置为该style 
  1. android:theme="@style/AppNoTitleTheme"
AppNoTitleTheme对应如下:
  1. <style name="AppNoTitleTheme" parent="Theme.AppCompat.Light.DarkActionBar">
  2. <!-- Customize your theme here. -->
  3. <item name="colorPrimary">@color/colorPrimary</item>
  4. <item name="windowNoTitle">true</item>
  5. <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
  6. <item name="colorAccent">@color/colorAccent</item>
  7. </style>
 
当然也可以直接引用系统预定义的样式:
  1. android:theme="@style/Theme.AppCompat.Light.NoActionBar"
2.是实现状态栏的背景为透明背景,实现可以看到状态栏下方的视图内容,该实现一般在Activity中代码添加FLAG实现
  1. protected void onCreate(Bundle savedInstanceState) {
  2. super.onCreate(savedInstanceState);
  3. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
  4. this.getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);//此FLAG可使状态栏透明,且当前视图在绘制时,从屏幕顶端开始即top = 0开始绘制,这也是实现沉浸效果的基础
  5. this.getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);//可不加
  6. }
  7. setContentView(R.layout.activity_main);//在此之前添加以上FLAG
  8. initView();
  9. initEvent();
  10. }
tip:该FLAG需要在Activity 的setContentView(R.layout.activity_main)方法之前添加
加上此段代码之后在KITKAT及之上的版本即可实现状态栏透明,并且当前要展示的视图view从top为0的地方开始绘制。

至此状态栏已经实现透明,且当前的Activity视图已经可以延伸到状态栏下方
效果如下:


该效果实现之后有如下问题:此时标题会随整个试图的延伸一起延伸到状态栏的下方,致使状态栏遮挡了部分自定义的标题
解决该问题,也很简单android原生已经提供了相关实现:
要想实现标题栏不被遮挡,可以在在根节点的布局文件中添加fitsSystemWindows clipToPadding的标志位即可。代码如下:
  1. <RelativeLayout
  2. android:fitsSystemWindows="true"
  3. android:clipToPadding="false"
  4. android:layout_width="match_parent"
  5. android:layout_height="match_parent"></RelativeLayout>

fitsSystemWindows:该属性值表示系统布局过程中是否会为该view自动适应系统窗口(状态栏/导航栏),为true时系统会在设置了该属性的view的布局中,自动为其添加状态栏的高度的paddingtop值,使其实际布内容不被状态栏遮挡;为false时,则不会为其添加该padding值。默认不设的值为false。

clipToPadding:该属性值为在绘制的时候是否可以在其padding区域内绘制。当为true时可以在其padding区域内绘制,否则不可绘制。
》》但此时效果如下:虽然视图沉了一个状态栏的高度,但是同时状态栏的颜色是透明的,显示出了原有的底色。

此时,需要解决的是让状态栏下的颜色为自定义的,在版本为Lollipop及之后s状态栏可以设置背景色,但是KITKAT版本并没有此方法,这也是KITKAT只是设置状态栏背景为透明是相关的。所以KITKAT版本相比Lollipop及更高版本的沉浸实现是有一定差别。对此常用的解决方法就是
不再添加fitSystemwindows属性,而是手动添加一个占位布局,设置其高度为statusbar的高度,背景为设置为自己想要的颜色即可。

进一步,如果要实现底层背景是整张图片,并以该图片为背景色,需要其整个延伸至状态栏下方,则可做如下布局,同样在自定义的titlebar的布局上添加占位控件。设置其高度,同时设置其颜色为透明即可。
  1. <RelativeLayout
  2. android:layout_width="match_parent"
  3. android:layout_height="match_parent">
  4. <ImageView
  5. android:id="@+id/textdemo_image"
  6. android:src="@drawable/bg3"
  7. android:layout_width="match_parent"
  8. android:layout_height="match_parent" />
  9. <LinearLayout
  10. android:layout_width="match_parent"
  11. android:layout_height="match_parent"
  12. android:orientation="vertical">
  13.                        
  14.                          <!-- textdemo_titleholderview 此控件为占位控件设置其高度为状态栏的高度-->
  15.     <View
  16.     android:id="@+id/textdemo_titleholderview"
  17.     android:layout_width="match_parent"
  18.     android:layout_height="0dp" />
  19.                          <!-- titlebar_view此控件为自定义的标题栏titlebar-->
  20.     <include layout="@layout/titlebar_view"></include>
  21. <View
  22. android:layout_width="match_parent"
  23. android:layout_height="0dp"
  24. android:layout_weight="1" />
  25. <LinearLayout
  26. android:layout_width="match_parent"
  27. android:layout_height="wrap_content"
  28. android:orientation="vertical">
  29. <include layout="@layout/line_mode_view"></include>
  30. <include layout="@layout/line_mode_view"></include>
  31. </LinearLayout>
  32. </LinearLayout>
  33. </RelativeLayout>
占位控件代码实现如下:
  1. ViewGroup.LayoutParams layoutParams = titlebarHolder.getLayoutParams();//titlebarHolder为添加的顶部的(标题栏上方)占位控件
  2. layoutParams.height = getStatueBarHeight();
  3. titlebarHolder.setLayoutParams(layoutParams);
  4. titlebarHolder.setBackgroundColor(Color.TRANSPARENT);//此处也可设置自定义的颜色,设置为透明则会直接看到底层的图片
》》》背景为整张图片的最终效果:

 
也可参考此篇博客:android状态栏总结。

========至此已经完整做到了沉浸式页面的简单实现=========

二.下面将要实现沉浸效果+折叠TitleBar样式单页面的实现

效果如下:

布局文件如下:需要添加

 xmlns:app="http://schemas.android.com/apk/res-auto"//以便引用其他包下定义的属性值, e.g. app:layout_scrollFlags="
  1. <?xml version="1.0" encoding="utf-8"?>
  2. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  3. xmlns:app="http://schemas.android.com/apk/res-auto"
  4. android:id="@+id/activity_main"
  5. android:layout_width="match_parent"
  6. android:layout_height="match_parent">
  7. <!--CoordinatorLayout:协调布局 该实例中可以协调子View的滑动事件效果,该实例中有两个子View
  8. 1.AppBarLayout:用来包括CollapsingToolbarLayout(折叠布局),主要使用该AppBarLayout,已有的android.support.design.widget.AppBarLayout$ScrollingViewBehavior
  9. 2.RecyclerView:该View通过设置app:layout_behavior="@string/appbar_scrolling_view_behavior"属性,直接与AppBarLayout,相关联。
  10. ps:因为CoordinatorLayout处理的是联动效果,一般需要一个可滑动操作的空间,一般可用的有(RecyclerView4包中的NestedScrollView),但是(ListView)不可用
  11. pps:整体联动实现是,先通过layout_behavior实现该RecyclerViewAppBarLayout的联动。然后是CollapsingToolbarLayout内部的效果,通过设置其app:layout_scrollFlags
  12. android:minHeight以及子viewapp:layout_collapseMode="pin"实现。
  13. -->
  14. <android.support.design.widget.CoordinatorLayout
  15. android:layout_width="match_parent"
  16. android:layout_height="match_parent">
  17. <android.support.design.widget.AppBarLayout
  18. android:layout_width="match_parent"
  19. android:layout_height="match_parent">
  20. <!-- CollapsingToolbarLayout:折叠布局视图继承自Framlayout,所有子view默认从左上角开始
  21. 该实例中定义了两个子View,其中的
  22. 1.RelativeLayout:充当整个CollapsingToolbarLayout的可滑动布局,跟随滑动事件一起滑动
  23. 2.LinearLayout:充当titlebar使用,设置属性app:layout_collapseMode="pin"使其位置固定不动
  24. ps:可以使用ToolBar代替上面的的LinearLayout,但是此时CollapsingToolbarLayoutminHeight,无效会自动匹配该Toolbar的高度,
  25. 且该Toolbar不太好定义样式。
  26. -->
  27. <android.support.design.widget.CollapsingToolbarLayout
  28. android:id="@+id/collapsingToolbarLayout"
  29. android:layout_width="match_parent"
  30. android:layout_height="match_parent"
  31. android:minHeight="200dp"
  32. app:layout_scrollFlags="scroll|snap|exitUntilCollapsed">
  33. <!--该RelativeLayout定义了整体的可折叠CollapsingToolbarLayout的底层布局
  34. 有整体的背景图片以ImageView引入,设置其android:scaleType="centerCrop"防止背景变形
  35. 其他为所需要的自定义的布局
  36. -->
  37. <RelativeLayout
  38. android:layout_width="match_parent"
  39. android:layout_height="match_parent">
  40. <!--整体的折叠视图背景图片-->
  41. <ImageView
  42. android:id="@+id/textdemo_image"
  43. android:layout_width="match_parent"
  44. android:layout_height="match_parent"
  45. android:scaleType="centerCrop"
  46. android:src="@drawable/bg3" />
  47. <!-- 自己所需的要跟随滚动折叠的布局试图-->
  48. <LinearLayout
  49. android:layout_width="match_parent"
  50. android:layout_height="match_parent"
  51. android:orientation="vertical">
  52. <View
  53. android:layout_width="match_parent"
  54. android:layout_height="0dp"
  55. android:layout_weight="1" />
  56. <LinearLayout
  57. android:layout_width="match_parent"
  58. android:layout_height="wrap_content"
  59. android:orientation="vertical">
  60. <!--简单的文字布局-->
  61. <include layout="@layout/line_mode_view"></include>
  62. <include layout="@layout/line_mode_view"></include>
  63. </LinearLayout>
  64. </LinearLayout>
  65. </RelativeLayout>
  66. <!--充当titleBar的布局-->
  67. <LinearLayout
  68. android:layout_width="match_parent"
  69. android:layout_height="wrap_content"
  70. android:orientation="vertical"
  71. app:layout_collapseMode="pin">
  72. <!--占位控件,用来使下面的布局偏移到状态栏之下,具体高度代码设置-->
  73. <View
  74. android:id="@+id/textdemo_titleholderview"
  75. android:layout_width="match_parent"
  76. android:layout_height="0dp" />
  77. <!--实际的TitleBar布局文件-->
  78. <include layout="@layout/titlebar_view"></include>
  79. </LinearLayout>
  80. </android.support.design.widget.CollapsingToolbarLayout>
  81. </android.support.design.widget.AppBarLayout>
  82. <android.support.v7.widget.RecyclerView
  83. android:id="@+id/textdemo_recyclerview"
  84. android:layout_width="match_parent"
  85. android:layout_height="match_parent"
  86. app:layout_behavior="@string/appbar_scrolling_view_behavior">
  87. </android.support.v7.widget.RecyclerView>
  88. </android.support.design.widget.CoordinatorLayout>
  89. </LinearLayout>
布局层级关系如下:


对应该Acticity的代码如下:
  1. public class MainActivity extends AppCompatActivity {
  2. private RecyclerView recyclerview;
  3. private LinearLayout titlebar;
  4. private View titlebarHolder;
  5. private ImageView textdemo_image;
  6. private CollapsingToolbarLayout collapsingToolbarLayout;
  7. @Override
  8. protected void onCreate(Bundle savedInstanceState) {
  9. super.onCreate(savedInstanceState);
  10. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
  11. this.getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);//沉浸式状态栏实现的前提
  12. this.getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);//可不加
  13. } else {
  14. }
  15. setContentView(R.layout.activity_main);
  16. initView();
  17. initEvent();
  18. }
  19. private void initView() {
  20. recyclerview = (RecyclerView) findViewById(R.id.textdemo_recyclerview);
  21. titlebar = (LinearLayout) findViewById(R.id.textdemo_titlebar);
  22. titlebarHolder = findViewById(R.id.textdemo_titleholderview);
  23. textdemo_image = (ImageView) findViewById(R.id.textdemo_image);
  24. collapsingToolbarLayout = (CollapsingToolbarLayout) findViewById(R.id.collapsingToolbarLayout);
  25. }
  26. private void initEvent() {
  27. LinearLayoutManager manager = new LinearLayoutManager(getApplicationContext());
  28. textdemo_image.setScaleType(ImageView.ScaleType.CENTER_CROP);
  29. ViewGroup.LayoutParams layoutParams = titlebarHolder.getLayoutParams();
  30. layoutParams.height = getStatueBarHeight();
  31. titlebarHolder.setLayoutParams(layoutParams);
  32. RecyclerView.Adapter adapter = new MyAdapter();
  33. recyclerview.setLayoutManager(manager);
  34. recyclerview.setAdapter(adapter);
  35.         //fitSystemWindow();
  36. }
  37. //private void fitSystemWindow() { //bug相关解决代码
  38. // ViewCompat.setOnApplyWindowInsetsListener(collapsingToolbarLayout, new OnApplyWindowInsetsListener() {
  39. // @Override
  40. // public WindowInsetsCompat onApplyWindowInsets(View v, WindowInsetsCompat insets) {
  41. // insets.replaceSystemWindowInsets(0, 0, 0, 0);
  42.     // return insets.consumeSystemWindowInsets();
  43. // return insets;
  44. // }
  45. // });
  46. //}
  47. private int getStatueBarHeight() {//拿取状态栏的高度
  48. int identifier = getResources().getIdentifier("status_bar_height", "dimen", "android");
  49. if (identifier > 0) {
  50. return (int) getResources().getDimension(identifier);
  51. }
  52. return 0;
  53. }
  54. class MyAdapter extends RecyclerView.Adapter {
  55. @Override
  56. public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
  57. return new MyViewholder(new TextView(getApplicationContext()));
  58. }
  59. @Override
  60. public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
  61. ((MyViewholder) holder).updateData("我是模拟条目文本:::::" + position);
  62. }
  63. @Override
  64. public int getItemCount() {
  65. return 50;
  66. }
  67. }
  68. class MyViewholder extends RecyclerView.ViewHolder {
  69. public MyViewholder(View itemView) {
  70. super(itemView);
  71. }
  72. public void updateData(String str) {
  73. ((TextView) itemView).setText(str);
  74. }
  75. }
  76. }

首先要用到的控件有:1.CoordinatorLayout 2.AppBarLayout  3.CollapsingToolbarLayout 4.可滑动的控件RecyclerView
以下是这几个控件的简单介绍,可对比上方布局中的代码查看:

1.CoordinatorLayout:Design包下的协调布局,简单说就是一个可以协调其子view之间动作(一般是滑动操作)的一个总的Layout,继承自GroupLayout,可以通过定义可滑动子view的行为layout_behavior来实现与其他相关子view的行为互动。

2.AppBarLayout,Design包下的继承自LinearLayout,使用该布局是因为CoordinatorLayout子view的layout_behavior中有已经预定义的与AppBarLayout协调互动的behavior:android.support.design.widget.AppBarLayout$ScrollingViewBehavior,该联动效果已能满足基本需求(该layout_behavior可以自定义)。

3.CollapsingToolbarLayout ,可折叠的toolbar。就是可以拉伸和折叠的toolbar布局。该布局中需要声明的属性有:
  • == 》滑动时候的行为:app:layout_collapseMode="pin",设置相关view的行为模式(也可设置CollapsingToolbarLayout 的子view
  •     pin-当滑动时,在CollapsingToolbarLayout 控件可见时,其一直会位于布局位置,不随其他view一块滑动(一般给子View中充当toolbar的布局添加该属性)
  •     parallax -设置为这个模式时,在滑动中CollapsingToolbarLayout中的该View(比如ImageView)也可以同时滚动,实现视差滚动效果,通常和layout_collapseParallaxMultiplier(设置视差因子)搭配使用。

  • ==》视差因子:layout_collapseParallaxMultiplier:会在滚动中与相关联动的view一起滚动,但有视差效果,取值从0-1表示,滑动结束时,联动控件之间相互重叠的比例

  • ==》对什么动作响应,什么时候响应:该属性有标志位决定:app:layout_scrollFlags="scroll|exitUntilCollapsed" 
  • scroll - 想滚动就必须设置这个(也可以说是设置其对滚动事件)
  • enterAlways - 实现quick return效果, 当向下移动时,立即显示该View(即初始时该View为全部折叠位于屏幕之外,当下拉动作时该控件会首先做出反应,直接显示出该view)
  • enterAlwaysCollapsed - 当你的View已经设置minHeight属性又使用此标志时,你的View只能以最小高度进入,只有当滚动视图到达顶部时才扩大到完整高度。(此属性值与上面属性配合使用)
    exitUntilCollapsed - 向上滚动时收缩View,但可以固定Toolbar一直在上面。

  • ==》折叠前的控件高度大小:就是控件的layout_height属性值。

  • ==》折叠后的最小高度:android:minHeight e.g. android:minHeight="200dp";****注意:该属性值只有在其子view中没有使用Toolbar控件时才有用。当子view中使用了android.support.v7.widget.Toolbar则会以该Toolbar的高度为准。

  • ==》在折叠的时候 状态栏的背景颜色android.support.design:statusBarScrim  e.g. app:statusBarScrim="#123456" The drawable to use as a scrim for the status bar content when the CollapsingToolbarLayout has been scrolled sufficiently off screen. )

  • ==》折叠后该layout的背景色:android.support.design:contentScrim  e.g.  app:contentScrim="#ff5252",The drawable to use as a scrim on top of the CollapsingToolbarLayouts content when it has been scrolled sufficiently off screen. 


4.该该可滑动控件可以是RecyclerView或者v4包中的NestedScrollView,但是对于Listview无效。

具体更为详细的这几个控件的介绍可参考:http://blog.csdn.net/lxk_1993/article/details/51443045


下面是踩坑时间可跳过(design包版本为:com.android.suport:design:23.4.0时会出现以下状况):

此时加上上面介绍的沉浸式页面的实现按说已经可以实现所需效果,但是发现

1.在KITKAT版本手机上运行时效果已经正常,

2.但在Lollipop版本的系统上运行时会出现如下效果:review代码可知此时关于沉浸式状态栏的设置,只有在Activity中添加的Flag(this.getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);


鉴于此参考大部分的做法,在CollapsingToolbarLayout直接子View添加属性fitsSystemWindows = true;(回顾此属性的意义为系统自动适配状态栏高度到控件,给设置此属性的View添加paddingTop值,以适应系统窗口 比如:状态栏/导航栏),所以按说不应该添加此属性值才对。


但实际效果为:

1.Lollipop版本的系统运行已经达到预期的效果。

2.但是,KITKAT版本的系统运行效果却又出现了和上面类似效果如下(有部分差别,此时KITKAT版本只有CollapsingToolbarLayout下的第一个设置了fitsSystemWindows = true的View会下移一个状栏的高度,其他的不会下移,在此表现即为CollapsingToolbarLayout子View中的第一个RelativeLayout会下移,但是作为TitleBar使用的第二个子View却不会下移


 解决方法可有:
1.代码中判断版本并设置CollapsingToolbarLayout直接子View的fitsSystemWindows属性值代码如下:
  1. for (int i = 0; i < collapsingToolbarLayout.getChildCount(); i++) {//拿取CollapsingToolbarLayout的子View并根据版本设置其fitsSystemWindows的属性
  2. if (Build.VERSION.SDK_INT > Build.VERSION_CODES.KITKAT) {
  3. collapsingToolbarLayout.getChildAt(i).setFitsSystemWindows(true);
  4. } else if (Build.VERSION.SDK_INT == Build.VERSION_CODES.KITKAT) {
  5. collapsingToolbarLayout.getChildAt(i).setFitsSystemWindows(false);
  6. } else {//不支持沉浸式状态栏的版本,要把使View向下偏移的占位控件的高度设置为0
  7. //设置充当TitleBar使用的控件里面的占位空间的高度为0,
  8. }
  9. }
2.直接在布局文件中把CollapsingToolbarLayout 的所有子View的fitsSystemWindows属性设置为true,此时Lollipop版本效果已经OK,但由于在KITKAT版本系统中CollapsingToolbarLayout的第一个设置为fitsSystemWindows为true的View会被下移一个状态栏的高度,因此可以简单的再在该 CollapsingToolbarLayout下添加一个高度为0的View,把其   fitsSystemWindows属性也设置为true即可。此操作可以用代码,也可直接写入布局文件中。

现在问题解决让我们稍微深究下:在同时使用沉浸式页面和CollapsingToolbarLayout时为什么会出现View下移一个状态栏的高度
现象为:当页面设置为沉浸式的时候,CollapsingToolbarLayout的子View正常的不设置fitsSystemWIndows的属性时,即其默认为false,正常不会下移以适应状态栏的高度时,在Lollipop版本为什么依旧会往下偏移,而KITKAT版本就是正常的:注意,此处使用的design包版本为:com.android.suport:design:23.4.0
直接上CollapsingToolbarLayout 的源码,先直接找其onLayout方法,其中一段代码如下:

  1. // Update our child view offset helpers
  2. for (int i = 0, z = getChildCount(); i < z; i++) {
  3. final View child = getChildAt(i);
  4. if (mLastInsets != null && !ViewCompat.getFitsSystemWindows(child)) {
  5. final int insetTop = mLastInsets.getSystemWindowInsetTop();
  6. if (child.getTop() < insetTop) {
  7. // If the child isn't set to fit system windows but is drawing within the inset
  8. // offset it down
  9. ViewCompat.offsetTopAndBottom(child, insetTop);
  10. }
  11. }
  12. getViewOffsetHelper(child).onViewLayout();
  13. }
从此段代码可知CollapsingToolbarLayout 在的layout过程中,会遍历子View,并当在mLastInset不为null,且其fitsSystemWIndows属性为false时会拿到mLastInset中的SysTemWindowsInsetTop值(即复制的状态栏的高度),当该top值比子View设置的paddingtop值大(即状态栏会遮挡到子view的布局内容时)会自动为其添加一个状态栏高度的padding值,而这也正是上面当不设置fitsSystemWIndows属性时会向下偏移一个状态栏的高度。而在设置了之后反而不会下移。
但现在的问题是为什么KITKAT的就不会出现此问题:我们接下来看,这个mLastInsets是什么?代码中可以看到为WindowInsetsCompat类型的对象,再看其是在何处初始化的:
  1. private WindowInsetsCompat setWindowInsets(WindowInsetsCompat insets) {
  2. if (mLastInsets != insets) {
  3. mLastInsets = insets;
  4. requestLayout();
  5. }
  6. return insets.consumeSystemWindowInsets();
  7. }
而该setWindowInsets()方法的调用是在CollapsingToolbarLayout 的构造函数中源码如下:
  1. ViewCompat.setOnApplyWindowInsetsListener(this,
  2. new android.support.v4.view.OnApplyWindowInsetsListener() {
  3. @Override
  4. public WindowInsetsCompat onApplyWindowInsets(View v,
  5. WindowInsetsCompat insets) {
  6. return setWindowInsets(insets);
  7. }
  8. });
至此可知其初始化是在一个OnApplyWindowInsetsListener()的回调中进行的,而设置此监听的在ViewCompat中的相关代码如下,
  1. /**
  2.     *方法的相关声明
  3. * Set an {@link OnApplyWindowInsetsListener} to take over the policy for applying
  4. * window insets to this view. This will only take effect on devices with API 21 or above.
  5. */
  6. public static void setOnApplyWindowInsetsListener(View v,
  7. OnApplyWindowInsetsListener listener) {
  8. IMPL.setOnApplyWindowInsetsListener(v, listener);
  9. }
继续看IMPL的初始化:其是在ViewCompat的静态代码块中初始化的:
  1. static final ViewCompatImpl IMPL;
  2. static {
  3. final int version = android.os.Build.VERSION.SDK_INT;
  4. if (BuildCompat.isAtLeastN()) {
  5. IMPL = new Api24ViewCompatImpl();
  6. } else if (version >= 23) {
  7. IMPL = new MarshmallowViewCompatImpl();
  8. } else if (version >= 21) {
  9. IMPL = new LollipopViewCompatImpl();
  10. } else if (version >= 19) {
  11. IMPL = new KitKatViewCompatImpl();
  12. } else if (version >= 18) {
  13. IMPL = new JbMr2ViewCompatImpl();
  14. } else if (version >= 17) {
  15. IMPL = new JbMr1ViewCompatImpl();
  16. } else if (version >= 16) {
  17. IMPL = new JBViewCompatImpl();
  18. } else if (version >= 15) {
  19. IMPL = new ICSMr1ViewCompatImpl();
  20. } else if (version >= 14) {
  21. IMPL = new ICSViewCompatImpl();
  22. } else if (version >= 11) {
  23. IMPL = new HCViewCompatImpl();
  24. } else {
  25. IMPL = new BaseViewCompatImpl();
  26. }
  27. }
至此此已可以看到官方的声明该方法只有在API为21及其以上才能使用(即只有在Lollipop版本及其之上才会设置该listener),同样CollapsingToolbarLayout 中的在onLayout()方法中用到的WindowInsetsCompat对象mLastInset只有在Lollipop版本及其之上才会被初始化,因此回到CollapsingToolbarLayout 的onLayout的那段代码:
  1. // Update our child view offset helpers
  2. for (int i = 0, z = getChildCount(); i < z; i++) {
  3. final View child = getChildAt(i);
  4. if (mLastInsets != null && !ViewCompat.getFitsSystemWindows(child)) {
  5. final int insetTop = mLastInsets.getSystemWindowInsetTop();
  6. if (child.getTop() < insetTop) {
  7. // If the child isn't set to fit system windows but is drawing within the inset
  8. // offset it down
  9. ViewCompat.offsetTopAndBottom(child, insetTop);
  10. }
  11. }
  12. getViewOffsetHelper(child).onViewLayout();
  13. }
以上总结及另一种解决方法为:
1.在CollapsingToolbarLayout+沉浸式状态栏的实现中,当不设置fitsSystemWindows为true时,在版本为KITKAT,mLastInsets为null,根本不会进入到下面的逻辑,其子View也就不会向下偏移。而对于LOLLIPOP及其以上则其子View会向下偏移。
而上面的解决方法中的设置CollapsingToolbarLayout的子View的fitsSystemWindows为true的方案只是在该处的if判断中加了拦截,从而达到预期效果。
基于此,也可以使mLastInsets为null来解决问题,可在collapsingToolbarLayout初始化之后调用以下代码:
  1. ViewCompat.setOnApplyWindowInsetsListener(collapsingToolbarLayout, new OnApplyWindowInsetsListener() {
  2. @Override
  3. public WindowInsetsCompat onApplyWindowInsets(View v, WindowInsetsCompat insets) {
  4. //insets.replaceSystemWindowInsets(0, 0, 0, 0); 该行代码无效
  5.                 // return insets.consumeSystemWindowInsets();
  6. return insets;
  7. }
  8. });
对比CollapsingToolbarLayout的构造函数里面的该方法可知此时在回调过程中不会触发mLastInsets的初始化操作,所以mLastInsets也不会被初始化。同样可以实现所需效果

2.CollapsingToolbarLayout+沉浸式状态栏的实现中,当设置fitsSystemWindows为true时,为什么KITKAT版本的只会使第一个子View下移一个状态栏的高度,倒没有找到源码在那个地方。
至此则已经可以实现支持沉浸式的版本KITKAT和LOLLIPOP及以上版本的在CollapsingToolbarLayout实现上的兼容。

三:将以上单页面的实现放入Fragment中,再放入ViewPager

在解决掉第二歨的兼容问题问题之后,在ViewPager的Fragment中使用该组合时也不会出现个别fragment中会有view向下偏移状态栏高度的问题,即一开始想要得到的效果。


PS:
1.以上的解决方法中都只是使onLayout()中view向下偏移的代码不执行来实现的。但是最终原因应该是在沉浸式的页面中WindowInsetsCompat对象中的mLastInsets.getSystemWindowInsetTop()拿到的值是状态栏的高度。但是如果能改变该对象内的值应该也是可行的,至少在镶套进ViewPager的Fragment之后会出现第二个页面此处拿到的值为0。但是并没有找到原因。如果那位同学知道还望留言告知。
2.以上当使用的在design包版本为:com.android.suport:design:23.4.0会出现,但是在使用design包的版本为25.0.0时,不需要设置CollapsingToolbarLayout的fitsSystemWindows属性,在KITKAT和LOLLIPOP的系统中都可以得到想要的效果。代码中可以看到在25.0.0版本中的CollapsingToolbarLayout代码中,
onLayout()方法中用到的WindowInsetsCompat对象mLastInset并不是在构造函数中设置的listener中初始化的,而是在另外一个函数中进行的初始化,具体可以查看源码

最终的实例代码在以下地址:https://github.com/M075097/ImmersionStyleDemo













 
0 0
原创粉丝点击