Android自定義實現(xiàn)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","都是","一","一","商店","商店","發(fā)","一","一","阿薩德","一","商店",};@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());}}
自定義屬性:
只需要把任意文字轉(zhuǎn)成list ,按規(guī)則傳入,即可自動按字母排序,點擊后可返回當前字母和字母對應的key值。
效果圖:

下面說一下實現(xiàn)的思路:
????1.控件由兩部分構(gòu)成:下面的RecyclerView和右側(cè)的自定義字母,使用的是幀布局。
????2.將輸入的文字按字母分類。
? ? 3.將分類好的字母放入新的list中,字母和字母對應的文字用type進行區(qū)分。
????4.為RecyclerView創(chuàng)建適配器,添加點擊事件。
????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);//字母監(jiān)聽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 {/*** 將字符串中的中文轉(zhuǎn)化為拼音,其他字符不變** @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表達式,根據(jù)首字母將數(shù)據(jù)進行分組:
Map> letterMap = letters.stream().collect(Collectors.groupingBy(letter -> PinYinUtils.getFirstLetter(letter.getValues()))); 分完組之后的數(shù)據(jù)就是一個map集合 map的key就是 各數(shù)據(jù)對應的首字母,value就是各首字母對應的數(shù)據(jù),再將map中的數(shù)據(jù),放入一個新的list中,所以現(xiàn)在混亂數(shù)據(jù)就變成了一組組有序集合了,但現(xiàn)在還不夠,因為是按照A-Z排序,目前不是按A-Z,可能是g,f,a,d 這樣亂排序的,只是把數(shù)據(jù)都放在了它們各自的首字母下面而已,那么,下面就借助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滾動到指定位置做準備。
