当前位置: 移动技术网 > 科技>操作系统>windows > Android造轮子---联系人快速索引

Android造轮子---联系人快速索引

2020年10月09日  | 移动技术网科技  | 我要评论
联系人快速索引,可以根据右边索引导航来定位具体拼音首字母的数据,下面是效果图:实现主要使用三个View:1、ListView:负责展示联系人数据2、右边的索引IndexView(自定义View):负责处理用户点击或滑动事件,根据事件坐标值定位相应的字母索引,并通知索引事件监听者3、正中间的当前索引拼音首字母提示ViewIndexView实现原理:1、根据控件宽度和高度计算出每个字母所占的区域大小2、通过Paint.getTextBounds()计算出每个字母本身的宽高3、..

联系人快速索引,可以根据右边索引导航来定位具体拼音首字母的数据,下面是效果图:

 

实现主要使用三个View:
1、ListView:负责展示联系人数据
2、右边的索引IndexView(自定义View):负责处理用户点击或滑动事件,根据事件坐标值定位相应的字母索引,并通知索引事件监听者
3、正中间的当前索引拼音首字母提示View

 

IndexView实现原理:
1、根据控件宽度和高度计算出每个字母所占的区域大小
2、通过Paint.getTextBounds()计算出每个字母本身的宽高
3、根据计算出的控件宽高,计算出每个字母需要绘制的左下角坐标(用于文字绘制)
4、计算出每个字母有效的点击/触摸区域坐标(用Rect保存,后面可以通过Rect.contains()方法来判断某个坐标是否在该区域内)
5、被点击/触目到的字母区域用另外一个Paint来绘制通过不同画笔颜色来区分
6、通过回调接口通知事件监听者索引的变化
【注意】要注意获取控件宽高的时机,这里是在onMeasure()方法被调用时获取。


LisetView的定位原理:
1、数据需根据拼音进行排序
2、当IndexView索引发生变化,需要在回调函数里将所有数据的首字母拼音与回调的拼音进行比较,从而获取到具体的数据索引
3、通过ListView.setSelection(int index)方法来定位到具体数据索引的位置

 

索引拼音首字母提示View:
1、当IndexView索引发生变化,在回调函数里设置当前字母数据并控制View的显示和隐藏

 

下面开始贴代码,首先是布局文件:

contacts.xml

<?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">

    <ListView
        android:id="@+id/lv_contacts"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

    <TextView
        android:id="@+id/tv_abc"
        android:layout_width="80dp"
        android:layout_height="80dp"
        android:layout_centerInParent="true"
        android:textSize="18sp"
        android:gravity="center"
        android:textColor="#FFFFFF"
        android:background="#88333333"
        android:visibility="gone"/>

    <com.log.anotherapp.customView.IndexView
        android:id="@+id/index_words"
        android:layout_width="50dp"
        android:layout_height="match_parent"
        android:background="#88ff0000"
        android:layout_alignParentRight="true"/>

</RelativeLayout>

contacts_item.xml

<?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">

    <TextView
        android:id="@+id/tv_title"
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:background="#C3BCBB"
        android:gravity="center_vertical"
        android:paddingLeft="20dp"
        android:textStyle="bold"
        android:textSize="18sp" />
    
    <TextView
        android:id="@+id/tv_content"
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:gravity="center_vertical"
        android:paddingLeft="20dp"
        android:textSize="16sp" />

</LinearLayout>

IndexView.java

package com.log.anotherapp.customView;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.Typeface;
import android.util.AttributeSet;
import android.util.Pair;
import android.view.MotionEvent;
import android.view.View;

import androidx.annotation.Nullable;

import com.log.anotherapp.util.DensityUtil;

public class IndexView extends View {

    private 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 Pair<Integer, Integer>[] wordSizes = new Pair[words.length];
    // 每个文字的坐下角坐标
    private Pair<Float, Float>[] wordCoordinates = new Pair[words.length];
    // 每个文字的有效区域(用于判断是否点击或滑动该文字区域,做高亮显示)
    private Rect[] wordRects = new Rect[words.length];
    // 常态文字的paint
    private Paint paint;
    // 高亮文字的paint
    private Paint selectedPaint;
    // 每一个字母的宽度和高度
    private float itemWidth;
    private float itemHeight;
    // 当前字母索引
    private int currentIndex = -1;
    // 绘制的文本字体大小
    private int textSize;
    private OnIndexListener listener;
    // 是否已经计算过字体大小和坐标,防止重复计算
    private boolean isNotComputed = true;

    public IndexView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        // 系统调用这个方法后,本控件才测量出宽和高
        itemWidth = getMeasuredWidth();
        itemHeight = getMeasuredHeight() / words.length;
    }

    /**
     * 设置字体大小
     *
     * @param textSize
     */
    public void setTextSize(int textSize) {
        this.textSize = textSize;
    }

    private void init() {
        // 默认字体大小
        if (textSize <= 0) {
            textSize = DensityUtil.sp2px(getContext(), 18);
        }

        paint = new Paint();
        paint.setAntiAlias(true);
        paint.setTextSize(textSize);
        paint.setTypeface(Typeface.DEFAULT_BOLD); // 粗体字
        paint.setColor(Color.WHITE);

        selectedPaint = new Paint();
        selectedPaint.setAntiAlias(true);
        selectedPaint.setTextSize(textSize);
        selectedPaint.setTypeface(Typeface.DEFAULT_BOLD); // 粗体字
        selectedPaint.setColor(Color.GRAY);
    }

    private void compute() {
        Rect rect = new Rect();
        for (int i = 0; i < words.length; i++) {
            // 计算文字宽高
            paint.getTextBounds(words[i], 0, 1, rect);
            Pair pair = new Pair(rect.width(), rect.height());
            wordSizes[i] = pair;

            // 计算每个字的左下角x和y坐标
            float x = (itemWidth - wordSizes[i].first) / 2.f;
            float y = (itemHeight + wordSizes[i].second) / 2.f + itemHeight * i;
            wordCoordinates[i] = new Pair<>(x, y);

            // 计算文字点击/触摸有效区域
            wordRects[i] = new Rect(0, (int) (i * itemHeight), (int) itemWidth, (int) ((i + 1) * itemHeight));
        }
        isNotComputed = false;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        if (isNotComputed) {
            compute();
        }

        // 高亮文字的画笔
        Paint currentPaint;
        for (int i = 0; i < words.length; i++) {
            if (i == currentIndex) {
                currentPaint = selectedPaint;
            } else {
                currentPaint = paint;
            }
            canvas.drawText(words[i], wordCoordinates[i].first, wordCoordinates[i].second, currentPaint);
        }
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        float x = event.getX();
        float y = event.getY();
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
            case MotionEvent.ACTION_MOVE:
                for (int i = 0; i < wordRects.length; i++) {
                    // 判断点击坐标落在哪个字母上
                    if (wordRects[i].contains((int) x, (int) y)) {
                        // 当前选中索引
                        currentIndex = i;

                        invalidate();

                        if (listener != null) {
                            listener.onIndexChange(words[i]);
                        }
                        break;
                    }
                }
                break;

            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_CANCEL:
                currentIndex = -1;
                invalidate();
                if (listener != null) {
                    listener.onIndexRelease();
                }
                break;
        }
        return true;
    }

    public void setOnIndexListener(OnIndexListener listener) {
        this.listener = listener;
    }

    public interface OnIndexListener {
        void onIndexChange(String text);

        void onIndexRelease();
    }
}

PinYinUtil.java

package com.log.anotherapp.util;

import net.sourceforge.pinyin4j.PinyinHelper;
import net.sourceforge.pinyin4j.format.HanyuPinyinCaseType;
import net.sourceforge.pinyin4j.format.HanyuPinyinOutputFormat;
import net.sourceforge.pinyin4j.format.HanyuPinyinToneType;
import net.sourceforge.pinyin4j.format.exception.BadHanyuPinyinOutputFormatCombination;

public class PinYinUtil {

    public static String getPinYin(String chineseWord) {
        String pinyin = "";

        HanyuPinyinOutputFormat format = new HanyuPinyinOutputFormat();//控制转换是否大小写,是否带音标
        format.setCaseType(HanyuPinyinCaseType.UPPERCASE);//大写
        format.setToneType(HanyuPinyinToneType.WITHOUT_TONE);

        //由于不能直接对多个汉字转换,只能对单个汉字转换
        char[] arr = chineseWord.toCharArray();
        for (int i = 0; i < arr.length; i++) {
            if (Character.isWhitespace(arr[i])) continue;//如果是空格,则不处理,进行下次遍历

            //汉字是2个字节存储,肯定大于127,所以大于127就可以当为汉字转换
            if (arr[i] > 127) {
                try {
                    //由于多音字的存在,单 dan shan
                    String[] pinyinArr = PinyinHelper.toHanyuPinyinStringArray(arr[i], format);

                    if (pinyinArr != null) {
                        pinyin += pinyinArr[0];
                    } else {
                        pinyin += arr[i];
                    }
                } catch (BadHanyuPinyinOutputFormatCombination e) {
                    e.printStackTrace();
                    //不是正确的汉字
                    pinyin += arr[i];
                }
            } else {
                //不是汉字,
                pinyin += arr[i];
            }
        }
        return pinyin;
    }
}

ContactsActivity.java

package com.log.anotherapp;

import android.os.Bundle;
import android.os.Handler;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ListView;
import android.widget.TextView;

import androidx.annotation.Nullable;

import com.log.anotherapp.customView.IndexView;
import com.log.anotherapp.model.Contacts;
import com.log.anotherapp.util.PinYinUtil;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

public class ContactsActivity extends BaseActivity implements IndexView.OnIndexListener {

    private ListView listView;
    private IndexView indexView;
    private TextView textView;
    private Handler handler;
    private List<Contacts> contacts;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.contacts);
        initView();
    }

    private void initView() {
        handler = new Handler();
        textView = findViewById(R.id.tv_abc);
        listView = findViewById(R.id.lv_contacts);
        indexView = findViewById(R.id.index_words);
        indexView.setOnIndexListener(this);
        initData();
        ContactsAdapter adapter = new ContactsAdapter();
        listView.setAdapter(adapter);
    }

    private void initData() {
        contacts = new ArrayList<>();

        contacts.add(new Contacts("阿一", PinYinUtil.getPinYin("阿一")));
        contacts.add(new Contacts("奥利奥", PinYinUtil.getPinYin("奥利奥")));
        contacts.add(new Contacts("布尔", PinYinUtil.getPinYin("布尔")));
        contacts.add((new Contacts("曹操", PinYinUtil.getPinYin("曹操"))));
        contacts.add((new Contacts("曹尼玛", PinYinUtil.getPinYin("曹尼玛"))));
        contacts.add((new Contacts("董卿", PinYinUtil.getPinYin("董卿"))));
        contacts.add((new Contacts("厄尼", PinYinUtil.getPinYin("厄尼"))));
        contacts.add((new Contacts("凡妮莎", PinYinUtil.getPinYin("凡妮莎"))));
        contacts.add((new Contacts("高手", PinYinUtil.getPinYin("高手"))));
        contacts.add((new Contacts("韩红", PinYinUtil.getPinYin("韩红"))));
        contacts.add((new Contacts("花果", PinYinUtil.getPinYin("花果"))));
        contacts.add((new Contacts("InDon", PinYinUtil.getPinYin("InDon"))));
        contacts.add((new Contacts("节操", PinYinUtil.getPinYin("节操"))));
        contacts.add((new Contacts("康嘉", PinYinUtil.getPinYin("康嘉"))));
        contacts.add((new Contacts("岚岚", PinYinUtil.getPinYin("岚岚"))));
        contacts.add((new Contacts("玛尼", PinYinUtil.getPinYin("玛尼"))));
        contacts.add((new Contacts("能升", PinYinUtil.getPinYin("能升"))));
        contacts.add((new Contacts("欧洋", PinYinUtil.getPinYin("欧洋"))));
        contacts.add((new Contacts("盼盼", PinYinUtil.getPinYin("盼盼"))));
        contacts.add((new Contacts("钱途", PinYinUtil.getPinYin("钱途"))));
        contacts.add((new Contacts("让龙", PinYinUtil.getPinYin("让龙"))));
        contacts.add((new Contacts("诗人", PinYinUtil.getPinYin("诗人"))));
        contacts.add((new Contacts("唐僧", PinYinUtil.getPinYin("唐僧"))));
        contacts.add((new Contacts("ULove", PinYinUtil.getPinYin("ULove"))));
        contacts.add((new Contacts("VLog", PinYinUtil.getPinYin("VLog"))));
        contacts.add((new Contacts("王帝", PinYinUtil.getPinYin("王帝"))));
        contacts.add((new Contacts("现金", PinYinUtil.getPinYin("现金"))));
        contacts.add((new Contacts("媛媛", PinYinUtil.getPinYin("媛媛"))));
        contacts.add((new Contacts("正宗", PinYinUtil.getPinYin("正宗"))));

        // 按照拼音排序
        Collections.sort(contacts, new Comparator<Contacts>() {
            @Override
            public int compare(Contacts o1, Contacts o2) {
                return o1.getPinyin().compareTo(o2.getPinyin());
            }
        });
    }

    @Override
    public void onIndexChange(String text) {
        textView.setText(text);
        textView.setVisibility(View.VISIBLE);

        // 查找指定的拼音首字母在联系人数组的哪个索引
        // 由于数组已经排序,只要找到第一个匹配的就可以了
        int index = -1;
        for (int i = 0; i < contacts.size(); i++) {
            if (contacts.get(i).getPinyin().substring(0,1).equalsIgnoreCase(text)) {
                index = i;
                break;
            }
        }

        // 跳转到ListView指定位置
        if (index >= 0) {
            listView.setSelection(index);
        }
    }

    @Override
    public void onIndexRelease() {
        // 延迟一秒再隐藏
        handler.postDelayed(new Runnable() {
            @Override
            public void run() {
                textView.setVisibility(View.GONE);
            }
        }, 1000);
    }

    class ContactsAdapter extends BaseAdapter {

        @Override
        public int getCount() {
            return contacts == null ? 0 : contacts.size();
        }

        @Override
        public Object getItem(int position) {
            return contacts == null ? null : contacts.get(position);
        }

        @Override
        public long getItemId(int position) {
            return position;
        }

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            ViewHolder holder;
            if (convertView == null) {
                holder = new ViewHolder();
                convertView = LayoutInflater.from(getContext()).inflate(R.layout.contacts_item, null);
                holder.title = convertView.findViewById(R.id.tv_title);
                holder.content = convertView.findViewById(R.id.tv_content);
                convertView.setTag(holder);
            } else {
                holder = (ViewHolder) convertView.getTag();
            }

            Contacts data = contacts.get(position);
            if (position == 0) {
                holder.title.setText(data.getPinyin().substring(0, 1));
                holder.title.setVisibility(View.VISIBLE);
            } else {
                // 比较上一个是否与当前的拼音首字母是否一样
                if (data.getPinyin().charAt(0) == contacts.get(position - 1).getPinyin().charAt(0)) {
                    holder.title.setVisibility(View.GONE);
                } else {
                    holder.title.setText(data.getPinyin().substring(0, 1));
                    holder.title.setVisibility(View.VISIBLE);
                }
            }
            holder.content.setText(data.getName());

            return convertView;
        }

        class ViewHolder {
            TextView title;
            TextView content;
        }
    }

}

因为用到了Pinyin4j,需要在build.gradle里添加依赖:

// https://mvnrepository.com/artifact/org.clojars.cbilson/pinyin4j
    implementation group: 'org.clojars.cbilson', name: 'pinyin4j', version: '2.5.0'

 

本文地址:https://blog.csdn.net/lognic10/article/details/108979041

如您对本文有疑问或者有任何想说的,请点击进行留言回复,万千网友为您解惑!

相关文章:

验证码:
移动技术网