android字母排序

本文介绍了一种通过自定义View实现在列表视图旁边添加字母侧边栏的方法,该方法可以辅助用户快速定位到列表中的特定条目。文章详细解释了如何绘制字母侧边栏、如何响应触摸事件以获取触摸的字母,并展示了如何与ExpandableListView结合使用以实现固定头部和自动定位的功能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

因为项目中使用到了通过滑动侧边栏上的字母bar来定位列表数据,我在此将业务代码剥离只留下了字母查找的功能,思路基本上是自个儿想的,也要一些是参照网上其他朋友写的,比如固定listview的头部,如果有疑问的朋友欢迎留言,一定及时回复!好,废话不多说,直接上代码和demo程序!!

1、首先,字母侧边栏我是自定义view来实现的,大体就是在屏幕右边画26个字母,然后根据字母的大小来计算他们的显示位置,比如需要居中显示。然后,定义了一个回调接口,是给调用者提供监听,监听我手指滑过字母bar并获得字母。最后,调用者传入他们自己的字母数组即可。

package com.example.querybyletter.widget;

import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;

import com.example.querybyletter.util.DensityUtil;

/**
 * Created by wangyangzi on 2016/12/2.
 */

public class LetterBar extends View {
    private Paint mPaint;
    private int textSize;
    private int marginRight;
    private int marginBottom;

    public LetterBar(Context context) {
        this(context,null);
    }

    public LetterBar(Context context, AttributeSet attrs) {
        this(context, attrs,0);
    }

    public LetterBar(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        //字母大小14sp
        textSize = DensityUtil.sp2px(context,12);
        //字母右边距4dp
        marginRight = DensityUtil.dip2px(context,4);
        //字母下边距4dp
        marginBottom = DensityUtil.dip2px(context,4);

        mPaint = new Paint();
        mPaint.setAntiAlias(true);
        mPaint.setStyle(Paint.Style.FILL);
        mPaint.setTextSize(textSize);
        mPaint.setColor(Color.parseColor("#ff00ff"));
    }

    //默认是显示24个字母,如果调用者设置了setLetters(char[] letters)的话,会使用传入的字母数组
    private String[] 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"};
    private int[][] points = new int[letters.length][2];
    @Override
    protected void onDraw(Canvas canvas) {
        //计算字母的x坐标
        int textPosX = getWidth() - textSize - marginRight;
        //计算letterbar的高度
        int letterBarHeight = (textSize + marginBottom) * letters.length;
        int textPosY = 0;
        for(int i=0;i<letters.length;i++){
            if(i == 0){
                //使letterbar居中显示
                textPosY = (getHeight() - letterBarHeight) / 2;
            }else{
                //计算每个字母的y坐标
                textPosY += textSize + marginBottom;
            }
            //将字母画到界面中
            canvas.drawText(letters[i],textPosX,textPosY,mPaint);
            //保存每个字母的坐标位置
            points[i][0] = textPosX;
            points[i][1] = textPosY;
        }
    }

    @SuppressLint("ClickableViewAccessibility")
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()){
            case MotionEvent.ACTION_UP:
                if(letterBarTouchListener != null){
                    letterBarTouchListener.offLetterBar();
                }
                break;
            case MotionEvent.ACTION_DOWN:
            case MotionEvent.ACTION_MOVE:
                float x = event.getX();
                float y = event.getY();
                //基于左上角位置+字体大小+边距去规划字母的可触摸区域
                for(int i=0;i<points.length;i++){
                    int xStart = points[i][0] - marginRight;
                    int xEnd = points[i][0] + textSize + marginRight;
                    int yStart = points[i][1] - (marginBottom / 2);
                    int yEnd = points[i][1] + textSize + (marginBottom / 2);
                    if(x >= xStart && x<= xEnd && y>= yStart && y<= yEnd){
                        //获得手指触摸到的字母
                        if(letterBarTouchListener != null){
                            letterBarTouchListener.showCurrentTouchLetter(letters[i]);
                        }
                        break;
                    }
                }
                break;
        }
        return true;
    }

    public void setLetters(String[] letters){
        if(letters != null && letters.length > 0){
            this.letters = letters;
        }
    }

    //定义回调接口
    private OnLetterBarTouchListener letterBarTouchListener;
    public void setOnLetterBarTouchListener(OnLetterBarTouchListener letterBarTouchListener){
        this.letterBarTouchListener = letterBarTouchListener;
    }
    public interface OnLetterBarTouchListener{
        void showCurrentTouchLetter(String letter);
        void offLetterBar();
    }
}

2、然后我自己写了一个demo数据,这里大家可以把他换成接口中 的数据。

package com.example.querybyletter;


/**
 * Created by wangyangzi on 2016/12/4.
 */

public class LetterData {
    public static String[] getLetters() {
        String[] letters = new String[]{"A","C","D","E","F","G","H","J","K","L","M","N","P","Q","R","S","T","W","X","Y","Z"};
        return letters;
    }

    public static String[][] getCities() {
        String[][] cities = new String[][]{
                {"鞍山市","安阳市","安康市","澳门"},{"承德市","成都市","长春市","常州市","潮州市","长沙市","赤峰市","崇左市"},{"大同市","大连市","大庆市","东营市","东莞市"},
                {"鄂州市"},{"抚顺市","阜阳市","福州市","抚州市","佛山市"},{"赣州市","广州市","桂林市","广元市","贵阳市"},
                {"衡水市","合肥市"},{"晋江市","九江市","吉安市","济宁市","荆州市"},{"开封市"},
                {"乐山市","拉萨市"},{"牡丹江市","马鞍山市","绵阳市"},{"南京市","南通市","宁波市","南昌市","南宁市","怒江市"},
                {"萍乡市"},{"秦皇岛市","泉州市","青岛市","清远市","曲靖市","庆阳市"},{"日照市"},
                {"石家庄市","四平市","松原市","上海市","绍兴市","三明市","深圳市","沈阳市","上饶市","三亚市"},{"天津市","唐山市","太原市","通化市","台州市","天水市",},{"温州市","威海市","武汉市"},
                {"徐州市","信阳市"},{"阳泉市","运城市","营口市","扬州市","宜春市","雅安市","银川市"},{"漳州市","株洲市"}
        };
        return cities;
    }
}

3、letteradapter是通过继承BaseExpandableListAdapter来实现的,里面重点是方法getPositionForSection(int sectionIndex)、getSectionForPosition(int position),这两方法作用是给listview的每一项指定一个位置以及这个位置对应的字母分组。在监听listview的滚动时,需要通过首显项来确定下一个字母的位置,确定他还距离顶部有多少距离,会使用到这两个方法。

package com.example.querybyletter.adapter;


import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import android.annotation.SuppressLint;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseExpandableListAdapter;
import android.widget.TextView;

import com.example.querybyletter.R;

/**
 * Created by wangyangzi on 2016/12/4.
 */

public class LetterAdapter extends BaseExpandableListAdapter {
    private Context context;
    private String[] letters;
    private String[][] cities;

    public LetterAdapter(Context context,String[] letters,String[][] cities){
        this.context = context;
        this.letters = letters;
        this.cities = cities;
        initGroupAndItemPosition();
    }

    //给每个字母及对应的城市指定一个position(group position- item position)
    @SuppressLint("UseSparseArrays")
    private static HashMap<Integer,List<Integer>> groupMap = new HashMap<Integer,List<Integer>>();
    private void initGroupAndItemPosition(){
        int itemPosition = 0;
        for(int i=0;i<cities.length;i++){
            List<Integer> itemPos = new ArrayList<Integer>();
            //字母
            groupMap.put(i,itemPos);
            for(int j=0;j<cities[i].length;j++){
                //城市(组名也算一个位置)
                if(j == 0){
                    itemPos.add(itemPosition++);
                }
                itemPos.add(itemPosition++);
            }
        }
    }

    @Override
    public int getGroupCount() {
        return letters.length;
    }

    @Override
    public int getChildrenCount(int groupPosition) {
        return cities[groupPosition].length;
    }

    @Override
    public Object getGroup(int groupPosition) {
        return letters[groupPosition];
    }

    @Override
    public Object getChild(int groupPosition, int childPosition) {
        return cities[groupPosition][childPosition];
    }

    @Override
    public long getGroupId(int groupPosition) {
        return 0;
    }

    @Override
    public long getChildId(int groupPosition, int childPosition) {
        return 0;
    }

    @Override
    public boolean hasStableIds() {
        return true;
    }

    @Override
    public boolean isChildSelectable(int groupPosition, int childPosition) {
        return true;
    }

    @Override
    public void onGroupExpanded(int groupPosition) {

    }

    @SuppressLint("InflateParams")
    @Override
    public View getGroupView(int groupPosition, boolean isExpanded, View convertView, ViewGroup parent) {
        String letter = (String) getGroup(groupPosition);
        ViewHolder viewHolder = null;
        if(convertView == null){
            viewHolder = new ViewHolder();
            convertView = LayoutInflater.from(context).inflate(R.layout.area_letter_item,null);
            viewHolder.letter = (TextView) convertView.findViewById(R.id.letter_tv);
            convertView.setTag(viewHolder);
        }else{
            viewHolder = (ViewHolder) convertView.getTag();
        }
        viewHolder.letter.setText(letter);

        return convertView;
    }

    @SuppressLint("InflateParams")
    @Override
    public View getChildView(int groupPosition, int childPosition, boolean isLastChild, View convertView, ViewGroup parent) {
        String name = (String) getChild(groupPosition,childPosition);
        ViewHolder viewHolder = null;
        if(convertView == null){
            viewHolder = new ViewHolder();
            convertView = LayoutInflater.from(context).inflate(R.layout.area_name_item,null);
            viewHolder.name = (TextView) convertView.findViewById(R.id.city_tv);
            convertView.setTag(viewHolder);
        }else{
            viewHolder = (ViewHolder) convertView.getTag();
        }

        viewHolder.name.setText(name);

        return convertView;
    }

    //通过group的位置获得第一个子item的位置
    public int getPositionForSection(int sectionIndex) {
        List<Integer> itemList = groupMap.get(sectionIndex);
        if(itemList != null && itemList.size() > 0){
            return itemList.get(0);
        }
        return -1;
    }

    //通过item的位置获得该对应的group的位置
    public int getSectionForPosition(int position) {
        for(Map.Entry<Integer,List<Integer>> entry : groupMap.entrySet()){
            List<Integer> itemList = entry.getValue();
            //取出第一项和最后一项,如果在之间则属于这个分组
            if(itemList.size() > 0){
                int firstPos = itemList.get(0);
                int endPos = itemList.get(itemList.size() - 1);
                if(position >= firstPos && position <= endPos){
                    return entry.getKey();
                }
            }
        }
        return -1;
    }

    public static class ViewHolder{
        public TextView letter;
        public TextView name;
    }

}

4、最后是MainActivity。主要实现了将手指触摸到的字母并把他显示出来和listview滚动时头部固定及字母查找。

package com.example.querybyletter;

import java.util.Arrays;

import android.os.Bundle;
import android.support.v7.app.ActionBarActivity;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AbsListView;
import android.widget.ExpandableListView;
import android.widget.TextView;

import com.example.querybyletter.adapter.LetterAdapter;
import com.example.querybyletter.widget.LetterBar;

public class MainActivity extends ActionBarActivity {

    private static final String TAG = "MainActivity";

    private TextView letterTv;
    private String currentLetter;
    private LetterBar letterBar;
    private ExpandableListView expandableListView;
    private LetterAdapter letterAdapter;
    private TextView letterHeaderTv;

    private String[] letters;
    private String[][] cities;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        //获得列表显示数据
        letters = LetterData.getLetters();
        cities =  LetterData.getCities();
        //控件初始化
        letterTv = (TextView) this.findViewById(R.id.current_letter);
        letterHeaderTv = (TextView) this.findViewById(R.id.letter_header);
        expandableListView = (ExpandableListView) this.findViewById(R.id.area_elv);
        letterBar = (LetterBar) this.findViewById(R.id.letterBar);
        letterBar.setLetters(letters);
        //监听手指触摸latterbar事件
        letterBar.setOnLetterBarTouchListener(new LetterBar.OnLetterBarTouchListener() {
            @Override
            public void showCurrentTouchLetter(String letter) {
                if(!letter.equals(currentLetter)){
                    currentLetter = letter;
                    showLetterDialog(currentLetter);
                    //根据选中字母进行定位
                    locationForLetterbar(currentLetter);
                }
            }

            @Override
            public void offLetterBar() {
                letterTv.setVisibility(View.GONE);
            }
        });
        //绑定数据
        letterAdapter = new LetterAdapter(this,letters,cities);
        expandableListView.setAdapter(letterAdapter);
        //去小箭头
        expandableListView.setGroupIndicator(null);
        //去分隔线
        expandableListView.setDivider(null);
        //默认全部展开
        for (int i = 0; i < letters.length; i++) {
            expandableListView.expandGroup(i);
        }
        //不允许收缩
        expandableListView.setOnGroupClickListener(new ExpandableListView.OnGroupClickListener() {
            @Override
            public boolean onGroupClick(ExpandableListView parent, View v, int groupPosition, long id) {
                return true;
            }
        });
        //滚动时始终保持letterbar在页面顶部
        expandableListView.setOnScrollListener(new AbsListView.OnScrollListener() {
            @Override
            public void onScrollStateChanged(AbsListView view, int scrollState) {

            }

            @Override
            public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
                //获取第一个可见项的下一项的position
                int section = letterAdapter.getSectionForPosition(firstVisibleItem);
                int nextSectionPosition = letterAdapter.getPositionForSection(section+1);

                ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) letterHeaderTv.getLayoutParams();

                //如果当前第一个可见项的下一项是nextSectionPosition,表示快滚动到了下一个字母位置,这个时候就要开始考虑移动了
                if(nextSectionPosition == firstVisibleItem + 1){
                    View childView = view.getChildAt(0);
                    if(childView != null){
                        int distance = childView.getBottom() - letterHeaderTv.getHeight();
                        if(distance <= 0){//仅当childView的bottom距离小于或等于letterHeaderTv的高度时才开始移动
                            params.topMargin = distance;
                            letterHeaderTv.setLayoutParams(params);
                            letterHeaderTv.setText(letters[section]);
                        }
                    }
                }
                //其他情况保持letterbar在顶部不动
                else{
                    params.topMargin = 0;
                    letterHeaderTv.setLayoutParams(params);
                    letterHeaderTv.setText(letters[section]);
                }

            }
        });
    }

    //用侧边栏选中的字母去定位列表数据
    private void locationForLetterbar(String letter){
        //获得该字母对应的section
        int section = Arrays.binarySearch(letters,letter);
        //获得group的第一个子项,既group本身的position
        int letterPos = letterAdapter.getPositionForSection(section);
        //定位到该item
        expandableListView.setSelection(letterPos);
        //设置顶部letterbar的位置和显示字母
        ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) letterHeaderTv.getLayoutParams();
        letterHeaderTv.setText(letter);
        params.topMargin = 0;
        letterHeaderTv.setLayoutParams(params);
    }

    //显示当前选中的字母
    private void showLetterDialog(String letter){
        letterTv.setText(letter);
        letterTv.setVisibility(View.VISIBLE);
    }
}

5、界面布局我就不啰嗦了,具体的大家可以去下载源码试着运行一遍,然后有疑问随时提,刚学会写博客回馈社会,写的不好的大家勿喷!谢谢啦!

demo地址:http://download.youkuaiyun.com/detail/fuyang7412/9702588

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值