这可能对你有帮助。
<FrameLayout
android:layout_width="300dp"
android:layout_height="300dp"
android:rotation="-90"
android:layout_gravity="center_vertical">
<com.google.android.material.tabs.TabLayout
android:id="@+id/tabLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
app:tabIndicatorHeight="4dp"
app:tabTextAppearance="@style/CDTabLayoutTextAppearance"
app:tabInlineLabel="true"
app:tabIconTint="@color/tab_tint_color_selector"
app:tabIndicatorFullWidth="false"
app:tabIndicator="@drawable/ic_community_tab_selected"
app:tabIndicatorColor="@color/colorColoringDesk"
android:background="@color/white" >
<com.google.android.material.tabs.TabItem
android:id="@+id/tabItemContacts"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Contacts" />
<com.google.android.material.tabs.TabItem
android:id="@+id/tabItemChat"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Chat" />
</com.google.android.material.tabs.TabLayout>
</FrameLayout>
将 TabLayout 与 ViewPager2 一起使用。
我使用以下代码制作了垂直 tabLayout。你也可以尝试一下:-
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:rotation="-90"
android:transformPivotX="0dp"
android:layout_marginTop="@dimen/tab_width"
android:layout_marginLeft="-24dp">
<com.google.android.material.tabs.TabLayout
android:layout_width="@dimen/tab_width"
android:layout_height="wrap_content"
android:background="@color/black"
app:tabTextColor="@color/light_grey"
app:tabSelectedTextColor="@color/white"
app:tabIndicatorHeight="2dp"
app:tabIndicatorColor="@color/white"
app:tabPaddingBottom="-10dp">
<com.google.android.material.tabs.TabItem
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="ABC"/>
<com.google.android.material.tabs.TabItem
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="XYZ"/>
</com.google.android.material.tabs.TabLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
这是 VerticalTabLayout 的代码! 我正在使用 Jetpack Compose。对于垂直选项卡,我使用 Modifier.onGloballyPositioned 来计算项目高度和指示器高度。我正在使用垂直滚动列作为项目列表。
@Composable
fun MDVerticalTabLayout(
modifier: Modifier = Modifier,
bottomPaddingDp: Dp = 0.dp,
list: SnapshotStateList<CategoryItem>,
gradientFirstColor: Color,
gradientSecondColor: Color,
indicatorColor: Color,
indicatorHeightType: IndicatorHeightType = IndicatorHeightType.FIXED,
indicatorHeight: Dp = 58.dp,
textStyle: TextStyle = TextStyle.Default,
onItemClick: (CategoryItem) -> Unit,
) {
val context = LocalContext.current
val globalOffsetYPosition = rememberSaveable {
mutableIntStateOf(0)
}
val globalHeight = rememberSaveable {
mutableIntStateOf(0)
}
val selectedPosition = rememberSaveable {
mutableIntStateOf(0)
}
val movementThreshHold = remember {
100
}
val offsetYPosition = rememberSaveable {
mutableIntStateOf(0)
}
val heightOfItem = rememberSaveable {
mutableIntStateOf(0)
}
val listState = rememberScrollState()
val coroutineScope = rememberCoroutineScope()
val map = remember {
hashMapOf<Int, Int>()
}
val durationMillis = rememberSaveable {
mutableStateOf(0)
}
// Define the animation spec
val animationSpec by remember {
derivedStateOf {
tween<Offset>(
durationMillis = durationMillis.value,
easing = FastOutSlowInEasing,
)
}
}
val animationSpecForScroll by remember {
derivedStateOf {
tween<Float>(
durationMillis = 600,
easing = FastOutSlowInEasing,
)
}
}
// Create an animated offset
val animatedOffset by animateOffsetAsState(
targetValue = Offset(x = 0F, y = offsetYPosition.value.toFloat()),
animationSpec = animationSpec,
label = "",
)
val indicatorHeight by remember {
derivedStateOf {
when (indicatorHeightType) {
IndicatorHeightType.FIXED -> {
indicatorHeight
}
IndicatorHeightType.FILL -> {
getDp(context, heightOfItem.intValue)
}
}
}
}
Scaffold(
modifier = modifier
.background(Color.White)
.fillMaxHeight()
.width(78.dp)
.onGloballyPositioned {
globalOffsetYPosition.value = it.positionInRoot().y.toInt()
globalHeight.value = it.size.height
},
content = { paddingValues ->
Surface(modifier = Modifier.fillMaxHeight().padding(paddingValues), elevation = 10.dp) {
Box(
contentAlignment = Alignment.TopEnd,
) {
Column(
modifier = Modifier
.verticalScroll(listState)
.width(78.dp),
) {
list.forEachIndexed { position, item ->
map[position] = 0
var height: Int = 0
ItemSubCategory(
item = item,
onChecked = selectedPosition.value == position,
gradientFirstColor = gradientFirstColor,
gradientSecondColor = gradientSecondColor,
textStyle = textStyle,
modifier = Modifier
.onGloballyPositioned {
map[position] = it.positionInRoot().y.toInt()
height = it.size.height
if (selectedPosition.value == position) {
height = it.size.height
offsetYPosition.value =
it.positionInRoot().y.toInt() - globalOffsetYPosition.value
heightOfItem.value = height
}
}
.fillMaxWidth(),
) {
durationMillis.value = 600
offsetYPosition.intValue = map[position] ?: (0 - globalOffsetYPosition.intValue)
selectedPosition.intValue = position
heightOfItem.intValue = height
coroutineScope.launch {
if (offsetYPosition.intValue <= height + movementThreshHold) {
listState.animateScrollTo(animationSpec = animationSpecForScroll, value = listState.value - height / 2 - movementThreshHold)
} else if (offsetYPosition.intValue >= globalHeight.intValue - height / 2 - movementThreshHold) {
listState.animateScrollTo(animationSpec = animationSpecForScroll, value = (listState.value + height / 2 + movementThreshHold))
}
delay(600)
durationMillis.value = 0
}
onItemClick(item)
}
if (position == list.size - 1) {
Spacer(
modifier = Modifier
.padding(bottom = bottomPaddingDp)
.fillMaxWidth()
.background(Color.White),
)
} else {
Spacer(
modifier = Modifier
.fillMaxWidth()
.background(Color.White),
)
}
}
}
Box(
modifier = Modifier
.offset(
x = getDp(context, animatedOffset.x.toInt()),
y = getDp(context, animatedOffset.y.toInt()),
)
.padding(top = 24.dp)
.width(3.dp)
.height(indicatorHeight)
.background(
color = indicatorColor,
shape = RoundedCornerShape(2.dp),
),
)
}
}
},
)
}
fun getDp(context: Context, offset: Int): Dp {
val resources = context.resources
val metrics = resources.displayMetrics
val dpValue = offset / (metrics.densityDpi.toFloat() / DisplayMetrics.DENSITY_DEFAULT)
return dpValue.dp
}
enum class IndicatorHeightType {
FILL,
FIXED,
}