原文出处: http://my.oschina.net/Hideeee/blog/500933
先来看一下下面这张图的效果。
这个是新浪微博的一个页面,整体布局大致分了三块:正文内容、转发评论赞的数字条、评论列表
其中数字条是 可以跟着ScrollView一起滑动,但在滑到最顶部时固定在最上面,而下面的评论内容可以继续滑动。
这种效果还是挺赞的,但一开始没有什么思路,所以就去搜了下相关技术代码,一下就恍然大悟!原来是自己想复杂了,其实原理很简单!
下面是自己实现的效果图:
首先建一个自定义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);
两句话就实现了固定头部的效果。