0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Compoeseでトグルボタンを実装する

Posted at

はじめに

今回はトグルボタンを独自で実装していきます

コード

/**
 * テキストベースのトグルスイッチコンポーネント
 *
 * 2つのテキストオプションを切り替えるセグメントコントロール風のUI
 * 例: "今回の記録" ⇔ "学習済の記録"
 *
 * @param leftText 左側のテキスト
 * @param rightText 右側のテキスト
 * @param selectedIndex 選択されているインデックス(0: 左, 1: 右)
 * @param onSelectedChange 選択が変更されたときのコールバック
 * @param modifier Modifier
 * @param enabled 有効かどうか
 * @param backgroundColor 背景色
 * @param selectedBackgroundColor 選択された部分の背景色
 * @param selectedTextColor 選択されたテキストの色
 * @param unselectedTextColor 選択されていないテキストの色
 * @param height コンポーネントの高さ
 */
@Composable
fun TextToggle(
    leftText: String,
    rightText: String,
    selectedIndex: Int,
    onSelectedChange: (Int) -> Unit,
    modifier: Modifier = Modifier,
    enabled: Boolean = true,
    backgroundColor: Color = AppColors.Gray300,
    selectedBackgroundColor: Color = AppColors.Gray800,
    selectedTextColor: Color = AppColors.White,
    unselectedTextColor: Color = AppColors.Gray900,
    disabledBackgroundColor: Color = AppColors.Gray300,
    disabledTextColor: Color = AppColors.Gray600,
    height: Dp = 56.dp,
) {
    var containerWidthPx by remember { mutableStateOf(0) }

    // アニメーション設定
    val animationSpec = tween<Float>(durationMillis = 250)
    val slideOffset by animateFloatAsState(
        targetValue = if (selectedIndex == 0) 0f else 1f,
        animationSpec = animationSpec,
        label = "slideOffset",
    )

    // 有効/無効に応じた色の設定
    val currentBackgroundColor = if (enabled) backgroundColor else disabledBackgroundColor
    // 選択されている部分の背景色は常に同じ(有効/無効に関わらず)
    val currentSelectedBackgroundColor = selectedBackgroundColor

    Box(
        modifier = modifier
            .fillMaxWidth()
            .height(height)
            .clip(RoundedCornerShape(height / 2))
            .background(currentBackgroundColor)
            .padding(4.dp)
            .onSizeChanged { size ->
                containerWidthPx = size.width
            },
    ) {
        // 選択されている部分の背景(スライドするボックス)
        Box(
            modifier = Modifier
                .fillMaxWidth(0.5f)
                .height(height - 8.dp)
                .offset {
                    val maxOffset = (containerWidthPx / 2)
                    IntOffset((maxOffset * slideOffset).toInt(), 0)
                }
                .clip(RoundedCornerShape((height - 8.dp) / 2))
                .background(currentSelectedBackgroundColor),
        )

        // テキストボタン
        Row(
            modifier = Modifier
                .fillMaxWidth()
                .height(height - 8.dp),
            verticalAlignment = Alignment.CenterVertically,
        ) {
            // 左側のテキスト
            Box(
                modifier = Modifier
                    .weight(1f)
                    .clickable(
                        interactionSource = remember { MutableInteractionSource() },
                        indication = null,
                        enabled = enabled,
                        onClick = { onSelectedChange(0) },
                    ),
                contentAlignment = Alignment.Center,
            ) {
                val textColor by animateColorAsState(
                    targetValue = if (selectedIndex == 0) {
                        selectedTextColor
                    } else if (!enabled) {
                        disabledTextColor
                    } else {
                        unselectedTextColor
                    },
                    animationSpec = tween(durationMillis = 250),
                    label = "leftTextColor",
                )
                Text(
                    text = leftText,
                    color = textColor,
                    fontSize = 16.sp,
                    fontWeight = if (selectedIndex == 0) FontWeight.Bold else FontWeight.Normal,
                    textAlign = TextAlign.Center,
                )
            }

            // 右側のテキスト
            Box(
                modifier = Modifier
                    .weight(1f)
                    .clickable(
                        interactionSource = remember { MutableInteractionSource() },
                        indication = null,
                        enabled = enabled,
                        onClick = { onSelectedChange(1) },
                    ),
                contentAlignment = Alignment.Center,
            ) {
                val textColor by animateColorAsState(
                    targetValue = if (selectedIndex == 1) {
                        selectedTextColor
                    } else if (!enabled) {
                        disabledTextColor
                    } else {
                        unselectedTextColor
                    },
                    animationSpec = tween(durationMillis = 250),
                    label = "rightTextColor",
                )
                Text(
                    text = rightText,
                    color = textColor,
                    fontSize = 16.sp,
                    fontWeight = if (selectedIndex == 1) FontWeight.Bold else FontWeight.Normal,
                    textAlign = TextAlign.Center,
                )
            }
        }
    }
}

最後に

テキストベースはないので作ってみましたが、若干入れ子が多くてみづらいなぁというのが感想でした

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?