这里贡献下我使用Android-PickerView实现地址选择器遇到的坑,算是一个笔记。首先要吐槽下后台接口,为了实现移动端和web端的统一(ps:可能他没搞过后台接口),修改地址的时候本来要用下拉框。。。我去,后面ios的大兄弟苦苦哀求,改成了他们ios的PickerView,就是地址选择联动的,我一想也可以,不是有个大兄弟老早就封装了精仿这个PickerView吗,美美的~可是有点曲折
给看下最终的效果图:
这个是那个大兄弟的github:https://github.com/saiwu-bigkoo/Android-PickerView
大写的挽尊,我去
这个大兄弟已经离开编程界了,虽然这也是我的终极目标,嘿嘿
好吧,我们只能做一些摸索了,还好留下了demo,这个控件堪称完美,兄弟们可以一起去用用,大家交流体验。做那种滚动选项选择的不在话下,用了这个,就可以跟ios的大兄弟同步了,当然我不再介绍这里的时间选择器,数据固定已经封装。弱水三千,我取一瓢饮。我这里只拿选项选择器来进行地址滚动选择器(省、市、区三级联动)的用法。主要是数据不统一性,我不改动pickerView已封装好的控件,只在使用上下个道道。
欲哭无泪的是,我这边的后台数据接口需要我这边修改地址选择提交的是id,就像ERP系统的下拉框一样,展示的时候显示键(name),实际提交的时候是提交值(id),有点点恶心,但是恶心也有个恶心的做法。然后我这边的数据库只做查询操作,所以直接生成后,放在assert文件下读取使用了,当然你还会发现更好的做法,大兄弟,你可以带带我~~
一、首先是添加依赖
compile 'com.bigkoo:pickerview:2.1.1'
二、然后你就可以欢快地去使用了,网上有可以直接读取assert文件夹下数据库的方法,稍微封装了下。还有几个bean实体类,用于存储城市编码,省份id,等字段(最重要的是要存储id做后续修改提交的参数)。
1、读取assert下的sqlite数据库
package db;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.res.AssetManager;
import android.database.sqlite.SQLiteDatabase;
import android.util.Log;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.HashMap;
import java.util.Map;
/**
* This is a Assets Database Manager
* Use it, you can use a assets database file in you application
* It will copy the database file to "/data/data/[your application package name]/database" when you first time you use it
* Then you can get a SQLiteDatabase object by the assets database file
* @author RobinTang
* @time 2012-09-20
*
*
* How to use:
* 1. Initialize AssetsDatabaseManager
* 2. Get AssetsDatabaseManager
* 3. Get a SQLiteDatabase object through database file
* 4. Use this database object
*
* Using example:
* AssetsDatabaseManager.initManager(getApplication()); // this method is only need call one time
* AssetsDatabaseManager mg = AssetsDatabaseManager.getManager(); // get a AssetsDatabaseManager object
* SQLiteDatabase db1 = mg.getDatabase("db1.db"); // get SQLiteDatabase object, db1.db is a file in assets folder
* db1.??? // every operate by you want
* Of cause, you can use AssetsDatabaseManager.getManager().getDatabase("xx") to get a database when you need use a database
*/
public class AssetsDatabaseManager {
private static String tag = "AssetsDatabase"; // for LogCat
private static String databasepath = "/data/data/%s/databases"; // %s is packageName
// A mapping from assets database file to SQLiteDatabase object
private Map databases = new HashMap();
// Context of application
private Context context = null;
// Singleton Pattern
private static AssetsDatabaseManager mInstance = null;
/**
* Initialize AssetsDatabaseManager
* @param context, context of application
*/
public static void initManager(Context context){
if(mInstance == null){
mInstance = new AssetsDatabaseManager(context);
}
}
/**
* Get a AssetsDatabaseManager object
* @return, if success return a AssetsDatabaseManager object, else return null
*/
public static AssetsDatabaseManager getManager(){
return mInstance;
}
private AssetsDatabaseManager(Context context){
this.context = context;
}
/**
* Get a assets database, if this database is opened this method is only return a copy of the opened database
* @param dbfile, the assets file which will be opened for a database
* @return, if success it return a SQLiteDatabase object else return null
*/
public SQLiteDatabase getDatabase(String dbfile) {
if(databases.get(dbfile) != null){
Log.i(tag, String.format("Return a database copy of %s", dbfile));
return (SQLiteDatabase) databases.get(dbfile);
}
if(context==null)
return null;
Log.i(tag, String.format("Create database %s", dbfile));
String spath = getDatabaseFilepath();
String sfile = getDatabaseFile(dbfile);
File file = new File(sfile);
SharedPreferences dbs = context.getSharedPreferences(AssetsDatabaseManager.class.toString(), 0);
boolean flag = dbs.getBoolean(dbfile, false); // Get Database file flag, if true means this database file was copied and valid
if(!flag || !file.exists()){
file = new File(spath);
if(!file.exists() && !file.mkdirs()){
Log.i(tag, "Create \""+spath+"\" fail!");
return null;
}
if(!copyAssetsToFilesystem(dbfile, sfile)){
Log.i(tag, String.format("Copy %s to %s fail!", dbfile, sfile));
return null;
}
dbs.edit().putBoolean(dbfile, true).commit();
}
SQLiteDatabase db = SQLiteDatabase.openDatabase(sfile, null, SQLiteDatabase.NO_LOCALIZED_COLLATORS);
if(db != null){
databases.put(dbfile, db);
}
return db;
}
private String getDatabaseFilepath(){
return String.format(databasepath, context.getApplicationInfo().packageName);
}
private String getDatabaseFile(String dbfile){
return getDatabaseFilepath()+"/"+dbfile;
}
private boolean copyAssetsToFilesystem(String assetsSrc, String des){
Log.i(tag, "Copy "+assetsSrc+" to "+des);
InputStream istream = null;
OutputStream ostream = null;
try{
AssetManager am = context.getAssets();
istream = am.open(assetsSrc);
ostream = new FileOutputStream(des);
byte[] buffer = new byte[1024];
int length;
while ((length = istream.read(buffer))>0){
ostream.write(buffer, 0, length);
}
istream.close();
ostream.close();
}
catch(Exception e){
e.printStackTrace();
try{
if(istream!=null)
istream.close();
if(ostream!=null)
ostream.close();
}
catch(Exception ee){
ee.printStackTrace();
}
return false;
}
return true;
}
/**
* Close assets database
* @param dbfile, the assets file which will be closed soon
* @return, the status of this operating
*/
public boolean closeDatabase(String dbfile){
if(databases.get(dbfile) != null){
SQLiteDatabase db = (SQLiteDatabase) databases.get(dbfile);
db.close();
databases.remove(dbfile);
return true;
}
return false;
}
/**
* Close all assets database
*/
static public void closeAllDatabase(){
Log.i(tag, "closeAllDatabase");
if(mInstance != null){
for(int i=0; iif(mInstance.databases.get(i)!=null){
mInstance.databases.get(i).close();
}
}
mInstance.databases.clear();
}
}
}
package db;
import android.app.Application;
import android.database.sqlite.SQLiteDatabase;
public class DBManager {
public static SQLiteDatabase getdb(Application mApplication) {
// 初始化,只需要调用一次
AssetsDatabaseManager.initManager(mApplication);
// 获取管理对象,因为数据库需要通过管理对象才能够获取
AssetsDatabaseManager mg = AssetsDatabaseManager.getManager();
// 通过管理对象获取数据库
SQLiteDatabase db = mg.getDatabase("china_citys_name.sqlite");
return db;
}
}
2、这里的数据提取需要注意要严格区分层级关系。其实就是你要模拟占位你的父级元素的个数,因为后面是通过ArrayList的position提取的。这个作者没讲清楚,我也没时间做进一步封装,因为主体源码都是作者的,不好意思提炼出来做二次封装。我debug模式下,贴几张图,给大伙感受下:
如果你报了这个错误,就好好用心感受下,这7张图。。。血的教训
ArrayList数组越界
java.lang.IndexOutOfBoundsException: Invalid index 1, size is 1
1、3个数据最外层的size必须一致
4、第二层list中的才是每个省份所对应的城市
5、区域最外层(跟省份size一致)
6、区域第二层(跟对应省份的对应城市的size一致)
7、区域第三层(跟对应省份的对应城市的对应区域的size)
8、主要原因是因为,提取显示的时候是严格按照list下的position来获取的,所以一切都清楚了
//返回的分别是三个级别的选中位置
String tx = options1Items.get(options1).getPro_name()
+ options2Items.get(options1).get(option2).getName()
+ options3Items.get(options1).get(option2).get(options3).getName();
3、初始化OptionsPickerView,并使用
MainActivity.java
package com.pickerview.pickerviewdemo;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.TextView;
import com.bigkoo.pickerview.OptionsPickerView;
import java.util.ArrayList;
import db.AreaBean;
import db.CityBean;
import db.DBManager;
import db.ProvinceBean;
public class MainActivity extends AppCompatActivity {
private TextView tvTitle;
private OptionsPickerView pvOptions;//地址选择器
private ArrayList options1Items = new ArrayList<>();//省
private ArrayList> options2Items = new ArrayList<>();//市
private ArrayList>> options3Items = new ArrayList<>();//区
private ArrayList Provincestr = new ArrayList<>();//省
private ArrayList> Citystr = new ArrayList<>();//市
private ArrayList>> Areastr = new ArrayList<>();//区
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
initData();
initEvent();
}
private void initView() {
tvTitle = (TextView) findViewById(R.id.tvTitle);
}
private void initData() {
//选项选择器
pvOptions = new OptionsPickerView(this);
// 获取数据库
SQLiteDatabase db = DBManager.getdb(getApplication());
//省
Cursor cursor = db.query("province", null, null, null, null, null,
null);
while (cursor.moveToNext()) {
int pro_id = cursor.getInt(0);
String pro_code = cursor.getString(1);
String pro_name = cursor.getString(2);
String pro_name2 = cursor.getString(3);
ProvinceBean provinceBean = new ProvinceBean(pro_id, pro_code, pro_name, pro_name2);
options1Items.add(provinceBean);//添加一级目录
Provincestr.add(cursor.getString(2));
//查询二级目录,市区
Cursor cursor1 = db.query("city", null, "province_id=?", new String[]{pro_id + ""}, null, null,
null);
ArrayList cityBeanList = new ArrayList<>();
ArrayList cityStr = new ArrayList<>();
//地区集合的集合(注意这里要的是当前省份下面,当前所有城市的地区集合我去)
ArrayList> options3Items_03 = new ArrayList<>();
ArrayList> options3Items_str = new ArrayList<>();
while (cursor1.moveToNext()) {
int cityid = cursor1.getInt(0);
int province_id = cursor1.getInt(1);
String code = cursor1.getString(2);
String name = cursor1.getString(3);
String provincecode = cursor1.getString(4);
CityBean cityBean = new CityBean(cityid, province_id, code, name, provincecode);
//添加二级目录
cityBeanList.add(cityBean);
cityStr.add(cursor1.getString(3));
//查询三级目录
Cursor cursor2 = db.query("area", null, "city_id=?", new String[]{cityid + ""}, null, null,
null);
ArrayList areaBeanList = new ArrayList<>();
ArrayList areaBeanstr = new ArrayList<>();
while (cursor2.moveToNext()) {
int areaid = cursor2.getInt(0);
int city_id = cursor2.getInt(1);
// String code0=cursor2.getString(2);
String areaname = cursor2.getString(3);
String citycode = cursor2.getString(4);
AreaBean areaBean = new AreaBean(areaid, city_id, areaname, citycode);
areaBeanList.add(areaBean);
areaBeanstr.add(cursor2.getString(3));
}
options3Items_str.add(areaBeanstr);//本次查询的存储内容
options3Items_03.add(areaBeanList);
}
options2Items.add(cityBeanList);//增加二级目录数据
Citystr.add(cityStr);
options3Items.add(options3Items_03);//添加三级目录
Areastr.add(options3Items_str);
}
//设置三级联动效果
pvOptions.setPicker(Provincestr, Citystr, Areastr, true);
//设置选择的三级单位
// pvOptions.setLabels("省", "市", "区");
pvOptions.setTitle("选择城市");
//设置是否循环滚动
pvOptions.setCyclic(false, false, false);
//设置默认选中的三级项目
//监听确定选择按钮
pvOptions.setSelectOptions(0, 0, 0);
pvOptions.setOnoptionsSelectListener(new OptionsPickerView.OnOptionsSelectListener() {
@Override
public void onOptionsSelect(int options1, int option2, int options3) {
//返回的分别是三个级别的选中位置
String tx = options1Items.get(options1).getPro_name()
+ options2Items.get(options1).get(option2).getName()
+ options3Items.get(options1).get(option2).get(options3).getName();
tvTitle.setText(tx);
}
});
}
private void initEvent() {
tvTitle.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
pvOptions.show();
}
});
}
}
4、xml文件就不贴了,就是一个Hellow World的标签
看下这个demo的运行效果图:
5、github地址:https://github.com/TrebleZ/Pickerviewdemo
6、下载地址:http://download.csdn.net/detail/z_zt_t/9717228
总结:等有空还是要完善下的。希望我的这些坑对你有一点点的帮助,have a nice day~