之前做项目的时候遇到一个需求是实现品牌的字母排序功能,网上的资料很多,但是有一部分有bug,这篇文章是我学习和解决部分bug之后的总结。今天带来的是RecyclerView的A-Z字母排序和过滤搜索功能。
首先上效果图:
重点:1、实现数据排序分类 2、实现右侧的字母导航 3、搜索
这里使用了一个中文转化为拼音的工具包,即pinyin4j-2.5.0.jar。官网地址:http://pinyin4j.sourceforge.net/
布局顶部是一个带删除按钮的文本编辑框,我们在输入框中输入字母或汉字可以自动过滤出我们想要的东西,当输入框中没有数据自动替换到原来的数据列表,然后下面一个RecyclerView用来显示数据列表,右侧是一个字母索引表,其实就是一个自定的View,当我们点击不同的字母,RecyclerView会定位到该字母索引地item。
这是主界面布局代码:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:focusable="true"
android:focusableInTouchMode="true">
<com.xp.sortrecyclerview.ClearEditText
android:id="@+id/filter_edit"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingLeft="8dp"
android:background="#9DDE76"
android:drawableLeft="@drawable/search_bar_icon_normal"
android:hint="请输入关键字"
android:maxLines="1"
android:textSize="15dp" />
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent" >
<android.support.v7.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
<TextView
android:id="@+id/dialog"
android:layout_width="80dp"
android:layout_height="80dp"
android:layout_centerInParent="true"
android:background="#9DDE76"
android:gravity="center"
android:textColor="#ffffffff"
android:textSize="30dp"
android:visibility="invisible" />
<com.xp.sortrecyclerview.SideBar
android:id="@+id/sideBar"
android:layout_width="30dp"
android:layout_height="match_parent"
android:layout_alignParentRight="true"
/>
RelativeLayout>
LinearLayout>
之前出现过两个问题,一个就是,每次进入界面后EditText自动获取焦点,导致输入法自动弹出来,所以为了解决这个问题在EditText的父布局也就是我们的根布局加了两个属性:android:focusable=”true”和android:focusableInTouchMode=”true”。
还有一个问题就是侧边栏会被输入法顶上去,结解决办法是在清单配置文件的对应Activity加上android:windowSoftInputMode=”adjustPan”,防止布局被输入法顶上去。
中间的TextView是用来显示选中的字母索引。
主界面的话主要是三个方法:
初始化的方法主要是对比较器的初始化,设置监听,对数据排序和对RecyclerView的初始化。
代码:
private void initViews() {
//初始化比较器
pinyinComparator = new PinyinComparator();
sideBar = (SideBar) findViewById(R.id.sideBar);
dialog = (TextView) findViewById(R.id.dialog);
sideBar.setTextView(dialog);
//设置右侧SideBar触摸监听
sideBar.setOnTouchingLetterChangedListener(new SideBar.OnTouchingLetterChangedListener() {
@Override
public void onTouchingLetterChanged(String s) {
//该字母首次出现的位置
int position = adapter.getPositionForSection(s.charAt(0));
if (position != -1) {
manager.scrollToPositionWithOffset(position, 0);
}
}
});
mRecyclerView = (RecyclerView) findViewById(R.id.recyclerView);
SourceDateList = filledData(getResources().getStringArray(R.array.date));
// 根据a-z进行排序源数据
Collections.sort(SourceDateList, pinyinComparator);
//RecyclerView社置manager
manager = new LinearLayoutManager(this);
manager.setOrientation(LinearLayoutManager.VERTICAL);
mRecyclerView.setLayoutManager(manager);
adapter = new SortAdapter(this, SourceDateList);
mRecyclerView.setAdapter(adapter);
//item点击事件
/*adapter.setOnItemClickListener(new SortAdapter.OnItemClickListener() {
@Override
public void onItemClick(View view, int position) {
Toast.makeText(MainActivity.this, ((SortModel)adapter.getItem(position)).getName(),Toast.LENGTH_SHORT).show();
}
});*/
mClearEditText = (ClearEditText) findViewById(R.id.filter_edit);
//根据输入框输入值的改变来过滤搜索
mClearEditText.addTextChangedListener(new TextWatcher() {
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
//当输入框里面的值为空,更新为原来的列表,否则为过滤数据列表
filterData(s.toString());
}
@Override
public void beforeTextChanged(CharSequence s, int start, int count,
int after) {
}
@Override
public void afterTextChanged(Editable s) {
}
});
}
接下来是将数据列表的内容按拼音排序的方法,先将汉字转化成拼音,在用正则表达式分类,下边是代码:
private List filledData(String[] date) {
List mSortList = new ArrayList<>();
for (int i = 0; i < date.length; i++) {
SortModel sortModel = new SortModel();
sortModel.setName(date[i]);
//汉字转换成拼音
String pinyin = PinyinUtils.getPingYin(date[i]);
String sortString = pinyin.substring(0, 1).toUpperCase();
// 正则表达式,判断首字母是否是英文字母
if (sortString.matches("[A-Z]")) {
sortModel.setLetters(sortString.toUpperCase());
} else {
sortModel.setLetters("#");
}
mSortList.add(sortModel);
}
return mSortList;
}
最后就是根据输入的内容进行数据筛选的方法:
private void filterData(String filterStr) {
List filterDateList = new ArrayList<>();
if (TextUtils.isEmpty(filterStr)) {
filterDateList = SourceDateList;
} else {
filterDateList.clear();
for (SortModel sortModel : SourceDateList) {
String name = sortModel.getName();
if (name.indexOf(filterStr.toString()) != -1 ||
PinyinUtils.getFirstSpell(name).startsWith(filterStr.toString())
//不区分大小写
|| PinyinUtils.getFirstSpell(name).toLowerCase().startsWith(filterStr.toString())
|| PinyinUtils.getFirstSpell(name).toUpperCase().startsWith(filterStr.toString())
) {
filterDateList.add(sortModel);
}
}
}
// 根据a-z进行排序
Collections.sort(filterDateList, pinyinComparator);
adapter.updateList(filterDateList);
}
RecyclerView适配器的代码,都比较简单就不用多解释了,直接上代码:
public class SortAdapter extends RecyclerView.Adapter {
private LayoutInflater mInflater;
private List mData;
private Context mContext;
public SortAdapter(Context context, List data) {
mInflater = LayoutInflater.from(context);
mData = data;
this.mContext = context;
}
@Override
public SortAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = mInflater.inflate(R.layout.item, parent,false);
ViewHolder viewHolder = new ViewHolder(view);
viewHolder.tvTag = (TextView) view.findViewById(R.id.tag);
viewHolder.tvName = (TextView) view.findViewById(R.id.name);
return viewHolder;
}
@Override
public void onBindViewHolder(final SortAdapter.ViewHolder holder, final int position) {
int section = getSectionForPosition(position);
//如果当前位置等于该分类首字母的Char的位置 ,则认为是第一次出现
if (position == getPositionForSection(section)) {
holder.tvTag.setVisibility(View.VISIBLE);
holder.tvTag.setText(mData.get(position).getLetters());
} else {
holder.tvTag.setVisibility(View.GONE);
}
if (mOnItemClickListener != null) {
holder.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mOnItemClickListener.onItemClick(holder.itemView, position);
}
});
}
holder.tvName.setText(this.mData.get(position).getName());
holder.tvName.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Toast.makeText(mContext, mData.get(position).getName(),Toast.LENGTH_SHORT).show();
}
});
}
@Override
public int getItemCount() {
return mData.size();
}
//**********************itemClick************************
public interface OnItemClickListener {
void onItemClick(View view, int position);
}
private OnItemClickListener mOnItemClickListener;
public void setOnItemClickListener(OnItemClickListener mOnItemClickListener) {
this.mOnItemClickListener = mOnItemClickListener;
}
//**************************************************************
public static class ViewHolder extends RecyclerView.ViewHolder {
TextView tvTag, tvName;
public ViewHolder(View itemView) {
super(itemView);
}
}
/**
* 提供给Activity刷新数据
* @param list
*/
public void updateList(List list){
this.mData = list;
notifyDataSetChanged();
}
public Object getItem(int position) {
return mData.get(position);
}
/**
* 根据ListView的当前位置获取分类的首字母的char ascii值
*/
public int getSectionForPosition(int position) {
return mData.get(position).getLetters().charAt(0);
}
/**
* 根据分类的首字母的Char ascii值获取其第一次出现该首字母的位置
*/
public int getPositionForSection(int section) {
for (int i = 0; i < getItemCount(); i++) {
String sortStr = mData.get(i).getLetters();
char firstChar = sortStr.toUpperCase().charAt(0);
if (firstChar == section) {
return i;
}
}
return -1;
}
}
PinyinComparator是个比较器类,主要就是根据ASCII码来对数据进行比较排序:
public class PinyinComparator implements Comparator<SortModel> {
public int compare(SortModel o1, SortModel o2) {
if (o1.getLetters().equals("@")
|| o2.getLetters().equals("#")) {
return -1;
} else if (o1.getLetters().equals("#")
|| o2.getLetters().equals("@")) {
return 1;
} else {
return o1.getLetters().compareTo(o2.getLetters());
}
}
}
PinyinUtils是一个将中文转化为拼音的工具类,主要提供汉字转拼音的方法和获取首字母的方法:
public class PinyinUtils {
/**
* 获取拼音
*
* @param inputString
* @return
*/
public static String getPingYin(String inputString) {
HanyuPinyinOutputFormat format = new HanyuPinyinOutputFormat();
format.setCaseType(HanyuPinyinCaseType.LOWERCASE);
format.setToneType(HanyuPinyinToneType.WITHOUT_TONE);
format.setVCharType(HanyuPinyinVCharType.WITH_V);
char[] input = inputString.trim().toCharArray();
String output = "";
try {
for (char curChar : input) {
if (Character.toString(curChar).matches("[\\u4E00-\\u9FA5]+")) {
String[] temp = PinyinHelper.toHanyuPinyinStringArray(curChar, format);
output += temp[0];
} else
output += Character.toString(curChar);
}
} catch (BadHanyuPinyinOutputFormatCombination e) {
e.printStackTrace();
}
return output;
}
/**
* 获取第一个字的拼音首字母
* @param chinese
* @return
*/
public static String getFirstSpell(String chinese) {
StringBuffer pinYinBF = new StringBuffer();
char[] arr = chinese.toCharArray();
HanyuPinyinOutputFormat defaultFormat = new HanyuPinyinOutputFormat();
defaultFormat.setCaseType(HanyuPinyinCaseType.LOWERCASE);
defaultFormat.setToneType(HanyuPinyinToneType.WITHOUT_TONE);
for (char curChar : arr) {
if (curChar > 128) {
try {
String[] temp = PinyinHelper.toHanyuPinyinStringArray(curChar, defaultFormat);
if (temp != null) {
pinYinBF.append(temp[0].charAt(0));
}
} catch (BadHanyuPinyinOutputFormatCombination e) {
e.printStackTrace();
}
} else {
pinYinBF.append(curChar);
}
}
return pinYinBF.toString().replaceAll("\\W", "").trim();
}
}
最后是我们的侧边栏SideBar的代码:
public class SideBar extends View {
// 触摸事件
private OnTouchingLetterChangedListener onTouchingLetterChangedListener;
public static String[] b = { "A", "B", "C", "D", "E", "F", "G", "H", "I",
"J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V",
"W", "X", "Y", "Z", "#" };
private int choose = -1;
private Paint paint = new Paint();
private TextView mTextDialog;
/**
* 为SideBar设置显示字母的TextView
* @param textDialog
*/
public void setTextView(TextView textDialog) {
this.mTextDialog = textDialog;
}
public SideBar(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public SideBar(Context context, AttributeSet attrs) {
super(context, attrs);
}
public SideBar(Context context) {
super(context);
}
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
int height = getHeight();
int width = getWidth();
int singleHeight = height / b.length;// 获取每一个字母的高度
for (int i = 0; i < b.length; i++) {
paint.setColor(Color.rgb(33, 65, 98));
// paint.setColor(Color.WHITE);
paint.setTypeface(Typeface.DEFAULT_BOLD);
paint.setAntiAlias(true);
paint.setTextSize(30);
if (i == choose) {// 选中的状态
paint.setColor(Color.parseColor("#3399ff"));
paint.setFakeBoldText(true);
}
// x坐标等于中间-字符串宽度的一半
float xPos = width / 2 - paint.measureText(b[i]) / 2;
float yPos = singleHeight * i + singleHeight;
canvas.drawText(b[i], xPos, yPos, paint);
paint.reset();
}
}
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
final int action = event.getAction();
final float y = event.getY();
final int oldChoose = choose;
final OnTouchingLetterChangedListener listener = onTouchingLetterChangedListener;
final int c = (int) (y / getHeight() * b.length);// 点击y坐标所占总高度的比例*b数组的长度就等于点击b中的个数
switch (action) {
case MotionEvent.ACTION_UP:
setBackground(new ColorDrawable(0x00000000));
choose = -1;//
invalidate();
if (mTextDialog != null) {
mTextDialog.setVisibility(View.INVISIBLE);
}
break;
default:
setBackgroundResource(R.drawable.sidebar_background);
if (oldChoose != c) {
if (c >= 0 && c < b.length) {
if (listener != null) {
listener.onTouchingLetterChanged(b[c]);
}
if (mTextDialog != null) {
mTextDialog.setText(b[c]);
mTextDialog.setVisibility(View.VISIBLE);
}
choose = c;
invalidate();
}
}
break;
}
return true;
}
/**
* 触摸事件
* @param onTouchingLetterChangedListener
*/
public void setOnTouchingLetterChangedListener(
OnTouchingLetterChangedListener onTouchingLetterChangedListener) {
this.onTouchingLetterChangedListener = onTouchingLetterChangedListener;
}
/**
* 回调接口
*/
public interface OnTouchingLetterChangedListener {
void onTouchingLetterChanged(String s);
}
}
以上就是主要代码了,如果需要的话可以下载整个项目源码,欢迎star。
点击下载源码
这里是一个升级版本,新增顶部悬浮,侧边栏使用wavesidebar,实现方式更加优雅。
《RecyclerView字母排序,顶部悬浮,过滤搜索最优雅的实现方式》
http://blog.csdn.net/SilenceOO/article/details/77839683