由于这段时间工作上比较忙,就一直没有更新博客,这毕竟是第一份实习,当然要尽心尽力,但是做了很多努力 和付出,感觉得不到重视,有些低沉。 发了几句牢骚,接下来进入主题,今天给大家带来ListView的A-Z字母排序和过滤搜索功能并且实现汉字转成拼音的功能,这篇博客是前一篇的升级版,前一篇只是实现了联系人的排序和查询,因为Android系统中的联系人和城市列表我们可以直接从数据库中获取他的汉字拼音,而且系统进行了排序,所以我们不用考虑按A-Z实现分类和排序,而对于一般的数据,我们怎么实现A-Z的排序,我们需要将汉字转换成拼音就行了,接下来就带大家实现一般数据的A-Z排序功能,汉字转换为拼音我使用了第三方的一个包,可以将汉字转换为拼音,排序的话如果大家用for循环效率太低了(我现在做的这个项目中就使用到了这个功能,发现当要排序的数量达到100以上,for循环就会变得非常慢,需要开启线程来执行,不然程序会崩),时间复杂度是O(n^2),所以这里还是用到了BST(查询二叉树)这样就将时间复杂度降到O(nlog2n)这样程序效率大大增高。而且我接下来实现的demo中使用到了模板的设计模式。 首先给大家看一下效果图:
大家可以看到1000多的数据量,整个排序只需要1秒不到就可以排好,可见数据结构是程序员必修的一门课程,
和上一篇一样,当按到右侧的边框时,listView会跳到相应的字母栏,也可以在右侧滑动。
接下来,先看一下我的项目结构,我在这里使用的Android Studio,会和使用Eclipse的朋友结构会有所不一样
首先是CityInfo类是一个简单的自定义的数据类,有name和pinyin两个属性。 CityInfoAdapter是一个继承CommonAdapter的一个适配listview数据的类,模板设计模式,就是将共同的步骤抽离出来,将不同的函数用abstract定义,由相应的子类去实现,这样就可以少些大量的代码:
package com.example.liyachao.sectionstest;
import android.content.Context;
import android.view.View;
import android.widget.LinearLayout;
import android.widget.SectionIndexer;
import android.widget.TextView;
import java.util.List;
/**
* 列表适配器。
*
* @author liyachao
*/
public class CityInfoAdapter extends CommonAdapter<CityInfo> {
/**
* 需要渲染的item布局文件
*/
private List<CityInfo> datas;
private boolean flag = true;
/**
* 字母表分组工具
*/
private SectionIndexer mIndexer;
public CityInfoAdapter(Context context, List<CityInfo> datas, int layoutId) {
super(context, datas, layoutId);
}
//
// public CityInfoAdapter(Context context, int textViewResourceId, List<CityInfo> players) {
//
// resource = textViewResourceId;
// this.context = context;
// this.players = players;
// }
public void dataChanged(List<CityInfo> datas) {
this.datas = datas;
notifyDataSetChanged();
flag = false;
}
@Override
public void convert(com.example.liyachao.sectionstest.ViewHolder holder, CityInfo cityInfo, int position) {
TextView name = holder.getView(R.id.name);
LinearLayout sortKeyLayout = holder.getView(R.id.sort_key_layout);
TextView sortKey = holder.getView(R.id.sort_key);
name.setText(cityInfo.name);
if (flag) {
int section = mIndexer.getSectionForPosition(position);
if (position == mIndexer.getPositionForSection(section)) {
sortKey.setText(Character.toUpperCase(cityInfo.pinyin.charAt(0)) + "");
sortKeyLayout.setVisibility(View.VISIBLE);
} else {
sortKeyLayout.setVisibility(View.GONE);
}
} else {
sortKeyLayout.setVisibility(View.GONE);
}
}
/**
* 给当前适配器传入一个分组工具。
*
* @param indexer
*/
public void setIndexer(SectionIndexer indexer) {
mIndexer = indexer;
flag = true;
}
}
大家可以看到这里的代码非常少,如果是继承baseAdapter会重载很多方法,而这这些方法又都是重复的。
package com.example.liyachao.sectionstest;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import java.util.List;
/**
* Created by liyachao on 15-4-21.
*/
public abstract class CommonAdapter<T> extends BaseAdapter {
protected Context context;
protected List<T> datas;
protected LayoutInflater inflater;
protected int layoutId;
public CommonAdapter(Context context, List<T> datas, int layoutId) {
this.context = context;
this.datas = datas;
inflater = LayoutInflater.from(context);
this.layoutId = layoutId;
}
@Override
public int getCount() {
return datas.size();
}
@Override
public T getItem(int position) {
return datas.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder = ViewHolder.get(context, convertView, parent, layoutId, position);
convert(holder, getItem(position), position);
return holder.getConvertView();
}
public abstract void convert(ViewHolder holder, T t, int position);
}
将大家经常写的ViewHolder写成一个单独的类:
package com.example.liyachao.sectionstest;
import android.content.Context;
import android.util.SparseArray;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
/**
* Created by liyachao on 15-4-21.
*/
public class ViewHolder {
private int mPosition;
private View mConvertView;
private SparseArray<View> mViews;
public ViewHolder(Context context, ViewGroup parent, int layoutId, int position) {
this.mPosition = position;
this.mViews = new SparseArray<View>();
mConvertView = LayoutInflater.from(context).inflate(layoutId, parent, false);
mConvertView.setTag(this);
}
public static ViewHolder get(Context context, View convertView, ViewGroup parent,
int layoutId, int position) {
if (convertView == null) {
return new ViewHolder(context, parent, layoutId, position);
} else {
ViewHolder viewHolder = (ViewHolder) convertView.getTag();
viewHolder.mPosition = position;
return viewHolder;
}
}
public <T extends View> T getView(int viewId) {
View view = mViews.get(viewId);
if (view == null) {
view = mConvertView.findViewById(viewId);
mViews.put(viewId, view);
}
return (T) view;
}
public View getConvertView() {
return mConvertView;
}
}
以后写ListView类的适配器时,直接继承CommonAdapter可以省很多力气。
然后下来是MySectionIndexer一个继承SectionIndexer的分类的类:
package com.example.liyachao.sectionstest;
import android.widget.SectionIndexer;
import java.util.Arrays;
import java.util.List;
/**
* Created by liyachao on 15-4-9.
*/
public class MySectionIndexer<T> implements SectionIndexer {
private static String[] mSections = {"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 static int OTHER_INDEX = 0; // index of other in the mSections array
private int[] mPositions; // store the list of starting position index for
// each section
// e.g. A start at index 0, B start at index 20,
// C start at index 41 and so on
private int mCount; // this is the count for total number of contacts
// Assumption: the contacts array has been sorted
public MySectionIndexer(List<T> citys, String sections) {
mCount = citys.size();
initPositions(citys);
}
/**
* 得到字符串的第一个字母
*
* @param indexableItem
* @return
*/
public int getSectionIndex(String indexableItem) {
if (indexableItem == null) {
return OTHER_INDEX;
}
indexableItem = indexableItem.trim();
String firstLetter = "A";
if (indexableItem.length() == 0) {
return OTHER_INDEX;
} else {
// get the first letter
firstLetter = String.valueOf(indexableItem.charAt(0)).toUpperCase();
}
int sectionCount = mSections.length;
for (int i = 0; i < sectionCount; i++) {
if (mSections[i].equals(firstLetter)) {
return i;
}
}
return OTHER_INDEX;
}
// initialize the position index
public void initPositions(List<T> contacts) {
int sectionCount = mSections.length;
mPositions = new int[sectionCount];
Arrays.fill(mPositions, -1); // initialize everything to -1
int itemIndex = 0;
for (T contact : contacts) {
String indexableItem = ((CityInfo) contact).pinyin;
int sectionIndex = getSectionIndex(indexableItem); // find out which
if (mPositions[sectionIndex] == -1) { // if not set before, then do
mPositions[sectionIndex] = itemIndex;
}
itemIndex++;
}
int lastPos = -1;
// now loop through, for all the ones not found, set position to the one
// before them
// this is to make sure the array is sorted for binary search to work
for (int i = sectionCount - 1; i >= 0; i--) {
if (mPositions[i] == -1) {
mPositions[i] = lastPos;
}
lastPos = mPositions[i];
}
}
@Override
public int getPositionForSection(int section) {
if (section < 0 || section >= mSections.length) {
return -1;
}
return mPositions[section];
}
@Override
public int getSectionForPosition(int position) {
if (position < 0 || position >= mCount) {
return -1;
}
int index = Arrays.binarySearch(mPositions, position);
// 注意这个方法的返回值,它就是index<0时,返回-index-2的原因
// 解释Arrays.binarySearch,如果搜索结果在数组中,刚返回它在数组中的索引,如果不在,刚返回第一个比它大的索引的负数-1
// 如果没弄明白,请自己想查看api
return index >= 0 ? index : -index - 2;
}
@Override
public Object[] getSections() {
return mSections;
}
}
PinYinUtil类就是将汉字转换为拼音:
package com.example.liyachao.sectionstest;
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.HanyuPinyinVCharType;
import net.sourceforge.pinyin4j.format.exception.BadHanyuPinyinOutputFormatCombination;
public class PinYinUtil {
static HanyuPinyinOutputFormat outputFormat;
static {
try {
outputFormat = new HanyuPinyinOutputFormat();
outputFormat.setVCharType(HanyuPinyinVCharType.WITH_V);
outputFormat.setCaseType(HanyuPinyinCaseType.UPPERCASE);
outputFormat.setToneType(HanyuPinyinToneType.WITHOUT_TONE);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 汉字返回拼音,字母原样返回 必须在Android中使用
*
* @param source stirng
* @return string
*/
public static String getFullPinYin(String source) {
StringBuilder sb = new StringBuilder();
if (source.length() > 0) {
for (int i = 0; i < source.length(); i++) {
try {
String[] arrays = PinyinHelper.toHanyuPinyinStringArray(
source.charAt(i), outputFormat);
if (arrays != null && arrays.length > 0) {
sb.append(arrays[0]);
}
} catch (BadHanyuPinyinOutputFormatCombination badHanyuPinyinOutputFormatCombination) {
badHanyuPinyinOutputFormatCombination.printStackTrace();
}
}
}
return sb.toString();
}
/**
* 获取汉字拼音首字母大写 必须在Android中使用
*
* @param source string
* @return string
*/
public static String getFirstPinYin(String source) {
// StringBuilder sb = new StringBuilder();
if (source.length() > 0) {
if (!isEnglish(source)) {
try {
String[] arrays = PinyinHelper.toHanyuPinyinStringArray(
source.charAt(0), outputFormat);
if (arrays != null && arrays.length > 0) {
return arrays[0];
}
} catch (BadHanyuPinyinOutputFormatCombination badHanyuPinyinOutputFormatCombination) {
badHanyuPinyinOutputFormatCombination.printStackTrace();
}
} else {
return String.valueOf(source.charAt(0));
}
}
return "#";
}
public static boolean isEnglish(String s) {
char c = s.charAt(0);
int i = (int) c;
if ((i >= 65 && i <= 90) || (i >= 97 && i <= 122)) {
return true;
} else {
return false;
}
}
}
剩下的代码我就不贴出来了,感兴趣的朋友可以下载下来,研究研究。
大部分代码有注释,而且也不难,大家应该可以看明白。