7、搜索功能实现:SearchFragment集成、语音搜索与搜索建议
搜索功能,说白了就是用户在大屏上找内容的入口。TV端的搜索和手机端差别很大——用户没有触摸屏,只能靠遥控器上下左右移动焦点。我刚开始做TV搜索时,直接照搬了手机端的搜索框,结果用户反馈“太难用了”。嗯,今天我们就来聊聊,怎么在Android TV上做一个真正好用的搜索功能。
7.1 SearchFragment:Google给你的现成方案
Android TV官方其实提供了一个搜索组件——SearchFragment。我个人习惯直接用它,而不是自己从头写一个。为什么呢?因为它已经处理好了焦点移动、软键盘弹出、搜索历史这些麻烦事。
集成方式很简单,在你的布局里放一个SearchView:
<androidx.leanback.widget.SearchView
android:id="@+id/search_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:searchBackgroundColor="@color/search_bg"
app:searchHintText="搜索电影或电视剧"
app:searchIconColor="@color/white" />
然后在Fragment中这样使用:
public class MySearchFragment extends SearchFragment
implements SearchFragment.SearchResultProvider {
private ArrayObjectAdapter mRowsAdapter;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mRowsAdapter = new ArrayObjectAdapter(new ListRowPresenter());
setSearchResultProvider(this);
}
@Override
public boolean onQueryTextChange(String newQuery) {
// 用户每输入一个字符都会触发这里
loadResults(newQuery);
return true;
}
@Override
public boolean onQueryTextSubmit(String query) {
// 用户按下确认键提交搜索
loadResults(query);
return true;
}
private void loadResults(String query) {
// 这里调用你的搜索API或本地数据库
// 把结果塞进mRowsAdapter
}
}
小提示:onQueryTextChange会在每次按键时触发,如果你的搜索是网络请求,记得做防抖处理。我一般用RxJava的debounce操作符,或者简单点用Handler延迟300ms。
7.2 语音搜索:大屏上最自然的交互方式
你想想看,用遥控器在虚拟键盘上一个字母一个字母地选,多痛苦。语音搜索才是TV端的王道。Android TV系统级就支持语音输入,你只需要做一件事——集成SpeechRecognitionCallback。
具体做法:
public class MySearchFragment extends SearchFragment {
private SpeechRecognitionCallback mSpeechRecognitionCallback;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mSpeechRecognitionCallback = new SpeechRecognitionCallback() {
@Override
public void recognizeSpeech() {
// 启动系统语音识别
Intent intent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL,
RecognizerIntent.LANGUAGE_MODEL_FREE_FORM);
intent.putExtra(RecognizerIntent.EXTRA_PROMPT, "说出你要搜索的内容");
startActivityForResult(intent, REQUEST_SPEECH);
}
};
setSpeechRecognitionCallback(mSpeechRecognitionCallback);
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == REQUEST_SPEECH && resultCode == Activity.RESULT_OK) {
ArrayList<String> results =
data.getStringArrayListExtra(RecognizerIntent.EXTRA_RESULTS);
if (results != null && results.size() > 0) {
String spokenText = results.get(0);
// 把语音识别结果设置到搜索框
setSearchQuery(spokenText, true);
}
}
}
}
注意:语音识别需要网络连接。我曾经遇到过用户离线状态下点击语音按钮,应用直接崩溃的情况。记得检查网络状态,并给出友好提示。
7.3 搜索建议:让用户少按几次遥控器
搜索建议,说白了就是用户还没输完,你就猜他想搜什么。TV端尤其需要这个功能——少按一次遥控器,体验就好一分。
实现搜索建议需要两步:
- 提供建议数据源
- 在界面上展示建议列表
SearchFragment自带了一个建议展示机制,你只需要实现SearchFragment.SearchResultProvider接口:
@Override
public boolean onQueryTextChange(String newQuery) {
if (newQuery.length() > 2) { // 至少输入3个字符才开始建议
List<String> suggestions = getSuggestionsFromLocal(newQuery);
// 把建议展示在搜索结果区域
mRowsAdapter.clear();
for (String suggestion : suggestions) {
mRowsAdapter.add(new SuggestionItem(suggestion));
}
}
return true;
}
建议的来源可以是:
- 本地缓存的热门搜索词
- 用户的历史搜索记录
- 服务端返回的联想词
我个人习惯把热门搜索词存在本地SQLite里,这样即使断网也能给出建议。用户选择建议后,直接触发搜索,省去输入完整关键词的麻烦。
7.4 搜索结果的展示与交互
搜索结果展示,我推荐用RowsFragment或者VerticalGridFragment。为什么?因为TV端内容展示的核心就是“行”和“网格”。
举个例子,搜索结果可以按类型分几行:
- 第一行:电影结果
- 第二行:电视剧结果
- 第三行:演员结果
每一行内部用HorizontalGridView横向滚动,用户左右选择,上下切换行。这种交互模式在TV上非常成熟,用户一看就懂。
// 创建搜索结果的行
ArrayObjectAdapter rowsAdapter = new ArrayObjectAdapter(new ListRowPresenter());
// 电影行
ArrayObjectAdapter movieAdapter = new ArrayObjectAdapter(new CardPresenter());
movieAdapter.add(new Movie("流浪地球"));
movieAdapter.add(new Movie("哪吒"));
rowsAdapter.add(new ListRow(new HeaderItem("电影"), movieAdapter));
// 电视剧行
ArrayObjectAdapter tvAdapter = new ArrayObjectAdapter(new CardPresenter());
tvAdapter.add(new TVShow("三体"));
tvAdapter.add(new TVShow("狂飙"));
rowsAdapter.add(new ListRow(new HeaderItem("电视剧"), tvAdapter));
核心要点:搜索结果为空时,不要直接显示空白页面。我一般会展示一个“没有找到相关内容”的提示,同时推荐一些热门内容。这样用户不会感到失落,还能继续浏览。
7.5 知识体系总览
下面这张图总结了搜索功能的整体架构,从用户输入到结果展示的完整流程:
7.6 避坑指南
做TV搜索,有几个坑我踩过,分享给你:
- 焦点丢失问题:搜索结果更新时,如果当前焦点所在的item被移除了,焦点会飞到奇怪的地方。我一般会在更新数据前,先记录当前焦点的位置,更新后手动恢复。
- 软键盘适配:不同品牌的TV,软键盘样式不一样。有些遥控器没有“完成”键,用户不知道怎么提交搜索。我建议在搜索框旁边放一个“搜索”按钮,作为备选方案。
- 语音权限:Android 6.0以上需要动态申请录音权限。我曾经忘记处理权限拒绝的情况,结果语音按钮点了没反应,用户以为坏了。
小技巧:搜索历史可以存在SharedPreferences里,用Gson转成JSON数组。每次搜索时,把新词插到最前面,最多保留20条。这样实现简单,而且不需要引入数据库。
好了,搜索功能的核心实现就这些。说白了就是三件事:集成SearchFragment、接入语音识别、提供搜索建议。把这三件事做好,你的TV搜索体验就不会差。