LoginSignup
1
0

More than 3 years have passed since last update.

[Modern Android] Jetpack Compose その5

Posted at

こんにちは!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


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

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0