闲话
之前没做过联动菜单,最近项目中要用到几个,百度网上有不少现成的控件实现,有用到spinner的,还有用到其他控件的,不过风格不是我需要的,索性自己来实现一下,虽然不如其他大神的方法简便,但自己写的,还是更喜欢。
正文
制作数据源
首先要有级联用的数据源,我这里有一份,我忘记是从哪位大神资源上下载的了,感谢,自己制作一份json数据也行:
省市区json下载
可以把这份json文档放在项目目录的assets下,以便调取。
布局文件
因为用到listview作为展示级联的控件,因此,主布局很简单,只用建立3个listview。
<?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"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="horizontal"
android:background="@color/colorBackground">
<ListView
android:layout_marginLeft="10dp"
android:layout_marginRight="5dp"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="match_parent"
android:divider="@color/colorBackground"
android:dividerHeight="2dp"
android:id="@+id/lvProvince" />
<ListView
android:layout_marginLeft="5dp"
android:layout_marginRight="5dp"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="match_parent"
android:divider="@color/colorBackground"
android:dividerHeight="2dp"
android:id="@+id/lvCity"/>
<ListView
android:layout_marginLeft="5dp"
android:layout_marginRight="10dp"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="match_parent"
android:divider="@color/colorBackground"
android:dividerHeight="2dp"
android:id="@+id/lvArea"/>
</LinearLayout>
然后是item子布局,写demo懒一些,每个listview公用一个item布局。
我写布局的时候,不知道为什么,如果把textview充满父布局,然后让文字居中显示android:gravity=”center”,这样写感觉没什么问题,但实际测试时发现,第一次展示数据的时候,不会有什么问题,但是点击几次之后,有些item就不会在执行居中操作了,很奇怪,我也不知道为什么,所以才修改位textview自适应,布局居中android:layout_centerInParent=”true”
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/colorWhite">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:textColor="@color/colorWord"
android:textSize="16sp"
android:text="省市区"
android:gravity="center"
android:layout_marginTop="5dp"
android:layout_marginBottom="5dp"
android:id="@+id/tvPCA"/>
</RelativeLayout>
Adapter
adapter也很简单,继承BaseAdapter。怎样给选中的item修改颜色,以便提醒用户,我的处理方法是在adapter中添加一个方法setSelectPosition(int position),传入点击的item,在getView中,判断当前item是否是点击的item,如果是,就设置选中的颜色。稍微有点麻烦,不过效果还可以。
public void setSelectPosition(int position){
this.selectPosition=position;
}
整个adapter代码
public class AdapterForPCA extends BaseAdapter {
private List<String> list=null;
private Context context;
private int selectPosition;
public AdapterForPCA(List<String> list, Context context){
this.list=list;
this.context=context;
}
public void setSelectPosition(int position){
this.selectPosition=position;
}
@Override
public int getCount() {
if (list!=null){
return list.size();
}
return 0;
}
@Override
public Object getItem(int i) {
return null;
}
@Override
public long getItemId(int i) {
return 0;
}
@Override
public View getView(int i, View view, ViewGroup viewGroup) {
ViewHolder viewHolder=null;
if (view==null){
view= LayoutInflater.from(context).inflate(R.layout.item_listview_province_city_area,null);
viewHolder=new ViewHolder();
viewHolder.tvPCA=Utils.findView(view,R.id.tvPCA);
view.setTag(viewHolder);
}else {
viewHolder=(ViewHolder)view.getTag();
}
viewHolder.tvPCA.setText(list.get(i));
viewHolder.tvPCA.setGravity(Gravity.CENTER);
//为了让点击的item显示不同的颜色,必须在item点击后 重新 notifyDataSetChange
if (selectPosition==i){
viewHolder.tvPCA.setBackgroundColor(context.getResources().getColor(R.color.colorButton));
view.setBackgroundColor(context.getResources().getColor(R.color.colorButton));
viewHolder.tvPCA.setTextColor(context.getResources().getColor(R.color.colorWhite));
}else {
viewHolder.tvPCA.setBackgroundColor(context.getResources().getColor(R.color.colorWhite));
view.setBackgroundColor(context.getResources().getColor(R.color.colorWhite));
viewHolder.tvPCA.setTextColor(context.getResources().getColor(R.color.colorWord));
}
return view;
}
class ViewHolder{
TextView tvPCA;
}
}
Activity应用
先从assets中读取数据源
/**
* 从文本中读取 省市区城市列表
* @return
*/
private String readPCA(){
InputStream in=null;
ByteArrayOutputStream out=null;
try {
in=getAssets().open("PCA.json");
out=new ByteArrayOutputStream();
byte[] b=new byte[1024];
int length=-1;
while ((length=in.read(b))!=-1){
out.write(b,0,length);
}
return new String(out.toByteArray());
} catch (IOException e) {
e.printStackTrace();
return null;
} finally {
if (in!=null){
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
读取之后先存在保存为JSON数组
try {
jsonArrayP=new JSONArray(readPCA());
} catch (JSONException e) {
e.printStackTrace();
}
然后分别获取省市区的列表
/**
* 获取省列表
*/
private void initListP(){
try {
listP.clear();
if (jsonArrayP!=null) {
for (int i = 0; i < jsonArrayP.length(); i++) {
JSONObject objP = jsonArrayP.getJSONObject(i);//获取省份对象
listP.add(objP.getString("name"));//获取省份名字
}
}
} catch (JSONException e) {
e.printStackTrace();
}
}
/**
* 根据点击的省份,获取该省的城市列表
* @param provinceNum
*/
private void setListC(int provinceNum){
listC.clear();
try {
jsonObjP=jsonArrayP.getJSONObject(provinceNum);//获取点击的省份对象
jsonArrayC=jsonObjP.getJSONArray("city");//获取该省份的城市数组
for (int i=0;i<jsonArrayC.length();i++){
JSONObject objC=jsonArrayC.getJSONObject(i);//获取城市数组的城市对象
listC.add(objC.getString("name"));//获取城市名字
}
} catch (JSONException e) {
e.printStackTrace();
}
}
/**
* 根据点击的城市获得地区列表
* @param cityNum
*/
private void setListA(int cityNum){
listA.clear();
try {
jsonObjC=jsonArrayC.getJSONObject(cityNum);//根据点击的城市对象
JSONArray arrayA=jsonObjC.getJSONArray("area");//获取该城市的地区列表
for (int i=0;i<arrayA.length();i++){
listA.add(arrayA.getString(i));//添加地区列表到list
}
} catch (JSONException e) {
e.printStackTrace();
}
}
最后做初始化UI 和 listview的点击事件,注释比较详细,就不一一说明了
private void initUI(){
try {
jsonArrayP=new JSONArray(readPCA());
} catch (JSONException e) {
e.printStackTrace();
}
listP=new ArrayList<>();
initListP();
listC=new ArrayList<>();
pro=0;
setListC(pro);//设置默认展示的省份,根据json数组的系列号
listA=new ArrayList<>();
city=0;
setListA(city);//设置默认展示哪个城市的地区
lvProvince=Utils.findView(this,R.id.lvProvince);
adapterP=new AdapterForPCA(listP,this);
adapterP.setSelectPosition(pro);//设置默认选中的省份变颜色
lvProvince.setAdapter(adapterP);
lvProvince.setOnItemClickListener(this);
lvCity=Utils.findView(this,R.id.lvCity);
adapterC=new AdapterForPCA(listC,this);
adapterC.setSelectPosition(city);//设置默认选中的城市变颜色
lvCity.setAdapter(adapterC);
lvCity.setOnItemClickListener(this);
lvArea=Utils.findView(this,R.id.lvArea);
adapterA=new AdapterForPCA(listA,this);
adapterA.setSelectPosition(area);//设置默认选中的地区变颜色
lvArea.setAdapter(adapterA);
lvArea.setOnItemClickListener(this);
}
/**
* 每个list 点击事件
* @param adapterView
* @param view
* @param i
* @param l
*/
@Override
public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
switch (adapterView.getId()){
case R.id.lvProvince://点击省列表
pro=i;//获得选择的省的item
adapterP.setSelectPosition(pro);//为了让选择的item显示不同的颜色
//每一次选择省,都需要把 之前选择的市和区的item初始化为0,默认选择第一个
city=0;
area=0;
adapterC.setSelectPosition(city);//为了让选择的item显示不同的颜色
adapterA.setSelectPosition(area);//为了让选择的item显示不同的颜色
//获得点击的省份对应的 城市列表
setListC(pro);
//点击省份,显示城市,由于还没有点击城市,所以默认选择第一个城市,展示第一个城市的区列表
setListA(city);
//点击省份,需要三个listview都刷新data ,以便执行 setSelectPosition ,刷新选择的项目的颜色
adapterP.notifyDataSetChanged();
adapterC.notifyDataSetChanged();
adapterA.notifyDataSetChanged();
break;
case R.id.lvCity:
city=i;//点击的城市的item
area=0;//由于还没有选择区,所以区默认选择第一个
adapterC.setSelectPosition(city);//为了让选择的item显示不同的颜色
adapterA.setSelectPosition(area);//为了让选择的item显示不同的颜色
setListA(i);//获取选择的城市的区列表
//点击城市,需要刷新 城市 和 区 listview, 以便执行 setSelectPosition ,刷新选择的项目的颜色
adapterC.notifyDataSetChanged();
adapterA.notifyDataSetChanged();
break;
case R.id.lvArea:
area=i;//点击的区
adapterA.setSelectPosition(area);为了让选择的item显示不同的颜色
//点击城市,需要刷新 区 listview, 以便执行 setSelectPosition ,刷新选择的项目的颜色
adapterA.notifyDataSetChanged();
Toast.makeText(MainActivity.this, listP.get(pro)+""+listC.get(city)+""+listA.get(area), Toast.LENGTH_SHORT).show();
break;
}
}
好了,这样,一个简单的级联菜单就做好了,看看效果