Android通用搜索页的分析与封装
Posted 冷不冷
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android通用搜索页的分析与封装相关的知识,希望对你有一定的参考价值。
前言
之前写过一篇文章是关于搜索框的封装( Android通用的搜索框),当时只是对搜索栏部分进行简单的封装,把搜索逻辑、页面和数据处理封装了一下,而搜索页的处理(包括数据源,控件等)还需要手动的去创建和修改,用起来还是比较麻烦,最近不太忙,刚好需要用到这部分内容,于是又进一步的封装了一下.(文章只适用于数据量不太大的本地搜索,例如通讯录,城市列表,本地订单等)
还是先看一下效果图吧
主要看一下封装的实现.
分析与实现
先看一下之前写的搜索页代码(没看过( Android通用的搜索框)的童鞋,可以先大概看一下)
public class MainActivity extends AppCompatActivity
/**
* TAG
*/
private static final String TAG = "MainActivity";
/**
* 通用搜索框
*/
private CommolySearchView<SearchBean> mCommolySearchView;
/**
* 数据显示listview
*/
private ListView mListView;
/**
* 数据源
*/
private List<SearchBean> mDatas;
/**
* 适配器
*/
private SearchAdapter adapter;
//===========================三国部分===========================//
/**
* 三国通用搜索框
*/
private CommolySearchView<SanGuoBean> mSGCommolySearchView;
/**
* 三国数据源
*/
private List<SanGuoBean> mSGDatas;
/**
* 三国适配器
*/
private SGAdapter sgAdapter;
@Override
protected void onCreate(Bundle savedInstanceState)
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// initData();
// initView();
initDataI();
initViewI();
/**
* 初始化数据
*/
private void initData()
mDatas = new ArrayList<SearchBean>();
SearchBean bean1 = new SearchBean();
bean1.setSearchName("小明");
bean1.setSearchDescribe("一年级二班");
...
mDatas.add(bean1);
...
mDatas.add(bean7);
/**
* 初始化控件
*/
private void initView()
mCommolySearchView = (CommolySearchView) findViewById(R.id.csv_show);
mListView = (ListView) findViewById(R.id.lv_show);
adapter = new SearchAdapter(this, mDatas);
mListView.setAdapter(adapter);
// 设置数据源
mCommolySearchView.setDatas(mDatas);
// 设置适配器
mCommolySearchView.setAdapter(adapter);
// 设置筛选数据
mCommolySearchView.setSearchDataListener(new CommolySearchView.SearchDatas<SearchBean>()
@Override
public List<SearchBean> filterDatas(List<SearchBean> datas, List<SearchBean> filterdatas, String inputstr)
Log.e(TAG, "filterDatas: " + inputstr);
for (int i = 0; i < datas.size(); i++)
// 筛选条件
if ((datas.get(i).getSearchName()).contains(inputstr) || datas.get(i).getSearchDescribe().contains(inputstr))
filterdatas.add(datas.get(i));
return filterdatas;
);
mListView.setOnItemClickListener(new AdapterView.OnItemClickListener()
@Override
public void onItemClick(AdapterView<?> adapterView, View view, int i, long l)
Toast.makeText(MainActivity.this, "当前点击的是" + mCommolySearchView.getFilterDatas().get(i).getSearchName(), Toast.LENGTH_SHORT).show();
);
/**
* 初始化数据
*/
private void initDataI()
mSGDatas = new ArrayList<SanGuoBean>();
SanGuoBean sgbean1 = new SanGuoBean();
sgbean1.setSgName("刘备");
sgbean1.setSgPetName("玄德");
sgbean1.setSgHeadBp(R.drawable.lb);
sgbean1.setSgDescribe("刘备(161年-223年6月10日),字玄德,东汉末年幽州涿郡涿县(今河北省涿州市)人");
...
mSGDatas.add(sgbean1);
...
mSGDatas.add(sgbean7);
/**
* 初始化控件
*/
private void initViewI()
mSGCommolySearchView = (CommolySearchView) findViewById(R.id.csv_show);
mListView = (ListView) findViewById(R.id.lv_show);
sgAdapter = new SGAdapter(this, mSGDatas);
mListView.setAdapter(sgAdapter);
// 设置数据源
mSGCommolySearchView.setDatas(mSGDatas);
// 设置适配器
mSGCommolySearchView.setAdapter(sgAdapter);
// 设置筛选数据
mSGCommolySearchView.setSearchDataListener(new CommolySearchView.SearchDatas<SanGuoBean>()
@Override
public List<SanGuoBean> filterDatas(List<SanGuoBean> datas, List<SanGuoBean> filterdatas, String inputstr)
for (int i = 0; i < datas.size(); i++)
// 筛选条件
if ((datas.get(i).getSgDescribe()).contains(inputstr) || datas.get(i).getSgName().contains(inputstr) || datas.get(i).getSgPetName().contains(inputstr))
filterdatas.add(datas.get(i));
return filterdatas;
);
mListView.setOnItemClickListener(new AdapterView.OnItemClickListener()
@Override
public void onItemClick(AdapterView<?> adapterView, View view, int i, long l)
// 点击操作
...
);
为了节省篇幅,省略了一些初始化数据的内容.
分析一下那些地方是需要进行优化和封装的.
一)可以看到当搜索不同内容时,需要创建不同的List来接收不同类型的数据源
/**
* 数据源1
*/
private List<SearchBean> mDatas;
/**
* 三国数据源(数据源2)
*/
private List<SanGuoBean> mSGDatas;
二)通用搜索栏的实现中为了通用性,使用了泛型,所以每次定义CommolySearchView时需要传入一个泛型,来规定引用类型
/**
* 数据源1的搜索栏
*/
private CommolySearchView<SearchBean> mCommolySearchView;
/**
* 三国数据源通用搜索框(数据源2)
*/
private CommolySearchView<SanGuoBean> mSGCommolySearchView;
三)当然使用listview时,不同的数据源对应着不同的适配器
/**
* 数据源1适配器
*/
private SearchAdapter adapter;
/**
* 三国适配器(数据源2适配器)
*/
private SGAdapter sgAdapter;
针对这三点,来分析一下
先来看第一点,关于不同数据源的问题.看到这个问题,脑海里第一个想到的就是使用泛型,不过要如何设计和使用呢.直接在搜索页定义一个list< T > 来接收不同的数据源并规定引用类型?显然不合适.最好是有一个中介. 这里定义一个CommonSearchBean来传递和使用不同类型的数据源
package com.example.junweiliu.commonlysearchview.bean;
import java.io.Serializable;
import java.util.List;
/**
* Created by junweiliu on 17/3/29.
* 封装的搜索bean用于传递和使用不同类型的数据源
*/
public class CommonSearchBean<T> implements Serializable
/**
* 数据源
*/
private List<T> list;
/**
* 搜索类型 1代表搜索测试1,2代表搜索测试2
*/
private int searchType;
public List<T> getList()
return list;
public void setList(List<T> list)
this.list = list;
public int getSearchType()
return searchType;
public void setSearchType(int searchType)
this.searchType = searchType;
这里有一个searchType,是为了区分不同的搜索类型,之后会用到.这样数据源传入和使用就可以通过CommonSearchBean来完成
// 传输数据
CommonSearchBean commonSearchBean = new CommonSearchBean();
// 设置数据
commonSearchBean.setList(mDatas);
...
//---------------------分割线------------------------//
// 获取数据
mDatas = commonSearchBean.getList();
...
再来看一下第二点,关于通用搜索栏,怎么再进一步的优化和封装呢?使用通用搜索栏时需要传入一个泛型< T >去规定引用类型,但实际并不关心引用类型是什么,只是需要在搜索回调中,把符合筛选条件的数据返回即可,相关代码
// 设置筛选数据
mSGCommolySearchView.setSearchDataListener(new CommolySearchView.SearchDatas<SanGuoBean>()
@Override
public List<SanGuoBean> filterDatas(List<SanGuoBean> datas, List<SanGuoBean> filterdatas, String inputstr)
for (int i = 0; i < datas.size(); i++)
// 筛选条件
if ((datas.get(i).getSgDescribe()).contains(inputstr) || datas.get(i).getSgName().contains(inputstr) || datas.get(i).getSgPetName().contains(inputstr))
filterdatas.add(datas.get(i));
return filterdatas;
);
大多数的搜索都是使用包含条件,所以这里规定一个接口BaseSearch,通过getSearchCondition()方法返回筛选条件
package com.example.junweiliu.commonlysearchview.bean;
/**
* Created by junweiliu on 17/3/29.
*/
public interface BaseSearch
/**
* 获取筛选条件
*
* @return
*/
String getSearchCondition();
在相关实体bean中实现该接口,例如
package com.example.junweiliu.commonlysearchview.bean;
import java.io.Serializable;
/**
* Created by junweiliu on 17/3/29.
*/
public class SearchDemoBeanTwo implements BaseSearch, Serializable
/**
* 头像资源
*/
private int iconRes;
/**
* 头像地址
*/
private int iconUrl;
/**
* 姓名
*/
private String name;
/**
* 电话
*/
private String phone;
/**
* 邮箱
*/
private String email;
public int getIconRes()
return iconRes;
public void setIconRes(int iconRes)
this.iconRes = iconRes;
public int getIconUrl()
return iconUrl;
public void setIconUrl(int iconUrl)
this.iconUrl = iconUrl;
public String getName()
return name;
public void setName(String name)
this.name = name;
public String getPhone()
return phone;
public void setPhone(String phone)
this.phone = phone;
public String getEmail()
return email;
public void setEmail(String email)
this.email = email;
/**
* 搜索条件
*
* @return
*/
@Override
public String getSearchCondition()
StringBuilder searchStr = new StringBuilder();
searchStr.append(name);
searchStr.append(phone);
searchStr.append(email);
return searchStr.toString();
把筛选条件合并(当然可以根据需求返回筛选条件,因为需要修改字符串,所以这里使用StringBuilder,效率比较高),通过getSearchCondition()方法返回.之后在搜索页就可以这样写
/**
* 搜索框
*/
CommolySearchView<BaseSearch> mCsvShow;
...
// 设置搜索
mCsvShow.setSearchDataListener(new CommolySearchView.SearchDatas<BaseSearch>()
@Override
public List<BaseSearch> filterDatas(List<BaseSearch> datas, List<BaseSearch> filterdatas, String inputstr)
for (int i = 0; i < datas.size(); i++)
// 筛选条件,如果有必要,在此做修改
if (datas.get(i).getSearchCondition().contains(inputstr))
filterdatas.add(datas.get(i));
return filterdatas;
);
最后看一下第三个问题,不同的适配器.适配器初始化一般都需要上下文,数据源,布局文件资源.数据源的问题在第一个问题中已经解决,所以在搜索页定义一个BaseAdapter对象,再根据搜索类型实例化不同的对象即可,相关代码如下
/**
* 适配器
*/
private BaseAdapter mAdapter;
/**
* 封装的搜索bean
*/
private CommonSearchBean mCommonSearchBean;
/**
* 搜索类型(用于区分不同的搜索)
*/
private int searchType;
...
if (1 == searchType) // 测试例子1
// 适配器1
mAdapter = new SearchDemoAapterOne(this, mCommonSearchBean.getList(), R.layout.item_search_one);
else if (2 == searchType) // 测试例子2
// 适配器2
mAdapter = new SearchDemoAdapterTwo(this, mCommonSearchBean.getList());
细心的童鞋可能注意到在定义CommonSearchBean时没有规定引用类型,这里不规定引用类型的原因就是为了通用性,当使用泛型不规定引用类型时,默认为Object,在适配器初始化时传入的数据源通过CommonSearchBean.getList()方法获得,只需要保证setList()方法中的数据类型和getList()方法中的数据类型一致即可.
具体代码
最后看一下主要的代码
搜索页CommonSearchActivity
package com.example.junweiliu.commonlysearchview.activity;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.AdapterView;
import android.widget.BaseAdapter;
import android.widget.ListView;
import android.widget.Toast;
import com.example.junweiliu.commonlysearchview.R;
import com.example.junweiliu.commonlysearchview.adapter.SearchDemoAapterOne;
import com.example.junweiliu.commonlysearchview.adapter.SearchDemoAdapterTwo;
import com.example.junweiliu.commonlysearchview.bean.BaseSearch;
import com.example.junweiliu.commonlysearchview.bean.CommonSearchBean;
import com.example.junweiliu.commonlysearchview.bean.SearchDemoBeanOne;
import com.example.junweiliu.commonlysearchview.bean.SearchDemoBeanTwo;
import com.example.junweiliu.commonlysearchview.widget.CommolySearchView;
import java.util.List;
/**
* Created by junweiliu on 17/3/29.
*/
public class CommonSearchActivity extends AppCompatActivity
/**
* 搜索框
*/
CommolySearchView<BaseSearch> mCsvShow;
/**
* 数据展示
*/
ListView mLvShow;
/**
* 适配器
*/
private BaseAdapter mAdapter;
/**
* 封装的搜索bean
*/
private CommonSearchBean mCommonSearchBean = new CommonSearchBean();
/**
* 搜索类型(用于区分不同的搜索)
*/
private int searchType;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState)
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_common_search);
initData();
initView();
initAdapter();
initSearch();
/**
* 初始化适配器,一般的扩展只需修改该方法即可
*/
private void initAdapter()
if (1 == searchType) // 测试例子1
// 适配器1
mAdapter = new SearchDemoAapterOne(this, mCommonSearchBean.getList(), R.layout.item_search_one);
// 点击事件
mLvShow.setOnItemClickListener(new AdapterView.OnItemClickListener()
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id)
Toast.makeText(CommonSearchActivity.this, "点击的是:" + ((SearchDemoBeanOne) mCommonSearchBean.getList().get(position)).getName(), Toast.LENGTH_SHORT).show();
);
else if (2 == searchType) // 测试例子2
// 适配器2
mAdapter = new SearchDemoAdapterTwo(this, mCommonSearchBean.getList());
// 点击事件
mLvShow.setOnItemClickListener(new AdapterView.OnItemClickListener()
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id)
Toast.makeText(CommonSearchActivity.this, "点击的是:" + ((SearchDemoBeanTwo) mCommonSearchBean.getList().get(position)).getName(), Toast.LENGTH_SHORT).show();
);
/**
* 初始化数据
*/
private void initData()
mCommonSearchBean = (CommonSearchBean) getIntent().getSerializableExtra("seachBean");
searchType = mCommonSearchBean.getSearchType();
/**
* 初始化view
*/
private void initView()
mCsvShow = (CommolySearchView<BaseSearch>) findViewById(R.id.csv_show);
mLvShow = (ListView) findViewById(R.id.lv_show);
/**
* 初始化搜索
*/
private void initSearch()
mLvShow.setAdapter(mAdapter);
// 设置数据源
mCsvShow.setDatas(mCommonSearchBean.getList());
// 设置适配器
mCsvShow.setAdapter(mAdapter);
// 设置搜索
mCsvShow.setSearchDataListener(new CommolySearchView.SearchDatas<BaseSearch>()
@Override
public List<BaseSearch> filterDatas(List<BaseSearch> datas, List<BaseSearch> filterdatas, String inputstr)
for (int i = 0; i < datas.size(); i++)
// 筛选条件,如果有必要,在此做修改
if (datas.get(i).getSearchCondition().contains(inputstr))
filterdatas.add(datas.get(i));
return filterdatas;
);
注释写的比较全,每次添加新的搜索类型时,只需要在initAdapter()方法中添加新的搜索类型的扩展即可(主要是实例化新的适配器),有必要的话,可以去修改筛选逻辑或者扩展BaseSearch,大多数包含情况下是不需要修改的.当然本文只是提供一个思路,应该有更好的封装方法.
通用搜索栏CommolySearchView
package com.example.junweiliu.commonlysearchview.widget;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.text.Editable;
import android.text.TextWatcher;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.BaseAdapter;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.LinearLayout;
import com.example.junweiliu.commonlysearchview.R;
import java.util.ArrayList;
import java.util.List;
/**
* Created by junweiliu on 16/5/31.
*/
public class CommolySearchView<T> extends LinearLayout
/**
* 上下文
*/
private Context mContext;
/**
* 编辑框
*/
private EditText mEditText;
/**
* 清除按钮
*/
private ImageView mClearImg;
/**
* 搜索图标
*/
private ImageView mSearchBarImg;
/**
* 适配器
*/
private BaseAdapter mAdapter;
/**
* 数据源
*/
private List<T> mDatas = new ArrayList<T>();
/**
* 数据源副本
*/
private List<T> mDupDatas = new ArrayList<T>();
/**
* 筛选后的数据源
*/
private List<T> mFilterDatas = new ArrayList<T>();
/**
* 筛选后的数据源副本
*/
private List<T> mDupFilterDatas = new ArrayList<T>();
/**
* 搜索图标
*/
private Bitmap mSearchIcon;
/**
* 搜索框距离左边边距
*/
private int mSearchIconMarginLeft;
/**
* 搜索框距离右边边距
*/
private int mSearchIconMarginRight;
/**
* 清除图标
*/
private Bitmap mClearIcon;
/**
* 清除图标距离左边边距
*/
private int mClearIconMarginLeft;
/**
* 清除图标距离右边边距
*/
private int mClearIconMarginRight;
/**
* 搜索文字大小
*/
private int mSearchTextSize;
/**
* 搜索文字颜色
*/
private int mSearchTextColor;
/**
* 回调接口
*
* @param <T>
*/
public interface SearchDatas<T>
/**
* 参数一:全部数据,参数二:筛选后的数据,参数三:输入的内容
*
* @param datas
* @param filterdatas
* @param inputstr
* @return
*/
List<T> filterDatas(List<T> datas, List<T> filterdatas, String inputstr);
/**
* 回调
*/
private SearchDatas<T> mListener;
/**
* 设置回调
*
* @param listener
*/
public void setSearchDataListener(SearchDatas<T> listener)
mListener = listener;
public CommolySearchView(Context context)
this(context, null);
public CommolySearchView(Context context, AttributeSet attrs)
this(context, attrs, 0);
public CommolySearchView(Context context, AttributeSet attrs, int defStyleAttr)
super(context, attrs, defStyleAttr);
mContext = context;
// 自定义属性
TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.CommolySearchView);
Drawable searchD = ta.getDrawable(R.styleable.CommolySearchView_SearchBarIconSrc);
mSearchIcon = drawableToBitamp(searchD);
mSearchIconMarginLeft = px2dip(context, ta.getDimensionPixelOffset(R.styleable.CommolySearchView_SearchBarIconMarginLeft, 0));
mSearchIconMarginRight = px2dip(context, ta.getDimensionPixelOffset(R.styleable.CommolySearchView_SearchBarIconMarginRight, 0));
Drawable clearD = ta.getDrawable(R.styleable.CommolySearchView_ClearIconSrc);
mClearIcon = drawableToBitamp(clearD);
mClearIconMarginLeft = px2dip(context, ta.getDimensionPixelOffset(R.styleable.CommolySearchView_ClearIconMarginLeft, 0));
mClearIconMarginRight = px2dip(context, ta.getDimensionPixelOffset(R.styleable.CommolySearchView_ClearIconMarginRight, 0));
mSearchTextSize = px2sp(context, ta.getDimensionPixelOffset(R.styleable.CommolySearchView_SearchTextSize, 0));
mSearchTextColor = ta.getColor(R.styleable.CommolySearchView_SearchTextColor, 0);
ta.recycle();
// 绑定布局文件
LayoutInflater.from(context).inflate(R.layout.searchview_layout, this);
initView();
/**
* 初始化控件
*/
private void initView()
mEditText = (EditText) findViewById(R.id.et_search);
mClearImg = (ImageView) findViewById(R.id.iv_search_clear);
mSearchBarImg = (ImageView) findViewById(R.id.iv_search_icon);
// 处理自定义属性
if (0 != mSearchIconMarginLeft || 0 != mSearchIconMarginRight)
mSearchBarImg.setPadding(mSearchIconMarginLeft, 0, mSearchIconMarginRight, 0);
if (0 != mClearIconMarginLeft || 0 != mClearIconMarginRight)
mClearImg.setPadding(mClearIconMarginLeft, 0, mClearIconMarginRight, 0);
if (null != mSearchIcon)
mSearchBarImg.setImageBitmap(mSearchIcon);
if (null != mClearIcon)
mClearImg.setImageBitmap(mClearIcon);
if (0 != mSearchTextSize)
mEditText.setTextSize(mSearchTextSize);
if (0 != mSearchTextColor)
mEditText.setTextColor(mSearchTextColor);
// 清空按钮处理事件
mClearImg.setOnClickListener(new OnClickListener()
@Override
public void onClick(View view)
mEditText.setText("");
mClearImg.setVisibility(View.GONE);
if (null != mDatas)
mDatas.clear();
mDatas.addAll(mDupDatas);
mAdapter.notifyDataSetChanged();
reSetDatas();
);
// 搜索栏处理事件
mEditText.addTextChangedListener(new TextWatcher()
@Override
public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2)
@Override
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2)
// 获取筛选后的数据
mFilterDatas = mListener.filterDatas(mDupDatas, mFilterDatas, charSequence.toString());
if (charSequence.toString().length() > 0 && !charSequence.toString().equals(""))
mClearImg.setVisibility(View.VISIBLE);
else
mClearImg.setVisibility(View.GONE);
if (null != mDatas)
mDatas.clear();
mDatas.addAll(mFilterDatas);
mAdapter.notifyDataSetChanged();
reSetDatas();
@Override
public void afterTextChanged(Editable editable)
);
/**
* 获取筛选后的数据
*
* @return
*/
public List<T> getFilterDatas()
return (null != mDupFilterDatas && mDupFilterDatas.size() > 0) ? mDupFilterDatas : mDupDatas;
/**
* 重置数据
*/
private void reSetDatas()
if (null != mFilterDatas)
if (null != mDupFilterDatas)
mDupFilterDatas.clear();
mDupFilterDatas.addAll(mFilterDatas);
mFilterDatas.clear();
/**
* 设置数据源
*
* @param datas
*/
public void setDatas(List<T> datas)
if (null == datas)
return;
if (null != mDatas)
mDatas.clear();
if (null != mDupDatas)
mDupDatas.clear();
mDatas = datas;
mDupDatas.addAll(mDatas);
/**
* 设置适配器
*
* @param adapter
*/
public void setAdapter(BaseAdapter adapter)
if (null == adapter)
return;
mAdapter = adapter;
/**
* drawable转bitmap
*
* @param drawable
* @return
*/
private Bitmap drawableToBitamp(Drawable drawable)
if (null == drawable)
return null;
if (drawable instanceof BitmapDrawable)
BitmapDrawable bd = (BitmapDrawable) drawable;
return bd.getBitmap();
int w = drawable.getIntrinsicWidth();
int h = drawable.getIntrinsicHeight();
Bitmap bitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
drawable.setBounds(0, 0, w, h);
drawable.draw(canvas);
return bitmap;
/**
* 将px值转换为dip或dp值,保证尺寸大小不变
*
* @param pxValue
* @return
* @param(DisplayMetrics类中属性density)
*/
public int px2dip(Context context, float pxValue)
final float scale = context.getResources().getDisplayMetrics().density;
return (int) (pxValue / scale + 0.5f);
/**
* 将px值转换为sp值,保证文字大小不变
*
* @param pxValue
* @return
*/
public static int px2sp(Context context, float pxValue)
final float fontScale = context.getResources().getDisplayMetrics().scaledDensity;
return (int) (pxValue / fontScale + 0.5f);
源码下载
以上是关于Android通用搜索页的分析与封装的主要内容,如果未能解决你的问题,请参考以下文章