热门标签 | HotTags
当前位置:  开发笔记 > Android > 正文

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

这篇文章主要介绍了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里面,所以,在首次加载的时候,下面的文本在开头需要留出一定的间隔,防止被遮挡,当上面的布局隐藏之后,就没有问题了。
    在简单分析之后,我再给出实现的布局的代码

 
 
  
 
 
 

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

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


推荐阅读
  • 资源:吊炸天!74款APP完整源码!android界面中点击输入框时弹出输入法如果输入框在底部会出现输入法遮挡输入内容的问题解决办法设置activity的windowsoftinpu ... [详细]
  • android listview OnItemClickListener失效原因
    最近在做listview时发现OnItemClickListener失效的问题,经过查找发现是因为button的原因。不仅listitem中存在button会影响OnItemClickListener事件的失效,还会导致单击后listview每个item的背景改变,使得item中的所有有关焦点的事件都失效。本文给出了一个范例来说明这种情况,并提供了解决方法。 ... [详细]
  • 本文讨论了Alink回归预测的不完善问题,指出目前主要针对Python做案例,对其他语言支持不足。同时介绍了pom.xml文件的基本结构和使用方法,以及Maven的相关知识。最后,对Alink回归预测的未来发展提出了期待。 ... [详细]
  • 本文讲述了如何通过代码在Android中更改Recycler视图项的背景颜色。通过在onBindViewHolder方法中设置条件判断,可以实现根据条件改变背景颜色的效果。同时,还介绍了如何修改底部边框颜色以及提供了RecyclerView Fragment layout.xml和项目布局文件的示例代码。 ... [详细]
  • 在说Hibernate映射前,我们先来了解下对象关系映射ORM。ORM的实现思想就是将关系数据库中表的数据映射成对象,以对象的形式展现。这样开发人员就可以把对数据库的操作转化为对 ... [详细]
  • 本文介绍了在SpringBoot中集成thymeleaf前端模版的配置步骤,包括在application.properties配置文件中添加thymeleaf的配置信息,引入thymeleaf的jar包,以及创建PageController并添加index方法。 ... [详细]
  • XML介绍与使用的概述及标签规则
    本文介绍了XML的基本概念和用途,包括XML的可扩展性和标签的自定义特性。同时还详细解释了XML标签的规则,包括标签的尖括号和合法标识符的组成,标签必须成对出现的原则以及特殊标签的使用方法。通过本文的阅读,读者可以对XML的基本知识有一个全面的了解。 ... [详细]
  • Java验证码——kaptcha的使用配置及样式
    本文介绍了如何使用kaptcha库来实现Java验证码的配置和样式设置,包括pom.xml的依赖配置和web.xml中servlet的配置。 ... [详细]
  • Android系统移植与调试之如何修改Android设备状态条上音量加减键在横竖屏切换的时候的显示于隐藏
    本文介绍了如何修改Android设备状态条上音量加减键在横竖屏切换时的显示与隐藏。通过修改系统文件system_bar.xml实现了该功能,并分享了解决思路和经验。 ... [详细]
  • 本文介绍了Android 7的学习笔记总结,包括最新的移动架构视频、大厂安卓面试真题和项目实战源码讲义。同时还分享了开源的完整内容,并提醒读者在使用FileProvider适配时要注意不同模块的AndroidManfiest.xml中配置的xml文件名必须不同,否则会出现问题。 ... [详细]
  • Android开发实现的计时器功能示例
    本文分享了Android开发实现的计时器功能示例,包括效果图、布局和按钮的使用。通过使用Chronometer控件,可以实现计时器功能。该示例适用于Android平台,供开发者参考。 ... [详细]
  • 本文是关于自学Android的笔记,包括查看类的源码的方法,活动注册的必要性以及布局练习的重要性。通过学习本文,读者可以了解到在自学Android过程中的一些关键点和注意事项。 ... [详细]
  • 本文介绍了在Mac上安装Xamarin并使用Windows上的VS开发iOS app的方法,包括所需的安装环境和软件,以及使用Xamarin.iOS进行开发的步骤。通过这种方法,即使没有Mac或者安装苹果系统,程序员们也能轻松开发iOS app。 ... [详细]
  • 我收到这个错误.我怎么能在我的情况下解决这个问题?Bitmapcannotberesolvedtoatype发生错误的行publicvoidonPageStart ... [详细]
  • 前言作为一个移动端初学者、爱好者,能使用前端技术开发原生游戏一直是一件渴望而不可及的事情,暂且不说游戏逻辑的复杂度,算法的健壮性ÿ ... [详细]
author-avatar
几杯茶轶事
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有