仿电商应用中搜索模块的实现【GreenDao数据库存储数据】
来源:互联网 发布:淘宝几星可以开直通车 编辑:程序博客网 时间:2024/05/30 04:31
好久没写博客了,主要是最近太忙了(好吧,其实是因为自己太懒了)。进入正题,今天主要实现的功能是:搜索。 没错,搜索模块在很多电商类app中是很常见的,实现起来也比较容易,但是经常在群里看见有人问有没有关于搜索功能的demo,加之我最近自己做的一个项目中也有搜索模块,所以这里就来说说我是怎么实现的。
这里非常感谢 干货集中营 提供的搜索的接口。据说不含效果图的文章都是耍流氓,,吓得我赶紧来了张效果图,如下图所示
需求介绍
1.首先,我们要监听搜索框EditText 我们在afterTextChanged()方法中需要去进行判断,当EditText有值时,搜索框后面的清除图标显示,我们根据输入的关键字向服务器发送请求(通过Handler每隔一段时间延迟请求),这样做的原因是因为 假如用户输入io然后又在极短时间内继续输入s,延迟发送请求就可以避免重复像服务器提交数据。需要注意的是,每次发送请求之前需要检查是否还存在有未处理的消息,有,则需要取消掉。
2.当我们向服务器请求到数据之后,点击item进入详情页的时候,我们需要将这条数据存储起来,作为历史记录向用户展示。关于数据存储的方式有很多,这里我采用的是GreenDao数据库来保存数据。这里特别说明一下,新版本的GreenDao使用步骤简化了很多,使用起来还是很爽的。关于GreenDao的使用,如果还没用过此数据库的童鞋可以看我这篇文章http://blog.csdn.net/xiaxiazaizai01/article/details/53118748,这里就不再详细介绍了。
3.当我们用电商类的搜索时,可以发现,最近搜索的记录会显示在最上面,并且当再次搜索此字段时,并不会重复添加,只是更新了时间,因为我们是按时间排序的。
4.主要介绍下此demo中用到的一些比较不错的框架,BaseRecyclerViewAdapterHelper 非常好用的一个Recyclerview框架,OkGo 网络请求
具体代码说明
首先,看看我们的布局文件 activity_main.xml
<?xml version="1.0" encoding="utf-8"?><RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/activity_main" android:layout_width="match_parent" android:layout_height="match_parent" android:background="#ECECEC" > <LinearLayout android:id="@+id/ll_top" android:layout_width="match_parent" android:layout_height="45dp" android:gravity="center_vertical" android:background="#FFFFFF" android:orientation="horizontal" > <ImageView android:id="@+id/search_back" android:layout_width="45dp" android:layout_height="match_parent" android:paddingLeft="6dp" android:paddingRight="6dp" android:src="@drawable/back_ic" /> <LinearLayout android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:layout_marginTop="5dp" android:layout_marginBottom="5dp" android:gravity="center_vertical" android:background="@drawable/search_bg_shape" android:orientation="horizontal" > <EditText android:id="@+id/et_search" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:drawableLeft="@drawable/search_icon" android:hint="android/ios/福利/前端/休息视频" android:drawablePadding="10dp" android:textSize="14sp" android:layout_margin="5dp" android:singleLine="true" android:background="@null" /> <ImageView android:id="@+id/search_clear" android:layout_width="20dp" android:layout_height="20dp" android:layout_margin="5dp" android:src="@drawable/clear_search_ic" android:visibility="gone"/> </LinearLayout> <TextView android:id="@+id/tv_search" android:layout_width="wrap_content" android:layout_height="match_parent" android:gravity="center" android:paddingLeft="10dp" android:paddingRight="10dp" android:text="搜索" android:textColor="#333333" android:textSize="16sp"/> </LinearLayout> <View android:id="@+id/line" android:layout_width="match_parent" android:layout_height="1dp" android:layout_below="@id/ll_top" android:background="#e2e2e2"/> <!-- 热搜模块,这里不做重点,省略。。。 --> <!-- 搜索历史模块 --> <LinearLayout android:id="@+id/ll_history" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" android:layout_below="@id/line" android:visibility="gone"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="8dp" android:layout_marginBottom="8dp" android:layout_marginLeft="10dp" android:text="历史搜索" android:textColor="#333333" android:textSize="16sp" android:textStyle="bold"/> <View android:layout_width="match_parent" android:layout_height="1dp" android:background="#e2e2e2"/> <android.support.v7.widget.RecyclerView android:id="@+id/history_search_recyclerview" android:layout_width="match_parent" android:layout_height="match_parent"/> </LinearLayout> <!-- 搜索的结果展示模块 --> <android.support.v7.widget.RecyclerView android:id="@+id/search_data_recyclerview" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_below="@id/line" android:visibility="gone"/> <RelativeLayout android:id="@+id/rl_progress" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_below="@id/line" android:layout_marginTop="20dp" android:gravity="center_horizontal" android:visibility="gone" > <com.json.search.CustomProgressBar android:id="@+id/progress_bar" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerHorizontal="true" android:layout_centerVertical="true" android:padding="5dp" app:search_progress_color="#10a679" app:search_progress_width="1dp" app:search_radius="6dp" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_toRightOf="@id/progress_bar" android:layout_marginLeft="5dp" android:layout_centerVertical="true" android:text="加载中..."/> </RelativeLayout></RelativeLayout>
接着,看下我们存储数据库所需要的实体类是如何定义的
/** * 历史搜索的实体类 保存到数据库 */@Entitypublic class SearchHistoryBean { @Id private Long id; @Property(nameInDb = "title") private String desc; @Property(nameInDb = "url") private String url; @Property(nameInDb = "time") private long publishedAt; @Generated(hash = 649301100) public SearchHistoryBean(Long id, String desc, String url, long publishedAt) { this.id = id; this.desc = desc; this.url = url; this.publishedAt = publishedAt; } @Generated(hash = 1570282321) public SearchHistoryBean() { } public Long getId() { return this.id; } public void setId(Long id) { this.id = id; } public String getDesc() { return this.desc; } public void setDesc(String desc) { this.desc = desc; } public String getUrl() { return this.url; } public void setUrl(String url) { this.url = url; } public long getPublishedAt() { return this.publishedAt; } public void setPublishedAt(long publishedAt) { this.publishedAt = publishedAt; }}
主要逻辑是在我们的MainActivity中,接下来我们就来简单的看看EditText的监听。注释写的很详细了
etSearch.addTextChangedListener(new TextWatcher() { @Override public void beforeTextChanged(CharSequence charSequence, int start, int count, int after) { } @Override public void onTextChanged(CharSequence charSequence, int start, int before, int count) { } @Override public void afterTextChanged(Editable editable) { //EditText上有文字变动时,有未发出的搜索请求,则应取消 if(mHandler.hasMessages(SEARCH_MESSAGE)){ mHandler.removeMessages(SEARCH_MESSAGE); } //如果为空,直接显示搜索历史,否则,显示搜索结果 if(TextUtils.isEmpty(editable)){ searchClear.setVisibility(View.GONE); rlProgress.setVisibility(View.GONE);//显示加载中的提示语 searchResultRv.setVisibility(View.GONE); //需要进一步判断,因为用户如果点击的是键盘上自带的删除键的话,会导致历史搜索的布局不隐藏 if(mDatas != null && mDatas.size() > 0){ llHistory.setVisibility(View.VISIBLE); }else{ llHistory.setVisibility(View.GONE); } } else { if(lists != null && lists.size() > 0){//先判断之前的集合是否有数据,有的话则先清空,不然的话紧接着搜索,此时列表中还显示着上次的结果 lists.clear(); lists = null; } searchClear.setVisibility(View.VISIBLE); rlProgress.setVisibility(View.VISIBLE);//显示加载中的提示语 searchResultRv.setVisibility(View.VISIBLE); llHistory.setVisibility(View.GONE); //延迟500ms开始搜索 mHandler.sendEmptyMessageDelayed(SEARCH_MESSAGE, 500); } } });
将数据插入数据库,当我们用电商类的搜索时,可以发现,最近搜索的记录会显示在最上面,并且当再次搜索此字段时,并不会重复添加,只是更新了时间,因为我们是按时间排序的。
private void saveDatasToDb(int position) { //插入数据之前先查 防止重复添加 这里查单条(一个实体)数据 SearchHistoryBean historyBeen = historyDao.queryBuilder().where(SearchHistoryBeanDao.Properties.Desc.eq(lists.get(position).getDesc())).build().unique(); if(historyBeen != null){ historyDao.delete(historyBeen);//存在,则删除此条数据 } bean = new SearchHistoryBean(null,lists.get(position).getDesc(), lists.get(position).getUrl(), Long.parseLong(TimeDifferentUtil.formatTimeDate(format,System.currentTimeMillis()))); //historyDao.insert(bean); historyDao.insertOrReplace(bean);//其实这里用的是insertOrReplace 即使相同也会添加并且替换之前的 }
我们再来看看查询数据
//先判断数据库中是否有数据,有,则显示历史记录 historyDao = MyApplication.getDaoSession(MainActivity.this).getSearchHistoryBeanDao(); List<SearchHistoryBean> historyBeenLists = historyDao.queryBuilder().orderDesc(SearchHistoryBeanDao.Properties.PublishedAt).build().list(); if(historyBeenLists != null && historyBeenLists.size() > 0){ //说明数据库中含有数据 llHistory.setVisibility(View.VISIBLE); mDatas = new ArrayList<>(); for(SearchHistoryBean bean : historyBeenLists){ String title = bean.getDesc(); SearchResultBean.ResultsBean resultBean = new SearchResultBean.ResultsBean(); resultBean.setDesc(title); resultBean.setUrl(bean.getUrl()); mDatas.add(resultBean); } historyAdapter = new SearchHistoryAdapter(mDatas); historySearchRv.setAdapter(historyAdapter); if(mDatas != null && mDatas.size() > 0){ historyAdapter.addFooterView(historyFooter); }else{ historyAdapter.removeFooterView(historyFooter); } }
下面给出完整的MainActivity的代码
public class MainActivity extends AppCompatActivity implements View.OnClickListener,BaseQuickAdapter.RequestLoadMoreListener{ public static String SEARCHURL = "http://gank.io/api/search/query/listview/category/"; private RelativeLayout rlProgress;//提示正在加载中 private ImageView ivBack;//返回按钮 private EditText etSearch; private ImageView searchClear;//清除按钮 private TextView tvSearch;//标题栏上面的搜索按钮 private LinearLayout llHistory;//历史搜索的整体布局 private RecyclerView historySearchRv;//历史搜索记录 private SearchHistoryAdapter historyAdapter;//搜索历史的adapter private RecyclerView searchResultRv;//搜索结果 private SearchResultAdapter resultAdapter;//搜索结果的adapter private SwipeRefreshLayout swipeRefreshLayout; private List<SearchResultBean.ResultsBean> lists;//数据源 private Gson gson; private int currentPage = 1;//当前页 private int pageSize = 10; private String format= "yyyyMMddHHmmss"; //初始化一些时间设置格式 private SearchHistoryBeanDao historyDao;//DAO private SearchHistoryBean bean; private List<SearchResultBean.ResultsBean> mDatas; private View historyFooter;//添加尾部 private TextView tvClear;//清空历史记录 private static final int SEARCH_MESSAGE = 1; private Handler mHandler = new Handler(){ @Override public void handleMessage(Message msg) { switch (msg.what){ case SEARCH_MESSAGE: //网络请求数据 requestNetWorkDatas(); break; } } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); //初始化控件 iniViews(); //初始化搜索结果的adapter resultAdapter = new SearchResultAdapter(null); resultAdapter.isFirstOnly(false); resultAdapter.setOnLoadMoreListener(this); searchResultRv.setAdapter(resultAdapter); //先判断数据库中是否有数据,有,则显示历史记录 historyDao = MyApplication.getDaoSession(MainActivity.this).getSearchHistoryBeanDao(); List<SearchHistoryBean> historyBeenLists = historyDao.queryBuilder().orderDesc(SearchHistoryBeanDao.Properties.PublishedAt).build().list(); if(historyBeenLists != null && historyBeenLists.size() > 0){ //说明数据库中含有数据 llHistory.setVisibility(View.VISIBLE); mDatas = new ArrayList<>(); for(SearchHistoryBean bean : historyBeenLists){ String title = bean.getDesc(); SearchResultBean.ResultsBean resultBean = new SearchResultBean.ResultsBean(); resultBean.setDesc(title); resultBean.setUrl(bean.getUrl()); mDatas.add(resultBean); } historyAdapter = new SearchHistoryAdapter(mDatas); historySearchRv.setAdapter(historyAdapter); if(mDatas != null && mDatas.size() > 0){ historyAdapter.addFooterView(historyFooter); }else{ historyAdapter.removeFooterView(historyFooter); } } } private void iniViews() { rlProgress = (RelativeLayout) findViewById(R.id.rl_progress); ivBack = (ImageView) findViewById(R.id.search_back); etSearch = (EditText) findViewById(R.id.et_search); searchClear = (ImageView) findViewById(R.id.search_clear); tvSearch = (TextView) findViewById(R.id.tv_search); llHistory = (LinearLayout) findViewById(R.id.ll_history); //当历史搜索有数据时,则加载footer布局 清空历史记录 historyFooter = LayoutInflater.from(this).inflate(R.layout.history_search_footer, null); tvClear = (TextView) historyFooter.findViewById(R.id.clear_tv); //清空历史记录 tvClear.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { //清空集合 mDatas.clear(); //清空数据库 删除所有数据 historyDao.deleteAll(); //实时更新UI界面,还需要隐藏掉尾布局,以及历史搜索等字样 historyAdapter.notifyDataSetChanged(); historyFooter.setVisibility(View.GONE); llHistory.setVisibility(View.GONE); } }); ivBack.setOnClickListener(this); etSearch.setOnClickListener(this); searchClear.setOnClickListener(this); tvSearch.setOnClickListener(this); historySearchRv = (RecyclerView) findViewById(R.id.history_search_recyclerview); searchResultRv = (RecyclerView) findViewById(R.id.search_data_recyclerview); //设置展示样式 historySearchRv.setLayoutManager(new LinearLayoutManager(MainActivity.this)); searchResultRv.setLayoutManager(new LinearLayoutManager(MainActivity.this)); etSearch.addTextChangedListener(new TextWatcher() { @Override public void beforeTextChanged(CharSequence charSequence, int start, int count, int after) { } @Override public void onTextChanged(CharSequence charSequence, int start, int before, int count) { } @Override public void afterTextChanged(Editable editable) { //EditText上有文字变动时,有未发出的搜索请求,则应取消 if(mHandler.hasMessages(SEARCH_MESSAGE)){ mHandler.removeMessages(SEARCH_MESSAGE); } //如果为空,直接显示搜索历史,否则,显示搜索结果 if(TextUtils.isEmpty(editable)){ searchClear.setVisibility(View.GONE); rlProgress.setVisibility(View.GONE);//显示加载中的提示语 searchResultRv.setVisibility(View.GONE); //需要进一步判断,因为用户如果点击的是键盘上自带的删除键的话,会导致历史搜索的布局不隐藏 if(mDatas != null && mDatas.size() > 0){ llHistory.setVisibility(View.VISIBLE); }else{ llHistory.setVisibility(View.GONE); } } else { if(lists != null && lists.size() > 0){//先判断之前的集合是否有数据,有的话则先清空,不然的话紧接着搜索,此时列表中还显示着上次的结果 lists.clear(); lists = null; } searchClear.setVisibility(View.VISIBLE); rlProgress.setVisibility(View.VISIBLE);//显示加载中的提示语 searchResultRv.setVisibility(View.VISIBLE); llHistory.setVisibility(View.GONE); //延迟500ms开始搜索 mHandler.sendEmptyMessageDelayed(SEARCH_MESSAGE, 500); } } }); //点击搜索结果item,跳转到相应页面,并且将item上的内容保存到greendao数据库 searchResultRv.addOnItemTouchListener(new OnItemClickListener() { @Override public void onSimpleItemClick(BaseQuickAdapter adapter, View view, int position) { //先将内容填充到EditText上,再跳转 // etSearch.setText(lists.get(position).getDesc()); //跳转 Intent intent = new Intent(MainActivity.this, InfoActivity.class); intent.putExtra("url",lists.get(position).getUrl()); startActivity(intent); //保存到数据库 saveDatasToDb(position); //finish MainActivity.this.finish(); } }); //点击历史搜索中的item 跳转到相应的页面 historySearchRv.addOnItemTouchListener(new OnItemClickListener() { @Override public void onSimpleItemClick(BaseQuickAdapter adapter, View view, int position) { //先将内容填充到EditText上,再跳转 // etSearch.setText(mDatas.get(position).getDesc()); //跳转 Intent intent = new Intent(MainActivity.this, InfoActivity.class); intent.putExtra("url",mDatas.get(position).getUrl()); startActivity(intent); //同时将此数据更新到最上面 //插入数据之前先查 防止重复添加 这里查单条(一个实体)数据 SearchHistoryBean historyBeen = historyDao.queryBuilder().where(SearchHistoryBeanDao.Properties.Desc.eq(mDatas.get(position).getDesc())).build().unique(); if(historyBeen != null){ historyDao.delete(historyBeen);//存在,则删除此条数据 } //historyDao = MyApplication.getDaoSession(SearchActivity.this).getSearchHistoryBeanDao(); bean = new SearchHistoryBean(null,mDatas.get(position).getDesc(), mDatas.get(position).getUrl(), Long.parseLong(TimeDifferentUtil.formatTimeDate(format,System.currentTimeMillis()))); historyDao.insertOrReplace(bean);//其实这里用的是insertOrReplace 即使相同也会添加并且替换之前的 //finish MainActivity.this.finish(); } }); } private void saveDatasToDb(int position) { //插入数据之前先查 防止重复添加 这里查单条(一个实体)数据 SearchHistoryBean historyBeen = historyDao.queryBuilder().where(SearchHistoryBeanDao.Properties.Desc.eq(lists.get(position).getDesc())).build().unique(); if(historyBeen != null){ historyDao.delete(historyBeen);//存在,则删除此条数据 } //historyDao = MyApplication.getDaoSession(SearchActivity.this).getSearchHistoryBeanDao(); bean = new SearchHistoryBean(null,lists.get(position).getDesc(), lists.get(position).getUrl(), Long.parseLong(TimeDifferentUtil.formatTimeDate(format,System.currentTimeMillis()))); //historyDao.insert(bean); historyDao.insertOrReplace(bean);//其实这里用的是insertOrReplace 即使相同也会添加并且替换之前的 } @Override public void onClick(View view) { switch (view.getId()){ case R.id.search_back: finish(); break; case R.id.search_clear: etSearch.setText(""); //还需要判断 if(mDatas != null && mDatas.size() > 0){ llHistory.setVisibility(View.VISIBLE); }else{ llHistory.setVisibility(View.GONE); } break; case R.id.tv_search: break; } } private void requestNetWorkDatas() { currentPage = 1; OkGo.get(SEARCHURL+etSearch.getText().toString().trim()+"/count/10/page/"+currentPage) .tag("MainActivity") .execute(new StringCallback() { @Override public void onSuccess(String s, Call call, Response response) { gson = new Gson(); SearchResultBean searchResultBean = gson.fromJson(s, SearchResultBean.class); if(!searchResultBean.isError()){ lists = searchResultBean.getResults(); if(lists != null && lists.size() > 0){ resultAdapter.setNewData(lists); } } } @Override public void onError(Call call, Response response, Exception e) { super.onError(call, response, e); } @Override public void onBefore(BaseRequest request) { super.onBefore(request); //弹窗提醒正在加载中 rlProgress.setVisibility(View.VISIBLE); } @Override public void onAfter(String s, Exception e) { super.onAfter(s, e); //关闭弹窗 rlProgress.setVisibility(View.GONE); } }); } @Override public void onLoadMoreRequested() { currentPage++; searchResultRv.postDelayed(new Runnable() { @Override public void run() { OkGo.get(SEARCHURL+etSearch.getText().toString().trim()+"/count/10/page/"+currentPage) .tag("MainActivity") .execute(new StringCallback() { @Override public void onSuccess(String s, Call call, Response response) { gson = new Gson(); SearchResultBean searchResultBean = gson.fromJson(s, SearchResultBean.class); if(!searchResultBean.isError()){ List<SearchResultBean.ResultsBean> lists = searchResultBean.getResults(); if(lists != null && lists.size() > 0){ //定义接口时,我们设置的是每页显示10条数据 if(lists.size() < pageSize){ //请求获取到的总数据 < 每页需要显示的数据时,隐藏掉没有更多数据的提示 resultAdapter.loadMoreEnd(true);//true:隐藏提示 false:显示提示 }else{ // if(mCurrentCounter >= totalCounts){//由于接口获取不到总的数据量,所以这里先注释掉 // categoryThreeAdapter.loadMoreEnd(false);//刷新完成后,提示没有更多数据,false:提示 true:隐藏 // }else{ //注意,这里用的是addData(),在之前的数据集合后面添加下一条 resultAdapter.addData(lists); // mCurrentCounter = categoryThreeAdapter.getData().size(); //调用完成的方法 注意:加载完数据之后一定要调用完成的方法,否则不会再执行上拉刷新,这里我将刷新完成放在了请求完成方法中 // categoryThreeAdapter.loadMoreComplete(); // } } }else{ //当上拉加载失败时,提示 resultAdapter.loadMoreFail(); } } } @Override public void onError(Call call, Response response, Exception e) { super.onError(call, response, e); } @Override public void onAfter(String s, Exception e) { super.onAfter(s, e); //最后调用结束刷新的方法 resultAdapter.loadMoreComplete(); } }); } }, 1000); } @Override protected void onDestroy() { super.onDestroy(); OkGo.getInstance().cancelTag("MainActivity"); mHandler.removeMessages(SEARCH_MESSAGE); mHandler = null; }}
其实,在做的过程中还是有很多细节需要去处理的,比如说:当页面销毁时,我们要取消网络请求,同时移除handler消息等等,否则的话就会持有Activity的引用,造成内存泄漏。
点击这里下载完整代码
- 仿电商应用中搜索模块的实现【GreenDao数据库存储数据】
- GreenDao的数据库存储
- android的存储GreenDao数据库
- GreenDao+存储数据库
- 高效的GreenDao 数据库操作框架应用
- MongoDB数据库的海量数据存储应用
- 数据库数据实现模块
- 一个应用二叉搜索树实现的字典,并存储结构于文件中
- Android 中数据存储模块 实现商品展示
- greenDao数据库存储框架解析
- greendao的存储路径
- GreenDao的简单存储
- GreenDao存储list集合数据
- Android中的GreenDao框架修改数据库的存储路径
- SearchView+RecyclerView+GreenDao的搜索功能实现(1)
- SearchView+RecyclerView+GreenDao的搜索功能实现(2)
- SearchView+RecyclerView+GreenDao的搜索功能实现(2)
- 数据库中存储json类型的数据
- 9*9乘法表
- Android 中使用自定义ttf字体实现酷炫效果
- Angular Module声明和获取重载
- HTML5模仿刮奖效果-页面涂抹消失插件wScratch
- PageRank算法--从原理到实现
- 仿电商应用中搜索模块的实现【GreenDao数据库存储数据】
- mysql sql语句大全【转】
- Flask Web开发--前言
- RecyclerView实现滑动和拖拽功能(带小例子)
- org.apache.commons.io.FileUtils的使用
- 递归
- Linux系统启动流程
- 每个程序猿应该阅读的10本经典书籍
- Android 软键盘控制方法、以及开发中遇到的一些问题。