С помощью двух пальцев можно выполнить масштабирование контента и создать эффекта увеличения (при разведении пальцев) или уменьшения масштаба (при сведении пальцев). Этот тип жестов обрабатывается с помощью модификатора transformable(), который принимает в качестве параметра состояние типа TransformableState:
fun Modifier.transformable( state: TransformableState, lockRotationOnZoomPan: Boolean = false, enabled: Boolean = true ): Modifier
Для создания объекта TransformableState
применяется функция rememberTransformableState(), которая принимает функцию с тремя параметрами:
(zoomChange: Float, panChange: Offset, rotationChange: Float) -> Unit
zoomChange
: значение Float, которое обновляется при выполнении жестов масштабирования.
panChange
: значение Offset, которое содержит текущие значения смещения x и y. Это значение обновляется, когда с помощью жестов производится перемещение целевого компонента.
rotationChange
: значение Float, которое представляет текущее изменение угла с помощью жестов вращения.
При вызове функции rememberTransformableState() необходимо объявить все три параметра, даже если их не предполагается использовать. Типичное объявление
TransformableState
, которое отслеживает изменения масштаба, может выглядеть следующим образом:
var scale by remember { mutableStateOf(1f) } val state = rememberTransformableState { scaleChange, offsetChange, rotationChange -> scale *= scaleChange }
Затем созданное состояние можно передать в вызов модификатора transformable()
:
Composable(modifier = Modifier.transformable(state = state) { } )
Например, рассмотрим следующее приложение:
package com.example.helloapp import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.compose.foundation.background import androidx.compose.foundation.gestures.rememberTransformableState import androidx.compose.foundation.gestures.transformable import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.size import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { var scale by remember { mutableStateOf(1f) } val state = rememberTransformableState { scaleChange, offsetChange, rotationChange -> scale *= scaleChange } Box(contentAlignment = Alignment.Center, modifier = Modifier.fillMaxSize()) { Box(Modifier .graphicsLayer(scaleX = scale, scaleY = scale) .transformable(state = state) .background(Color.DarkGray) .size(150.dp) ) } } } }
Здесь внутренний компонент Box использует модификатор transformable()
для отслеживания масштабирования.
По мере выполнения жеста масштабирования состояние масштаба - переменная scale будет обновляться. Чтобы отразить эти изменения, мы обращаемся к графическому слою компонента, установив параметры scaleX и scaleY в текущее состояние масштаба:
Box(Modifier .graphicsLayer(scaleX = scale, scaleY = scale)
Таким образом, с помощью жеста масштабирования мы сможем изменять размер вложенного компонента Box:
Аналогично с помощью того же модификатора transformable()
можно обрабатывать жесты вращения:
package com.example.helloapp import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.compose.foundation.background import androidx.compose.foundation.gestures.rememberTransformableState import androidx.compose.foundation.gestures.transformable import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.size import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { var angle by remember { mutableStateOf(0f) } val state = rememberTransformableState { scaleChange, offsetChange, rotationChange -> angle += rotationChange } Box(contentAlignment = Alignment.Center, modifier = Modifier.fillMaxSize()) { Box(Modifier .graphicsLayer(rotationZ = angle) .transformable(state = state) .background(Color.DarkGray) .size(200.dp) ) } } } }
В данном случае для отслеживания угла поворота определяем переменную angle и затем изменяем ее, прибавляя к ней значение из rotationChange. А у компонента Box устанавливаем угол поворота с помощью
параметра rotationZ
модификатора graphicsLayer
И последний тип трансформаций - перемещение компонента жестами делается аналогично:
package com.example.helloapp import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.compose.foundation.background import androidx.compose.foundation.gestures.rememberTransformableState import androidx.compose.foundation.gestures.transformable import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.size import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { var offset by remember { mutableStateOf(Offset.Zero)} val state = rememberTransformableState { scaleChange, offsetChange, rotationChange -> offset += offsetChange } Box(contentAlignment = Alignment.Center, modifier = Modifier.fillMaxSize()) { Box(Modifier .graphicsLayer( translationX = offset.x, translationY = offset.y ) .transformable(state = state) .background(Color.DarkGray) .size(200.dp) ) } } } }
Здесь для хранения данных о перемещении определяем переменную offset:
var offset by remember { mutableStateOf(Offset.Zero)}
Причем эта переменная представляет объект Offset и будет содержать смещение сразу по обоим осям - X и Y.
При обработке перемещения к этой переменной прибавляем значения перемещения из параметра offsetChange:
val state = rememberTransformableState { scaleChange, offsetChange, rotationChange -> offset += offsetChange }
Для установки привязки компонента Box к значениям из переменной offset используем параметры translationX и translationY функции-модификатора graphicsLayer()
:
Box(Modifier .graphicsLayer( translationX = offset.x, translationY = offset.y )
При необходимости можно объединить обработку нескольких типов жестов:
package com.example.helloapp import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.compose.foundation.background import androidx.compose.foundation.gestures.rememberTransformableState import androidx.compose.foundation.gestures.transformable import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.size import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { var scale by remember { mutableStateOf(1f) } var angle by remember { mutableStateOf(0f) } var offset by remember { mutableStateOf(Offset.Zero)} val state = rememberTransformableState { scaleChange, offsetChange, rotationChange -> scale *= scaleChange angle += rotationChange offset += offsetChange } Box(contentAlignment = Alignment.Center, modifier = Modifier.fillMaxSize()) { Box(Modifier .graphicsLayer( scaleX = scale, scaleY = scale, rotationZ = angle, translationX = offset.x, translationY = offset.y ) .transformable(state = state) .background(Color.DarkGray) .size(200.dp) ) } } } }