转载请注明出处:http://blog.csdn.net/zhaokaiqiang1992

2014已经远去,2015年的目标很简单,就是继续熟悉Android的上层API,虽然偶尔会为了某个问题去研究下FrameWork的代码,但是对于我们这种新手来说,只有对上层的API用的熟练了,才能更好的往下研究原理。所以,今年的任务就是继续学习和研究Android的上层API的使用,顺便写一篇毕业论文,然后毕个业。

OK,从这篇开始,咱们就开始【凯子哥带你夯实应用层】系列,如果你有想要实现的界面效果,或者是有一些开发中的疑问,请私信我,如果我觉得比较好的话,会自己实现一下,然后写blog和大家分享实现思路。

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

在实现之前,我们先根据上面的动画效果,研究下需求,因为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() {@Overridepublic 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.MyScrollViewandroid:id="@+id/scroller"android:layout_width="match_parent"android:layout_height="wrap_content"><TextViewandroid: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><FrameLayoutandroid: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"><ImageViewandroid:id="@+id/img_author"android:layout_width="match_parent"android:layout_height="80dp"android:scaleType="fitXY"android:src="@drawable/bg_author"/><TextViewandroid: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"/><ImageViewandroid:id="@+id/img_bar"android:layout_width="match_parent"android:layout_height="55dp"android:scaleType="fitXY"android:src="@drawable/bg_actionbar"/></FrameLayout><ImageViewandroid: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() {@Overridepublic 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;}@Overridepublic void onBottom() {if (isToolsHide) {showTools();}}@Overridepublic 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,这篇文章就到这里,如果你有疑问或者是建议,都可以评论或者是私信。

下载地址:https://github.com/ZhaoKaiQiang/ZhiHuDetailDemo

----------------------------------------分割线---------------------------------------------

咱们上面咱们是用ScrollView代替的WebView,有的同学可能问了,如果也要用WebView咋办呢?

这里给出两种方案。

第一种,JS和Android交互,用JS传递网页的移动位置,没试过,只是一个思路,不知道能不能实现。

第二种,就是集成WebView,他也有onScrollChanged(),剩下的就不用多说了~

----------------------------------------分割线---------------------------------------------

昨天又体验了下,发现知乎还有个功能,就是点击屏幕,可以实现上部布局和Tools布局的隐藏和显示,这个其实也不难,我们可以自己定义一个GestureDetector,然后实现Gesturedetector.SimpleOnGestureListener,onDown返回true,然后在onSingleTapConfirmed里面调用hideXXX和showXXX即可,仅提供思路,剩下的就自己去做吧,你一定可以的!

【凯子哥带你夯实应用层】都说“知乎”逼格高,我们来实现“知乎”回答详情页动画效果相关推荐

  1. 【凯子哥带你夯实应用层】Android的Google官方设计指南(上)

    Android 设计规范 时间 2015.3.2 版本 V1.0 翻译 杨鹏 整理 赵凯强 本文章是我公司一个大牛之前的公司同事翻译的Android的Google官方设计指导,经过我整理而成,分享给大 ...

  2. 【凯子哥带你夯实应用层】使用ActionMode实现有删除动画的多选删除功能

    转载请注明出处:http://blog.csdn.net/zhaokaiqiang1992 ActionMode是3.0之后,官方推荐的一种上下文菜单的实现方式,在之前一直用的是Context Men ...

  3. 【凯子哥带你学Android】Andriod性能优化之列表卡顿——以“简书”APP为例

    这几天闲得无聊,就打开手机上的开发者模式里面的"GPU过度绘制"功能,看看别家的App做的咋样,然后很偶然的打开了"简书",然后就被它的过度绘制惊呆了,于是写了 ...

  4. 【凯子哥带你学Framework】Activity界面显示全解析

    前几天凯子哥写的Framework层的解析文章<Activity启动过程全解析>,反响还不错,这说明"写让大家都能看懂的Framework解析文章"的思想是基本正确的. ...

  5. 【凯子哥带你做高仿】“煎蛋”Android版的高仿及优化(二)——大图显示模式、评论“盖楼”效果实现详解

    转载请注明出处:http://blog.csdn.net/zhaokaiqiang1992 在前一篇文章中,我们学习了如何进行逆向工程和TcpDump进行抓包,获取我们的数据接口,那么有了数据之后,我 ...

  6. 【凯子哥带你做高仿】“煎蛋”Android版的高仿及优化(三)——使用GreenDao实现本地Sqlite缓存

    到目前为止,煎蛋的Android项目算是告一段落了,功能基本都已完成,那么今天,我就介绍一下在煎蛋这个项目里,是怎么完成数据缓存功能的.想看代码的请戳煎蛋项目的GITHUB地址 转载请注明出处:htt ...

  7. jdbc代码_凯哥带你从零学大数据系列之数据库篇---第三章:JDBC基础

    温馨提示:如果想学扎实,一定要从头开始看凯哥的一系列文章(凯哥带你从零学大数据系列),千万不要从中间的某个部分开始看,知识前后是有很大关联,否则学习效果会打折扣. 系列文章第一篇是拥抱大数据:凯哥带你 ...

  8. ios 获取一个枚举的所有值_凯哥带你从零学大数据系列之Java篇---第十一章:枚举...

    温馨提示:如果想学扎实,一定要从头开始看凯哥的一系列文章(凯哥带你从零学大数据系列),千万不要从中间的某个部分开始看,知识前后是有很大关联,否则学习效果会打折扣. 系列文章第一篇是拥抱大数据:凯哥带你 ...

  9. java lambda表达式_凯哥带你从零学大数据系列之Java篇---第二十二章:Lambda表达式...

    温馨提示:如果想学扎实,一定要从头开始看凯哥的一系列文章(凯哥带你从零学大数据系列),千万不要从中间的某个部分开始看,知识前后是有很大关联,否则学习效果会打折扣. 系列文章第一篇是拥抱大数据:凯哥带你 ...

最新文章

  1. 英文书《用unreal来学习c++》_用机器学习来提升你的用户增长:第四步,客户流失预测
  2. 机器字长,指令字长,数据子长,MDR
  3. oracle对象类型的member方法
  4. 如何成为领袖? 学习任正非小沃森郭士纳
  5. c++ 显示图片_飞利浦256P1FR显示器一线直连MacBook使用体验分享
  6. 【note】Swift初见笔记
  7. C#如何生成随机不重复的数字
  8. StreamInsight 编程模型之适配器
  9. C# 从入门到精通 学习笔记1 第2章 使用变量、操作符和表达式
  10. 怎么快速学习App后台开发
  11. 微型计算机系统的五大组成部分,计算机系统的组成计算机硬件的五大部分是什么...
  12. Excel二次开发学习笔记——获取某列最后一个非空单元格的行号
  13. android中android:wight详解
  14. 小米扫地机器人粉尘盒_小米扫地机器人尘盒怎么打开
  15. linux 桌面 修复工具下载,恢复ubuntu20.04默认桌面管理器
  16. matplotlib之箱型图
  17. 基于STM32F030驱动MQ7一氧化碳传感器
  18. python字典表示摩尔斯电码_Python中的摩尔斯电码翻译器
  19. RFC系列协议--rfc2373--IP Version 6 Addressing Architecture
  20. 从零开始编译OpenWrt固件

热门文章

  1. 上海无居住证120积分随迁子女如何求学(中考)
  2. 手把手教你学51单片机-定时器与数码管
  3. CSS学习笔记2字体属性和文本属性
  4. AlsaLib基本使用(基于1.2.4版本)
  5. 本题要求实现一个函数,对给定平面任意两点坐标(x 1​ ,y 1​ )和(x 2​ ,y 2​ ),求这两点之间的距离。
  6. 麒麟OS 强制设置短密码
  7. Windows Server系统使用Windows图片查看器
  8. Qt 按钮控件虚线框
  9. 如何让开源项目成为你的良师益友
  10. 【Rust日报】 2019-06-16:用 Rust, Haskell, C++, Python, Scala 和 OCaml 实现同一个工程的比较...