在开发app的过程中,如果用到通讯录或者类似的列表,需要快速在其中定位,可以根据列表项的拼音首字母来定位,这时候就需要用到右侧字母索引了。必如现在的微信通讯录界面就是如此。在实现这种功能的过程中,还是挺复杂的,很难我觉得。在网上各种查找资料,困难重重,好在最后终于捯饬出来了,伤不起。。。。特此记录一下写的过程。
1、创建自定的view,用作右侧列表索引。
public class RulerWidget extends View {
public static String[] indexStr = {
"#", "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"
};
public static int INDEX_LENGTH = indexStr.length;
OnTouchingLetterChangedListener onTouchingLetterChangedListener;
Paint mPaint = new Paint();
boolean showBkg = false;
int choose = -1;
public RulerWidget(Context context) {
super(context);
}
public RulerWidget(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public RulerWidget(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// if(showBkg){
canvas.drawColor(Color.parseColor("#40000000"));
// }
int height = getHeight();
int width = getWidth();
int singleHeight = height / indexStr.length;
for(int i=0;i<indexStr.length;i++){
mPaint.setColor(Color.WHITE);
mPaint.setTextSize(24);
mPaint.setTypeface(Typeface.DEFAULT_BOLD);
mPaint.setAntiAlias(true);
if(i == choose){
mPaint.setColor(Color.parseColor("#3399ff"));
mPaint.setFakeBoldText(true);
}
float xPos = width/2 - mPaint.measureText(indexStr[i])/2;
float yPos = singleHeight * i + singleHeight;
canvas.drawText(indexStr[i], xPos, yPos, mPaint);
mPaint.reset();
}
}
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
final int action = event.getAction();
final float y = event.getY();
final int oldChoose = choose;
final OnTouchingLetterChangedListener listener = onTouchingLetterChangedListener;
final int c = (int) (y/getHeight()*indexStr.length);
switch (action) {
case MotionEvent.ACTION_DOWN:
showBkg = true;
if(oldChoose != c && listener != null){
if(c > 0 && c< indexStr.length){
listener.onTouchingLetterChanged(indexStr[c]);
choose = c;
invalidate();
}
}
break;
case MotionEvent.ACTION_MOVE:
if(oldChoose != c && listener != null){
if(c > 0 && c< indexStr.length){
listener.onTouchingLetterChanged(indexStr[c]);
choose = c;
invalidate();
}
}
break;
case MotionEvent.ACTION_UP:
// showBkg = false;
choose = -1;
invalidate();
break;
}
return true;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
return super.onTouchEvent(event);
}
public void setOnTouchingLetterChangedListener(
OnTouchingLetterChangedListener onTouchingLetterChangedListener) {
this.onTouchingLetterChangedListener = onTouchingLetterChangedListener;
}
}
对应的回调接口定义:
/**
* @date 2014-9-3
* @Description: ruler触摸回调
*/
public interface OnTouchingLetterChangedListener{
public void onTouchingLetterChanged(String s);
}
2、创建fragment片段对应的布局文件:
<?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" > <FrameLayout android:layout_width="match_parent" android:layout_height="match_parent" android:background="#ffffff" > <ListView android:id="@+id/listView" android:layout_width="match_parent" android:layout_height="wrap_content" android:cacheColorHint="#00000000" android:fadingEdge="none" android:scrollbars="none" > </ListView> <TextView android:id="@+id/tv" android:layout_width="60dp" android:layout_height="60dp" android:gravity="center" android:background="#f0606060" android:layout_gravity="center" android:text="A" android:textColor="#ffffff" android:textSize="30sp" /> <com.hy.ticket.view.RulerWidget android:id="@+id/sidrbar" android:layout_width="30.0dip" android:layout_height="fill_parent" android:layout_gravity="right|center" /> </FrameLayout> </LinearLayout>
3、引入pinyin4j.jar包(版本建议使用最新),写一个工具类,包含获取中文字符串拼音首字母,获取中文拼音等方法。
public class StringHelper {
public static String getPingYin(String src) {
char[] t = src.toCharArray();
String[] strs = new String[t.length];
HanyuPinyinOutputFormat format = new HanyuPinyinOutputFormat();
format.setCaseType(HanyuPinyinCaseType.LOWERCASE);
format.setToneType(HanyuPinyinToneType.WITHOUT_TONE);
format.setVCharType(HanyuPinyinVCharType.WITH_V);
String str = "";
try{
for(int i=0; i<t.length; i++) {
if(java.lang.Character.toString(t[i]).matches(
"[\\u4E00-\\u9FA5]+")) {
strs = PinyinHelper.toHanyuPinyinStringArray(t[i], format);
str += strs[0];
}else{
str += java.lang.Character.toString(t[i]);
}
}
//长沙,长春多音字处理
if(str.equals("zhangsha")) {
str = "changsha";
}
if(str.equals("zhangchun")) {
str = "changchun";
}
return str;
}catch(Exception e) {
e.printStackTrace();
}
return str;
}
public static String getHeaderChar(String src) {
String convert = "";
char word = src.charAt(0);
String[] arr = PinyinHelper.toHanyuPinyinStringArray(word);
if(arr != null) {
convert += arr[0].charAt(0);
}else{
convert += word;
}
return convert.toUpperCase();
}
public static String getPinYinHeaderChar(String src) {
String convert = "";
for(int i=0; i<src.length(); i++) {
char word = src.charAt(i);
String[] arr = PinyinHelper.toHanyuPinyinStringArray(word);
if(arr != null) {
convert += arr[0].charAt(0);
}else{
convert += word;
}
}
return convert.toUpperCase();
}
}
4、建立一个工具类,用于根据首字母排序,获取大些字母及对应的中文字符串(如A 阿尔法 B 北京 C 长沙 长春 常州等):
public class SortUtil {
public static List<Station> sortList(String[] srcNames, List<Station> list) {
List<Station> newList = new ArrayList<Station>();
for(int i=0; i<srcNames.length; i++) {
if(srcNames[i].length() != 1) {
for(int j=0; j<list.size(); j++) {
if(srcNames[i].equals(list.get(j).getPinYinName())) {
Station s = new Station(list.get(j).getName(), list.get(j).getPinYinName());
newList.add(s);
}
}
}else{
newList.add(new Station(srcNames[i]));
}
}
return newList;
}
public static String[] sortIndex(List<Station> stations) {
TreeSet<String> set = new TreeSet<String>();
// 获取初始化数据源中的首字母,添加到set中
for (Station station : stations) {
set.add(StringHelper.getPinYinHeaderChar(station.getName()).substring(
0, 1));
}
// 新数组的长度为原数据加上set的大小
String[] names = new String[stations.size() + set.size()];
int i = 0;
for (String string : set) {
names[i] = string;
i++;
}
String[] pinYinNames = new String[stations.size()];
for (int j = 0; j < stations.size(); j++) {
stations.get(j).setPinYinName(
StringHelper
.getPingYin(stations.get(j).getName().toString()));
pinYinNames[j] = StringHelper.getPingYin(stations.get(j).getName()
.toString());
}
// 将原数据拷贝到新数据中
System.arraycopy(pinYinNames, 0, names, set.size(), pinYinNames.length);
// 自动按照首字母排序
Arrays.sort(names, String.CASE_INSENSITIVE_ORDER);
return names;
}
}
listViewAdapter,填充ListView的适配器:
public class ListViewAdapter extends BaseAdapter {
private Context context;
private List<Station> stations;
private ViewHolder viewHolder;
public ListViewAdapter(Context context, List<Station> stations) {
this.context = context;
this.stations = stations;
}
@Override
public int getCount() {
return stations.size();
}
@Override
public Object getItem(int position) {
return stations.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public boolean isEnabled(int position) {
if (stations.get(position).getName().length() == 1)// 如果是字母索引
return false;// 表示不能点击
return super.isEnabled(position);
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
String item = stations.get(position).getName();
viewHolder = new ViewHolder();
if(item.length() == 1) {
convertView = LayoutInflater.from(context).inflate(R.layout.index, null);
TextView indexTV = (TextView) convertView.findViewById(R.id.indexTV);
indexTV.setText(stations.get(position).getName());
viewHolder.indexTV = indexTV;
}else{
convertView = LayoutInflater.from(context).inflate(R.layout.item, null);
TextView itemTV = (TextView) convertView.findViewById(R.id.itemTV);
itemTV.setText(stations.get(position).getName());
viewHolder.itemTV = itemTV;
}
return convertView;
}
//内部类
private class ViewHolder {
private TextView itemTV;
private TextView indexTV;
}
}
5、最后一步,建立布局文件对应的fragment片段(或Activity,这里使用的是fragment):
public class ListStation extends Fragment {
private Map<String, Integer> selector;
private LinearLayout layoutIndex;
private ListView listView;
private TextView textView;
private ListViewAdapter adapter;
private String[] sections = {
"#", "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 List<Station> stations;
private List<Station> newStations;
private int height;
private boolean flag = false;
private LinearLayout layout;
private RulerWidget ruler;
private Handler handler;
private OverlayThread overlayThread;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.station_list, container, false);
listView = (ListView) view.findViewById(R.id.listView);
textView = (TextView) view.findViewById(R.id.tv);
ruler = (RulerWidget) view.findViewById(R.id.sidrbar);
ruler.setOnTouchingLetterChangedListener(new LetterListViewListener());
initOverlay();
return view;
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
stations = StationService.getAllStation();
String[] allNames = SortUtil.sortIndex(stations);
newStations = SortUtil.sortList(allNames, stations);
//这个map是建立大写字母对应位于listView位置的索引
selector = new HashMap<String, Integer>();
for(int i=0; i<allNames.length; i++) {
if(allNames[i].length() == 1) {
selector.put(allNames[i], i);
}
}
adapter = new ListViewAdapter(getActivity(), newStations);
listView.setAdapter(adapter);
handler = new Handler();
overlayThread = new OverlayThread();
}
//初始化汉语拼音首字母弹出提示框
private void initOverlay() {
LayoutInflater inflater = LayoutInflater.from(getActivity());
// overlay = (TextView) inflater.inflate(R.layout.overlay, null);
textView.setVisibility(View.INVISIBLE);
WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
LayoutParams.WRAP_CONTENT,
LayoutParams.WRAP_CONTENT,
WindowManager.LayoutParams.TYPE_APPLICATION,
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE| WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE,
PixelFormat.TRANSLUCENT);
WindowManager windowManager = (WindowManager) getActivity().getSystemService(Context.WINDOW_SERVICE);
// windowManager.addView(textView, lp);
}
private class LetterListViewListener implements OnTouchingLetterChangedListener{
@Override
public void onTouchingLetterChanged(final String s) {
if(selector.get(s) != null) {
int position = selector.get(s);
listView.setSelection(position);
textView.setText(s);
textView.setVisibility(View.VISIBLE);
handler.removeCallbacks(overlayThread);
//延迟一秒后执行,让overlay为不可见
handler.postDelayed(overlayThread, 1500);
}
}
}
//设置overlay不可见
private class OverlayThread implements Runnable {
@Override
public void run() {
textView.setVisibility(View.GONE);
}
}
/*class GetDataAsyTask extends AsyncTask {
@Override
protected void onPreExecute() {
super.onPreExecute();
}
@Override
protected Object doInBackground(Object... params) {
stations = StationService.getAllStation();
String[] allNames = SortUtil.sortIndex(stations);
newStations = SortUtil.sortList(allNames, stations);
if(newStations != null) {
newStations.clear();
newStations = null;
}
if(stations != null) {
stations.clear();
stations = null;
}
if(allNames != null) {
allNames = null;
}
return null;
}
@Override
protected void onPostExecute(Object result) {
// super.onPostExecute(result);
if(newStations == null) {
// textView.setVisibility(View.VISIBLE);
return;
}
handleSuccessData();
}
}
private Integer getPosition(final int j) {
Integer pos = null;
int i = j;
while (pos == null && i <= RulerWidget.indexStr.length - 1) {
pos = selector.get(RulerWidget.indexStr[i]);
i++;
}
if (pos == null) {
pos = newStations.size() - 1;
}
return pos;
}
private void handleSuccessData() {
listView.setVisibility(View.VISIBLE);
ruler.setVisibility(View.VISIBLE);
adapter = new ListViewAdapter(getActivity(), newStations);
ruler.setOnRulerTouch(new OnRulerTouch() {
@Override
public void onUP() {
}
@Override
public void onOthers() {
}
@Override
public void onMove(int position) {
textView.setText(RulerWidget.indexStr[position]);
listView.setSelection(getPosition(position));
}
@Override
public void onDown(int position) {
textView.setVisibility(View.VISIBLE);
textView.setText(RulerWidget.indexStr[position]);
listView.setSelection(getPosition(position));
}
});
listView.setAdapter(adapter);
adapter.notifyDataSetChanged();
}
*/
}
这样,带拼音索引的listView建立完成。。