版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u011394071/article/details/53439261
自定义组合控件之省市区三级联动选择
需求
- 一般的购物网站都会有收件地址的填写,为了让用户快速输入自己想要的地址,将全国省市区地址预定好,用户只要动动手指选择就可以。
- 做这个组合控件之前,是由于项目的需要,就是做地址管理的模块,当时想模仿京东商城的实现,但时间和水平有限,所以将就的做了如下实现。
- 先罗列当时开发前后的设计思路
设计思路
-
地址管理模块
功能: 增删该查 三级联动 默认地址
-
状态1:用户未登录
入口1:在商品界面, 显示:显示发送至:地址显示-->省,市,区;整个视图属于自定义组合控件,点击整个控件监听点击事件,显示三级地址选项,选择顺序:省,市,区 数据加载和缓存 持久化缓存:本地 非持久化缓存:内存 动画:点击后从屏幕下方往上弹出动画,或者往下弹出可选择的listView,可以放在Poupwindow中显示或者是alertDialog 选择:选择 区 完成后自动关闭弹出框,将数据显示到自定义控件中的Textview中 数据回显:用户选择后,保存在sharepreferences中,每次进入商品详情界面,就读取数据回显。(后面发现是在存在服务器上) 数据传送:用户结算时将数据传递给用户 入口2:在我的中心界面,看不到链接
-
状态2:用户登录:
入口1:在商品界面,显示用户已有的地址列表 下面可以显示其他地址:三级联动-->省市区 入口2:在我的中心界面,只有用户登录状态可见账户管理连接,进入账户管理-->地址管理连接 数据加载和缓存 持久化缓存:本地,服务器数据库 非持久化缓存:内存 地址管理界面:头部应该是可以在框架里固定,中间listview展示不同的地址条目,底部显示新建按钮 Listview条目信息:收件人姓名,联系电话,联系地址 设为默认值,编辑,删除 新建地址的界面: 收货人: 手机号码: 选择联系人:需要读取联系人列表,activity的跳转选择联系人,intent传递意图返回数据 所在地区:又是三级联动:省市区 详情地址:
-
分析:
发现弹出选择省市区的弹框可以设置为自定义控件,里面水平放置四个东西,由左向右 //(后面发现可以只用一个listview) 1.文本提示:配送至/地址 2.地址指示小图标 3.省市区地址 4.展开dialog小图标 下方可能还有个Textview,显示几点之前下单完成,预计什么时候到达
-
小结:
1.点击默认时,有个小bug 重新去数据库获取最新的数据,拿到最新的数据, 解决新增条目设置默认无效的问题,因为如果不去获取数据库最新的数据 集合中新增的条目还没有分配到id,这个id是数据库自动分配的 所以点击设置为默认时,实际上数据库的数据没有改变,必须重新获取最新数据设置 2.从数据库拿回来的数据时,最新的数据排在集合最后,让客户看到最新数据,跳到最后一条 3.省市区三级联动自定义控件的实现 自定义dialog(去掉ationBar,布局在屏幕底部,相对屏幕高度0.6倍,动态适配不同屏幕分辨率) + listView:展示省市区的数据 + 本地json数据:全国省市区json字符串 使用一个listview,点击条目时,修改数据集合,更新适配器,达到三级联动效果 4.可以优化的地方 1.自定义控件选择后的文字动画效果可以添加,增强动感 2.dialog弹出时可以做成动画 3.读取手机联系人时,应该判断是否为空
具体实现
-
首先,因为是组合控件,所以自定义的view要继承viewGrup,这里继承LinearLayout
package skxy.dev.addresslib; import android.app.Activity; import android.app.Dialog; import android.content.Context; import android.os.Handler; import android.util.AttributeSet; import android.view.Display; import android.view.Gravity; import android.view.View; import android.view.Window; import android.view.WindowManager; import android.widget.AdapterView; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.ListView; import android.widget.ProgressBar; import android.widget.TextView; import com.google.gson.Gson; import com.google.gson.reflect.TypeToken; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.List; import skxy.dev.addresslib.adapter.ProvinceAdapter; import skxy.dev.addresslib.bean.AddressBean; /** * ClassName ReceviceAdressView * Created by skxy on 2016/8/30. * DES 自定义的收货地址视图 */ public class ReceviceAdressView extends LinearLayout implements View.OnClickListener, AdapterView.OnItemClickListener { private Context mContext; private ImageView mAddressView; private View mInflatView; private ListView mListView; private List<String> mTitleDatas; private Dialog mDialog; private List<AddressBean.CityBean> mCityDatas; private List<AddressBean> mProvinceDatas; private TextView mAddressTv; private List<String> mCurrentDatas = new ArrayList<>();//存放当前的数据 private ProvinceAdapter maddressAdapter; private TextView mTvContent; private ImageView ivCloase; private ProgressBar mPb; //设置地址图标不可见 public void addressIvToggle(boolean isShow) { if (isShow) { mAddressView.setVisibility(View.GONE); } else { mAddressView.setVisibility(View.VISIBLE); } } /** * 设置省市区地址内容 */ public void setAddress(String content) { mAddressTv.setText(content); } /** * 获取省市区数据 * * @param */ public String getAddress() { return sb.toString().replaceAll(">",""); } public ReceviceAdressView(Context context) { super(context); } public ReceviceAdressView(Context context, AttributeSet attrs) { super(context, attrs); this.mContext = context; initView(context); initData(); initEvent(); } /** * 初始化数据 * 外界触发加载 */ public void initData() { //标题 mTitleDatas = new ArrayList<>(); //省级 mProvinceDatas = new ArrayList<>(); //市级 mCityDatas = new ArrayList<>(); } private void initEvent() { this.setOnClickListener(this); mListView.setOnItemClickListener(this); ivCloase.setOnClickListener(new OnClickListener() { @Override public void onClick(View view) { mDialog.dismiss(); } }); } //初始化视图 private void initView(Context context) { View view = View.inflate(context, R.layout.receive_address_view, this); mAddressTv = (TextView) view.findViewById(R.id.receive_tv_address); mAddressView = (ImageView) view.findViewById(R.id.receive_iv_address); //listView mInflatView = View.inflate(mContext, R.layout.listview, null); mListView = (ListView) mInflatView.findViewById(R.id.receive_listview); mListView.setDividerHeight(0); mTvContent = (TextView) mInflatView.findViewById(R.id.dialog_tv_content); ivCloase = (ImageView) mInflatView.findViewById(R.id.dialog_iv_close); mPb = (ProgressBar) mInflatView.findViewById(R.id.receive_progress); maddressAdapter = new ProvinceAdapter(mContext); mListView.setAdapter(maddressAdapter); } @Override public void onClick(View view) { showDialog(); } Handler handler = new Handler(); /** * 显示弹出对话框 */ private void showDialog() { //初始化数据 mTvContent.setText("请选择"); sb.delete(0, sb.length()); index = 0; mTitleDatas.clear(); mCurrentDatas.clear(); if (mDialog == null) { mDialog = new Dialog(mContext, R.style.dialog); } mDialog.setContentView(mInflatView); mDialog.setTitle("收货地址"); //设置对其方式 Window dialogWindow = mDialog.getWindow(); WindowManager.LayoutParams lp = dialogWindow.getAttributes(); dialogWindow.setGravity(Gravity.BOTTOM); Activity mAct = (Activity) mContext; WindowManager m = mAct.getWindowManager(); Display d = m.getDefaultDisplay(); // 获取屏幕宽、高用 WindowManager.LayoutParams p = dialogWindow.getAttributes(); // 获取对话框当前的参数值 p.height = (int) (d.getHeight() * 0.6); // 高度设置为屏幕的0.6 p.width = (int) (d.getWidth()); // 宽度设置为屏幕的0.65 dialogWindow.setAttributes(p); mDialog.show(); //子线程请求数据 mPb.setVisibility(View.VISIBLE); new Thread(new Runnable() { @Override public void run() { //子线程去加载数据 loadDatas(); } }).start(); } private void loadDatas() { //context.getClass().getClassLoader().getResourceAsStream("assets/"+资源名); //读取本地Json字符串,解析字符串为对应的bean,赋值给对应集合 if (mProvinceDatas.size() != 0) { //如果内存中有数据,就不需要再加载 for (AddressBean datas : mProvinceDatas) { String province = datas.name; mCurrentDatas.add(province); } } else { String jsonString = getDatas(); //解析 resolveDatas(jsonString); } //主线程更新UI handler.post(new Runnable() { @Override public void run() { mPb.setVisibility(View.GONE); maddressAdapter.setDatas(mCurrentDatas); } }); } private void resolveDatas(String jsonString) { if (jsonString != null) { Gson gson = new Gson(); mProvinceDatas = gson.fromJson(jsonString, new TypeToken<List<AddressBean>>() { }.getType()); for (AddressBean datas : mProvinceDatas) { //解析后先将省级名字存储到当前数据集合 String province = datas.name; mCurrentDatas.add(province); } } } /** * 获取本地json数据 */ private String getDatas() { InputStream in = null; try { in = mContext.getClass().getClassLoader().getResourceAsStream("assets/" + "addresslist.json"); ByteArrayOutputStream bao = new ByteArrayOutputStream(); byte[] buffer = new byte[1024*8]; int len = 0; while ((len = in.read(buffer)) != -1) { bao.write(buffer, 0, len); } return bao.toString();//转为string } catch (IOException e) { e.printStackTrace(); return null; } finally { if (in != null) { try { in.close(); } catch (IOException e) { e.printStackTrace(); } } } } //存储临时的地址 StringBuilder sb = new StringBuilder(); int index = 0; //listView条目点击事件 @Override public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) { String name = ""; switch (index) { case 0://省级 name = mCurrentDatas.get(i); AddressBean addressBean = mProvinceDatas.get(i); mCityDatas = addressBean.city;//市级对应的数据集合 mCurrentDatas.clear();//为了重复使用集合,先将原有的数据清空 for (AddressBean.CityBean cityBean : mCityDatas) { mCurrentDatas.add(cityBean.name); } break; case 1://市级 name = mCurrentDatas.get(i); AddressBean.CityBean cityBean = mCityDatas.get(i); List<String> area = cityBean.area; mCurrentDatas.clear(); for (String s : area) { mCurrentDatas.add(s); } break; case 2://地区 name = mCurrentDatas.get(i); break; } mTitleDatas.add(name); //设置地址 if (mTitleDatas.size() == 3) { sb.append(mTitleDatas.get(mTitleDatas.size() - 1)); mTvContent.setText(sb.toString());//dialog中的地址标题 mAddressTv.setText(sb.toString());//整个控件的地址 mDialog.dismiss(); } else { sb.append(mTitleDatas.get(mTitleDatas.size() - 1)).append(">"); mTvContent.setText(sb.toString()); mAddressTv.setText(sb.toString());//整个控件的地址 maddressAdapter.setDatas(mCurrentDatas); mListView.setSelection(0); } index++; } }
-
将省市区三级地址以json格式保存在assets中,需要的时候读取即可。
-
另外这里开始我是以一个modle的方式开发的,后面才将其作为库的方式添加到项目中使用,这个过程主要有几个步骤,这里总结为三步骤:
-
1.build.gradle文件中将
apply plugin: 'com.android.application' 替换为: apply plugin: 'com.android.library'
-
2.将defaultConfig节点中的applicationId去掉,如
defaultConfig { //applicationId "org.skxy.www.healthcat"//这里注释掉 minSdkVersion 15 targetSdkVersion 24 versionCode 1 versionName "1.0" }
-
3.将AndroidManifest文件中的其他节点全部删除,只留manifest节点,如
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="skxy.dev.addresslib"> </manifest>
-
-
这样就把一个普通的Modle做成Lib了。
-
在需要使用的项目中添加依赖
compile project(':addresslib')
-
在项目布局文件中使用
<skxy.dev.addresslib.ReceviceAdressView android:layout_width="wrap_content" android:layout_height="45dp"/>
-
项目中获取控件选择的地址
mAdressView = (ReceviceAdressView) findViewById(R.id.address); //获取自定义控件中的地址 String address = mAdressView.getAddress();
-
设置是否显示图标
mAdressView.setAddressIv(false);
-
好了,来看看最终效果
- 源码地址:https://github.com/skxy2016/AddressView
- 欢迎探讨