以降のセクションでは、一般的な UI パターンで Modifier.blur、BlurRadiusSpec、BlurStop、BlurScope を組み合わせる方法について説明します。これらの API の概要については、Compose でのぼかしをご覧ください。
スクロール可能なコンテンツやドラッグ可能なコンテンツの端をぼかす
スクロール リスト、カルーセル、選択ツールを表示する場合、マルチストップ プログレッシブ ブラーを使用すると、コンテナの端付近のアイテムをぼかし、アクティブな中央領域をシャープに保つことができます。
スクロール リストの上端と下端をフェードする
垂直リストのビューポートにスクロールインまたはスクロールアウトするアイテムをソフトにするには、BlurStop エントリを含む BlurRadiusSpec.verticalGradient を LazyColumn に適用します。次の例では、曲の歌詞を 1 行ずつスクロールします。0.42f と 0.58f の間の 0.dp 停止により、アクティブな行が中央にシャープに表示され、上端と下端に向かって徐々にぼやけていきます。
@Composable fun SongListTopBottomBlurExample(lyrics: List<String>) { // ... LazyColumn( state = listState, flingBehavior = flingBehavior, modifier = Modifier .width(330.dp) .height(480.dp) .padding(horizontal = 24.dp) .blur( blurRadiusSpec = BlurRadiusSpec.verticalGradient( stops = listOf( BlurStop(fraction = 0.00f, radius = 18.dp), // Heavily blurred top edge BlurStop(fraction = 0.24f, radius = 8.dp), // Soft upper transition BlurStop(fraction = 0.42f, radius = 0.dp), // Sharp center focus starts BlurStop(fraction = 0.58f, radius = 0.dp), // Sharp center focus ends BlurStop(fraction = 0.76f, radius = 8.dp), // Soft lower transition BlurStop(fraction = 1.00f, radius = 18.dp), // Heavily blurred bottom edge ), ), edgeTreatment = BlurredEdgeTreatment.Unbounded, ), contentPadding = PaddingValues(vertical = 36.dp), verticalArrangement = Arrangement.spacedBy(26.dp), horizontalAlignment = Alignment.CenterHorizontally, ) { // ... } }
水平コントロールの左右の端をフェードする
同様に、ホイール ピッカー、カルーセル、測定スケールなど、水平方向にスクロール可能またはドラッグ可能なコンポーネントの中心にフォーカスを合わせるには、BlurStop エントリを含む BlurRadiusSpec.horizontalGradient を適用します。次のドラッグ可能な体重計では、左右の端がぼやけていますが、中央のインジケーターの針の近くの目盛りと数字は完全に鮮明です。
@Composable fun HorizontalScaleBlurExample() { var selectedValue by remember { mutableFloatStateOf(65.0f) } val animatedValue by animateFloatAsState( targetValue = selectedValue, animationSpec = tween(durationMillis = 900, easing = FastOutSlowInEasing), label = "horizontalScaleSmoothScroll", ) Column( modifier = Modifier .width(340.dp) .background(Color(0xFF0F172A), RoundedCornerShape(24.dp)) .padding(vertical = 24.dp), horizontalAlignment = Alignment.CenterHorizontally, ) { Text( text = "${(animatedValue * 10).roundToInt() / 10.0} kg", color = Color.White, fontSize = 44.sp, fontWeight = FontWeight.Black, ) Spacer(Modifier.height(16.dp)) Box( // ... ) { // Ruler ticks blurred at the left and right edges, sharp at center Canvas( modifier = Modifier .fillMaxSize() .blur( blurRadiusSpec = BlurRadiusSpec.horizontalGradient( stops = listOf( BlurStop(fraction = 0.00f, radius = 14.dp), BlurStop(fraction = 0.28f, radius = 0.dp), BlurStop(fraction = 0.72f, radius = 0.dp), BlurStop(fraction = 1.00f, radius = 14.dp), ), ), edgeTreatment = BlurredEdgeTreatment.Unbounded, ), ) { // Draw horizontal ruler ticks offset by `animatedValue` // ... } // ... } } }
linearGradient で斜めのぼかしを作成する
BlurRadiusSpec.linearGradient は、レイヤの座標空間の start 値と end DpOffset 値の間の線分に沿ってぼかし半径を変化させます。Modifier.blur ラムダ ブロック内で BlurScope.size を読み取り、ヒーロー画像に左上から右下にかけて斜めのプログレッシブ ブラーを適用できます。左上の被写体は完全にシャープなまま、オーバーレイされたテキストの背後にある右下の領域を徐々にぼかします。
@Composable fun DiagonalLinearBlurCardExample() { Box( modifier = Modifier .wrapContentSize() .background(Color(0xFFF8FAFC)) .padding(10.dp), ) { Image( painter = painterResource(id = R.drawable.dog), contentDescription = stringResource(id = R.string.dog_content_description), contentScale = ContentScale.Crop, modifier = Modifier .size(width = 300.dp, height = 380.dp) .blur { blurRadiusSpec = BlurRadiusSpec.linearGradient( start = DpOffset(0.dp, 0.dp), end = DpOffset(size.width, size.height), stops = listOf( BlurStop(fraction = 0.00f, radius = 0.dp), BlurStop(fraction = 0.50f, radius = 0.dp), BlurStop(fraction = 0.75f, radius = 14.dp), BlurStop(fraction = 1.00f, radius = 28.dp), ), ) edgeTreatment = BlurredEdgeTreatment(RoundedCornerShape(24.dp)) }, ) // ... } }
radialGradient でフォーカス スポットライトを作成する
BlurRadiusSpec.radialGradient は、焦点から外側に向かってぼかし半径を変化させることで、円形のスポットライト効果を作成します。center を DpOffset として定義し、fallOffRadius を endRadius に到達する center からの Dp 距離として定義します。center を DpOffset.Unspecified のままにすると、グラデーションはコンポーザブルの中央に配置されます。fallOffRadius を Dp.Unspecified のままにすると、デフォルトでレイヤの小さい方の寸法の半分になります。
たとえば、テキストのパッセージに BlurRadiusSpec.radialGradient を適用し、ポインタのドラッグ イベントから center を更新すると、ユーザーは段落全体に円形の読書スポットライトをドラッグしながら、周囲のテキストをぼかすことができます。次の例をご覧ください。
@Composable fun RadialSpotlightTextExample() { var touchOffset by remember { mutableStateOf(Offset.Unspecified) } Box( modifier = Modifier .size(width = 330.dp, height = 340.dp) .background(Color(0xFF0F172A), RoundedCornerShape(20.dp)) .pointerInput(Unit) { detectDragGestures( onDragStart = { offset -> touchOffset = offset }, onDrag = { change, _ -> touchOffset = change.position }, ) } .blur { val spotlightCenter = if (touchOffset.isSpecified) { DpOffset(touchOffset.x.toDp(), touchOffset.y.toDp()) } else { DpOffset(x = size.width / 2, y = size.height / 2) } blurRadiusSpec = BlurRadiusSpec.radialGradient( startRadius = 0.dp, endRadius = 10.dp, center = spotlightCenter, fallOffRadius = 215.dp, ) edgeTreatment = BlurredEdgeTreatment(RoundedCornerShape(20.dp)) } .padding(22.dp), ) { // ... } }
シェーダーを使用してカスタムのぼかしマスクを作成する
線形グラデーションや円形グラデーション以外の複雑な空間ぼかし分布(繰り返しパターン、任意の形状、プロシージャル ノイズなど)の場合は、カスタムの Shader(AGSL RuntimeShader など)を BlurRadiusSpec.shader に渡します。シェーダーは、左上の原点からレイヤのピクセル座標で評価され、サンプリングされたアルファ チャンネル(0..1)は maxRadius をスケーリングします。
remember コンポーズ可能な関数を使用してシェーダーと BlurRadiusSpec.shader 構成を 1 回作成し、再コンポーズ全体で 1 つのインスタンスが再利用されるようにします。Compose は、解決ごとにレイヤサイズを指定して構成ブロックを呼び出します。ここで、サイズに依存する uniform を設定します。
@RequiresApi(Build.VERSION_CODES.TIRAMISU) @Composable fun ShaderBlurExample() { // Creates 6 diagonal frosted-glass bands across the image. val maskShader = remember { RuntimeShader( """ uniform float2 size; half4 main(float2 coord) { float2 uv = coord / size; float band = smoothstep(0.4, 0.6, fract((uv.x + uv.y) * 3.0)); return half4(0.0, 0.0, 0.0, half(band)); } """.trimIndent(), ) } val maskBlurSpec = remember(maskShader) { BlurRadiusSpec.shader(maxRadius = 24.dp) { sizePx -> maskShader.setFloatUniform("size", sizePx.width, sizePx.height) maskShader } } Image( painter = painterResource(id = R.drawable.dog), contentDescription = stringResource(id = R.string.dog_content_description), contentScale = ContentScale.Crop, modifier = Modifier .size(150.dp) .blur(blurRadiusSpec = maskBlurSpec), ) }