Jetpack Compose 1.2.0-rc01
Mori Atsushi
Compose Compiler
rc01からstableとして扱われるtypeとfunctionが増えた
RyuNen344.iconguavaって素で扱ったことはないなぁ。。。。
code:diff
// TODO: FunctionReference
private val stableBuiltinTypes = mapOf(
"kotlin.Pair" to 0b11,
"kotlin.Triple" to 0b111,
"kotlin.Comparator" to 0,
"kotlin.Result" to 0b1,
"kotlin.ranges.ClosedRange" to 0b1,
"kotlin.ranges.ClosedFloatingPointRange" to 0b1,
+ // Guava
+ "com.google.common.collect.ImmutableList" to 0b1,
+ "com.google.common.collect.ImmutableEnumMap" to 0b11,
+ "com.google.common.collect.ImmutableMap" to 0b11,
+ "com.google.common.collect.ImmutableEnumSet" to 0b1,
+ "com.google.common.collect.ImmutableSet" to 0b1,
+ // Kotlinx immutable
+ "kotlinx.collections.immutable.ImmutableList" to 0b1,
+ "kotlinx.collections.immutable.ImmutableSet" to 0b1,
+ "kotlinx.collections.immutable.ImmutableMap" to 0b11,
)
code:diff
// TODO: buildList, buildMap, buildSet, etc.
private val stableProducingFunctions = mapOf(
"kotlin.collections.CollectionsKt.emptyList" to 0,
"kotlin.collections.CollectionsKt.listOf" to 0b1,
"kotlin.collections.CollectionsKt.listOfNotNull" to 0b1,
+ "kotlin.collections.MapsKt.mapOf" to 0b11,
+ "kotlin.collections.MapsKt.emptyMap" to 0,
+ "kotlin.collections.SetsKt.setOf" to 0b1,
+ "kotlin.collections.SetsKt.emptySet" to 0,
)
stableとして扱われると、それを引数に取るcomposableがskippableになる
Mori Atsushi.icon builder functionがstableになるとどういう挙動なのかはよくわかってない…
KotlinのListはMutableList等変更可能である可能性があるのでstableでなかった
以下のcomposable関数は必ずrecomposeされる
code:kotlin
@Composable
fun List(list: List<Int>) {
/* ... */
}
v0.3.5で開発中
本番利用は推奨されないRyuNen344.iconこれは厳しい....
Mori Atsushi.icon ImmutableListはわかるけどPersistentListってなんだ?
ImmutableList等が引数の場合、リストに変更がなければrecomposeされない
code:kotlin
@Composable
fun List(list: ImmutableList<Int>) {
/* ... */
}
ほとんどのケースはListで問題ないと言われている
RyuNen344
@Previewまわり RyuNen344.iconほんとにこれしかなかった;;
下記のType追加
Devices.PHONE
Devices.FOLDABLE
Devices.TABLET
Devices.DESKTOP
独自アノテーションを作成し、Previewアノテーションを付与するとPreviewアノテーションが推移される
code:kotlin
@Preview
annotation class MyAnnotation()
@Composable
@MyAnnotation
// これもPreviewされるようになる
fun Multipreview() {
Surface(color = Color.Red) {
Text("Hello world")
}
}
compose.ui.test Key周りAPI Changes
code:kotlin
@ExperimentalTestApi
interface KeyInjectionScope : InjectionScope {
// 1.2.0追加
val isCapsLockOn: Boolean
// 1.2.0で追加
val isNumLockOn: Boolean
// 1.2.0で追加
val isScrollLockOn: Boolean
fun keyDown(key: Key)
fun keyUp(key: Key)
fun isKeyDown(key: Key): Boolean
}
// この類の拡張関数群が追加
// keysDown, keysUp, withKeyDown, withKeysDown
@ExperimentalTestApi
fun KeyInjectionScope.keysUp(
keys: List<Key>,
pauseDuration: Long = DefaultPauseDurationBetweenKeyPresses
) {
require(keys.size == keys.distinct().size) {
"List of keys must not contain any duplicates."
}
keys.forEachIndexed { idx: Int, key: Key ->
if (idx != 0) advanceEventTime(pauseDuration)
keyUp(key)
}
}
拡張関数群はキーボードを同時に操作している際のテストに使う -> デスクトップやタブレットを意識している
キーイベントをテストに差し込むこと自体はbeta03で追加された
saku
compose.foundation API Changes
New!! OverscrollEffect
オーバースクロール効果をカスタムするinterface
Modifer.scrollable な場所で Modifier.overscrollにOverscrollEffectを渡す
リファレンスのサンプルコード長かった;;
code:Kotlin
import androidx.compose.animation.core.Animatable
import androidx.compose.animation.core.spring
import androidx.compose.foundation.background
import androidx.compose.foundation.gestures.rememberScrollableState
import androidx.compose.foundation.gestures.scrollable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.overscroll
import androidx.compose.material.Text
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.unit.IntOffset
@OptIn(ExperimentalFoundationApi::class)
// our custom offset overscroll that offset the element it is applied to when we hit the bound
// on the scrollable container.
class OffsetOverscrollEffect(val scope: CoroutineScope) : OverscrollEffect {
private val overscrollOffset = Animatable(0f)
override fun consumePreScroll(
scrollDelta: Offset,
pointerPosition: Offset?,
source: NestedScrollSource
): Offset {
// in pre scroll we relax the overscroll if needed
// relaxation: when we are in progress of the overscroll and user scrolls in the
// different direction = substract the overscroll first
val sameDirection = sign(scrollDelta.y) == sign(overscrollOffset.value)
return if (abs(overscrollOffset.value) > 0.5 && !sameDirection && isEnabled) {
val prevOverscrollValue = overscrollOffset.value
val newOverscrollValue = overscrollOffset.value + scrollDelta.y
if (sign(prevOverscrollValue) != sign(newOverscrollValue)) {
// sign changed, coerce to start scrolling and exit
scope.launch { overscrollOffset.snapTo(0f) }
Offset(x = 0f, y = scrollDelta.y + prevOverscrollValue)
} else {
scope.launch {
overscrollOffset.snapTo(overscrollOffset.value + scrollDelta.y)
}
scrollDelta.copy(x = 0f)
}
} else {
Offset.Zero
}
}
override fun consumePostScroll(
initialDragDelta: Offset,
overscrollDelta: Offset,
pointerPosition: Offset?,
source: NestedScrollSource
) {
// if it is a drag, not a fling, add the delta left to our over scroll value
if (abs(overscrollDelta.y) > 0.5 && isEnabled && source == NestedScrollSource.Drag) {
scope.launch {
// multiply by 0.1 for the sake of parallax effect
overscrollOffset.snapTo(overscrollOffset.value + overscrollDelta.y * 0.1f)
}
}
}
override suspend fun consumePreFling(velocity: Velocity): Velocity = Velocity.Zero
override suspend fun consumePostFling(velocity: Velocity) {
// when the fling happens - we just gradually animate our overscroll to 0
if (isEnabled) {
overscrollOffset.animateTo(
targetValue = 0f,
initialVelocity = velocity.y,
animationSpec = spring()
)
}
}
override var isEnabled: Boolean by mutableStateOf(true)
override val isInProgress: Boolean
get() = overscrollOffset.isRunning
// as we're building an offset modifiers, let's offset of our value we calculated
override val effectModifier: Modifier = Modifier.offset {
IntOffset(x = 0, y = overscrollOffset.value.roundToInt())
}
}
val offset = remember { mutableStateOf(0f) }
val scope = rememberCoroutineScope()
// Create the overscroll controller
val overscroll = remember(scope) { OffsetOverscrollEffect(scope) }
// let's build a scrollable that scroll until -512 to 512
val scrollStateRange = (-512f).rangeTo(512f)
Box(
Modifier
.size(150.dp)
.scrollable(
orientation = Orientation.Vertical,
state = rememberScrollableState { delta ->
// use the scroll data and indicate how much this element consumed.
val oldValue = offset.value
// coerce to our range
offset.value = (offset.value + delta).coerceIn(scrollStateRange)
offset.value - oldValue // indicate that we consumed what's needed
},
// pass the overscroll to the scrollable so the data is updated
overscrollEffect = overscroll
)
.background(Color.LightGray),
contentAlignment = Alignment.Center
) {
Text(
offset.value.roundToInt().toString(),
style = TextStyle(fontSize = 32.sp),
modifier = Modifier
// show the overscroll only on the text, not the containers (just for fun)
.overscroll(overscroll)
)
}
LocalOverScrollConfiguration
renamed from LocalOverscrollConfiguration
saku.icon 変わった場所に気づくのに10秒くらいかかった... (OverScroll → Overscroll)
moved compose.foundation.gesture → compose.foundation
composeライブラリはjdk8のデフォルトインターフェースメソッドを使用してビルドされるようになった
foudantion以外のパッケージも同じっぽい
RyuNen344.icon👆テスト周りのinterfaceにも付与されてました
code:diff
+ @JvmDefaultWithCompatibility
interface Animation<T, V : AnimationVector> {
New!! checkScrollableContainerConstraints()
scrollableなコンテナがネストされているかどうかをチェックできる
LazyLayoutでscrollableなカスタムレイアウトを作成するときに使うっぽい
Removed androidx.compose.foundation.lazy.LazyVerticalGrid
now androidx.compose.foundation.lazy.grid.LazyVerticalGrid
Mori Atsushi.icon 強い意志を感じる
https://scrapbox.io/files/62b317ab27c0cc00220daf19.png
気になるポイント
メモ
コメント