Android自定义A-Z 列表

项目中需要用到A-Z的列表,点击字母可以滚动到列表中对应字母位置,在百度谷歌上转了一圈,没看见合适的,又只能自己来了。。。。。。。。

先看下怎么使用:
xml中:

<com.example.siderquickbar.SiderQuickBarView
        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<ItemBeans> list = new ArrayList<>();
        for(int i=0;i<str.length;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()
                );
    }
}

自定义属性:

 <!--右侧字体颜色-->
        <attr name="sliderLetterColor" format="color"> </attr>
        <!--右侧字体大小-->
        <attr name="sliderLetterWidth" format="dimension"> </attr>
        <!--分类字母背景颜色-->
        <attr name="siderQuickItemTypebgColor" format="color"> </attr>
        <!--分类字母颜色-->
        <attr name="siderQuickItemTypeTextColor" format="color"> </attr>
        <!--分类字母大小-->
        <attr name="siderQuickItemTypeTextWidth" format="dimension"> </attr>
        <!--列表项颜色-->
        <attr name="itemColor" format="color"> </attr>

只需要把任意文字转成list ,按规则传入,即可自动按字母排序,点击后可返回当前字母和字母对应的key值。

QQ图片20200119200829.jpg
QQ图片20200119200837.jpg

下面说一下实现的思路:
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<LetterBean> letterBeans;
    private List<CityBean> 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<letterBeans.size();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<ItemBeans> letters){
        Map<String, List<ItemBeans>> letterMap = letters.stream().collect(Collectors.groupingBy(letter -> PinYinUtils.getFirstLetter(letter.getValues())));
        Iterator<Map.Entry<String, List<ItemBeans>>> itLetter = letterMap.entrySet().iterator();
        cityBeans = new ArrayList<>();
        letterBeans = new ArrayList<>();
        Iterator<String> 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<LetterBean> contentList = new ArrayList<LetterBean>();
            List<ItemBeans> 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<CityBean> {

        @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<String, List<ItemBeans>> 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<CityBean> {

        @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<letters.length;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滚动到指定位置做准备。

思路大概就是这个样子,只要思路是对的,东西就好写了。。。。完

慢。提前祝各位新年快乐!!!!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,444评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,421评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,036评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,363评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,460评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,502评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,511评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,280评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,736评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,014评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,190评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,848评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,531评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,159评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,411评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,067评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,078评论 2 352