转载请注明出处:http://blog.csdn.net/lxlyhm/article/details/52049321
自定义城市列表 相信大家都很常见,特别是在这两年非常受欢迎的在线旅游软件上(途牛旅游,驴妈妈旅游等)城市列表是不可或缺的一个模块,先上图看效果
点击搜索栏,输入搜索关键字,首字母,拼音:
首先分析界面需求,搭建布局。此界面主要被我分成5部分:
第一部分是头部标题栏部分,此处可以任意使用自定义标题栏也好,简单布局也好,只要能实现就可以,我在此处使用简单布局;
第二部分是搜索栏部分,此部分引用普通布局,设置布局背景drawable/shape_layout即可;
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" >
<corners android:radius="5dp"/>
<solid android:color="#ffffff"/>
</shape>
第三部分是自定义View字母索引部分,自定义一个类MyView1继承View,上代码,相信注释都写得比较清晰了
public class MyView1 extends View
{
private Paint mPaint;//初始画笔
private int right;//覆盖画布右边范围
private int index;//下标
private boolean ispress;//是否触碰点击
private Paint touchPaint;//触碰点击画笔
//如果json文件无数据 初始化数据和字母
private ArrayList<String> letterList = new ArrayList<String>();
String[] letters = new 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"};
//构造方法
public MyView1(Context context, AttributeSet attrs)
{
super(context, attrs);
initPaint();//新建画笔
initLetter();//初始化静态数据
}
//如果json文件无数据 初始化字母
public void initLetter()
{
for (int i = 0; i < letters.length; i++)
{
letterList.add(letters[i]);
}
}
// 新建画笔
public void initPaint()
{
//默认文本
mPaint = new Paint();
mPaint.setStyle(Paint.Style.STROKE);//设置风格
mPaint.setColor(Color.parseColor("#ff999999"));// 设置颜色
mPaint.setTextSize((int) this.getResources().getDimension(R.dimen.city_textSize));//小手机
mPaint.setTextAlign(Align.CENTER);//设置文本的对齐方式
//触摸按下文本
touchPaint = new Paint();
touchPaint.setStyle(Paint.Style.STROKE);//设置风格
touchPaint.setColor(Color.parseColor("#ffaf0000"));// 设置颜色
touchPaint.setTextSize((int) this.getResources().getDimension(R.dimen.city_textSize_pre));//小手机
touchPaint.setTextAlign(Align.CENTER);//设置文本的对齐方式
}
//canvas画布
@Override
protected void onDraw(Canvas canvas)
{
super.onDraw(canvas);
if (ispress)
{
canvas.drawARGB(100, 100, 100, 100);
}
canvas.translate(0, 28);//起始位置 绝对位移
int width = canvas.getWidth();//画布宽度
int height = canvas.getHeight();//画布高度
//画默认文本
for (int i = 0; i < letterList.size(); i++)
{
//平均分成几份,即每一个字母的高度=总高度/总份数
int h = height/letterList.size();
//按下 即触碰
if (index == i && ispress)
{
canvas.drawText(letterList.get(i), width/2, h*i+5, touchPaint);
}else
{
//没按下
canvas.drawText(letterList.get(i), width/2, h*i+5, mPaint);
}
}
}
@Override
public boolean onTouchEvent(MotionEvent event)
{
float y = event.getY();//获得手指触摸的Y轴坐标
float c = event.getX();//获得手指触摸的X轴坐标
//获得按下文本的下标=触摸处的高度/每一份的高度
index = (int) (y/(getHeight()/letterList.size()));
if (index >= letterList.size())
{
index = letterList.size() -1;
}
if (index < 0)
{
index = 0;
}
//捕获手指的动作 按下 滑动 抬起
switch (event.getAction())
{
case MotionEvent.ACTION_DOWN://按下
ispress = true;
listener.OnLetterTouch(letterList.get(index));
break;
case MotionEvent.ACTION_MOVE://移动
ispress = true;
listener.OnLetterTouch(letterList.get(index));
break;
case MotionEvent.ACTION_UP://抬起
ispress = false;
listener.OnLetterUp();
break;
default:
break;
}
invalidate();
return true;
}
//添加字母
public void setLetters(ArrayList<String> list)
{
letterList.clear();
letterList.addAll(list);
invalidate();
}
//声明接口变量
OnLetterListener listener;
//定义监听方法
public void setOnLetterListener(OnLetterListener l)
{
listener = l;
}
//定义接口
public interface OnLetterListener
{
void OnLetterTouch(String letter);
void OnLetterUp();
}
}
第四部分是普通列表部分,此处可以灵活使用RecyclerView或者ListView,我简单使用ListView,此部分可以用一个listview直接add三个头部即可;
由于热门城市也是一个列表,所有需要用listview的item是一个listview,即listview嵌套listview,使用过类似功能的小伙伴们肯定有过嵌套之后子listview显示不完全的情况发生,所有此处需要自定义一个listiew重新计算高度,作为主listview的头部添加作子listview,代码如下所示:
public class MyListView1 extends ListView {
public MyListView1(Context context, AttributeSet attrs) {
super(context, attrs);
}
public MyListView1(Context context) {
super(context);
}
public MyListView1(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
@Override
public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int expandSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2,
MeasureSpec.AT_MOST);
super.onMeasure(widthMeasureSpec, expandSpec);
}
}
第五部分是搜索列表部分,此处也可以灵活使用RecyclerView或者ListView,我简单使用ListView;
分析完页面需求部分以后便可以根据需求搭建布局了,此处我把所有城市显示列表的listview、城市搜索显示列表的listview和自定义字母索引view放入一个帧布局,方便显示隐藏操作,布局代码如下所示:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="35dp" >
<ImageView
android:id="@+id/city_close"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_alignParentLeft="true"
android:layout_marginLeft="10dp"
android:src="@drawable/btn_close" />
<TextView
android:id="@+id/textView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="定位城市"
android:textSize="15sp"
android:textColor="@color/color_af0000"
android:textAppearance="?android:attr/textAppearanceLarge" />
</RelativeLayout>
<RelativeLayout
android:id="@+id/relativeLayout_serch"
android:layout_width="match_parent"
android:layout_height="40dp"
android:background="#e0e0e0"
android:padding="5dp" >
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="30dp"
android:background="@drawable/shape_layout"
android:gravity="center" >
<EditText
android:id="@+id/et_city_search"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_toRightOf="@+id/image_search_city"
android:background="@null"
android:ems="10"
android:singleLine="true"
android:textSize="13sp"
android:textColor="#999999"
android:cursorVisible="false"
android:hint=" 请输入城市中文或拼音" />
<ImageView
android:id="@+id/image_search_city"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_centerVertical="true"
android:src="@drawable/btn_search_gray" />
</RelativeLayout>
</RelativeLayout>
<FrameLayout
android:id="@+id/FrameLayout1"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<ListView
android:id="@+id/listView1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:scrollbars="none" >
</ListView>
<com.example.util.MyView1
android:id="@+id/myView1"
android:layout_width="40dp"
android:layout_gravity="right"
android:layout_height="wrap_content" />
<ListView
android:id="@+id/listView2"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#ffffff"
android:visibility="gone" >
</ListView>
</FrameLayout>
</LinearLayout>
接下去就可以根据布局和业务逻辑敲代码了,所有代码如下所示( 此处有用到一个城市列表数据,这里提供给大家我存放在七牛云的虚拟城市列表数据):
public class CityActivity extends Activity implements OnClickListener
{
// 保存城市首字母
private ArrayList<String> list = new ArrayList<String>();
// 保存搜索城市信息
private ArrayList<CityData> searchList = new ArrayList<CityData>();
// 保存所有城市信息
private ArrayList<CityData> cityList = new ArrayList<CityData>();
// 保存热门城市信息
private ArrayList<CityData> hotCityList = new ArrayList<CityData>();
private MyView1 myView1;//自定义字母索引
private ListView allCityListView;//所有城市ListView
private MyAllCityAdater myAdater;//适配器
private String letter;//字段
private MyListView1 head_hot_listview;//自定义热门城市listview,防止listview嵌套listview滑动冲突
String url = "http://7xtycp.com1.z0.glb.clouddn.com/cities.txt";//网址
private HotCityAdater HotCityAdater;//热门城市适配器
private ImageView image_city_back;
private ListView searchListView;//城市搜索listview
private MySearchAdater mySearchAdater;
private EditText et_city_search;
private TextView tv_location;
private MyGridView mGridView;//最近访问
private GridAdater gridAdater;
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.city_activity);
initUI();
//TODO 城市定位
initLocation();//此处定位使用百度地图,如有不懂请自行查阅百度地图API
}
private void initUI()
{
myView1 = (MyView1) findViewById(R.id.myView1);
allCityListView = (ListView) findViewById(R.id.listView1);
searchListView = (ListView) findViewById(R.id.listView2);
image_city_back = (ImageView) findViewById(R.id.city_close);
// ListView 添加三个HeaderView:定位、历史、热门 (必须在适配器之前)
View location = getLayoutInflater().inflate(R.layout.city_heard_location, null);
tv_location = (TextView) location.findViewById(R.id.tv_location);
View recent = getLayoutInflater().inflate(R.layout.city_heard_recent, null);
mGridView = (MyGridView) recent.findViewById(R.id.city_recent_gridview);
View hotcity = getLayoutInflater().inflate(R.layout.city_heard_hotcity, null);
head_hot_listview = (MyListView1) hotcity.findViewById(R.id.head_hot_listview);
allCityListView.addHeaderView(location,null,false);
allCityListView.addHeaderView(recent,null,false);
allCityListView.addHeaderView(hotcity,null,false);
myAdater = new MyAllCityAdater();
mySearchAdater = new MySearchAdater();
HotCityAdater = new HotCityAdater();
gridAdater = new GridAdater();
mGridView.setAdapter(gridAdater);
searchListView.setAdapter(mySearchAdater);
head_hot_listview.setAdapter(HotCityAdater);
allCityListView.setAdapter(myAdater);
myView1.setLetters(list);
list.add("定位");
list.add("历史");
list.add("热门");
myView1.setOnLetterListener(new OnLetterListener()
{
@Override
public void OnLetterUp()
{
}
@Override
public void OnLetterTouch(String letter)
{
// TODO 点击自动跳到指定首字母位置的城市
int position = -1;
if (letter.equals("定位"))
{
position = 0;
} else if (letter.equals("历史"))
{
position = 1;
} else if (letter.equals("热门"))
{
position = 2;
} else
{
for (int i = 0; i < cityList.size(); i++)
{
String upperCase = cityList.get(i).getPinyin().substring(0, 1).toUpperCase();
if (upperCase.equals(letter))
{
position = i + 3;
break;
}
}
}
allCityListView.setSelection(position);
}
});
//异步任务下载
new JsonAsync().execute(url);
// 城市搜索
et_city_search = (EditText) findViewById(R.id.et_city_search);
et_city_search.addTextChangedListener(new TextWatcher()
{
@Override
public void onTextChanged(CharSequence s, int start, int before, int count)
{
String key = s.toString();
if ("".equals(key))
{
searchListView.setVisibility(View.GONE);
} else
{
searchListView.setVisibility(View.VISIBLE);
}
searchList.clear();
mySearchAdater.notifyDataSetChanged();
for (int i = 0; i < cityList.size(); i++)
{
CityData cityData = cityList.get(i);
if (cityData.getPinyin().startsWith(key.toLowerCase()) || cityData.getName().startsWith(key))
{
searchList.add(cityData);
mySearchAdater.notifyDataSetChanged();
}
}
}
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after)
{
}
@Override
public void afterTextChanged(Editable s)
{
}
});
allCityListView.setOnItemClickListener(new OnItemClickListener()
{
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id)
{
int count= allCityListView.getHeaderViewsCount();
CityData cityData = cityList.get(position-count);
//TODO 返回结果给首页面
Intent intent = new Intent();
intent.putExtra("city", cityData.getName());
setResult(1,intent);
finish();
}
});
head_hot_listview.setOnItemClickListener(new OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view,
int position, long id) {
Intent intent=new Intent();
intent.putExtra("city", hotCityList.get(position).getName());
setResult(1,intent);
finish();
}
});
searchListView.setOnItemClickListener(new OnItemClickListener()
{
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id)
{
CityData cityData = searchList.get(position);
Intent intent=new Intent();
intent.putExtra("city", cityData.getName());
setResult(1,intent);
finish();
}
});
mGridView.setOnItemClickListener(new OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view,
int position, long id) {
Intent intent=new Intent();
intent.putExtra("city", hotCityList.get(position).getName());
setResult(1,intent);
finish();
}
});
image_city_back.setOnClickListener(this);
}
public MyLocationListenner myListener = new MyLocationListenner();
private LocationClient mLocClient;
private void initLocation()
{
// 定位初始化
mLocClient = new LocationClient(this);
mLocClient.registerLocationListener(myListener);
LocationClientOption option = new LocationClientOption();
option.setOpenGps(true); // 打开gps
option.setCoorType("bd09ll"); // 设置坐标类型
option.setScanSpan(1000);
option.setAddrType("all");
mLocClient.setLocOption(option);
mLocClient.start();
}
/**
* 定位SDK监听函数
*/
public class MyLocationListenner implements BDLocationListener {
@Override
public void onReceiveLocation(BDLocation location)
{
String city = location.getCity();
if (city==null)
{
tv_location.setText("定位失败");
}else
{
tv_location.setText(city);
}
}
public void onReceivePoi(BDLocation poiLocation) {
}
}
@Override
protected void onDestroy() {
super.onDestroy();
// 退出时销毁定位
if (mLocClient != null)
{
mLocClient.stop();
}
}
class JsonAsync extends AsyncTask<String, Void, String>
{
@Override
protected String doInBackground(String... params)
{
ByteArrayOutputStream baos = new ByteArrayOutputStream();
String result = "";
InputStream is = null;
try
{
URL url = new URL(params[0]);
is = url.openStream();
int len = 0;
byte[] buffer = new byte[1024];
while (-1 != (len = is.read(buffer)))
{
baos.write(buffer, 0, len);
}
result = new String(baos.toByteArray());
baos.flush();
} catch (MalformedURLException e)
{
e.printStackTrace();
} catch (IOException e)
{
e.printStackTrace();
} finally
{
if (is != null)
{
try
{
is.close();
} catch (IOException e)
{
e.printStackTrace();
}
}
}
return result;
}
@Override
protected void onPostExecute(String result)
{
super.onPostExecute(result);
paraJson(result);
}
}
// 解析文件数据
public void paraJson(String result)
{
// 解析JSONObject数据 得到JSONObject对象
try
{
JSONObject object = new JSONObject(result);
// 解析JSONObject数据 得到JSONObject对象
JSONArray jsonArray = object.getJSONArray("allcity");
for (int i = 0; i < jsonArray.length(); i++)
{
JSONObject jsonObject = jsonArray.getJSONObject(i);
// 通过key获得key值
String name = jsonObject.getString("name");
String pinyin = jsonObject.getString("pinyin");
cityList.add(new CityData(name, pinyin));
letter = pinyin.substring(0, 1).toUpperCase();
if (!list.contains(letter))
{
list.add(letter);
}
}
JSONArray jsonArray1 = object.getJSONArray("hotcity");
for (int i = 0; i < jsonArray1.length(); i++)
{
JSONObject jsonObject1 = jsonArray1.getJSONObject(i);
// 通过key获得值
String name = jsonObject1.getString("name");
hotCityList.add(new CityData(name, null));
}
myView1.setLetters(list);
HotCityAdater.notifyDataSetChanged();
myAdater.notifyDataSetChanged();
gridAdater.notifyDataSetChanged();
} catch (JSONException e)
{
e.printStackTrace();
}
}
class ViewHolder
{
TextView tv_cityname;
TextView tv_first;
public RelativeLayout relativeLayout1;
}
//主listview适配器
class MyAllCityAdater extends BaseAdapter
{
@Override
public int getCount()
{
return cityList.size();
}
@Override
public Object getItem(int position)
{
return null;
}
@Override
public long getItemId(int position)
{
return 0;
}
@Override
public View getView(int position, View convertView, ViewGroup parent)
{
View layout = null;
ViewHolder holder = null;
if (convertView == null)
{
holder = new ViewHolder();
layout = getLayoutInflater().inflate(R.layout.city_list_item, null);
holder.tv_cityname = (TextView) layout.findViewById(R.id.tv_cityname);
holder.tv_first = (TextView) layout.findViewById(R.id.tv_first_zimu);
holder.relativeLayout1 = (RelativeLayout) layout.findViewById(R.id.relativeLayout1);
layout.setTag(holder);
} else
{
layout = convertView;
holder = (ViewHolder) layout.getTag();
}
CityData cityData = cityList.get(position);
// 得到前一行首字母
String first = cityData.getPinyin().substring(0, 1).toUpperCase();
if (position == 0)
{
holder.tv_first.setText(first);
holder.tv_first.setVisibility(View.VISIBLE);
holder.relativeLayout1.setVisibility(View.VISIBLE);
} else
{
// 得到前一行首字母
String prefirst = cityList.get(position - 1).getPinyin().substring(0, 1).toUpperCase();
if (prefirst.equals(first))
{
holder.tv_first.setVisibility(View.GONE);
holder.relativeLayout1.setVisibility(View.GONE);
} else
{
holder.tv_first.setText(first);
holder.tv_first.setVisibility(View.VISIBLE);
holder.relativeLayout1.setVisibility(View.VISIBLE);
}
}
holder.tv_cityname.setText(cityData.getName());
return layout;
}
}
//搜索的listview适配器
class MySearchAdater extends BaseAdapter
{
@Override
public int getCount()
{
return searchList.size();
}
@Override
public Object getItem(int position)
{
return null;
}
@Override
public long getItemId(int position)
{
return 0;
}
@Override
public View getView(int position, View convertView, ViewGroup parent)
{
View layout = null;
ViewHolder holder = null;
if (convertView == null)
{
holder = new ViewHolder();
layout = getLayoutInflater().inflate(R.layout.city_list_item, null);
holder.tv_cityname = (TextView) layout.findViewById(R.id.tv_cityname);
holder.tv_first = (TextView) layout.findViewById(R.id.tv_first_zimu);
holder.relativeLayout1 = (RelativeLayout) layout.findViewById(R.id.relativeLayout1);
layout.setTag(holder);
} else
{
layout = convertView;
holder = (ViewHolder) layout.getTag();
}
holder.tv_first.setVisibility(View.GONE);
holder.relativeLayout1.setVisibility(View.GONE);
CityData searchData = searchList.get(position);
holder.tv_cityname.setText(searchData.getName());
return layout;
}
}
class GridAdater extends BaseAdapter
{
@Override
public int getCount()
{
return hotCityList.size();
}
@Override
public Object getItem(int arg0)
{
return null;
}
@Override
public long getItemId(int arg0)
{
return 0;
}
@Override
public View getView(int position, View convertView, ViewGroup parent)
{
View view = getLayoutInflater().inflate(R.layout.city_recent_gridview_item, null);
TextView tv_recent_item = (TextView) view.findViewById(R.id.tv_recent_item);
CityData hotcityData = hotCityList.get(position);
tv_recent_item.setText(hotcityData.getName());
return view;
}
}
public class NoScrollGridView extends GridView
{
public NoScrollGridView(Context context)
{
super(context);
}
public NoScrollGridView(Context context, AttributeSet attrs)
{
super(context, attrs);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
int expandSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST);
super.onMeasure(widthMeasureSpec, expandSpec);
}
}
//TODO 热门城市listview
class HotCityAdater extends BaseAdapter
{
@Override
public int getCount()
{
return hotCityList.size();
}
@Override
public Object getItem(int arg0)
{
return null;
}
@Override
public long getItemId(int arg0)
{
return 0;
}
@Override
public View getView(int position, View convertView, ViewGroup parent)
{
View view = getLayoutInflater().inflate(R.layout.city_hotcity_list_item, null);
TextView tv_hotcity = (TextView) view.findViewById(R.id.tv_hotcity);
CityData hotcityData = hotCityList.get(position);
tv_hotcity.setText(hotcityData.getName());
return view;
}
}
@Override
public void onClick(View v)
{
switch (v.getId())
{
case R.id.city_close:
finish();
break;
default:
break;
}
}
}
此处只是用最原始的异步任务解析Json数据,大家也可以换成http、okhttp等常用开源框架解析
欢迎指正和优化~