こんにちは!7月にになりましたね。
前回に引き続きJetNewを続いて見ながら、
Jetpack Composeを理解しようと思います。
出来ればFlutterと比べながらみようと思います。
今回
HomeScreenのbodyContentを見てみましょう。
drawerContent、topAppBarは前回に確認しました。
bodyContent = { modifier ->
HomeScreenContent(postsRepository, modifier)
}
@Composable
fun HomeScreen(
postsRepository: PostsRepository,
scaffoldState: ScaffoldState = remember { ScaffoldState() }
) {
Scaffold(
scaffoldState = scaffoldState,
drawerContent = {
AppDrawer(
currentScreen = Screen.Home,
closeDrawer = { scaffoldState.drawerState = DrawerState.Closed }
)
},
topAppBar = {
TopAppBar(
title = { Text(text = "Jetnews") },
navigationIcon = {
IconButton(onClick = { scaffoldState.drawerState = DrawerState.Opened }) {
Icon(vectorResource(R.drawable.ic_jetnews_logo))
}
}
)
},
bodyContent = { modifier ->
HomeScreenContent(postsRepository, modifier)
}
)
}
HomeScreenContent
- HomeScreenContentもComposable関数である。
- PostsRepositoryからデータをひきLoadingHomeScreen()とSwipeToRefreshLayout()で処理する。
@Composable
private fun HomeScreenContent(
postsRepository: PostsRepository,
modifier: Modifier = Modifier
) {
val (postsState, refreshPosts) = refreshableUiStateFrom(postsRepository::getPosts)
if (postsState.loading && !postsState.refreshing) {
LoadingHomeScreen()
} else {
SwipeToRefreshLayout(
refreshingState = postsState.refreshing,
onRefresh = { refreshPosts() },
refreshIndicator = {
Surface(elevation = 10.dp, shape = CircleShape) {
CircularProgressIndicator(Modifier.preferredSize(50.dp).padding(4.dp))
}
}
) {
HomeScreenBodyWrapper(
modifier = modifier,
state = postsState,
onErrorAction = {
refreshPosts()
}
)
}
}
}
LoadingHomeScreen
- CircularProgressIndicator()はFlutterと同じである。
- https://api.flutter.dev/flutter/material/CircularProgressIndicator-class.html
- https://developer.android.com/reference/kotlin/androidx/ui/material/package-summary#circularprogressindicator
@Composable
private fun LoadingHomeScreen() {
Box(modifier = Modifier.fillMaxSize().wrapContentSize(Alignment.Center)) {
CircularProgressIndicator()
}
}
Box
- BoxはFlutterのContainerと似てるけど、ちょっと違う感じ。
- Containerと同じですと思ったら、コメントお願いします。
/**
* A convenience composable that combines common layout and draw logic.
*
* In order to define the size of the [Box], the [androidx.ui.layout.LayoutWidth],
* [androidx.ui.layout.LayoutHeight] and [androidx.ui.layout.LayoutSize] modifiers can be used.
* The [Box] will try to be only as small as its content. However, if it is constrained
* otherwise, [Box] will allow its content to be smaller and will position the content inside,
* according to [gravity].
*
* The specified [padding] will be applied inside the [Box]. In order to apply padding outside
* the [Box], the [androidx.ui.layout.LayoutPadding] modifier should be used.
*
* @sample androidx.ui.foundation.samples.SimpleCircleBox
*
* @param modifier The modifier to be applied to the Box
* @param shape The shape of the box
* @param backgroundColor The [Color] for background with. If [Color.Transparent], there will be no
* background
* @param border [Border] object that specifies border appearance, such as size and color. If
* `null`, there will be no border
* @param padding The padding to be applied inside Box, along its edges. Unless otherwise
* specified, content will be padded by the [Border.size], if [border] is provided
* @param paddingStart sets the padding of the start edge. Setting this will override [padding]
* for the start edge
* @param paddingTop sets the padding of the top edge. Setting this will override [padding] for
* the top edge
* @param paddingEnd sets the padding of the end edge. Setting this will override [padding] for
* the end edge
* @param paddingBottom sets the padding of the bottom edge. Setting this will override [padding]
* for the bottom edge
* @param gravity The gravity of the content inside Box
*/
@Composable
fun Box(
modifier: Modifier = Modifier,
shape: Shape = RectangleShape,
backgroundColor: Color = Color.Transparent,
border: Border? = null,
padding: Dp = border?.size ?: 0.dp,
paddingStart: Dp = Dp.Unspecified,
paddingTop: Dp = Dp.Unspecified,
paddingEnd: Dp = Dp.Unspecified,
paddingBottom: Dp = Dp.Unspecified,
gravity: ContentGravity = ContentGravity.TopStart,
children: @Composable () -> Unit = emptyContent()
) {
val borderModifier =
if (border != null) Modifier.drawBorder(border, shape) else Modifier
val backgroundModifier =
if (backgroundColor != Color.Transparent) {
Modifier.drawBackground(backgroundColor, shape)
} else {
Modifier
}
val paddingModifier =
if (needsPadding(padding, paddingStart, paddingTop, paddingEnd, paddingBottom)) {
Modifier.padding(
if (paddingStart != Dp.Unspecified) paddingStart else padding,
if (paddingTop != Dp.Unspecified) paddingTop else padding,
if (paddingEnd != Dp.Unspecified) paddingEnd else padding,
if (paddingBottom != Dp.Unspecified) paddingBottom else padding
)
} else {
Modifier
}
// TODO(malkov): support ContentColor prorogation (b/148129218)
val columnArrangement = gravity.toColumnArrangement()
val columnGravity = gravity.toColumnGravity()
Column(
modifier = modifier + backgroundModifier + borderModifier + paddingModifier,
verticalArrangement = columnArrangement,
horizontalGravity = columnGravity
) {
children()
}
}
CircularProgressIndicator
- package androidx.ui.materialのProgressIndicator.㏏中にあるComposable関数である。
- LinearProgressIndicatorも見える
- Semantics→Trainsition→Canvas→drawLinearIndicatorBackground→drawLinearIndicatorの感じ。
- 以外に簡単じゃない。
/**
* An indeterminate circular progress indicator that represents continual progress without a defined
* start or end point.
*
* @param color The color of the progress indicator.
* @param strokeWidth The stroke width for the progress indicator.
*/
@Composable
fun CircularProgressIndicator(
modifier: Modifier = Modifier,
color: Color = MaterialTheme.colors.primary,
strokeWidth: Dp = ProgressIndicatorConstants.DefaultStrokeWidth
) {
// TODO(b/154875304) create IndeterminateProgressIndicator in foundation and move the
// semantics there
Semantics(container = true, properties = { accessibilityValue = Strings.InProgress }) {
val stroke = with(DensityAmbient.current) {
Stroke(width = strokeWidth.toPx().value, cap = StrokeCap.square)
}
Transition(
definition = CircularIndeterminateTransition,
initState = 0,
toState = 1
) { state ->
val currentRotation = state[IterationProp]
val baseRotation = state[BaseRotationProp]
val currentRotationAngleOffset = (currentRotation * RotationAngleOffset) % 360f
var startAngle = state[TailRotationProp]
val endAngle = state[HeadRotationProp]
// How long a line to draw using the start angle as a reference point
val sweep = abs(endAngle - startAngle)
// Offset by the constant offset and the per rotation offset
startAngle += StartAngleOffset + currentRotationAngleOffset
startAngle += baseRotation
Canvas(
modifier.padding(CircularIndicatorPadding)
.preferredSize(CircularIndicatorDiameter)
) {
drawIndeterminateCircularIndicator(startAngle, strokeWidth, sweep, color, stroke)
}
}
}
}
SwipeToRefreshLayout
- SwipeToRefreshLayout⇒HomeScreenBodyWrapperの感じ。
- SwipeToRefreshLayoutもUIのComposable関数である。
- StateDraggableからは次に。。
@Composable
fun SwipeToRefreshLayout(
refreshingState: Boolean,
onRefresh: () -> Unit,
refreshIndicator: @Composable() () -> Unit,
content: @Composable() () -> Unit
) {
val size = with(DensityAmbient.current) { SWIPE_DISTANCE_SIZE.toPx().value }
// min is below negative to hide
val min = -size
val max = size * SWIPE_DOWN_OFFSET
StateDraggable(
state = refreshingState,
onStateChange = { shouldRefresh -> if (shouldRefresh) onRefresh() },
anchorsToState = listOf(min to false, max to true),
animationBuilder = TweenBuilder(),
dragDirection = DragDirection.Vertical,
minValue = min,
maxValue = max
) { dragPosition ->
val dpOffset = with(DensityAmbient.current) {
(dragPosition.value * 0.5).px.toDp()
}
Stack {
content()
Box(Modifier.gravity(Alignment.TopCenter).offset(0.dp, dpOffset)) {
if (dragPosition.value != min) {
refreshIndicator()
}
}
}
}
}
終わりに
次はStateDraggableから始めようと思います。
今回はここまでメモします。
つづく
今日分かったこと
CircularProgressIndicatorはFlutterと同じ名で使える。