Android 使用RecyclerView实现快速索引

来源:互联网 发布:淘宝白底图片怎么拍 编辑:程序博客网 时间:2024/05/30 18:30

之前做项目的时候遇到一个需求是实现品牌的字母排序功能,网上的资料很多,但是有一部分有bug,这篇文章是我学习和解决部分bug之后的总结。今天带来的是RecyclerView的A-Z字母排序和过滤搜索功能。 
首先上效果图: 
这里写图片描述

重点:1、实现数据排序分类 2、实现右侧的字母导航 3、搜索

这里使用了一个中文转化为拼音的工具包,即pinyin4j-2.5.0.jar。官网地址:http://pinyin4j.sourceforge.net/

布局顶部是一个带删除按钮的文本编辑框,我们在输入框中输入字母或汉字可以自动过滤出我们想要的东西,当输入框中没有数据自动替换到原来的数据列表,然后下面一个RecyclerView用来显示数据列表,右侧是一个字母索引表,其实就是一个自定的View,当我们点击不同的字母,RecyclerView会定位到该字母索引地item。

这是主界面布局代码:

<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    android:layout_width="match_parent"    android:layout_height="match_parent"    android:orientation="vertical"    android:focusable="true"    android:focusableInTouchMode="true">    <com.xp.sortrecyclerview.ClearEditText        android:id="@+id/filter_edit"        android:layout_width="match_parent"        android:layout_height="wrap_content"        android:paddingLeft="8dp"        android:background="#9DDE76"        android:drawableLeft="@drawable/search_bar_icon_normal"        android:hint="请输入关键字"        android:maxLines="1"        android:textSize="15dp" />    <RelativeLayout        android:layout_width="match_parent"        android:layout_height="match_parent" >        <android.support.v7.widget.RecyclerView            android:id="@+id/recyclerView"            android:layout_width="match_parent"            android:layout_height="match_parent"            />        <TextView            android:id="@+id/dialog"            android:layout_width="80dp"            android:layout_height="80dp"            android:layout_centerInParent="true"            android:background="#9DDE76"            android:gravity="center"            android:textColor="#ffffffff"            android:textSize="30dp"            android:visibility="invisible" />        <com.xp.sortrecyclerview.SideBar            android:id="@+id/sideBar"            android:layout_width="30dp"            android:layout_height="match_parent"            android:layout_alignParentRight="true"            />    </RelativeLayout></LinearLayout>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49

之前出现过两个问题,一个就是,每次进入界面后EditText自动获取焦点,导致输入法自动弹出来,所以为了解决这个问题在EditText的父布局也就是我们的根布局加了两个属性:android:focusable=”true”和android:focusableInTouchMode=”true”。 
还有一个问题就是侧边栏会被输入法顶上去,结解决办法是在清单配置文件的对应Activity加上android:windowSoftInputMode=”adjustPan”,防止布局被输入法顶上去。 
中间的TextView是用来显示选中的字母索引。

主界面的话主要是三个方法: 
初始化的方法主要是对比较器的初始化,设置监听,对数据排序和对RecyclerView的初始化。 
代码:

private void initViews() {        //初始化比较器        pinyinComparator = new PinyinComparator();        sideBar = (SideBar) findViewById(R.id.sideBar);        dialog = (TextView) findViewById(R.id.dialog);        sideBar.setTextView(dialog);        //设置右侧SideBar触摸监听        sideBar.setOnTouchingLetterChangedListener(new SideBar.OnTouchingLetterChangedListener() {            @Override            public void onTouchingLetterChanged(String s) {                //该字母首次出现的位置                int position = adapter.getPositionForSection(s.charAt(0));                if (position != -1) {                    manager.scrollToPositionWithOffset(position, 0);                }            }        });        mRecyclerView = (RecyclerView) findViewById(R.id.recyclerView);        SourceDateList = filledData(getResources().getStringArray(R.array.date));        // 根据a-z进行排序源数据        Collections.sort(SourceDateList, pinyinComparator);        //RecyclerView社置manager        manager = new LinearLayoutManager(this);        manager.setOrientation(LinearLayoutManager.VERTICAL);        mRecyclerView.setLayoutManager(manager);        adapter = new SortAdapter(this, SourceDateList);        mRecyclerView.setAdapter(adapter);        //item点击事件        /*adapter.setOnItemClickListener(new SortAdapter.OnItemClickListener() {            @Override            public void onItemClick(View view, int position) {                Toast.makeText(MainActivity.this, ((SortModel)adapter.getItem(position)).getName(),Toast.LENGTH_SHORT).show();            }        });*/        mClearEditText = (ClearEditText) findViewById(R.id.filter_edit);        //根据输入框输入值的改变来过滤搜索        mClearEditText.addTextChangedListener(new TextWatcher() {            @Override            public void onTextChanged(CharSequence s, int start, int before, int count) {                //当输入框里面的值为空,更新为原来的列表,否则为过滤数据列表                filterData(s.toString());            }            @Override            public void beforeTextChanged(CharSequence s, int start, int count,                                          int after) {            }            @Override            public void afterTextChanged(Editable s) {            }        });    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62

接下来是将数据列表的内容按拼音排序的方法,先将汉字转化成拼音,在用正则表达式分类,下边是代码:

private List<SortModel> filledData(String[] date) {        List<SortModel> mSortList = new ArrayList<>();        for (int i = 0; i < date.length; i++) {            SortModel sortModel = new SortModel();            sortModel.setName(date[i]);            //汉字转换成拼音            String pinyin = PinyinUtils.getPingYin(date[i]);            String sortString = pinyin.substring(0, 1).toUpperCase();            // 正则表达式,判断首字母是否是英文字母            if (sortString.matches("[A-Z]")) {                sortModel.setLetters(sortString.toUpperCase());            } else {                sortModel.setLetters("#");            }            mSortList.add(sortModel);        }        return mSortList;    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

最后就是根据输入的内容进行数据筛选的方法:

private void filterData(String filterStr) {        List<SortModel> filterDateList = new ArrayList<>();        if (TextUtils.isEmpty(filterStr)) {            filterDateList = SourceDateList;        } else {            filterDateList.clear();            for (SortModel sortModel : SourceDateList) {                String name = sortModel.getName();                if (name.indexOf(filterStr.toString()) != -1 ||                        PinyinUtils.getFirstSpell(name).startsWith(filterStr.toString())                        //不区分大小写                        || PinyinUtils.getFirstSpell(name).toLowerCase().startsWith(filterStr.toString())                        || PinyinUtils.getFirstSpell(name).toUpperCase().startsWith(filterStr.toString())                        ) {                    filterDateList.add(sortModel);                }            }        }        // 根据a-z进行排序        Collections.sort(filterDateList, pinyinComparator);        adapter.updateList(filterDateList);    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

RecyclerView适配器的代码,都比较简单就不用多解释了,直接上代码:

public class SortAdapter extends RecyclerView.Adapter<SortAdapter.ViewHolder> {    private LayoutInflater mInflater;    private List<SortModel> mData;    private Context mContext;    public SortAdapter(Context context, List<SortModel> data) {        mInflater = LayoutInflater.from(context);        mData = data;        this.mContext = context;    }    @Override    public SortAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {        View view = mInflater.inflate(R.layout.item, parent,false);        ViewHolder viewHolder = new ViewHolder(view);        viewHolder.tvTag = (TextView) view.findViewById(R.id.tag);        viewHolder.tvName = (TextView) view.findViewById(R.id.name);        return viewHolder;    }    @Override    public void onBindViewHolder(final SortAdapter.ViewHolder holder, final int position) {        int section = getSectionForPosition(position);        //如果当前位置等于该分类首字母的Char的位置 ,则认为是第一次出现        if (position == getPositionForSection(section)) {            holder.tvTag.setVisibility(View.VISIBLE);            holder.tvTag.setText(mData.get(position).getLetters());        } else {            holder.tvTag.setVisibility(View.GONE);        }        if (mOnItemClickListener != null) {            holder.itemView.setOnClickListener(new View.OnClickListener() {                @Override                public void onClick(View v) {                    mOnItemClickListener.onItemClick(holder.itemView, position);                }            });        }        holder.tvName.setText(this.mData.get(position).getName());        holder.tvName.setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View view) {                Toast.makeText(mContext, mData.get(position).getName(),Toast.LENGTH_SHORT).show();            }        });    }    @Override    public int getItemCount() {        return mData.size();    }    //**********************itemClick************************    public interface OnItemClickListener {        void onItemClick(View view, int position);    }    private OnItemClickListener mOnItemClickListener;    public void setOnItemClickListener(OnItemClickListener mOnItemClickListener) {        this.mOnItemClickListener = mOnItemClickListener;    }    //**************************************************************    public static class ViewHolder extends RecyclerView.ViewHolder {        TextView tvTag, tvName;        public ViewHolder(View itemView) {            super(itemView);        }    }    /**     * 提供给Activity刷新数据     * @param list     */    public void updateList(List<SortModel> list){        this.mData = list;        notifyDataSetChanged();    }    public Object getItem(int position) {        return mData.get(position);    }    /**     * 根据ListView的当前位置获取分类的首字母的char ascii值     */    public int getSectionForPosition(int position) {        return mData.get(position).getLetters().charAt(0);    }    /**     * 根据分类的首字母的Char ascii值获取其第一次出现该首字母的位置     */    public int getPositionForSection(int section) {        for (int i = 0; i < getItemCount(); i++) {            String sortStr = mData.get(i).getLetters();            char firstChar = sortStr.toUpperCase().charAt(0);            if (firstChar == section) {                return i;            }        }        return -1;    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112

PinyinComparator是个比较器类,主要就是根据ASCII码来对数据进行比较排序:

public class PinyinComparator implements Comparator<SortModel> {    public int compare(SortModel o1, SortModel o2) {        if (o1.getLetters().equals("@")                || o2.getLetters().equals("#")) {            return -1;        } else if (o1.getLetters().equals("#")                || o2.getLetters().equals("@")) {            return 1;        } else {            return o1.getLetters().compareTo(o2.getLetters());        }    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

PinyinUtils是一个将中文转化为拼音的工具类,主要提供汉字转拼音的方法和获取首字母的方法:

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 (char curChar : input) {                if (Character.toString(curChar).matches("[\\u4E00-\\u9FA5]+")) {                    String[] temp = PinyinHelper.toHanyuPinyinStringArray(curChar, format);                    output += temp[0];                } else                    output += Character.toString(curChar);            }        } catch (BadHanyuPinyinOutputFormatCombination e) {            e.printStackTrace();        }        return output;    }    /**     * 获取第一个字的拼音首字母     * @param chinese     * @return     */    public static String getFirstSpell(String chinese) {        StringBuffer pinYinBF = new StringBuffer();        char[] arr = chinese.toCharArray();        HanyuPinyinOutputFormat defaultFormat = new HanyuPinyinOutputFormat();        defaultFormat.setCaseType(HanyuPinyinCaseType.LOWERCASE);        defaultFormat.setToneType(HanyuPinyinToneType.WITHOUT_TONE);        for (char curChar : arr) {            if (curChar > 128) {                try {                    String[] temp = PinyinHelper.toHanyuPinyinStringArray(curChar, defaultFormat);                    if (temp != null) {                        pinYinBF.append(temp[0].charAt(0));                    }                } catch (BadHanyuPinyinOutputFormatCombination e) {                    e.printStackTrace();                }            } else {                pinYinBF.append(curChar);            }        }        return pinYinBF.toString().replaceAll("\\W", "").trim();    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59

最后是我们的侧边栏SideBar的代码:

public class SideBar extends View {    // 触摸事件    private OnTouchingLetterChangedListener onTouchingLetterChangedListener;    public static String[] b = { "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", "#" };    private int choose = -1;    private Paint paint = new Paint();    private TextView mTextDialog;    /**     * 为SideBar设置显示字母的TextView     * @param textDialog     */    public void setTextView(TextView textDialog) {        this.mTextDialog = textDialog;    }    public SideBar(Context context, AttributeSet attrs, int defStyle) {        super(context, attrs, defStyle);    }    public SideBar(Context context, AttributeSet attrs) {        super(context, attrs);    }    public SideBar(Context context) {        super(context);    }    protected void onDraw(Canvas canvas) {        super.onDraw(canvas);        int height = getHeight();        int width = getWidth();        int singleHeight = height / b.length;// 获取每一个字母的高度        for (int i = 0; i < b.length; i++) {            paint.setColor(Color.rgb(33, 65, 98));            // paint.setColor(Color.WHITE);            paint.setTypeface(Typeface.DEFAULT_BOLD);            paint.setAntiAlias(true);            paint.setTextSize(30);            if (i == choose) {// 选中的状态                paint.setColor(Color.parseColor("#3399ff"));                paint.setFakeBoldText(true);            }            // x坐标等于中间-字符串宽度的一半            float xPos = width / 2 - paint.measureText(b[i]) / 2;            float yPos = singleHeight * i + singleHeight;            canvas.drawText(b[i], xPos, yPos, paint);            paint.reset();        }    }    @Override    public boolean dispatchTouchEvent(MotionEvent event) {        final int action = event.getAction();        final float y = event.getY();        final int oldChoose = choose;        final OnTouchingLetterChangedListener listener = onTouchingLetterChangedListener;        final int c = (int) (y / getHeight() * b.length);// 点击y坐标所占总高度的比例*b数组的长度就等于点击b中的个数        switch (action) {        case MotionEvent.ACTION_UP:            setBackground(new ColorDrawable(0x00000000));            choose = -1;//            invalidate();            if (mTextDialog != null) {                mTextDialog.setVisibility(View.INVISIBLE);            }            break;        default:            setBackgroundResource(R.drawable.sidebar_background);            if (oldChoose != c) {                if (c >= 0 && c < b.length) {                    if (listener != null) {                        listener.onTouchingLetterChanged(b[c]);                    }                    if (mTextDialog != null) {                        mTextDialog.setText(b[c]);                        mTextDialog.setVisibility(View.VISIBLE);                    }                    choose = c;                    invalidate();                }            }            break;        }        return true;    }    /**     * 触摸事件     * @param onTouchingLetterChangedListener     */    public void setOnTouchingLetterChangedListener(            OnTouchingLetterChangedListener onTouchingLetterChangedListener) {        this.onTouchingLetterChangedListener = onTouchingLetterChangedListener;    }    /**     * 回调接口     */    public interface OnTouchingLetterChangedListener {        void onTouchingLetterChanged(String s);    }}点击下载源码
原创粉丝点击