Android实现超级棒的沉浸式体验教程
前言
大家在做app开发的过程中,有很多时候,我们需要实现类似于下面这种沉浸式的体验。
沉浸式体验
一开始接触的时候,似乎大家都会觉这种体验实现起来,会比较困难。难点在于:
- 头部的背景图在推上去的过程中,慢慢的变得不可见了,整个区域的颜色变成的暗黑色,然后标题出现了。
- statusbar变的透明,且空间可以被利用起来,看我们的图片就顶到了顶 了。
- 我们的viewpager推到actionbar的下方的时候,就固定在了actionbar的下方,不能在往上面推了。
- 底部有一个控件,随着列表的向上滑动,它退出视角范围,以便于给出更多的空间来展示列表,其实整个沉浸式体验都是为了给列表留出更多的空间来展示。
好,总结起来以上就是我们的问题,也是需要解决的,一个一个解决了,这种需求也就实现了,那么,我们如何去一步一步来解决以上的问题呢?
1、头部背景和标题的渐隐渐现
首先,我们来分析第一个问题,头部的背景图在推上去的过程中,慢慢的变得不可见了,这种听起来好像是某种collapse,因此,很容易让人想到collapsingtoolbarlayout,如果你想要比较容易的了解collapsingtoolbarlayout
应用,建议看,他给也给了一个动画,比较详细的介绍了这个的应用,例如:
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>
效果就是这样
当然,这时候,标题是需要你自己设置渐隐渐现的。那么,我们依据什么呢?
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的套路代码。
按照这个框架来,相信你很容易写出这个样子的布局。
总结
以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对的支持。
上一篇: 孕妇可以吃小米吗,你又了解多少
下一篇: Android列表动图展示的实现策略