[Modern Android] Jetpack Compose その5

こんにちは！7月にになりましたね。

前回に引き続きJetNewを続いて見ながら、
Jetpack　Composeを理解しようと思います。
出来ればFlutterと比べながらみようと思います。

#今回

HomeScreenのbodyContentを見てみましょう。
drawerContent、topAppBarは前回に確認しました。

> bodyContent = { modifier ->
            HomeScreenContent(postsRepository, modifier)
        }

```kotlin

@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()で処理する。


```kotlin
@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


```kotlin

@Composable
private fun LoadingHomeScreen() {
    Box(modifier = Modifier.fillMaxSize().wrapContentSize(Alignment.Center)) {
        CircularProgressIndicator()
    }
}
```

### Box
- BoxはFlutterのContainerと似てるけど、ちょっと違う感じ。
- Containerと同じですと思ったら、コメントお願いします。

```kotlin
/**
 * 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の感じ。
- 以外に簡単じゃない。


```kotlin

/**
 * 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からは次に。。

```kotlin

@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と同じ名で使える。