欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  科技

Android造*---联系人快速索引

程序员文章站 2022-04-14 11:22:27
联系人快速索引,可以根据右边索引导航来定位具体拼音首字母的数据,下面是效果图:实现主要使用三个View:1、ListView:负责展示联系人数据2、右边的索引IndexView(自定义View):负责处理用户点击或滑动事件,根据事件坐标值定位相应的字母索引,并通知索引事件监听者3、正中间的当前索引拼音首字母提示ViewIndexView实现原理:1、根据控件宽度和高度计算出每个字母所占的区域大小2、通过Paint.getTextBounds()计算出每个字母本身的宽高3、.....

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

Android造*---联系人快速索引

 

实现主要使用三个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