欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

Android实现超级棒的沉浸式体验教程

程序员文章站 2023-10-30 17:14:46
前言 大家在做app开发的过程中,有很多时候,我们需要实现类似于下面这种沉浸式的体验。 沉浸式体验 一开始接触的时候,似乎大家都会觉这种体验实现起来,会比较困...

前言

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

Android实现超级棒的沉浸式体验教程
沉浸式体验

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

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

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

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

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

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

Android实现超级棒的沉浸式体验教程
collapsingtoolbarlayout

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

<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 title"
 android:layout_gravity="center"
 android:id="@+id/toolbar_title" />


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

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

collapsingtoolbarlayout.setcollapsedtitletextcolor(color.white);
//collapsingtoolbarlayout.setexpandedtitlecolor(color.white);
collapsingtoolbarlayout.setexpandedtitlecolor(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:expandedtitlemarginend="10dp"
 app:expandedtitlemarginstart="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_title"
  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里面的布局是这样的

<?xml version="1.0" encoding="utf-8"?>
<linearlayout xmlns:android="http://schemas.android.com/apk/res/android"
 xmlns: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_title"
 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:contentdescription="@string/image_desc"
 android:scaletype="centerinside"
 android:src="@drawable/igame_actionbar_arrow_left" />
 </relativelayout>

 </relativelayout>
</linearlayout>

Android实现超级棒的沉浸式体验教程

效果就是这样

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

appbarlayout.addonoffsetchangedlistener(new appbarlayout.onoffsetchangedlistener() {
 @override
 public void onoffsetchanged(appbarlayout appbarlayout, int verticaloffset) {
 mtitle.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_title"
   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的方式,减少代码耦合。

总结

最后整个布局是这样子的

<?xml version="1.0" encoding="utf-8"?>
<com.tencent.igame.view.common.widget.igamerefreshlayout xmlns:android="http://schemas.android.com/apk/res/android"
 xmlns: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:expandedtitlemarginend="10dp"
  app:expandedtitlemarginstart="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_title"
   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的套路代码。

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

总结

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