Android自定义实现A-Z列表功能
项目中需要用到A-Z的列表,点击字母可以滚动到列表中对应字母位置。
先看下怎么使用:
xml中:
android:id="@+id/siderbar_letters"android:layout_width="match_parent"android:layout_height="match_parent"app:siderQuickItemTypebgColor="#3F88FF"app:sliderLetterColor="#808080"app:sliderLetterWidth="12sp"app:siderQuickItemTypeTextWidth="14sp"app:siderQuickItemTypeTextColor="#FFFFFF"/>
java中:
public class MainActivity extends AppCompatActivity {private String[] str = new String[]{"一","二","三","死","一","一","一","一","商店","一","重新","我","as","第","是","把","留","阿萨德","as","都是","一","一","商店","商店","发","一","一","阿萨德","一","商店",};@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);Listlist = new ArrayList<>(); for(int i=0;iItemBeans itemBeans = new ItemBeans();itemBeans.setType(i+1);itemBeans.setValues(str[i]);list.add(itemBeans);}SiderQuickBarView siderQuickBarView = findViewById(R.id.siderbar_letters);siderQuickBarView.setLetters(list).setOnItemClickListener(letterBean ->Toast.makeText(this,"type==>"+letterBean.getType()+" value==>"+letterBean.getLetterName(),Toast.LENGTH_SHORT).show());}}
自定义属性:
只需要把任意文字转成list ,按规则传入,即可自动按字母排序,点击后可返回当前字母和字母对应的key值。
效果图:

下面说一下实现的思路:
1.控件由两部分构成:下面的RecyclerView和右侧的自定义字母,使用的是帧布局。
2.将输入的文字按字母分类。
3.将分类好的字母放入新的list中,字母和字母对应的文字用type进行区分。
4.为RecyclerView创建适配器,添加点击事件。
5.为字母添加点击事件,并使RecyclerView滑动。
6.封装自定义属性。
好了,放一下主要代码:
public class SiderQuickBarView extends FrameLayout {private RecyclerView rvLetter;private SliderLetterView sliderLetterView;private GuideBbar guideBbar;private Context context;private ListletterBeans; private ListcityBeans; //自定义属性private int siderQuickItemTypebgColor;private int siderQuickItemTypeTvColor;private float siderQuickItemTypeTextWidth;private int sliderLetterColor;private int sliderLetterWidth;private OnItemClickListener onItemClickListener;public SiderQuickBarView(Context context) {super(context);initView(context);}public SiderQuickBarView(Context context, AttributeSet attrs) {super(context, attrs);TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.SiderQuickBarView);siderQuickItemTypebgColor = array.getColor(R.styleable.SiderQuickBarView_siderQuickItemTypebgColor, ContextCompat.getColor(context,R.color.colorAccent));siderQuickItemTypeTvColor = array.getColor(R.styleable.SiderQuickBarView_siderQuickItemTypeTextColor,ContextCompat.getColor(context,R.color.whilte));siderQuickItemTypeTextWidth = array.getDimensionPixelSize(R.styleable.SiderQuickBarView_siderQuickItemTypeTextWidth,20);sliderLetterColor = array.getColor(R.styleable.SiderQuickBarView_sliderLetterColor,ContextCompat.getColor(context,R.color.black));sliderLetterWidth = array.getDimensionPixelSize(R.styleable.SiderQuickBarView_sliderLetterWidth,50);initView(context);array.recycle();}private void initView(Context context) {this.context = context;//左边列表rvLetter = new RecyclerView(context);rvLetter.setLayoutManager(new LinearLayoutManager(context));LayoutParams layoutParams = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,LayoutParams.MATCH_PARENT);rvLetter.setLayoutParams(layoutParams);//右边字母列表sliderLetterView = new SliderLetterView(context,sliderLetterColor,sliderLetterWidth);LayoutParams layoutParams1 = new LayoutParams(LayoutParams.WRAP_CONTENT,LayoutParams.WRAP_CONTENT);layoutParams1.gravity = Gravity.END;layoutParams1.bottomMargin = 10;sliderLetterView.setLayoutParams(layoutParams1);//GuiBarguideBbar = new GuideBbar(context);FrameLayout.LayoutParams layoutParams2 = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,ViewGroup.LayoutParams.WRAP_CONTENT);layoutParams2.gravity = Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL;layoutParams2.bottomMargin = 100;guideBbar.setLayoutParams(layoutParams2);addView(rvLetter);addView(sliderLetterView);// addView(guideBbar);//字母监听setListener();}private void setListener() {sliderLetterView.setOnLetterTouchListener(letter -> {for(int i=0;iif(letter.equals(letterBeans.get(i).getLetterName())){moveToPosition(i);break;}}Toast.makeText(context,letter,Toast.LENGTH_SHORT).show();});rvLetter.addOnScrollListener(new RecyclerView.OnScrollListener() {@Overridepublic void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {super.onScrolled(recyclerView, dx, dy);double scrollY = recyclerView.computeVerticalScrollOffset();double s = recyclerView.computeVerticalScrollExtent();double sum = recyclerView.computeVerticalScrollRange()-s;double f = scrollY/sum;guideBbar.setGuideX(f);}});}private void moveToPosition(int position) {if (position != -1) {rvLetter.scrollToPosition(position);LinearLayoutManager mLayoutManager =(LinearLayoutManager) rvLetter.getLayoutManager();assert mLayoutManager != null;mLayoutManager.scrollToPositionWithOffset(position, 0);}}public SiderQuickBarView setLetters(Listletters){ Map> letterMap = letters.stream().collect(Collectors.groupingBy(letter -> PinYinUtils.getFirstLetter(letter.getValues()))); Iterator>> itLetter = letterMap.entrySet().iterator(); cityBeans = new ArrayList<>();letterBeans = new ArrayList<>();IteratorkeySet = letterMap.keySet().iterator(); while (keySet.hasNext()){CityBean cityBean = new CityBean();cityBean.setKey(keySet.next());cityBeans.add(cityBean);}//对字母排序SiderComparator siderComparator = new SiderComparator();Collections.sort(cityBeans,siderComparator);cityBeans.forEach(cityBean -> {ListcontentList = new ArrayList (); ListitemBeans = letterMap.get(cityBean.getKey()); LetterBean letterBean = new LetterBean();letterBean.setLetterType(LetterType.title);letterBean.setLetterName(cityBean.getKey());contentList.add(letterBean);itemBeans.forEach(item -> {LetterBean letterBean1 = new LetterBean();letterBean1.setLetterType(LetterType.contet);letterBean1.setType(item.getType());letterBean1.setLetterName(item.getValues());contentList.add(letterBean1);});cityBean.setBeanList(contentList);});cityBeans.forEach(cityBean -> {letterBeans.addAll(cityBean.getBeanList());});LetterListAdapter letterListAdapter = new LetterListAdapter(context, letterBeans, siderQuickItemTypeTvColor, siderQuickItemTypebgColor, siderQuickItemTypeTextWidth);rvLetter.setAdapter(letterListAdapter);letterListAdapter.setOnItemClickListener((adapter, position) -> {LetterListAdapter la = (LetterListAdapter) adapter;LetterBean item = la.getItem(position);if(onItemClickListener != null){onItemClickListener.onClick(item);}});return this;}interface OnItemClickListener{void onClick(LetterBean letterBean);}public SiderQuickBarView setOnItemClickListener(OnItemClickListener onItemClickListener){this.onItemClickListener = onItemClickListener;return this;}class SiderComparator implements Comparator{ @Overridepublic int compare(CityBean cityBean1, CityBean cityBean2) {return cityBean1.getKey().compareTo(cityBean2.getKey());}}@Retention(RetentionPolicy.SOURCE)@IntDef({LetterType.title,LetterType.contet})public @interface LetterType{int title = 0;int contet = 1;}}
这个就是整个控件了,继承自FrameLayout,包含一个RecyclerView和右边的自定义字母(忽略被注释的View)
这里最主要的就是将输入的字符串信息,按照字母分类,我这里用到了一个第三库:
implementation 'com.belerweb:pinyin4j:2.5.1'外加这个库的一个工具类:
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 (int i = 0; i < input.length; i++) {if (java.lang.Character.toString(input[i]).matches("[\\u4E00-\\u9FA5]+")) {String[] temp = PinyinHelper.toHanyuPinyinStringArray(input[i], format);output += temp[0];} else {output += java.lang.Character.toString(input[i]);}}} catch (BadHanyuPinyinOutputFormatCombination e) {e.printStackTrace();}return output;}/*** 获取汉字串拼音首字母,英文字符不变* @param chinese 汉字串* @return 汉语拼音首字母*/public static String getFirstSpell(String chinese) {StringBuffer pybf = new StringBuffer();char[] arr = chinese.toCharArray();HanyuPinyinOutputFormat defaultFormat = new HanyuPinyinOutputFormat();defaultFormat.setCaseType(HanyuPinyinCaseType.LOWERCASE);defaultFormat.setToneType(HanyuPinyinToneType.WITHOUT_TONE);for (int i = 0; i < arr.length; i++) {if (arr[i] > 128) {try {String[] temp = PinyinHelper.toHanyuPinyinStringArray(arr[i], defaultFormat);if (temp != null) {pybf.append(temp[0].charAt(0));}} catch (BadHanyuPinyinOutputFormatCombination e) {e.printStackTrace();}} else {pybf.append(arr[i]);}}return pybf.toString().replaceAll("\\W", "").trim();}/*** 获取汉字串拼音,英文字符不变* @param chinese 汉字串* @return 汉语拼音*/public static String getFullSpell(String chinese) {StringBuffer pybf = new StringBuffer();char[] arr = chinese.toCharArray();HanyuPinyinOutputFormat defaultFormat = new HanyuPinyinOutputFormat();defaultFormat.setCaseType(HanyuPinyinCaseType.LOWERCASE);defaultFormat.setToneType(HanyuPinyinToneType.WITHOUT_TONE);for (int i = 0; i < arr.length; i++) {if (arr[i] > 128) {try {pybf.append(PinyinHelper.toHanyuPinyinStringArray(arr[i], defaultFormat)[0]);} catch (BadHanyuPinyinOutputFormatCombination e) {e.printStackTrace();}} else {pybf.append(arr[i]);}}return pybf.toString();}/** 获取首字母* @param letter* @return*/public static String getFirstLetter(String letter){StringBuffer pybf = new StringBuffer();char[] arr = letter.toCharArray();HanyuPinyinOutputFormat defaultFormat = new HanyuPinyinOutputFormat();defaultFormat.setCaseType(HanyuPinyinCaseType.LOWERCASE);defaultFormat.setToneType(HanyuPinyinToneType.WITHOUT_TONE);for (int i = 0; i < arr.length; i++) {if (arr[i] > 128) {try {pybf.append(PinyinHelper.toHanyuPinyinStringArray(arr[i], defaultFormat)[0]);} catch (BadHanyuPinyinOutputFormatCombination e) {e.printStackTrace();}} else {pybf.append(arr[i]);}}return pybf.substring(0,1);}}
这个工具类的作用,可以将文字的首字母给取出来, 那么取出来干嘛呢??
下面我正好借助lamda表达式,根据首字母将数据进行分组:
Map> letterMap = letters.stream().collect(Collectors.groupingBy(letter -> PinYinUtils.getFirstLetter(letter.getValues()))); 分完组之后的数据就是一个map集合 map的key就是 各数据对应的首字母,value就是各首字母对应的数据,再将map中的数据,放入一个新的list中,所以现在混乱数据就变成了一组组有序集合了,但现在还不够,因为是按照A-Z排序,目前不是按A-Z,可能是g,f,a,d 这样乱排序的,只是把数据都放在了它们各自的首字母下面而已,那么,下面就借助Comparator这个接口让字母按照A-Z的顺序排序:
class SiderComparator implements Comparator{ @Overridepublic int compare(CityBean cityBean1, CityBean cityBean2) {return cityBean1.getKey().compareTo(cityBean2.getKey());}}//对字母排序SiderComparator siderComparator = new SiderComparator();Collections.sort(cityBeans,siderComparator);
排序完成之后,这个list就可以像普通的list一样放入RecyclerView的适配器中使用了。
下面看一下自定义的字母:
/*** 右边字母自定义* @author amggy*/public class SliderLetterView extends View {private Paint paint;private String[] letters;private double itemHeight;private int position;private OnLetterTouchListener onLetterTouchListener;private int paintColor;private int paintStrokWith;public SliderLetterView(Context context,int paintColor,int paintStrokWith) {super(context);this.paintColor = paintColor;this.paintStrokWith = paintStrokWith;initView(context);}public SliderLetterView(Context context, @Nullable AttributeSet attrs) {super(context, attrs);initView(context);}public SliderLetterView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);initView(context);}private void initView(Context context) {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"};position = 0;paint = new Paint();paint.setAntiAlias(true);paint.setDither(true);paint.setColor(paintColor);paint.setTextSize(paintStrokWith);paint.setTextAlign(Paint.Align.CENTER);}@Overrideprotected void onDraw(Canvas canvas) {super.onDraw(canvas);double screenHeight = getHeight();itemHeight = screenHeight /letters.length;float letterX = (float)getWidth()/2;for(int i=0;icanvas.drawText(letters[i],letterX,(float) ((i+1)*itemHeight),paint);}}@SuppressLint("ClickableViewAccessibility")@Overridepublic boolean onTouchEvent(MotionEvent event) {switch (event.getAction()){case MotionEvent.ACTION_DOWN:float eventY = event.getY();position = (int) (eventY/itemHeight);if(onLetterTouchListener != null){onLetterTouchListener.onTouch(letters[position]);}break;case MotionEvent.ACTION_MOVE:break;case MotionEvent.ACTION_UP:break;default:break;}return true;}@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {super.onMeasure(widthMeasureSpec, heightMeasureSpec);int with = MeasureSpec.getSize(widthMeasureSpec);int withMode = MeasureSpec.getMode(widthMeasureSpec);int high = MeasureSpec.getSize(heightMeasureSpec);int highMode = MeasureSpec.getMode(heightMeasureSpec);int w = 0;int h = 0;switch(withMode){case MeasureSpec.EXACTLY:w = with;break;case MeasureSpec.AT_MOST:w = 80;break;default:break;}switch(highMode){case MeasureSpec.EXACTLY:h = high;break;case MeasureSpec.AT_MOST:h = getMeasuredHeight();break;default:break;}setMeasuredDimension(w,h);}public void setOnLetterTouchListener(OnLetterTouchListener onLetterTouchListener){this.onLetterTouchListener = onLetterTouchListener;}public interface OnLetterTouchListener{/** 返回选中字母* @param letter 字母*/void onTouch(String letter);}}
自定义字母继承自View,按照控件的高度,等分的绘制字母,添加onTouch事件,将触碰到的字母返回出去,给RecyclerView滚动到指定位置做准备。
