たくさんの自由帳
Androidのお話
たくさんの自由帳
投稿日 : | 0 日前
文字数(だいたい) : 2338
どうもこんばんわ。
忘れそうなので
Modifier.onGlobalPosition { }
っていうModifier
を使うことで、表示されているコンポーネントの位置が取れる便利なやつなのですが...
ちょっと困った事があって
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContent {
JetpackComposeGlobalPositionOutViewTheme {
HomeScreen()
}
}
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun HomeScreen() {
// コンポーネントの座標
val position = remember { mutableStateOf(IntRect.Zero) }
// ドラッグで移動
val offset = remember { mutableStateOf(IntOffset(0, 0)) }
Scaffold(
topBar = { TopAppBar(title = { Text(text = "JetpackComposeGlobalPositionOutView") }) }
) { paddingValues ->
Box(
modifier = Modifier
.padding(paddingValues)
.fillMaxSize()
) {
Box(
modifier = Modifier
.size(100.dp)
.offset { offset.value }
.background(Color.Red)
// コンポーネントの座標
.onGloballyPositioned {
position.value = it
.boundsInWindow()
.roundToIntRect()
}
// ドラッグで移動
.pointerInput(Unit) {
detectDragGestures { change, dragAmount ->
change.consume()
offset.value = IntOffset(
x = (offset.value.x + dragAmount.x).toInt(),
y = (offset.value.y + dragAmount.y).toInt()
)
}
}
)
Text(
modifier = Modifier.align(Alignment.BottomCenter),
text = """
left = ${position.value.left}
top = ${position.value.top}
right = ${position.value.right}
bottom = ${position.value.bottom}
""".trimIndent()
)
}
}
}
というわけで横に長い、スクロールするような画面を用意しました。
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContent {
JetpackComposeGlobalPositionOutViewTheme {
HomeScreen()
}
}
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun HomeScreen() {
// コンポーネントの座標
val position = remember { mutableStateOf(IntRect.Zero) }
// ドラッグで移動
val offset = remember { mutableStateOf(IntOffset(0, 0)) }
Scaffold(
topBar = { TopAppBar(title = { Text(text = "JetpackComposeGlobalPositionOutView") }) }
) { paddingValues ->
Column(
modifier = Modifier
.padding(paddingValues)
.fillMaxSize()
) {
// 横にスクロールできるように
// スクロールといえばレッツノート
Box(
modifier = Modifier
.weight(1f)
.horizontalScroll(rememberScrollState())
) {
// 横に長ーーーいコンポーネントを置く
Box(
modifier = Modifier
.fillMaxHeight()
.requiredWidth(3000.dp)
) {
// スクロール出来てるか確認用に文字を横にズラーッと並べる
Row {
(0 until 50).forEach {
Text(
modifier = Modifier.weight(1f),
text = it.toString()
)
}
}
Box(
modifier = Modifier
.size(100.dp)
.offset { offset.value }
.background(Color.Red)
// コンポーネントの座標
.onGloballyPositioned {
position.value = it
.boundsInWindow()
.roundToIntRect()
}
// ドラッグで移動
.pointerInput(Unit) {
detectDragGestures { change, dragAmount ->
change.consume()
offset.value = IntOffset(
x = (offset.value.x + dragAmount.x).toInt(),
y = (offset.value.y + dragAmount.y).toInt()
)
}
}
)
}
}
Text(
text = """
left = ${position.value.left}
top = ${position.value.top}
right = ${position.value.right}
bottom = ${position.value.bottom}
""".trimIndent()
)
}
}
}
横並びで文字を入れてあるので、ちゃんとスクロール出来ていることがわかります。
もちろん動かせますよ。
で、ここからです。
赤い四角が画面外に行くようにスクロールすると・・・おや?座標が取れないですね。。。
なんなら画面外に赤い四角を追いやるでもダメですね。
う~~~ん。
こまった。
さて、スクロールして画面外から消えたらなんで座標まで取れなくなるのかと言うと、、、
boundsInWindow()
を使ってるからなんですね。
関数の名前から予想できる通り、Window
(アプリの領域)から見た座標ですね。
Window
(アプリの領域)から消えてしまったらそりゃ取れなくなりますわ、
で、boundsInParent
とか言うのがワンちゃん使える可能性があります。
- Box (スクロールできるやつ)
- Box ( requiredWidth で無理やり画面外にコンポーネントを拡大 )
- Box (赤い四角)
↑ 別にBox
である必要はないですが。
こんな感じに親がすぐrequiredWidth
というか、画面外にはみ出しているコンポーネントの場合はおそらく取れます。
boundsInParent()
を使うように直せばおっけ~
Box(
modifier = Modifier
.size(100.dp)
.offset { offset.value }
.background(Color.Red)
// コンポーネントの座標
.onGloballyPositioned {
position.value = it
.boundsInParent() // これね
.roundToIntRect()
}
// ドラッグで移動
.pointerInput(Unit) {
detectDragGestures { change, dragAmount ->
change.consume()
offset.value = IntOffset(
x = (offset.value.x + dragAmount.x).toInt(),
y = (offset.value.y + dragAmount.y).toInt()
)
}
}
)
先に答えを出すと、はみ出しているコンポーネントでModifier.onGloballyPositioned { }
を使って、LayoutCoordinates
をもらいます。
次に、座標が欲しいコンポーネントのModifier.onGloballyPositioned { }
でも、LayoutCoordinates
をもらいます。
最後に、はみ出しているLayoutCoordinates
でlocalBoundingBoxOf()
を呼び出し、引数に座標が欲しいコンポーネントの方のLayoutCoordinates
を入れると、はみ出しているコンポーネントからみた座標が取れます。
説明難しいのでコード見て。
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContent {
JetpackComposeGlobalPositionOutViewTheme {
HomeScreen()
}
}
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun HomeScreen() {
// コンポーネントの座標
val position = remember { mutableStateOf(IntRect.Zero) }
// ドラッグで移動
val offset = remember { mutableStateOf(IntOffset(0, 0)) }
// 横に長いコンポーネントの LayoutCoordinates
// 画面外にいるコンポーネントの座標の取得に必要
val longComponentLayoutCoordinates = remember { mutableStateOf<LayoutCoordinates?>(null) }
Scaffold(
topBar = { TopAppBar(title = { Text(text = "JetpackComposeGlobalPositionOutView") }) }
) { paddingValues ->
Column(
modifier = Modifier
.padding(paddingValues)
.fillMaxSize()
) {
// 横にスクロールできるように
// スクロールといえばレッツノート
Box(
modifier = Modifier
.weight(1f)
.horizontalScroll(rememberScrollState())
) {
// 横に長ーーーいコンポーネントを置く
Box(
modifier = Modifier
.fillMaxHeight()
.requiredWidth(3000.dp)
.onGloballyPositioned { longComponentLayoutCoordinates.value = it }
) {
// スクロール出来てるか確認用に文字を横にズラーッと並べる
Row {
(0 until 50).forEach {
Text(
modifier = Modifier.weight(1f),
text = it.toString()
)
}
}
if (longComponentLayoutCoordinates.value != null) {
Box(
modifier = Modifier
.size(100.dp)
.offset { offset.value }
.background(Color.Red)
// コンポーネントの座標
.onGloballyPositioned {
// 横に長いコンポーネントから見た座標を取り出す
// localBoundingBoxOf 参照
position.value = longComponentLayoutCoordinates.value!!
.localBoundingBoxOf(it)
.roundToIntRect()
}
// ドラッグで移動
.pointerInput(Unit) {
detectDragGestures { change, dragAmount ->
change.consume()
offset.value = IntOffset(
x = (offset.value.x + dragAmount.x).toInt(),
y = (offset.value.y + dragAmount.y).toInt()
)
}
}
)
}
}
}
Text(
text = """
left = ${position.value.left}
top = ${position.value.top}
right = ${position.value.right}
bottom = ${position.value.bottom}
""".trimIndent()
)
}
}
}
画面外にスクロールしましたが、ちゃんと座標が残ったままです!
boundsInRoot()
の中身をちょっと見てちょっとだけ分かった気がする。
LayoutCoordinates
てのがサイズを測った結果の何からしいんだけど、これにlocalBoundingBoxOf()
って関数があって、
引数に別のコンポーネントのLayoutCoordinates
を渡すとそいつの座標が返ってくる。
boundsInRoot()
は任意のLayoutCoordinates
から根っこのコンポーネントのLayoutCoordinates
が取れるまで再帰的に探して(親のLayoutCoordinates
が取れるので)、
そのあとLayoutCoordinates#localBoundingBoxOf
を呼び出して根っこから見た座標を取っているらしい。
が、別に今回は根っこのコンポーネントじゃなくて任意のコンポーネントのLayoutCoordinates
でいいので、基準とするコンポーネントのLayoutCoordinates
をModifier.onGloballyPositioned { }
で取って使ってる。
ただ、このレイアウト構造だとboundsInParent()
とやってること変わらないのでどっち使っても変化はないです。はい。
複雑なレイアウトだと役に立つかも!
https://github.com/takusan23/JetpackComposeGlobalPositionOutView
スクロール出来てかつ、ドラッグアンドドロップを使った時にonGloballyPositioned
動かなくてハマった。ので書きました。
もっといい方法があったらごめん。