Android城市选择列表(二)——快速索引

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/a1533588867/article/details/52934880

在上一篇文章中介绍了在RecyclerView中如何实现数据分组展示,如果你还没阅读过,建议先阅读上一篇Android地区选择列表(一)——RecyclerView数据分组。本篇接着在此基础上增加快速索引的功能。

先看效果图

这里写图片描述

这里我们需要自定义一个控件来实现快速索引。从效果图中可以发现其实要实现的界面很简单,我们一步一步来做。

首先创建QuickIndexView继承View

public class QuickIndexView extends View {

    private final static String[] WORDS = {"当","热","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 Paint paint;

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

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

    public QuickIndexView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        paint = new Paint();
        paint.setColor(Color.BLACK);
        paint.setAntiAlias(true);//抗锯齿
        paint.setTextSize(DensityUtil.dip2px(getContext(),11));//设置文字大小
        paint.setFakeBoldText(true);//设置文字粗体
    }
}

上面的代码将要显示的字统一封装在列表WORDS 中,另外还对创建Paint并对其相关参数进行设置。接着我们需要计算每个字显示所占据的空间大小,代码如下:

    private int cellWidth;
    private int cellHeight;
    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        cellWidth = getMeasuredWidth();
        cellHeight = getMeasuredHeight()/WORDS.length;
    }

这里我们定义了两个成员变量,cellWidth和cellHeight,分别代表每个字所分配到的宽和高。在API文档中可以知道,当控件大小发生改变的时候都会调用onSizeChanged方法,因此我们可以在onSizeChanged方法中获取到控件的测量高度和测量宽度。每个字所分配到的宽度就是控件的宽度,而每个字所分配到的高度则所以的字共同平分控件的高度。接着我们需要在onDraw方法中将每个字绘制到控件上。

    @Override
    protected void onDraw(Canvas canvas) {
        for(int i =0;i<WORDS.length;i++){
            String word = WORDS[i];
            Rect bound = new Rect();
            paint.getTextBounds(word,0,word.length(),bound);
            int x = (cellWidth-bound.width())/2;
            int y = i * cellHeight + (cellWidth+bound.width())/2;
            canvas.drawText(word,x,y,paint);
        }
    }

onDraw方法中我们主要处理的是如何得到每个字母显示的位置。Android绘制Text是以文本左下角的位置作为绘制起点。因此我们需要计算每个字显示在自己分配的区域中间时字的左下角的坐标。通过paint.getTextBounds()方法,将Rect传进去后,可以从Rect获取到每个字显示所要占据的宽和高.之后x坐标就是分配到的宽度-字本身显示所需要的宽度的差除以二,y坐标的高度为分配到的高度-字本身显示所需要的高度的差除以二,并且还需要加上显示在它上面的那些字所分配的高度。

这样我们就实现了控件的界面效果,接着需要处理触摸事件,这个比较简单,代码如下:

 private int curIndex = -1;
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()){
            case MotionEvent.ACTION_DOWN:
                int y = (int) event.getY();
                int index = y / cellHeight;
                if(index>=0 && index<WORDS.length) {
                    if(index!=curIndex){
                        curIndex = index;
                        if(indexChangeListener!=null){
                            indexChangeListener.onIndexChange(WORDS[curIndex]);
                        }
                    }
                }
                break;
            case MotionEvent.ACTION_MOVE:
                int y1 = (int) event.getY();
                int index1 = y1 / cellHeight;
                if(index1>=0 && index1<WORDS.length) {
                    if (index1 != curIndex) {
                        curIndex = index1;
                        if(indexChangeListener!=null){
                            indexChangeListener.onIndexChange(WORDS[curIndex]);
                        }
                    }
                }
                break;
            case MotionEvent.ACTION_UP:
                curIndex = -1;
                break;
        }
        return true;
    }

    private OnIndexChangeListener indexChangeListener;

    public void setOnIndexChangeListener(OnIndexChangeListener indexChangeListener) {
        this.indexChangeListener = indexChangeListener;
    }

    public interface OnIndexChangeListener{
        void onIndexChange(String words);
    }

我们主要处理onTouchEvent方法中的ACTION_DOWN和ACTION_MOVE两个事件。获取触摸事件的y坐标,除以字所分配到的高度得到触摸的下标,通过该下标获取WORDS数组中对应的字,再通过OnIndexChangeListener接口的onIndexChange暴露出去。这里定义了一个成员变量curIndex,用来标识上一次触摸的下标,如果是本次触摸的下标与上次相同,则不做事件回调。

到这QuickIndexView就完成了,接下来就是要和RecyclerView结合使用了。

在布局文件中加入QuickIndexView

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#f1f6f9">

    <android.support.v7.widget.RecyclerView
        android:id="@+id/recyclerView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

    <com.androidkun.indexselectcity.view.QuickIndexView
        android:id="@+id/quickIndexView"
        android:layout_width="30dp"
        android:layout_height="match_parent"
        android:layout_marginTop="40dp"
        android:layout_marginBottom="40dp"
        android:layout_alignParentRight="true"/>
</RelativeLayout>
QuickIndexView quickIndexView = (QuickIndexView) findViewById(R.id.quickIndexView);

        quickIndexView.setOnIndexChangeListener(new QuickIndexView.OnIndexChangeListener() {
            @Override
            public void onIndexChange(String words) {
                if(words.equals("当") || words.equals("热")){
                    LinearLayoutManager llm = (LinearLayoutManager) recyclerView
                            .getLayoutManager();
                    llm.scrollToPositionWithOffset(0, 0);
                    return;
                }
                List<CitiesBean.DatasBean> datas = adapter.getData();
                if(datas!=null && datas.size()>0) {
                    int count = 0;
                    for (int i = 0; i < datas.size(); i++) {
                        CitiesBean.DatasBean datasBean = datas.get(i);
                        if(datasBean.getAlifName().equals(words)){
                            LinearLayoutManager llm = (LinearLayoutManager) recyclerView
                                    .getLayoutManager();
                            llm.scrollToPositionWithOffset(count+1, 0);
                            return;
                        }
                        count+=datasBean.getAddressList().size()+1;
                    }
                }
            }
        });

由于当前地区和热门关键字我都放在了Head中,因此如果是这两个字,则将内容滚动到顶部。这里有个地方需要注意的是设置RecyclerView显示的位置是要通过LayoutManager的scrollToPositionWithOffset方法来设置。虽然RecyclerView中scrollToPosition方法,但是效果只是将你要显示的position显示在当前屏幕最低部而已,跟我们所要的效果不同。

最后需要说明的是头部的搜索和定位功能以及Item的点击响应不是本贴所要介绍的内容,这里就不实现了。

源码地址

猜你喜欢

转载自blog.csdn.net/a1533588867/article/details/52934880