본문 바로가기

기타/안드로이드

31. 30 Days Of Habit App

30 Days Of Habit App 만들기

프로젝트: 30일 앱 만들기

 

프로젝트: 30일 앱 만들기  |  Android Developers

선택한 테마로 스크롤 가능한 목록이나 그리드에 한 달 동안 매일 다른 도움말을 표시하여 보여주는 Android 앱을 만들어 보세요.

developer.android.com

위의 페이지를 참고하여 지금까지 배운 Scroll, Material Design, Animation을 한번에 실습해보자

 

리소스 준비

string.xml

<resources>
    <string name="app_name">P30DaysOfMakingHabit</string>
    <string name="desc1">You\'re now just beginner. Take easy. Taste little bit. Start with no pressure.</string>
    <string name="desc2">You can make this job into continuous thing. But you can quit any time. It\'s okay. Don\'t be responsible and just enjoy.</string>
    <string name="desc3">Many people quit in this moment. If you get through this time, you can easily go for a week.</string>
    <string name="desc4">Now this job becomes your daily job. Maybe you like this job so far. Enjoy that satisfaction.</string>
    <string name="desc5">You do this job for weekday. This means you do this job like your natural work. Can you do this in weekend? If you\'re not, just skip. It doesn't matter.</string>
    <string name="desc6">Whether you do this in weekend or not, you start to consider this work important and worth the time.</string>
    <string name="desc7">Many people quit in this moment. If you get through this time, you can easily go for a 2 weeks.</string>
    <string name="desc8">Now you consider this job is your job. Don\'t be afraid. Just another week like you did before. Let the time do this job. Take easy.</string>
    <string name="desc9">Maybe you do this job naturally now. Just let the time do this job. If you get stressed doing this job, take off your hands and make job more easier.</string>
    <string name="desc10">Ten days. Regardless how do you think about this, it\'s huge thing for start something. You did great so far.</string>
    <string name="desc11">Maybe you feels this job is not new and boring. All job is not always new and funny. Just do what did you do yesterday or do a little less than yesterday.</string>
    <string name="desc12">If you doing this job for an hour in a day, you have done this job for a half of a day.</string>
    <string name="desc13">Habit is your second nature and it breaks your first nature.</string>
    <string name="desc14">Many people quit in this moment. If you get through this time, you can easily go for a month.</string>
    <string name="desc15">Habit can be the best servant or the worst master.</string>
    <string name="desc16">Excellence is not an act, but a habit.</string>
    <string name="desc17">Once you made habit, then habit will make you.</string>
    <string name="desc18">Everyone born in same, habit makes different.</string>
    <string name="desc19">Habit change your life.</string>
    <string name="desc20">Twenty days. now it\'s on the different orbit. You know what? it\'s the maximum you can count using your fingers and toes.</string>
    <string name="desc21">It\'s been 3 weeks from start. It means you did this for at least 3 periods. Yeah, you\'re now used to it.</string>
    <string name="desc22">In the first half of life, you can make habit. In another half of life, you can\'t. You just live in habit.</string>
    <string name="desc23">Habit grows bigger like tree ring.</string>
    <string name="desc24">If you doing this job for an hour in a day, you have done this job for a pure day.</string>
    <string name="desc25">If you do this job just for weekday, you did this for 5 weeks. If you this job for money, today is your payday.</string>
    <string name="desc26">The best way to get rid of bad habit is not to start.</string>
    <string name="desc27">We are what we repeatedly do.</string>
    <string name="desc28">Habit is the best guidance in life.</string>
    <string name="desc29">Making good habit is harder than bad habit. However giving up good habit is easier than bad habit.</string>
    <string name="desc30">Now this job is your habit. Congratulations!</string>
</resources>

Day.kt

package com.example.p30daysofmakinghabit

import androidx.annotation.DrawableRes
import androidx.annotation.StringRes

data class Day(val num: Int, @DrawableRes val imageId: Int, @StringRes val stringId: Int)

object DayData{
    val days = listOf(
        Day(1, R.drawable.image1, R.string.desc1),
        Day(2, R.drawable.image2, R.string.desc2),
        Day(3, R.drawable.image3, R.string.desc3),
        Day(4, R.drawable.image4, R.string.desc4),
        Day(5, R.drawable.image5, R.string.desc5),
        Day(6, R.drawable.image6, R.string.desc6),
        Day(7, R.drawable.image7, R.string.desc7),
        Day(8, R.drawable.image8, R.string.desc8),
        Day(9, R.drawable.image9, R.string.desc9),
        Day(10, R.drawable.image10, R.string.desc10),
        Day(11, R.drawable.image11, R.string.desc11),
        Day(12, R.drawable.image12, R.string.desc12),
        Day(13, R.drawable.image13, R.string.desc13),
        Day(14, R.drawable.image14, R.string.desc14),
        Day(15, R.drawable.image15, R.string.desc15),
        Day(16, R.drawable.image16, R.string.desc16),
        Day(17, R.drawable.image17, R.string.desc17),
        Day(18, R.drawable.image18, R.string.desc18),
        Day(19, R.drawable.image19, R.string.desc19),
        Day(20, R.drawable.image20, R.string.desc20),
        Day(21, R.drawable.image21, R.string.desc21),
        Day(22, R.drawable.image22, R.string.desc22),
        Day(23, R.drawable.image23, R.string.desc23),
        Day(24, R.drawable.image24, R.string.desc24),
        Day(25, R.drawable.image25, R.string.desc25),
        Day(26, R.drawable.image26, R.string.desc26),
        Day(27, R.drawable.image27, R.string.desc27),
        Day(28, R.drawable.image28, R.string.desc28),
        Day(29, R.drawable.image29, R.string.desc29),
        Day(30, R.drawable.image30, R.string.desc30),
    )
}

drawbles

 

코드 작성하기

MainActivity.kt

@Composable
fun DayCard(day: Day, modifier: Modifier = Modifier) {
    Card(
        modifier = modifier,
        elevation = 3.dp
    ){
        Column(
            modifier = Modifier
                .fillMaxWidth()
                .padding(8.dp),
            horizontalAlignment = Alignment.CenterHorizontally
        ){
            Text(
                text = "Day " + day.num,
                style = MaterialTheme.typography.h2,
                modifier = Modifier
                    .fillMaxWidth(),
                textAlign = TextAlign.Left
            )
            Spacer(Modifier.height(16.dp))
            Image(
                painter = painterResource(day.imageId),
                contentDescription = null,
                modifier = Modifier
                    .width(400.dp)
                    .height(300.dp)
            )
            Text(
                text = stringResource(day.stringId),
                style = MaterialTheme.typography.body1
            )
        }
    }
}

@Composable
fun DayCardColumn(modifier: Modifier = Modifier){
    LazyColumn(modifier = modifier){
        items(DayData.days){ day ->
            DayCard(day,
                Modifier
                    .padding(
                        horizontal = 8.dp,
                        vertical = 4.dp
                    )
            )
        }
    }
}

 

테마 설정하기

Color.kt

package com.example.p30daysofmakinghabit.ui.theme

import androidx.compose.ui.graphics.Color

val light_primary = Color(0xFFff9600)
val light_secondary = Color(0xFFffdd00)
val light_surface = Color(0xFFfcfcfc)
val light_background = Color(0xFFFFFFFF)
val light_onprimary = Color(0xFF000000)
val light_onsecondary = Color(0xFF000000)
val light_onsurface = Color(0xFF4a4a4a)
val light_onbackground = Color(0xFF979797)


val dark_primary = Color(0xFFb96d00)
val dark_secondary = Color(0xFF887600)
val dark_surface = Color(0xFF696969)
val dark_background = Color(0xFF858585)
val dark_onprimary = Color(0xFFffffff)
val dark_onsecondary = Color(0xFFffffff)
val dark_onsurface = Color(0xFFf5f5f5)
val dark_onbackground = Color(0xFFececec)

Shape.kt

package com.example.p30daysofmakinghabit.ui.theme

import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.Shapes
import androidx.compose.ui.unit.dp

val Shapes = Shapes(
    small = RoundedCornerShape(8.dp),
    medium = RoundedCornerShape(8.dp),
    large = RoundedCornerShape(0.dp)
)

Type.kt

package com.example.p30daysofmakinghabit.ui.theme

import android.speech.tts.TextToSpeechService
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Typography
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.Font
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.sp
import com.example.p30daysofmakinghabit.R

val Signikanegative = FontFamily(
    Font(R.font.signikanegative_regular, FontWeight.Normal),
    Font(R.font.signikanegative_bold, FontWeight.Bold)
)
// Set of Material typography styles to start with
val Typography = Typography(
    h1 = TextStyle(
        fontFamily = Signikanegative,
        fontWeight = FontWeight.Bold,
        fontSize = 30.sp
    ),
    h2 = TextStyle(
        fontFamily = Signikanegative,
        fontWeight = FontWeight.Bold,
        fontSize = 26.sp
    ),
    body1 = TextStyle(
        fontFamily = Signikanegative,
        fontWeight = FontWeight.Normal,
        fontSize = 20.sp
    )
)

Theme.kt

package com.example.p30daysofmakinghabit.ui.theme

import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.material.MaterialTheme
import androidx.compose.material.darkColors
import androidx.compose.material.lightColors
import androidx.compose.runtime.Composable

private val DarkColorPalette = darkColors(
    primary = dark_primary,
    onPrimary = dark_onprimary,
    secondary = dark_secondary,
    onSecondary = dark_onsecondary,
    surface = dark_surface,
    onSurface = dark_onsurface,
    background = dark_background
)

private val LightColorPalette = lightColors(
    primary = light_primary,
    onPrimary = light_onprimary,
    secondary = light_secondary,
    onSecondary = light_secondary,
    surface = light_surface,
    onSurface = light_onsurface,
    background = light_background,
    onBackground = light_onbackground
)

@Composable
fun P30DaysOfMakingHabitTheme(
    darkTheme: Boolean = isSystemInDarkTheme(),
    content: @Composable () -> Unit
) {
    val colors = if (darkTheme) {
        DarkColorPalette
    } else {
        LightColorPalette
    }

    MaterialTheme(
        colors = colors,
        typography = Typography,
        shapes = Shapes,
        content = content
    )
}

color.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <color name="light_status">#ff9600</color>
    <color name="dark_status">#b96d00</color>
</resources>

themes.xml, themes.xml (v23)

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <style name="Theme.P30DaysOfMakingHabit" parent="android:Theme.Material.Light.NoActionBar">
        <item name="android:statusBarColor">@color/light_status</item>
        <item name="android:windowLightStatusBar">true</item>
    </style>
</resources>

themes.xml (night)

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <style name="Theme.P30DaysOfMakingHabit" parent="android:Theme.Material.NoActionBar">
        <item name="android:statusBarColor">@color/dark_status</item>
    </style>
</resources>

 

애니메이션 추가, TopAppBar 추가

MainActivity.kt

package com.example.p30daysofmakinghabit

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.animation.animateColorAsState
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.example.p30daysofmakinghabit.ui.theme.P30DaysOfMakingHabitTheme

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            P30DaysOfMakingHabitTheme {
                // A surface container using the 'background' color from the theme
                DayHabitApp()
            }
        }
    }
}

@Composable
fun DayHabitApp(modifier: Modifier = Modifier){
    Scaffold(
        topBar = { DayHabitTopAppBar() }
    ) {
        DayCardColumn()
    }
}

@Composable
fun DayHabitTopAppBar(modifier: Modifier = Modifier){
    Row(
        modifier = modifier
            .fillMaxWidth()
            .height(100.dp)
            .background(MaterialTheme.colors.primary),
        verticalAlignment = Alignment.CenterVertically,
        horizontalArrangement = Arrangement.Center
    ){
        Text(
            text = "30 Days of Making Habit",
            style = MaterialTheme.typography.h1,
            color = MaterialTheme.colors.onPrimary
        )
    }
}

@Composable
fun DayCard(day: Day, modifier: Modifier = Modifier) {
    var isClicked by remember { mutableStateOf(false) }
    val changedSurfaceColor by animateColorAsState(
        targetValue = if(isClicked) MaterialTheme.colors.primary else MaterialTheme.colors.surface
    )
    val changedTextColor by animateColorAsState(
        targetValue = if(isClicked) MaterialTheme.colors.onPrimary else MaterialTheme.colors.onSurface
    )
    Card(
        modifier = modifier
            .clickable { isClicked = !isClicked },
        elevation = 3.dp,
    ){
        Column(
            modifier = Modifier
                .background(changedSurfaceColor)
                .width(500.dp)
                .padding(
                    start = 16.dp,
                    end = 16.dp,
                    top = 8.dp,
                    bottom = 32.dp
                )
            ,
            horizontalAlignment = Alignment.CenterHorizontally
        ){
            Text(
                text = "Day " + day.num,
                style = MaterialTheme.typography.h2,
                modifier = Modifier
                    .fillMaxWidth(),
                textAlign = TextAlign.Left,
                color = changedTextColor
            )
            Spacer(Modifier.height(20.dp))
            if(isClicked == false) {
                    Image(
                        painter = painterResource(day.imageId),
                        contentDescription = null,
                        modifier = Modifier
                            .height(200.dp)
                    )
            } else {
                Text(
                    text = stringResource(day.stringId),
                    style = MaterialTheme.typography.body1,
                    modifier = Modifier
                        .width(400.dp)
                        .height(200.dp),
                    color = changedTextColor
                )
            }
        }
    }
}

@Composable
fun DayCardColumn(modifier: Modifier = Modifier){
    LazyColumn(
        modifier = modifier
            .fillMaxWidth()
            .background(MaterialTheme.colors.background),
        horizontalAlignment = Alignment.CenterHorizontally
    ){
        items(DayData.days){ day ->
            DayCard(day,
                Modifier
                    .padding(
                        horizontal = 16.dp,
                        vertical = 16.dp
                    )
            )
        }
    }
}

@Preview(showBackground = true)
@Composable
fun DefaultPreview() {
    P30DaysOfMakingHabitTheme {
        DayHabitTopAppBar()
    }
}

@Preview(showBackground = true)
@Composable
fun DefaultPreview1() {
    P30DaysOfMakingHabitTheme {
        DayHabitApp()
    }
}

 

결과화면