二级联动城市选择

很多地方用到多级城市选择,有人偏爱苹果的那种滚动的效果。查了下,android有类似的实现,但是实现起来比较麻烦。综合考虑下,还是使用android现有的组件来实现。

多级联动,一级操作触发其他的改变,很自然的想到用多个listview,多级和二级区别不大,因此暂时实现一个二级的城市选择,即只选择省和市,区县类似。实现的效果如下:



1、数据来源

这里选择网上得来的城市数据:中国天气网的城市数据。具体怎么完美获取最新的,今天尝试了下还是有点麻烦,之后的文章中会补上。当然最简单的方法就是下载别人弄好的。

2、数据库操作

我把数据库直接放到了assets文件夹下面,应用第一次启动的时候会拷贝城市数据库到应用的数据库目录下,然后就可以使用了。然后再封装一个数据库操作类,对其进行简单的操作,代码如下:

public class PubDBM {
	public static final String TABLE_CHINA_CITY_CODE = "china_city_code";
	public static final String CCC_PROVINCE = "province"; //
	public static final String CCC_CITY = "city"; //
	public static final String CCC_COUNTY = "county"; //
	public static final String CCC_CODE = "code"; //

	private static PubDBM dbm;

	private static final String DBNAME = "china_city_code.db";
	private String dbPath;// 数据库路径,不包括数据库名字

	private SQLiteDatabase db = null;
	private static Context mContext;

	public static PubDBM getInstance(Context context) {
		mContext = context;

		if (dbm == null) {
			dbm = new PubDBM();
		}
		return dbm;
	}

	private PubDBM() {
		// TODO context.getFilesDir().getPath()
		dbPath = "/data/data/" + mContext.getPackageName() + "/databases/";

		initPublicDataBase();
	}

	/**
	 * 初始化数据库
	 */
	private void initPublicDataBase() {

		File dbDir = new File(dbPath);
		if (!dbDir.exists()) {
			dbDir.mkdirs();
		}

		File dbFile = new File(dbPath + DBNAME);
		if (!dbFile.exists()) {
			try {
				dbFile.createNewFile();

				InputStream is = mContext.getResources().getAssets().open(DBNAME);
				OutputStream os = new FileOutputStream(dbPath + DBNAME);
				byte[] buffer = new byte[1024];
				int length = 0;
				while ((length = is.read(buffer)) > 0) {
					os.write(buffer, 0, length);
				}
				os.flush();
				os.close();
				is.close();
			} catch (IOException e) {
				e.printStackTrace();
				return;
			}
		}
		if (db == null) {
			db = SQLiteDatabase.openDatabase(dbPath + DBNAME, null, SQLiteDatabase.OPEN_READONLY);
		}
	}

	/**
	 * 关闭数据库
	 */
	public void close() {
		if (db != null && db.isOpen()) {
			db.close();
		}
	}

	/**
	 * 查询所有数据
	 * 
	 * @return
	 */
	public Cursor queryAllData() {
		if (db == null) {
			System.out.println("db==null");
			return null;
		}
		return db.rawQuery("select * from " + TABLE_CHINA_CITY_CODE + " order by " + CCC_CODE + " asc", null);
	}

	/**
	 * 查询省
	 * 
	 * @return
	 */
	public Cursor queryProvinceList() {
		String sql = "select distinct substr(" + CCC_CODE + ",1,5) as _id," + CCC_PROVINCE;
		sql += " from " + TABLE_CHINA_CITY_CODE;
		System.out.println(sql);

		if (db == null) {
			return null;
		}
		return db.rawQuery(sql, null);
	}

	/**
	 * 查询城市
	 * 能力有限,数据库查询不熟悉,结果并不是完全想要的,code对应不上
	 */
	public Cursor queryCityList(String province) {
		String sql = "select * from " + TABLE_CHINA_CITY_CODE;
		sql += " where " + CCC_PROVINCE + " = '" + province + "' group by " + CCC_CITY;
		
		if (db == null) {
			return null;
		}
		return db.rawQuery(sql, null);
	}

	/**
	 * 根据城市代码查询
	 * 
	 */
	public Cursor queryProvinceAndCity(String cityCode) {
		String sql = "select " + CCC_PROVINCE + ", " + CCC_CITY;
		sql += " from " + TABLE_CHINA_CITY_CODE;
		sql += " where " + CCC_CODE + " = " + cityCode;
		if (db == null) {
			return null;
		}
		return db.rawQuery(sql, null);
	}
}
3、界面实现:

在见面上展现就是一个EditText和一个Dialog的组合,点击EditText弹出一个Dialog,选择之后Dialog消失,EditText中显示选择的结果,需要使用的地方通过EditText获取选择的值。由于很多地方带有初始数据,因此我们在界面初始化的时候可能需要显示一个之前选择的城市,这个时候我们可以调用自定义的EditText设置一个初始城市代码,这样就可以显示初始数据了。

/**
 * 城市选择<br>
 * 1、布局中添加此控件<br>
 * 2、拿到此控件,设置数据库对象<br>
 * 3、通过getCityCode()拿到选择的城市代码<br>
 * 4、设置默认值是通过setCityCode(String code)方法<br>
 * 
 * @author ttworking
 * 
 */
public class CitySelect extends EditText implements android.view.View.OnClickListener {
	private String province, city, code;

	private CitySelectDialog dialog;
	private PubDBM dbm;

	public CitySelect(Context context, AttributeSet attrs) {
		super(context, attrs);
		this.dialog = new CitySelectDialog(context);

		this.dbm = PubDBM.getInstance(context);
		this.setOnClickListener(this);
		
		setFocusable(false);
		setClickable(true);
	}

	/**
	 * 获取省份加城市
	 * 
	 * @return 省份 城市
	 */
	public String getProvinceCity() {
		return getText().toString();
	}

	/**
	 * 获取选择的城市代码
	 * 
	 * @return 城市代码
	 */
	public String getCityCode() {
		return code;
	}

	/**
	 * 设置城市信息,参数为城市代码,设置之后界面会显示城市名称
	 * 
	 * @param code
	 *            城市代码
	 */
	public void setCityCode(String code) {
		this.code = code;
		if (dbm == null) {
			return;
		}
		Cursor cursor = dbm.queryProvinceAndCity(code);
		if (cursor != null && cursor.moveToFirst()) {
			this.province = cursor.getString(cursor.getColumnIndex(PubDBM.CCC_PROVINCE));
			this.city = cursor.getString(cursor.getColumnIndex(PubDBM.CCC_CITY));
			cursor.close();
		}
		setTextSummary(province, city);
	}

	private void setTextSummary(String province, String city) {
		if (province.equals(city)) {
			setText(city);
		} else {
			setText(province + " " + city);
		}
	}

	@Override
	public void onClick(View v) {
		dialog.show();
	}

	// 内部类
	class CitySelectDialog extends AlertDialog implements OnItemClickListener,OnClickListener {

		private Context context;
		private ListView lvProvince, lvCity;
		private Cursor pCursor, cCursor;
		private Button btClear;
		private SimpleCursorAdapter padapter, cadapter;

		public CitySelectDialog(Context context) {
			super(context);
			this.context = context;
		}

		public CitySelectDialog(Context context, int theme) {
			super(context, theme);
			this.context = context;
		}

		@Override
		protected void onCreate(Bundle savedInstanceState) {
			super.onCreate(savedInstanceState);
			// this.setView(R.layout.dialog_city_select);// 可能会有边框
			this.setContentView(R.layout.dialog_city_select);

			lvProvince = (ListView) findViewById(R.id.lvProvince);
			lvCity = (ListView) findViewById(R.id.lvCity);
			btClear = (Button) findViewById(R.id.btClear);
			btClear.setOnClickListener(this);
			
			if (dbm == null) {
				return;
			}

			pCursor = dbm.queryProvinceList();
			pCursor.moveToFirst();
			if (province == null) {
				province = pCursor.getString(pCursor.getColumnIndex(PubDBM.CCC_PROVINCE));
			}
			padapter = new SimpleCursorAdapter(context, R.layout.listview_item_city, pCursor, new String[] { PubDBM.CCC_PROVINCE },
					new int[] { R.id.tvSummary }, SimpleCursorAdapter.FLAG_REGISTER_CONTENT_OBSERVER);
			lvProvince.setAdapter(padapter);
			lvProvince.setOnItemClickListener(new OnItemClickListener() {
				@Override
				public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
					TextView tvSummary = (TextView) view.findViewById(R.id.tvSummary);
					tvSummary.setBackgroundResource(android.R.color.holo_blue_light);

					province = tvSummary.getText().toString();
					cadapter.changeCursor(dbm.queryCityList(province));
				}
			});
			lvProvince.setOnScrollListener(new OnScrollListener() {

				@Override
				public void onScrollStateChanged(AbsListView view, int scrollState) {
					// System.out.println("onScrollStateChanged"+scrollState);
				}

				@Override
				public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
					// System.out.println("onScroll:" + firstVisibleItem + "|" + visibleItemCount + "|" + totalItemCount);

					for (int i = 0; i < visibleItemCount; i++) {
						TextView tvSummary = (TextView) view.getChildAt(i).findViewById(R.id.tvSummary);
						// System.out.println("summary:" + tvSummary.getText().toString() + "||" + province);
						if (province.equals(tvSummary.getText().toString())) {
							tvSummary.setBackgroundResource(android.R.color.holo_blue_light);
						} else {
							tvSummary.setBackgroundColor(Color.TRANSPARENT);
						}
					}
				}
			});

			cCursor = dbm.queryCityList(province);
			cCursor.moveToFirst();
			code = cCursor.getString(cCursor.getColumnIndex(PubDBM.CCC_CODE));
			cadapter = new SimpleCursorAdapter(context, R.layout.listview_item_city, cCursor, new String[] { PubDBM.CCC_CITY,
					PubDBM.CCC_CODE }, new int[] { R.id.tvSummary, R.id.tvCode },
					SimpleCursorAdapter.FLAG_REGISTER_CONTENT_OBSERVER);
			lvCity.setAdapter(cadapter);
			lvCity.setOnItemClickListener(this);
		}

		@Override
		public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
			TextView tvSummary = (TextView) view.findViewById(R.id.tvSummary);
			TextView tvCode = (TextView) view.findViewById(R.id.tvCode);

			city = tvSummary.getText().toString();
			code = tvCode.getText().toString();

			setTextSummary(province, city);

			cancel();
		}

		@Override
		public void onClick(View v) {
			province = ""; 
			city = "";
			code = "";
			
			setText("");
			
			cancel();
		}
	}
}


代码还是比较简单的,由于时间仓促,二级目录内容的改变直接使用了

cadapter.changeCursor(dbm.queryCityList(province));
这个地方可能有更好的方法,不过城市的选择不会是经常选择的东西,应该问题不大。另外还有几个布局文件,都是一个界面上的东西了:

dialog_dity_select.xml

<?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="360dp"
    android:layout_marginLeft="8dp"
    android:layout_marginRight="8dp"
    android:background="@android:color/holo_blue_dark"
    android:orientation="vertical" >

    <TextView
        android:id="@+id/tvTitle"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerHorizontal="true"
        android:layout_marginTop="6dp"
        android:layout_marginBottom="8dp"
        android:gravity="center"
        android:text="城市选择"
        android:textColor="@android:color/holo_blue_bright"
        android:textSize="24sp" />

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_above="@+id/btClear"
        android:layout_below="@+id/tvTitle"
        android:padding="10dp" >

        <ListView
            android:id="@+id/lvProvince"
            android:layout_width="121dp"
            android:layout_height="246dp"
            android:layout_marginLeft="8dp"
            android:background="@android:color/holo_blue_bright"
            android:layout_alignParentLeft="true"
            android:divider="@null"/>

        <ListView
            android:id="@+id/lvCity"
            android:layout_width="121dp"
            android:layout_height="246dp"
            android:layout_marginRight="8dp"
            android:background="@android:color/holo_blue_bright"
            android:layout_alignParentRight="true"
            android:divider="@null"/>
    </RelativeLayout>

    <Button
        android:id="@+id/btClear"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_centerHorizontal="true"
        android:layout_gravity="center_horizontal"
        android:layout_marginBottom="8dp"
        android:text="清除选择"
        android:textColor="@android:color/holo_blue_bright" />

</RelativeLayout>
4、使用

这个就比较简单了,直接在xml中写上这个自定义的EditText,如下:

    <com.ttdevs.cityselect.util.CitySelect
        android:id="@+id/etCitySelect"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="请选择城市" />

Activity中:

public class MainActivity extends Activity implements OnClickListener {
	private CitySelect csCity;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);

		csCity = (CitySelect) findViewById(R.id.etCitySelect);
		csCity.setCityCode("101060406"); // 吉林 四平
	}

	@Override
	public void onClick(View v) {
		Toast.makeText(getApplicationContext(), "你选择了:"+csCity.getCityCode(), Toast.LENGTH_LONG).show();
	}
}

5、总结

上图中效果是项目中的,里面图片资源不好拿出来共享,最终的效果如下图。


源码:下载





  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值