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)
                )
            }
        }

    }

}
相关推荐
zhangphil14 小时前
Android Coil3缩略图、默认占位图placeholder、error加载错误显示,Kotlin(1)
android·kotlin
xvch20 小时前
Kotlin 2.1.0 入门教程(二十三)泛型、泛型约束、协变、逆变、不变
android·kotlin
xvch3 天前
Kotlin 2.1.0 入门教程(二十四)泛型、泛型约束、绝对非空类型、下划线运算符
android·kotlin
zhangphil3 天前
Android Coil ImageLoader MemoryCache设置Key与复用内存缓存,Kotlin
android·kotlin
mmsx3 天前
kotlin Java 使用ArrayList.add() ,set()前面所有值被 覆盖 的问题
android·开发语言·kotlin
lavins3 天前
android studio kotlin项目build时候提示错误 Unknown Kotlin JVM target: 21
jvm·kotlin·android studio
面向未来_3 天前
JAVA Kotlin Androd 使用String.format()格式化日期
java·开发语言·kotlin
alexhilton3 天前
选择Retrofit还是Ktor:给Android开发者的指南
android·kotlin·android jetpack
GordonH19914 天前
Kotlin 优雅的接口实现
android·java·kotlin