Android仿网易新闻导航栏实现教程 微信式字母索引效果的详细步骤及所需资料
日期:2026-02-28 04:53:48 / 人气:
还在使用微信那般右侧有着字母导航条的情况,却不解要如何去实现它?今日就要借助代码一点一点地理拆解这个具备实用性质的功能,以求让你能够迅速领会且应用至自身的App里面。
整体布局设计
首字母导航条实现的第一步,是搭建整体界面框架。一般采用左右分栏布局,左侧为联系人列表,右侧是导航条区域。在XML布局文件里,要定义两个核心控件,左侧用ListView展示联系人数据,右侧自定义一个View绘制A-Z字母列表。布局权重设置得合理,右侧导航条宽度通常是在30至40像素之间。
针对触摸事件的拦截处理而言,其也是相当关键的。当用户于右侧导航条之上开展滑动操作的时候,左侧的ListView则要迅速地定位到与之对应的字母所处的位置。而这样的交互逻辑是倚靠OnTouchListener监听器的,借助获取手指触摸位置的Y坐标,进而计算出当下所选中的字母索引情况。
数据分组处理
不能直接显示联系人数据,得按照首字母来开展分组排序。开头要从数据库中 fetch 出所有的联系人信息,涵盖姓名、头像、电话号码等字段。接着去遍历每一个联系人,从中提取姓名的首字母,把有着相同首字母的联系人归到同一个分组里。
在经过分组之后的数据将会被存放于具备着有序性质的数据结构范围里面,为各位诚心建议使用TreeMap这么样的一种结构,因为其能够以自动这样子是依据字母顺序那样子来进行排序处理的结果。在每一个分组的范围之内,同时还需要记录下来关于该分组的联系人数量、处于列表当中的起始位置等一系列的信息。前述这些数据实际上为后续的快速定位专门提供了相应的基础保障。
字母索引提取
难点在于将中文联系人转为拼音,这得借助专门的拼音转换工具。我们借助开源的pinyin4j库来达成此任务,该库能支持多种拼音格式输出。转化时需考虑多音字词的状况,能够凭借常用字库以及上下文予以大略处理,从而确保准确率处于95%以上。
当进行首字母的提取动作之际还需对特殊字符予以处理。要是联系人的姓名是以数字亦或是符号作为起始的情况,那就将其归拢到井号形成的分组当中。针对英文的联系人直接获取首字母并加以大写处理,对于中文联系人则提取拼音首字母且进行大写更改。这些处理方面的逻辑要领需打包成为单独的一个工具类样式,以便更加便利地出现多个处分用于调用。
自定义导航条绘制
导航条绘制,要继承View类,重写onDraw方法。于onMeasure里去计算每个字母的绘制高度,以确保26个字母能均匀分布在导航条之上。借助Paint来设置文字大小以及颜色,运用canvas.drawText方法逐个去绘制字母。在触摸时,选中的字母要进行高亮显示,这就需要改变画笔颜色并且重新绘制。
画图之际需考量边界情形,若联系人数量不多,导航条高度能够适度缩减,字母间距增大更利于点击。触摸反馈同样颇为关键,点击或者滑动时应具备视觉反馈,诸如字母变色、背景变暗淡等效果。
中间提示字母显示
参照微信的那种细节方面的体验,在触摸那个导航条之际,中间部位得浮现当下字母带有大号样式的提示内容。这个提示层能够借助 PopupWindow 去达成实现的操作,给它设置成半透明的背景模样,呈现出超大号的字母。凭借监听导航条相应的触摸事件流程,实时地去更新 PopupWindow 当中所显示的字母的内容。
要精心计算提示层的显示位置,一般是在屏幕中央稍稍偏上的那个位置。显示以及隐藏得具备动画效果,能够设置缩放还有淡入淡出的动画,以此让交互更为自然。在手指离开屏幕之际,提示层需平滑地消失掉,而不能忽然进行隐藏。
public class Contact {
//姓名
private String name;
//姓名的首字母
private String firstWord;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getFirstWord() {
return firstWord;
}
public void setFirstWord(String ch) {
this.firstWord = ch;
}
}
性能优化与适配
当列表快速滚动之际,极易出现卡顿现象,故而需运用ViewHolder模式来对ListView的性能予以优化。与此同时,要缓存每个分组的首字母位置,以此避免每次滑动之时都再度进行计算。针对500人以上的大联系人列表,得考虑异步加载头像,从而防止UI线程被阻塞。
不同屏幕尺寸的适配事宜也需予以留意,导航条字母大小得依据屏幕密度进行动态调整,dpi高的屏幕要适度加大字号,触摸区域的响应范围需适度予以扩大,以此提高操作成功率,横屏模式之下导航条能够自动隐藏,进而释放更多显示空间。
public class ContactAdapter extends BaseAdapter {
private ArrayList arrayList;
private Context context;
private String pre = "A";
public ContactAdapter(ArrayList arrayList, Context context) {
this.arrayList = arrayList;
this.context = context;
}
@Override
public int getCount() {
return arrayList.size();
}
@Override
public Object getItem(int position) {
return arrayList.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder viewHolder;
if (convertView == null) {
viewHolder = new ViewHolder();
convertView = LayoutInflater.from(context).inflate(R.layout.item, parent, false);
viewHolder.tv_firstWord = (TextView) convertView.findViewById(R.id.tv);
viewHolder.name = (TextView) convertView.findViewById(R.id.name);
convertView.setTag(viewHolder);
} else {
viewHolder = (ViewHolder) convertView.getTag();
}
viewHolder.tv_firstWord.setText(String.valueOf(arrayList.get(position).getFirstWord()));
viewHolder.name.setText(arrayList.get(position).getName());
/**
* 分组:根据汉字的首字母
*/
viewHolder.tv_firstWord.setVisibility(!arrayList.get(position).getFirstWord().equals(pre) ? View.VISIBLE : View.GONE);
pre = arrayList.get(position).getFirstWord();
return convertView;
}
class ViewHolder {
TextView tv_firstWord;
TextView name;
}
你打算于自身所涉项目之内尝试此项功能做法吗,欢迎于评论区域中将你的实现经历或者所碰到的难题予以分享,若感觉文章具备用处那就请给予点赞收藏操作!
public class MainActivity extends AppCompatActivity {
private ListView listView;
private TextView tv_first;
private ArrayList contacts;
private SlidView slidView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
initData();
}
private void initView() {
listView = (ListView) findViewById(R.id.friend_listview);
tv_first = (TextView) findViewById(R.id.tv_first);
slidView = (SlidView) findViewById(R.id.slidView);
slidView.setFirstListener(new OnTouchFirstListener() {
@Override
public void onTouch(String str) {
tv_first.setVisibility(View.VISIBLE);
tv_first.setText(str);
for (int i = 0; i < contacts.size(); i++) {
if (str.equals(contacts.get(i).getFirstWord())) {
listView.setSelection(i);
}
}
}
@Override
public void onRelease() {
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
tv_first.setVisibility(View.GONE);
}
}, 300);
}
});
}
private void initData() {
contacts = new ArrayList<>();
//获取字符数组资源
String[] attrs = getResources().getStringArray(R.array.myFriend);
Contact contact;
for (int i = 0; i < attrs.length; i++) {
contact = new Contact();
contact.setName(attrs[i]);
contact.setFirstWord(getPinYinHeadChar(attrs[i], 2));
contacts.add(contact);
}
//排序A-Z
Collections.sort(contacts, new Comparator() {
@Override
public int compare(Contact lhs, Contact rhs) {
return lhs.getFirstWord().compareTo(rhs.getFirstWord());
}
});
ContactAdapter contactAdapter = new ContactAdapter(contacts, this);
listView.setAdapter(contactAdapter);
}
/**
* 提取汉字的首字母,如果里面含有费中文字符则忽略之;如果全为非中文则返回""。
*
* @param caseType 当为1时获取的首字母为小写,否则为大写。
*/
public static String getPinYinHeadChar(String zn_str, int caseType) {
if (zn_str != null && !zn_str.trim().equalsIgnoreCase("")) {
char[] strChar = zn_str.toCharArray();
// 汉语拼音格式输出类
HanyuPinyinOutputFormat hanYuPinOutputFormat = new HanyuPinyinOutputFormat();
// 输出设置,大小写,音标方式等
if (1 == caseType) {
hanYuPinOutputFormat.setCaseType(HanyuPinyinCaseType.LOWERCASE);
} else {
hanYuPinOutputFormat.setCaseType(HanyuPinyinCaseType.UPPERCASE);
}
hanYuPinOutputFormat.setToneType(HanyuPinyinToneType.WITHOUT_TONE);
hanYuPinOutputFormat.setVCharType(HanyuPinyinVCharType.WITH_V);
StringBuffer pyStringBuffer = new StringBuffer();
char c = strChar[0];
char pyc;
if (String.valueOf(c).matches("[\\u4E00-\\u9FA5]+")) {//是中文或者a-z或者A-Z转换拼音
try {
String[] pyStirngArray = PinyinHelper.toHanyuPinyinStringArray(strChar[0], hanYuPinOutputFormat);
if (null != pyStirngArray && pyStirngArray[0] != null) {
pyc = pyStirngArray[0].charAt(0);
pyStringBuffer.append(pyc);
}
} catch (BadHanyuPinyinOutputFormatCombination e) {
e.printStackTrace();
}
}
return pyStringBuffer.toString();
}
return null;
}

