zoukankan      html  css  js  c++  java
  • Android如何实现超级棒的沉浸式体验

    欢迎大家前往腾讯云+社区,获取更多腾讯海量技术实践干货哦~

    本文由brzhang发表于云+社区专栏

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

    img沉浸式体验

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

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

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

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

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

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

    imgCollapsingToolbarLayout

    对于里面的用法,我这里不作讲解了,但是如果你不了解这个布局的应用,我强烈建议你好好了解一下,才能继续下面走,只是想说明一下,走到这里,你有一个坑需要去填,那就是我们的标题动画可以不是这样的,而且,还是标题还是居中的,注意,这里的实现,标题不是居中的,是靠左的,这本来是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>
    

    img效果就是这样

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

    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的套路代码。

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

    相关阅读
    【每日课程推荐】机器学习实战!快速入门在线广告业务及CTR相应知识

    此文已由作者授权腾讯云+社区发布,更多原文请点击

    搜索关注公众号「云加社区」,第一时间获取技术干货,关注后回复1024 送你一份技术课程大礼包!

    海量技术实践经验,尽在云加社区

  • 相关阅读:
    chrome浏览器中安装以及使用Elasticsearch head 插件
    windows10 升级并安装配置 jmeter5.3
    linux下部署Elasticsearch6.8.1版本的集群
    【Rollo的Python之路】Python 爬虫系统学习 (八) logging模块的使用
    【Rollo的Python之路】Python 爬虫系统学习 (七) Scrapy初识
    【Rollo的Python之路】Python 爬虫系统学习 (六) Selenium 模拟登录
    【Rollo的Python之路】Python 爬虫系统学习 (五) Selenium
    【Rollo的Python之路】Python 爬虫系统学习 (四) XPath学习
    【Rollo的Python之路】Python 爬虫系统学习 (三)
    【Rollo的Python之路】Python sys argv[] 函数用法笔记
  • 原文地址:https://www.cnblogs.com/qcloud1001/p/9925097.html
Copyright © 2011-2022 走看看