Upgrade to Pro — share decks privately, control downloads, hide ads and more …

プロダクトレベルで必要になる Jetpack Compose テクニック

Yuki Anzai
October 19, 2021

プロダクトレベルで必要になる Jetpack Compose テクニック

DroidKaigi 2021

Yuki Anzai

October 19, 2021
Tweet

More Decks by Yuki Anzai

Other Decks in Technology

Transcript

  1. :VLJ"O[BJ !ZBO[N

    %SPJE,BJHJ
    ϓϩμΫτϨϕϧͰඞཁʹͳΔ
    +FUQBDL$PNQPTFςΫχοΫ

    View full-size slide

  2. :VLJ"O[BJ
    w (PPHMF%FWFMPQFS&YQFSUGPS"OESPJE
    w UXJUUFS!ZBO[N
    w CMPHZBO[NCMPHTQPUDPN
    w גࣜձࣾ΢ϑΟΧ

    View full-size slide

  3. ͔ΒGVMM+FUQBDL$PNQPTFͰ։ൃ
    IUUQTCKQ

    View full-size slide

  4. ໨࣍
    w ൚༻తͳςΫχοΫ
    w ը໘ભҠΛͲ͏͢Δ͔/BWJHBUJPO$PNQPTFͱ7JFX.PEFM
    w ཉ͍͠$PNQPTBCMF΍ػೳͷ୳͠ํ
    w ࡉ͔͍5JQT
    w ͍Ζ͍Ζ঺հ͠·͢

    View full-size slide

  5. ը໘ભҠΛͲ͏͢Δ͔

    View full-size slide

  6. ը໘ભҠΛͲ͏͢Δʁ
    ը໘ભҠ DPNQPTFTBNQMFT
    ෳ਺"DUJWJUZ "DUJWJUZભҠ 'SBHNFOUࠩ͠ସ͑ $SBOF
    "DUJWJUZ 'SBHNFOUࠩ͠ସ͑ +FUDIBU +FUTVSWFZ
    "DUJWJUZ $PNQPTF +FU/FXT +FUDBTUFS 3BMMZ 0XM

    View full-size slide

  7. ը໘ભҠͲ͏͢Δʁ
    ը໘ભҠ DPNQPTFTBNQMFT
    ෳ਺"DUJWJUZ "DUJWJUZભҠ 'SBHNFOUࠩ͠ସ͑ $SBOF
    "DUJWJUZ 'SBHNFOUࠩ͠ସ͑ +FUDIBU +FUTVSWFZ
    "DUJWJUZ $PNQPTF +FU/FXT +FUDBTUFS 3BMMZ 0XM

    View full-size slide

  8. ը໘ભҠͲ͏͢Δʁ
    ը໘ભҠ DPNQPTFTBNQMFT
    ෳ਺"DUJWJUZ "DUJWJUZભҠ 'SBHNFOUࠩ͠ସ͑ $SBOF
    "DUJWJUZ 'SBHNFOUࠩ͠ସ͑ +FUDIBU +FUTVSWFZ
    "DUJWJUZ $PNQPTF
    /BWJHBUJPO$PNQPTF +FU/FXT +FUDBTUFS 3BMMZ 0XM
    ͦͷଞ

    View full-size slide

  9. ը໘ભҠͲ͏͢Δʁ
    ը໘ભҠ DPNQPTFTBNQMFT
    ෳ਺"DUJWJUZ "DUJWJUZભҠ 'SBHNFOUࠩ͠ସ͑ $SBOF
    "DUJWJUZ 'SBHNFOUࠩ͠ସ͑ +FUDIBU +FUTVSWFZ
    "DUJWJUZ $PNQPTF
    /BWJHBUJPO$PNQPTF +FU/FXT +FUDBTUFS 3BMMZ 0XM
    ͦͷଞ

    View full-size slide

  10. "DUJWJUZ௚Լͷ7JFX.PEFMͷϥΠϑαΠΫϧ
    "DUJWJUZ
    4DSFFO"
    4DSFFO#
    4DSFFO"
    4DSFFO#
    4DSFFO"
    7JFX.PEFM 4DSFFO#
    7JFX.PEFM
    4DSFFO"
    @Composable


    fun ScreenA(


    viewModel:ScreenAViewModel = viewModel()


    ) {





    }


    @Composable


    fun ScreenB(


    viewModel:ScreenBViewModel = viewModel()


    ) {





    }


    View full-size slide

  11. /BWJHBUJPO$PNQPTFͰͷ7JFX.PEFMͷϥΠϑαΠΫϧ
    4DSFFO"
    4DSFFO#
    4DSFFO"
    4DSFFO#
    4DSFFO"
    7JFX.PEFM
    4DSFFO#
    7JFX.PEFM
    4DSFFO"
    /BW)PTU
    "DUJWJUZ
    4DSFFO#
    7JFX.PEFM
    NavHost(





    ) {


    composable("ScreenA") {


    ScreenA(





    )


    }


    composable("ScreenB") {


    ScreenB(





    )


    }


    }

    View full-size slide

  12. ཉ͍͠$PNQPTBCMF΍ػೳͷ
    ୳͠ํ

    View full-size slide

  13. SFGFSFODFΛΈΔ
    w BOESPJEYDPNQPTFͷ5PQMFWFM
    GVODUJPOTTVNNBSZ
    w BOESPJEYDPNQPTFNBUFSJBMͷ
    $PNQPOFOUT
    IUUQTEFWFMPQFSBOESPJEDPNSFGFSFODFLPUMJOBOESPJEYDPNQPTFNBUFSJBMQBDLBHFTVNNBSZDPNQPOFOUT

    View full-size slide

  14. BDDPNQBOJTUʹͳ͍͔ௐ΂Δ
    w IUUQTHJUIVCDPNHPPHMFBDDPNQBOJTU
    w +FUQBDL$PNQPTFͷศརϥΠϒϥϦू
    w 4XJQFUP3FGSFTI
    w 1BHFS
    w ʜ

    View full-size slide

  15. ,PUMJO4MBDLͷDPNQPTFνϟϯωϧͰݕࡧ͢Δ
    w IUUQTLPUMJOMBOHTMBDLDPN
    w IUUQTLPUMJOMBOHPSHDPNNVOJUZ͔Βট଴ΛϦΫΤετͰ͖·͢
    ΩʔϘʔυ͕දࣔ͞Εͨͱ͖ʹɺϑΥʔΧε͞
    Ε͍ͯΔ5FYU'JFME͕ΩʔϘʔυͷԼʹӅΕͳ
    ͍Α͏ʹࣗಈͰεΫϩʔϧ͢Δํ๏͕͋Δ͔ʁ
    ˣ
    TDSPMMGPDVTͰݕࡧ
    ˣ
    3FMPDBUJPO3FRVFTUFSͱ͍͏ͷ͕͋ΔΒ͍͠

    View full-size slide

  16. $PNQPTFͷαϯϓϧίʔυΛಡΉ
    w $PNQPTFͷػೳ͸αϯϓϧ͕༻ҙ͞Ε͍ͯΔ͜ͱ͕ଟ͍
    w ,PUMJOTMBDL΍TUBDLPWFS
    fl
    PXͰݟ͚ͭͨػೳͷαϯϓϧ͕ͳ͍͔
    "OESPJE$PEF4FBSDIͰௐ΂Δ
    w IUUQTDTBOESPJEDPNBOESPJEYQMBUGPSNGSBNFXPSLTTVQQPSU
    BOESPJEYNBJODPNQPTF
    w

    View full-size slide

  17. +FUQBDL$PNQPTF3PBENBQΛνΣοΫ͢Δ
    w ࠓޙͷ࣮૷༧ఆ͕ެ։͞Ε͍ͯΔ
    w ࣗ෼Ͱ࣮૷͢Δ͔ɺ༻ҙ͞ΕΔ·Ͱ଴͔ͭͷ൑அࡐྉʹͳΔ
    IUUQTEFWFMPQFSBOESPJEDPNKFUQBDLBOESPJEYDPNQPTFSPBENBQ

    View full-size slide

  18. ࣗ෼Ͱ࣮૷͢ΔSEQBSUZMJCSBSZΛ୳͢
    w ࣗ෼Ͱ࣮૷͢Δ
    w άϥϑͱ͔ˠ$BOWBTDPNQPTBCMF .PEJ
    fi
    FSESBX99

    w ಠࣗͷαΠζɾ഑ஔˠ-BZPVUDPNQPTBCMF .PEJ
    fi
    FSMBZPVU

    w SEQBSUZMJCSBSZ
    w ྫʣඇಉظը૾ಡΈࠐΈˠDPJM

    View full-size slide

  19. /BWJHBUJPO$PNQPTFؔ܎ͷ
    ςΫχοΫ

    View full-size slide

  20. ભҠઌͷࢦఆΛUZQFTBGFʹ͍ͨ͠
    NavHost
    (


    )
    {


    composable("item/{id}")
    {

    val arguments = requireNotNull(it.arguments
    )

    val itemId = ItemId(requireNotNull(arguments.getString("id"))
    )

    ItemDetailScreen
    (



    )

    }

    }
    navController.navigate("item/${item.id.value}")
    /BWJHBUJPO$PNQPTFͰ͸ભҠઌΛจࣈྻͰࢦఆ͢Δ
    Ͳ͔͜Ͱ

    View full-size slide

  21. ભҠઌͷࢦఆΛUZQFTBGFʹ͍ͨ͠
    NavHost
    (


    )
    {


    composable("item/{id}")
    {

    val arguments = requireNotNull(it.arguments
    )

    val itemId = ItemId(requireNotNull(arguments.getString("id"))
    )

    ItemDetailScreen
    (



    )

    }

    }
    navController.navigate("item/${item.id.value}")
    /BWJHBUJPO$PNQPTFͰ͸ભҠઌΛจࣈྻͰࢦఆ͢Δ
    Ͳ͔͜Ͱ
    ભҠઌͷจࣈྻΛͭ͘Δͱ͖ʹؒҧ͑ͦ͏
    JEͷܕΛ੍ݶͰ͖ͳ͍

    View full-size slide

  22. fun NavHostController.navigateToItemDetail(id: ItemId)
    {

    navigate("item/${id.value}"
    )

    }

    fun NavGraphBuilder.itemDetailNavGraph
    (

    navController: NavHostControlle
    r

    )
    {

    composable("item/{id}")
    {

    val arguments = requireNotNull(it.arguments
    )

    val itemId = ItemId(requireNotNull(arguments.getString("id"))
    )

    ItemDetailScreen
    (

    // ..
    .

    )

    }

    }
    ભҠઌͷࢦఆΛUZQFTBGFʹ͍ͨ͠
    /BW)PTUͱ͸ผͷϑΝΠϧͰ
    ભҠઌͷจࣈྻͷੜ੒ॲཧΛભҠઌͷఆٛͱಉ͡ͱ͜Ζʹॻ͘
    ͜ͷϝιουܦ༝ͰભҠ͢ΔͷͰ
    ભҠઌͷจࣈྻΛؒҧ͑ͳ͍
    JEͷܕΛ੍ݶͰ͖Δ

    View full-size slide

  23. ભҠઌͷࢦఆΛUZQFTBGFʹ͍ͨ͠
    NavHost
    (


    )
    {


    itemDetailNavGraph(navController
    )

    }
    Ͳ͔͜Ͱ
    navController.navigateToItemDetail(itemId) *UFN*Eܕ͔͠౉ͤͳ͍

    View full-size slide

  24. ॳճ͚ͩνϡʔτϦΞϧը໘Λग़͍ͨ͠
    OBWJHBUF5P6TFS

    νϡʔτϦΞϧ
    ະදࣔ
    νϡʔτϦΞϧදࣔࡁΈ
    νϡʔτϦΞϧ
    ը໘
    Ϣʔβʔ
    ը໘

    View full-size slide

  25. ॳճ͚ͩνϡʔτϦΞϧը໘Λग़͍ͨ͠
    OBWJHBUF5P6TFS

    νϡʔτϦΞϧ
    ະදࣔ
    νϡʔτϦΞϧදࣔࡁΈ
    લͷը໘
    લͷը໘
    νϡʔτϦΞϧ
    ը໘
    લͷը໘
    Ϣʔβʔ
    ը໘

    View full-size slide

  26. ॳճ͚ͩνϡʔτϦΞϧը໘Λग़͍ͨ͠
    OBWJHBUF5P6TFS

    νϡʔτϦΞϧ
    ະදࣔ
    νϡʔτϦΞϧදࣔࡁΈ
    લͷը໘
    લͷը໘
    νϡʔτϦΞϧ
    ը໘
    લͷը໘
    Ϣʔβʔ
    ը໘
    ͜͜ΛͲ͏͢Δ͔

    View full-size slide

  27. ॳճ͚ͩνϡʔτϦΞϧը໘Λग़͍ͨ͠
    OBWJHBUF5P6TFS

    νϡʔτϦΞϧ
    ະදࣔ
    νϡʔτϦΞϧදࣔࡁΈ
    લͷը໘
    લͷը໘
    νϡʔτϦΞϧ
    ը໘
    લͷը໘
    Ϣʔβʔ
    ը໘
    લͷը໘
    ൑ఆ༻ը໘

    View full-size slide

  28. ॳճ͚ͩνϡʔτϦΞϧը໘Λग़͍ͨ͠
    OBWJHBUF5P6TFS

    νϡʔτϦΞϧ
    ະදࣔ
    νϡʔτϦΞϧදࣔࡁΈ
    લͷը໘
    νϡʔτϦΞϧ
    ը໘
    Ϣʔβʔ
    ը໘
    લͷը໘
    ൑ఆ༻ը໘
    OFTUFEHSBQI

    View full-size slide

  29. fun NavGraphBuilder.userNavGraph(


    navController: NavHostController


    ) {


    navigation(


    route = "user",


    startDestination = "user/top"


    ) {


    composable("user/top") {


    LaunchedEffect(Unit) {


    val isTutorialShown = …


    if (isTutorialShown) {


    navController.navigate("user/main") {


    popUpTo("user")


    }


    } else {


    navController.navigate("user/tutorial") {


    popUpTo("user")


    }


    }


    }


    }


    composable("user/tutorial") {





    }


    composable("user/main") {





    }




    OFTUFEHSBQI
    OBWJHBUF lVTFSz
    ͕ݺ͹ΕΔͱlVTFSUPQz൑ఆ༻ը໘͕
    දࣔ͞ΕΔ
    ൑ఆ༻ը໘͔ΒνϡʔτϦΞϧը໘΍
    Ϣʔβʔը໘ʹߦ͘ͱ͖ʹ
    QPQ6Q5P lVTFSz
    ͢Δ͜ͱͰ
    ൑ఆ༻ը໘͕όοΫελοΫ͔Β
    QPQ͞ΕΔ

    View full-size slide

  30. ެࣜυΩϡϝϯτʹ
    ʢ͋·Γʣॻ͍ͯͳ͍5JQT

    View full-size slide

  31. .BUFSJBM5IFNFؔ܎

    View full-size slide

  32. จࣈ৭͕൒ಁ໌ʹͳΒͳ͍Α͏ʹ͍ͨ͠
    w എܠ্ͷจࣈ৭΍ΞΠίϯͷ৭͸গ͠ಁ໌ʹͳ͍ͬͯΔ
    MaterialTheme
    {

    Surface(color = MaterialTheme.colors.background)
    {

    // contentColor ͸ MaterialTheme.colors.onBackground (= Color.Black
    )

    Column
    {

    // Color.Black.copy(alpha = 0.87f) ~= #21212
    1

    Text("Jetpack"
    )

    // Color.Blac
    k

    Text(“Compose", color = MaterialTheme.colors.onBackground
    )

    }

    }

    }

    View full-size slide

  33. -PDBM$POUFOU"MQIB͕ద༻͞Ε͍ͯΔ
    w .BUFSJBM5IFNFͰ-PDBM$POUFOU"MQIBʹ$POUFOU"MQIBIJHI͕ηοτ͞
    Ε͍ͯΔ

    View full-size slide

  34. -PDBM$POUFOU"MQIB͕ద༻͞Ε͍ͯΔ
    w .BUFSJBM5IFNFͰ-PDBM$POUFOU"MQIBʹ$POUFOU"MQIBIJHI͕ηοτ͞
    Ε͍ͯΔ
    w $POUFOU"MQIBʹ͸IJHI NFEJVN EJTBCMFE͕͋Δ
    w $POUFOU"MQIBͷ֤஋͸-PDBM$POUFOU$PMPSͷMVNJOBODFʢ໌Δ͞ʣͱ
    UIFNF MJHIUPSEBSL
    Ͱܾ·Δ

    View full-size slide

  35. UIFNF -PDBM$POUFOU$PMPS
    $POUFOU"MQIB
    IJHI NFEJVN EJTBCMFE
    MJHIU
    MVNJOBODF G G G
    MVNJOBODF G G G
    EBSL
    MVNJOBODF G G G
    MVNJOBODF G G G

    View full-size slide

  36. MaterialTheme
    {

    Surface(color = MaterialTheme.colors.background)
    {

    // contentColor ͸ MaterialTheme.colors.onBackground (= Color.Black
    )

    Column
    {

    // Color.Black.copy(alpha = 0.87f) ~= #21212
    1

    Text("Jetpack"
    )

    // Color.Blac
    k

    Text(“Compose", color = MaterialTheme.colors.onBackground
    )

    }

    }

    }
    ྫʣMJHIUUIFNFͰDPOUFOU$PMPS͕ࠇʢ$PMPS#MBDLʣͷ৔߹ɺMVNJOBODF͸ҎԼ
    ˠ4VSGBDF಺ͷจࣈʢ5FYUʣ΍ΞΠίϯʢ*DPOʣ͸গ͠ʢGʣಁ໌ʹͳΔ

    View full-size slide

  37. MaterialTheme
    {

    CompositionLocalProvider
    (

    LocalContentAlpha provides 1f
    ,

    )
    {

    Surface(color = MaterialTheme.colors.background)
    {

    Column
    {

    // Color.Blac
    k

    Text("Jetpack"
    )

    // Color.Blac
    k

    Text("Compose", color = MaterialTheme.colors.onBackground
    )

    }

    }

    }

    }
    $PNQPTJUJPO-PDBM1SPWJEFSͰ-PDBM$POUFOU"MQIBΛ্ॻ͖͢Δͱෆಁ໌ʹͰ͖Δ

    View full-size slide

  38. @Composabl
    e

    fun MyAppTheme(…) {


    MaterialTheme
    (


    )
    {

    CompositionLocalProvider
    (

    LocalContentAlpha provides 1f
    ,

    )
    {

    content(
    )

    }

    }

    }
    ΞϓϦͷςʔϚʹ૊ΈࠐΉͷ΋͋Γ
    ͨͩ͠ɺಛఆͷ$PNQPTBCMF಺Ͱ
    -PDBM$POUFOU"MQIBʹ໌ࣔతʹ
    $POUFOU"MQIBIJHIͳͲΛࢦఆ͍ͯ͠Δ΋
    ͷʢྫ5PQ"QQ#BSʣ͕͋ΔͷͰɺશͯͰ
    ஔ͖׵͑ΒΕΔΘ͚Ͱ͸ͳ͍
    MyAppTheme
    {

    Surface(color = MaterialTheme.colors.background)
    {

    Column
    {

    // LocalContentAlpha ͸ 1
    f

    Text("Hello"
    )

    }

    }

    }

    View full-size slide

  39. 5PQ"QQ#BSͷBDUJPOTΛUJUMFͱಉ͡ಁ໌౓ʹ͍ͨ͠
    OBWJHBUJPO*DPO΍UJUMFʹൺ΂ͯബ͍
    ͳΜͰʁ

    View full-size slide

  40. 5PQ"QQ#BSͷBDUJPOTΛUJUMFͱಉ͡ಁ໌౓ʹ͍ͨ͠
    @Composabl
    e

    fun TopAppBar
    (


    )
    {

    AppBar
    (



    )
    {


    Row
    (


    )
    {

    ProvideTextStyle(value = MaterialTheme.typography.h6)
    {

    CompositionLocalProvider
    (

    LocalContentAlpha provides ContentAlpha.high
    ,

    content = titl
    e

    )

    }

    }

    CompositionLocalProvider(LocalContentAlpha provides ContentAlpha.medium)
    {

    Row
    (

    Modifier.fillMaxHeight()
    ,

    horizontalArrangement = Arrangement.End
    ,

    verticalAlignment = Alignment.CenterVertically
    ,

    content = action
    s

    )

    }

    OBWJHBUJPO*DPO΍UJUMF͸
    $POUFOU"MQIBIJHI
    BDUJPOT͸
    $POUFOU"MQIBNFEJVN

    View full-size slide

  41. 5PQ"QQ#BSͷBDUJPOTΛUJUMFͱಉ͡ಁ໌౓ʹ͍ͨ͠
    TopAppBar
    (

    title = { Text("AppBarSample") }
    ,

    navigationIcon =
    {

    IconButton(onClick = { })
    {

    Icon
    (

    Icons.Default.ArrowBack
    ,

    contentDescription = "back"
    ,

    )

    }

    }
    ,

    actions =
    {

    CompositionLocalProvider(LocalContentAlpha provides ContentAlpha.high)
    {

    IconButton(onClick = { })
    {

    Icon
    (

    Icons.Default.Share
    ,

    contentDescription = "share"
    ,

    )

    }

    }

    }

    )
    $POUFOU"MQIBIJHI
    ʹࠩ͠ସ͑Δ

    View full-size slide

  42. TFMFDUBCMF*UFN#BDLHSPVOE#PSEFSMFTT
    w SFNFNCFS3JQQMFͰCPVOEFEʹGBMTFΛࢦఆͨ͠
    ΋ͷΛ.PEJ
    fi
    FSDMJDLBCMFͷJOEJDBUJPOʹࢦఆ͢Δ
    Modifier.clickable
    (

    interactionSource = remember { MutableInteractionSource() }
    ,

    indication = rememberRipple(bounded = false, radius = 40.dp
    )

    )
    {

    }

    View full-size slide

  43. 3JQQMFͷ৭Λม͍͑ͨ
    w SFNFNCFS3JQQMFͰDPMPSΛࢦఆͨ͠΋ͷΛ
    .PEJ
    fi
    FSDMJDLBCMFͷJOEJDBUJPOʹࢦఆ͢Δ
    Modifier.clickable
    (

    interactionSource = remember { MutableInteractionSource() }
    ,

    indication = rememberRipple(color = MaterialTheme.colors.secondary
    )

    )
    {

    }

    View full-size slide

  44. w $PNQPTJUJPO-PDBM1SPWJEFSͰ-PDBM3JQQMF5IFNF
    Λࠩ͠ସ͑Δ
    3JQQMFͷBMQIB΋ม͍͑ͨ
    private val MyRippleTheme = object : RippleTheme
    {

    @Composabl
    e

    override fun defaultColor(): Color
    {

    return MaterialTheme.colors.secondar
    y

    }

    @Composabl
    e

    override fun rippleAlpha(): RippleAlpha
    {

    return RippleAlpha(…
    )

    }

    }

    @Composabl
    e

    fun Sample()
    {

    CompositionLocalProvider(LocalRippleTheme provides MyRippleTheme)
    {



    }

    }

    View full-size slide

  45. 3JQQMFͷσϑΥϧτͷ৭ͱBMQIB
    UIFNF -PDBM$POUFOU$PMPS DPMPS BMQIB
    MJHIU
    MVNJOBODF
    -PDBM$POUFOU$PMPS
    -JHIU5IFNF)JHI$POUSBTU3JQQMF"MQIB
    MVNJOBODF -JHIU5IFNF-PX$POUSBTU3JQQMF"MQIB
    EBSL
    MVNJOBODF $PMPS8IJUF
    %BSL5IFNF3JQQMF"MQIB
    MVNJOBODF -PDBM$POUFOU$PMPS

    View full-size slide

  46. $PNQPTBCMFؔ܎

    View full-size slide

  47. Լʹ͋Δཁૉ͕λοϓΛरΘͳ͍Α͏ʹ͍ͨ͠
    w ॏͶ͚ͨͩͩͱλοϓ͸Լʹൈ͚Δ
    Box(contentAlignment = Alignment.Center)
    {

    Button(onClick = { /*TODO*/ })
    {

    Text("Button"
    )

    }

    Box
    (

    modifier = Modifie
    r

    .size(200.dp
    )

    .background(color = Color.Red.copy(alpha = 0.3f)
    )

    )
    {


    }

    }

    View full-size slide

  48. Լʹ͋Δཁૉ͕λοϓΛरΘͳ͍Α͏ʹ͍ͨ͠
    w .PEJ
    fi
    FSQPJOUFS*OQVU
    ͰλοϓΛर͏
    Box(contentAlignment = Alignment.Center)
    {

    Button(onClick = { /*TODO*/ })
    {

    Text("Button"
    )

    }

    Box
    (

    modifier = Modifie
    r

    .size(200.dp
    )

    .background(color = Color.Red.copy(alpha = 0.3f)
    )

    .pointerInput(Unit) {
    }

    )
    {


    }

    }

    View full-size slide

  49. Լʹ͋Δཁૉ͕λοϓΛरΘͳ͍Α͏ʹ͍ͨ͠
    w .PEJ
    fi
    FSQPJOUFS*OQVU
    ͰλοϓΛर͏
    w ·ͨ͸ɺ্ʹ͘ΔཁૉΛ4VSGBDFͰғΉ
    @Composabl
    e

    fun Surface
    (


    )
    {

    Surface
    (


    clickAndSemanticsModifier = Modifie
    r

    .semantics(mergeDescendants = false) {
    }

    .pointerInput(Unit) {
    }

    )

    }

    View full-size slide

  50. Լʹ͋Δཁૉ͕λοϓΛरΘͳ͍Α͏ʹ͍ͨ͠
    w .PEJ
    fi
    FSQPJOUFS*OQVU
    ͰλοϓΛर͏
    w ·ͨ͸ɺ্ʹ͘ΔཁૉΛ4VSGBDFͰғΉ
    Box(contentAlignment = Alignment.Center)
    {

    Button(onClick = { /*TODO*/ })
    {

    Text("Button"
    )

    }

    Surface
    (

    color = Color.Red.copy(alpha = 0.3f)
    ,

    modifier = Modifie
    r

    .size(200.dp
    )

    )
    {


    }

    }

    View full-size slide

  51. ԣฒͼͷ5FYUΛCBTFMJOFͰἧ͍͑ͨ
    w 3PX4DPQFͷ.PEJ
    fi
    FSBMJHO#Z#BTFMJOF
    Λ࢖͏
    Row
    {

    Text
    (

    text = "Hello"
    ,

    modifier = Modifier.alignByBaseline(
    )

    )

    Text
    (

    text = "Jetpack"
    ,

    fontSize = 32.sp
    ,

    modifier = Modifier.alignByBaseline(
    )

    )

    Text
    (

    text = "Compose"
    ,

    fontSize = 20.sp
    ,

    modifier = Modifier.alignByBaseline(
    )

    )

    }

    View full-size slide

  52. 4FMFDUJPO$POUBJOFSͷબ୒࣌ͷ৭Λม͍͑ͨ
    w .BUFSJBM5IFNFͰDPMPSTͷQSJNBSZΛ্ॻ͖͢Δ
    val primary = MaterialTheme.colors.primar
    y

    val secondary = MaterialTheme.colors.secondar
    y

    MaterialTheme
    (

    colors = MaterialTheme.colors.copy(primary = secondary
    )

    )
    {

    SelectionContainer
    {

    Text
    (

    text = "Hello DroidKaigi"
    ,

    fontSize = 32.sp
    ,

    color = primar
    y

    )

    }

    }
    બ୒จࣈͷCBDLHSPVOEDPMPSͷBMQIB஋Λ
    QSJNBSZͷ৭ʹԠ͍͍ͯ͡ײ͡ʹܭࢉͯ͘͠ΕΔ

    View full-size slide

  53. 4FMFDUJPO$POUBJOFSͷબ୒࣌ͷ৭Λม͍͑ͨ
    w -PDBM5FYU4FMFDUJPO$PMPSTΛࠩ͠ସ͑Δ
    val textSelectionColors = TextSelectionColors
    (

    handleColor = MaterialTheme.colors.secondary
    ,

    backgroundColor = MaterialTheme.colors.secondary.copy(alpha = 0.2f
    )

    )

    CompositionLocalProvider(LocalTextSelectionColors provides textSelectionColors)
    {

    SelectionContainer
    {

    Text
    (

    text = "Hello DroidKaigi"
    ,

    fontSize = 32.sp
    ,

    color = MaterialTheme.colors.primar
    y

    )

    }

    }
    બ୒จࣈͷCBDLHSPVOEDPMPSͷBMQIB஋΋
    ࣗ෼Ͱௐ੔͢Δ

    View full-size slide

  54. 4QJOOFSͬͯͳ͍ͷʁˠ%SPQEPXO.FOV
    var selected by remember { mutableStateOf("not selected")
    }

    var expanded by remember { mutableStateOf(false)
    }

    OutlinedButton(onClick = { expanded = true })
    {


    }

    DropdownMenu
    (

    expanded = expanded
    ,

    onDismissRequest = { expanded = false
    }

    )
    {

    DropdownMenuItem
    (

    onClick =
    {

    selected = "Cupcake
    "

    expanded = fals
    e

    }

    )
    {

    Text("Cupcake"
    )

    }


    }

    View full-size slide

  55. 0WFSGMPXNFOVͬͯͳ͍ͷʁˠ%SPQEPXO.FOV
    Scaffold
    (

    topBar =
    {

    TopAppBar
    (


    ,

    actions =
    {

    var expanded by remember { mutableStateOf(false)
    }

    IconButton(onClick = { expanded = true })
    {

    Icon
    (

    Icons.Default.MoreVert
    ,

    contentDescription = "more"
    ,

    )

    }

    DropdownMenu
    (

    expanded = expanded
    ,

    onDismissRequest = { expanded = false
    }

    )
    {

    DropdownMenuItem(…
    )


    }

    }

    )

    }

    View full-size slide

  56. ಠࣗσβΠϯͷ5FYU'JFMEΛ࡞Γ͍ͨ
    w #BTJD5FYU'JFMEΛ࢖͏
    BasicTextField
    (

    value = value
    ,

    onValueChange = onValueChange
    ,

    modifier = modifier
    ,

    decorationBox =
    {

    Box
    (

    modifier = Modifie
    r

    .background
    (

    Color.LightGray
    ,

    RoundedCornerShape(4.dp
    )

    )

    .padding(16.dp
    )

    )
    {

    it(
    )

    }

    }

    )

    View full-size slide

  57. 4IBEPXʹ৭Λ͚͍ͭͨ
    w .PEJ
    fi
    FSTIBEPX
    Ͱ͸৭ΛࢦఆͰ͖ͳ͍
    w 1BJOUBT'SBNFXPSL1BJOU
    ͱTFU4IBEPX-BZFS
    Λ࢖͏
    val modifier = Modifier.drawBehind
    {

    drawIntoCanvas
    {

    val paint = Paint(
    )

    val frameworkPaint = paint.asFrameworkPaint(
    )

    frameworkPaint.color = paintColo
    r

    frameworkPaint.setShadowLayer
    (

    shadowRadius.toPx()
    ,

    offsetX.toPx()
    ,

    offsetY.toPx()
    ,

    shadowColo
    r

    )

    it.drawRect
    (

    0f, 0f, size.width, size.height, pain
    t

    )

    }

    }

    View full-size slide

  58. "MFSU%JBMPHΛग़͍ͨ͠
    var showAlertDialog by remember { mutableStateOf(false)
    }


    if (showAlertDialog)
    {

    AlertDialog
    (

    title = { Text("Dialog title") }
    ,

    text = { Text("Dialog body text") }
    ,

    confirmButton =
    {

    TextButton(onClick = { … })
    {

    Text("Accept"
    )

    }

    }
    ,

    dismissButton =
    {

    TextButton(onClick = { showAlertDialog = false })
    {

    Text("Cancel"
    )

    }

    }
    ,

    onDismissRequest = { showAlertDialog = false
    }

    )

    }

    View full-size slide

  59. ೚ҙͷ%JBMPHΛग़͍ͨ͠
    Dialog(onDismissRequest = { showDialog = false })
    {

    Surface
    (

    modifier = Modifier
    ,

    shape = MaterialTheme.shapes.medium
    ,

    color = MaterialTheme.colors.surface
    ,

    )
    {

    Column(modifier = Modifier.padding(vertical = 8.dp))
    {

    Text
    (

    text = "ΞϧόϜ͔Βબ୒"
    ,

    modifier = …
    )

    Text
    (

    text = "ը૾Λ࡟আ"
    ,

    modifier = …
    )

    }

    }

    }

    View full-size slide

  60. %BUF1JDLFS5JNF1JDLFS%JBMPHΛग़͍ͨ͠
    Button
    (

    onClick =
    {

    DatePickerDialog
    (

    context
    ,

    { _, year, monthOfYear, dayOfMonth -
    >


    }
    ,

    date.year
    ,

    date.month - 1
    ,

    date.day
    ,

    )

    .show(
    )

    }
    )
    {

    Text(date.dateText
    )

    }
    ৭͸5IFNFͷ
    DPMPS4FDPOEBSZ

    View full-size slide

  61. %BUF1JDLFS5JNF1JDLFS%JBMPHΛग़͍ͨ͠
    @Composabl
    e

    fun DatePickerDialog
    (

    onDismissRequest: () -> Unit
    ,

    date: MyDate
    ,

    onDateChange: (MyDate) -> Unit
    ,

    )
    {

    Dialog(onDismissRequest = onDismissRequest)
    {

    Surface(shape = MaterialTheme.shapes.medium)
    {

    Column
    {

    var editDate by remember { mutableStateOf(date)
    }

    AndroidView(factory =
    {

    DatePicker(it).apply
    {

    updateDate(editDate.year, …
    )

    setOnDateChangedListener { … -
    >

    editDate = …
    }

    }

    })

    }

    }

    }

    View full-size slide

  62. ϨΠΞ΢τؔ܎

    View full-size slide

  63. #PUUPN4IFFUΛग़͍ͨ͠
    w NBUFSJBMJPDPNQPOFOUTTIFFUTCPUUPNͷछྨʹରԠͨ͠$PNQPTBCMF
    w 4UBOEBSECPUUPNTIFFUˠ#PUUPN4IFFU4DB
    ff
    PME
    w .PEBMCPUUPNTIFFUˠ.PEBM#PUUPN4IFFU-BZPVU
    w #PUUPN%SBXFS͸#PUUPN"QQ#BSͰ࢖͏
    w NBUFSJBMJPDPNQPOFOUTOBWJHBUJPOESBXFSCPUUPNESBXFS

    View full-size slide

  64. .PEBM#PUUPN4IFFU-BZPVU
    ModalBottomSheetLayout
    (

    sheetState = sheetState
    ,

    sheetContent =
    {



    }
    ,

    modifier = Modifier.fillMaxSize(
    )

    )
    {

    Scaffold
    {



    }

    }
    EJN͞ΕΔͷ͸DPOUFOUʹࢦఆͨ͠$PNQPTBCMF෦෼͚ͩ
    TUBUVT#BS΋EJN͢ΔͳΒ
    WindowCompat.setDecorFitsSystemWindows(window, false
    )

    View full-size slide

  65. TUBUVTCBS OBWJHBUJPOCBSͷαΠζΛ࢖͍͍ͨ
    w BDDPNQBOJTUͷJOTFUTΛ࢖͏
    w HPPHMFHJUIVCJPBDDPNQBOJTUJOTFUT
    w QBEEJOH.PEJ
    fi
    FSͰऔಘ
    w .PEJ
    fi
    FSTZTUFN#BST1BEEJOH
    .PEJ
    fi
    FSTUBUVT#BST1BEEJOH

    .PEJ
    fi
    FSOBWJHBUJPO#BST1BEEJOH

    w IFJHIUͰऔಘ
    w .PEJ
    fi
    FSTUBUVT#BS)FJHIU
    .PEJ
    fi
    FSOBWJHBUJPO#BST)FJHIU
    .PEJ
    fi
    FSOBWJHBUJPO#BST8JEUI

    w 1BEEJOH7BMVFTͰऔಘ
    w SFNFNCFS*OTFUT1BEEJOH7BMVFT

    rememberInsetsPaddingValues
    (

    insets = LocalWindowInsets.current.navigationBars
    )

    View full-size slide

  66. $PMMBQTJOH5PPMCBS͕΄͍͠ʢ$PMVNOʣ
    Box(modifier = Modifier.fillMaxSize())
    {

    val scrollState = rememberScrollState(
    )

    Column(modifier = Modifier.verticalScroll(scrollState))
    {

    Spacer(modifier = Modifier.height(expandedHeight)
    )


    }

    Surface
    (


    modifier = Modifie
    r

    .height(expandedHeight
    )

    .offset
    {

    val y = (-scrollState.value
    )

    .coerceIn(-(expandedHeight - appBarHeight).roundToPx(), 0)
    IntOffset(0, y
    )

    }

    )
    {

    Box(modifier = Modifier.fillMaxSize())
    {

    TopAppBar(…)
    }

    }

    }

    View full-size slide

  67. $PMMBQTJOH5PPMCBS͕΄͍͠ʢ-B[Z$PMVNOʣ
    Box(modifier = Modifier.fillMaxSize())
    {

    val listState = rememberLazyListState(
    )

    LazyColumn(state = listState)
    {

    item
    {

    Spacer(modifier = Modifier.height(expandedHeight)
    )

    }

    }

    Surface
    (


    modifier = Modifie
    r

    .height(expandedHeight
    )

    .offset
    {

    val scrollValue = if (listState.firstVisibleItemIndex == 0)
    {

    listState.firstVisibleItemScrollOffse
    t

    } else
    {

    Int.MAX_VALU
    E

    }

    val y = (-scrollValue
    )

    .coerceIn(-(expandedHeight - appBarHeight).roundToPx(), 0
    )

    IntOffset(0, y
    )

    }

    {

    View full-size slide

  68. ԼʹεΫϩʔϧͨ͠ͱ͖ʹग़ͯ͘Δ5PPMCBS͕΄͍͠
    val toolbarOffsetY = remember { mutableStateOf(0f)
    }

    val nestedScrollConnection = remember
    {

    object : NestedScrollConnection
    {

    override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset
    {

    val newOffset = toolbarOffsetY.value + available.y
    toolbarOffsetY.value = newOffset.coerceIn(-toolbarHeightPx, 0f
    )

    return Offset.Zer
    o

    }

    }

    }

    Box(modifier = Modifier.fillMaxSize().nestedScroll(nestedScrollConnection))
    {

    LazyColumn(contentPadding = PaddingValues(top = toolbarHeight))
    {


    }

    TopAppBar
    (

    title = { Text("NestedScrollConnectionSample") }
    ,

    modifier = Modifie
    r

    .height(toolbarHeight
    )

    .offset { IntOffset(x = 0, y = toolbarOffsetY.value.roundToInt())
    }

    )

    }

    View full-size slide

  69. ԼʹεΫϩʔϧͨ͠ͱ͖ʹग़ͯ͘Δ5PPMCBS͕΄͍͠
    val toolbarOffsetY = remember { mutableStateOf(0f)
    }

    val nestedScrollConnection = remember
    {

    object : NestedScrollConnection
    {

    override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset
    {

    val newOffset = toolbarOffsetY.value + available.y
    toolbarOffsetY.value = newOffset.coerceIn(-toolbarHeightPx, 0f
    )

    return available
    }

    }

    }

    Box(modifier = Modifier.fillMaxSize().nestedScroll(nestedScrollConnection))
    {

    LazyColumn(contentPadding = PaddingValues(top = toolbarHeight))
    {


    }

    TopAppBar
    (

    title = { Text("NestedScrollConnectionSample") }
    ,

    modifier = Modifie
    r

    .height(toolbarHeight
    )

    .offset { IntOffset(x = 0, y = toolbarOffsetY.value.roundToInt())
    }

    )

    }
    εΫϩʔϧΛ͜͜ͰফඅͰ͖Δ

    View full-size slide

  70. GMFYCPYMBZPVU͕΄͍͠
    w ຊମʹੲ͋ͬͨ'MPX$PMVNOͱ'MPX3PX͸EFQSFDBUFEʹͳͬͨ
    w BDDPNQBOJTUͷ
    fl
    PXMBZPVUΛ࢖͏
    w HPPHMFHJUIVCJPBDDPNQBOJTU
    fl
    PXMBZPVU
    FlowRow
    (

    mainAxisSpacing = 8.dp
    ,

    modifier = Modifier.fillMaxWidth(
    )

    )
    {

    repeat(10)
    {

    Button(onClick = { /*TODO*/ })
    {

    Text("Button $it"
    )

    }

    }

    }

    View full-size slide

  71. ը໘දࣔ࣌ʹΩʔϘʔυΛग़͍ͨ͠
    w 'PDVT3FRVFTUFSΛ࢖͏
    val focusRequester = remember { FocusRequester()
    }

    TextField
    (


    modifier = Modifie
    r

    .focusOrder(focusRequester) // or .focusRequester(focusRequester
    )

    )

    LaunchedEffect(Unit)
    {

    focusRequester.requestFocus(
    )

    }

    View full-size slide

  72. ΩʔϘʔυΛ։ด͍ͨ͠ʢࠓ·Ͱʣ
    w 5FYU*OQVU4FSWJDFΛ࢖͏
    w 5FYU'JFMEͳͲςΩετೖྗΛड͚औΕΔDPNQPTBCMF
    ͕ϑΥʔΧε͞Ε͍ͯͳ͍ͱΩʔϘʔυ͸ग़ͳ͍
    val textInputService = LocalTextInputService.curren
    t

    Button(onClick =
    {

    textInputService?.showSoftwareKeyboard(
    )

    })
    {


    }

    Button(onClick =
    {

    textInputService?.hideSoftwareKeyboard(
    )

    })
    {


    }
    !*OUFSOBM5FYU"1*͕͍ͭͯ
    %FQSFDBUFEʹͳΔ༧ఆ
    IUUQTJTTVFUSBDLFSHPPHMFDPNJTTVFT

    View full-size slide

  73. ΩʔϘʔυΛ։ด͍ͨ͠ʢ͜Ε͔Βʣ
    w -PDBM4PGUXBSF,FZCPBSE$POUSPMMFSΛ࢖͏
    w 5FYU'JFMEͳͲςΩετೖྗΛड͚औΕΔDPNQPTBCMF
    ͕ϑΥʔΧε͞Ε͍ͯͳ͍ͱΩʔϘʔυ͸ग़ͳ͍
    val keyboardController = LocalSoftwareKeyboardController.curren
    t

    Button(onClick =
    {

    keyboardController?.show(
    )

    })
    {


    }

    Button(onClick =
    {

    keyboardController?.hide(
    )

    })
    {


    }

    View full-size slide

  74. ΤϯλʔΩʔ͕ԡ͞Εͨͱ͖ʹϑΥʔΧεҠಈ͍ͨ͠
    val focusRequester1 = remember { FocusRequester()
    }

    val focusRequester2 = remember { FocusRequester()
    }

    val focusManager = LocalFocusManager.curren
    t

    TextField
    (



    singleLine = true
    ,

    keyboardActions = KeyboardActions
    {

    focusManager.moveFocus(FocusDirection.Next
    )

    }
    ,

    modifier = Modifie
    r

    .focusOrder(focusRequester1)
    {

    next = focusRequester
    2

    down = focusRequester
    2

    }
    ,

    )

    TextField
    (


    modifier = Modifie
    r

    .focusOrder(focusRequester2
    )

    )

    View full-size slide

  75. PO3FTVNFͷλΠϛϯάͰॲཧΛ͍ͨ͠
    @Composabl
    e

    fun DoOnResume(action: () -> Unit)
    {

    val currentAction by rememberUpdatedState(action
    )

    val lifecycle = LocalLifecycleOwner.current.lifecycl
    e

    val lifecycleObserver = remember
    {

    LifecycleEventObserver { _, event -
    >

    if (event == Lifecycle.Event.ON_RESUME)
    {

    currentAction(
    )

    }

    }

    }

    DisposableEffect(lifecycle, lifecycleObserver)
    {

    lifecycle.addObserver(lifecycleObserver
    )

    onDispose
    {

    lifecycle.removeObserver(lifecycleObserver
    )

    }

    }

    }

    View full-size slide

  76. PO3FTVNFͷλΠϛϯάͰॲཧΛ͍ͨ͠
    @Composabl
    e

    fun DoOnResume(action: () -> Unit)
    {

    val currentAction by rememberUpdatedState(action
    )

    val lifecycle = LocalLifecycleOwner.current.lifecycl
    e

    val lifecycleObserver = remember
    {

    LifecycleEventObserver { _, event -
    >

    if (event == Lifecycle.Event.ON_RESUME)
    {

    currentAction(
    )

    }

    }

    }

    DisposableEffect(lifecycle, lifecycleObserver)
    {

    lifecycle.addObserver(lifecycleObserver
    )

    onDispose
    {

    lifecycle.removeObserver(lifecycleObserver
    )

    }

    }

    }

    View full-size slide

  77. $BNFSB9Λ࢖͍͍ͨ
    w "OESPJE7JFXDPNQPTBCMFΛ࢖ͬͯ1SFWJFX7JFXΛ૊ΈࠐΉ
    w 1SFWJFX7JFXͷJNQMFNFOUBUJPO.PEFͰ$0.1"5*#-&Λࢦఆ͢Δ
    AndroidView
    (

    modifier = Modifier.fillMaxSize()
    ,

    factory = { context -
    >

    PreviewView(context).apply
    {

    implementationMode = PreviewView.ImplementationMode.COMPATIBL
    E


    }

    }
    ,

    update =
    {


    }

    )

    View full-size slide

  78. 7JFX.PEFMؔ܎

    View full-size slide

  79. 7JFX.PEFMͷίϯετϥΫλͰJEΛ౉͍ͨ͠
    w %BHHFSͷ"TTJTUFE*OKFDUػೳΛ࢖͏
    IUUQZBO[NCMPHTQPUDPNKFUQBDLDPNQPTFWJFXNPEFMOBWJHBUJPOIUNM
    class ItemDetailViewModel @AssistedInject constructor
    (

    @Assisted val id: ItemId
    ,


    ,

    ) : ViewModel()
    {

    @AssistedFactor
    y

    interface Factory
    {

    fun create(id: ItemId): ItemDetailViewMode
    l

    }

    @EntryPoin
    t

    @InstallIn(ActivityComponent::class
    )

    interface ActivityCreatorEntryPoint
    {

    fun getItemDetailViewModelFactory(): Factor
    y

    }

    companion object
    {
    {

    View full-size slide

  80. .VUBCMF4UBUFͷঢ়ଶΛ4BWFE4UBUF)BOEMFʹอଘ͍ͨ͠
    w 4BWFE4UBUF)BOEMFTFU4BWFE4UBUF1SPWJEFS
    Λ࢖͏ͱศར
    @HiltViewMode
    l

    class MyViewModel @Inject constructor
    (


    savedStateHandle: SavedStateHandl
    e

    ) : ViewModel()
    {

    private val _uiState = mutableStateOf(UiState.Initial
    )


    init
    {

    savedStateHandle.setSavedStateProvider("[KEY]")
    {

    _uiState.value.toBundle(
    )

    }

    savedStateHandle.get("[KEY]")?.let
    {

    _uiState.value = it.toUiState(
    )

    }

    View full-size slide

  81. ·ͱΊ
    w ը໘ભҠ΋+FUQBDL$PNQPTFͰ΍Δ৔߹ɺ/BWJHBUJPO$PNQPTFΛ࢖͏
    ͷ͕͓͢͢Ί
    w "OESPJE$PEF4FBSDI DTBOESPJEDPN
    Ͱ$PNQPTBCMFͷ࣮૷ίʔυ΍
    αϯϓϧίʔυΛಡ΋͏
    w +FUQBDL$PNQPTF࢖ͬͯΈΑ͏ʂ

    View full-size slide