安卓App里,搜索是一个常用功能,是开发中高频需求。借着在公司技术分享的契机,我们一起聊聊那些年我们开发过的搜索栏。
本文分为两大部分:1. 搜索栏的产品逻辑 2. 搜索交互的代码实现。(如果对搜索栏产品逻辑不感兴趣,可以直接跳到第二部分阅读)
产品经理篇
没有系统的论述搜索栏的交互体系和生命周期,而是尝试通过回答以下三个问题来表达我对搜索栏的理解
什么时候需要搜索栏
对于用户来说,用户喜欢点击、长按、滑动、拖拽,而不喜欢输入;搜索需要用户打字输入。另一方面,并不是任何阶段的App都需要搜索栏
当列表不够用时
搜索是为了提供用户快速获得他们最想要的数据内容,当总的数据量很少时,直接列表展示所有数据即可。
比如一款旅行类的App,提供各个城市的景点,美食,商城的攻略和介绍。最初,这家初创的互联网公司只提供国内城市, 用户想要查看的各个城市的景点,直接通过滑动城市列表找寻
如果列表展示不够了,考虑在App里增加搜索栏。
当一种元素分类不够用时
随着这家旅游公司的成长,它能提供的国内城市由最初的十个不到,增多到的几十个,列表展示这几十个城市已经不合适了,这时候需要对城市进行分类。比如按城市名首字的字母表排序分类,比如按城市所在区域分类。通过分类,每个类别下只有若干个城市名,用户就能快速找到想要的数据。
如果一种分类展示不够用了,考虑在App里增加搜索栏
多种维度分类不够用时
这家旅游公司发展迅速,不仅能提供国内的城市,还提供国外城市,比如美国,欧洲等旅游城市,城市名增加到了上千个,这时候需要从之前的一种分类变成多种维度分类。按区域划分变成按五大洲分类,每个州按国家二级分类,每个国家按城市首字字母表三级分类。
如果多级分类不够用了,考虑在App里增加搜索栏
用户搜索时都在想什么
搜索速度
用户输入关键词查询,能接受的等待时间是有一个范围的,当超过这个范围,用户就会明显感受到等待,这个时间大体为一秒。另一方面,在搜索的时候吸引用户注意力能让用户的时间感知降低,比如引入加载的动画,立刻展示预置的通用图案和内容等
查看关心的信息
用户之所以搜索,就是想找到自己想要的搜索内容。这个搜索结果的构成,可以根据App的特性和下一个交互流程来决定。
还是以上文提到的旅游App为例,用户搜索城市,想要知道这个城市好玩的景点,好吃的美食,搜索的结果就需要体现这个城市的景点和美食。这家旅行App的的盈利方式之一是提供车票预订服务,那么搜索结果可以提供跳转到去往该城市的机票或火车票的下单页面。
搜索结束,用户接下来要做什么
一旦搜索结束,就意味着下一个交互流程的开始。给用户什么新的交互流程,根据两个方面吧:
延续搜索交互
分析App运营数据,分析用户高频的行为,用户搜索的心理活动,对于搜索结果,用户希望获得什么内容。通常是支持用户点击进入详情。比如城市名搜索完,点击进入城市详情页,详细展示该城市的景点和美食
提供新的流程
这部分需要结合App的产品定位和业务需求。
依然拿上文那家旅游公司为例,该公司后期提供了车票下单服务,提供用户和当地城市导游一对一聊天提问的服务。在App里,底栏Tab有四个:“城市”,“车票”,“聊天”,“我的”
这时候搜索到的城市结果展示,就需要提供点击跳转到该城市的车票下单页面,以及点击进入和当地导游聊天的页面
参考资料
程序员篇
有了搜索栏产品经理篇所述的认识,开发搜索栏的意义和方向和产品经理感同身受,对UI交互要求有了共鸣
Gmail搜索交互效果
谷歌邮箱App的搜索页交互效果
上面是谷歌邮箱App搜索的交互效果,如何实现这个UI效果?
实现这个效果,有三个步骤
有两层标题栏,前层是一个toolbar,背后的自己写的布局
当点击前层搜索,让后层的布局显示,前层的布局不显示
实施搜索动画,让背后的布局淡入显示出来,返回时则是上述的逆过程
步骤一
toolbar的使用有规范,自制的布局这里也不详诉了
步骤二
如果两个布局,一个显示,一个隐藏,通常会想到setVisible()方法来控制,这里提供另一种思路:View对象里,有一个方法bringToFront(),能改变子View在父容器位置顺序,改变了位置,就能改变前景的显示了。API文档对bringToFront的介绍如下:
bringToFront() Change the view's z order in the tree, so it's on top of other sibling views.
步骤三
上图看到,点击搜索图标后,标题栏和状态栏从红色变成了白色,这里涉及到如何代码改变状态栏,淡入和后退键标题栏谈出用到了属性动画
核心代码如下:
private void reactionToClickSearchAction() { View childView = mRevealFrameLayout.getChildAt(0); childView.bringToFront(); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); getWindow().addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); getWindow().setStatusBarColor(getResources().getColor(R.color.status_bar_gray)); } ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(mLlSearchBar, "alpha", 0, 1); objectAnimator.setDuration(300).setInterpolator(new FastOutSlowInInterpolator()); objectAnimator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { super.onAnimationEnd(animation); mIsShowSearch = true; mEdSearch.requestFocus(); KeyboardUtils.showSoftInput(mEdSearch, GMailMainActivity.this); } }); objectAnimator.start(); }@Overridepublic void onBackPressed() { if (mIsShowSearch) { mEdSearch.clearFocus(); KeyboardUtils.hideSoftInput(mEdSearch, this); ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(mLlSearchBar, "alpha", 1, 0); objectAnimator.setDuration(200).setInterpolator(new FastOutSlowInInterpolator()); objectAnimator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { super.onAnimationEnd(animation); View childView = mRevealFrameLayout.getChildAt(0); childView.bringToFront(); } }); objectAnimator.start(); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); getWindow().addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); getWindow().setStatusBarColor(getResources().getColor(R.color.colorPrimaryDark)); } mIsShowSearch = false; return; } super.onBackPressed(); }
Google Play搜索交互效果
Google Play搜索页
上面是Google Play子目录页搜索的交互效果,这个交互效果怎么实现呢?
会发现它和Gmail的交互很像,不同的之处有两点:
点击搜索的动画不同,Google Play的动画不是淡入而是像水波纹一样展开的
状态栏颜色不改变
如何让一个布局水波纹似地展开呢?
从API 21 android5.0 棒棒糖版本开始,安卓提供了一个方法ViewAnimationUtils.createCircularReveal()
static Animator createCircularReveal(View view, int centerX, int centerY, float startRadius, float endRadius)Returns an Animator which can animate a clipping circle.
那么在5.0以下的版本怎么办,除了自己实现这种效果外,github上已经有人开源了ozodrukh/CircularReveal
核心代码如下:
/** * 点击搜索 */private void reactionToClickSearchAction() { mShowSearchToolbar = true; View childView = mRevealFrameLayout.getChildAt(0); childView.setVisibility(View.VISIBLE); childView.bringToFront(); int centerX = childView.getRight(); int centerY = childView.getBottom() / 2; Animator circularReveal = ViewAnimationUtils.createCircularReveal(childView, centerX, centerY, 0, childView.getWidth()); circularReveal.setDuration(300).setInterpolator(new LinearInterpolator()); circularReveal.start(); circularReveal.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { super.onAnimationEnd(animation); reactionToCover(true); mEdSearch.requestFocus(); KeyboardUtils.showSoftInput(mEdSearch, GplayMainActivity.this); } }); }private boolean reactionToBackPressed() { if (mShowSearchToolbar) { KeyboardUtils.hideSoftInput(mEdSearch, this); View childView = mRevealFrameLayout.getChildAt(0); childView.bringToFront(); int centerX = childView.getLeft(); int centerY = childView.getBottom() / 2; Animator circularReveal = ViewAnimationUtils.createCircularReveal(childView, centerX, centerY, 0, childView.getWidth()); circularReveal.setDuration(300).setInterpolator(new DecelerateInterpolator()); circularReveal.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { super.onAnimationEnd(animation); reactionToCover(false); } }); circularReveal.start(); mShowSearchToolbar = false; return true; } return false; }public void reactionToCover(boolean isDark){ if(isDark) { mFrameBodyCover.setVisibility(View.VISIBLE); }else{ mFrameBodyCover.setVisibility(View.GONE); } }
小结
上述代码已提交到Github:SearchBarDemo
作者:sugaryaruan
链接:https://www.jianshu.com/p/4408584276ff
共同学习,写下你的评论
评论加载中...
作者其他优质文章