一,介绍SectionIndexer

package android.widget;/*** 接口可以被adapter实现,使AbsListView的sections之间可以快速滑动* 一个secton是一群列表,有很多相似点,例如他们可能以相同的字母开头,或者是来自同一个歌手的歌曲* ExpandableListAdapters认为groups和sections是同一个缩放组,应该返回一个合适的位置* 可以看一下AbsListView中setFastScrollEnabled(boolean)方法*/
public interface SectionIndexer {//返回一个代表sections列表的非空的数组对象//当滑动的时候,这个list view可以调用toString()来显示预览文本。//例如,一个adapter可以返回代表字母表中字母的字符串数组,或者返回section titles的对象数组Object[] getSections();//提供section索引,通过section数组对象,返回adapter中section开始的位置int getPositionForSection(int sectionIndex);//提供adapter中的位置,在section数组对象中返回相应的section索引//如果postion位置在索引开始位置之前,则返回0int getSectionForPosition(int position);
}

这么一说仿佛有点抽象,给个图就知道了:

getSectionForPosition() 通过该项的位置,获得所在分类组的索引号

getPositionForSection() 根据分类列的索引号获得该序列的首个位置

二,具体实现

2.1 定义侧面字母栏

public class SideBar extends AppCompatTextView {private String[] letters = new String[]{"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 Paint textPaint;private Paint bigTextPaint;private Paint scaleTextPaint;private Canvas canvas;private int itemH;private int w;private int h;/*** 普通情况下字体大小*/float singleTextH;/*** 缩放离原始的宽度*/private float scaleWidth;/*** 滑动的Y*/private float eventY = 0;/*** 缩放的倍数*/private int scaleSize = 1;/*** 缩放个数item,即开口大小*/private int scaleItemCount = 6;private ISideBarSelectCallBack callBack;/*** 回调接口*/public interface ISideBarSelectCallBack {void onSelectStr(int index, String selectStr);}public SideBar(Context context) {super(context);}public SideBar(Context context, @Nullable AttributeSet attrs) {super(context, attrs);}public SideBar(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);init(attrs);}/*** 初始化函数** @param attrs attr*/private void init(AttributeSet attrs) {if (attrs != null) {TypedArray ta = getContext().obtainStyledAttributes(attrs, R.styleable.SideBar);scaleSize = ta.getInteger(R.styleable.SideBar_scaleSize, 1);scaleItemCount = ta.getInteger(R.styleable.SideBar_scaleItemCount, 6);scaleWidth = ta.getDimensionPixelSize(R.styleable.SideBar_scaleWidth, dp(100));ta.recycle();}textPaint = new Paint(Paint.ANTI_ALIAS_FLAG);textPaint.setColor(getCurrentTextColor());textPaint.setTextSize(getTextSize());textPaint.setTextAlign(Paint.Align.CENTER);bigTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);bigTextPaint.setColor(getCurrentTextColor());bigTextPaint.setTextSize(getTextSize() * (scaleSize + 3));bigTextPaint.setTextAlign(Paint.Align.CENTER);scaleTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);scaleTextPaint.setColor(getCurrentTextColor());scaleTextPaint.setTextSize(getTextSize() * (scaleSize + 1));scaleTextPaint.setTextAlign(Paint.Align.CENTER);}public void setDataResource(String[] data) {letters = data;invalidate();}public void setOnStrSelectCallBack(ISideBarSelectCallBack callBack) {this.callBack = callBack;}/*** 设置字体缩放比例*/public void setScaleSize(int scale) {scaleSize = scale;invalidate();}/*** 设置缩放字体的个数,即开口大小*/public void setScaleItemCount(int scaleItemCount) {this.scaleItemCount = scaleItemCount;invalidate();}private int dp(int px) {final float scale = getContext().getResources().getDisplayMetrics().density;return (int) (px * scale + 0.5f);}@Overridepublic boolean onTouchEvent(MotionEvent event) {switch (event.getAction()) {case MotionEvent.ACTION_DOWN:case MotionEvent.ACTION_MOVE:if (event.getX() > (w - getPaddingRight() - singleTextH - 10)) {eventY = event.getY();invalidate();return true;}else {eventY = 0;invalidate();break;}case MotionEvent.ACTION_CANCEL:eventY = 0;invalidate();return true;case MotionEvent.ACTION_UP:if (event.getX() > (w - getPaddingRight() - singleTextH - 10)) {eventY = 0;invalidate();return true;}elsebreak;}return super.onTouchEvent(event);}@Overrideprotected void onDraw(Canvas canvas) {this.canvas = canvas;DrawView(eventY);}private void DrawView(float y) {int currentSelectIndex = -1;if (y != 0) {for (int i = 0; i < letters.length; i++) {float currentItemY = itemH * i;float nextItemY = itemH * (i + 1);if (y >= currentItemY && y < nextItemY) {currentSelectIndex = i;if (callBack != null) {callBack.onSelectStr(currentSelectIndex, letters[i]);}//画大的字母Paint.FontMetrics fontMetrics = bigTextPaint.getFontMetrics();float bigTextSize = fontMetrics.descent - fontMetrics.ascent;canvas.drawText(letters[i], w - getPaddingRight() - scaleWidth - bigTextSize, singleTextH + itemH * i, bigTextPaint);}}}drawLetters(y, currentSelectIndex);}private void drawLetters(float y, int index) {w = getMeasuredWidth();h = getMeasuredHeight();itemH = h / letters.length;Paint.FontMetrics fontMetrics = textPaint.getFontMetrics();singleTextH = fontMetrics.descent - fontMetrics.ascent;for (int i = 0; i < letters.length; i++) {canvas.drawText(letters[i], w - getPaddingRight(), singleTextH + itemH * i, textPaint);}}
}

自定义view,主要作用就是,显示侧面的字母栏,后期点击或者滑动时,能显示出点击的大字母。

2.2 xml布局

联系人界面的xml布局:

<RelativeLayoutxmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"tools:context=".fragment.ChatFragment"><androidx.recyclerview.widget.RecyclerViewandroid:id="@+id/recyclercontactlistId"android:layout_width="match_parent"android:layout_height="match_parent"/><com.Jason.materialdesign.widget.SideBarandroid:id="@+id/sidebar"android:layout_width="match_parent"android:layout_height="match_parent"android:layout_alignParentRight="true"android:background="@android:color/transparent"android:paddingRight="10dp"android:textSize="15sp" />
</RelativeLayout>

2.3 实现 adapter  主要是实现SectionIndexer接口

这个adapter主要是联系人的item布局,这个相对来说能理解,都直接原生库原生接口,应该能明白。主要是就是实现sectionIndexer接口,这儿稍微注意点。

public class ContactsAdapter extends RecyclerView.Adapter<ContactsAdapter.ContactsListHolder> implements SectionIndexer {/*** 联系人分区*/private SparseIntArray mPositionOfSection;/*** 联系人分区*/private SparseIntArray mSectionOfPosition;// 联系人列表private List<ContactData> mContactDataList;public ContactsAdapter(List<ContactData> contactDatas) {this.mContactDataList = contactDatas;}@NonNull@Overridepublic ContactsListHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.recycler_list_item_contact, parent,false);ContactsListHolder holder = new ContactsListHolder(view);return holder;}@Overridepublic void onBindViewHolder(@NonNull ContactsListHolder holder, int position) {ContactData contactData = mContactDataList.get(position);if (contactData != null){holder.avaterView.setImageResource(R.mipmap.default_avatar);holder.nameTxt.setText(contactData.getUserName());}}@Overridepublic int getItemCount() {return mContactDataList.size();}/***SectionIndexer分区方法*/@Overridepublic Object[] getSections() {mPositionOfSection = new SparseIntArray();mSectionOfPosition = new SparseIntArray();// 传入的联系人数量int count = mContactDataList.size();// list后期转换为arrayList<String> list = new ArrayList<>();list.add("");mPositionOfSection.put(0, 0);mSectionOfPosition.put(0, 0);// 开始分区for (int i = 1; i < count; i++){String letter = mContactDataList.get(i).getFirstLetter();int section = list.size() - 1;if (list.get(section) != null && !list.get(section).equals(letter)){list.add(letter);section++;mPositionOfSection.put(section, i);}mSectionOfPosition.put(i, section);}return list.toArray(new String[list.size()]);}/***SectionIndexer分区方法*/@Overridepublic int getPositionForSection(int sectionIndex) {return mPositionOfSection.get(sectionIndex);}/***SectionIndexer分区方法*/@Overridepublic int getSectionForPosition(int position) {return mSectionOfPosition.get(position);}/*** 普通联系人holder*/class ContactsListHolder extends RecyclerView.ViewHolder {ImageView avaterView;TextView nameTxt;public ContactsListHolder(View  view) {super(view);avaterView = (ImageView)view.findViewById(R.id.avatar);nameTxt = (TextView)view.findViewById(R.id.name);}}
}

2.4 ContactFragment的代码,包括一些初始化,比较排序等

public class ContactFragment extends Fragment {private ContactsAdapter mContactsAdapter;private RecyclerView mRecyclerView;private SideBar mSideBar;@Overridepublic void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);}@Overridepublic View onCreateView(LayoutInflater inflater, ViewGroup container,Bundle savedInstanceState) {// Inflate the layout for this fragmentView view = inflater.inflate(R.layout.fragment_contact, container, false);initContacts(view);return view;}/*** 装载数据* @param view 根view*/private void initContacts(View view){mRecyclerView = view.findViewById(R.id.recyclercontactlistId);mSideBar = view.findViewById(R.id.sidebar);mContactsAdapter = new ContactsAdapter(initTestData());LinearLayoutManager verticalLayoutManager = new LinearLayoutManager(this.getContext());mRecyclerView.setLayoutManager(verticalLayoutManager);mRecyclerView.setAdapter(mContactsAdapter);}/*** 初始化测试数据* @return*/private List<ContactData> initTestData(){List<ContactData> contactData = new ArrayList<>();contactData.add(new ContactData("alibaba", "123456", 1));contactData.add(new ContactData("tencent", "654321", 2));contactData.add(new ContactData("spaceon", "999999", 3));contactData.add(new ContactData("666", "0000", 4));contactData.add(new ContactData("老黄", "18381680077", 0));contactData.add(new ContactData("ligj", "1550805", 9));return contactData;}
}

初始化数据这里,还需要对首字母进行排序:

  /*** 联系人数据排序** @return*/private List<ContactData> getOrderContacts(List<ContactData> contactDataList) {List<ContactData> contactOrderList = new ArrayList<>(contactDataList.size());// 排序for (ContactData contactData : contactDataList) {String name = contactData.getUserName();String head = PinyinUtils.getPinyin(name.trim());//英文字符串时不能正确返回拼音首字母head = head.length() > 1 ? head.substring(0, 1) : head;head = (head.compareToIgnoreCase("A") < 0 || head.compareToIgnoreCase("Z") > 0) ? "#" : head;contactData.setFirstLetter(head);//根据head值进行排序从A-Zif (head.equals("#")) {contactOrderList.add(contactOrderList.size(), contactData);}else {int i;for (i = 0; i < contactOrderList.size(); i++) {String head1 = contactOrderList.get(i).getFirstLetter();if (head.compareToIgnoreCase(head1) <= 0 || head1.compareTo("#") == 0) {break;}}contactOrderList.add(i, contactData);}}//把首字母相同的归为一组(清空首字母)String lastHead = "";for (ContactData d : contactOrderList) {if (!lastHead.equals("")) {if (lastHead.equals(d.getFirstLetter())) {d.setFirstLetter("");}else {lastHead = d.getFirstLetter();}}else {lastHead = d.getFirstLetter();}}return contactOrderList;}

另外,sideBar的点击事件还要传进去。adpter的刷新等也要设置。具体见完整代码。

三,结果图


此专栏上一篇文章:BottomNavigationView+ViewPager+Fragment仿微信底部导航栏

github代码:我的github

继承SectionIndexer,实现联系人侧边栏相关推荐

  1. SideBar 仿微信联系人侧边栏界面搜索Viwe

    需要做了一个仿微信侧边烂搜索界面的View,这种简单的控件如果去github上的话,很麻烦,所以就自己写了一个,代码很简单,不多说,上代码 public class SideBar extends V ...

  2. Android contacts 联系人 通讯录 源码 完全解析

    Android contacts 联系人 通讯录 源码 完全解析 1简介 2软件架构 3各功能模块分析 1联系人数据的显示 1联系人列表显示 2联系人详细信息数据的显示 2联系人数据的编辑和存储 1编 ...

  3. ChatGPT教程之深入了解魔术背后的技术

    解开谜团:深入探索 ChatGPT 的技术奇迹. ChatGpt 无处不在,无论是在播客.博客.YouTube 还是社交媒体上.当我注意到这项新技术如此受欢迎时,我决定试一试,我被震惊了!有很多关于 ...

  4. 通讯录管理系统C++版

    用c++编写的基础版本,很好理解. 原创不易,禁止抄袭,转载请注明出处: 功能:编写一个通讯录系统包含手机通讯录和手机卡通讯录,实现对通讯录的添加.修改.删除.浏览.查找.以及通讯录的清空. 总体描述 ...

  5. Android 开发人员不得不收集的工具类集合

    RxTools 项目地址:vondear/RxTools  简介:Android 开发人员不得不收集的工具类集合 | 支付宝支付 | 微信支付(统一下单) | 微信分享 | Zip4j 压缩(支持分卷 ...

  6. 带滑动侧边栏的联系人界面

    先上效果图,呵呵,首先说明这个效果本人还是比较满意的~ 首先来分析一下页面元素的,其实就是左侧的一个listview和右侧的一个自定义linearlayout,右侧部分进行滑动时,左侧的联系人可以随着 ...

  7. Android开发制作带有侧边栏的联系人列表

    首先需要下载pinyin4j-2.5.0.jar包,这是下载链接https://sourceforge.net/projects/pinyin4j/,下载完成之后在lib文件夹下面能够找到该jar包. ...

  8. Android系统联系人全特效实现(上),分组导航和挤压动画

    转载请注明出处:http://blog.csdn.net/guolin_blog/article/details/9033553 记得在我刚接触Android的时候对系统联系人中的特效很感兴趣,它会根 ...

  9. 模仿QQ带侧边栏框架搭建

    侧边栏实现的两种方法 App展示: 方法一:SlidingMenu https://github.com/jfeinstein10/SlidingMenu 在GitHub上下载SlidingMenu文 ...

最新文章

  1. 经典的两数互换【指针】
  2. ICCV 2017 DSiam:《Learning Dynamic Siamese Network for Visual Object Tracking》论文笔记
  3. mug网络用语_下面这些短语你尽管翻译,全对算我输!
  4. 1005 继续(3n+1)猜想 (25 分)(c语言实现)
  5. 2019年ICPC银川区域赛 Easy Problem(简单莫比乌斯函数 + 欧拉降幂)
  6. 解决前后端交互Long类型精度丢失的问题
  7. java web 对cookie技术、session技术进行小结
  8. Asp.net中Global.asax
  9. 关于微信隐藏分享按钮的心得
  10. 4.IPv4和IPv6地址长度
  11. 商品-商品订单-支付订单
  12. Java买飞机票代码
  13. Discover Your Missed ASM Disks
  14. VS工程文件常见后缀名文件含义
  15. 第17次Scrum会议(10/29)【欢迎来怼】
  16. ios安装并信任证书
  17. 数据论《西游记》关系网:猪八戒最主动喜欢别人
  18. (Python实现中文分词最大匹配算法)研究生命的起源
  19. 如何理解区分HTTP状态码401和403
  20. 南开大学计算机学院宿舍,数说CS | 南开大学计算机学院推免生源大起底!

热门文章

  1. python 制作高斯mask_【趣味案例】用Python制作各种酷炫词云图,原来这么简单!...
  2. LMDIF_函数源码
  3. MFC多线程同步互斥
  4. 姚期智:这是一个“前所未有”的金融科技与计算机科学的黄金时代
  5. 修改点击cell时显示的颜色
  6. android项目中记录ListView滚动停止位置与设置显示位置
  7. [Cocoa, 医疗]Dicom Image Viewer for iPad
  8. [收藏]比较著名的.net技术论坛网址(含国外的)
  9. c++入门之初话结构体
  10. hihocoder编程练习赛75