RecyclerView实现顶部悬浮、字母排序、过滤搜索最优雅的方式
来源:互联网 发布:中国为什么网络扫黄 编辑:程序博客网 时间:2024/06/05 08:36
效果:
这篇文章算是之前一篇的升级版,在上一篇的基础上新增了顶部悬停功能、波浪侧边栏和关于多音字的一个处理。
上一篇链接 :
《Android 使用RecyclerView实现(仿微信)的联系人A-Z字母排序和过滤搜索功能》
http://blog.csdn.net/silenceoo/article/details/75661590
主界面布局代码:
<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" xmlns:app="http://schemas.android.com/apk/res-auto" android:orientation="vertical" android:focusable="true" android:focusableInTouchMode="true"> <com.xp.wavesidebarrecyclerview.ClearEditText android:id="@+id/filter_edit" android:layout_width="match_parent" android:layout_height="wrap_content" android:paddingLeft="8dp" android:background="#bef9b81b" android:drawableLeft="@drawable/search_bar_icon_normal" android:hint="请输入关键字" android:maxLines="1" android:textSize="15dp" /> <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <android.support.v7.widget.RecyclerView android:id="@+id/rv" android:layout_width="match_parent" android:layout_height="match_parent"> </android.support.v7.widget.RecyclerView> <com.xp.wavesidebar.WaveSideBar android:id="@+id/sideBar" android:layout_width="match_parent" android:layout_height="match_parent" app:chooseTextColor="@android:color/white" app:textColor="#969696" app:backgroundColor="#bef9b81b" app:textSize="10sp" app:hintTextSize="32sp" app:radius="24dp" app:circleRadius="24dp"/> </FrameLayout></LinearLayout>
这里的WaveSideBar参考的是:https://github.com/Solartisan/WaveSideBar
WaveSideBar下边的自定义属性也可以不设置,不设置的话就是使用默认值,详细的实现方式可以看源码。
主界面的逻辑代码主要是三个方法:
1、初始化的方法主要是对比较器的初始化,设置监听,对数据排序和对RecyclerView的初始化。
private void initViews() { mComparator = new PinyinComparator(); mSideBar = (WaveSideBar) findViewById(R.id.sideBar); //设置右侧SideBar触摸监听 mSideBar.setOnTouchLetterChangeListener(new WaveSideBar.OnTouchLetterChangeListener() { @Override public void onLetterChange(String letter) { //该字母首次出现的位置 int position = mAdapter.getPositionForSection(letter.charAt(0)); if (position != -1) { manager.scrollToPositionWithOffset(position, 0); } } }); mRecyclerView = (RecyclerView) findViewById(R.id.rv); mDateList = filledData(getResources().getStringArray(R.array.date)); // 根据a-z进行排序源数据 Collections.sort(mDateList, mComparator); //RecyclerView设置manager manager = new LinearLayoutManager(this); manager.setOrientation(LinearLayoutManager.VERTICAL); mRecyclerView.setLayoutManager(manager); mAdapter = new SortAdapter(this, mDateList); mRecyclerView.setAdapter(mAdapter); mDecoration = new TitleItemDecoration(this, mDateList); //如果add两个,那么按照先后顺序,依次渲染。 mRecyclerView.addItemDecoration(mDecoration); mRecyclerView.addItemDecoration(new DividerItemDecoration(MainActivity.this, DividerItemDecoration.VERTICAL)); 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) { } }); }
列表顶部字母索引和悬停的实现使用的是RecyclerView的ItemDecoration。比之前每个item都包含一个字母索引更优雅。
接下来是将数据列表的内容按拼音排序的方法,先将汉字转化成拼音,在用正则表达式分类,下边是代码:
/** * 为RecyclerView填充数据 * * @param date * @return */ private List<SortModel> filledData(String[] date) { List<SortModel> 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; }
最后就是根据输入的内容进行数据筛选的方法:
/** * 根据输入框中的值来过滤数据并更新RecyclerView * * @param filterStr */ private void filterData(String filterStr) { List<SortModel> filterDateList = new ArrayList<>(); if (TextUtils.isEmpty(filterStr)) { filterDateList = filledData(getResources().getStringArray(R.array.date)); } else { filterDateList.clear(); for (SortModel sortModel : mDateList) { 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, mComparator); mDateList.clear(); mDateList.addAll(filterDateList); mAdapter.notifyDataSetChanged(); }
TitleItemDecoration:
An ItemDecoration allows the application to add a special drawing and layout offset to specific item views from the adapter's data set. This can be useful for drawing dividers between items, highlights, visual grouping boundaries and more.ItemDecoration主要是用来对RecyclerView进行一些修饰,是对adapter数据集中的数据视图增加修饰或空位。经常被用来画分割线、强调效果、可见的分组边界等。
这个类是继承自RecyclerView.ItemDecoration。主要方法:
1、getItemOffsets():绘制间距,绘制标题栏空出间隙。主要逻辑是通过当前view的position判断是否需要在上方空出矩形范围。
@Override public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { super.getItemOffsets(outRect, view, parent, state); int position = ((RecyclerView.LayoutParams) view.getLayoutParams()).getViewLayoutPosition(); if (position > -1) { //等于0的时候绘制title if (position == 0) { outRect.set(0, mTitleHeight, 0, 0); } else { if (null != mData.get(position).getLetters() && !mData.get(position).getLetters().equals(mData.get(position - 1).getLetters())) { //字母不为空,并且不等于前一个,绘制title outRect.set(0, mTitleHeight, 0, 0); } else { outRect.set(0, 0, 0, 0); } } } }
2、onDraw():进行标题栏等绘制,即在每组view的上方,即getItemOffset()的区域进行标题栏的绘制
@Override public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) { super.onDraw(c, parent, state); final int left = parent.getPaddingLeft(); final int right = parent.getWidth() - parent.getPaddingRight(); final int childCount = parent.getChildCount(); for (int i = 0; i < childCount; i++) { final View child = parent.getChildAt(i); final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child .getLayoutParams(); int position = params.getViewLayoutPosition(); if (position > -1) { if (position == 0) {//等于0的时候绘制title drawTitle(c, left, right, child, params, position); } else { if (null != mData.get(position).getLetters() && !mData.get(position) .getLetters().equals(mData.get(position - 1).getLetters())) { //字母不为空,并且不等于前一个,也要title drawTitle(c, left, right, child, params, position); } } } } }
drawTitle():
/** * 绘制Title区域背景和文字的方法 *最先调用,绘制最下层的title * @param c * @param left * @param right * @param child * @param params * @param position */ private void drawTitle(Canvas c, int left, int right, View child, RecyclerView.LayoutParams params, int position) { mPaint.setColor(TITLE_BG_COLOR); c.drawRect(left, child.getTop() - params.topMargin - mTitleHeight, right, child.getTop() - params.topMargin, mPaint); mPaint.setColor(TITLE_TEXT_COLOR); mPaint.getTextBounds(mData.get(position).getLetters(), 0, mData.get(position).getLetters().length(), mBounds); c.drawText(mData.get(position).getLetters(), child.getPaddingLeft(), child.getTop() - params.topMargin - (mTitleHeight / 2 - mBounds.height() / 2), mPaint); }
3、onDrawOver():实现悬浮分组栏,以及悬浮分组栏效果绘制。
对于整个列表的绘制流程,是遵循如下的顺序:
ItemDecoration#onDraw() -> ItemView的绘制 -> ItemDecoration#onDrawOver
在onDrawOver中实现可以满足“悬浮”,这个方法里实现了两种效果:一种是下边的字母将上边的顶上去;还有一种是下边的字母直接覆盖上边的字母。
/** * 最后调用,绘制最上层的title * @param c * @param parent * @param state */ @Override public void onDrawOver(Canvas c, final RecyclerView parent, RecyclerView.State state) { int position = ((LinearLayoutManager) (parent.getLayoutManager())).findFirstVisibleItemPosition(); if (position == -1) return;//在搜索到没有的索引的时候position可能等于-1,所以在这里判断一下 String tag = mData.get(position).getLetters(); View child = parent.findViewHolderForLayoutPosition(position).itemView; //Canvas是否位移过的标志 boolean flag = false; if ((position + 1) < mData.size()) { //当前第一个可见的Item的字母索引,不等于其后一个item的字母索引,说明悬浮的View要切换了 if (null != tag && !tag.equals(mData.get(position + 1).getLetters())) { //当第一个可见的item在屏幕中剩下的高度小于title的高度时,开始悬浮Title的动画 if (child.getHeight() + child.getTop() < mTitleHeight) { c.save(); flag = true; /** * 下边的索引把上边的索引顶上去的效果 */ c.translate(0, child.getHeight() + child.getTop() - mTitleHeight); /** * 头部折叠起来的视效(下边的索引慢慢遮住上边的索引) */ /*c.clipRect(parent.getPaddingLeft(), parent.getPaddingTop(), parent.getRight() - parent.getPaddingRight(), parent.getPaddingTop() + child.getHeight() + child.getTop());*/ } } } mPaint.setColor(TITLE_BG_COLOR); c.drawRect(parent.getPaddingLeft(), parent.getPaddingTop(), parent.getRight() - parent.getPaddingRight(), parent.getPaddingTop() + mTitleHeight, mPaint); mPaint.setColor(TITLE_TEXT_COLOR); mPaint.getTextBounds(tag, 0, tag.length(), mBounds); c.drawText(tag, child.getPaddingLeft(), parent.getPaddingTop() + mTitleHeight - (mTitleHeight / 2 - mBounds.height() / 2), mPaint); if (flag) c.restore();//恢复画布到之前保存的状态 }
适配器里面的代码很简单,直接上代码吧:
public class SortAdapter extends RecyclerView.Adapter<SortAdapter.ViewHolder> { private LayoutInflater mInflater; private List<SortModel> mData; private Context mContext; public SortAdapter(Context context, List<SortModel> 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_name, parent,false); ViewHolder viewHolder = new ViewHolder(view); viewHolder.tvName = (TextView) view.findViewById(R.id.tvName); return viewHolder; } @Override public void onBindViewHolder(final SortAdapter.ViewHolder holder, final int position) { 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 tvName; public ViewHolder(View itemView) { super(itemView); } } /** * 提供给Activity刷新数据 * @param list */ public void updateList(List<SortModel> 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; }}
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(); } /** * 汉字转换位汉语拼音首字母,英文字符不变,特殊字符丢失 支持多音字,生成方式如(长沙市长:cssc,zssz,zssc,cssz) * * @param chines * 汉字 * @return 拼音 */ public static String converterToFirstSpell(String chines) { StringBuffer pinyinName = new StringBuffer(); char[] nameChar = chines.toCharArray(); HanyuPinyinOutputFormat defaultFormat = new HanyuPinyinOutputFormat(); defaultFormat.setCaseType(HanyuPinyinCaseType.LOWERCASE); defaultFormat.setToneType(HanyuPinyinToneType.WITHOUT_TONE); for (int i = 0; i < nameChar.length; i++) { if (nameChar[i] > 128) { try { // 取得当前汉字的所有全拼 String[] str = PinyinHelper.toHanyuPinyinStringArray( nameChar[i], defaultFormat); if (str != null) { for (int j = 0; j < str.length; j++) { // 取首字母 pinyinName.append(str[j].charAt(0)); if (j != str.length - 1) { pinyinName.append(","); } } } // else { // pinyinName.append(nameChar[i]); // } } catch (BadHanyuPinyinOutputFormatCombination e) { e.printStackTrace(); } } else { pinyinName.append(nameChar[i]); } pinyinName.append(" "); } // return pinyinName.toString(); return parseTheChineseByObject(discountTheChinese(pinyinName.toString())); } /** * 汉字转换位汉语全拼,英文字符不变,特殊字符丢失 * 支持多音字,生成方式如(重当参:zhongdangcen,zhongdangcan,chongdangcen * ,chongdangshen,zhongdangshen,chongdangcan) * * @param chines * 汉字 * @return 拼音 */ public static String converterToSpell(String chines) { StringBuffer pinyinName = new StringBuffer(); char[] nameChar = chines.toCharArray(); HanyuPinyinOutputFormat defaultFormat = new HanyuPinyinOutputFormat(); defaultFormat.setCaseType(HanyuPinyinCaseType.LOWERCASE); defaultFormat.setToneType(HanyuPinyinToneType.WITHOUT_TONE); for (int i = 0; i < nameChar.length; i++) { if (nameChar[i] > 128) { try { // 取得当前汉字的所有全拼 String[] str = PinyinHelper.toHanyuPinyinStringArray( nameChar[i], defaultFormat); if (str != null) { for (int j = 0; j < str.length; j++) { pinyinName.append(str[j]); if (j != str.length - 1) { pinyinName.append(","); } } } } catch (BadHanyuPinyinOutputFormatCombination e) { e.printStackTrace(); } } else { pinyinName.append(nameChar[i]); } pinyinName.append(" "); } // return pinyinName.toString(); return parseTheChineseByObject(discountTheChinese(pinyinName.toString())); } /** * 去除多音字重复数据 * * @param theStr * @return */ private static List<Map<String, Integer>> discountTheChinese(String theStr) { // 去除重复拼音后的拼音列表 List<Map<String, Integer>> mapList = new ArrayList<>(); // 用于处理每个字的多音字,去掉重复 Map<String, Integer> onlyOne; String[] firsts = theStr.split(" "); // 读出每个汉字的拼音 for (String str : firsts) { onlyOne = new Hashtable<>(); String[] china = str.split(","); // 多音字处理 for (String s : china) { Integer count = onlyOne.get(s); if (count == null) { onlyOne.put(s, new Integer(1)); } else { onlyOne.remove(s); count++; onlyOne.put(s, count); } } mapList.add(onlyOne); } return mapList; } /** * 解析并组合拼音,对象合并方案(推荐使用) * * @return */ private static String parseTheChineseByObject( List<Map<String, Integer>> list) { Map<String, Integer> first = null; // 用于统计每一次,集合组合数据 // 遍历每一组集合 for (int i = 0; i < list.size(); i++) { // 每一组集合与上一次组合的Map Map<String, Integer> temp = new Hashtable<>(); // 第一次循环,first为空 if (first != null) { // 取出上次组合与此次集合的字符,并保存 for (String s : first.keySet()) { for (String s1 : list.get(i).keySet()) { String str = s + s1; temp.put(str, 1); } } // 清理上一次组合数据 if (temp != null && temp.size() > 0) { first.clear(); } } else { for (String s : list.get(i).keySet()) { String str = s; temp.put(str, 1); } } // 保存组合数据以便下次循环使用 if (temp != null && temp.size() > 0) { first = temp; } } String returnStr = ""; if (first != null) { // 遍历取出组合字符串 for (String str : first.keySet()) { returnStr += (str + ","); } } if (returnStr.length() > 0) { returnStr = returnStr.substring(0, returnStr.length() - 1); } return returnStr; }}
就介绍到这里吧,代码还是比较多,就不全部贴出来了,有兴趣的朋友可以下载完整代码,欢迎star。
点击下载完整代码
- RecyclerView实现顶部悬浮、字母排序、过滤搜索最优雅的方式
- Android 使用RecyclerView实现(仿微信)的联系人A-Z字母排序和过滤搜索功能
- Android RecyclerView 顶部悬浮实现
- Android RecyclerView 顶部悬浮实现
- RecyclerView实现带有头部的顶部悬浮置顶布局
- RecyclerView实现顶部悬浮条效果 自定义!
- RecyclerView实现多种Item,最优雅的方式,getItemViewType(以即时通讯app为例子)
- Android 实现ListView的A-Z字母排序和过滤搜索功能,实现汉字转成拼音
- Android实现ListView的A-Z字母排序和过滤搜索功能,实现汉字转成拼音
- Android实现ListView的A-Z字母排序和过滤搜索功能,实现汉字转成拼音
- Android实现ListView的A-Z字母排序和过滤搜索功能,实现汉字转成拼音
- Android实现ListView的A-Z字母排序和过滤搜索功能,实现汉字转成拼音
- Android 实现ListView的A-Z字母排序和过滤搜索功能,实现汉字转成拼音
- Android 实现ListView的A-Z字母排序和过滤搜索功能,实现汉字转成拼音
- Android 实现ListView的A-Z字母排序和过滤搜索功能,实现汉字转成拼音
- Android 实现ListView的A-Z字母排序和过滤搜索功能,实现汉字转成拼音
- Android 实现ListView的A-Z字母排序和过滤搜索功能,实现汉字转成拼音
- Android 实现ListView的A-Z字母排序和过滤搜索功能,实现汉字转成拼音
- 【Ionic2】Ionic2 Android 打包 Crosswalk报错问题的解决方案
- Java写XML文件
- java读取word文档
- Servlet理论介绍
- 猎头告诉你,他喜欢怎样的简历!
- RecyclerView实现顶部悬浮、字母排序、过滤搜索最优雅的方式
- crypt.2.最大公约数:欧几里得算法
- 文本框监听输入内容变化事件
- MFC 无边框对话框实现点击任务栏图标 还原窗口的功能
- 一点java的学习思路
- iOS之三目运算符和内联复合表达式
- 小米实习生笔试题<风口的猪-中国牛市>Java代码
- 进制相互转化的算法公式
- 抓包工具Charles —— 破解、抓包入门