Android tips(十四)-->解决AppBarLayout滑动不畅的问题

本文介绍如何使用Android 5.0的AppBarLayout实现Toolbar与顶部内容区域的级联滚动效果,并解决滚动到顶部时的问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

转载请标明出处:一片枫叶的专栏

最近在开发的App中需要实现Toolbar与顶部内容区域级联滚动的效果,一开始就想到了Android 5.0 中新添加的AppBarLayout控件,其正好是实现这个效果的,所以马上就使用这个组件实现了一个类似的效果,具体的效果图如下所示:
这里写图片描述

好吧,使用Android 5.0中新添加的AppBarLayout可以比较方便的实现类似的效果,下面就是如何使用AppBarLayout实现类似的效果:

(一)添加Compile引用

compile 'com.android.support:design:22.2.1'

(二)添加Layout布局文件

<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout
    android:id="@+id/main_content"
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    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="200dp"
        android:fitsSystemWindows="true"
        android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">

        <android.support.design.widget.CollapsingToolbarLayout
            android:id="@+id/collapsingToolbar"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:fitsSystemWindows="true"
            app:contentScrim="?attr/colorPrimary"
            app:expandedTitleMarginEnd="64dp"
            app:expandedTitleMarginStart="48dp"
            app:layout_scrollFlags="scroll|snap|exitUntilCollapsed|enterAlwaysCollapsed">

            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:orientation="vertical"
                app:layout_collapseMode="parallax"
                >

                <ImageView
                    android:layout_width="60dp"
                    android:layout_height="60dp"
                    android:layout_marginTop="50dp"
                    android:layout_gravity="center_horizontal"
                    android:background="@mipmap/ic_launcher"
                    />

                <TextView
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:text="火星社"
                    android:textSize="15dp"
                    android:textColor="@android:color/white"
                    android:layout_gravity="center_horizontal"
                    android:layout_marginTop="10dp"
                    />

                <LinearLayout
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_gravity="center_horizontal"
                    android:layout_marginTop="8dp"
                    >

                    <View
                        android:layout_width="30dp"
                        android:layout_height="1dp"
                        android:background="@android:color/white"
                        android:layout_gravity="center_vertical"
                        />

                    <TextView
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:text="专注于国内外最新最热的自媒体"
                        android:layout_gravity="center_horizontal"
                        android:layout_marginLeft="5dp"
                        />

                    <View
                        android:layout_width="30dp"
                        android:layout_height="1dp"
                        android:background="@android:color/white"
                        android:layout_gravity="center_vertical"
                        android:layout_marginLeft="5dp"
                        />
                </LinearLayout>
            </LinearLayout>

            <android.support.v7.widget.Toolbar
                android:id="@+id/toolbar"
                android:layout_width="match_parent"
                android:layout_height="?attr/actionBarSize"
                app:layout_collapseMode="pin"
                app:popupTheme="@style/ThemeOverlay.AppCompat.Light"
                >

                <RelativeLayout
                    android:id="@+id/titleView"
                    android:layout_width="match_parent"
                    android:layout_height="match_parent"
                    android:visibility="visible"
                    >

                    <TextView
                        android:id="@+id/titleText"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:text="火星社"
                        android:textSize="18dp"
                        android:textColor="@android:color/white"
                        android:layout_centerInParent="true"
                        android:alpha="0"
                        />

                    <TextView
                        android:id="@+id/communityConnect"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:textColor="#ffffff"
                        android:text="+ 关注"
                        android:textSize="16dp"
                        android:layout_gravity="right"
                        android:layout_alignParentRight="true"
                        android:layout_centerVertical="true"
                        android:layout_marginRight="8dp"
                        />
                </RelativeLayout>
            </android.support.v7.widget.Toolbar>

        </android.support.design.widget.CollapsingToolbarLayout>

    </android.support.design.widget.AppBarLayout>

    <android.support.v7.widget.RecyclerView
        android:id="@+id/videoCommunityRecyclerview"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@android:color/white"
        android:clipToPadding="false"
        android:overScrollMode="never"
        app:layout_behavior="@string/appbar_scrolling_view_behavior"
        />

</android.support.design.widget.CoordinatorLayout>

在布局文件的定义中:
1) 首先需要用CoordinatorLayout包住AppBarLayout;
CoordinatorLayout也是Android 5.0新出的控件,新的思路通过协调调度子布局的形式实现触摸影响布局的形式产生动画效果。CoordinatorLayout通过设置子View的 Behaviors来调度子View。系统(Support V7)提供了AppBarLayout.Behavior, AppBarLayout.ScrollingViewBehavior, FloatingActionButton.Behavior, SwipeDismissBehavior 等。

2) 然后是添加AppBarLayout,顶部区域的View都放在AppBarLayout里面;
AppBarLayout也是Android 5.0新出的控件,AppBarLayout继承自LinearLayout,布局方向为垂直方向。它可以让你定制当某个可滚动View的滚动手势发生变化时,其内部的子View实现何种动作。

3) AppBarLayout外面,CoordinatorLayout里面,放一个带有可滚动的View。比如:RecyclerView。

4) 在AppBarLayout里面的View,通过app:layout_scrollFlags属性来控制,滚动时候的表现.其中有4种Flag的类型.

  • scroll:值设为scroll的View会跟随滚动事件一起发生移动

  • enterAlways:值设为enterAlways的View,当ScrollView往下滚动时,该View会直接往下滚动。而不用考虑ScrollView是否在滚动

  • exitUntilCollapsed:值设为exitUntilCollapsed的View,当这个View要往上逐渐“消逝”时,会一直往上滑动,直到剩下的的高度达到它的最小高度后,再响应ScrollView的内部滑动事件

  • enterAlwaysCollapsed:是enterAlways的附加选项,一般跟enterAlways一起使用,它是指,View在往下“出现”的时候,首先是enterAlways效果,当View的高度达到最小高度时,View就暂时不去往下滚动,直到ScrollView滑动到顶部不再滑动时,View再继续往下滑动,直到滑到View的顶部结束

  • snap:设置滚动View不可停留在半截,也就是滚动View要不是展开状态就是收缩状态

5) 在可以滚动的View上设置属性 app:layout_behavior,比如我们的例子中:app:layout_behavior=”@string/appbar_scrolling_view_behavior”是Android Support Library 定义后的值,可以被直接使用。这个Behavior的class是真正控制滚动时候View的滚动行为。当然了有的时候我们也可以手动的实现一个自定义的Behavior去实现特有的滚动行为。

这样就实现了一个简单的RecyclerView与AppBarLayou的级联滚动效果了,但是实现完成之后有一个问题就是,当我的RecyclerView向下滚动的时候,滚动到头了,就直接停止了,而不可以继续将AppBarLayout向下滚动,好吧,看来这也是一个bug。

既然这样就需要自己实现RecyclerView滚动到头级联AppBarLayout的效果,后来想到可以通过检测RecyclerView滚动到头然后手动执行AppBarLayout的展开动作。具体代码部分,只需要实现RecylerView的逻辑就可以了.

/**
 * 自定义RecyclerView实现对AppBarLayout的滚动效果
 */
recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
            override fun onScrolled(recyclerView: RecyclerView?, dx: Int, dy: Int) {
            }

            override fun onScrollStateChanged(recyclerView: RecyclerView?, newState: Int) {
                if (newState == RecyclerView.SCROLL_STATE_IDLE) {
                    var visiblePosition = linearLayoutManager.findFirstCompletelyVisibleItemPosition()
                    if (visiblePosition == 0) {
                        barLayout.setExpanded(true, true)
                    }
                }
            }
        })

这样经过重写之后我们在滑动RecyclerView的时候,当RecyclerView滑动到顶部时其就会滑动AppBarLayout:

这里写图片描述

好吧,可以发现当RecyclerView向下滚动到头的时候,若还有向下滑动的速度则AppBarLayout会被展开,看效果还是不错的,^_^

在实现过程中还有一个问题就是布局文件中:

<android.support.v7.widget.Toolbar
                android:id="@+id/toolbar"
                android:layout_width="match_parent"
                android:layout_height="?attr/actionBarSize"
                app:layout_collapseMode="pin"
                app:popupTheme="@style/ThemeOverlay.AppCompat.Light"
                >

                <RelativeLayout
                    android:id="@+id/titleView"
                    android:layout_width="match_parent"
                    android:layout_height="match_parent"
                    android:visibility="visible"
                    >

                    <TextView
                        android:id="@+id/titleText"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:text="火星社"
                        android:textSize="18dp"
                        android:textColor="@android:color/white"
                        android:layout_centerInParent="true"
                        android:alpha="0"
                        />

                    <TextView
                        android:id="@+id/communityConnect"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:textColor="#ffffff"
                        android:text="+ 关注"
                        android:textSize="16dp"
                        android:layout_gravity="right"
                        android:layout_alignParentRight="true"
                        android:layout_centerVertical="true"
                        android:layout_marginRight="8dp"
                        />
                </RelativeLayout>
            </android.support.v7.widget.Toolbar>

其中Toolbar下的RelativeLayout始终无法居中Toolbar,在Toolbar的左侧有一个Padding长度无法占满,最终造成RelativeLayout中的Title无法居中显示,最后想到了一个变通的方法:

/**
         * 更新title位置
         */
        titleText.post {
            titleText.offsetLeftAndRight(- titleView.left / 2)
        }

在代码中通过RelativeLayout的left长度移动titleText的位置,进而使title居中显示。

<think>我们正在讨论UniApp中的`<page-container>`组件。根据引用[3]中的信息,我们知道在UniApp中,原生组件(如map、video等)的层级高于前端组件,因此为了覆盖这些原生组件,UniApp提供了`cover-view`和`cover-image`组件。而`<page-container>`组件是UniApp中用于页面布局的一个特殊组件,它通常作为页面的根容器,用于包裹页面内容,并且可以包含原生组件和覆盖其上的视图。 在之前的回答中,我们提到在`app.vue`中必须保留`<page-container />`标签,因为它是页面内容的渲染入口。那么,在普通页面中,我们如何使用`<page-container>`组件?它有什么注意事项? ### `<page-container>`组件介绍 `<page-container>`是UniApp提供的一个内置组件,用于作为页面的容器。它提供了一些特殊的样式和行为,特别是在处理原生组件覆盖时。 ### 使用方法 在普通页面中,我们通常不需要显式地使用`<page-container>`,因为每个页面默认会有一个根容器。但是,在某些特殊场景下,比如需要自定义整个页面的布局,或者需要覆盖原生组件时,我们可以使用`<page-container>`。 例如,在`detail.vue`页面(引用[2]中提到的页面)中,我们可以这样使用: ```vue <template> <page-container> <!-- 页面内容 --> <view> <text>{{ memos[index].content }}</text> </view> <!-- 覆盖在原生组件上的视图 --> <cover-view class="custom-cover">覆盖文本</cover-view> </page-container> </template> <script> export default { data() { return { index: 0, memos: [ /* 数据 */ ] } }, onLoad(options) { // 页面加载时执行,比如根据options参数初始化数据 this.index = options.index || 0; } } </script> ``` ### 注意事项 1. **作为根容器**:`<page-container>`可以作为页面的根容器,它内部可以包含普通视图组件和覆盖组件(`cover-view`、`cover-image`)。 2. **覆盖原生组件**:当页面中有原生组件(如`video`)时,如果需要在原生组件上覆盖视图,必须使用`cover-view`或`cover-image`,并且这些覆盖组件必须放在`<page-container>`内才能生效(引用[3])。 3. **样式和布局**:`<page-container>`默认会占据整个页面,并且具有一些默认样式(如背景色为白色)。如果需要自定义样式,可以通过CSS覆盖。 4. **页面生命周期**:在`<page-container>`包裹的页面中,页面的生命周期(如`onLoad`、`onShow`)仍然正常触发(引用[2]中提到了`onLoad`方法的使用)。 5. **兼容性**:`<page-container>`在H5、小程序和App端的表现可能有所不同,特别是在处理覆盖原生组件时。在H5端,由于没有真正的原生组件,所以覆盖组件的层级问题可能不存在,但在小程序和App端需要特别注意。 ### 示例:在视频组件上覆盖文字 ```vue <template> <page-container> <video src="https://example.com/video.mp4" controls></video> <cover-view class="video-title">视频标题</cover-view> </page-container> </template> <style> .video-title { position: absolute; top: 10px; left: 10px; background-color: rgba(0,0,0,0.5); color: white; padding: 5px; } </style> ``` ### 总结 - 在普通页面中,`<page-container>`不是必须的,因为页面默认有根容器。但在需要控制原生组件覆盖层时,使用`<page-container>`包裹内容,并在其中使用`cover-view`和`cover-image`是必要的。 - 注意覆盖组件的样式定位(通常使用绝对定位)以及层级关系。 §§相关问题§§ 1. UniApp中`cover-view`和`cover-image`的使用场景和限制有哪些? 2. 如何在UniApp中正确使用原生组件(如video、map)? 3. UniApp页面布局的常见问题解决方案?</think>### UniApp `<page-container>` 组件使用指南 在 UniApp 中,`<page-container>` 是一个特殊的容器组件,主要用于解决**原生组件层级覆盖问题**(引用[3])。以下是详细使用方法及注意事项: --- #### 基本使用方法 ```vue <template> <!-- 必须作为根容器 --> <page-container> <!-- 页面内容 --> <view class="content"> <text>{{ memos[index].content }}</text> <!-- 引用[2]数据示例 --> </view> <!-- 覆盖原生组件的元素 --> <cover-view class="custom-overlay"> <cover-image src="/static/icon.png"></cover-image> 悬浮按钮 </cover-view> </page-container> </template> <script> export default { data() { return { index: 0, memos: [ /* 引用[2]数据结构 */ ] } }, onLoad(options) { // 引用[2]生命周期示例 // 初始化数据 } } </script> <style> .custom-overlay { position: absolute; bottom: 20px; right: 20px; background-color: rgba(0,0,0,0.7); padding: 10px; border-radius: 50%; } </style> ``` --- ### 核心注意事项 1. **必须作为根容器** `<page-container>` 必须是页面最外层容器,不能嵌套在其他普通组件内。 2. **专属覆盖组件** 覆盖原生组件(如 map/video)只能使用其专属子组件: - `<cover-view>`:覆盖文本/按钮 - `<cover-image>`:覆盖图片 ```vue <page-container> <map></map> <cover-view class="marker">坐标点</cover-view> ✅ 正确 <!-- <view>普通元素无法覆盖</view> ❌ 错误 --> </page-container> ``` 3. **样式限制** - 不支持伪元素(::before/::after) - 不支持 CSS 动画 - 不支持 `overflow: visible` - 定位需使用 `position: absolute` 4. **平台差异** | 特性 | H5 | 小程序 | App | |--------------|-------|--------|-------| | 层级覆盖 | 支持 | 支持 | 部分支持 | | CSS 动画 | 支持 | 不支持 | 不支持 | | 滚动穿透 | 自动处理 | 需手动处理 | 自动处理 | 5. **性能优化** 避免在 `<page-container>` 内放置复杂逻辑: ```vue <page-container> <!-- 轻量级内容 --> <simple-component></simple-component> <!-- 复杂内容单独封装 --> <heavy-component v-if="showHeavy"></heavy-component> <!-- 条件渲染 --> </page-container> ``` --- ### 典型使用场景 1. **地图标记覆盖** ```vue <page-container> <map :latitude="39.9" :longitude="116.4"> <cover-marker :latitude="39.9" :longitude="116.4"></cover-marker> </map> <cover-view class="map-tips">北京故宫</cover-view> </page-container> ``` 2. **视频播放控制栏** ```vue <page-container> <video src="video.mp4"></video> <cover-view class="video-controls"> <cover-image src="/static/play.png"></cover-image> <cover-view>进度控制</cover-view> </cover-view> </page-container> ``` 3. **直播悬浮元素** ```vue <page-container> <live-player></live-player> <cover-view class="gift-panel"> <cover-image v-for="gift in gifts" :src="gift.icon"></cover-image> </cover-view> </page-container> ``` --- ### 常见问题解决 1. **覆盖元素不显示** - 检查是否使用了 `<cover-view>`/`<cover-image>` - 确认层级关系:原生组件 > cover 组件 > 普通组件 - 添加 `z-index` 属性 2. **滚动穿透问题** 在小程序端添加 `@touchmove.stop`: ```vue <cover-view class="popup" @touchmove.stop="() => {}"> <!-- 阻止滚动穿透 --> </cover-view> ``` 3. **动态内容更新延迟** 使用 `this.$nextTick()` 确保渲染完成: ```javascript this.showOverlay = true; this.$nextTick(() => { this.$refs.coverView.updatePosition(); }); ``` > 提示:在需要唤起其他应用时(如引用[1]的唤起知乎),注意 `<page-container>` 内的链接元素同样需要条件编译处理跨平台兼容性。
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值