效果如下:

说明:

实现sku的方式一般采用在获取到数据后拆分所有条件的可能性,实现方式参考js的实现,代码如下:

SkuHelp.kt

/**
不考虑服务端的格式类型,将对应格式翻译成如下格式:*[* { "颜色": "红", "尺码": "大", "型号": "A", "skuId": "3158055" },* { "颜色": "白", "尺码": "大", "型号": "A", "skuId": "3158054" },* { "颜色": "白", "尺码": "中", "型号": "B", "skuId": "3133859" },* { "颜色": "蓝", "尺码": "小", "型号": "C", "skuId": "3516833" }*](注:skuId为数据id)
**/
class SkuHelp {private val skuArray = "skuArray"// 将数据翻译为sku可识别的通用数据,此处为特殊处理方式fun transformToBean(goodsSpecArray: List<SpecificationsBean>): Pair<Map<String, String>, List<Map<String, String>>> {val rootArray = mutableListOf<MutableMap<String, String>>()val tagImgMap = mutableMapOf<String, String>()goodsSpecArray.forEach { bean ->if (bean.id != null && bean.goodsSpecValue != null) {val valueMap = bean.goodsSpecValue.mapFromJson<String, String>()valueMap["id"] = bean.id// 处理数据中有图片的现象valueMap.forEach { (k, v) ->// 判断value上是否有图片存在if (v.contains(",")) {val splitArray = v.split(',')valueMap[k] = splitArray[0]tagImgMap[splitArray[0]] = splitArray[1]}}rootArray.add(valueMap)}}return Pair(tagImgMap, rootArray)}/*** rootArray 格式为:*[* { "颜色": "红", "尺码": "大", "型号": "A", "skuId": "3158055" },* { "颜色": "白", "尺码": "大", "型号": "A", "skuId": "3158054" },* { "颜色": "白", "尺码": "中", "型号": "B", "skuId": "3133859" },* { "颜色": "蓝", "尺码": "小", "型号": "C", "skuId": "3516833" }*]*/fun initData(rootArray: List<Map<String, String>>): Triple<Map<String, List<String>>, Map<String, Map<String, List<String>>>, List<String>> {val keyArray = getSkuKey(rootArray)val (allKeyArray, resultMap) = combineAttr(rootArray, keyArray)val conditionMap = buildResult(allKeyArray)return Triple(resultMap, conditionMap, keyArray)}/*** 处理合并后的条件会有多个特殊字符*/fun trimSplit(trim: String): String {// ⊙abc⊙ => abc// ⊙a⊙⊙b⊙c⊙ => a⊙b⊙cval reLeft = Regex("^$spliter+")val reRight = Regex("$spliter+\$")val reSplit = Regex("$spliter+")return trim.replace(reLeft, "").replace(reRight, "").replace(reSplit, spliter)}// 获取条件skufun getSkuArray(condition: String, conditionMap: Map<String, Map<String, List<String>>>): List<String>? {val newMap = conditionMap[condition]return newMap?.get(skuArray)}// 获取key, 如:颜色,品牌,尺码private fun getSkuKey(rootArray: List<Map<String, String>>): List<String> {val keyArray = mutableListOf<String>()if (rootArray.isNotEmpty()) {val valueMap = rootArray[0]valueMap.forEach { (k, _) ->// 过滤数据为id的项if (k == "id") {return@forEach}keyArray.add(k)}}return keyArray}val spliter = "\u2299"// 计算组合数据private fun combineAttr(rootArray: List<Map<String, String>>,keyArray: List<String>): Pair<List<MutableMap<String, String>>, MutableMap<String, MutableList<String>>> {// 将数据转换成可视化数据,通过id查找对应数据/*** 为了通用,此处将数据打包成如下格式:[{ "颜色": "红", "尺码": "大", "型号": "A", "skuId": "3158055" },{ "颜色": "白", "尺码": "大", "型号": "A", "skuId": "3158054" },{ "颜色": "白", "尺码": "中", "型号": "B", "skuId": "3133859" },{ "颜色": "蓝", "尺码": "小", "型号": "C", "skuId": "3516833" }]*/
//        val beanArray = mutableListOf<MutableMap<String, String>>()
//        goodsSpecArray.forEach { bean ->
//            if (bean.id != null && bean.goodsSpecValue != null) {//                val valueMap = bean.goodsSpecValue.mapFromJson<String, String>()
//                valueMap["id"] = bean.id
//                beanArray.add(valueMap)
//            }
//        }// 将界面展示数据分离如: {"颜色":["红","白","蓝"],"尺码":["大","中","小"],"型号":["A","B","C"]}val resultMap = mutableMapOf<String, MutableList<String>>()// 将条件与id整合起来如:[{{path=红⊙大⊙A, sku=3158055}...}]val allKeyArray = mutableListOf<MutableMap<String, String>>()for (itemMap in rootArray) {val valueArray = mutableListOf<String>()for (key in keyArray) {val array = mutableListOf<String>()if (resultMap[key] != null) {array.addAll(resultMap[key]!!)}if (!array.contains(itemMap[key])) {array.add(itemMap[key]!!)}resultMap[key] = arrayvalueArray.add(itemMap[key]!!)}allKeyArray.add(mutableMapOf("path" to valueArray.joinToString(separator = spliter),"sku" to itemMap["id"]!!))}return Pair(allKeyArray, resultMap)}// 合并所有条件private fun getAllKeys(allKeyArray: List<Map<String, String>>): List<String> {// 如: ["红⊙大⊙A",...]val keyArray = mutableListOf<String>()allKeyArray.forEach { keyMap ->keyArray.add(keyMap["path"]!!)}return keyArray}// 生成所有子集是否可选、库存状态 mapprivate fun buildResult(allKeyArray: List<Map<String, String>>): MutableMap<String, MutableMap<String, MutableList<String>>> {// 将条件整合成一个key的Listval allKeys = getAllKeys(allKeyArray)// 获取所有数据的可能性如: {"蓝色⊙X"={skuArray=[1, 2, 3]}...}val resMap = mutableMapOf<String, MutableMap<String, MutableList<String>>>()allKeys.forEachIndexed { index, allKey ->val sku = allKeyArray[index]["sku"]val values = allKey.split(spliter)val allSets = powerSet(values)// 每个组合的子集allSets.forEachIndexed { _, set ->val key = set.joinToString(separator = spliter)if (resMap[key] != null) {resMap[key]?.get(skuArray)?.add(sku!!)} else {resMap[key] = mutableMapOf(skuArray to mutableListOf(sku!!))}}}return resMap}/*** 取得集合的所有子集「幂集」arr = [1,2,3]i = 0, ps = [[]]:j = 0; j < ps.length => j < 1:i=0, j=0 ps.push(ps[0].concat(arr[0])) => ps.push([].concat(1)) => [1]ps = [[], [1]]i = 1, ps = [[], [1]] :j = 0; j < ps.length => j < 2i=1, j=0 ps.push(ps[0].concat(arr[1])) => ps.push([].concat(2))  => [2]i=1, j=1 ps.push(ps[1].concat(arr[1])) => ps.push([1].concat(2)) => [1,2]ps = [[], [1], [2], [1,2]]i = 2, ps = [[], [1], [2], [1,2]]j = 0; j < ps.length => j < 4i=2, j=0 ps.push(ps[0].concat(arr[2])) => ps.push([3])    => [3]i=2, j=1 ps.push(ps[1].concat(arr[2])) => ps.push([1, 3]) => [1, 3]i=2, j=2 ps.push(ps[2].concat(arr[2])) => ps.push([2, 3]) => [2, 3]i=2, j=3 ps.push(ps[3].concat(arr[2])) => ps.push([2, 3]) => [1, 2, 3]ps = [[], [1], [2], [1,2], [3], [1, 3], [2, 3], [1, 2, 3]]*/private fun powerSet(set: List<String>): List<List<String>> {//已知所求集合的幂集会有2^n个元素val size = 2 shl set.sizeval powerSet: MutableList<List<String>> = ArrayList(size)//首先空集肯定是集合的幂集powerSet.add(Collections.emptyList())for (element in set) {//计算当前元素与已存在幂集的组合val preSize = powerSet.sizefor (i in 0 until preSize) {val combineSubset: MutableList<String> = ArrayList(powerSet[i])combineSubset.add(element)powerSet.add(combineSubset)}}return powerSet}
}

SpecificationsDialog.kt

/*** 商品规格选择sku*/
class SpecificationsDialog(private val contextX: Context) :AlertDialog(contextX, R.style.DialogWindowStyle_Shop_bg) {private val binding: PdDialogLayoutSpecificationsBinding =PdDialogLayoutSpecificationsBinding.inflate(layoutInflater)private val adapter: SpecificationsDialogAdapter = SpecificationsDialogAdapter()// 存储有库存,以id建立的字典方便为了获取具体数据private val mapModel = mutableMapOf<String, SpecificationsBean>()// 需要满足的条件总数private var totalConditions = 0// adapter数据源private val skuBeanArray = mutableListOf<ShowSKUBean>()// 缓存之前存储的条件 - 选中条件,key是(如:品牌:x1), value是goodsSpecValueprivate val cacheValueMap = LinkedHashMap<String, String>()// =private val cacheConditionMap: MutableMap<String, Map<String, List<String>>> = mutableMapOf()// sku 列的名称,如:品牌,..private val keysArray: MutableList<String> = mutableListOf()// 具体sku数据,用于提交private var cacheModel: SpecificationsBean? = nullprivate val skuHelp = SkuHelp()override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(binding.root)initDefWindows()initView()initTestData()binding.pdTvStock.text = "1000"}// 重置所有属性private fun resetData() {cacheModel = nulltotalConditions = 0keysArray.clear()cacheConditionMap.clear()cacheValueMap.clear()skuBeanArray.clear()mapModel.clear()}// 初始化数据fun initData(beanArray: List<SpecificationsBean>) {resetData()// 将数据转换成map,用id对应数据方便查找beanArray.forEachIndexed { index, bean ->if (index == 0) {cacheModel = bean}mapModel[bean.id!!] = bean}val (tagImgMap, goodsSpecMap) = skuHelp.transformToBean(beanArray)val (uiMap, conditionMap, keysArray) = skuHelp.initData(goodsSpecMap)this.keysArray.addAll(keysArray)// 需要满足的条件总数totalConditions = uiMap.sizeuiMap.forEach { (k, vList) ->val dvList = vList.map { v ->if (tagImgMap[v] == null) {DialogSpecsValue(value = v)} else {DialogSpecsValue(value = v, img = tagImgMap[v])}}skuBeanArray.add(ShowSKUBean(k, dvList))}cacheConditionMap.putAll(conditionMap)defSelected()adapter.setList(skuBeanArray)}// 默认选中数据中第一条private fun defSelected() {cacheModel?.let { bean ->if (bean.goodsSpecValue == null) return@letval valueMap = bean.goodsSpecValue.mapFromJson<String, String>()keysArray.forEach { key ->val value = valueMap[key] ?: return@forEachif (value.contains(",")) {val splitArray = value.split(',')cacheValueMap[key] = splitArray[0]} else {cacheValueMap[key] = value}}defSettingSelected()}}// TODO 暂时用于测试,部分逻辑可以通用// 模拟数据private fun initTestData() {val goodsSpecArray = mutableListOf(SpecificationsBean("1","0",goodsDefaluePrice = 100f,goodsPrice = 85.6f,goodsNums = 10000,goodsSpecValue = "{\"颜色\":\"蓝色\",\"尺码\":\"X\",\"风格\":\"时尚\"}"),SpecificationsBean("2","0",goodsDefaluePrice = 100f,goodsPrice = 87.2f,goodsNums = 10000,goodsSpecValue = "{\"颜色\":\"蓝色\",\"尺码\":\"X\",\"风格\":\"简约\"}"),SpecificationsBean("3","0",goodsDefaluePrice = 100f,goodsPrice = 87.2f,goodsNums = 10000,goodsSpecValue = "{\"颜色\":\"蓝色\",\"尺码\":\"X\",\"风格\":\"欧式\"}"),SpecificationsBean("4","0",goodsDefaluePrice = 100f,goodsPrice = 67.2f,goodsNums = 10000,goodsSpecValue = "{\"颜色\":\"蓝色\",\"尺码\":\"M\",\"风格\":\"简约\"}"),SpecificationsBean("5","0",goodsDefaluePrice = 100f,goodsPrice = 57.2f,goodsNums = 10000,goodsSpecValue = "{\"颜色\":\"蓝色\",\"尺码\":\"M\",\"风格\":\"欧式\"}"),SpecificationsBean("6","0",goodsDefaluePrice = 100f,goodsPrice = 87.2f,goodsNums = 10000,goodsSpecValue = "{\"颜色\":\"红色\",\"尺码\":\"X\",\"风格\":\"时尚\"}"),SpecificationsBean("7","0",goodsDefaluePrice = 100f,goodsPrice = 83.2f,goodsNums = 10000,goodsSpecValue = "{\"颜色\":\"红色\",\"尺码\":\"M\",\"风格\":\"时尚\"}"),)resetData()// 将数据转换成map,用id对应数据方便查找goodsSpecArray.forEachIndexed { index, bean ->if (index == 0) {cacheModel = bean}mapModel[bean.id!!] = bean}val (tagImgMap, goodsSpecMap) = skuHelp.transformToBean(goodsSpecArray)val (uiMap, conditionMap, keysArray) = skuHelp.initData(goodsSpecMap)this.keysArray.addAll(keysArray)// 需要满足的条件总数totalConditions = uiMap.sizeuiMap.forEach { (k, vList) ->val dvList = vList.map { v ->if (tagImgMap[v] == null) {DialogSpecsValue(value = v)} else {DialogSpecsValue(value = v, img = tagImgMap[v])}}skuBeanArray.add(ShowSKUBean(k, dvList))}cacheConditionMap.putAll(conditionMap)defSelected()adapter.setList(skuBeanArray)}private fun initView() {binding.pdItvBack.setOnClickListener { dismiss() }binding.pdRvSpecs.adapter = adapterval layoutManager = LinearLayoutManager(contextX)layoutManager.orientation = LinearLayoutManager.VERTICALbinding.pdRvSpecs.layoutManager = layoutManageradapter.setItemClick { holder, p, dialogSpecsValue, key ->// 将选中条件缓存if (!dialogSpecsValue.isSelect) {cacheValueMap[key] = dialogSpecsValue.value} else {cacheValueMap.remove(key)}settingSelected(key, dialogSpecsValue)adapter.notifyDataSetChanged()// 判断是否需要给出价格if (this.totalConditions == cacheValueMap.size) {val synthesisKey = obtainConditionSplicing()val valueArray = skuHelp.getSkuArray(synthesisKey, cacheConditionMap)var id = ""if (valueArray.isNullOrEmpty()) {"无此属性搭配".showToast()return@setItemClick} else {id = valueArray[0]}cacheModel = mapModel[id]Log.e("fyc", " bean >>>> $cacheModel")} else {cacheModel = null}}}private fun initDefWindows() {setCanceledOnTouchOutside(true)setCancelable(true)val localWindow = this.windowlocalWindow?.setWindowAnimations(R.style.DialogWindowStyle)localWindow?.setGravity(Gravity.BOTTOM)localWindow?.setBackgroundDrawableResource(android.R.color.transparent)val lp = localWindow!!.attributesval wh =intArrayOf(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)lp.width = wh[0]lp.height = wh[1]localWindow.attributes = lp}// 更新所有属性状态private fun updateStatus() {val cacheValueArray = mutableListOf<String>()cacheValueMap.forEach { (_, v) ->cacheValueArray.add(v)}skuBeanArray.forEachIndexed { i, bean ->val copyArray = Array(totalConditions){""}for (z in 0..totalConditions) {if (z < cacheValueArray.size) {copyArray[z] = cacheValueArray[z]}}bean.valueList.forEach valueTag@ { valueBean ->// 选中项,忽略if (cacheValueMap[bean.key] == valueBean.value) {// continuereturn@valueTag}Log.e("fyc", "${bean.key} :  ${valueBean.value}")copyArray[i] = valueBean.value// 合并成条件val conditionKey = skuHelp.trimSplit(copyArray.joinToString(separator = skuHelp.spliter))// 获取合适的条件if (cacheConditionMap[conditionKey] == null) {valueBean.isCanSelect = falsevalueBean.isSelect = false} else {valueBean.isCanSelect = truevalueBean.isSelect = false}}}}// 设置选中项 - item点击事件private fun settingSelected(key: String, bean: DialogSpecsValue) {// 遍历现有数据判断可选项skuBeanArray.forEach { model ->// 判断当前项目是否具备选中项val selectionVal = cacheValueMap[model.key]model.valueList.forEach { dialogSpecsValue ->// 表示当前项目被选中if (selectionVal == dialogSpecsValue.value) {dialogSpecsValue.isSelect = true}// 当前选中项,是无效选项触发if (selectionVal == dialogSpecsValue.value && !dialogSpecsValue.isCanSelect) {cacheValueMap.clear()cacheValueMap[key] = bean.valuedialogSpecsValue.isSelect = truedialogSpecsValue.isCanSelect = true}}}// 对当前条件进行排序,防止出现,选中组合混乱val selectMap = LinkedHashMap<String, String>()keysArray.forEach { dataKey ->if (cacheValueMap[dataKey] != null) {selectMap[dataKey] = cacheValueMap[dataKey]!!}}cacheValueMap.clear()cacheValueMap.putAll(selectMap)updateStatus()}// 设置选中项private fun defSettingSelected() {// 遍历现有数据判断可选项skuBeanArray.forEach { model ->// 判断当前项目是否具备选中项val selectionVal = cacheValueMap[model.key]model.valueList.forEach { dialogSpecsValue ->// 表示当前项目被选中if (selectionVal == dialogSpecsValue.value) {dialogSpecsValue.isSelect = true}}}updateStatus()}// 将选中的条件组成和实际可选条件获取keyprivate fun obtainConditionSplicing(): String {val splicingArray = mutableListOf<String>()cacheValueMap.forEach { (_, v) ->splicingArray.add(v)}return splicingArray.joinToString(separator = skuHelp.spliter)}
}

SpecificationsBean.kt

/*** 商铺规格*/
@JsonClass(generateAdapter=true)
data class SpecificationsBean(// 商品规格idval id: String?,// 商品idval goodsId: String?,// 商品规格值val goodsSpecValue: String?,// 商品价格val goodsPrice: Float?,// 商品原价val goodsDefaluePrice: Float?,// 商品数量 - 库存val goodsNums: Int?
)

ShowSKUBean.kt

/*** 将map数据转换成rv可识别的数据*/
data class ShowSKUBean(// 如:品牌val key: String,// 如:x1,x2...val valueList: List<DialogSpecsValue>
)

DialogSpecsValue.kt

/*** dialog-sku展示项*/
data class DialogSpecsValue(// 是否能选择var isCanSelect: Boolean = true,// 是否能选中var isSelect: Boolean = false,var value: String = "",// 对应数据图片,没有可为空var img: String? = null
)

总结:代码比较多,需要给位耐心看完,可以实现图片中一样的效果,懒得废话太多,sku实现的思路并不复杂,麻烦的是如何写算法处理,此处只展示一种sku实现方式,这种方式的弊端是吃内存,但至少效果可以实现,一般来讲后端会对应作出限制,理论上内存这块不用太过于担心,由于我的数据中如:‘颜色:蓝色,/static/x/x.jpg’ value可能携带图片此处处理方式是针对于需要图片的方式,如果不需要处理图片,直接将图片处理的代码删除即可,希望本文对大家有所帮助。

Android 实现SKU选择通用方式相关推荐

  1. Android文件实现选择打开方式

      对于Android的一些文件,假如我们要实现和电脑上选择打开方式的话,Android上面提供了比较简便的方式. 首先获取文件类型,即MIME类型 MIME类型表后面附上 /** * 根据文件后缀名 ...

  2. 怎样进入android模式,安卓手机如何进入Recovery模式的通用方式详解

    2014-12-12 15:24:16 安卓手机如何进入Recovery模式的通用方式详解 标签:安卓 Recovery模式 教程 Recovery模式是什么?这里说的Recovery模式主要指的是安 ...

  3. android 电影筛选,自己造轮子--android常用多条件筛选菜单实现思路(类似美团,爱奇艺电影票下拉菜单),--android电影票,选择实现方式若是看到第一...

    自己造轮子--android常用多条件筛选菜单实现思路(类似美团,爱奇艺电影票下拉菜单),--android电影票,选择实现方式若是看到第一 就在昨天,我的一个项目DropDownMenu被daima ...

  4. Pandas简明教程:五、Pandas简单统计操作及通用方式

    文章目录 1.DataFrame的方法使用举例 2.DataFrame的方法调用通用方式 3.DataFrame直接调用其它方法 本系列教程教程完整目录: 前面已经提到,Pandas的DataFram ...

  5. ASP.NET MVC传递Model到视图的多种方式之通用方式的使用

    ASP.NET MVC传递Model到视图的多种方式总结--通用方式的使用 有多种方式可以将数据传递到视图,如下所示: ViewData ViewBag PartialView TempData Vi ...

  6. android+双卡imei,以编程方式在Android中为双SIM卡检索IMEI号码

    对于单个SIM,以下代码有效: TelephonyManager tm = (TelephonyManager)getSystemService(TELEPHONY_SERVICE); String ...

  7. Android逆向之旅---动态方式破解apk进阶篇(IDA调试so源码)

    一.前言 今天我们继续来看破解apk的相关知识,在前一篇:Eclipse动态调试smali源码破解apk 我们今天主要来看如何使用IDA来调试Android中的native源码,因为现在一些app,为 ...

  8. 【Android取证篇】Android设备USB调试打开方式(开发者模式)

    [Android取证篇]Android设备USB调试打开方式(开发者模式) Android各个版本系统手机开启"USB调试"的入口不全相同,仅供参考-[蘇小沐] 1.[Androi ...

  9. Android逆向之旅---动态方式破解apk前奏篇(Eclipse动态调试smail源码)

    一.前言 今天我们开始apk破解的另外一种方式:动态代码调试破解,之前说的主要采用的是静态方式,步骤也很简单,首先使用apktool来反编译apk,得到smail源码,然后分析smail代码,采用代码 ...

最新文章

  1. 关于 ulimit -SHn 65535
  2. Github 总结!「Java知识体系详细汇总2021版」开放下载了!
  3. MPB:南农成艳芬组-瘤胃微生物体外发酵过程与注意事项
  4. 2018/12/08 L1-042 日期格式化 Java
  5. jfinal框架中后台获取前端传递的参数
  6. 前端windows下常用的CMD 命令归纳
  7. css 设置背景图片铺满固定不动
  8. python头文件库_跟老齐学Python之不要红头文件(1)
  9. 38. 遵循按照值传递的原则来设计函数子类
  10. 开源BI工具 - Superset
  11. P2P技术体系结构与分类
  12. android拍照文件没有读写权限,Android6.0之后的拍照+本地存储权限问题
  13. python元组的定义方式_python基础之元组(Tuple)、字典(Dictionary)详解
  14. 面纱星云的西面纱中的女巫帚星云
  15. WorkNC如何创建夹具系统 (以虎钳为例)
  16. 计算机导论模板,计算机导论论文提纲范文模板 计算机导论论文大纲怎样写
  17. 17、简单的企业人事管理系统(ssh)
  18. 数据结构[1]--学习--绪论(学习记录)
  19. 应对word无法编辑、无法选中、无法删除的方法
  20. 十大家用投影仪排名,什么牌子的投影仪质量最好?

热门文章

  1. CSS 去掉点li 的点
  2. VS2015生成的exe文件在其他电脑下运行
  3. ApkScan-PKID 查壳工具下载使用以及相关技术介绍
  4. 数据挖掘Task 5: 模型融合
  5. 用java输出出生日期,生成随机出生日期
  6. oracle vitu,Podnikové servery | Oracle Česká Republika
  7. asp毕业设计——基于asp+access的学生排课管理系统设计与实现(毕业论文+程序源码)——学生排课管理系统
  8. 用html写箭头的原理,5个编写高质量箭头函数的最佳实践
  9. JS中常用的判断函数
  10. 求旋转体体积表面积时的dx,ds问题的简单解释