仿电商应用中搜索模块的实现【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的引用,造成内存泄漏。

点击这里下载完整代码

1 0
原创粉丝点击