compose->tab

464 阅读2分钟

TabRow

image.png

fun TabRow(
    selectedTabIndex: Int,
    modifier: Modifier = Modifier,
    backgroundColor: Color = MaterialTheme.colors.primarySurface,
    contentColor: Color = contentColorFor(backgroundColor),
    indicator: @Composable (tabPositions: List<TabPosition>) -> Unit = @Composable { tabPositions ->
        TabRowDefaults.Indicator(
            Modifier.tabIndicatorOffset(tabPositions[selectedTabIndex])
        )
    },
    divider: @Composable () -> Unit = @Composable {
        TabRowDefaults.Divider()
    },
    tabs: @Composable () -> Unit
) 

selectedTabIndex: 从0开始,选中的tab的位置索引
backgroundColor:背景颜色
contentColor:文字内容颜色
indicator:游标,就是选中的那个横线的颜色
divider:最底层那条灰线
tabs:tab组件
顺道看下源码里tab,indicator,divider的层级结构

layout(tabRowWidth, tabRowHeight) {
    //先放置的是tab组件
    tabPlaceables.fastForEachIndexed { index, placeable ->
        placeable.placeRelative(index * tabWidth, 0)
    }

//接着是divider,是放置在bottom位置的
    subcompose(TabSlots.Divider, divider).fastForEach {
        val placeable = it.measure(constraints.copy(minHeight = 0))
        placeable.placeRelative(0, tabRowHeight - placeable.height)
    }
//最后放置indicator,在divider上层,也是bottom位置
    subcompose(TabSlots.Indicator) {
        indicator(tabPositions)
    }.fastForEach {
        it.measure(Constraints.fixed(tabRowWidth, tabRowHeight)).placeRelative(0, 0)
    }
}

上边图片效果的代码如下

@Composable
fun circle4() {
    val items = MoneyInfo.values().toList()
    var selectedTabIndex by remember {
        mutableStateOf(0)
    }
    TabRow(selectedTabIndex = selectedTabIndex,
        backgroundColor = MaterialTheme.colors.onPrimary,
        contentColor = MaterialTheme.colors.primary,
        ) {
        items.forEachIndexed { index, content ->
            val colorText = if (selectedTabIndex == index) {
                Color.Red
            } else {
                MaterialTheme.colors.onSurface.copy(alpha = 0.8f)
            }
            Tab(selected = selectedTabIndex == index,
                onClick = { selectedTabIndex = index },
                modifier = Modifier.heightIn(min = 48.dp)) {
                Text(text = content.name,
                    color = colorText,
                    style = MaterialTheme.typography.subtitle2,
                    modifier = Modifier.paddingFromBaseline(top = 20.dp))
            }
        }
    }
}

自定义divider ,indicator

image.png

TabRow(selectedTabIndex = selectedTabIndex,
    backgroundColor = MaterialTheme.colors.onPrimary,
    contentColor = MaterialTheme.colors.primary,
    indicator = { tabPositions ->
        Box(modifier = Modifier.tabIndicatorOffset(tabPositions[selectedTabIndex]).border(2.dp, Color.Red, RoundedCornerShape(50)).fillMaxSize())
    },
    divider = { Divider(Modifier.height(2.dp))}
    ) 

本以为完事了,结果发现不行,因为下边tab点击的时候有个ripple,它的形状是矩形的,和我们这个圆角矩形不匹配,所以看下Tab的源码,看能否修改

Tab

看到那个ripple是写死的,只有颜色可以外部修改,圆角无法修改 Tab组件实际是个Column,里边可以放多个其他组件的,比如图片加文字

@Composable
fun Tab(
    selected: Boolean,
    onClick: () -> Unit,
    modifier: Modifier = Modifier,
    enabled: Boolean = true,
    interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
    selectedContentColor: Color = LocalContentColor.current,
    unselectedContentColor: Color = selectedContentColor.copy(alpha = ContentAlpha.medium),
    content: @Composable ColumnScope.() -> Unit
) {
    // The color of the Ripple should always the selected color, as we want to show the color
    // before the item is considered selected, and hence before the new contentColor is
    // provided by TabTransition.
    val ripple = rememberRipple(bounded = true, color = selectedContentColor)

    TabTransition(selectedContentColor, unselectedContentColor, selected) {
        Column(
            modifier = modifier
                .selectable(
                    selected = selected,
                    onClick = onClick,
                    enabled = enabled,
                    role = Role.Tab,
                    interactionSource = interactionSource,
                    indication = ripple
                )
                .fillMaxWidth(),
            horizontalAlignment = Alignment.CenterHorizontally,
            verticalArrangement = Arrangement.Center,
            content = content
        )
    }
}

rememberRipple

public fun rememberRipple(
    bounded: Boolean = true,
    radius: Dp = Dp.Unspecified,
    color: Color = Color.Unspecified
)

重写Tab组件的代价太大,那咋解决ripple的范围问题?

外层包裹一层Box,由box来限制范围就ok了,如下

items.forEachIndexed { index, content ->
    val colorText = if (selectedTabIndex == index) {
        Color.Red
    } else {
        MaterialTheme.colors.onSurface.copy(alpha = 0.8f)
    }
    Box(modifier = Modifier.clip(RoundedCornerShape(50))){
        Tab(selected = selectedTabIndex == index,
            onClick = { selectedTabIndex = index },
            modifier = Modifier
                .heightIn(min = 48.dp)) {
            Text(text = content.name,
                color = colorText,
                style = MaterialTheme.typography.subtitle2,
                modifier = Modifier.paddingFromBaseline(top = 20.dp))
        }
    }
}

ok,TabRow基本用法就完事了,