Android自定义实现A-Z列表功能

共 12839字,需浏览 26分钟

 ·

2021-11-29 20:33

项目中需要用到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","都是","一","一","商店","商店","发","一",            "一","阿萨德","一","商店",};

@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); List list = new ArrayList<>(); for(int i=0;i ItemBeans 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 List letterBeans;    private List cityBeans;
//自定义属性 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);
//GuiBar guideBbar = 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;i if(letter.equals(letterBeans.get(i).getLetterName())){ moveToPosition(i); break; } } Toast.makeText(context,letter,Toast.LENGTH_SHORT).show(); });
rvLetter.addOnScrollListener(new RecyclerView.OnScrollListener() { @Override public 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(List letters){ Map> letterMap = letters.stream().collect(Collectors.groupingBy(letter -> PinYinUtils.getFirstLetter(letter.getValues()))); Iterator>> itLetter = letterMap.entrySet().iterator(); cityBeans = new ArrayList<>(); letterBeans = new ArrayList<>(); Iterator keySet = 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 -> { List contentList = new ArrayList(); List itemBeans = 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 {
@Override public 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 {
@Override public 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); }
@Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); double screenHeight = getHeight(); itemHeight = screenHeight /letters.length; float letterX = (float)getWidth()/2; for(int i=0;i canvas.drawText(letters[i],letterX,(float) ((i+1)*itemHeight),paint); } }
@SuppressLint("ClickableViewAccessibility") @Override public 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; }
@Override protected 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滚动到指定位置做准备。

浏览 41
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报