前言部分
最近新项目要中设计一个地址选择的效果(效果和某东的商城一样一样的),虽然网上已经有现成的方案了,但是最近刚好时间还算充裕所以想了想还是准备自己来做一个,顺便加深一下对自定义dialog的认识。
Demo中主要实现了京东的地址选择的控件效果,开始本来计划用dialog来实现的,但是网上似乎说推荐使用DialogFragment来封装,刚好我还没使用过DialogFragment来做一个,说是封装其实只是定义了一个AddressSelectDialog对外提供了一些基础的方法,如果需要使用可以自行修改。
放个效果图镇楼
内容部分
写一个功能的基本流程:构思5分,代码3分,完善2分。
因为以前我写功能都是上来就开写,一口气写个差不多完事,可是完成后你会发现写的很混乱,因为写之前没有很好的构思项目,这样看似效率很高,但是后来你回来改的时间会很多,这也就是为什么软件开发一直都是强调架构的原因吧。
进入正题
其实这个地址选择权很以前的三级联动一样不过是样式上不同,个人觉得还是比较舒服的。
功能分析拆解一下,做起来更有条不紊。
-
我们首先需要头部三个tab,这三个tab也不是一个固定的可能发生增减。
-
下方上一个地址的列表,省–市–区。
-
省市区的列表是有关联关系的,但是实际上同一时间只会有一个列表出现。
具体的实现过程
-
第一部分是头部这里我使用了design包中的TabLayout来实现,用起来还是比较舒服,下面看一下布局和相关代码:
<android.support.design.widget.TabLayout android:id="@+id/tb_head_indicator" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" app:tabIndicatorColor="@color/red" app:tabSelectedTextColor="@color/red" />
上面的布局汇总很简单,这里说明一下注意地方:
- 其实你观察源码会发现TabLayout是继承了HorizontalScrollView类,这也是可以滚动的原因所在。然后tabIndicatorColor是设置下面的指示标的颜色,tabSelectedTextColor设置选中文字的颜色。这里没有什么特殊的样式,其实TabLayout的功能还是很强大的,基本上你所需要的实现样式都提供了。
-
下面就是一个列表了啊,这个地方我们发现同时出现的只有一个列表,虽然是说省市区三个类表。这里的方案我想到三种:
-
一个是分别弄三个RecyclerView(或者ListView)来实现,然后重叠在这个布局中,通过标记来设置显示哪一个RecyclerView,这个方案的好处就是只需要初始化一次就可以了,并且选中的状态也可以直接保留在列表中。
-
一个是使用一个RecyclerView并给初始化三个不同的Adapter来实现,这个方案使得RecyclerView一只在界面中存在,通过给RecyclerView绑定不同的RecyclerView来实现切换。本Demo就是通过实现这个方案来做的。
-
这种就是RecyclerView和RecyclerView用同一个通过切换tab来更换里面的数据来做,其实这个和上面的很像,上面的方案也很容易改成这样。
以上我觉得都还好吧,写起来难以程度略不同,第2和第3 差不多,主要的逻辑都在通过标识位不断的更新数据上,唯一的区别就是一个更新数据源,一个直接把适配器换掉了;第一个写起来应该算是比较简单,因为独立出来三个不同的列表,只第一次把数据初始化进去就好了,后期只通过标识位来切换显示就可以。
下面贴出部分代码片段:
//初始化了三个adapter,先把第一个省的adapter绑定到RecyclerView上了。 private void initRecyclerView() { rvTabExample.setLayoutManager(new LinearLayoutManager(getContext())); rvTabExample.addItemDecoration(new DividerItemDecoration(getContext(), DividerItemDecoration.VERTICAL)); //省地址 recyclerViewTabAdapter = new RecyclerViewTabAdapter(getContext()); //市地址 recyclerViewCityAdapter = new RecyclerViewCityAdapter(getContext()); //区地址 recyclerViewAreaAdapter = new RecyclerViewAreaAdapter(getContext()); recyclerViewTabAdapter.setAddressList(cityBeanList); recyclerViewTabAdapter.setBaseItemClickListener(this); recyclerViewCityAdapter.setBaseItemClickListener(this); recyclerViewAreaAdapter.setBaseItemClickListener(this); rvTabExample.setAdapter(recyclerViewTabAdapter); }
处理adapter更新的代码部分,主要通过标记tabCurrentPosition当前显示的tab来区分显示,不同tab下adapter传入的类型不同,这里如果使用同一个的话需要在adapter的内部来进行判断了,或者在外面把数据做统一处理,传入adapter的都统一一下。
private void upDataArray() { if (tabCurrentPosition == oldTabCurrentPosition) { return; } //设置颜色 LogUtil.i("upDataArray--更新列表啊:::" + tabCurrentPosition); switch (tabCurrentPosition) { case 0: recyclerViewTabAdapter.setCurrentSelect(currentSelectMap.get(tabCurrentPosition)); recyclerViewTabAdapter.setAddressList(cityBeanList); rvTabExample.setAdapter(recyclerViewTabAdapter); break; case 1: cityIndex = currentSelectMap.get(0) == -1 ? initPosition : currentSelectMap.get(0); recyclerViewCityAdapter.setAddressList(cityBeanList.get(cityIndex).getCity()); recyclerViewCityAdapter.setCurrentSelect(currentSelectMap.get(tabCurrentPosition)); rvTabExample.setAdapter(recyclerViewCityAdapter); break; case 2: cityIndex = currentSelectMap.get(0) == -1 ? initPosition : currentSelectMap.get(0); int cityInnerIndex = currentSelectMap.get(1) == -1 ? initPosition : currentSelectMap.get(1); recyclerViewAreaAdapter.setAddressList(cityBeanList.get(cityIndex).getCity().get(cityInnerIndex).getArea()); recyclerViewAreaAdapter.setCurrentSelect(currentSelectMap.get(tabCurrentPosition)); rvTabExample.setAdapter(recyclerViewAreaAdapter); break; default: break; } oldTabCurrentPosition = tabCurrentPosition; }
因为联动的关系,所以每次点击tab或者点击item的时候都会出发更新的操作。
-
-
这里是比较关键的部分,主要是处理了点击事件,当点击事件发生的时候要伴随着tab的切换操作。
这里我们有一个标记的当前tab的标识tabCurrentPosition,并且我会把每一个列表里选中的position存储到map中,并且使用tabCurrentPosition当作key,这样在切换tab回显的时候就方便一些,这里以前我使用过List但是效果不好,因为不断切换Tab的话还要去重制List。
@Override public void onItemClick(View view, int position) { //设置一下tab上的文字 switch (tabCurrentPosition) { case 0: mTabLayout.getTabAt(tabCurrentPosition).setText(recyclerViewTabAdapter.getAddressList().get(position).getName()); break; case 1: mTabLayout.getTabAt(tabCurrentPosition).setText(recyclerViewCityAdapter.getAddressList().get(position).getName()); break; case 2: mTabLayout.getTabAt(tabCurrentPosition).setText(recyclerViewAreaAdapter.getAddressList().get(position)); break; default: break; } //相同tab下进行的position变化处理,这里要提醒一下,当满足这个条件的时候,就是说明你下一个tab对应的数据需要重新初始化了。 if (currentSelectMap.get(tabCurrentPosition) != position) { int removeTab = mTabLayout.getTabCount() - 1; while (removeTab > tabCurrentPosition) { if (mTabLayout.getTabAt(removeTab) != null) { mTabLayout.removeTabAt(removeTab); currentSelectMap.put(removeTab, -1); } removeTab--; } //防止越界啊,目前只写了三级。其实是可以写成很多。这里没有扩展性 if (tabCurrentPosition >= 2) { tabCurrentPosition = 2; } else { mTabLayout.addTab(mTabLayout.newTab().setText("请选择"), false); currentSelectMap.put(tabCurrentPosition + 1, -1); } } //设置当前tab下的选中状态 currentSelectMap.put(tabCurrentPosition, position); //滚动到下一个tab tabCurrentPosition++; //越界的话处理为最后一个tab if (tabCurrentPosition >= mTabLayout.getTabCount()) { tabCurrentPosition = mTabLayout.getTabCount() - 1; if (selectAddressResultListener != null) { int[] result = { currentSelectMap.get(0), currentSelectMap.get(1), currentSelectMap.get(2)}; selectAddressResultListener.selectAddressResult(result); dismiss(); } } else { mTabLayout.setScrollPosition(tabCurrentPosition, 0f, true); } LogUtil.i("tabCurrentPosition--" + tabCurrentPosition + "tabCount" + mTabLayout.getTabCount()); //如果在最后一个列表中切换的话这里单独处理一下,因为upDataArray限制里更新的频率。 if (tabCurrentPosition == oldTabCurrentPosition) { switch (tabCurrentPosition) { case 0: recyclerViewTabAdapter.setCurrentSelect(currentSelectMap.get(tabCurrentPosition)); recyclerViewTabAdapter.notifyDataSetChanged(); break; case 1: recyclerViewCityAdapter.setCurrentSelect(currentSelectMap.get(tabCurrentPosition)); recyclerViewCityAdapter.notifyDataSetChanged(); break; case 2: recyclerViewAreaAdapter.setCurrentSelect(currentSelectMap.get(tabCurrentPosition)); recyclerViewAreaAdapter.notifyDataSetChanged(); break; default: break; } } else { upDataArray(); } }
其实上面基本就算主要步骤了,包含初始化—数据填充—点击处理,完成的话基本上就实现了效果,下面我稍微完善一下这个控件。
-
数据回显,这是个比较常规的需求,所以也加了上来。如下使用了两个方法,因为我发现如果show页面后直接更新数据的话会报错空指针,如果延迟一会就不出现问题,感觉是初始化界面的未完成就使用控件导致。所以稍微延迟一点来绕过这个问题。
public void setSelectAddress(final int[] selectAddress) { new Handler().postDelayed(new Runnable() { @Override public void run() { setSelectAddress(selectAddress, true); } }, 60); } private void setSelectAddress(int[] selectAddress, boolean isUpdata) { if (selectAddress != null && selectAddress.length != 0 && cityBeanList != null) { for (int i = 0; i < 3; i++) { //保存选择 currentSelectMap.put(i, selectAddress[i]); } CityBean cityBean = cityBeanList.get(currentSelectMap.get(0)); CityBean.CityBeanInner cityBeanInner = cityBean.getCity().get(currentSelectMap.get(1)); String name = cityBeanInner.getArea().get(currentSelectMap.get(2)); mTabLayout.removeAllTabs(); //初始化tab mTabLayout.addTab(mTabLayout.newTab().setText(cityBean.getName()), false); mTabLayout.addTab(mTabLayout.newTab().setText(cityBeanInner.getName()), false); mTabLayout.addTab(mTabLayout.newTab().setText(name), true); tabCurrentPosition = currentSelectMap.size() - 1; //选择最后 mTabLayout.setScrollPosition(tabCurrentPosition, 0f, true); //更新集合 upDataArray(); } }
数据回显的实现是通过传入标识来完成,因为我在dialog内部维护了这个标志,外部只需要传如标识并且更新一下界面就可以,界面的话需要更新三个tab和最后一个列表就可以了。由于初期就设计了三个列表所以这个标识我只是用了前三个长度(偷懒一下)。
以上步骤就基本完成了这个功能了
剩下一些零碎东西,如:数据源的设置,你需要参考一下我的CityBean实体;监听设置,当点击最后列表的条目时候就把完整的数据返回去取了。
结束部分
现在android开源的东西太多,我们似乎正在沦为代码搬运工,但是我认为有些些东西还是要亲自去做,能看懂和能写出来中间差了很长一段路。再次共勉,共同进步。
传送到GitHub
有问题欢迎纠正,谢谢啦
如果对你有帮助就点个赞把,你的鼓励是我写作的动力。
今天的文章实现京东商城地址选择效果(效果还挺一致的)分享到此就结束了,感谢您的阅读。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/67356.html