android中带索引的列表-----索引的高级使用

在Android中索引无处不在 比如通讯录 方便检索信息的展示页等

下面来介绍一个带索引检索的简约实用的list,首先来看效果图:
这里写图片描述
由于csdn 上传gif限制 图片略失真;

在这个demo中主要实现了如下功能:
1.在listView快速滑动时 检索索引在右侧出现,当滑动结束 ,不触摸索引条,3s后 索引渐变消失
2.listView随着索引的滑动点击 滑动到相应位置 如果是拼音 建议导入拼音包来检索
3.在索引条上滑动时候,屏幕中间 会有灰色来显示检索的关键字

1 初始化数据

接下来看代码 详解代码逻辑:
首先看MainActivity,这是比较简单的代码,只展示:

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


        initView();

        initData();

        initEvent();

    }

    private void initData() {
        mItems = new ArrayList<String>();
        mItems.add("A 想念这过去的朋友 我们的爱变成期待");
        mItems.add("Steve Jobs");
        mItems.add("Inheritance (The Inheritance Cycle)");
        mItems.add("17/08/15 Good Moment");
        mItems.add("Title Is Mine");
        mItems.add("Book For My Firend");
        mItems.add("E 北京的夏晚  独自走在安河桥北");
        mItems.add("C 人潮人落 加班撸代码到深夜程序员的痛");
        mItems.add("Every Body I M Super Soul");
        mItems.add("Death Comes to Pemberley");
        mItems.add("B OK 笨鸟当先飞 亦当迟回");
        mItems.add("Steve Jobs");
        mItems.add("Inheritance (The Inheritance Cycle)");
        mItems.add("11/22/63: A Novel");
        mItems.add("The Hunger Games");
        mItems.add("TC 加油 希望未来会感谢现在的自己");
        mItems.add("Explosive Eighteen: A Stephanie Plum Novel");
        mItems.add("Catching Fire (The Second Book of the Hunger Games)");
        mItems.add("Elder Scrolls V: Skyrim: Prima Official Game Guide");
        mItems.add("Death Comes to Pemberley");

        //对集合进行排序
        Collections.sort(mItems);

    }

    private void initEvent() {
        SectionAdapter adapter = new SectionAdapter(this, android.R.layout.simple_list_item_1,mItems);
        mList.setAdapter(adapter);
        mList.setFastScrollEnabled(true);
    }

    private void initView() {

        mList = (ListView) findViewById(R.id.listView);

    }

2 初始化Adapter 实现 SectionIndexer

下面来看 Adaper 这里impletement SectionIndexer ,完成索引条数据的初始化和索引和listViewItem的匹配逻辑

public class SectionAdapter extends ArrayAdapter<String> implements SectionIndexer{


        private String mSection = "#ABCDEFGHIJKLMNOPQRSTUVWXYZ";

        public SectionAdapter(@NonNull Context context, @LayoutRes int resource, @NonNull List<String> objects) {
            super(context, resource, objects);
        }


        //获取全部索引
        @Override
        public Object[] getSections() {
            String[] section = new String[mSection.length()];
            for (int i = 0; i < mSection.length(); i++) {
                section[i] = String.valueOf(mSection.charAt(i));
            }
            return section;
        }

        @Override
        public int getPositionForSection(int section) {

            for (int i = section; i >= 0; i--) {

                for (int j = 0; j < getCount(); j++) {

                    if (i == 0){

                        for (int k = 0; k < 9; k++) {
                            if (StringMatcher.match(String.valueOf(getItem(j).charAt(0)),String.valueOf(k)))
                                return j;
                        }

                    }else {
                        if (StringMatcher.match(String.valueOf(getItem(j).charAt(0)),
                                String.valueOf(mSection.charAt(i))))
                            return j;

                    }

                }
            }


            return 0;
        }

        @Override
        public int getSectionForPosition(int pos) {
            return 0;
        }
    }
}

在这里主要看两个方法:getSections()和getPositionForSection(int section);在getSection中完成索引集合的填充和返回(详细看上一块代码);
在这里我们主要看下getPositionForSection(int section):

 @Override
        public int getPositionForSection(int section) {
            for (int i = section; i >= 0; i--) {
                for (int j = 0; j < getCount(); j++) {
                    if (i == 0){
                        for (int k = 0; k < 9; k++) {
                            if (StringMatcher.match(String.valueOf(getItem(j).charAt(0)),String.valueOf(k)))
                                return j;
                        }
                    }else {
                        if (StringMatcher.match(String.valueOf(getItem(j).charAt(0)),
                                String.valueOf(mSection.charAt(i))))
                            return j;
                    }
                }
            }
            return 0;
        }

这里双重循环,索引条中”#”和”A~Z”分别匹配ListView首字母为数字和A~Z的字母,在这个demo中略有局限,有需求 可以单独导入拼音包;殊途同归,下面 看匹配算法StringMatcher.match:就是一个java的API:

public static boolean match(String text,String keyword){
        return text.contains(keyword) ;
    }

有兴趣的可以自己手写实现,不同我觉得没必要 , 毕竟都是有经验的程序员;

3

        接着我们看自定义的listView:
 @Override
    public boolean isFastScrollEnabled() {
        return mIsFast;
    }


    //设置快速滑动
    @Override
    public void setFastScrollEnabled(boolean enabled) {
        //super.setFastScrollEnabled(enabled);
        mIsFast = enabled;
        //Log.i("==onDraw",enabled + "");

        if (mIsFast){
            if (mScroll == null){
                mScroll = new IndexScroll(getContext(),this);
            }
        }else{
            if (mScroll != null)
            {
                mScroll.hide();
                mScroll = null;
            }
        }

    }

    @Override
    public void setAdapter(ListAdapter adapter) {
        super.setAdapter(adapter);
        if (mScroll != null){
            Log.i("===","setAdapter");
            mScroll.setAdapter(adapter);
        }
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if (mScroll != null){
            //Log.i("===","onDraw");
            mScroll.onDraw(canvas);
            //Log.i("==onDraw","draw");
        }

    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        if (mScroll != null){
            mScroll.onSizeChanged(w,h,oldw,oldh);
        }
    }


    @Override
    public boolean onTouchEvent(MotionEvent ev) {


        if (mScroll != null && mScroll.onTouchEvent(ev)){
            return true;
        }

        //监听手势控制索引条的显隐
        if (mGes == null){
            mGes = new GestureDetector(getContext(),
                    new GestureDetector.SimpleOnGestureListener() {

                        @Override
                        public boolean onFling(MotionEvent e1, MotionEvent e2,
                                               float velocityX, float velocityY) {
                            // If fling happens, index bar shows
                            if (mScroll != null)
                                mScroll.show();
                            //Log.i("==GestureDetector","showing");
                            return super.onFling(e1, e2, velocityX, velocityY);
                        }

                    });
        }
        mGes.onTouchEvent(ev);
        return super.onTouchEvent(ev);
    }


    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {

        //if (mScroll != null){
            if (mScroll.containXY(ev.getX(),ev.getY())){
                return true;
            }
        return super.onInterceptTouchEvent(ev);

    }

在这些代码中大部分是重写listView的绘制方法和生命周期.当然这些是核心,但是我们一会要单独放在一个类中写,下面会详细讲;所以暂时搁浅,来看自定义ListView中一个关键的逻辑:通过监听手势,listView快速滑动时候控制索引条的显隐(当然不要忘了在MainActivity中初始化ListView时候讲快速滑动设置为true,开篇就又代码,不清楚的可以自己稍微瞅一下):

 //是否快速滑动
    @Override
    public boolean isFastScrollEnabled() {
        return mIsFast;
    }


    //设置快速滑动
    @Override
    public void setFastScrollEnabled(boolean enabled) {
        //super.setFastScrollEnabled(enabled);
        mIsFast = enabled;
        //Log.i("==onDraw",enabled + "");

        if (mIsFast){
            if (mScroll == null){
                mScroll = new IndexScroll(getContext(),this);
            }
        }else{
            if (mScroll != null)
            {
                mScroll.hide();
                mScroll = null;
            }
        }

    }

        @Override
    public boolean onTouchEvent(MotionEvent ev) {


        if (mScroll != null && mScroll.onTouchEvent(ev)){
            return true;
        }

        //监听手势控制索引条的显隐
        if (mGes == null){
            mGes = new GestureDetector(getContext(),
                    new GestureDetector.SimpleOnGestureListener() {

                        @Override
                        public boolean onFling(MotionEvent e1, MotionEvent e2,
                                               float velocityX, float velocityY) {
                            // If fling happens, index bar shows
                            if (mScroll != null)
                                mScroll.show();
                            //Log.i("==GestureDetector","showing");
                            return super.onFling(e1, e2, velocityX, velocityY);
                        }

                    });
        }
        mGes.onTouchEvent(ev);
        return super.onTouchEvent(ev);
    }

这些都是一些固定的Api 相关关键逻辑我代码中有标记有注释,在这里不做解释;

4 核心类的实现

核心类的实现IndexScroll,在我当初写的时候是为了分担自定义listView的代码和逻辑压力,相当于抽取类吧;
ok~我们来一步步看代码:
4.1代码初始化

        首先来看初始化:
public void onSizeChanged(int w, int h, int oldw, int oldh) {

        //Log.i("==onSizeChanged","onSizeChanged");
        //开始初始化变量
        mDety = (int) mContext.getResources().getDisplayMetrics().density;
        mScaleDety = (int) mContext.getResources().getDisplayMetrics().scaledDensity;

        setAdapter(mList.getAdapter());
        mState = HIDEN;

        mListWidth = w;
        mListHeight = h;



        mIndexWidth = 20 * mDety;
        mIndexMagin = 5 * mDety;

        mIndexTextSIze = 12 * mScaleDety;



        mCenterPadding = 10 * mScaleDety;
        mCenterTextSize = 30 * mScaleDety;


        mIndexHeight = h - 2 * mIndexMagin;


        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);

        mCenterSize = 50 * mDety;








        mIndexbarRect = new RectF(w - mIndexWidth - mIndexMagin,mIndexMagin,
                w - mIndexMagin,
                mIndexMagin + mIndexHeight);


        //mCenterRect = new RectF(mListWidth - )
    }

我们把初始化逻辑放在onSizeChanged中可以防止重复调用初始化,占用资源;
接着看setAdapter 为索引条获取数据资源:

public void setAdapter(Adapter adapter) {
        this.adapter = adapter;
        mSectionAdapter = (SectionIndexer) adapter;
        mSections = (String[]) mSectionAdapter.getSections();
    }

通过传入实现SectionIndexer的adapter传入来获取索引的数据:
接下来 来看绘制:索引条和索引条数据绘制(暂时mAlph设为0来保证补触发事件不会随意显隐);以及触摸索引条,屏幕中心显示的弹出框:

public void onDraw(Canvas canvas) {
        //Log.i("==onSizeChanged","onDraw");
        if (mState == HIDEN){
            return;
        }


        //Log.i("===onDraw","draw");
        mPaint.setColor(Color.BLACK);
        //mPaint.setStyle(Paint.Style.FILL);
        mPaint.setAlpha(65 * mAlph);
        mPaint.setAntiAlias(true);
        //开始绘制索引条
        canvas.drawRoundRect(mIndexbarRect,5 * mDety, 5 * mDety,mPaint);

        //绘制索引条的数据
        mTextPaint.setColor(Color.WHITE);
        mTextPaint.setTextSize(mIndexTextSIze);
        mTextPaint.setAntiAlias(true);
        //mPaint.setAlpha(255 * mAlph);

        for (int i = 0; i < mSections.length; i++) {
            mIndexCell = (mIndexHeight - 2 * mIndexMagin)/mSections.length;
            int paddingTop = (int) ((mIndexCell + (mTextPaint.ascent() - mTextPaint.descent()))/2);
            canvas.drawText(mSections[i],
                    mIndexbarRect.left + ((mIndexWidth - mTextPaint.measureText(mSections[i]))/2),
                    mIndexbarRect.top + mIndexMagin + mIndexCell * i + paddingTop - mTextPaint.ascent(),mTextPaint);
        }

        if (mSections != null && mSections.length > 0 ){
            if (mCenterPos >= 0){

                //绘制弹出框
                mPaint.setColor(Color.BLACK);
                mPaint.setAlpha(90);
                int top = (mListHeight - mCenterSize)/2;
                int left = (mListWidth - mCenterSize)/2;
                mCenterRect = new RectF(left,top,left + mCenterSize,top + mCenterSize);
                canvas.drawRoundRect(mCenterRect,5 * mDety,5 * mDety,mPaint);



                //绘制弹出框的数据
                mTextPaint.setTextSize(mCenterTextSize);
                //mCenterSize = (int) (mTextPaint.measureText(mSections[mCenterPos]) + mCenterPadding * 2);
                int l = (int) (left + ( mCenterSize - mTextPaint.measureText(mSections[mCenterPos]))/2);
                int paddingTop = (int) ((mCenterSize + (mTextPaint.ascent() - mTextPaint.descent()))/2);
                int t = (int) ((mListHeight - mCenterSize)/2 + paddingTop - mTextPaint.ascent());
                canvas.drawText(mSections[mCenterPos],l,t,mTextPaint);


            }

        }




    }

接着来看show()和hide()方法 这样比较r容易理解 ,毕竟上一层的自定义listView的手势监听是通过indexScroll的show和hide来实现:

 public void show() {
        if (mState == HIDEN){
            Log.i("==show","HIDEN....");
            setState(SHOWING);
        }


    }

    public void hide() {
        if (mState == SHOWN)
        setState(HIDEING);

    }
    接着看setState方法:
public void setState(int state){

        if (state < HIDEN || state > HIDEING)
            return;

        mState = state;
        switch (mState){
            case SHOWN:
                mHandler.removeMessages(0);
                //Log.i("==mHandler",SHOWN + "");
                break;
            case SHOWING:
                mAlph = 0;
                start(0);
                //Log.i("==mHandler",SHOWING + "");
                break;
            case HIDEN:
                mHandler.removeMessages(0);
                //Log.i("==mHandler",HIDEN + "");
                break;
            case HIDEING:
                mAlph = 1;
                start(3000);
                //Log.i("==mHandler",HIDEING + "");
                break;
        }

    }

当mState = SHOWING ,则会设置mAlph = 0 通过handler来线程重复调用刷新,当mAlph = 1时设置为mState = SHOWN 若索引条没有触摸,则开线程将mState == HIDEIND,然后mAlph = 1;延迟3s后开始刷新绘制 逐渐隐藏索引条,最后mAlph = 0 ,mState = HIDEN;注意当mState = SHOWN 和mState = HIDEN时要移除线程;
那么 瞄一眼线程开启的逻辑:

    private void start(int delay) {
        mHandler.removeMessages(0);
        mHandler.sendEmptyMessageAtTime(0, SystemClock.uptimeMillis() + delay);
    }

    private Handler mHandler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);

            switch (mState){
                case SHOWING:
                    mAlph += (1 - mAlph)*0.2;
                    //Log.i("==mHandler",mAlph + "");
                    if (mAlph > 0.9){
                        mAlph = 1;
                        setState(SHOWN);
                    }

                    mList.invalidate();
                    start(20);

                    break;
                case SHOWN:
                    setState(HIDEING);
                    break;
                case HIDEING:
                    mAlph -= mAlph * 0.2;
                    //Log.i("==HIDEING","HIDEING...");
                    if (mAlph <0.1){
                        mAlph = 0;
                        setState(HIDEN);
                    }

                    mList.invalidate();
                    start(10);
                    break;

            }

        }
    };

接着来看手指在索引条中滑动逻辑,其实就是通过x,y坐标限制来拦截listView的滑动:

 public boolean onTouchEvent(MotionEvent ev) {

        int action = ev.getAction();
        switch (action){
            case MotionEvent.ACTION_DOWN:

                if (mState != HIDEN && containXY(ev.getX(),ev.getY())){
                    setState(SHOWN);
                    int pos = getPos(ev.getY());
                    //mList.setSelection(pos);
                    mCenterPos = pos;
                    int positionForSection = mSectionAdapter.getPositionForSection(pos);
                    mList.setSelection(positionForSection);
                    //mList.setSelection(.getPositionForSection(mCurrentSection));
                    return true;
                }



            case MotionEvent.ACTION_MOVE:

                if (containXY(ev.getX(),ev.getY())){
                    int pos = getPos(ev.getY());
                    int positionForSection = mSectionAdapter.getPositionForSection(pos);
                    mList.setSelection(positionForSection);
                    mCenterPos = pos;
                    return true;
                }


            case MotionEvent.ACTION_UP:

                setState(HIDEING);

                mCenterPos = -1;

                break;

        }

        return false;
    }

以上代码就是手指在索引条中滑动的触摸事件逻辑;其也是根据mState状态来控制setState来通过线程完成一部分逻辑;

ok~ 到这里 代码就详尽的解释完了 过段时间代码回上传github 有需求的朋友可以评价 谢谢 感谢支持 期待共同进步

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值