自定义组合控件之省市区三级联动选择

自定义组合控件之省市区三级联动选择

需求

  • 一般的购物网站都会有收件地址的填写,为了让用户快速输入自己想要的地址,将全国省市区地址预定好,用户只要动动手指选择就可以。
  • 做这个组合控件之前,是由于项目的需要,就是做地址管理的模块,当时想模仿京东商城的实现,但时间和水平有限,所以将就的做了如下实现。
  • 先罗列当时开发前后的设计思路

设计思路

  • 地址管理模块

    功能:
        增删该查
        三级联动
        默认地址
    
  • 状态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);
    
  • 好了,来看看最终效果


  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值