android中利用实现二级联动的效果

按照惯例,首先上一张效果图。

android中利用实现二级联动的效果

本篇文章实现的效果就是如图中所圈的那样,实现类似于HTML中的二级联动的效果。

对于第一个选项我们读取的是本地xml文件来填充数据的,

对于第二个选项我们读取的是通过*气象台提供的API返回的xml格式的数据来填充的。

首先是主页面排版,由于我做的是一个天气预报的功能,所以添加了很多与本文无关的控件,在代码注释中写的很清楚,大家可以直接略过。

public class WeatherPage extends RelativeLayout{
	private Context parentContext;
	
	/**监听*/
	private MyButtonListen mylisten;
	
	/**定义天气对象*/
	private GetWeatherService service;
	
	private RelativeLayout toplayout;
	private int toplayoutid=100;
	private RelativeLayout centerlayout;
	private int centerlayoutid=200;
	private RelativeLayout footlayout;
	private int footlayoutid=300;
	
	/** topView*/
	private ImageView dogpetimg;
	private TextView titleview;
	private ImageButton switchBtn;
	
	/** showweatherView*/
	private RelativeLayout showWeather;
	private int showWeatherid=201;
	private TextView datetext;
	
	/** functionView*/
	private LinearLayout functionLayout;
	private TextView selectCitytext;
	private Spinner selectProvince;
	private ArrayAdapter<CharSequence> adapterProvince;
	private Map<String,String> provincemap;
	
	private Spinner selectCity;
	private Map<Integer,CityWeather> citymap;
	private ArrayAdapter<CharSequence> adapterCity;
	
	private ImageButton okBtn;
	private ImageButton selectBtn;
	
	/** 本类对象的样式 */
	private LayoutParams lp;//准备放入ViewFlipper中,所以暂且准备一下,
	
	public WeatherPage(Context context) {
		super(context);
		this.parentContext=context;
		mylisten=new MyButtonListen();
		service=new GetWeatherService();
		init();
	}
	private void init() {
		toplayout=new RelativeLayout(parentContext);
		toplayout.setId(toplayoutid);
		LayoutParams lptoplayout=new LayoutParams(-1,-2);
		lptoplayout.setMargins(0, 20, 0, 0);
		//添加组件
		addTopLayout(lptoplayout);
		addView(toplayout,lptoplayout);
		
		
		
		centerlayout=new RelativeLayout(parentContext);
		centerlayout.setId(centerlayoutid);
		LayoutParams lpcenterlayout=new LayoutParams(-1,370);
		lpcenterlayout.setMargins(0, 30, 0, 0);
		lpcenterlayout.addRule(RelativeLayout.BELOW,toplayoutid);
		//添加组件
		addCenterLayout(lpcenterlayout);
		addView(centerlayout,lpcenterlayout);
		
		
		footlayout=new RelativeLayout(parentContext);
		footlayout.setBackgroundColor(Color.RED);
		footlayout.setId(footlayoutid);
		LayoutParams lpfootlayout=new LayoutParams(-1,-2);
		lpfootlayout.setMargins(20, 10, 20, 0);
		//添加组件
		addFootLayout(lpfootlayout);
		lpfootlayout.addRule(RelativeLayout.BELOW,centerlayoutid);
		addView(footlayout,lpfootlayout);
		
	}
	
	public LayoutParams getLp() {
		this.lp=new LayoutParams(-1,-1);
		return lp;
	}
	
	public void addTopLayout(LayoutParams lp){
		dogpetimg=new ImageView(parentContext);
		LayoutParams lpdogpetimg=new LayoutParams(60,60);
		lpdogpetimg.addRule(RelativeLayout.ALIGN_PARENT_LEFT);
		dogpetimg.setBackgroundResource(R.drawable.dogsmall);
		lpdogpetimg.setMargins(10, 0, 0, 0);
		
		titleview=new TextView(parentContext);
		titleview.setText("天气预报");
		titleview.setTextColor(Color.BLUE);
		LayoutParams lptitleview=new LayoutParams(-2,-2);
		lptitleview.addRule(RelativeLayout.CENTER_HORIZONTAL);
		
		switchBtn=new ImageButton(parentContext);
		//先进行判断,判断原来的开关状态,然后再添加背景图片,标记位设置在helper类中。
		switchBtn.setBackgroundResource(R.drawable.start);
		LayoutParams lpswitchBtn=new LayoutParams(40,40);
		lpswitchBtn.addRule(RelativeLayout.ALIGN_PARENT_RIGHT);
		lpswitchBtn.setMargins(0, 20, 50, 0);
		//添加监听
		switchBtn.setOnClickListener(mylisten);

		toplayout.addView(dogpetimg,lpdogpetimg);
		toplayout.addView(titleview, lptitleview);
		toplayout.addView(switchBtn,lpswitchBtn);
		
	}

	public void addCenterLayout(LayoutParams lp) {
		showWeather=new RelativeLayout(parentContext);
		showWeather.setId(showWeatherid);
		LayoutParams lpshowWeather=new LayoutParams(400,300);
		lpshowWeather.addRule(RelativeLayout.CENTER_HORIZONTAL);
		showWeather.setBackgroundColor(Color.CYAN);
		
		datetext=new TextView(parentContext);
		LayoutParams lpdatetext=new LayoutParams(400,-2);
		lpdatetext.addRule(RelativeLayout.CENTER_HORIZONTAL);
		lpdatetext.addRule(RelativeLayout.BELOW,showWeatherid);
		lpdatetext.setMargins(20, 20, 20, 0);
//		datetext.setBackgroundColor(Color.LTGRAY);
		datetext.setText(TimeHelper.getDateInChina());
		datetext.setGravity(Gravity.CENTER_HORIZONTAL);
		
		centerlayout.addView(showWeather, lpshowWeather);
		centerlayout.addView(datetext, lpdatetext);
	}

	public void addFootLayout(LayoutParams lp) {
		
		functionLayout=new LinearLayout(parentContext);
		functionLayout.setId(301);
//		functionLayout.setBackgroundColor(Color.YELLOW);
		LayoutParams lpfunctionLayout=new LayoutParams(-2,-2);
		lpfunctionLayout.addRule(RelativeLayout.ALIGN_PARENT_TOP);
		lpfunctionLayout.addRule(RelativeLayout.CENTER_HORIZONTAL);
		lpfunctionLayout.setMargins(10, 0, 0, 0);
		
		//添加显示文字
		selectCitytext=new TextView(parentContext);
		selectCitytext.setText("请设置:");
		
		//添加选择省
		selectProvince=new Spinner(parentContext);
		selectProvince.setPrompt("请选择省份");
		//获取省份Map<序列号,省份对象>
		provincemap=service.getProvinceMap();
		String[] provinceData=service.getProvinceArray(provincemap);
		//定义下拉列表适配器,用于填充内容
		adapterProvince=null;
		adapterProvince=new ArrayAdapter<CharSequence>(parentContext,android.R.layout.simple_spinner_item,provinceData);
		//设置列表显示风格
		adapterProvince.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
		selectProvince.setAdapter(adapterProvince);
		selectProvince.setOnItemSelectedListener(new MyOnItemSelectedListen());
		
		//添加选择市
		selectCity=new Spinner(parentContext);
		selectCity.setPrompt("请选择城市");
		//定义下拉列表适配器,用于填充内容
		adapterCity=null;
		//设置列表显示风格
		selectCity.setAdapter(adapterCity);
		
		functionLayout.addView(selectCitytext);
		functionLayout.addView(selectProvince);
		functionLayout.addView(selectCity);
		
		okBtn=new ImageButton(parentContext);
		okBtn.setBackgroundResource(R.drawable.okbtn);//给绑定按钮添加背景图片
		LayoutParams lpokBtn=new LayoutParams(-2,-2);
		lpokBtn.setMargins(20, 20, 0, 0);
		lpokBtn.addRule(RelativeLayout.ALIGN_PARENT_LEFT);
		lpokBtn.addRule(RelativeLayout.BELOW,301);
		//添加监听
		okBtn.setOnClickListener(mylisten);
		
		selectBtn=new ImageButton(parentContext);
		selectBtn.setBackgroundResource(R.drawable.selectbn);//给查询按钮添加背景图片
		LayoutParams lpselectBtn=new LayoutParams(-2,-2);
		lpselectBtn.setMargins(0, 20, 20, 0);
		lpselectBtn.addRule(RelativeLayout.ALIGN_PARENT_RIGHT);
		lpselectBtn.addRule(RelativeLayout.BELOW,301);
		//添加监听
		selectBtn.setOnClickListener(mylisten);
		
		footlayout.addView(functionLayout,lpfunctionLayout);
		footlayout.addView(okBtn, lpokBtn);
		footlayout.addView(selectBtn, lpselectBtn);
	}

	/**
	 * 监听各种按钮的点击事件
	 * @author Administrator
	 */
	class MyButtonListen implements OnClickListener{
		@Override
		public void onClick(View v) {
			// TODO Auto-generated method stub
			if(v==switchBtn){
				//关闭外显示功能
				System.out.println("点击外显示开关");
				//换色
				switchBtn.setBackgroundResource(R.drawable.end);
			}else if(v==okBtn){
				//确定,对城市进行保存。写入小型数据库
				System.out.println("点击保存开关");
				
			}else if(v==selectBtn){
				//确定,对城市进行查询,不保存
				System.out.println("点击查询开关");
			}else{
				
				Log.e("tag", "问题,输入的值不对");
			}
		}
	}
	/**
	 * 监听第一个选项的选择,当地一个选项框被选择数据的时候出发该类中的事件
	 * @author Administrator
	 */
	class MyOnItemSelectedListen implements OnItemSelectedListener{

		@Override
		public void onItemSelected(AdapterView<?> item, View view,
				int position, long arg3) {
			
			String provincename = item.getAdapter().getItem(position).toString();
			System.out.println(provincename);
			String pinyin = provincemap.get(provincename);
			Map<String, String> cityIdMap = service.getCityMap(pinyin);
			String[] cityArray = service.getCityArray(cityIdMap);
			
			adapterCity=new ArrayAdapter<CharSequence>(parentContext,android.R.layout.simple_spinner_item,cityArray);
			//设置列表显示风格
			adapterCity.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
			selectCity.setAdapter(adapterCity);
		}

		@Override
		public void onNothingSelected(AdapterView<?> arg0) {
			// TODO Auto-generated method stub
			
		}
	}
}


完成了这些代码,已经基本可以实现骨架的二级联动了。

我这里第一级的连动使用的是读取本地xml文件,

第二级的联动使用的读取API,所以专门建立了一个service类来处理这些事件:

public class GetWeatherService {
	
	private GetWeatherDao dao;
	
	public GetWeatherService() {
		dao=new GetWeatherDao();
	}
	
	/**
	 * 获取城市气象信息的流程
	 * @param province
	 * @param selectcity
	 * @return
	 */
	public CityWeather getWeatherDay(String province,String selectcity){
		//首先,获取所有省份名和拼音的map
		Map<String, String> provincemap = getProvinceMap();
		//根据所有的省份名 获取 所需要的省份名拼音
		String provincexml = provincemap.get(province);
		if(provincexml==null){
			//有问题,省份不存在
			return null;
		}
		//其次,输入值省份名拼音,获取指定市的城市气象id
		Map<String, String> citymap = getCityMap(provincexml);
		
		if(citymap==null){
			//有问题,城市不存在
			return null;
		}
		
		String cityid = citymap.get(selectcity);
		
		//根据id获取城市的json数据,并且解析该数据返回城市未来七天气象信息
		CityWeather cityWeather = getCityWeather(cityid);
		if(cityWeather==null){
			//有问题,解析不正确
			return null;
		}
		return cityWeather;
	}
	
	/**
	 * 获取所有省份名和拼音的map
	 */
	public Map<String, String> getProvinceMap() {
		Map<String, String> proMap = new HashMap<String, String>();
		String res = "";
		try {
			InputStream in = Helper.getHelper().getContext().getResources()
					.getAssets().open("province.xml");
			InputStream is = IOHelper.fromInputStreamToInputStreamInCharset(in,"utf-8");
//			int length = in.available();
//			byte[] buffer = new byte[length];
//			in.read(buffer);
//			res = EncodingUtils.getString(buffer, "UTF-8");
//			InputStream is = IOHelper.fromStringToIputStream(res);
			SAXReader sr = new SAXReader();// 获取读取xml的对象。
			Document document = sr.read(is);
			Element root = document.getRootElement();
			List<?> elementlist = root.elements("city");
			for (Object obj : elementlist) {
				Element row = (Element) obj;
				String quName = row.attribute("quName").getText();
				String pyName = row.attribute("pyName").getText();
				proMap.put(quName, pyName);
			}
		} catch (Exception e) {
			Log.e("tag", "读取配置失败");
			e.printStackTrace();
		}
		return proMap;
	}
	
	/**
	 * 获取指定省份的城市列表
	 */
	public Map<String, String> getCityMap(String provincename) {
		InputStream cityXml = dao.getCityXml(provincename);
		
		Map<String,String> citymap=new HashMap<String,String>();
		try {
			SAXReader sr = new SAXReader();// 获取读取xml的对象。
			Document document = sr.read(cityXml);
			Element root = document.getRootElement();
			List<?> elementlist = root.elements("city");

			for (Object obj : elementlist) {
				Element row = (Element) obj;
				String cityname = row.attribute("cityname").getText();
				String cityid =row.attribute("url").getText();
				citymap.put(cityname,cityid);
			}
		} catch (Exception e) {
			e.printStackTrace();
			Log.e("tag", "问题"+e.toString());
			return null;
		}
		return citymap;
	}
	
	/**
	 * 根据指定的城市气象id获取该城市未来七天的天气信息
	 */
	public CityWeather getCityWeather(String cityid) {
		 CityWeather cityWeather=null;
		String weatherJson = dao.getWeatherJson(cityid);
		weatherJson="["+weatherJson+"]";
		try {
		// 对json数组进行循环,一般应该只返回一个。
		JSONTokener jsonParser = new JSONTokener(weatherJson);
		JSONObject object = (JSONObject) jsonParser.nextValue();
		// 接下来的就是JSON对象的操作了
		JSONObject weatherday = object.getJSONObject("weatherinfo");

		cityWeather = new CityWeather();
		String city = weatherday.get("city").toString();
		String city_en = weatherday.get("city_en").toString();
		String date_y = weatherday.get("date_y").toString();
		String week = weatherday.get("week").toString();

		String temp1 = weatherday.get("temp1").toString();// 今天温度
		String temp2 = weatherday.get("temp2").toString();// 明天温度
		String temp3 = weatherday.get("temp3").toString();// 后天温度

		String weather1 = weatherday.get("weather1").toString();// 今天温度
		String weather2 = weatherday.get("weather2").toString();// 明天温度
		String weather3 = weatherday.get("weather3").toString();// 后天温度

		cityWeather.setCityname(city);
		cityWeather.setCity_en(city_en);
		cityWeather.setCityid(cityid);
		cityWeather.setDate_y(date_y);
		cityWeather.setWeek(week);
		cityWeather.setTempToday(temp1);
		cityWeather.setTempTommorrow(temp2);
		cityWeather.setTempAt(temp3);
		cityWeather.setWeatherToday(weather1);
		cityWeather.setWeatherTommorrow(weather2);
		cityWeather.setWeatherAt(weather3);
		} catch (Exception e) {
			// TODO: handle exception
			return null;
		}
		return cityWeather;
	}

	/**
	 * 返回省名称的数组
	 * @param map
	 * @return
	 */
	public String[] getProvinceArray(Map<String, String> map){
		String[] provinceArray=new String[map.size()];
		int i=0;
		for(String key:map.keySet()){
			provinceArray[i++]=key;
		}
		return provinceArray;
	}
	
	public String[] getCityArray(Map<String, String> map){
		String[] cityArray=new String[map.size()];
		int k=0;
		for(String key:map.keySet()){
			cityArray[k++]=key;
		}
		return cityArray;
	}
}

程序中经常涉及到io流的处理,所以为了方法处理,专门写了一个工具类,为了便于大家调试,也附上代码:

public class IOHelper {
	/**
	 * 输入InputStream流,返回字符串文字。
	 * @param is
	 * @return
	 */
	public static String fromIputStreamToString(InputStream is){
		ByteArrayOutputStream baos = new ByteArrayOutputStream();
		int i = -1;
		try {
			while ((i = is.read()) != -1) {
				baos.write(i);
			}
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return baos.toString();
	}
	
	/**
	 * 输入InputStream流和文件地址,返回成功与否。
	 * @param is
	 * @return
	 */
	public static boolean fromIputStreamToFile(InputStream is,String outfilepath){
		byte[] b=new byte[1024];
		FileOutputStream fos=null;
		try {
			fos=new FileOutputStream(new File(outfilepath));
			while((is.read(b, 0, 1024))!=-1){
				fos.write(b);
			}
			fos.flush();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			return false;
		}finally{
			try {
				fos.close();
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		return true;
	}
	/**
	 * 输入文件地址,返回inputStream流。
	 * @param is
	 * @return
	 */
	public static InputStream fromFileToIputStream(String infilepath){
		FileInputStream fis=null;
		try {
			fis=new FileInputStream(new File(infilepath));
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return fis;
	}
	
	public static InputStream fromStringToIputStream(String s) {
	    if (s != null && !s.equals("")) {
	        try {

	            ByteArrayInputStream stringInputStream = new ByteArrayInputStream(
	                    s.getBytes());
	            return stringInputStream;
	        } catch (Exception e) {

	            e.printStackTrace();
	        }
	    }
	    return null;
	}
	//把输入流转换为存有UTF-8格式数据的输入流
	public static InputStream fromInputStreamToInputStreamInCharset(
			InputStream in, String charset) throws Exception {
		StringBuilder builder=new StringBuilder();
		byte[] buffer = new byte[2048];
		int len = -1;
		while ((len = in.read(buffer)) != -1) {
			builder.append(EncodingUtils.getString(buffer, 0, len, "UTF-8"));
		}
		return IOHelper.fromStringToIputStream(builder.toString());
	}
	
}

本类中的主要实现效果是二级联动,所以API调用的DAO层代码我就不上传了。。

疑问:大家可能也注意到了,我的IOHelper类中,专门写了一个方法fromInputStreamToInputStreamInCharset来处理输入流,那是因为我发现我直接通过SAXReader.read(io)读取的时候,出现了乱码的现象,我知道是编码格式的问题,本来想直接让SAXReader以UTF-8格式来读取,可以搜了一圈,没找到合适的方法就专门写了一个这样的方法。希望更好处理方式的朋友麻烦告之下。

另外我现在实现的方式两个选项栏里面的数据都是无序的,大家有兴趣的可以写一个比较器来实现排序的效果。

最后附上最终实现的效果图:

android中利用实现二级联动的效果