在我写项目的时候要用到Spinner。找了找,目前compose里好像还没有类似Spinner的函数。使用AndroidView来使用Spinner的话又会出现一些问题。于是自己使用Compose实现了个简易的Spinner组件。
/**
* @param modifier 应用于布局的修饰符
* @param dataArray 数据数组
* @param position 选择的item
* @param expanded 是否展开
* @param arrowColor 下拉箭头颜色
* @param arrowSize 下拉箭头大小
* @param maxShowHeight 下拉列表最大高度
* @param enabled 是否启用
* @param selectChange 选择item状态改变回调
* @param expandedChange 列表 展开/收起 状态改变会标
* @param itemContent 描述item的Compose组件内容。lambda参数:data为数据(dataArray[index]),modifier里写好了用于监听item的点击选择回调
*/
@OptIn(ExperimentalAnimationApi::class)
@Composable
fun <T> Spinner(
modifier: Modifier = Modifier,
dataArray: Array<T>?,
position: Int = 0,
expanded: Boolean = false,
arrowColor: Color = LocalContentColor.current.copy(alpha = LocalContentAlpha.current),
arrowSize: Dp = 30.dp,
maxShowHeight: Dp = 100.dp,
enabled: Boolean = true,
selectChange: (Int, T) -> Unit = { _,_ -> },
expandedChange: (Boolean) -> Unit = {},
itemContent: @Composable (data: T, modifier: Modifier) -> Unit,
) {
}
后面代码中用到了实验性api:AnimatedVisibility,要加入@OptIn(ExperimentalAnimationApi::class)
Column(modifier = modifier) {
Row(modifier = Modifier.clickable(enabled = enabled) { expandedChange.invoke(!expanded) }) {
//item父容器
Box(modifier = Modifier.align(Alignment.CenterVertically)) {
//item组件
}
//下拉箭头
Icon(
imageVector = Icons.Default.ArrowDropDown,
contentDescription = if (expanded) "下拉" else "上拉",
tint = arrowColor,
modifier = Modifier
.size(arrowSize)
.align(Alignment.CenterVertically)
.rotate(degrees)
)
}
//下拉列表
}
Column作为最外层的容器,包住Spinner的选中内容和下拉列表。
Row里有左右两个东西,item和下拉箭头
Box包住item方便控制摆放位置
Icon则是下拉箭头
下拉列表待会在加上
//dataArray为空时使用Spacer占位
if (dataArray == null) {
Spacer(modifier = Modifier.size(width = 100.dp, height = 38.dp))
} else {
//调用itemContent显示item
itemContent.invoke(dataArray[position], Modifier)
}
//dataArray不为空时才显示下拉列表
if (dataArray != null) {
//显示/隐藏动画
AnimatedVisibility(expanded) {
LazyColumn(modifier = Modifier.height(showHeight),
content = {
items(dataArray.size) {
itemContent.invoke(
dataArray[it],
Modifier.clickable(enabled = enabled) { selectChange.invoke(it) }
)
}
}
)
}
}
//禁用时收起下拉列表
if (!enabled && expanded) expandedChange.invoke(false)
@Preview
@Composable
fun SpinnerPreview() {
val dataArray = arrayOf("Test1", "Test2", "Test3")
var position: Int by remember { mutableStateOf(0) }
var expanded: Boolean by remember{ mutableStateOf(false) }
Spinner(
modifier = Modifier.background(Color.White),
dataArray = dataArray,
position = position,
expanded = expanded,
selectChange = { i, data ->
position = i
},
expandedChange = {
expanded = it
}
) { data, modifier ->
Text(text = data, modifier)
}
}
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.ExperimentalAnimationApi
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowDropDown
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.rotate
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
/**
* @param modifier 应用于布局的修饰符
* @param dataArray 数据数组
* @param position 选择的item
* @param expanded 是否展开
* @param arrowColor 下拉箭头颜色
* @param arrowSize 下拉箭头大小
* @param maxShowHeight 下拉列表最大高度
* @param enabled 是否启用
* @param selectChange 选择item状态改变回调
* @param expandedChange 列表 展开/收起 状态改变会标
* @param itemContent 描述item的Compose组件内容。lambda参数:data为数据(dataArray[index]),modifier里写好了用于监听item的点击选择回调
*/
@OptIn(ExperimentalAnimationApi::class)
@Composable
fun <T> Spinner(
modifier: Modifier = Modifier,
dataArray: Array<T>?,
position: Int = 0,
expanded: Boolean = false,
arrowColor: Color = LocalContentColor.current.copy(alpha = LocalContentAlpha.current),
arrowSize: Dp = 30.dp,
maxShowHeight: Dp = 100.dp,
enabled: Boolean = true,
selectChange: (Int, T) -> Unit = { _,_ -> },
expandedChange: (Boolean) -> Unit = {},
itemContent: @Composable (data: T, modifier: Modifier) -> Unit,
) {
//下拉箭头旋转角度
val degrees: Float by animateFloatAsState(targetValue = if (expanded) 0f else 90f)
Column(modifier = modifier) {
Row(modifier = Modifier.clickable(enabled = enabled) { expandedChange.invoke(!expanded) }) {
Box(modifier = Modifier.align(Alignment.CenterVertically)) {
//dataArray为空时使用Spacer占位
if (dataArray == null) {
Spacer(modifier = Modifier.size(width = 100.dp, height = 38.dp))
} else {
//调用itemContent显示item
itemContent.invoke(dataArray[position], Modifier)
}
}
Icon(
imageVector = Icons.Default.ArrowDropDown,
contentDescription = if (expanded) "下拉" else "收起",
tint = arrowColor,
modifier = Modifier
.size(arrowSize)
.align(Alignment.CenterVertically)
.rotate(degrees)
)
}
//dataArray不为空时才显示下拉列表
if (dataArray != null) {
//显示/隐藏动画
AnimatedVisibility(expanded) {
LazyColumn(modifier = Modifier.heightIn(max = maxShowHeight),
content = {
items(dataArray.size) {
itemContent.invoke(
dataArray[it],
Modifier.clickable(enabled = enabled) { selectChange.invoke(it, dataArray[it]) }
)
}
}
)
}
}
//禁用时收起下拉列表
if (!enabled && expanded) expandedChange.invoke(false)
}
}