先来看一下上面这张图的效果。
这个是新浪微博的一个页面,整体布局大致分了三块:正文内容、转发评论赞的数字条、评论列表
其中数字条是可以跟着ScrollView一起滑动,但在滑到最顶部时固定在最上面,而下面的评论内容可以继续滑动。
这种效果还是挺赞的,但一开始没有什么思路,所以就去搜了下相关技术代码,一下就恍然大悟!原来是自己想复杂了,其实原理很简单!
下面是自己实现的效果图:
实现原理:
当滚动条划过头部时,把需要固定的头部从父布局中移除,然后添加到最外层布局的顶部。
当滚动条返回时,又把最外层的头部移除,然后重新添加到原来的父布局里面。
整个实现代码,不算上布局,也就100行左右。
详细实现逻辑:
首先建一个自定义View叫MyHoveringScrollView继承自FrameLayout,在布局里MyHoveringScrollView处于最外层。由于FrameLayout本身是不支持滚动条的,所以在FrameLayout内部有一个自定义的ScrollView。
在初始化的时候,通过getChildAt(0)把子布局拿到,然后清空整个布局,然后实例化一个自己的ScrollView,把之前拿到的子布局添加到ScrollView里面,
最后把ScrollView添加到MyHoveringScrollView里面。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
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传进来:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
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方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
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);
}
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
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就是头部应该取消固定,还原到原来父布局的时候
至此,功能就实现了!
怎么使用呢?首先先写布局:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
|
<
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:
1
2
|
view_hover = (MyHoveringScrollView) findViewById(R.id.view_hover);
view_hover.setTopView(R.id.top);
|
两句话就实现了固定头部的效果。
来自:http://my.oschina.net/Hideeee/blog/500933