Android如何实现超级棒的沉浸式体验
欢迎大家前往腾讯云+社区,获取更多腾讯海量技术实践干货哦~
做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 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的套路代码。
按照这个框架来,相信你很容易写出这个样子的布局。
此文已由作者授权腾讯云+社区发布,更多原文请点击
搜索关注公众号「云加社区」,第一时间获取技术干货,关注后回复1024 送你一份技术课程大礼包!
海量技术实践经验,尽在云加社区!
Android如何实现超级棒的沉浸式体验的更多相关文章
- Android隐藏状态栏实现沉浸式体验
转自: Android状态栏微技巧,带你真正理解沉浸式模式 什么叫沉浸式? 根据百度百科上的定义,沉浸式就是要给用户提供完全沉浸的体验,使用户有一种置身于虚拟世界之中的感觉. 那么对应到Android ...
- 第六章 大数据,6.3 突破传统,4k大屏的沉浸式体验(作者: 彦川、小丛)
6.3 突破传统,4k大屏的沉浸式体验 前言 能够在 4K 的页面上表演,对设计师和前端开发来说,即是机会也是挑战,我们可以有更大的空间设计宏观的场景,炫酷的转场,让观众感受影院式视觉体验,但是,又必 ...
- Android 5.0以上Material Design 沉浸式状态栏
偶然在知乎上看到这个问题,Android 5.0 如何实现将布局的内容延伸到状态栏,之前也见过多个应用的这个功能,但是知乎上的答案却没有一个真正实现此功能的一类是把标题栏设置App主题颜色,一类是提取 ...
- Android:UI 沉浸式体验,适合第一屏的引导图片、预览图片。
链接:http://www.cnblogs.com/liushilin/p/5799381.html
- Botanical Dimensions:借助第九代智能英特尔® 酷睿™ 处理器实现独特沉浸式体验
本文介绍位于洛杉矶的互动体验设计工作室 Master of Shapes (MOS) 打造 Botanical Dimensions 时所做出的技术努力.在这种互动式多世界体验中,参与者将穿越丛林,寻 ...
- Android UI体验之全屏沉浸式透明状态栏效果
前言: Android 4.4之后谷歌提供了沉浸式全屏体验, 在沉浸式全屏模式下, 状态栏. 虚拟按键动态隐藏, 应用可以使用完整的屏幕空间, 按照 Google 的说法, 给用户一种 身临其境 的体 ...
- Android状态栏微技巧,带你真正意义上的沉浸式
记得之前有朋友在留言里让我写一篇关于沉浸式状态栏的文章,正巧我确实有这个打算,那么本篇就给大家带来一次沉浸式状态栏的微技巧讲解. 其实说到沉浸式状态栏这个名字我也是感到很无奈,真不知道这种叫法是谁先发 ...
- 实现Android K的伪沉浸式
在Android 5.0之后引入了MD风格,从而状态栏沉浸也成为了一种设计习惯.而停留在之Android L之前的Android系统则不能直接实现沉浸式,这里就介绍一下如何实现Android K系列的 ...
- Android状态栏微技巧,带你真正理解沉浸式模式【转】
感谢! 本文转自大佬郭霖:http://blog.csdn.net/guolin_blog/article/details/51763825 转载请注明出处:http://blog.csdn.net/ ...
随机推荐
- Java线程池核心原理剖析
在系统开发时,我们经常会遇到“池”的概念.使用池一种以空间换时间的做法,通常在内存中事先保存一系列整装待命的对象,以供后期供其他对象随时调用.常见的池有:数据库连接池,socket连接池,线程池等.今 ...
- [Swift]LeetCode443. 压缩字符串 | String Compression
Given an array of characters, compress it in-place. The length after compression must always be smal ...
- [Swift]LeetCode740. 删除与获得点数 | Delete and Earn
Given an array nums of integers, you can perform operations on the array. In each operation, you pic ...
- Hbase篇--HBase中一对多和多对多的表设计
一.前述 今天分享一篇关于HBase的一对多和多对多的案例的分析. 二.具体案例 案例一.多对多 人员-角色 人员有多个角色 角色优先级 角色有多个人员 人员 删除添加角色 角 ...
- 分享几个 SpringBoot 实用的小技巧
前言 最近分享的一些源码.框架设计的东西.我发现大家热情不是特别高,想想大多数应该还是正儿八经写代码的居多:这次就分享一点接地气的: SpringBoot 使用中的一些小技巧. 算不上多高大上的东西, ...
- python中的pip
python中的pip python有两个著名的包管理工具,其中,pip是一个.它对python的包进行管理和升级等操作. 问题一:pip本地的模块安装在哪里? 使用pip install numpy ...
- Kibana安全特性之权限控制
1. 前言 在之前的例子中,我们都是直接输入地址访问的,系统也没有提示我们要输入用户名密码.但是,在实际使用过程中不大可能所有人都是超级管理员可以做任何操作,一定是有权限控制的,这里我们借助X-Pa ...
- Python爬虫入门教程 29-100 手机APP数据抓取 pyspider
1. 手机APP数据----写在前面 继续练习pyspider的使用,最近搜索了一些这个框架的一些使用技巧,发现文档竟然挺难理解的,不过使用起来暂时没有障碍,估摸着,要在写个5篇左右关于这个框架的教程 ...
- Jvm垃圾回收器(算法篇)
在<Jvm垃圾回收器(基础篇)>中我们主要学习了判断对象是否存活还是死亡?两种基础的垃圾回收算法:引用计数法.可达性分析算法.以及Java引用的4种分类:强引用.软引用.弱引用.虚引用.和 ...
- 【转载】ASP.NET Core 依赖注入
本文转自:http://www.jessetalk.cn/2017/11/06/di-in-aspnetcore/ 为什么要写这个博客 DI在.NET Core里面被提到了一个非常重要的位置, 这篇文 ...