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

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

在实现之前,我们先根据上面的动画效果,研究下需求,因为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,这篇文章就到这里,希望对大家的学习有所帮助。

时间: 2016-02-20

Android开屏页倒计时功能实现的详细教程

最近我司产品提出了一个很常见的需求:App 在开屏页(Splash 界面) 需要加上一个 3s 倒计时按钮,可以选择看 3s 的广告,或者点击按钮跳过广告. 一.布局实现(使用 FrameLayout 悬浮在广告的右上角,显示倒计时的 TextView 的宽高尽量不要写死,要考虑字体很多的情况!!) <FrameLayout android:id="@+id/start_skip" android:layout_width="wrap_content" and

Material Design系列之Behavior实现Android知乎首页

本博客目的:仿知乎首页向上滑动时动画隐藏Toolbar.FlocationActionButton.Tab导航,下滑时显示,如果和你的期望不同,那么你可以不需要看了,免的浪费你的宝贵时间噢. 效果预览 知乎效果: 本博客实现效果: 今天效果的源代码下载链接在文章末尾. 实现分析 这个效果其实并不难实现,但是它的用处很大,当用户手指上滑,屏幕上显示下方内容的时候,隐藏Toolbar.Tab导航.FAB来腾出更大的空间显示内容,让用户爽.简单粗暴,但这就是我们的目的. 首先就是头部的Toolbar,

Android仿知乎日报开屏页效果

先看看知乎日报开屏页的效果,非常漂亮的开屏效果 ezgif.com-resize (2).gif 然后我来一个 ezgif.com-resize (1).gif 也不错~感觉可以以假乱真了~ 很简单,直接开始. 实现这个效果先制定个三步走策略 底部布局上滑展示. 画一个知弧. 显示图片 底部布局上滑展示 直接上代码吧,属性动画基本使用 private void startAnimation() { //位移动画,从底部滑出,Y方向移动,mHeight是底部布局的高度 ObjectAnimator

Android仿淘宝商品详情页效果

本文实例为大家分享了Android仿淘宝商品详情页的具体代码,供大家参考,具体内容如下 Demo地址:先上效果图 效果就是上面图片的效果 接下来看看如何实现 首先我们来看下布局文件 <LinearLayout android:id="@+id/header" android:layout_width="match_parent" android:layout_height="72dp" android:paddingTop="24

Android仿知乎悬浮功能按钮FloatingActionButton效果

前段时间在看属性动画,恰巧这个按钮的效果可以用属性动画实现,所以就来实践实践.效果基本出来了,大家可以自己去完善. 首先看一下效果图: 我们看到点击FloatingActionButton后会展开一些item,然后会有一个蒙板效果,这都是这个View的功能.那么这整个View肯定是个ViewGroup,我们一部分一部分来看. 首先是这个最小的Tag: 这个Tag带文字,可以是一个TextView,但为了美观,我们使用CardView,CardView是一个FrameLayout,我们要让它具有显

Android仿硬币转动微信红包动画效果

项目需要研究了一下微信红包动画,即硬币转动的效果,原理其实就是三张不同角度的图片利用AnimationDrawable帧动画进行播放,在参考了案例之后,给自己记录一下完成的过程. 1,在XML文件中定义动画: 步骤如下: ①新建 Android 项目 ②在drawable目录中新建一个anim.xml(注意文件名小写) <?xml version="1.0" encoding="utf-8"?> <animation-list xmlns:andr

基于jQuery和Bootstrap框架实现仿知乎前端动态列表效果

最近基于jQuery和Bootstrap框架实现了一个仿知乎动态列表的前端效果,基本实现了和知乎动态列表相同的效果.如下: 1.基本列表项 2.列表项全文展开.折叠(图中为展开第一项) 3.评论项展开 4.列表数据加载(加载全部) 5.带动画效果的点赞功能 6.回复列表的hover显示功能 HTML代码如下: <!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <meta h

Android仿知乎客户端关注和取消关注的按钮点击特效实现思路详解

先说明一下,项目代码已上传至github,不想看长篇大论的也可以先去下代码,对照代码,哪里不懂点哪里. 代码在这https://github.com/zgzczzw/ZHFollowButton 前几天发现知乎关注的点击效果确实赞,查了一下实现方式,刚好看到这个问题,花了一天时间终于把这个效果实现了,现在来回答一下,很不幸,楼上各位的答案都不全对,且听我一一道来. 首先,我先详细观察了一些知乎的效果,其中有一个很神奇的地方,如图: 注意看第二张图,这个圆形在扩散的时候,圆形底下的字还在,而且新的

Android利用悬浮按钮实现翻页效果

今天给大家分享下自己用悬浮按钮点击实现翻页效果的例子. 首先,一个按钮要实现悬浮,就要用到系统顶级窗口相关的WindowManager,WindowManager.LayoutParams.那么在AndroidManifest.xml中添加权限: <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" /> 然后,我们要对WindowManager,WindowManager.Layout

Android仿支付宝的头部伸缩动画效果

Android5.0推出的MaterialDesign库包含了处理头部工具栏的多个控件,不但允许自定义顶部导航栏,而且导航栏高度是可以伸缩的.如此一来,一方面导航栏能够放得下更多控件,另一方面在用户想看具体内容时也能腾出更多的屏幕空间. 这么说可能比较抽象,那就先来看看两张导航栏的效果图,第一张是导航栏完全展开时的界面,此时页面头部的导航栏占据了较大部分的高度: 第二张是导航栏完全收缩时的界面,此时头部导航栏只剩矮矮的一个长条. 看起来很眼熟是不是,上面的截图正是仿支付宝首页的头部效果.如果你熟

Android 仿今日头条简单的刷新效果实例代码

点击按钮,先自动进行下拉刷新,也可以手动刷新,刷新完后,最后就多一行数据.有四个选项卡. 前两天导师要求做一个给本科学生预定机房座位的app,出发点来自这里.做着做着遇到很多问题,都解决了.这个效果感觉还不错,整理一下. MainActivity package com.example.fragmentmytest; import android.content.DialogInterface; import android.graphics.Color; import android.os.B

Android使用BottomTabBar实现底部导航页效果

1. 导依赖 compile 'com.hjm:BottomTabBar:1.1.1' 2. 在所实现的XML中定义一下该控件 <com.hjm.bottomtabbar.BottomTabBar android:id="@+id/bottom_tab_bar" android:layout_width="match_parent" android:layout_height="match_parent" > </com.hjm