Android模仿知乎的回答详情页的动画效果
作者:一叶飘舟 发布时间:2022-12-25 06:08:57
废话不多说,咱们第一篇文章就是模仿“知乎”的回答详情页的动画效果,先上个原版的效果图,咱们就是要做出这个效果
在实现之前,我们先根据上面的动画效果,研究下需求,因为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的实际属性并不改变,这两种都可以,我们这里使用属性动画
/**
* 显示工具栏
*/
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,判断手指方向,实现动画效果的调用
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里面,所以,在首次加载的时候,下面的文本在开头需要留出一定的间隔,防止被遮挡,当上面的布局隐藏之后,就没有问题了。
在简单分析之后,我再给出实现的布局的代码
<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的绘制,所以得不到控件的真实高度,我们可以用下面的方法,获取这个时期的高度
//获取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并没有给我们这样一个 * ,咋办?重写!
/**
* 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接口来监听滑动到底部的事件,写好之后就很简单了
mScroller.setBottomListener(this);
mScroller.setScrollListener(this);
/**
* 显示上部的布局
*/
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,这篇文章就到这里,希望对大家的学习有所帮助。


猜你喜欢
- 1.Object类的equals()方法:比较两个对象是否是同一个对象,equals() 方法比较两个对象,是判断两个对象引用指向的是同一个
- 冒泡排序算法演示图:public static void bubbleSort(int[] array) { &
- 我们都知道Android Studio用起来很棒,其中布局预览更棒。我们在调UI的时候基本是需要实时预览来看效果的,在Android Stu
- 本文主要和大家分享介绍了关于Java JDK * 使用的相关内容,分享出来供大家参考学习,下面来一起看看详细的介绍:前言代理是一种常用的
- 一、引言在移动应用程序的架构设计中,界面与数据即不可分割又不可混淆。在绝大部分的开发经历中,我们都是使用Fragment来进行界面编程,即使
- 前面我们讲到了Spring在进行事务逻辑织入的时候,无论是事务开始,提交或者回滚,都会触发相应的事务事件。本文首先会使用实例进行讲解Spri
- .sln:解决方案文件,为解决方案资源管理器提供显示管理文件的图形接口所需的信息。.csproj:项目文件,创建应用程序所需的引用、数据连接
- 文章描述弱水三千,我只取一瓢饮。一张动图,我只想要其中一帧。如何将一个GIF动态图分割成一帧一帧的图片?其实现在这样的工具随处可见,无论是在
- 使用区间使用 in 运算符来检测某个数字是否在指定区间内,区间格式为x..y:实例fun main(args: Array<Strin
- 在Servlet2.5中,我们要实现文件上传功能时,一般都需要借助第三方开源组件,例如Apache的commons-fileupload组件
- 随着现在分布式架构越来越盛行,在很多场景下需要使用到分布式锁。很多小伙伴对于分布式锁还不是特别了解,所以特地总结了一篇文章,让大家一文读懂分
- 本文实例讲述了Java 反射机制原理与用法。分享给大家供大家参考,具体如下:反射反射,程序员的快乐!1、什么是反射?Java反射就是在运行状
- 在java中的整数类型有四种,分别是 byte short int long 其中byte只有一个字节 0或1,在此不详细讲解。
- 一个类,有时候搞不清楚到底用成员变量还是属性。 如: 成员变量 public string
- 一、树概念及结构1.1 树的概念树是一种非线性的数据结构,它是由n(n>=0)个有限结点组成一个具有层次关系的集合。把它叫做树是因 为
- System.Collections.ArrayList类是一个特殊的数组。通过添加和删除元素,就可以动态改变数组的长度。一.优点1。支持自
- 前言在日常开发中,除了修改请求参数、设置响应header,响应body外,还有一种需求就是url重新,或者是修改url,这里简述一下怎么在z
- 1、需要引入依赖<dependency> &l
- 这几天用winform做了一个设置壁纸的小工具, 为图片添加当月的日历并设为壁纸,可以手动设置壁纸,也可以定时设置壁纸,最主要的特点是在图片
- 这篇文章主要介绍了spring boot2X Consul如何使用Feign实现服务调用,文中通过示例代码介绍的非常详细,对大家的学习或者工