前言
大家在做APP开发的过程中,有很多时候,我们需要实现类似于下面这种沉浸式的体验。
沉浸式体验
一开始接触的时候,似乎大家都会觉这种体验实现起来,会比较困难。难点在于:
- 头部的背景图在推上去的过程中,慢慢的变得不可见了,整个区域的颜色变成的暗黑色,然后标题出现了。
- StatusBar变的透明,且空间可以被利用起来,看我们的图片就顶到了顶 了。
- 我们的viewpager推到actionbar的下方的时候,就固定在了actionbar的下方,不能在往上面推了。
- 底部有一个控件,随着列表的向上滑动,它退出视角范围,以便于给出更多的空间来展示列表,其实整个沉浸式体验都是为了给列表留出更多的空间来展示。
好,总结起来以上就是我们的问题,也是需要解决的,一个一个解决了,这种需求也就实现了,那么,我们如何去一步一步来解决以上的问题呢?
1、头部背景和标题的渐隐渐现
首先,我们来分析第一个问题,头部的背景图在推上去的过程中,慢慢的变得不可见了,这种听起来好像是某种collapse,因此,很容易让人想到CollapsingToolbarLayout,如果你想要比较容易的了解CollapsingToolbarLayout
应用,建议看这位兄台的文章,他给也给了一个动画,比较详细的介绍了这个的应用,例如:
CollapsingToolbarLayout
对于里面的用法,我这里不作讲解了,但是如果你不了解这个布局的应用,我强烈建议你好好了解一下,才能继续下面走,只是想说明一下,走到这里,你有一个坑需要去填,那就是我们的标题动画可以不是这样的,而且,还是标题还是居中的,注意,这里的实现,标题不是居中的,是靠左的,这本来是Android设计规范,但是设计师偏偏不买Android规范的账,因此,我们必须躺过这个坑,然后,从Stack Overflow上了解到一个issue:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
<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开始是透明的,并且改变返回按钮的图片:
1
2
3
|
collapsingToolbarLayout.setCollapsedTitleTextColor(Color.WHITE); //collapsingToolbarLayout.setExpandedTitleColor(Color.WHITE); collapsingToolbarLayout.setExpandedTitleColor(Color.TRANSPARENT); |
然而,假设,始终只是一个假设,实际上,这个假设不成立,我在尝试的时候,发现Toolbar中的TextView根本就不能使用android:layout_gravity="center"这种属性好吧,即使强行加上,效果也是靠左的。
那么,如何做,我的解决方式是这样的
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
|
<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里面的布局是这样的
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
|
<? 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 > |
效果就是这样
当然,这时候,标题是需要你自己设置渐隐渐现的。那么,我们依据什么呢?
1
2
3
4
5
6
|
appBarLayout.addOnOffsetChangedListener( new AppBarLayout.OnOffsetChangedListener() { @Override public void onOffsetChanged(AppBarLayout appBarLayout, int verticalOffset) { mTitle.setAlpha(-verticalOffset * 1 .0f / appBarLayout.getTotalScrollRange()); } }); |
依据的就是对appBarLayout的监听。
2、将statusBar变为透明,且利用他的空间来放我们的布局内容。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
/** * 使状态栏透明,并覆盖状态栏,对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中并没有这个属性
1
|
android:fitsSystemWindows= "true" |
如果你加了这个属性,嘿嘿,statusbar虽然空间可以利用,但是有一个你挥之不去的颜色覆盖在上面,
然后,你还记得上面那个布局中
1
2
3
4
5
|
//*****请注意这个View*******/// <View android:id= "@+id/common_index_activity_view_status_bar" android:layout_width= "match_parent" android:layout_height= "0dp" /> |
这个作用可大了,就是为了对status_bar原始空间做偏移的,在代码中,需要动态的改变这个View的高度为statusBar的高度,怎么获取:
1
2
3
4
5
6
7
8
9
10
11
|
/** * 获取状态栏高度 * * @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了
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
<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:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
|
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的方式,减少代码耦合。
总结
最后整个布局是这样子的
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
|
<? 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的套路代码。
按照这个框架来,相信你很容易写出这个样子的布局。
总结
以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对服务器之家的支持。
原文链接:https://cloud.tencent.com/developer/article/1358534