文档章节

Android -- 固定在ScrollView顶部的View,类似于新浪微博的评论列表的顶部

Hideeee
 Hideeee
发布于 2015/09/03 16:56
字数 1274
阅读 5148
收藏 10

先来看一下上面这张图的效果。

这个是新浪微博的一个页面,整体布局大致分了三块:正文内容、转发评论赞的数字条、评论列表

其中数字条是可以跟着ScrollView一起滑动,但在滑到最顶部时固定在最上面,而下面的评论内容可以继续滑动。

这种效果还是挺赞的,但一开始没有什么思路,所以就去搜了下相关技术代码,一下就恍然大悟!原来是自己想复杂了,其实原理很简单!


下面是自己实现的效果图:



实现原理:

    当滚动条划过头部时,把需要固定的头部从父布局中移除,然后添加到最外层布局的顶部。

    当滚动条返回时又把最外层的头部移然后重新添加到原来的父布局里面。

    整个实现代码,不算上布局,也就100行左右


详细实现逻辑:

首先建一个自定义View叫MyHoveringScrollView继承自FrameLayout,在布局里MyHoveringScrollView处于最外层。由于FrameLayout本身是不支持滚动条的,所以在FrameLayout内部有一个自定义的ScrollView。

在初始化的时候,通过getChildAt(0)把子布局拿到,然后清空整个布局,然后实例化一个自己的ScrollView,把之前拿到的子布局添加到ScrollView里面,

最后把ScrollView添加到MyHoveringScrollView里面。

public void init() {
        post(new Runnable() {
            @Override
            public void run() {
                mContentView = (ViewGroup) getChildAt(0);
                removeAllViews();

                MyScrollView scrollView = new MyScrollView(getContext(), MyHoveringScrollView.this);
                scrollView.addView(mContentView);
                addView(scrollView);

            }
        });
    }

可能注意到了两点:

1、我用了post()。因为在构造方法里面布局还没有生成,getChildAt(0)是拿不到东西的,但是post()会把动作放到队列里,等布局完成后再从队列里取出来,所以这里是个小窍门。

2、我把MyHoveringScrollView传入到了ScrollView里面,这么做其实是为了让ScrollView回调MyHoveringScrollView的方法。(比较懒,不想写接口……)


然后通过setTopView()方法,把需要固定在顶部的ID传进来:

public void setTopView(final int id) {
        post(new Runnable() {
            @Override
            public void run() {
                mTopView = (ViewGroup) mContentView.findViewById(id);

                int height = mTopView.getChildAt(0).getMeasuredHeight();
                ViewGroup.LayoutParams params = mTopView.getLayoutParams();
                params.height = height;
                mTopView.setLayoutParams(params);
                mTopViewTop = mTopView.getTop();
                mTopContent = mTopView.getChildAt(0);

            }
        });
    }

注意为什么要调用mTopView.setLayoutParams(),因为头部的布局高度必须得固定,如果是wrap_content,虽然也不会有什么错误,但效果不太好,可以自己试一下。


接下来,在ScrollView里面重写onScrollChanged()方法,并回调给MyHoveringScrollView的onScroll方法:

private static class MyScrollView extends ScrollView {

        private MyHoveringScrollView mScrollView;

        public MyScrollView(Context context, MyHoveringScrollView scrollView) {
            super(context);
            mScrollView = scrollView;
        }


        @Override
        protected void onScrollChanged(int l, int t, int oldl, int oldt) {
            super.onScrollChanged(l, t, oldl, oldt);
            mScrollView.onScroll(t);
        }

    }
public void onScroll(final int scrollY) {
        post(new Runnable() {
            @Override
            public void run() {
                if (mTopView == null
                        ) return;

                if (scrollY >= mTopViewTop
                        && mTopContent.getParent() == mTopView) {
                    mTopView.removeView(mTopContent);
                    addView(mTopContent);
                } else if (scrollY < mTopViewTop
                        && mTopContent.getParent() == MyHoveringScrollView.this) {
                    removeView(mTopContent);
                    mTopView.addView(mTopContent);
                }

            }
        });
    }

如果scrollY >= mTopViewTop就是头部应该被固定在顶部的时候

如果scrollY < mTopViewTop就是头部应该取消固定,还原到原来父布局的时候

至此,功能就实现了!

    

怎么使用呢?首先先写布局:

<com.hide.myhoveringscroll.app.MyHoveringScrollView
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@+id/view_hover"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
    <LinearLayout android:layout_width="match_parent"
                  android:layout_height="match_parent"
                  android:orientation="vertical"
            >
        <TextView android:layout_width="match_parent"
                  android:layout_height="300dp"
                  android:text="这是头部"
                  android:gravity="center"
                />

        <FrameLayout android:layout_width="match_parent" android:layout_height="wrap_content"
                     android:id="@+id/top"
                >
            <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content"
                          android:padding="20dp"
                          android:background="#AAff0000"
                          android:orientation="horizontal">
                <TextView android:layout_width="0dp" android:layout_height="wrap_content"
                          android:layout_weight="1"
                          android:gravity="center"
                          android:layout_gravity="center"
                          android:textSize="16sp"
                          android:text="这是固定部分"
                        />
                <Button android:layout_width="wrap_content" android:layout_height="wrap_content"
                        android:text="点我一下"
                        android:id="@+id/btn"
                        />

            </LinearLayout>
        </FrameLayout>

        <TextView android:layout_width="match_parent" android:layout_height="wrap_content"
                  android:paddingTop="10dp"
                  android:text="内容\n内容\n内容\n内容\n内容\n内容\n内容\n内容\n内容\n内容\n内容\n内容\n内容\n内容\n内容\n内容\n内容\n内容\n内容\n内容\n内容\n内容\n内容\n内容\n内容\n内容\n内容\n内容\n内容\n内容\n内容\n内容\n内容\n内容\n内容\n内容\n内容\n内容\n内容\n内容\n内容\n内容\n内容\n内容\n内容\n内容\n内容\n内容\n内容\n内容\n内容\n内容\n内容\n内容\n内容\n内容\n内容\n内容\n内容\n内容\n内容\n内容\n内容\n内容\n内容\n内容\n内容\n内容\n内容\n"
                />

    </LinearLayout>
</com.hide.myhoveringscroll.app.MyHoveringScrollView>

其中:MyHoveringScrollView在最外层,充当ScrollView的角色(所以子布局只能有一个)

android:id="@+id/top也就是需要固定在顶部的布局


最后回到Activity:

view_hover = (MyHoveringScrollView) findViewById(R.id.view_hover);
view_hover.setTopView(R.id.top);

两句话就实现了固定头部的效果。


源代码地址:https://github.com/w9xhc/MyHoveringScroll


© 著作权归作者所有

共有 人打赏支持
Hideeee
粉丝 7
博文 4
码字总数 1677
作品 0
成都
程序员
加载中

评论(12)

好饼哥
好饼哥

引用来自“Evasi0n”的评论

评论列表应该是listview吧,怎么处理滑动事件的冲突呢

引用来自“Hideeee”的评论

去掉ListView的滚动条,让ScrollView来滚动就行了。 代码如下: 自己写个类继承自ListView,然后重写onMeasure()方法。 加上这两句话:int expandSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST); super.onMeasure(widthMeasureSpec, expandSpec);
楼主 我按照你的方法写了 可是listview滑动不了 :joy:
a
ajie191

引用来自“Evasi0n”的评论

评论列表应该是listview吧,怎么处理滑动事件的冲突呢

引用来自“Hideeee”的评论

去掉ListView的滚动条,让ScrollView来滚动就行了。 代码如下: 自己写个类继承自ListView,然后重写onMeasure()方法。 加上这两句话:int expandSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST); super.onMeasure(widthMeasureSpec, expandSpec);
这样子会有问题的,listview的复用没法起作用!!!
chencg
chencg

引用来自“chencg”的评论

楼主,可否共享一下源码,学习学习?我按你文章中写的代码试了一下,一些细节的处理还不是很确定

引用来自“Hideeee”的评论

已传到Github:https://github.com/w9xhc/MyHoveringScroll
谢谢
Hideeee
Hideeee

引用来自“chencg”的评论

楼主,可否共享一下源码,学习学习?我按你文章中写的代码试了一下,一些细节的处理还不是很确定
已传到Github:https://github.com/w9xhc/MyHoveringScroll
chencg
chencg
楼主,可否共享一下源码,学习学习?我按你文章中写的代码试了一下,一些细节的处理还不是很确定
Hideeee
Hideeee

引用来自“Evasi0n”的评论

评论列表应该是listview吧,怎么处理滑动事件的冲突呢
去掉ListView的滚动条,让ScrollView来滚动就行了。 代码如下: 自己写个类继承自ListView,然后重写onMeasure()方法。 加上这两句话:int expandSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST); super.onMeasure(widthMeasureSpec, expandSpec);
Evasi0n
Evasi0n
评论列表应该是listview吧,怎么处理滑动事件的冲突呢
Hideeee
Hideeee

引用来自“suiyuan-sh”的评论

弱弱地问一句,你自定义的MyScrollView都没有在布局文件中使用,能有那个效果么?
第一段代码中有一句:addView(scrollView); 通过代码方式把自定义ScrollView添加进去了,所以布局里面就不用再添加了。
s
suiyuan-sh
弱弱地问一句,你自定义的MyScrollView都没有在布局文件中使用,能有那个效果么?
小手哇哇凉
小手哇哇凉
赞~
高仿美团app,浮动layout滑动到顶部悬停效果

做了个类似美团app的一个效果 当一个浮动layout的滑动到顶部时,这个浮动layout就悬停下来,当屏幕往下滑动时,浮动layout也跟着往下移动。 因此,我特意也写了一个:浮动layuot滑动到顶部悬...

谁带我去看看世界
2015/06/16
0
4
总结的一些android公共库

最新最准确内容建议直接访问原文:Android公共库(缓存 下拉ListView 下载管理Pro 静默安装 root运行 Java公共类) 介绍总结的一些android公共库,包含缓存(图片缓存、预取缓存)、公共View(下拉...

Trinea
2012/07/16
0
1
android - scrollview不会自动显示顶部的问题(scroll view 中包含了re

android - scrollview不会自动显示顶部的问题(scroll view 中包含了re 申思维的站点/Siwei's site2017-08-2917 阅读 Android包含scrollScrollViewview 参考: https://stackoverflow.com/qu......

申思维的站点/Siwei's site
2017/08/29
0
0
Android-仿豌豆荚首页导航实现

1:结果图: 2: 实现思路: 2.1:UI层次框架: 如 图所示:最低层为RelativeLayout,第二层为ScrollView,第三层为顶部的祝导航栏。IndexNavigator是在 ScrollView层上,会跟着一起滑动。而Mai...

叶大侠
2014/12/20
0
6
Google Chrome 70 安卓测试版加入新功能:长按返回键呼出历史

Chrome浏览器Canary通道的最新版本v70出现新功能,长按返回键可以呼出访问历史列表,便于快速导航。当然,这里说的是Android平台。启用的方法是,定位到chrome://flags#long-press-back-for-...

快科技
07/30
0
0

没有更多内容

加载失败,请刷新页面

加载更多

为什么 vue 默认导出的是 vue.common.js,它和 vue.js 的区别在哪里,又有什么关系?

这个问题在囧克斯的博客中有提到。 Vue 最早会打包生成三个文件,一个是 runtime only 的文件 vue.common.js,一个是 compiler only 的文件 compiler.js,一个是 runtime + compiler 的文件 ...

粒子数反转
28分钟前
3
0
php正则表达式替换图片地址

<?php /*PHP正则提取图片img标记中的任意属性*/ $str = '<center><img src="/uploads/images/20100516000.jpg" height="120" width="120"><br />PHP正则提取或更改图片img标记中的任意属性<......

mdoo
32分钟前
2
0
一个简单的系统监控脚本

一个简单的系统信息监控脚本 #!/bin/bash# DATE:20181018# System monitor by Kxvzinterval=5while :doecho '==========================================================...

Kxvz
35分钟前
2
0
七牛云助你度寒冬 | 每天 10:24, 新用户抢全额免单

近年来,中美贸易战、股市暴跌、房地产变天、人民币贬值等等,企业艰难生存于冰川夹缝之中,融资发展难上加难。 凛冬将至, 七牛云特此推出免单好礼,为新用户(2018 年 10 月 10 日后新注册...

七牛云
36分钟前
1
0
Echarts X轴刻度标签换行显示

xAxis: [ { 'type':'category', splitLine: {show: false}, axisLabel: { show: true,//是否显示 interval:0,//强制显示 ......

郭周园
41分钟前
2
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部