很多地方用到多级城市选择,有人偏爱苹果的那种滚动的效果。查了下,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、总结
上图中效果是项目中的,里面图片资源不好拿出来共享,最终的效果如下图。
源码:下载