[Android] 仿通讯录拼音索引与跳转以及Pinyin4j的使用(上)
提要:上周实现了一个通讯录的列表。大致要实现将列表中的数据按照拼音进行排序,并且实现拼音索引与跳转的一个功能
因为太长了,因此分为上下两篇。
随缘加内容,没准会更多。
分析一下实现思路:
- 首先引入pinyin工具类。时期能够获取列表第一个字符拼音的首字母。
- 实体类继承comparable接口,重写compareTo方法,实现拼音排序
- 列表的adapter中,在getView方法中,根据拼音首字母,设置背景颜色
- 在右边添加一个索引,能够实现滑动跳转、出现拼音气泡
既然如此,思路就可以打开了:
1.引入Pinyin工具类
这里使用的是Pinyin4j。直接在gradle中引入即可。
dependencies {
implementation 'com.belerweb:pinyin4j:2.5.0'
...
}
点击sync,下载相关依赖。静静等待依赖下载完成。
使用Pinyin4j写相关util
在这里本来有更多功能的。但在这个例子里,我们只用找出拼音的首字母。不用其他的功能了。
因此只需要使用PinyinHelper.toHanyuPinyinStringArray()
即可。
package com.example.pinyinindex.util;
import net.sourceforge.pinyin4j.PinyinHelper;
public class PinyinUtil {
// 获取字符串第一个字符的拼音
public static String getPinyin(String str) {
String[] pinyinArray = PinyinHelper.toHanyuPinyinStringArray(str.charAt(0));
return pinyinArray[0];
}
// 获取字符串第一个字符的拼音首字母
public static char getFirstLetter(String str) {
String result = getPinyin(str);
return result.charAt(0);
}
}
冷知识
写到这里突然想说一嘴:其实Pinyin4j,读作:Pinyin for j
, 也就是 拼音 for java
。
像这种还有2 读作 to
。在看到这种依赖的时候,基本就能知道这个是为了什么场景使用的了。
如何使用pinyin4j?
在这个例子里,我们只需要使用PinyinHelper.toHanyuPinyinStringArray()
。这个方法会返回一个数组,是这个汉字的所有读音(毕竟中文有多音字)。比如说“哦”,会返回[o,e]
两个音。这个的具体使用就在过多介绍了。详细可以看Pinyin4j官网。
2.编写实体类,实现相关接口
因为想要用更优雅的方式写比较排序方法 (主要是因为懒,不想写),在这里直接让相关实体类实现一下Comparable接口。这样子可以直接使用sort()
方法进行排序。
package com.example.pinyinindex;
import com.example.pinyinindex.util.PinyinUtil;
public class Item implements Comparable<Item>{
private String name;
// 如果不知道tag是做什么的,可以忽略,后面会讲
private String tag;
public Item(String name, String tag) {
this.name = name;
this.tag = tag;
}
public String getName() {
return name;
}
public String getTag() {
return tag;
}
// 实现Comparable接口需要重写一下这个方法。
@Override
public int compareTo(Item item) {
char otherLetter = PinyinUtil.getFirstLetter(item.name);
char myLetter = PinyinUtil.getFirstLetter(this.name);
return myLetter - otherLetter;
}
}
3.进行排序
反正都已经实现Comparable接口了,直接使用sort()
方法进行排序即可。但是,这里还有一个小问题。我想要在列表中,设置背景颜色。如果直接排序的话,列表怎么知道什么时候要设置背景呢?所以在这里还需要添加一个tag。
这个tag,就是用来记录当前这个item的拼音首字母。如果一个item没有名字,只有tag,则证明这个就是我们需要设置背景的元素啦。
创建对应数据
// 这个就是要传递给adapter的数据
private List<Item> itemList = new ArrayList<>();
private void initData() {
// DataSource用来模拟数据
List<Item> data = DataSource.getData();
// 上一个item的拼音首字母
final char[] lastTag = {' '};
data.stream()
.sorted()
.forEach(item -> {
char itemLetter = PinyinUtil.getFirstLetter(item.getName());
// 如果当前item的拼音首字母和上一个的不同,则证明需要添加一个tag
if (itemLetter != lastTag[0]) {
lastTag[0] = itemLetter;
itemList.add(new Item(null, String.valueOf(itemLetter)));
}
itemList.add(item);
});
}
这里用到了stream流,和forEach。关于这点已经写过博客了,详情可以去看看Java8新特性之Stream流
对不清楚的小伙伴说一嘴:在这里我们对数据进行了排序,并且添加了tag。最后形成了一个数据数组,最后把这个数据数组传递给adapter就可以了
4.编写Adapter,实现关于背景颜色的设置
因为是用的RecyclerView,所以这里直接使用RecyclerView.Adapter。
package com.example.pinyinindex;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import java.util.List;
public class ItemAdapter extends RecyclerView.Adapter<ItemAdapter.myViewHolder> {
private final List<Item> itemList;
// 构造函数,获取数据
public ItemAdapter(List<Item> itemList){
this.itemList = itemList;
}
// 绑定布局
@NonNull
@Override
public myViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.adapter_item_tag,parent,false);
return new myViewHolder(v);
}
// 绑定数据
@Override
public void onBindViewHolder(@NonNull myViewHolder holder, int position) {
// 进行初始化,防止在快速上下滚动的时候显示错乱
holder.mainLayout.setVisibility(View.GONE);
holder.tagTv.setVisibility(View.GONE);
Item item = itemList.get(position);
// 如果没有名字则证明是tag
if (item.getName() == null) {
holder.tagTv.setVisibility(View.VISIBLE);
holder.tagTv.setText(item.getTag());
return;
}
holder.mainLayout.setVisibility(View.VISIBLE);
holder.nameTv.setText(item.getName());
}
@Override
public int getItemCount() {
return itemList.size();
}
// 创建ViewHolder
class myViewHolder extends RecyclerView.ViewHolder{
public TextView tagTv;
public TextView nameTv;
public LinearLayout mainLayout;
public myViewHolder(View itemView){
super(itemView);
tagTv = itemView.findViewById(R.id.adapter_tag);
nameTv = itemView.findViewById(R.id.adapter_name);
mainLayout = itemView.findViewById(R.id.adapter_main);
}
}
}
这里偷懒,直接在布局文件中写了两个TextView,一个用来显示tag,一个用来显示名字。按照需要显示隐藏。
看一眼布局文件吧:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="A"
android:id="@+id/adapter_tag"
android:background="#999"
android:textColor="@color/white"
android:paddingLeft="10dp"/>
<LinearLayout
android:id="@+id/adapter_main"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="姓名"
android:id="@+id/adapter_name"
android:textSize="32sp"/>
</LinearLayout>
</LinearLayout>
注意:在这个Adapter里,刚绑定数据的时候,会进行初始化,也就是让tag和name都隐藏,防止在快速上下滚动的时候显示错乱。如果不这么做,在快速上下滚动的时候,可能会出现tag和name同时显示的情况。
5.绑定数据
package com.example.pinyinindex;
import android.app.Activity;
import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.example.pinyinindex.util.PinyinUtil;
import java.util.ArrayList;
import java.util.List;
import kotlin.collections.ArrayDeque;
public class MainActivity extends Activity {
private RecyclerView rv;
private final List<Item> itemList = new ArrayList<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findViews();
initData();
rv.setLayoutManager(new LinearLayoutManager(this));
rv.setAdapter(new ItemAdapter(itemList));
}
private void initData() {
List<Item> data = DataSource.getData();
final char[] lastTag = {' '};
data.stream()
.sorted()
.forEach(item -> {
char itemLetter = PinyinUtil.getFirstLetter(item.getName());
if (itemLetter != lastTag[0]) {
lastTag[0] = itemLetter;
itemList.add(new Item(null, String.valueOf(itemLetter)));
}
itemList.add(item);
});
}
private void findViews() {
rv = findViewById(R.id.recyclerView);
}
}
6.全部代码&效果
DataSource.java
package com.example.pinyinindex;
import java.util.ArrayList;
import java.util.List;
public class DataSource {
public static List<Item> getData() {
ArrayList<Item> result = new ArrayList<>();
result.add(new Item("李白", null));
result.add(new Item("杜甫", null));
result.add(new Item("李清照", null));
result.add(new Item("王维", null));
result.add(new Item("李商隐", null));
result.add(new Item("张三", null));
result.add(new Item("李四", null));
result.add(new Item("王五", null));
result.add(new Item("赵六", null));
result.add(new Item("田七", null));
result.add(new Item("张三丰", null));
result.add(new Item("张无忌", null));
result.add(new Item("马冬梅", null));
result.add(new Item("张飞", null));
result.add(new Item("李元芳", null));
result.add(new Item("诸葛亮", null));
result.add(new Item("姜子牙", null));
result.add(new Item("岳云鹏", null));
result.add(new Item("斐擒虎", null));
result.add(new Item("孙悟空", null));
result.add(new Item("猪八戒", null));
result.add(new Item("沙和尚", null));
result.add(new Item("白骨精", null));
result.add(new Item("白龙马", null));
result.add(new Item("唐僧", null));
result.add(new Item("姬小满", null));
result.add(new Item("典韦", null));
result.add(new Item("曹操", null));
result.add(new Item("刘备", null));
result.add(new Item("关羽", null));
result.add(new Item("张飞", null));
result.add(new Item("赵云", null));
result.add(new Item("黄忠", null));
result.add(new Item("孙权", null));
result.add(new Item("孙尚香", null));
result.add(new Item("孙策", null));
result.add(new Item("孙凯", null));
result.add(new Item("黄月英", null));
result.add(new Item("李典", null));
result.add(new Item("孙坚", null));
result.add(new Item("马超", null));
result.add(new Item("张辽", null));
result.add(new Item("司马懿", null));
result.add(new Item("夏侯惇", null));
result.add(new Item("许褚", null));
result.add(new Item("郭嘉", null));
result.add(new Item("吕蒙", null));
result.add(new Item("黄盖", null));
result.add(new Item("华佗", null));
result.add(new Item("华雄", null));
result.add(new Item("袁术", null));
result.add(new Item("魏延", null));
result.add(new Item("于吉", null));
result.add(new Item("周泰", null));
result.add(new Item("张角", null));
result.add(new Item("曹丕", null));
result.add(new Item("曹真", null));
result.add(new Item("曹爽", null));
return result;
}
}
其他代码都在上面啦,就不贴了。
总结
实现这个效果实际上很简单,不用怎么复杂,只要按照我上面说的步骤,就可以实现这个效果。其实不用我说应该也能写出来。
下一篇博客会介绍如何实现通讯录的拼音索引与跳转。应该会在不久写出来。如果对你有帮助,希望能给我点一个免费的赞,您的每一个点赞是我最大的动力。