模仿Airbnb的悬浮搜索框动画
2015-08-21 14:11
465 查看
观察Airbnb搜索框动画的原理
先看看airbnb的效果把。看了几遍,发现是这么一个原理:
最初搜索框是展开的
稍微向下滑动一点,搜索框收起
当搜索框已经收起后再向下滑,搜索框一直保持收起状态
在向上滑动时,只有滑动到顶部,搜索框才展开
探索实现方法
觉得这个搜索框的展开与收起跟顶部有着很大的关系,到达顶部才会展开,离开顶部就收起。查阅Api发现,Listview OnScrollListener onScroll中有这几个参数:
/** * Callback method to be invoked when the list or grid has been scrolled. This will be * called after the scroll has completed * @param view The view whose scroll state is being reported * @param firstVisibleItem the index of the first visible cell (ignore if * visibleItemCount == 0) * @param visibleItemCount the number of visible cells * @param totalItemCount the number of items in the list adaptor */ public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount);
firstVisibleItem这个参数就可以判断当前第一个显示的item是不是列表中第一个item。
而RecyclerView LinearLayoutManager中有这两个方法:
public int findFirstVisibleItemPosition() { View child = this.findOneVisibleChild(0, this.getChildCount(), false, true); return child == null?-1:this.getPosition(child); } public int findFirstCompletelyVisibleItemPosition() { View child = this.findOneVisibleChild(0, this.getChildCount(), true, false); return child == null?-1:this.getPosition(child); }
所以可以肯定的是,不管是Listview还是RecyclerView,都能够很容易的判断当前是否在列表的顶部。
实现过程
了解了原理,找到啦响应的API,下面就是实现了。鉴于大家现在使用更多的是RecyclerView,所以我就以RecycleView为例来实现这个功能。
判断何时收起展开
很简单,无非就是一个列表,实现了OnScrollListener,在onScrolled中判断当前显示的item中的第一个,是不是就是列表中的第一个,如果是,表明已经到达了顶部,展开搜索框;如果不是,表明离开了顶部,收起搜索框。isExpand = true; mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() { @Override public void onScrollStateChanged(RecyclerView recyclerView, int newState) { super.onScrollStateChanged(recyclerView, newState); } @Override public void onScrolled(RecyclerView recyclerView, int dx, int dy) { super.onScrolled(recyclerView, dx, dy); int firstPosition = mLayoutManager.findFirstCompletelyVisibleItemPosition(); if (firstPosition != 0 && isExpand) { search.updateShow(false); isExpand = false; } if (firstPosition == 0 && !isExpand) { search.updateShow(true); isExpand = true; } } });
这里增加了一个isExpand标志位,用处在于当搜索框已经收起了,而用户继续向下滑动时,不再收起搜索框,反之向上滑也是一样,只是不再重复而已。
执行收起展开
写到这里时发现一个问题ios上的airbnb每次收起搜索框时:
从一个圆角矩形变成弧形边角的长柱形,然后长柱形水平缩短直到成为一个圆形。
ios上的airbnb每次展开搜索框时:
先从一个圆形变成正方形,然后正方形水平拉伸,成为一个圆角矩形。
从一个圆形渐变成为一个正方形,从一个正方形,渐变成为一个圆,这确实想不到好的办法。
跟设计师商量之后,改成了不用圆角矩形了,把圆角边直接改成弧形。
这样就靠一张9patch图就可以实现展开跟收起的效果,只需要做水平方向的scaleX变换就好。
收起展开遇到的问题
然而跟想象的不一样虽然我指定了9patch的拉伸区域,但是发现根本跟你预想的两码事,你指定的9patch只能是view静态的一种变化,而这种动态的变化确实没有办法指定拉伸哪部分,或者是我不知道改用什么办法。
后来就打算使用障眼法了,一个圆形,一个圆柱形
最初时,圆形隐藏状态,圆柱形初始化为展开状态,使用9patch。
在收起时,渐渐的显示出圆形,scaleX圆柱形由大变小,然后隐藏圆柱形(因为圆柱形已经变形,只能隐藏掉)。
在展开时,隐藏圆形,scaleX圆柱形由小变大,由于圆柱形原本就是展开的,现在由收起恢复到展开,不会变形。
实现
private void expandSearch() { circle.setVisibility(View.VISIBLE); ObjectAnimator anim1 = ObjectAnimator.ofFloat(circle, "alpha", 1f, 0f); ObjectAnimator anim2 = ObjectAnimator.ofFloat(round, "alpha", 0f, 1f); ObjectAnimator anim3 = ObjectAnimator.ofFloat(round, "scaleX", scale, 1f); round.setPivotX(0); AnimatorSet animSet2= new AnimatorSet(); animSet2.play(anim1).with(anim2).with(anim3); animSet2.setDuration(100); animSet2.start(); } private void closeSearch() { ObjectAnimator anim2 = ObjectAnimator.ofFloat(round, "scaleX", 1f, scale); ObjectAnimator anim3 = ObjectAnimator.ofFloat(circle, "alpha", 0f, 1f); circle.setVisibility(View.VISIBLE); ObjectAnimator anim4 = ObjectAnimator.ofFloat(round, "alpha", 1f, 0f); round.setPivotX(0); round.setPivotY(round.getHeight() / 2); AnimatorSet animSet1 = new AnimatorSet(); animSet1.play(anim2).with(anim3).with(anim4); animSet1.setDuration(100); animSet1.start(); }
就不贴出来全部的代码了,一会给出Github地址。
效果
注意
如果你使用的ListView,并且是可以下拉刷新的Listview,那么你要注意了,因为你下拉刷新如果使用的是addHeaderView,然后通过控制header的padding的形式,那么你会发现在下拉刷新时,这个搜索框会不停的收起展开。为什么会这样呢?
其实想一下就能明白,在可以下拉刷新的Listvew中,header在一般情况下是隐藏状态,那么当前列表的顶部就是列表数据的第一个item,然而一旦下拉,header露出来后,列表的顶部就变成header了,所以就会出现问题。
只要需要在下拉刷新时,屏蔽掉搜索框的变化就可以了。
源码下载地址:
https://github.com/nicewarm/AirbnbSearchAnimation/tree/master
希望能帮到你。
相关文章推荐
- pygal and matplotlib(again)
- org.hibernate.LazyInitializationException: failed to lazily initialize a collection..的解决方案
- Rails - 将日期格式字符串转换为秒整数
- 什么是领域驱动设计(Domain Driven Design)?
- raid
- TIME_WAIT 太多的解决办法
- GrailsDispatcherServlet中的doDispatch方法
- 安装apk时出现错误Failure [INSTALL_FAILED_DEXOPT]问题解决的方法
- hdu1023 Train Problem II
- Container With Most Water
- LightOJ-1138 Trailing Zeroes (III) (二分搜索)
- 磁盘阵列RAID理解
- time_wait的内核调优
- 你知道RAID中的Write Hole问题吗?
- 你知道RAID中的Write Hole问题吗?
- UVA 10976 Fractions Again?!
- Trailing Zeroes (III) -;lightoj 1138
- 无法解析的外部符号 _main,该符号在函数 ___tmainCRTStartup 中被引用
- HUST team contest #E A Mountain Road||poj 3846 (dp)
- 阻塞套接字返回EAGAIN