废话不多说,咱们第一篇文章就是模仿“知乎”的回答详情页的动画效果,先上个原版的效果图,咱们就是要做出这个效果
在实现之前,我们先根据上面的动画效果,研究下需求,因为gif帧数有限,所以不是很连贯,推荐你直接下载一个知乎,找到这个界面自己玩玩
☞当文章往上移动到一定位置之后,最上面的标题栏bar和问题布局title是会隐藏的,回答者author布局不会隐藏
☞当文章往下移动移动到一定位置之后,原先隐藏的标题栏bar和问题布局title会下降显示
☞当文章往上移动的时候,下部隐藏的tools布局会上升显示
☞当文章往下移动的时候,如果tools布局是显示的,则隐藏
☞当标题栏bar和问题布局title下降显示的时候,title是从bar的下面出来的,有个遮挡的效果
☞当快速滑动内容到达底部的时候,隐藏的tools会显示出来
☞当快速滑动内容到顶部的时候,隐藏的bar和title也会显示出来
不分析不知道,这样一个简单地效果,经过分析需要完成不少东西呢,那么下面根据要实现的需求,咱们分析一下解决方案。
在做这种仿界面之前,我们可以使用adt带的view hierarchy工具看一下“知乎”原生是怎么实现的
从右边的分析图可以看出,知乎的这个界面,内容用的webview,这很正常,因为用户的回答里面格式比较复杂,用webview是最好的解决方案,而标题栏是一个view,是actionbar还是自定义view呢,不得而知,下面是就是一个linearlayout包了4个togglebutton,布局很简单,我们没有webview,所以使用scrollview代替,上面的布局直接imageview了,设置个src,模拟一个布局。
其实布局很简单,咱们一个效果一个效果的来实现。
首先是下面的tools如何显示和隐藏呢?当然是用动画了!什么动画呢?能实现的有属性动画和帧动画,属性动画能够真实的改变view的属性,帧动画只是视觉上移动了,view的实际属性并不改变,这两种都可以,我们这里使用属性动画
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
|
/**
* 显示工具栏
*/
private void showtools() {
objectanimator anim = objectanimator.offloat(img_tools, "y" , img_tools.gety(),
img_tools.gety() - img_tools.getheight());
anim.setduration(time_animation);
anim.start();
istoolshide = false ;
}
/**
* 隐藏工具栏
*/
private void hidetools() {
objectanimator anim = objectanimator.offloat(img_tools, "y" , img_tools.gety(),
img_tools.gety() + img_tools.getheight());
anim.setduration(time_animation);
anim.start();
istoolshide = true ;
}
|
那么什么时候调用呢?从上面的需求分析中,我们知道,用户手指下拉的时候,tools显示,反之隐藏,那么我们就可以监听scrollview的ontouch,判断手指方向,实现动画效果的调用
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
|
mscroller.setontouchlistener( new view.ontouchlistener() {
@override
public boolean ontouch(view v, motionevent event) {
switch (event.getaction()) {
case motionevent.action_down:
lasty = event.gety();
break ;
case motionevent.action_move:
float disy = event.gety() - lasty;
//垂直方向滑动
if (math.abs(disy) > viewslop) {
//是否向上滑动
isupslide = disy < 0 ;
//实现底部tools的显示与隐藏
if (isupslide) {
if (!istoolshide)
hidetools();
} else {
if (istoolshide)
showtools();
}
}
break ;
}
return false ;
}
});
|
用变量istoolshide放置代码重复调用。
下面的tools的问题解决了,我们再看一下上面的布局动画如何来实现。上面的思路和下面一样,也是通过属性动画,完成位置的移动,移动的布局有bar、title和根布局。为什么答题人布局author不移动呢?因为根布局必须移动,否则就会挡住下面的文字内容,根布局的移动会让子布局都跟着移动,所以只移动根布局即可。
对了,为了更大范围的现实文本,“知乎”的webview是占据整个布局的,其他布局都是在根布局framelayout里面,所以,在首次加载的时候,下面的文本在开头需要留出一定的间隔,防止被遮挡,当上面的布局隐藏之后,就没有问题了。
在简单分析之后,我再给出实现的布局的代码
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
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
|
<framelayout xmlns:android= "http://schemas.android.com/apk/res/android"
android:layout_width= "match_parent"
android:layout_height= "match_parent"
android:background= "@android:color/white"
>
<com.socks.zhihudetail.myscrollview
android:id= "@+id/scroller"
android:layout_width= "match_parent"
android:layout_height= "wrap_content"
>
<textview
android:layout_width= "match_parent"
android:layout_height= "match_parent"
android:textsize= "16sp"
android:textcolor= "@android:color/black"
android:text= "@string/hello_world" />
</com.socks.zhihudetail.myscrollview>
<framelayout
android:id= "@+id/ll_top"
android:layout_width= "match_parent"
android:layout_height= "wrap_content"
android:background= "@android:color/white"
android:orientation= "vertical"
android:layout_gravity= "top" >
<imageview
android:id= "@+id/img_author"
android:layout_width= "match_parent"
android:layout_height= "80dp"
android:scaletype= "fitxy"
android:src= "@drawable/bg_author" />
<textview
android:id= "@+id/tv_title"
android:layout_width= "match_parent"
android:layout_height= "wrap_content"
android:layout_margintop= "55dp"
android:text= "为什么美国有那么多肌肉极其强大的肌肉男?"
android:textsize= "18sp"
android:background= "#dbdbdb"
android:gravity= "center|left"
android:paddingleft= "15dp"
android:paddingright= "15dp"
android:paddingtop= "5dp"
android:paddingbottom= "5dp"
android:textcolor= "@android:color/darker_gray"
/>
<imageview
android:id= "@+id/img_bar"
android:layout_width= "match_parent"
android:layout_height= "55dp"
android:scaletype= "fitxy"
android:src= "@drawable/bg_actionbar" />
</framelayout>
<imageview
android:id= "@+id/img_tools"
android:layout_width= "match_parent"
android:layout_height= "wrap_content"
android:scaletype= "fitxy"
android:layout_gravity= "bottom"
android:src= "@drawable/bg_bottom" />
</framelayout>
|
效果图如下,文本留了一些空行,保证不被遮挡。
有的同学看了上面的效果图可能会疑惑,这里为什么没有答题人的布局呢?
其实是这样的,为了模拟上部的布局显示时,title从bar下面出现的效果,所以特意这样设计的。我试过用linearlayout实现,效果也是可以实现的,但是当title往下移动显示的时候,会覆盖在bar上面,这也很好理解,linearlayout没有层次顺序,所以会遮挡。我试过view.bringtofront(),试图把bar的布局提高层次,但是这样会导致布局的紊乱,在首次加载的时候,bar会显示在最下面,是因为提高层次之后,bar的布局重新计算,所以不按照linearlayout的布局规则来了。无奈之下,换成了framelayout,但是又出现了问题,bar的高度可以设置,但是title的高度会随着文本的增加而改变,这样一来,最下面author的布局的位置就不能设置了,因为不知道距离上面多远,所以我们只能在代码里面动态的计算bar和title的高度,然后在界面加载的时候,动态的给author的布局设置margentop,保证位置的正确。
因为在oncreate里面,还没有开始view的绘制,所以得不到控件的真实高度,我们可以用下面的方法,获取这个时期的高度
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
//获取bar和title的高度,完成auther布局的margentop设置
viewtreeobserver viewtreeobserver = fl_top.getviewtreeobserver();
viewtreeobserver.addonpredrawlistener( new viewtreeobserver.onpredrawlistener() {
@override
public boolean onpredraw() {
if (!hasmeasured) {
framelayout.layoutparams layoutparams = new framelayout.layoutparams(framelayout
.layoutparams.match_parent, framelayout.layoutparams.wrap_content);
layoutparams.setmargins( 0 , img_bar.getheight() + tv_title.getheight(), 0 , 0 );
img_author.setlayoutparams(layoutparams);
hasmeasured = true ;
}
return true ;
}
});
|
获取了高度之后,我们就可以正确地设置位置了。但是,如果保证上面的布局随着我们的内容的移动,而改变现实状态呢?
经过我手动直观测试,知乎的这个界面是根据一个固定的值,来改变显示状态的,因此,我们可以监听scrollview的滑动距离,来判断。但是scrollview并没有给我们这样一个监听器,咋办?重写!
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
49
50
51
52
53
54
55
56
57
58
59
60
61
|
/**
* created by zhaokaiqiang on 15/2/26.
*/
public class myscrollview extends scrollview {
private bottomlistener bottomlistener;
private onscrolllistener scrolllistener;
public myscrollview(context context) {
this (context, null );
}
public myscrollview(context context, attributeset attrs) {
super (context, attrs);
}
protected void onscrollchanged( int l, int t, int oldl, int oldt) {
super .onscrollchanged(l, t, oldl, oldt);
if (getscrolly() + getheight() >= computeverticalscrollrange()) {
if ( null != bottomlistener) {
bottomlistener.onbottom();
}
}
if ( null != scrolllistener) {
scrolllistener.onscrollchanged(l, t, oldl, oldt);
}
}
public void setbottomlistener(bottomlistener bottomlistener) {
this .bottomlistener = bottomlistener;
}
public void setscrolllistener(onscrolllistener scrolllistener) {
this .scrolllistener = scrolllistener;
}
public interface onscrolllistener {
public void onscrollchanged( int l, int t, int oldl, int oldt);
}
public interface bottomlistener {
public void onbottom();
}
}
|
我们只需要重写onscrollchange()方法即可,在里面不光可以时时的得到位置的变化,还添加了一个bottomlistener接口来监听滑动到底部的事件,写好之后就很简单了
1
2
|
mscroller.setbottomlistener( this );
mscroller.setscrolllistener( this );
|
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
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
|
/**
* 显示上部的布局
*/
private void showtop() {
objectanimator anim1 = objectanimator.offloat(img_bar, "y" , img_bar.gety(),
0 );
anim1.setduration(time_animation);
anim1.start();
objectanimator anim2 = objectanimator.offloat(tv_title, "y" , tv_title.gety(),
img_bar.getheight());
anim2.setinterpolator( new decelerateinterpolator());
anim2.setduration(time_animation + 200 );
anim2.start();
objectanimator anim4 = objectanimator.offloat(fl_top, "y" , fl_top.gety(),
0 );
anim4.setduration(time_animation);
anim4.start();
istophide = false ;
}
/**
* 隐藏上部的布局
*/
private void hidetop() {
objectanimator anim1 = objectanimator.offloat(img_bar, "y" , 0 ,
-img_bar.getheight());
anim1.setduration(time_animation);
anim1.start();
objectanimator anim2 = objectanimator.offloat(tv_title, "y" , tv_title.gety(),
-tv_title.getheight());
anim2.setduration(time_animation);
anim2.start();
objectanimator anim4 = objectanimator.offloat(fl_top, "y" , 0 ,
-(img_bar.getheight() + tv_title.getheight()));
anim4.setduration(time_animation);
anim4.start();
istophide = true ;
}
@override
public void onbottom() {
if (istoolshide) {
showtools();
}
}
@override
public void onscrollchanged( int l, int t, int oldl, int oldt) {
if (t <= dp2px(top_distance_y) && istophide && isanimationfinish) {
showtop();
log.d(tag, "显示" );
} else if (t > dp2px(top_distance_y) && !istophide && isanimationfinish) {
hidetop();
log.d(tag, "隐藏" );
}
}
|
我们只需要根据当前的位置,来实现布局的显示和隐藏就可以啦!
ok,这篇文章就到这里,希望对大家的学习有所帮助。