Scroll 修飾子
verticalScroll
修飾子と horizontalScroll
修飾子は、コンテンツの境界が最大サイズ制約より大きい場合にユーザーが要素をスクロールできるようにする最も簡単な方法を提供します。verticalScroll
修飾子と horizontalScroll
修飾子では、コンテンツを変換またはオフセットする必要はありません。
@Composable private fun ScrollBoxes() { Column( modifier = Modifier .background(Color.LightGray) .size(100.dp) .verticalScroll(rememberScrollState()) ) { repeat(10) { Text("Item $it", modifier = Modifier.padding(2.dp)) } } }
ScrollState
を使用すると、スクロール位置を変更したり、現在の状態を取得したりできます。デフォルトのパラメータでこれを作成するには、rememberScrollState()
を使用します。
@Composable private fun ScrollBoxesSmooth() { // Smoothly scroll 100px on first composition val state = rememberScrollState() LaunchedEffect(Unit) { state.animateScrollTo(100) } Column( modifier = Modifier .background(Color.LightGray) .size(100.dp) .padding(horizontal = 8.dp) .verticalScroll(state) ) { repeat(10) { Text("Item $it", modifier = Modifier.padding(2.dp)) } } }
Scrollable 修飾子
「
scrollable
スクロール修飾子とは異なり、scrollable
ではスクロール修飾子が
スクロール操作を行い、差分を取得しますが、その内容はオフセットされません。
自動的に適用されます。これは代わりにユーザーに委任されます。
ScrollableState
は、この修飾子が正常に動作するために必要です。
ScrollableState
を作成する際に、consumeScrollDelta
を指定する必要があります。
スクロール ステップごとに呼び出される関数(ジェスチャー入力、
(スクロールやフリングなど)をピクセル単位の差分で表します。この関数は
消費されるスクロール距離の量(イベントが適切に
scrollable
を持つネストされた要素がある場合に伝播される
修飾子を使用します。
次のスニペットは、操作を検出してオフセットの数値を表示しますが、要素のオフセットは行いません。
@Composable private fun ScrollableSample() { // actual composable state var offset by remember { mutableStateOf(0f) } Box( Modifier .size(150.dp) .scrollable( orientation = Orientation.Vertical, // Scrollable state: describes how to consume // scrolling delta and update offset state = rememberScrollableState { delta -> offset += delta delta } ) .background(Color.LightGray), contentAlignment = Alignment.Center ) { Text(offset.toString()) } }
ネストされたスクロール
ネストされたスクロールは、複数のスクロール コンポーネントが含まれるシステム 1 つのスクロール操作に反応し、それに反応して相互作用することで、 スクロール差分(変更)を通知します。
ネストされたスクロール システムを使用すると、 スクロール可能で階層的にリンクされている(ほとんどの場合、同じ親を共有することによって) このシステムは、スクロール コンテナをリンクし、スクロールの操作を可能にする 差分のみが含まれます
Compose には、コンポーザブル間のネストされたスクロールを処理する方法が複数用意されています。 ネストされたスクロールの典型的な例は、別のリスト内にあるリストです。 ツールバーです。
自動ネスト スクロール
シンプルなネスト スクロールでは、アプリ側のアクションは必要ありません。スクロール アクションを開始する操作は、子から親に自動的に伝播されます。これにより、子がそれ以上スクロールできなくなると、親要素によって操作が処理されます。
自動的にネストされたスクロールがサポートされており、
Compose のコンポーネントと修飾子:
verticalScroll
horizontalScroll
,
scrollable
,
Lazy
API とTextField
。つまり、ユーザーが内側の画面をスクロールすると、
前の修飾子がスクロールを伝播させるため、
ネストされたスクロールをサポートする親に対する差分
次の例は、
verticalScroll
verticalScroll
を持つコンテナ内で修飾子が適用されている
適用されています。
@Composable private fun AutomaticNestedScroll() { val gradient = Brush.verticalGradient(0f to Color.Gray, 1000f to Color.White) Box( modifier = Modifier .background(Color.LightGray) .verticalScroll(rememberScrollState()) .padding(32.dp) ) { Column { repeat(6) { Box( modifier = Modifier .height(128.dp) .verticalScroll(rememberScrollState()) ) { Text( "Scroll here", modifier = Modifier .border(12.dp, Color.DarkGray) .background(brush = gradient) .padding(24.dp) .height(150.dp) ) } } } } }
nestedScroll
修飾子の使用
複数の要素間で高度な調整されたスクロールを作成する必要がある場合は、
nestedScroll
修飾子を使用すると、ネストされたスクロール階層を柔軟に定義できます。として
一部のコンポーネントには、ネスト スクロールが組み込まれています。
サポート。ただし、自動的にスクロールできないコンポーザブルの場合
Box
または Column
の場合、これらのコンポーネントでのスクロールの差分は、
ネスト スクロール システムであり、差分が NestedScrollConnection
にも届かない
渡されます。この問題を解決するには、nestedScroll
を使用して、
カスタム コンポーネントを含む他のコンポーネントへのサポート。
ネストされたスクロール サイクル
ネストされたスクロール サイクルは、上下にディスパッチされるスクロールのデルタの流れです。
ネストされたすべてのコンポーネント(またはノード)を
スクロール可能なコンポーネントと修飾子を使用するなど、スクロール可能なシステム
nestedScroll
。
ネストされたスクロール サイクルのフェーズ
スクロール可能なイベントによってトリガー イベント(操作など)が検出されたとき 実際のスクロール操作がトリガーされる前に、 デルタはネストされたスクロール システムに送信され、次の 3 つのフェーズを経由します。 事前スクロール、ノードの消費、ポストスクロールの 3 つです。
最初の事前スクロール フェーズでは、トリガー イベントを受信したコンポーネントが デルタでは、これらのイベントが階層ツリーを通じて最上位の できます。デルタイベントはバブルダウンします。つまり、差分は 最もルートの親から下で、開始元の子に向かって ネストされたスクロールサイクルが トリガーされます
これにより、ネストされたスクロールの親(nestedScroll
または
スクロール可能な修飾子など)がある場合、
ノード自体で使用できます
ノードの使用フェーズでは、ノード自体は、更新されていない 使用しないでください。この段階でスクロールの動きが実際に実行され、 表示されます。
この段階では、子は残りのリソースの一部を使用するか、すべてを使用するかを選択できます。 スクロールできます。残っているものは、ポスト スクロール フェーズに進むために戻されます。
最後のポストスクロール フェーズでは、ノード自体が消費しなかったものが 消費のために再び祖先に送られます
ポストスクロール フェーズは、事前スクロール フェーズと同様に機能します。 食べるかしないかを選ぶ保護者の割合。
スクロールと同様に、ドラッグ操作が終了するとユーザーの意図は フリング(アニメーションを使ってスクロール)する速度に 作成します。フリングもネストされたスクロール サイクルの一部であり、 ドラッグ イベントによって生成される速度は、プリフリング、 ノード消費量、フリング後などですなお、フリング アニメーションは、 タップ操作に対応しており、a11y や a11y などのイベントではトリガーされません。 ハードウェア スクロール。
ネストされたスクロール サイクルに参加する
このサイクルに参加するということは、 デルタの消費を制御できます。Compose には、タスクを実行するための ネストされたスクロール システムの仕組みと、 たとえば、スクロールの差分処理を スクロール可能なコンポーネントが スクロールを開始する場合もあります
ネストされたスクロール サイクルがノードのチェーンで動作するシステムの場合、
nestedScroll
修飾子は、これらの変更をインターセプトして挿入する方法であり、
チェーンに伝播されるデータ(スクロール デルタ)に影響します。この
修飾子は階層のどこにでも配置でき、
ネストされたスクロール修飾子がツリーの上位に表示されるように、
このチャンネル。この修飾子の構成要素は NestedScrollConnection
です。
および NestedScrollDispatcher
。
NestedScrollConnection
ネストされたスクロール サイクルのフェーズに対応し、
ネストされたスクロールシステムですこれは 4 つのコールバック メソッドで構成され、
消費フェーズ(プリ/ポスト スクロールとプリ/フリング)のいずれかを表します。
val nestedScrollConnection = object : NestedScrollConnection { override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset { println("Received onPreScroll callback.") return Offset.Zero } override fun onPostScroll( consumed: Offset, available: Offset, source: NestedScrollSource ): Offset { println("Received onPostScroll callback.") return Offset.Zero } }
各コールバックは、伝播される差分に関する情報も提供します。
その特定のフェーズで available
のデルタ、消費された consumed
のデルタ
理解しましたある時点で差分の伝播を停止したい場合は、
ネストされたスクロール接続を使用できます。
val disabledNestedScrollConnection = remember { object : NestedScrollConnection { override fun onPostScroll( consumed: Offset, available: Offset, source: NestedScrollSource ): Offset { return if (source == NestedScrollSource.SideEffect) { available } else { Offset.Zero } } } }
すべてのコールバックは、
NestedScrollSource
あります。
NestedScrollDispatcher
ネストされたスクロール サイクルを初期化します。ディスパッチャの使用とそのメソッドを呼び出す
循環をトリガーしますスクロール可能なコンテナには、
システムへのジェスチャー中にキャプチャされたデルタ。このため、ほとんどのユースケースでは
ネストされたスクロールをカスタマイズするには、代わりに NestedScrollConnection
を使用する
新しい差分を送信するのではなく、既存の差分に反応します。
詳しくは、
NestedScrollDispatcherSample
をご覧ください。
ネストされたスクロールの相互運用
スクロール可能なコンポーザブルでスクロール可能な View
要素をネストしようとする場合、または
逆にすると、問題が発生する可能性があります。特に注目に値するのは、
子をスクロールして開始境界または終了境界に到達し、
親にスクロールを引き継ぎますただし、この想定される動作は、
期待どおりに動作しない可能性があります。
この問題は、スクロール可能なコンポーザブルに組み込まれている前提の結果です。スクロール可能なコンポーザブルには「デフォルトでネスト スクロール」というルールがあります。つまり、スクロール可能なコンテナはすべて、ネスト スクロール チェーンに、NestedScrollConnection
を介して親として参加し、NestedScrollDispatcher
を介して子として参加する必要があります。そうすることで、子が境界にあるときに、子が親のネスト スクロールを行います。このようなルールにより、たとえば Compose の Pager
と Compose の LazyRow
が適切に連携します。ただし、相互運用スクロールが ViewPager2
または RecyclerView
で行われている場合、これらは NestedScrollingParent3
を実装していないため、子から親への連続スクロールはできません。
スクロール可能な View
要素とスクロール可能なコンポーザブルが両方向でネストされており、それらの間でネスト スクロールの相互運用 API を有効にする場合、以下のシナリオでネスト スクロールの相互運用 API を使用することで、このような問題を軽減できます。
子 ComposeView
を含む、協力する親 View
連携する親 View
は、すでに実装しているものです。
NestedScrollingParent3
そのため、協調するネスト構造からスクロールの差分を受け取れます。
子コンポーザブル:この場合、ComposeView
が子として機能し、次のようになります。
実装する必要が
NestedScrollingChild3
。
協力する親の例の 1 つは、
androidx.coordinatorlayout.widget.CoordinatorLayout
。
スクロール可能な View
である親コンテナとネストされたスクロール可能な子コンポーザブルの間にネスト スクロールの相互運用が必要な場合は、rememberNestedScrollInteropConnection()
を使用できます。
rememberNestedScrollInteropConnection()
により、NestedScrollingParent3
を実装する親 View
と子 Compose との間でネスト スクロールの相互運用を有効にする NestedScrollConnection
の許可と保存が可能になります。これは nestedScroll
修飾子と組み合わせて使用する必要があります。Compose 側ではネスト スクロールがデフォルトで有効になっているため、
この接続を使用して、View
側でネストされたスクロールの両方を有効にし、
Views
とコンポーザブルの間の必要なグルーロジック。
よく使用されるユースケースでは、CoordinatorLayout
、CollapsingToolbarLayout
、
子コンポーザブルを返します。
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent"> <com.google.android.material.appbar.AppBarLayout android:id="@+id/app_bar" android:layout_width="match_parent" android:layout_height="100dp" android:fitsSystemWindows="true"> <com.google.android.material.appbar.CollapsingToolbarLayout android:id="@+id/collapsing_toolbar_layout" android:layout_width="match_parent" android:layout_height="match_parent" android:fitsSystemWindows="true" app:layout_scrollFlags="scroll|exitUntilCollapsed"> <!--...--> </com.google.android.material.appbar.CollapsingToolbarLayout> </com.google.android.material.appbar.AppBarLayout> <androidx.compose.ui.platform.ComposeView android:id="@+id/compose_view" app:layout_behavior="@string/appbar_scrolling_view_behavior" android:layout_width="match_parent" android:layout_height="match_parent"/> </androidx.coordinatorlayout.widget.CoordinatorLayout>
アクティビティまたはフラグメントで、子コンポーザブルと
必須
NestedScrollConnection
:
open class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) findViewById<ComposeView>(R.id.compose_view).apply { setContent { val nestedScrollInterop = rememberNestedScrollInteropConnection() // Add the nested scroll connection to your top level @Composable element // using the nestedScroll modifier. LazyColumn(modifier = Modifier.nestedScroll(nestedScrollInterop)) { items(20) { item -> Box( modifier = Modifier .padding(16.dp) .height(56.dp) .fillMaxWidth() .background(Color.Gray), contentAlignment = Alignment.Center ) { Text(item.toString()) } } } } } } }
子 AndroidView
を含む親コンポーザブル
このシナリオでは、子 AndroidView
を含んでいる親コンポーザブルがある場合の Compose 側のネスト スクロールの相互運用 API の実装を扱います。AndroidView
は、スクロールにおける親である Compose に対しては子になるので NestedScrollDispatcher
を実装し、スクロールにおける子である View
に対しては親になるので NestedScrollingParent3
を実装します。Compose の親のアクション
その後、ネストされたスクロール可能な子から、ネストされたスクロールの差分を受け取ることができる
View
。
次の例は、このスライドでネストされたスクロールの相互運用を実現する方法を示しています。 Compose の折りたたみツールバーとともに表示されます。
@Composable
private fun NestedScrollInteropComposeParentWithAndroidChildExample() {
val toolbarHeightPx = with(LocalDensity.current) { ToolbarHeight.roundToPx().toFloat() }
val toolbarOffsetHeightPx = remember { mutableStateOf(0f) }
// Sets up the nested scroll connection between the Box composable parent
// and the child AndroidView containing the RecyclerView
val nestedScrollConnection = remember {
object : NestedScrollConnection {
override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
// Updates the toolbar offset based on the scroll to enable
// collapsible behaviour
val delta = available.y
val newOffset = toolbarOffsetHeightPx.value + delta
toolbarOffsetHeightPx.value = newOffset.coerceIn(-toolbarHeightPx, 0f)
return Offset.Zero
}
}
}
Box(
Modifier
.fillMaxSize()
.nestedScroll(nestedScrollConnection)
) {
TopAppBar(
modifier = Modifier
.height(ToolbarHeight)
.offset { IntOffset(x = 0, y = toolbarOffsetHeightPx.value.roundToInt()) }
)
AndroidView(
{ context ->
LayoutInflater.from(context)
.inflate(R.layout.view_in_compose_nested_scroll_interop, null).apply {
with(findViewById<RecyclerView>(R.id.main_list)) {
layoutManager = LinearLayoutManager(context, VERTICAL, false)
adapter = NestedScrollInteropAdapter()
}
}.also {
// Nested scrolling interop is enabled when
// nested scroll is enabled for the root View
ViewCompat.setNestedScrollingEnabled(it, true)
}
},
// ...
)
}
}
private class NestedScrollInteropAdapter :
Adapter<NestedScrollInteropAdapter.NestedScrollInteropViewHolder>() {
val items = (1..10).map { it.toString() }
override fun onCreateViewHolder(
parent: ViewGroup,
viewType: Int
): NestedScrollInteropViewHolder {
return NestedScrollInteropViewHolder(
LayoutInflater.from(parent.context)
.inflate(R.layout.list_item, parent, false)
)
}
override fun onBindViewHolder(holder: NestedScrollInteropViewHolder, position: Int) {
// ...
}
class NestedScrollInteropViewHolder(view: View) : ViewHolder(view) {
fun bind(item: String) {
// ...
}
}
// ...
}
次の例は、scrollable
修飾子で、この API を使用する方法を示しています。
@Composable
fun ViewInComposeNestedScrollInteropExample() {
Box(
Modifier
.fillMaxSize()
.scrollable(rememberScrollableState {
// View component deltas should be reflected in Compose
// components that participate in nested scrolling
it
}, Orientation.Vertical)
) {
AndroidView(
{ context ->
LayoutInflater.from(context)
.inflate(android.R.layout.list_item, null)
.apply {
// Nested scrolling interop is enabled when
// nested scroll is enabled for the root View
ViewCompat.setNestedScrollingEnabled(this, true)
}
}
)
}
}
次にある最後の例では、ネスト スクロールの相互運用 API を BottomSheetDialogFragment
で使用して、ドラッグして閉じる動作を実現する方法を示しています。
class BottomSheetFragment : BottomSheetDialogFragment() {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
val rootView: View = inflater.inflate(R.layout.fragment_bottom_sheet, container, false)
rootView.findViewById<ComposeView>(R.id.compose_view).apply {
setContent {
val nestedScrollInterop = rememberNestedScrollInteropConnection()
LazyColumn(
Modifier
.nestedScroll(nestedScrollInterop)
.fillMaxSize()
) {
item {
Text(text = "Bottom sheet title")
}
items(10) {
Text(
text = "List item number $it",
modifier = Modifier.fillMaxWidth()
)
}
}
}
return rootView
}
}
}
注:
rememberNestedScrollInteropConnection()
は、
NestedScrollConnection
必ず指定してください。NestedScrollConnection
は、デルタを Compose レベルから View
レベルに送信します。これにより、
要素がネストされたスクロールに参加できますが、
要素を自動的にスクロールすることもできます。スクロール不可のコンポーザブルに変更
Box
や Column
など、スクロールの差分が自動的に
差分がネストされたスクロール システムに伝播され、
NestedScrollConnection
提供元: rememberNestedScrollInteropConnection()
、
そのため、その差分は親の View
コンポーネントに到達しません。この問題を解決するには
スクロール可能な修飾子もこれらのタイプのネストされた
作成できます。詳しくは、前のセクションのネストされた
スクロールを使用すると、
情報です。
子 ComposeView
を持つ非協力的な親 View
連携しない View とは、View
側で必要な NestedScrolling
インターフェースを実装していない View のことです。つまり、こうした Views
とのネスト スクロールの相互運用は、そのままでは機能しません。連携しない Views
は、RecyclerView
と ViewPager2
です。
あなたへのおすすめ
- 注: JavaScript がオフになっている場合はリンクテキストが表示されます
- ジェスチャーについて
CoordinatorLayout
を Compose に移行する- Compose でビューを使用する