前言

大家在做APP开发的过程中,有很多时候,我们需要实现类似于下面这种沉浸式的体验。

\"\"
沉浸式体验

一开始接触的时候,似乎大家都会觉这种体验实现起来,会比较困难。难点在于:

  • 头部的背景图在推上去的过程中,慢慢的变得不可见了,整个区域的颜色变成的暗黑色,然后标题出现了。
  • StatusBar变的透明,且空间可以被利用起来,看我们的图片就顶到了顶 了。
  • 我们的viewpager推到actionbar的下方的时候,就固定在了actionbar的下方,不能在往上面推了。
  • 底部有一个控件,随着列表的向上滑动,它退出视角范围,以便于给出更多的空间来展示列表,其实整个沉浸式体验都是为了给列表留出更多的空间来展示。

好,总结起来以上就是我们的问题,也是需要解决的,一个一个解决了,这种需求也就实现了,那么,我们如何去一步一步来解决以上的问题呢?

1、头部背景和标题的渐隐渐现

首先,我们来分析第一个问题,头部的背景图在推上去的过程中,慢慢的变得不可见了,这种听起来好像是某种collapse,因此,很容易让人想到CollapsingToolbarLayout,如果你想要比较容易的了解CollapsingToolbarLayout

应用,建议看这位兄台的文章,他给也给了一个动画,比较详细的介绍了这个的应用,例如:

\"\"
CollapsingToolbarLayout

对于里面的用法,我这里不作讲解了,但是如果你不了解这个布局的应用,我强烈建议你好好了解一下,才能继续下面走,只是想说明一下,走到这里,你有一个坑需要去填,那就是我们的标题动画可以不是这样的,而且,还是标题还是居中的,注意,这里的实现,标题不是居中的,是靠左的,这本来是Android设计规范,但是设计师偏偏不买Android规范的账,因此,我们必须躺过这个坑,然后,从Stack Overflow上了解到一个issue

<android.support.v7.widget.Toolbar
 android:id=\"@+id/toolbar_top\"
 android:layout_height=\"wrap_content\"
 android:layout_width=\"match_parent\"
 android:minHeight=\"?attr/actionBarSize\"
 android:background=\"@color/action_bar_bkgnd\"
 app:theme=\"@style/ToolBarTheme\" >


 <TextView
 android:layout_width=\"wrap_content\"
 android:layout_height=\"wrap_content\"
 android:text=\"Toolbar  \"
 android:layout_gravity=\"center\"
 android:id=\"@+id/toolbar_ \" />


</android.support.v7.widget.Toolbar>

假设,这个方式是可行的,那么要解决居中的问题后,把返回按钮改为我们的按钮样式,然后,在耍点小诡计,让 开始是透明的,并且改变返回按钮的图片

collapsingToolbarLayout.setCollapsed TextColor(Color.WHITE);
//collapsingToolbarLayout.setExpanded Color(Color.WHITE);
collapsingToolbarLayout.setExpanded Color(Color.TRANSPARENT);

然而,假设,始终只是一个假设,实际上,这个假设不成立,我在尝试的时候,发现Toolbar中的TextView根本就不能使用android:layout_gravity=\"center\"这种属性好吧,即使强行加上,效果也是靠左的。

那么,如何做,我的解决方式是这样的

<android.support.design.widget.AppBarLayout
 android:id=\"@+id/appbarlayout\"
 android:layout_width=\"match_parent\"
 android:layout_height=\"wrap_content\"
 app:elevation=\"0dp\">

 <android.support.design.widget.CollapsingToolbarLayout
 android:id=\"@+id/collapsing_tool_bar\"
 android:layout_width=\"match_parent\"
 android:layout_height=\"wrap_content\"
 app:contentScrim=\"@color/b_G6\"
 app:expanded MarginEnd=\"10dp\"
 app:expanded MarginStart=\"10dp\"
 app:layout_scrollFlags=\"scroll|exitUntilCollapsed|snap\">

 <android.support.constraint.ConstraintLayout
  android:layout_width=\"match_parent\"
  android:layout_height=\"match_parent\">

  <ImageView
  android:id=\"@+id/igame_arena_rank_class_header_bg\"
  android:layout_width=\"match_parent\"
  android:layout_height=\"0dp\"
  android:scaleType=\"centerCrop\"
  android:src=\"@drawable/bg_arena_rank_class\"
  app:layout_constraintDimensionRatio=\"375:156\" />
  .........

 </android.support.constraint.ConstraintLayout>

 <android.support.v7.widget.Toolbar
  android:id=\"@+id/common_index_activity_tb_ \"
  android:layout_width=\"match_parent\"
  android:layout_height=\"wrap_content\"
  android:minHeight=\"?android:attr/actionBarSize\"
  android:visibility=\"visible\"
  app:contentInsetLeft=\"0dp\"
  app:contentInsetStart=\"0dp\"
  app:layout_collapseMode=\"pin\">

  <include
  layout=\"@layout/igame_common_tool_bar\"
  android:layout_width=\"match_parent\"
  android:layout_height=\"wrap_content\"
  android:layout_gravity=\"center\" />
 </android.support.v7.widget.Toolbar>


 </android.support.design.widget.CollapsingToolbarLayout>

 </android.support.design.widget.AppBarLayout>

然后,include里面的布局是这样的

<?  version=\"1.0\" encoding=\"utf-8\"?>
<LinearLayout  ns:android=\"http://schemas.android.com/apk/res/android\"
  ns:tools=\"http://schemas.android.com/tools\"
 android:layout_width=\"match_parent\"
 android:layout_height=\"wrap_content\"
 android:orientation=\"vertical\">

//*****请注意这个View*******///
 <View
 android:id=\"@+id/common_index_activity_view_status_bar\"
 android:layout_width=\"match_parent\"
 android:layout_height=\"0dp\" />

 <RelativeLayout
 android:layout_width=\"match_parent\"
 android:layout_height=\"50dp\">

 <TextView
 android:id=\"@+id/tv_toolbar_bg\"
 android:layout_width=\"match_parent\"
 android:layout_height=\"50dp\"
 android:layout_centerInParent=\"true\"
 tools:background=\"@color/b_G6\" />

 <TextView
 android:id=\"@+id/common_index_header_tv_ \"
 android:layout_width=\"wrap_content\"
 android:layout_height=\"wrap_content\"
 android:layout_centerInParent=\"true\"
 android:gravity=\"center\"
 android:textColor=\"@color/b_G99\"
 android:textSize=\"@dimen/igame_textsize_xl\"
 tools:text=\"这里是标题\" />


 <RelativeLayout
 android:id=\"@+id/common_index_header_rl_back\"
 android:layout_width=\"48dp\"
 android:layout_height=\"48dp\"
 android:layout_centerVertical=\"true\"
 android:layout_gravity=\"center_vertical\"
 android:visibility=\"visible\">

 <ImageView
 android:layout_width=\"match_parent\"
 android:layout_height=\"match_parent\"
 android:layout_centerInParent=\"true\"
 android:contentDe ion=\"@string/image_desc\"
 android:scaleType=\"centerInside\"
 android:src=\"@drawable/igame_actionbar_arrow_left\" />
 </RelativeLayout>

 </RelativeLayout>
</LinearLayout>

\"\"

效果就是这样

当然,这时候,标题是需要你自己设置渐隐渐现的。那么,我们依据什么呢?

appBarLayout.addOnOffsetChangedListener(new AppBarLayout.OnOffsetChangedListener() {
 @Override
 public void onOffsetChanged(AppBarLayout appBarLayout, int verticalOffset) {
 m .setAlpha(-verticalOffset * 1.0f / appBarLayout.getTotalScrollRange());
 }
 });

依据的就是对appBarLayout的监听。

2、将statusBar变为透明,且利用他的空间来放我们的布局内容。

 /**
 * 使状态栏透明,并覆盖状态栏,对API大于19的显示正常,但小于的界面扩充到状态栏,但状态栏不为透明
 */
 @TargetApi(Build.VERSION_CODES.KITKAT)
 public static void transparentAndCoverStatusBar(Activity activity) {
 //FLAG_LAYOUT_NO_LIMITS这个千万别用,带虚拟按键的机型会有特别多问题

// //FLAG_TRANSLUCENT_STATUS要求API大于19
// activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
// activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN);
// //FLAG_LAYOUT_NO_LIMITS对API没有要求
// activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS);

 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
 Window window = activity.getWindow();
 window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
 window.getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
  | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
 window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
 window.setStatusBarColor(Color.TRANSPARENT);
 window.setNavigationBarColor(Resources.getSystem().getColor(android.R.color.background_dark));
 } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
 Window window = activity.getWindow();
 window.setFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS,
  WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
 }
 }

这里是在网上找的一个方法,直接调用即可,但是API需要大于19,相信目前基本上都满足吧。请注意,我的AppBarLayout中并没有这个属性

android:fitsSystemWindows=\"true\"

如果你加了这个属性,嘿嘿,statusbar虽然空间可以利用,但是有一个你挥之不去的颜色覆盖在上面,

然后,你还记得上面那个布局中

//*****请注意这个View*******///
 <View
 android:id=\"@+id/common_index_activity_view_status_bar\"
 android:layout_width=\"match_parent\"
 android:layout_height=\"0dp\" />

这个作用可大了,就是为了对status_bar原始空间做偏移的,在代码中,需要动态的改变这个View的高度为statusBar的高度,怎么获取:

/**
 * 获取状态栏高度
 *
 * @param context context
 * @return 状态栏高度
 */
 public static int getStatusBarHeight(Context context) {
 // 获得状态栏高度
 int resourceId = context.getResources().getIdentifier(\"status_bar_height\", \"dimen\", \"android\");
 return context.getResources().getDimensionPixelSize(resourceId);
 }

完了之后,还需要设置我们自己塞进去的那个toolbar的高度为toolbar的高度加上StatusBar的高度。

3、ViewPager推到actionbar下面就不让在推了

这个其实需要你CollapsingToolbarLayout里面有一个子view是要使用pin模式的,那么这个子view是谁,显然就是那个toolbar了

<android.support.v7.widget.Toolbar
   android:id=\"@+id/common_index_activity_tb_ \"
   android:layout_width=\"match_parent\"
   android:layout_height=\"wrap_content\"
   android:minHeight=\"?android:attr/actionBarSize\"
   android:visibility=\"visible\"
   app:contentInsetLeft=\"0dp\"
   app:contentInsetStart=\"0dp\"
   app:layout_collapseMode=\"pin\">

   <include
   layout=\"@layout/igame_common_tool_bar\"
   android:layout_width=\"match_parent\"
   android:layout_height=\"wrap_content\"
   android:layout_gravity=\"center\" />
  </android.support.v7.widget.Toolbar>

4、底部控件随着列表的滑动渐渐隐藏

可以看到,底部的控件是覆盖在列表上的,列表向上滑动的时候,把他隐藏,就可以空出更多的控件看列表。那么,如何做呢?

既然,我们是包裹在CoordinatorLayout中,那么,显然,最好的方式是使用layout_behavior了,我这里实现了一个BottomBehavior:

public class BottomBehavior extends CoordinatorLayout.Behavior {
 private int id;
 private float bottomPadding;
 private int screenWidth;
 private float designWidth = 375.0f;//设计视图的宽度,通常是375dp,

 public BottomBehavior() {
 super();
 }

 public BottomBehavior(Context context, AttributeSet attrs) {
 super(context, attrs);
 screenWidth = getScreenWidth(context);
 TypedArray typedArray = context.getResources().obtainAttributes(attrs, R.styleable.BottomBehavior);
 id = typedArray.getResourceId(R.styleable.BottomBehavior_anchor_id, -1);
 bottomPadding = typedArray.getFloat(R.styleable.BottomBehavior_bottom_padding, 0f);
 typedArray.recycle();
 }

 @Override
 public void onAttachedToLayoutParams(@NonNull CoordinatorLayout.LayoutParams params) {
 params.dodgeInsetEdges = Gravity.BOTTOM;
 }

 @Override
 public boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency) {
 return dependency.getId() == id;
 }

 @Override
 public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) {
 child.setTranslationY(-(dependency.getTop() - (screenWidth * bottomPadding / designWidth)));
 Log.e(\"BottomBehavior\", \"layoutDependsOn() called with: parent = [\" + dependency.getTop());
 return true;
 }


 public static int getScreenWidth(Context context) {
 WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
 Display display = null;
 if (wm != null) {
  display = wm.getDefaultDisplay();
  Point size = new Point();
  display.getSize(size);
  int width = size.x;
//  int height = size.y;
  return width;
 }
 return 0;
 }
}

这个里面有两个自定义属性,id,bottomPadding,id表示基于哪个控件的相对位置改变,我这打算基于viewpager

这个控件,看源码可以知道,只有当onDependentViewChanged返回ture时,layoutDependsOn才会被回调。bottomPadding是表示一个初始的偏移,因为viewpager本身不是顶在屏幕顶端的(开始被图片占据了一部分控件),因此,需要扣除这部分占有。

同理,加入让你实现一个悬浮在左侧,右侧,滑动隐藏,停止显示的,也都可以参考类似Behavior的方式,减少代码耦合。

总结

最后整个布局是这样子的

<?  version=\"1.0\" encoding=\"utf-8\"?>
<com.tencent.igame.view.common.widget.IGameRefreshLayout  ns:android=\"http://schemas.android.com/apk/res/android\"
  ns:app=\"http://schemas.android.com/apk/res-auto\"
 android:id=\"@+id/igame_competition_detail_fragment_refresh\"
 android:layout_width=\"match_parent\"
 android:layout_height=\"match_parent\">

 <android.support.design.widget.CoordinatorLayout
 android:layout_width=\"match_parent\"
 android:layout_height=\"match_parent\">

 <android.support.design.widget.AppBarLayout
  android:id=\"@+id/appbarlayout\"
  android:layout_width=\"match_parent\"
  android:layout_height=\"wrap_content\"
  app:elevation=\"0dp\">

  <android.support.design.widget.CollapsingToolbarLayout
  android:id=\"@+id/collapsing_tool_bar\"
  android:layout_width=\"match_parent\"
  android:layout_height=\"wrap_content\"
  app:contentScrim=\"@color/b_G6\"
  app:expanded MarginEnd=\"10dp\"
  app:expanded MarginStart=\"10dp\"
  app:layout_scrollFlags=\"scroll|exitUntilCollapsed|snap\">

  <android.support.constraint.ConstraintLayout
   android:layout_width=\"match_parent\"
   android:layout_height=\"match_parent\">

   <ImageView
   android:id=\"@+id/igame_arena_rank_class_header_bg\"
   android:layout_width=\"match_parent\"
   android:layout_height=\"0dp\"
   android:scaleType=\"centerCrop\"
   android:src=\"@drawable/bg_arena_rank_class\"
   app:layout_constraintDimensionRatio=\"375:156\" />
   ............

  </android.support.constraint.ConstraintLayout>

  <android.support.v7.widget.Toolbar
   android:id=\"@+id/common_index_activity_tb_ \"
   android:layout_width=\"match_parent\"
   android:layout_height=\"wrap_content\"
   android:minHeight=\"?android:attr/actionBarSize\"
   android:visibility=\"visible\"
   app:contentInsetLeft=\"0dp\"
   app:contentInsetStart=\"0dp\"
   app:layout_collapseMode=\"pin\">

   <include
   layout=\"@layout/igame_common_tool_bar\"
   android:layout_width=\"match_parent\"
   android:layout_height=\"wrap_content\"
   android:layout_gravity=\"center\" />
  </android.support.v7.widget.Toolbar>


  </android.support.design.widget.CollapsingToolbarLayout>

 </android.support.design.widget.AppBarLayout>

 <com.tencent.igame.widget.viewpager.IgameViewPager
  android:id=\"@+id/igame_arena_rank_class_vp_content\"
  android:layout_width=\"match_parent\"
  android:layout_height=\"match_parent\"
  app:layout_behavior=\"@string/appbar_scrolling_view_behavior\" />

 <android.support.constraint.ConstraintLayout
  android:layout_width=\"match_parent\"
  android:layout_height=\"60dp\"
  android:layout_gravity=\"bottom\"
  android:background=\"@color/b_G6\"
  android:paddingLeft=\"12dp\"
  android:paddingRight=\"12dp\"
  app:anchor_id=\"@+id/igame_arena_rank_class_vp_content\"
  app:bottom_padding=\"156.0\"
  app:layout_behavior=\"com.tencent.igame.common.widget.BottomBehavior\">
..........底部布局

 </android.support.constraint.ConstraintLayout>

 </android.support.design.widget.CoordinatorLayout>

</com.tencent.igame.view.common.widget.IGameRefreshLayout>

注:IGameRefreshLayout实际上就是封装的PullToRefreshView,IgameViewPager是我们封装的Viewpager,减少每次写Viewpager的套路代码。

按照这个框架来,相信你很容易写出这个样子的布局。

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对脚本之家的支持。

收藏 打印