記得在我剛開始接觸到美團網(wǎng)的時候就對美團網(wǎng)這個城市定位慧瘤、選擇城市功能很感興趣,覺得它做得很棒合瓢。有如下幾個點:
一:實現(xiàn)ListView的A-Z字母排序功能
二:根據(jù)輸入框的輸入值改變來過濾搜索結果屹堰,如果輸入框里面的值為空,更新為原來的列表览徒,否則為過濾數(shù)據(jù)列表
三:漢字轉成拼音的功能,很多時候實現(xiàn)聯(lián)系人或者城市列表等實現(xiàn)A-Z的排序功能颂龙,我們可以直接從數(shù)據(jù)庫中獲取他的漢字拼音习蓬,而對于一般的數(shù)據(jù),我們怎么實現(xiàn)A-Z的排序措嵌,這里我使用了PinYin4j.jar將漢字轉換為拼音.
按照慣例先來看一下最終效果圖:
接下來分析下整個功能模塊的布局結構:
(1)首先一個帶刪除按鈕的EditText躲叼,我們在輸入框中輸入我們查找的城市可以自動過濾出最終的結果,當輸入框中沒有數(shù)據(jù)自動替換到原來的數(shù)據(jù)列表企巢;
(2)中間是當前定位的城市和熱門的城市枫慷,其中熱門城市使用到了GridView;
(3)下面是一個ListView用來顯示數(shù)據(jù)列表,右側是一個字母索引表流礁,當我們點擊不同的字母,ListView會定位到該字母地方
現(xiàn)在我們來看下項目結構圖
按照項目中類的順序來一一介紹
1.PinYin4j.jar用于將漢字轉換為拼音,你還可以使用其他方式將漢子轉換為拼音罗丰,我之前有介紹過神帅,這里就不詳講啦,探索PinYin4j.jar將漢字轉換為拼音的基本用法
2.CitySortModel一個實體類萌抵,一個顯示的城市和相對應的拼音首字母
package com.adan.selectcitydome.view;
public class CitySortModel {
private String name;//顯示的數(shù)據(jù)
private String sortLetters;//顯示數(shù)據(jù)拼音的首字母
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSortLetters() {
return sortLetters;
}
public void setSortLetters(String sortLetters) {
this.sortLetters = sortLetters;
}
}
3.EditTextWithDel類是自定義的一個帶清除功能的輸入框控件,也可以用Android原生的EditText找御,這個類上一篇博客有介紹,這里就不貼上代碼了Android 帶清除功能的輸入框控件EditTextWithDel
4.MyGridView類就是自定義GridView绍填,主要是解決了在熱門城市中嵌套Grideview的顯示不完全的問題
package com.adan.selectcitydome.view;
import android.content.Context;
import android.util.AttributeSet;
import android.widget.GridView;
/**
* 自定義GridView霎桅,解決嵌套Grideview的顯示不完全的問題
*/
public class MyGridView extends GridView {
public MyGridView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public MyGridView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public MyGridView(Context context) {
super(context);
}
/**
* 其中onMeasure函數(shù)決定了組件顯示的高度與寬度;
* makeMeasureSpec函數(shù)中第一個函數(shù)決定布局空間的大小讨永,第二個參數(shù)是布局模式
* MeasureSpec.AT_MOST的意思就是子控件需要多大的控件就擴展到多大的空間
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int expandSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2,
MeasureSpec.AT_MOST);
super.onMeasure(widthMeasureSpec, expandSpec);
}
}
5.PinyinComparator類用來對ListView中的數(shù)據(jù)根據(jù)A-Z進行排序滔驶,前面兩個if判斷主要是將不是以漢字開頭的數(shù)據(jù)放在后面
package com.adan.selectcitydome.view;
import java.util.Comparator;
/**
* 用來對ListView中的數(shù)據(jù)根據(jù)A-Z進行排序,前面兩個if判斷主要是將不是以漢字開頭的數(shù)據(jù)放在后面
*/
public class PinyinComparator implements Comparator<CitySortModel> {
public int compare(CitySortModel o1, CitySortModel o2) {
//這里主要是用來對ListView里面的數(shù)據(jù)根據(jù)ABCDEFG...來排序
if (o1.getSortLetters().equals("@")
|| o2.getSortLetters().equals("#")) {
return -1;
} else if (o1.getSortLetters().equals("#")
|| o2.getSortLetters().equals("@")) {
return 1;
} else {
return o1.getSortLetters().compareTo(o2.getSortLetters());
}
}
}
6.PinyinUtils類卿闹,就是第一點所講的PinYin4j.jar用于將漢字轉換為拼音啦揭糕,這里就不粘貼代碼啦
7.SideBar類就是ListView右側的字母索引View,我們需要使用setTextView(TextView mTextDialog)來設置用來顯示當前按下的字母的TextView,以及使用setOnTouchingLetterChangedListener方法來設置回調(diào)接口锻霎,在回調(diào)方法onTouchingLetterChanged(String s)中來處理不同的操作
package com.adan.selectcitydome.view;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Typeface;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.widget.TextView;
import com.adan.selectcitydome.R;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* ListView右側的字母索引View
*/
public class SideBar extends View {
public static String[] INDEX_STRING = {"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 OnTouchingLetterChangedListener onTouchingLetterChangedListener;
private List<String> letterList;
private int choose = -1;
private Paint paint = new Paint();
private TextView mTextDialog;
public SideBar(Context context) {
this(context, null);
}
public SideBar(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public SideBar(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init();
}
private void init() {
setBackgroundColor(Color.parseColor("#F0F0F0"));
letterList = Arrays.asList(INDEX_STRING);
}
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
int height = getHeight();// 獲取對應高度
int width = getWidth();// 獲取對應寬度
int singleHeight = height / letterList.size();// 獲取每一個字母的高度
for (int i = 0; i < letterList.size(); i++) {
paint.setColor(Color.parseColor("#606060"));
paint.setTypeface(Typeface.DEFAULT_BOLD);
paint.setAntiAlias(true);
paint.setTextSize(20);
// 選中的狀態(tài)
if (i == choose) {
paint.setColor(Color.parseColor("#4F41FD"));
paint.setFakeBoldText(true);
}
// x坐標等于中間-字符串寬度的一半.
float xPos = width / 2 - paint.measureText(letterList.get(i)) / 2;
float yPos = singleHeight * i + singleHeight / 2;
canvas.drawText(letterList.get(i), xPos, yPos, paint);
paint.reset();// 重置畫筆
}
}
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
final int action = event.getAction();
final float y = event.getY();// 點擊y坐標
final int oldChoose = choose;
final OnTouchingLetterChangedListener listener = onTouchingLetterChangedListener;
final int c = (int) (y / getHeight() * letterList.size());// 點擊y坐標所占總高度的比例*b數(shù)組的長度就等于點擊b中的個數(shù).
switch (action) {
case MotionEvent.ACTION_UP:
setBackgroundColor(Color.parseColor("#F0F0F0"));
choose = -1;
invalidate();
if (mTextDialog != null) {
mTextDialog.setVisibility(View.GONE);
}
break;
default:
setBackgroundResource(R.drawable.sidebar_background);
if (oldChoose != c) {
if (c >= 0 && c < letterList.size()) {
if (listener != null) {
listener.onTouchingLetterChanged(letterList.get(c));
}
if (mTextDialog != null) {
mTextDialog.setText(letterList.get(c));
mTextDialog.setVisibility(View.VISIBLE);
}
choose = c;
invalidate();
}
}
break;
}
return true;
}
public void setIndexText(ArrayList<String> indexStrings) {
this.letterList = indexStrings;
invalidate();
}
/**
* 為SideBar設置顯示當前按下的字母的TextView
*
* @param mTextDialog
*/
public void setTextView(TextView mTextDialog) {
this.mTextDialog = mTextDialog;
}
/**
* 向外公開的方法
*
* @param onTouchingLetterChangedListener
*/
public void setOnTouchingLetterChangedListener(
OnTouchingLetterChangedListener onTouchingLetterChangedListener) {
this.onTouchingLetterChangedListener = onTouchingLetterChangedListener;
}
/**
* 接口
*/
public interface OnTouchingLetterChangedListener {
void onTouchingLetterChanged(String s);
}
}
8.CityAdapter就是熱門城市中GridView的適配器
package com.adan.selectcitydome;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.LinearLayout;
import java.util.List;
/**
* @author: xiaolijuan
* @description:
* @projectName: SelectCityDome
* @date: 2016-03-01
* @time: 17:25
*/
public class CityAdapter extends ArrayAdapter<String> {
/**
* 需要渲染的item布局文件
*/
private int resource;
public CityAdapter(Context context, int textViewResourceId, List<String> objects) {
super(context, textViewResourceId, objects);
resource = textViewResourceId;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
LinearLayout layout = null;
if (convertView == null) {
layout = (LinearLayout) LayoutInflater.from(getContext()).inflate(resource, null);
} else {
layout = (LinearLayout) convertView;
}
Button name = (Button) layout.findViewById(R.id.tv_city);
name.setText(getItem(position));
return layout;
}
}
9.SortAdapter 數(shù)據(jù)的適配器類著角,這里我們需要用到的就是SectionIndexer接口,它能夠有效地幫助我們對分組進行控制旋恼。使用SectionIndexer接口需要實現(xiàn)三個方法:getSectionForPosition(int position)吏口,getPositionForSection(int section),getSections()冰更,我們只需要自行實現(xiàn)前面兩個方法:
(一)getSectionForPosition(int position)是根據(jù)ListView的position來找出當前位置所在的分組
getPositionForSection(int section)就是根據(jù)首字母的Char值來獲取在該ListView中第一次出現(xiàn)該首字母的位置,也就是當前分組所在的位置
package com.adan.selectcitydome;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.SectionIndexer;
import android.widget.TextView;
import com.adan.selectcitydome.view.CitySortModel;
import java.util.List;
public class SortAdapter extends BaseAdapter implements SectionIndexer {
private List<CitySortModel> list = null;
private Context mContext;
public SortAdapter(Context mContext, List<CitySortModel> list) {
this.mContext = mContext;
this.list = list;
}
/**
* 當ListView數(shù)據(jù)發(fā)生變化時,調(diào)用此方法來更新ListView
*
* @param list
*/
public void updateListView(List<CitySortModel> list) {
this.list = list;
notifyDataSetChanged();
}
public int getCount() {
return this.list.size();
}
public Object getItem(int position) {
return list.get(position);
}
public long getItemId(int position) {
return position;
}
public View getView(final int position, View view, ViewGroup arg2) {
ViewHolder viewHolder = null;
final CitySortModel mContent = list.get(position);
if (view == null) {
viewHolder = new ViewHolder();
view = LayoutInflater.from(mContext).inflate(R.layout.item_select_city, null);
viewHolder.tvTitle = (TextView) view.findViewById(R.id.tv_city_name);
view.setTag(viewHolder);
viewHolder.tvLetter = (TextView) view.findViewById(R.id.tv_catagory);
} else {
viewHolder = (ViewHolder) view.getTag();
}
int section = getSectionForPosition(position);
if (position == getPositionForSection(section)) {
viewHolder.tvLetter.setVisibility(View.VISIBLE);
viewHolder.tvLetter.setText(mContent.getSortLetters());
} else {
viewHolder.tvLetter.setVisibility(View.GONE);
}
viewHolder.tvTitle.setText(this.list.get(position).getName());
return view;
}
final static class ViewHolder {
TextView tvLetter;
TextView tvTitle;
}
public int getSectionForPosition(int position) {
return list.get(position).getSortLetters().charAt(0);
}
public int getPositionForSection(int section) {
for (int i = 0; i < getCount(); i++) {
String sortStr = list.get(i).getSortLetters();
char firstChar = sortStr.toUpperCase().charAt(0);
if (firstChar == section) {
return i;
}
}
return -1;
}
@Override
public Object[] getSections() {
return null;
}
}
10.MainActivity 對EditTextWithDel設置addTextChangedListener監(jiān)聽产徊,當輸入框內(nèi)容發(fā)生變化根據(jù)里面的值過濾ListView,里面的值為空顯示原來的列表和給ListView添加表頭等
package com.adan.selectcitydome;
import android.app.Activity;
import android.os.Bundle;
import android.text.Editable;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.view.View;
import android.widget.AdapterView;
import android.widget.GridView;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
import com.adan.selectcitydome.view.CitySortModel;
import com.adan.selectcitydome.view.EditTextWithDel;
import com.adan.selectcitydome.view.PinyinComparator;
import com.adan.selectcitydome.view.PinyinUtils;
import com.adan.selectcitydome.view.SideBar;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class MainActivity extends Activity {
private ListView sortListView;
private SideBar sideBar;
private TextView dialog, mTvTitle;
private SortAdapter adapter;
private EditTextWithDel mEtCityName;
private List<CitySortModel> SourceDateList;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initViews();
}
private void initViews() {
mEtCityName = (EditTextWithDel) findViewById(R.id.et_search);
sideBar = (SideBar) findViewById(R.id.sidrbar);
dialog = (TextView) findViewById(R.id.dialog);
mTvTitle = (TextView) findViewById(R.id.tv_title);
sortListView = (ListView) findViewById(R.id.country_lvcountry);
initDatas();
initEvents();
setAdapter();
}
private void setAdapter() {
SourceDateList = filledData(getResources().getStringArray(R.array.provinces));
Collections.sort(SourceDateList, new PinyinComparator());
adapter = new SortAdapter(this, SourceDateList);
sortListView.addHeaderView(initHeadView());
sortListView.setAdapter(adapter);
}
private void initEvents() {
//設置右側觸摸監(jiān)聽
sideBar.setOnTouchingLetterChangedListener(new SideBar.OnTouchingLetterChangedListener() {
@Override
public void onTouchingLetterChanged(String s) {
//該字母首次出現(xiàn)的位置
int position = adapter.getPositionForSection(s.charAt(0));
if (position != -1) {
sortListView.setSelection(position + 1);
}
}
});
//ListView的點擊事件
sortListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view,
int position, long id) {
mTvTitle.setText(((CitySortModel) adapter.getItem(position - 1)).getName());
Toast.makeText(getApplication(), ((CitySortModel) adapter.getItem(position)).getName(), Toast.LENGTH_SHORT).show();
}
});
//根據(jù)輸入框輸入值的改變來過濾搜索
mEtCityName.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
//當輸入框里面的值為空冬殃,更新為原來的列表囚痴,否則為過濾數(shù)據(jù)列表
filterData(s.toString());
}
@Override
public void afterTextChanged(Editable s) {
}
});
}
private void initDatas() {
sideBar.setTextView(dialog);
}
private View initHeadView() {
View headView = getLayoutInflater().inflate(R.layout.headview, null);
GridView mGvCity = (GridView) headView.findViewById(R.id.gv_hot_city);
String[] datas = getResources().getStringArray(R.array.city);
ArrayList<String> cityList = new ArrayList<>();
for (int i = 0; i < datas.length; i++) {
cityList.add(datas[i]);
}
CityAdapter adapter = new CityAdapter(getApplicationContext(), R.layout.gridview_item, cityList);
mGvCity.setAdapter(adapter);
return headView;
}
/**
* 根據(jù)輸入框中的值來過濾數(shù)據(jù)并更新ListView
*
* @param filterStr
*/
private void filterData(String filterStr) {
List<CitySortModel> mSortList = new ArrayList<>();
if (TextUtils.isEmpty(filterStr)) {
mSortList = SourceDateList;
} else {
mSortList.clear();
for (CitySortModel sortModel : SourceDateList) {
String name = sortModel.getName();
if (name.toUpperCase().indexOf(filterStr.toString().toUpperCase()) != -1 || PinyinUtils.getPingYin(name).toUpperCase().startsWith(filterStr.toString().toUpperCase())) {
mSortList.add(sortModel);
}
}
}
// 根據(jù)a-z進行排序
Collections.sort(mSortList, new PinyinComparator());
adapter.updateListView(mSortList);
}
private List<CitySortModel> filledData(String[] date) {
List<CitySortModel> mSortList = new ArrayList<>();
ArrayList<String> indexString = new ArrayList<>();
for (int i = 0; i < date.length; i++) {
CitySortModel sortModel = new CitySortModel();
sortModel.setName(date[i]);
String pinyin = PinyinUtils.getPingYin(date[i]);
String sortString = pinyin.substring(0, 1).toUpperCase();
if (sortString.matches("[A-Z]")) {
sortModel.setSortLetters(sortString.toUpperCase());
if (!indexString.contains(sortString)) {
indexString.add(sortString);
}
}
mSortList.add(sortModel);
}
Collections.sort(indexString);
sideBar.setIndexText(indexString);
return mSortList;
}
}
布局文件就不貼出來了,有興趣的可以下載代碼审葬,有什么疑問的深滚,請在下面留言,感謝各位_
本項目改造自http://blog.csdn.net/xiaanming/article/details/12684155
源代碼請戳這里:Android 仿美團網(wǎng),探索ListView的A-Z字母排序功能實現(xiàn)選擇省份或者城市