Android级联选择器,下拉菜单

近期android开发,遇到的需求,分享二个android可能用到的小组件

**下拉选择器:**它的实现,主要是需要监听它依附的组件当前距离屏幕顶端的位置。

在显示下拉菜单中,如果需要点击上面有响应。可通过activity拿到decorview(activity.window.decorView),然后把下拉的view添加到decorView,然后设置该菜单距离顶端的上边距。如果需要先点击屏幕让菜单先消失,可搞一个全屏的下拉菜单,距顶端高度搞一个透明的view来填充(设置一个点击事件,处理decorView.remove该下拉组件)

Kotlin 复制代码
object DropDownItemsUtils {
    const val DROP_DOWN_ID = -1000
    fun showDropDownView(activity: Activity,marginTopPx:Float,list:List<String>,selectIndex:Int = -1,onItemClick: ((pos: Int?) -> Unit)?=null){
        val decorView = activity.window.decorView as ViewGroup
        decorView.findViewById<View>(DROP_DOWN_ID)?.let {
            decorView.removeView(it)
        }
        val composeView = ComposeView(activity).apply {
            setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
            setContent{
                DropDownItems(modifier = Modifier.fillMaxWidth().wrapContentHeight(), list = list, selectIndex = selectIndex) {
                    decorView.removeView(this@apply)
                    onItemClick?.invoke(it)
                }
            }
            id = DROP_DOWN_ID
        }
        val lp = FrameLayout.LayoutParams(-1,-1)
        lp.topMargin = marginTopPx.toInt()
        decorView.addView(composeView,lp)
    }

    fun dismissDropDownView(activity: Activity){
        val decorView = activity.window.decorView as ViewGroup
        decorView.findViewById<View>(DROP_DOWN_ID)?.let {
            decorView.removeView(it)
        }
    }
}

**级联选择器:**它的承载容器是一个底部弹窗的 DialogFragment,显示的内容目前我用compose实现,已选的级联它横向显示采用的LazyRow,列表项使用LazyColumn实现。具体实现如下。

Kotlin 复制代码
data class LevelsItem(
    val text:String,
    var list:List<LevelsItem>? = null,
    var parentId:String? = null,
    var level:Int? = null,
    val tag:Any? = null //LevelsItem,是从服务端给的数据模型转的,实际可以再转化的时候,将该tag    
                        //设置为服务端给的模型.如下我转换的代码
)
/**
    fun convertMotorcadeRespToLevelsItem(list: List<MotorcadeResp>): List<LevelsItem> {
        return list.map { motorcadeResp ->
            LevelsItem(
                text = motorcadeResp.name ?: "", // Map name to text
                parentId = motorcadeResp.parentId,
                level = motorcadeResp.departmentLevel,
                list = motorcadeResp.childDepartment?.let { convertMotorcadeRespToLevelsItem(it) }, // Recursively map childDepartment
                tag = motorcadeResp // Set the original MotorcadeResp as tag
            )
        }
    }
*/
Kotlin 复制代码
/**
 * 级联选择,可以自行选择。自行点击确定,完成选择成功
 */
class LevelsSelector1Dialog : BottomDialogFragment() {
    private val SELECTSTR: String = "-10000000"

    var title: String? = null

    var list: List<LevelsItem> = ArrayList()

    var onConfirm: ((LevelsItem?) -> Unit)? = null

    //用来横向显示tab项列表的item
    private var currentSelectItem: SnapshotStateList<LevelsItem> = mutableStateListOf()
    //记录当前选择的值,最后用来点击确定,来回调给调用方使用的
    private var currentSelectItemVal: SnapshotStateList<LevelsItem> = mutableStateListOf()


    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
        return ComposeView(requireContext()).apply {
            setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
            setContent {
                WelcomeAssistantTheme {
                    LevelsSelectorScreen()
                }
            }
        }
    }


    @Preview(widthDp = 375, heightDp = 812)
    @Composable
    private fun LevelsSelectorScreen() {
        currentSelectItem.clear()
        //横向tab,添加一个默认项 "请选择"
        currentSelectItem.add(LevelsItem(SELECTSTR, list))
        val height =
            (0.64f * requireContext().resources.displayMetrics.heightPixels) / LocalDensity.current.density

        Box(
            modifier = Modifier
                .fillMaxWidth()
                .height(height.dp)
                .background(
                    color = Color.White,
                    shape = RoundedCornerShape(
                        topStart = 24.dp,
                        topEnd = 24.dp,
                        bottomStart = 0.dp,
                        bottomEnd = 0.dp
                    )
                )
                .padding(16.dp)
        ) {
            Image(
                painterResource(R.mipmap.ic_levels_selector_close),
                modifier = Modifier
                    .clickable {
                        dismiss()
                    }
                    .size(24.dp)
                    .align(Alignment.TopEnd),
                contentDescription = null
            )
            Text(
                modifier = Modifier
                    .padding(top = 24.dp, bottom = 16.dp)
                    .align(Alignment.TopCenter),
                text = title ?: "",
                style = buildMSDBTextStyle(fontSize = 20.sp, color = ff0A1733)
            )
            val listItems = remember {
                mutableStateOf(list)
            }
            val currentSelectTabIndex = remember {
                mutableIntStateOf(0)
            }
            LazyRow(
                modifier = Modifier
                    .padding(top = 68.dp)
                    .fillMaxWidth()
                    .height(30.dp),
            ) {
                itemsIndexed(currentSelectItem) {index, selectItem ->
                    Column {
                        if (SELECTSTR == selectItem.text) {
                            Text(
                                modifier = Modifier.padding(end = 24.dp).singleClickable {
                                    if(currentSelectTabIndex.intValue == index){
                                        return@singleClickable
                                    }
                                    currentSelectTabIndex.intValue = index
                                    listItems.value =
                                        currentSelectItem[index - 1].list ?: ArrayList()
                                },
                                text = stringResource(R.string.levels_select),
                                style = buildMSDNTextStyle(color = FF9DA2AD, fontSize = 16.sp)
                            )
                        } else {
                            Text(
                                modifier = Modifier
                                    .padding(end = 24.dp)
                                    .widthIn(max = 110.dp)
                                    .singleClickable {
                                        if(currentSelectTabIndex.intValue == index){
                                            return@singleClickable
                                        }
                                        if (index >= 1) {
                                            listItems.value =
                                                currentSelectItem[index - 1].list ?: ArrayList()
                                        } else {
                                            listItems.value =
                                                currentSelectItem[currentSelectItem.size - 1].list
                                                    ?: ArrayList()
                                        }
                                        currentSelectTabIndex.intValue = index
                                    },
                                text = selectItem.text,
                                style = buildMSDBTextStyle(
                                    color = ff0A1733,
                                    fontSize = 16.sp,
                                ),
                                overflow = TextOverflow.Ellipsis,
                                maxLines = 1
                            )
                        }
                        if(currentSelectTabIndex.intValue == index) {
                            Box(
                                modifier = Modifier
                                    .align(Alignment.CenterHorizontally)
                                    .padding(end = 24.dp)
                                    .height(3.dp)
                                    .width(24.dp)
                                    .background(color = FF306DF4, shape = RoundedCornerShape(3.dp))
                            )
                        }
                    }
                }
            }

            LazyColumn(
                modifier = Modifier
                    .padding(top = 108.dp, bottom = 70.dp)
                    .fillMaxSize()
            ) {
                var currentLevelSelectItem: LevelsItem? = null
                itemsIndexed(listItems.value) {i, levelsItem ->
                    var isAlreadySelectItem = currentSelectItemVal.contains(levelsItem)
                    if (isAlreadySelectItem) {
                        currentLevelSelectItem = levelsItem
                    }
                    Row(modifier = Modifier
                        .fillMaxWidth()
                        .padding(vertical = 12.dp)
                        .singleClickable {
                            //子item会有很多,可以判断当前item的父id是否相同
                            val index = currentSelectItem.indexOfFirst {
                                it.parentId == levelsItem.parentId
                            }
                            //可选项是否还有子列表
                            if (levelsItem.list.isNullOrEmpty()) {
                                //级联最后的那个列表
                                if (currentLevelSelectItem != null) {
                                    currentSelectItemVal.removeRange(currentSelectItemVal.indexOf(currentLevelSelectItem),currentSelectItemVal.size)
                                }
                                isAlreadySelectItem = true
                                if(index > 0){
                                    currentSelectItem.removeAt(index)
                                }
                                currentSelectItemVal.add(levelsItem)
                            } else {
                                //可以点出下一个级联
                                if (index < 0) {
                                    currentSelectItem.add(
                                        currentSelectItem.size - 1,
                                        levelsItem
                                    )
                                } else {
                                    currentSelectItem.removeAt(index)
                                    currentSelectItem.add(index,levelsItem)
                                }
                                currentSelectTabIndex.intValue += 1
                                listItems.value = levelsItem.list ?: ArrayList()

                                if (!isAlreadySelectItem) {
                                    if (currentLevelSelectItem != null) {
                                        currentSelectItemVal.removeRange(
                                            currentSelectItemVal.indexOf(
                                                currentLevelSelectItem
                                            ), currentSelectItemVal.size
                                        )
                                    }
                                    currentSelectItemVal.add(levelsItem)
                                }
                            }
                        }) {

                        Row(modifier = Modifier.fillMaxWidth()) {
                            Text(
                                modifier = Modifier.weight(1f),
                                text = levelsItem.text,
                                style = buildMSDNTextStyle(
                                    color = if (isAlreadySelectItem) FF306DF4 else ff0A1733,
                                    fontSize = 16.sp
                                )
                            )
                            if (isAlreadySelectItem) {
                                Image(
                                    modifier = Modifier.size(24.dp),
                                    painter = painterResource(R.mipmap.ic_dialog_levels_check),
                                    contentDescription = null
                                )
                            }
                        }
                    }

                }
            }

            Spacer(
                modifier = Modifier
                    .align(alignment = Alignment.BottomCenter)
                    .padding(bottom = 69.5.dp)
                    .height(0.5.dp)
                    .fillMaxSize()
                    .background(color = FFF2F3F4)
            )
            Box(
                modifier = Modifier
                    .align(alignment = Alignment.BottomCenter)
                    .padding(start = 16.dp, end = 16.dp, bottom = 16.dp)
                    .fillMaxWidth()
                    .height(40.dp)
                    .background(color = FF306DF4, RoundedCornerShape(24.dp))
                    .singleClickable {
                        if (currentSelectItemVal.size > 0) {
                            onConfirm?.invoke(currentSelectItemVal[currentSelectItemVal.size - 1])
                        } else {
                            onConfirm?.invoke(null)
                        }
                        dismiss()
                    },
                contentAlignment = Alignment.Center
            ) {
                Text(
                    text = stringResource(R.string.dialog_confirm_position_txt),
                    style = TextStyle(color = Color.White, fontSize = 14.sp)
                )
            }
        }

    }

}
相关推荐
小书房7 小时前
Kotlin的内联函数
java·开发语言·kotlin·inline·内联函数
zhangphil10 小时前
Android Page3与Flow分页查媒体数据库展示宫格图片列表,Kotlin
android·kotlin
胡致和1 天前
配置变更后,弹窗为什么飞到了最左边?
kotlin
zhangphil1 天前
Android Page 3 Flow读sql数据库媒体文件,Kotlin
android·kotlin
小书房1 天前
Kotlin使用体验及理解1
android·开发语言·kotlin
Kapaseker1 天前
我想让同事知道我很懂 Compose 怎么办?
android·kotlin
jinanwuhuaguo2 天前
OpenClaw工程解剖——RAG、向量织构与“记忆宫殿”的索引拓扑学(第十三篇)
android·开发语言·人工智能·kotlin·拓扑学·openclaw
jinanwuhuaguo2 天前
OpenClaw协议霸权——从 MCP 标准到意图封建化的政治经济学(第十八篇)
android·人工智能·kotlin·拓扑学·openclaw
zhangphil2 天前
Android sql查媒体数据封装room Dao构造AndroidViewModel,RecyclerView宫格展示,Kotlin
android·kotlin
jinanwuhuaguo2 天前
反熵共同体——OpenClaw的宇宙热力学本体论(第十七篇)
大数据·人工智能·安全·架构·kotlin·openclaw