前言

对于有导航条的列表我们一定是再熟悉不过了,很多通讯类应用都会使用sidebar来帮助用户快速定位到需要查看的地方,今天我们来亲手撸一个sidebar。
先上图:

列表数据排序

如果使用导航sidebar,则我们使用的数据必须是有序的。

如图中所示,我们对城市以首字符拼音排序。需要用到的工具类 PinyinUtils 可在github中查看源码。

    /*** 对数据重新排序*/private void initDatas() {String[] data = getResources().getStringArray(R.array.provinces);mDatas = new ArrayList<>();for (int i = 0; i < data.length; i++) {for (int j = 0; j < data.length - i - 1; j++) {if (PinyinUtils.getPinyinFirstLetter(data[j]).compareTo(PinyinUtils.getPinyinFirstLetter(data[j + 1])) > 0) {// 比较名称拼音首字符String tmp = data[j];data[j] = data[j + 1];data[j + 1] = tmp;}}}for (int i = 0; i < data.length; i++) {CityBean cityBean = new CityBean(PinyinUtils.getPinyinFirstLetter(data[i]).toUpperCase(), data[i]);mDatas.add(cityBean);}}

Adapter 实现 SectionIndexer 接口

A section is a group of list items that have something in common. For example, they may begin with the same letter or they may be songs from the same artist.
翻译过来就是一个section是一组具有共同点的列表项。 例如,他们可能以相同的字母开头,或者他们可能是同一位艺术家的歌曲。

SectionIndexer 接口中有三个方法

  1. Object[] getSections();
  2. int getPositionForSection(int section);
  3. int getSectionForPosition(int position);

我们需要做的就是重写 getPositionForSection 方法,通过 Section 获取位置信息。

@Overridepublic int getPositionForSection(int sectionIndex) {CityBean bean ;String firstLetter;if (sectionIndex == '!'){return 0;}else {for (int i = 0; i < mList.size();i++){bean = mList.get(i);// 取首字母firstLetter = PinyinUtils.getPinyinFirstLetter(bean.getCity());char firstChar = firstLetter.toUpperCase().charAt(0);if (firstChar == sectionIndex) {return i;}}}bean = null;firstLetter = null;return -1;}

自定义View

对于自定义控件,第一步当然是定义属性。我希望可改变的属性包括:

  • 默认背景色
  • 触摸后的背景色
  • 导航字体大小、颜色
  • 弹出框背景
  • 弹出框的宽高
  • 弹出框字体大小、颜色
  • 导航的文字
<?xml version="1.0" encoding="utf-8"?>
<resources><declare-styleable name="SideBar"><attr name="sidebar_background" format="color"/><attr name="sidebar_background_hint" format="color"/><attr name="sidebar_textcolor" format="color"/><attr name="sidebar_textsize" format="dimension"/><attr name="dialog_textcolor" format="color"/><attr name="dialog_textsize" format="dimension"/><attr name="dialog_background" format="color"/><attr name="dialog_width" format="dimension"/><attr name="dialog_height" format="dimension"/><attr name="sidebar_gidits" format="string"/></declare-styleable>
</resources>

获得自定义的属性

public SideBar(Context context) {this(context, null);}public SideBar(Context context, AttributeSet attrs) {this(context, attrs, 0);}public SideBar(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);// 获取属性值TypedArray array = context.getTheme().obtainStyledAttributes(attrs, R.styleable.SideBar, defStyleAttr, 0);digits = array.getString(R.styleable.SideBar_sidebar_gidits);backgroundColorHint = array.getColor(R.styleable.SideBar_sidebar_background_hint, backgroundColorHint);textColor = array.getColor(R.styleable.SideBar_sidebar_textcolor, textColor);sidebarTextsize = array.getDimensionPixelSize(R.styleable.SideBar_sidebar_textsize, sidebarTextsize);backgroundColor = array.getColor(R.styleable.SideBar_sidebar_background, backgroundColor);dialogTextsize = array.getDimensionPixelSize(R.styleable.SideBar_dialog_textsize, dialogTextsize);dialogTextColor = array.getColor(R.styleable.SideBar_dialog_textcolor, dialogTextColor);dialogBackground = array.getColor(R.styleable.SideBar_dialog_background, dialogBackground);mWidth = (int) array.getDimension(R.styleable.SideBar_dialog_width, mWidth);mHeight = (int) array.getDimension(R.styleable.SideBar_dialog_height, mHeight);array.recycle();init(context);paint = new Paint();}/*** 初始化导航栏* @param context*/private void init(Context context) {mLetters = new char[]{'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'};if (!TextUtils.isEmpty(digits)) {digits = digits.toUpperCase();mLetters = new char[digits.length()];for (int i = 0; i < digits.length(); i++) {mLetters[i] = digits.charAt(i);}}initDialogText(context);}

onDraw 绘制

1. 初始化 Paint

        paint.setAntiAlias(true); // 抗锯齿paint.setColor(textColor);paint.setTextSize(sidebarTextsize);paint.setStyle(Paint.Style.FILL);paint.setTextAlign(Paint.Align.CENTER);//居中绘制

2.设置背景色

setBackgroundColor(backgroundColorHint);

3.绘制导航文字

因为我们设置了 paint.setTextAlign(Paint.Align.CENTER);,所以文字的位置使用文字中心点来确定。用总高度/文字个数算出每个文字应占用的高度。

float widthCenter = getMeasuredWidth() / 2;if (mLetters.length > 0) {float height = getMeasuredHeight() / mLetters.length;for (int i = 0; i < mLetters.length; i++) {canvas.drawText(String.valueOf(mLetters[i]), widthCenter, (i + 1) * height, paint);}}

4.初始化弹出框

     /*** 初始化提示框* @param context*/private void initDialogText(Context context) {mDialogText = new TextView(context);mDialogText.setVisibility(View.INVISIBLE); // 默认不显示mDialogText.setTextSize(dialogTextsize);mDialogText.setTextColor(dialogTextColor);mDialogText.setBackgroundColor(dialogBackground);mDialogText.setMinHeight(mHeight);mDialogText.setMaxHeight(mHeight);mDialogText.setMinWidth(mWidth);mDialogText.setMaxWidth(mWidth);mDialogText.setGravity(Gravity.CENTER);WindowManager mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);WindowManager.LayoutParams lp = new WindowManager.LayoutParams(WindowManager.LayoutParams.WRAP_CONTENT,WindowManager.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.TYPE_APPLICATION,WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,PixelFormat.TRANSLUCENT);mWindowManager.addView(mDialogText, lp);}

5.设置导航条事件监听接听

    /*** sidebar 选中监听*/public interface onSelecListener {void setSelection(int position);}public void setOnSelecListener(onSelecListener listener) {this.listener = listener;}

6.重写 onTouchEvent 方法

// 为了确定位置,Adapter 需要实现 SectionIndexer 接口
private SectionIndexer mSectionIndexer = null;
@Overridepublic boolean onTouchEvent(MotionEvent event) {super.onTouchEvent(event);int y = (int) event.getY();int idx = y / (getHeight() / mLetters.length);if (idx >= mLetters.length) {idx = mLetters.length - 1;} else if (idx < 0) {idx = 0;}if (event.getAction() == MotionEvent.ACTION_DOWN || event.getAction() == MotionEvent.ACTION_MOVE) {setBackgroundColor(backgroundColor); // 改变背景颜色mDialogText.setVisibility(View.VISIBLE); // 显示弹出框mDialogText.setText(String.valueOf(mLetters[idx]));// 设置弹出框文字if (mSectionIndexer == null) {throw new NullPointerException("mSectionIndexer can't be null");}int position = mSectionIndexer.getPositionForSection(mLetters[idx]);if (position == -1) {return true;}if (listener != null) {listener.setSelection(position);}} else {mDialogText.setVisibility(View.INVISIBLE);}if (event.getAction() == MotionEvent.ACTION_UP) {setBackgroundColor(backgroundColorHint);}return true;}

列表的跳转需要在页面中调用 SideBar.setOnSelecListener 方法实现 onSelecListener 接口。

使用

     <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"……/><android.support.v7.widget.RecyclerView……/><com.zhang.sidebar_demo.SideBarandroid:id="@+id/sideBar"android:layout_width="25dp"android:layout_height="match_parent"android:layout_alignParentEnd="true"android:layout_gravity="end|center_vertical" />
</RelativeLayout>

在页面中需要调用 sideBar 的 setOnSelecListener 方法。因为RecyclerView的 smoothScrollToPosition 方法有一些小问题,我们需要自己写列表的跳转方法。具体可见http://blog.csdn.net/u014527323/article/details/69389529。

        sideBar = (SideBar) findViewById(R.id.sideBar);sideBar.setSectionIndexer((SectionIndexer) mRecyclerView.getAdapter());sideBar.setOnSelecListener(new SideBar.onSelecListener() {@Overridepublic void setSelection(int position) {smoothMoveToPosition(mRecyclerView,position);}});/*** 滑动到指定位置* @param mRecyclerView* @param position*/private void smoothMoveToPosition(RecyclerView mRecyclerView, final int position) {// 第一个可见位置int firstItem = mRecyclerView.getChildLayoutPosition(mRecyclerView.getChildAt(0));// 最后一个可见位置int lastItem = mRecyclerView.getChildLayoutPosition(mRecyclerView.getChildAt(mRecyclerView.getChildCount() - 1));if (position < firstItem) {// 如果跳转位置在第一个可见位置之前,就smoothScrollToPosition可以直接跳转mRecyclerView.smoothScrollToPosition(position);} else if (position <= lastItem) {// 跳转位置在第一个可见项之后,最后一个可见项之前// smoothScrollToPosition根本不会动,此时调用smoothScrollBy来滑动到指定位置int movePosition = position - firstItem;if (movePosition >= 0 && movePosition < mRecyclerView.getChildCount()) {int top = mRecyclerView.getChildAt(movePosition).getTop();mRecyclerView.smoothScrollBy(0, top);}}else {// 如果要跳转的位置在最后可见项之后,则先调用smoothScrollToPosition将要跳转的位置滚动到可见位置// 再通过onScrollStateChanged控制再次调用smoothMoveToPosition,进入上一个控制语句mRecyclerView.smoothScrollToPosition(position);mToPosition = position;mShouldScroll = true;}}

源码

附上github 地址
https://github.com/695336128/SideBar-Demo。

自定义View-SideBar相关推荐

  1. 红橙Darren视频笔记 自定义sidebar 自定义View ViewGroup套路

    参考链接 https://www.jianshu.com/p/1dc41a770f64 1效果 2 目的 学习onMeasure onDraw onTouchEvent等自定义view方法的使用 3 ...

  2. Android自定义View基本步骤

    一.自定义属性 1.在res下的values下面新建attrs.xml 2.在布局中使用,声明命名空间 3.在自定义View构造方法中通过TypedArray获取属性 4.必须回收 array.rec ...

  3. Android自定义View —— TypedArray

    在上一篇中Android 自定义View Canvas -- Bitmap写到了TypedArray 这个属性 下面也简单的说一下TypedArray的使用 TypedArray 的作用: 用于从该结 ...

  4. Android 自定义View Canvas —— Bitmap

    Bitmap 绘制图片 常用的方法有一下几种 (1) drawBitmap(@NonNull Bitmap bitmap, float left, float top, @Nullable Paint ...

  5. Android 自定义View —— Canvas

    上一篇在android 自定义view Paint 里面 说了几种常见的Point 属性 绘制图形的时候下面总有一个canvas ,Canvas 是是画布 上面可以绘制点,线,正方形,圆,等等,需要和 ...

  6. Android 自定义View —— Paint

    上一篇说了自定义view的坐标系以及view 的使用,下面说下自定义view Paint 的使用 Paint 相对于画笔 ,可以使用Paint 来决定画的内容的颜色,边距粗细,设置样式,字体大小 ,等 ...

  7. Android 自定义View (入门 篇) 的使用

    每次都是过了很久都需要温习一下,自己打算整理一下方便查阅, 自定义view 首选需要明白的就是它的坐标系了,以手机左上角为起始点(0.0),横向的为x轴,竖向的为y轴 为了更好的理解我画了一幅草图如下 ...

  8. 28自定义View 模仿联系人字母侧栏

    自定义View LetterView.java package com.qf.sxy.customview02;import android.content.Context; import andro ...

  9. android炫酷的自定义view,Android自定义View实现炫酷进度条

    本文实例为大家分享了Android实现炫酷进度条的具体代码,供大家参考,具体内容如下 下面我们来实现如下效果: 第一步:创建attrs文件夹,自定义属性: 第二步:自定义View: /** * Cre ...

  10. android 自定义音乐圆形进度条,Android自定义View实现音频播放圆形进度条

    本篇文章介绍自定义View配合属性动画来实现如下的效果 实现思路如下: 根据播放按钮的图片大小计算出圆形进度条的大小 根据音频的时间长度计算出圆形进度条绘制的弧度 通过Handler刷新界面来更新圆形 ...

最新文章

  1. 怎么在python下载网站内容-用Python下载一个网页保存为本地的HTML文件实例
  2. ReactiveCocoa的使用方法
  3. OC__part11.2
  4. javaweb学习总结(二十九)——EL表达式
  5. ZHS16GBK的数据库导入到字符集为AL32UTF8的数据库
  6. 数据分析面试必考的AB-Test知识点整理
  7. Java 中removelinked_Java LinkedHashSet remove()用法及代码示例
  8. c语言实现配置文件的读写
  9. sqlite 的几点见解
  10. python e_pythone函数基础(8)内置函数学习
  11. 案例 项目经理评分 c# 1613922661
  12. 计算机信息专业又分为哪三种,计算机类型大致分为哪三类
  13. Windows2003四大必知版本
  14. linux yum 命令 详解
  15. 动易html编辑器漏洞,动易网站管理系统删除任意文件漏洞
  16. 2021-2027全球与中国编码器附件市场现状及未来发展趋势
  17. 计算机体系结构期末重点,计算机系统结构期末重点题目及考点
  18. Triplet-Graph Reasoning Network for few-shot Metal Generic Surface Defect Segmentation论文理解
  19. 微服务团队_为什么团队文化对于成功的微服务至关重要
  20. 智汀双管齐下,玩转米家HomeKit智能家居

热门文章

  1. change丶未来科技公众号成立了!!!!!!!!!
  2. 广东未来科技荣膺2021粤港澳大湾区新经济企业100强
  3. 在微信小程序中 调用前置摄像头拍照 后置摄像头拍照扫码
  4. html files是什么文件夹,电脑硬盘里的program files文件夹是什么意思
  5. 中国直接针对消费者的疾病风险和健康DNA测试行业市场供需与战略研究报告
  6. 一套完整的动环监控系统,适用于各类机房、学校机房、医院机房、银行库房等
  7. 《Java Testing with Spock》_4写单元测试
  8. WinZip Pro for Mac专业文件解压缩软件
  9. python 二维坐标多边形 计算多边形中心点,以及距该中心点最远的距离
  10. 是程序员都可能用到版本控制,如何使用它?如何在Vim中使用Git?