Android级联选择器,下拉菜单
近期android开发,遇到的需求,分享二个android可能用到的小组件
下拉选择器:它的实现,主要是需要监听它依附的组件当前距离屏幕顶端的位置。
在显示下拉菜单中,如果需要点击上面有响应。可通过activity拿到decorview(activity.window.decorView),然后把下拉的view添加到decorView,然后设置该菜单距离顶端的上边距。如果需要先点击屏幕让菜单先消失,可搞一个全屏的下拉菜单,距顶端高度搞一个透明的view来填充(设置一个点击事件,处理decorView.remove该下拉组件)
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实现。具体实现如下。
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
)
}
}
*/
/**
* 级联选择,可以自行选择。自行点击确定,完成选择成功
*/
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)
)
}
}
}
}