自定义一个仿拼多多地址选择器

前言

做了一个仿拼多多的地址选择器,但是与拼多多实现方法有些出入,大体效果是差不多的。废话不多说,先上一张效果动图:

img

开始

  1. 先说说本文的一些概念。地区级别:就是比如省级,市级,县级,镇级,那么这种最多就是4级。
  2. 好了,我们分析一波效果图,当一个级别的地区选择好之后会创建出一个新的Tab,到了最后一个地区级别之后就不会再创建新的。如果倒回去重新选择一个级别的地区,会移除后面的Tab之后再创建一个新的Tab。选择好之后,如果点击Tab会切换到相应地区级别,并且滚动到之前选择的地区显示,创建新的Tab就默认滚动到第一个position的位置。
  3. 其次,来看看我们这个界面的布局:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"android:layout_width="match_parent"android:layout_height="560dp"android:orientation="vertical"android:paddingStart="12dp"android:paddingEnd="12dp"><!-- Dialog的标题 --><TextViewandroid:id="@+id/user_tv_dialog_title"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginTop="18dp"android:layout_gravity="center_horizontal"/><!-- 标题下的第一条横线 --><Viewandroid:layout_width="match_parent"android:layout_height="1dp"android:background="#e6e6e6"android:layout_marginTop="17dp"/><!-- 顶部的TabLayout --><android.support.design.widget.TabLayoutandroid:id="@+id/user_tb_dialog_tab"android:layout_width="match_parent"android:layout_height="wrap_content"app:tabSelectedTextColor="@color/colorPrimary"app:tabGravity="fill"app:tabMode="scrollable"/><!-- TabLayout下方的横线 --><Viewandroid:layout_width="match_parent"android:layout_height="1dp"android:background="#e6e6e6"/><!-- 显示地区数据的RecyclerView --><android.support.v7.widget.RecyclerViewandroid:id="@+id/user_rv_dialog_list"android:layout_width="match_parent"android:layout_height="0dp"android:layout_weight="1"/>
</LinearLayout>
  1. 从布局中我们可以看出,我最主要靠TabLayout加RecyclerView实现这个效果,而拼多多个人猜测是TabLayout加RecyclerView加ViewPager,所以拼多多的RecyclerView是可以侧滑到上一个Tab页或下一个,这也就是和拼多多效果的不同之处。

开始撸代码

  1. 从代码下手,首先把单个地区列表的布局写好:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="wrap_content"android:orientation="horizontal"android:paddingTop="10dp"android:paddingBottom="10dp"tools:ignore="UseCompoundDrawables"><!-- 显示地区名称 --><TextViewandroid:id="@+id/user_tv_address_dialog"android:layout_width="wrap_content"android:layout_height="wrap_content"/><!-- 显示后面的勾选图标 --><ImageViewandroid:id="@+id/user_iv_address_dialog"android:layout_width="13dp"android:layout_height="9dp"android:src="@drawable/user_icon_address_check"android:layout_marginStart="11dp"android:layout_gravity="center_vertical"android:visibility="gone"tools:ignore="ContentDescription" />
</LinearLayout>
  1. 把地区这个实体对象创建好:
public class AddressItem {// 地区名private String address;// 是否勾选private boolean isChecked;// 地区的ID,我这边项目需要的是int型,大家可以根据自己项目需要进行修改private int id;public String getAddress() {return this.address;}public void setAddress(String address) {this.address = address;}public boolean isChecked() {return this.isChecked;}public void setChecked(boolean checked) {this.isChecked = checked;}public int getId() {return this.id;}public void setId(int id) {this.id = id;}@Overridepublic String toString() {return "AddressItem{" +"address='" + address + '\'' +", isChecked=" + isChecked +", id=" + id +'}';}
}
  1. 把RecyclerView的适配器写好:
public class AddressAdapter extends RecyclerView.Adapter<AddressAdapter.MyViewHolder> {// 保存地区数据的列表private List<AddressItem> list = new ArrayList<>();// 自定义的单项被点击监听事件private ItemClickListener listener;@NonNull@Overridepublic MyViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int i) {View view = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.user_item_address_bottom_sheet_dialog, viewGroup, false);return new MyViewHolder(view);}@Overridepublic void onBindViewHolder(@NonNull MyViewHolder myViewHolder, int i) {AddressItem item = list.get(i);if (item.isChecked()) {myViewHolder.tvAddress.setText(item.getAddress());myViewHolder.tvAddress.setTextColor(Color.parseColor("#1F83FF"));myViewHolder.ivChecked.setVisibility(View.VISIBLE);} else {myViewHolder.tvAddress.setText(item.getAddress());myViewHolder.tvAddress.setTextColor(Color.BLACK);myViewHolder.ivChecked.setVisibility(View.GONE);}}@Overridepublic int getItemCount() {return this.list == null ? 0 : list.size();}public void setList(List<AddressItem> list) {if (this.list != null && list != null) {this.list.clear();this.list.addAll(list);this.notifyDataSetChanged();}}public void setOnItemClickListener(@NonNull ItemClickListener listener) {this.listener = listener;}class MyViewHolder extends RecyclerView.ViewHolder {TextView tvAddress;ImageView ivChecked;MyViewHolder(@NonNull View itemView) {super(itemView);tvAddress = itemView.findViewById(R.id.user_tv_address_dialog);ivChecked = itemView.findViewById(R.id.user_iv_address_dialog);if (listener != null) {itemView.setOnClickListener(v -> listener.onItemClick(getAdapterPosition()));}}}public interface ItemClickListener {void onItemClick(int position);}
}
  1. 首先自己动手写了两个BaseDialog,没什么营养,代码也很简单:
public abstract class CustomBaseDialog extends Dialog {protected Context context;public CustomBaseDialog(@NonNull Context context) {super(context);this.context = context;}protected abstract Integer getLayout();protected abstract Integer getGravity();protected abstract Integer getBackgroundRes();protected abstract Integer getWindowAnimations();protected abstract void initView();@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);if (getLayout() != null)setContentView(getLayout());Window window = getWindow();if (window != null) {// 去除DecorView默认的内边距,好让布局占满整个横向屏幕View decorView = window.getDecorView();decorView.setPadding(0,0,0,0);if (getGravity() != null)window.setGravity(getGravity());elsewindow.setGravity(Gravity.CENTER);if (getWindowAnimations() != null)window.setWindowAnimations(getWindowAnimations());if (getBackgroundRes() != null)decorView.setBackgroundResource(getBackgroundRes());}initView();}protected void setClickListener(int id, View.OnClickListener listener) {findViewById(id).setOnClickListener(listener);}
}public abstract class CustomBaseBottomSheetDialog extends CustomBaseDialog {public CustomBaseBottomSheetDialog(@NonNull Context context) {super(context);}@Overrideprotected Integer getGravity() {return Gravity.BOTTOM;}@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);Window window = getWindow();if (null != window) {// 去除window的margin,目的也是为了让布局占满屏幕WindowManager.LayoutParams layoutParams = window.getAttributes();layoutParams.width = WindowManager.LayoutParams.MATCH_PARENT;layoutParams.horizontalMargin = 0;window.setAttributes(layoutParams);}}
}
  1. 接着才是重点,自定义地址选择器Dialog:
public class AddressBottomSheetDialog extends CustomBaseBottomSheetDialog {private TabLayout tabLayout;private AddressAdapter addressAdapter;private int maxLevel;   // 最大有多少级的地区,可以通过setMaxLevel方法进行自定义private SparseArray<List<AddressItem>> levelList;     // 级别列表数据private SparseIntArray levelPosition;                 // 各个级别选中的列表positionprivate SparseIntArray levelIds;                      // 各个级别选择的地址IDprivate String title;  // 标题private String tabText = "请选择";                    // 新的Tab默认显示的文本private TabSelectChangeListener changeListener;       // Tab的选择被改变的监听public AddressBottomSheetDialog(@NonNull Context context) {super(context);}@Overrideprotected Integer getLayout() {return R.layout.user_layout_address_bottom_sheet_dialog;}@Overrideprotected Integer getBackgroundRes() {return R.drawable.bg_dialog_bottom;}@Overrideprotected Integer getWindowAnimations() {return R.style.DialogBottom;}@Overrideprotected void initView() {levelList = new SparseArray<>();levelPosition = new SparseIntArray();levelIds = new SparseIntArray();((TextView)findViewById(R.id.user_tv_dialog_title)).setText(title);tabLayout = findViewById(R.id.user_tb_dialog_tab);final RecyclerView recyclerView = findViewById(R.id.user_rv_dialog_list);tabLayout.addOnTabSelectedListener(new TabLayout.BaseOnTabSelectedListener() {@Overridepublic void onTabSelected(TabLayout.Tab tab) {final int position = tab.getPosition();List<AddressItem> list = levelList.get(position);if (null != list && !list.isEmpty()) {   // 如果选中级别的List没有数据就通过执行回调来获取,否则直接复用addressAdapter.setList(list);final int lastClickPositon = levelPosition.get(position, -1); // 获取上一次选中的地区的position,如果找不到,默认返回-1if (lastClickPositon >= 0) recyclerView.smoothScrollToPosition(lastClickPositon); // 如果上一次有选择,RecyclerView滚动到指定position} else if (changeListener != null) {changeListener.onSelectChange(position, levelIds.get(position));}}@Overridepublic void onTabUnselected(TabLayout.Tab tab) {}@Overridepublic void onTabReselected(TabLayout.Tab tab) {}});addressAdapter = new AddressAdapter();// 列表单项点击事件addressAdapter.setOnItemClickListener(position -> {final int selectedTabPosition = tabLayout.getSelectedTabPosition(); // 选中的Tab的positionlevelIds.put(selectedTabPosition, levelList.get(selectedTabPosition).get(position).getId()); // 更新选中的地区的IDchangeSelect(selectedTabPosition, position);levelPosition.put(selectedTabPosition, position); // 更新选中的地区在列表中的positionsetTabText(selectedTabPosition, levelList.get(selectedTabPosition).get(position).getAddress()); // 将选中的地区的名字显示在Tab上if (selectedTabPosition < maxLevel - 1 && selectedTabPosition == tabLayout.getTabCount() - 1) { // 如果没达到MaxLevel并且选中的Tab是最后一个就添加一个Tab,并且RecyclerView滚动到最顶部tabLayout.addTab(createTab(), true);recyclerView.smoothScrollToPosition(0);}});recyclerView.setLayoutManager(new LinearLayoutManager(context));recyclerView.setAdapter(addressAdapter);tabLayout.addTab(createTab(), true); // 默认添加一个Tab}// 创建一个请选择的tab并返回private TabLayout.Tab createTab() {return tabLayout.newTab().setText(tabText);}// 当点击了RecyclerView条目的时候执行的方法private void changeSelect(int selectedTabPosition, int nowClickPosition) {// 保存下来的当前列表上一个点击位置.如果找不到该值,默认返回-1final int lastPosition = levelPosition.get(selectedTabPosition, -1);// 如果上一个点击位置和下一个点击位置相同,则不做改变if (nowClickPosition == lastPosition) {return;}// 如果不是最后一个并且又重新选择了级别地区,移除后面的Tabfinal int count = tabLayout.getTabCount();// 这里要倒过来移除Tab,不然会出现这样的情况,假如你有四个Tab,你移除第0个,接着移除第一个的话,第一个不是原来的第一个。因为你把第0个移除,原来的第一个就到了第0个的位置上。所以倒过来移除是明智的做法if (selectedTabPosition < count - 1) {TabLayout.Tab nowTab = tabLayout.getTabAt(selectedTabPosition);if (null != nowTab) nowTab.setText(tabText);for (int i = count - 1; i > selectedTabPosition; i--) {// 将相应地区级别的列表数据移除levelList.remove(i);// 将之前选中的position重置为-1levelPosition.put(i, -1);// 将之前记录的地区ID重置为-1levelIds.put(i, -1);tabLayout.removeTabAt(i);}}// 将现在选择的地区设置为已经选中levelList.get(selectedTabPosition).get(nowClickPosition).setChecked(true);// 通过adapter更新列表单个对象addressAdapter.notifyItemChanged(nowClickPosition);if (lastPosition >= 0) {// 将上一个选中的地区标记为未选中levelList.get(selectedTabPosition).get(lastPosition).setChecked(false);// 通过adapter更新列表单个对象addressAdapter.notifyItemChanged(lastPosition);}}// 设置第几个tab的文字private void setTabText(int tabPosition, String text) {TabLayout.Tab tab = tabLayout.getTabAt(tabPosition);if (null != tab) tab.setText(text);}// -----------------------------  以下是对外公开方法与接口  --------------------------/***  设置Dialog的标题* @param title 标题文字*/public void setDialogTitle(String title) {this.title = title;}/***  设置在当前tab下还未选择区域时候tab默认显示的文字* @param tabDefaultText 默认显示的文字*/public void setTabDefaultText(String tabDefaultText) {this.tabText = tabDefaultText;}/***  设置地址最大级别(如:省,市,县,镇的话就是最大4级)* @param level 最大级别*/public void setMaxLevel(int level) {this.maxLevel = level;}/***  设置当前级别列表需要显示的列表数据* @param list 列表数据* @param level 地区级别*/public void setCurrentAddressList(List<AddressItem> list, int level) {levelList.put(level, list);addressAdapter.setList(list);}/***  设置Dialog中Tab点击切换的监听* @param listener tab切换监听实现*/public void setTabSelectChangeListener(@NonNull TabSelectChangeListener listener) {this.changeListener = listener;}/***  自定义的Tab切换监听接口*/public interface TabSelectChangeListener {void onSelectChange(int level, int parentId);}
}
  1. 使用方法:
private void init() {mDialog = new AddressBottomSheetDialog(this);mDialog.setDialogTitle("配送至");mDialog.setMaxLevel(4);mDialog.setTabDefaultText("请选择");mDialog.setTabSelectChangeListener((level, parentId) ->mDialog.setCurrentAddressList(requestAddress(level, parentId), level));binding.userIvSelectAddress.setOnClickListener(v -> mDialog.show());
}
private List<AddressItem> requestAddress(int level, int parentID) {List<AddressItem> list = new ArrayList<>();String levelTxt = "未知";switch (level) {case 0:levelTxt = "省级";break;case 1:levelTxt = "市级";break;case 2:levelTxt = "县级";break;case 3:levelTxt = "镇级";}for (int i = 0; i < 32; i++) {AddressItem item = new AddressItem();item.setChecked(false);item.setAddress(levelTxt + i);list.add(item);}return list;
}  

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/15270.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

map,set的封装(基于改造红黑树)

目录 引言 1.迭代器 2.map的[]重载 3.KeyOfValue模板参数 4.整体代码展示 //改造后的红黑树代码 #include <iostream> using namespace std;enum Colour {RED 0,BLACK, };//为了实现map与set封装使用同一个模板红黑树&#xff0c;前者的value是pair&#xff0c;后者…

WebAgent-基于大型语言模型的代理程序

大型语言模型&#xff08;LLM&#xff09;可以解决多种自然语言任务&#xff0c;例如算术、常识、逻辑推理、问答、文本生成、交互式决策任务。最近&#xff0c;LLM在自主网络导航方面也取得了巨大成功&#xff0c;代理程序助HTML理解和多步推理的能力&#xff0c;通过控制计算…

Spring——更快捷的存储 / 获取Bean对象

文章目录 前言一、存储 Bean 对象类注解为什么有五个类注解使用类注解存储对象配置扫描路径(重中之重)添加注解存储 Bean 对象 方法注解配置扫描路径(重中之重)使用方法注解存储对象 二、获取 Bean 对象Autowired属性注入Setter注入构造方法注入 Resource 总结 前言 本人是一个…

【雕爷学编程】MicroPython动手做(20)——掌控板之三轴加速度6

知识点&#xff1a;什么是掌控板&#xff1f; 掌控板是一块普及STEAM创客教育、人工智能教育、机器人编程教育的开源智能硬件。它集成ESP-32高性能双核芯片&#xff0c;支持WiFi和蓝牙双模通信&#xff0c;可作为物联网节点&#xff0c;实现物联网应用。同时掌控板上集成了OLED…

htmlCSS-----定位

目录 前言 定位 分类和取值 定位的取值 1.相对定位 2.绝对位置 元素居中操作 3.固定定位 前言 今天我们来学习html&CSS中的元素的定位&#xff0c;通过元素的定位我们可以去更好的将盒子放到我们想要的位置&#xff0c;下面就一起来看看吧&#xff01; 定位 定位posi…

rust 闭包函数

函数有自己的类型&#xff0c;可以像使用基础类型一样使用函数&#xff0c;包括将函数保存在变量中、保存在 vec 中、声明在结构体成员字段中。闭包函数也是函数&#xff0c;也有自己的类型定义。不过&#xff0c;函数实际上是指针类型&#xff0c;在 rust 所有权中属于借用的关…

Tomcat修改端口号

网上的教程都比较老&#xff0c;今天用tomcat9.0记录一下 conf文件夹下server.xml文件 刚开始改了打红叉的地方&#xff0c;发现没用&#xff0c;改了上面那行

SpringBoot百货超市商城系统 附带详细运行指导视频

文章目录 一、项目演示二、项目介绍三、运行截图四、主要代码 一、项目演示 项目演示地址&#xff1a; 视频地址 二、项目介绍 项目描述&#xff1a;这是一个基于SpringBoot框架开发的百货超市系统。首先&#xff0c;这是一个很适合SpringBoot初学者学习的项目&#xff0c;代…

Beyond Compare和git merge、git rebase

文章目录 各个分支线将dev1 rebase进 dev2将dev1 merge进dev2 各个分支线 将dev1 rebase进 dev2 gitTest (dev2)]$ git rebase dev1local: 是rebase的分支dev1remote&#xff1a;是当前的分支dev2base&#xff1a;两个分支的最近一个父节点 将dev1 merge进dev2 gitTest (dev…

json-server创建静态服务器2

上次写的 nodejs创建静态服务器 这次再来个v2.0 利用json-server很方便就可以实现。 vscode打开文件夹&#xff0c;文件夹所在终端&#xff1a; json-server.cmd --watch db.json 这里视频教程是没有上述命令标红的&#xff0c;但是会报错&#xff0c;具体不详&#xff0c…

uniapp小程序自定义loding,通过状态管理配置全局使用

一、在项目中创建loding组件 在uniapp的components文件夹下创建loding组件&#xff0c;如图&#xff1a; 示例代码&#xff1a; <template><view class"loginLoading"><image src"../../static/loading.gif" class"loading-img&q…

SpringBoot环境标识设置及nacos匹配配置

本地环境标识设置 本地父类maven配置 可以看到相关的分类&#xff0c;设置环境标识主要需要用到profiles; <profiles><profile><id>dev</id><properties><!-- 环境标识&#xff0c;需要与配置文件的名称相对应 --><profiles.active&…

用html+javascript打造公文一键排版系统9:主送机关排版

一、主送机关的规定 公文一般在标题和正文之间还有主送机关&#xff0c;相关规定为&#xff1a; 主送机关 编排于标题下空一行位置&#xff0c;居左顶格&#xff0c;回行时仍顶格&#xff0c;最后一个机关名称后标全角冒号。如主送机关名称过多导致公文首页不能显示正文时&…

c刷题(一)

目录 1.输出100以内3的倍数 2.将3个数从大到小输出 3.打印100~200素数 方法一 方法二 4.显示printf的返回值 最大公约数 试除法 辗转相除法 九九乘法表 求十个数的最大值 1.输出100以内3的倍数 法一&#xff1a; int n 0; while (n*3 < 100){printf("%d &q…

基于物联网、视频监控与AI视觉技术的智慧电厂项目智能化改造方案

一、项目背景 现阶段&#xff0c;电力行业很多企业都在部署摄像头对电力巡检现场状况进行远程监控&#xff0c;但是存在人工查看费时、疲劳、出现问题无法第一时间发现等管理弊端&#xff0c;而且安全事件主要依靠人工经验判断分析、管控&#xff0c;效率十分低下。 为解决上述…

基于双 STM32+FPGA 的桌面数控车床控制系统设计

桌 面数控 设 备 对 小 尺寸零件加工在成 本 、 功 耗 和 占 地 面 积等方 面有 着 巨 大 优 势 。 桌 面数控 设 备 大致 有 3 种 实 现 方 案 : 第 一种 为 微 型 机 床搭 配 传统 数控系 统 &#xff0c; 但 是 桌 面数控 设 备 对 成 本 敏感 ; 第二 种 为 基 于 PC…

使用Flutter的image_picker插件实现设备的相册的访问和拍照

文章目录 需求描述Flutter插件image_picker的介绍使用步骤1、添加依赖2、导入 例子完整的代码效果 总结 需求描述 在应用开发时&#xff0c;我们有很多场景要使用到更换图片的功能&#xff0c;即将原本的图像替换设置成其他的图像&#xff0c;从设备的相册或相机中选择图片或拍…

使用serverless实现从oss下载文件并压缩

公司之前开发一个网盘系统, 可以上传文件, 打包压缩下载文件, 但是在处理大文件的时候, 服务器遇到了性能问题, 主要是这个项目是单机部署.......(离谱), 然后带宽只有100M, 现在用户比之前多很多, 然后所有人的压缩下载请求都给到这一台服务器了, 比如多个人下载的时候带宽问…

tensorRT多batch动态推理

tensorRT的多batch推理&#xff0c;导出的onnx模型必须是动态batch&#xff0c;只需在导出的时候&#xff0c;设置一个dynamic_axis参数即可。 torch.onnx.export(hybrik_model, dummy_input, "./best_model.onnx", verboseTrue, input_namesinput_names, \output_…

计算机基本硬件的内部结构

1.早期冯诺依曼机结构 世界上第一台计算机ENIAC是使用手动接线来控制计算&#xff0c;十分麻烦。 冯诺依曼提出“存储程序”的概念&#xff0c;是指将指令以二进制代码的形式事先输入计算机的主存储器&#xff08;内存&#xff09;&#xff0c;然后按照其在存储器中的首地址执…