欢迎你们前往腾讯云+社区,获取更多腾讯海量技术实践干货哦~android
本文由 brzhang发表于 云+社区专栏
作APP开发的过程当中,有不少时候,咱们须要实现相似于下面这种沉浸式的体验。app
沉浸式体验框架
一开始接触的时候,彷佛你们都会觉这种体验实现起来,会比较困难。难点在于:机器学习
好,总结起来以上就是咱们的问题,也是须要解决的,一个一个解决了,这种需求也就实现了,那么,咱们如何去一步一步来解决以上的问题呢?ide
首先,咱们来分析第一个问题,头部的背景图在推上去的过程当中,慢慢的变得不可见了,这种听起来好像是某种collapse,所以,很容易让人想到CollapsingToolbarLayout,若是你想要比较容易的了解CollapsingToolbarLayout布局
应用,建议看这位兄台的文章,他给也给了一个动画,比较详细的介绍了这个的应用,例如:学习
CollapsingToolbarLayout动画
对于里面的用法,我这里不做讲解了,可是若是你不了解这个布局的应用,我强烈建议你好好了解一下,才能继续下面走,只是想说明一下,走到这里,你有一个坑须要去填,那就是咱们的标题动画能够不是这样的,并且,仍是标题仍是居中的,注意,这里的实现,标题不是居中的,是靠左的,这原本是Android设计规范,可是设计师恰恰不买Android规范的帐,所以,咱们必须躺过这个坑,而后,从Stack Overflow上了解到一个issue:ui
<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开始是透明的,而且改变返回按钮的图片:spa
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的监听。
/** * 使状态栏透明,并覆盖状态栏,对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的高度。
这个其实须要你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>
能够看到,底部的控件是覆盖在列表上的,列表向上滑动的时候,把他隐藏,就能够空出更多的控件看列表。那么,如何作呢?
既然,咱们是包裹在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的套路代码。
按照这个框架来,相信你很容易写出这个样子的布局。
相关阅读
【每日课程推荐】机器学习实战!快速入门在线广告业务及CTR相应知识
此文已由做者受权腾讯云+社区发布,更多原文请点击
搜索关注公众号「云加社区」,第一时间获取技术干货,关注后回复1024 送你一份技术课程大礼包!
海量技术实践经验,尽在云加社区!