Android模仿知乎的回答详情页的动画效果

作者:一叶飘舟 时间:2022-12-25 06:08:57 

 废话不多说,咱们第一篇文章就是模仿“知乎”的回答详情页的动画效果,先上个原版的效果图,咱们就是要做出这个效果

Android模仿知乎的回答详情页的动画效果

    在实现之前,我们先根据上面的动画效果,研究下需求,因为gif帧数有限,所以不是很连贯,推荐你直接下载一个知乎,找到这个界面自己玩玩
☞当文章往上移动到一定位置之后,最上面的标题栏Bar和问题布局Title是会隐藏的,回答者Author布局不会隐藏
☞当文章往下移动移动到一定位置之后,原先隐藏的标题栏Bar和问题布局Title会下降显示
☞当文章往上移动的时候,下部隐藏的Tools布局会上升显示
☞当文章往下移动的时候,如果Tools布局是显示的,则隐藏
☞当标题栏Bar和问题布局Title下降显示的时候,Title是从Bar的下面出来的,有个遮挡的效果
☞当快速滑动内容到达底部的时候,隐藏的Tools会显示出来
☞当快速滑动内容到顶部的时候,隐藏的Bar和Title也会显示出来

    不分析不知道,这样一个简单地效果,经过分析需要完成不少东西呢,那么下面根据要实现的需求,咱们分析一下解决方案。
    在做这种仿界面之前,我们可以使用ADT带的View Hierarchy工具看一下“知乎”原生是怎么实现的

Android模仿知乎的回答详情页的动画效果

    从右边的分析图可以看出,知乎的这个界面,内容用的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>

    效果图如下,文本留了一些空行,保证不被遮挡。

Android模仿知乎的回答详情页的动画效果

    有的同学看了上面的效果图可能会疑惑,这里为什么没有答题人的布局呢?
    其实是这样的,为了模拟上部的布局显示时,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,这篇文章就到这里,希望对大家的学习有所帮助。

标签:Android,知乎,详情页
0
投稿

猜你喜欢

  • 通过Java实现在Word中创建可填充表单

    2023-08-05 21:11:40
  • C++类中六个默认的成员函数详解

    2022-11-25 10:46:30
  • spring cloud整合ribbon问题及解决方案

    2023-07-25 04:24:49
  • Zookeeper事务日志预分配空间解读

    2022-03-16 22:36:49
  • 利用java反射机制实现自动调用类的简单方法

    2023-11-29 15:57:22
  • Android中webview与JS交互、互调方法实例详解

    2023-04-30 19:18:27
  • Java获取任意http网页源代码的方法

    2022-07-06 01:45:37
  • Android10 启动之SystemServer源码分析

    2021-08-15 05:38:13
  • 基于mybatis-plus timestamp返回为null问题的排除

    2023-08-07 00:43:50
  • Java中关于二叉树层序遍历深入了解

    2023-07-26 07:06:09
  • Android Webview滑进出屏幕闪烁的解决方法

    2021-11-30 11:57:33
  • C#数据结构之堆栈(Stack)实例详解

    2022-12-17 21:25:37
  • 详谈Java中Object类中的方法以及finalize函数作用

    2021-05-29 07:59:25
  • Kotlin协程与并发深入全面讲解

    2022-09-11 12:28:25
  • Android启动页优化之实现应用秒开

    2021-05-27 23:51:32
  • C语言折半查找法的超详细讲解

    2022-10-26 19:33:16
  • C#使用iTextSharp封装的PDF文件操作类实例

    2023-05-16 22:26:52
  • Flutter自动路由插件auto_route使用详解

    2022-04-25 12:39:52
  • java9开始——接口中可以定义private私有方法

    2023-03-27 06:53:15
  • Android使用ViewPager实现类似laucher左右拖动效果

    2022-05-18 20:33:59
  • asp之家 软件编程 m.aspxhome.com