📱 안드로이드 Android ~ Kotlin

[Android/Kotiln] MVVM 적용한 ToDo 앱 만들기 (LiveData)

핑크빛연어 2021. 8. 13. 00:28

 

안드로이드 아키텍쳐 중 LiveData 를 통해 MVVM 을 적용한 MyToDo 앱 을 만들어보았습니다.

 

https://developer.android.com/topic/libraries/architecture/livedata?hl=ko 

 

LiveData 개요  |  Android 개발자  |  Android Developers

LiveData를 사용하여 수명 주기를 인식하는 방식으로 데이터를 처리합니다.

developer.android.com

 

https://eunoia3jy.tistory.com/119

 

안드로이드 아키텍쳐 컴포넌트 AAC (Android Architecture Components)

AAC 안드로이드 아키텍쳐 컴포넌트 (Android Architecture Components) 안드로이드 아키텍쳐 컴포넌트는 앱 구조를 더 튼튼하고, 테스트에 용이하고, 유지 보수성이 뛰어나게 만들어 주는 라이브러리 모음

eunoia3jy.tistory.com

 

아래의 사진처럼 Recyclerview 를 이용하여 ToDo 아이템 목록을 만들고 추가/삭제 버튼을 통해 insert/Delete 하여 아이템 목록을 추가/삭제 하는 앱을 만들어보도록 할게요! 😬😬😬

 

💡 작성한 파일 목록 입니다.

  1. build.gradle(:app)

  2. Room 생성 
    2-1. TodoModel.kt
    2-2. TodoDao.kt
    2-3. TodoDatabase.kt

  3. Repository 생성
    3-1. TodoRepository.kt

  4. ViewModel 생성
    4-1. TodoViewModel.kt

  5. View 생성
    5-1. MainActivity.kt
    5-2. activity_main.xml
    5-3. TodoListAdapter.kt
    5-4. item_list.xml
    5-5. dialog_add.xml

 

 


 

1. build.gradle(:app)

Kotlin 언어를 사용하기 위해 상단 plugins {} 안에 id 'kotlin-kapt' 플러그인을 적용합니다.

그리고 하단 dependencies {} 안에 room, livedata, recyclerview, cardview 에 대한 라이브러리를 사용하기 위해 추가해 줍니다.

plugins {
    id 'com.android.application'
    id 'kotlin-android'
    id 'kotlin-kapt'
}

android {
    compileSdkVersion 30
    buildToolsVersion "30.0.3"

    defaultConfig {
        applicationId "com.eun.mytodokotlin_mvvm_01"
        minSdkVersion 21
        targetSdkVersion 30
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
    kotlinOptions {
        jvmTarget = '1.8'
    }
}

dependencies {

    implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
    implementation 'androidx.core:core-ktx:1.2.0'
    implementation 'androidx.appcompat:appcompat:1.3.1'
    implementation 'com.google.android.material:material:1.4.0'
    implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
    testImplementation 'junit:junit:4.+'
    androidTestImplementation 'androidx.test.ext:junit:1.1.3'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'

    //room
    implementation "androidx.room:room-runtime:2.2.6"
    kapt "androidx.room:room-compiler:2.2.6"

    //livedata
    implementation 'android.arch.lifecycle:extensions:1.1.1'
    implementation "androidx.lifecycle:lifecycle-extensions:2.2.0"
    kapt 'android.arch.lifecycle:compiler:1.1.1'

    //recyclerview, cardview
    implementation 'androidx.recyclerview:recyclerview:1.2.1'
    implementation 'androidx.cardview:cardview:1.0.0'
    
}

 

 

Room 데이터 생성

룸 Room : 안드로이드 로컬 SQLite 데이터베이스를 사용하는 라이브러리.

https://developer.android.com/jetpack/androidx/releases/room?hl=ko

 

Room  |  Android 개발자  |  Android Developers

Room Room 지속성 라이브러리는 SQLite에 추상화 레이어를 제공하여 SQLite를 완벽히 활용하면서 더 견고한 데이터베이스 액세스를 가능하게 합니다. 최근 업데이트 현재 안정화 버전 다음 버전 후보

developer.android.com

 

2-1. TodoModel.kt

data class 를 만들고 @Entity 속성을 통해 테이블 및 컬럼을 생성하는 TodoModel 클래스를 만듭니다.

package com.eun.mytodokotlin_mvvm_01.data.model

import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey

@Entity(tableName = "tb_todo")  //@Entity 의 tableName = : 테이블명 지정
data class TodoModel(

    @PrimaryKey(autoGenerate = true)  //@PrimaryKey 의 autoGenerate = : null 을 받으면 id 값을 자동으로 할당해준다
    var id: Long?,

    @ColumnInfo(name = "seq")  //@ColumnInfo : 컬럼명 지정. 컬럼명을 변수명과 같이 쓰려면 생략 가능
    var seq: Int,

    @ColumnInfo(name = "title")  //@ColumnInfo : 컬럼명 지정. 컬럼명을 변수명과 같이 쓰려면 생략 가능
    var title: String,

    @ColumnInfo(name = "content")  //@ColumnInfo : 컬럼명 지정. 컬럼명을 변수명과 같이 쓰려면 생략 가능
    var content: String,

    @ColumnInfo(name = "createDate")  //@ColumnInfo : 컬럼명 지정. 컬럼명을 변수명과 같이 쓰려면 생략 가능
    var createDate: Long

) {
    constructor() : this(null, 0, "", "", -1)
}

 

2-2. TodoDao.kt 

SQL 작성을 위한 TodoDao 인터페이스를 만듭니다.

@Query, @Insert, @Update, @Delete 등의 어노테이션을 사용할 수 있습니다.

그 중 @Insert, @Update 는 onConflict 속성을 지정할 수 있어요.

package com.eun.mytodokotlin_mvvm_01.data.dao

import androidx.lifecycle.LiveData
import androidx.room.*
import com.eun.mytodokotlin_mvvm_01.data.model.TodoModel


@Dao
interface TodoDao {

    @Query("SELECT * FROM tb_todo ORDER BY SEQ ASC")
    fun getTodoListAll(): LiveData<List<TodoModel>>  //getAll 함수에 LiveData 를 반환 

    @Insert(onConflict = OnConflictStrategy.REPLACE)  //@Insert 의 onConflict = : 중복된 데이터의 경우 어떻게 처리할 것인지에 대한 처리를 지정
    fun insert(todo: TodoModel)

    @Delete
    fun delete(todo: TodoModel)

}

 

2-3. TodoDatabase.kt

실질적인 데이터베이스 인스턴스를 생성하는 TodoDatabase 클래스는 RoomDatabase 를 상속하는 추상 클래스로 생성합니다. 

@Database 어노테이션을 이용해줍니다.

package com.eun.mytodokotlin_mvvm_01.data.database

import android.content.Context
import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase
import com.eun.mytodokotlin_mvvm_01.data.dao.TodoDao
import com.eun.mytodokotlin_mvvm_01.data.model.TodoModel


/*
* @Database 의 entites = : entity 정의
* @Database 의 version = : SQLite 버전 지정
* */
@Database(entities = [TodoModel::class], version = 1)  
abstract class TodoDatabase : RoomDatabase() {

    abstract fun todoDao(): TodoDao

    //데이터베이스 인스턴스를 싱글톤으로 사용하기 위해 companion object 안에 만들어준다
    companion object {

        private var INSTANCE: TodoDatabase? = null

        //getInstance() :  여러 스레드가 접근하지 못하도록 synchronized로 설정
        fun getInstance(context: Context): TodoDatabase? {

            if (INSTANCE == null) {
                synchronized(TodoDatabase::class) {
                    INSTANCE = Room.databaseBuilder(  //Room.databaseBuilder 로 인스턴스를 생성
                        context.applicationContext,
                        TodoDatabase::class.java,
                        "tb_todo"
                    )
                        .fallbackToDestructiveMigration()  //.fallbackToDestructiveMigration() : 데이터베이스가 갱신될 때 기존의 테이블을 버리고 새로 사용하도록 설정
                        .build()
                }
            }
            return INSTANCE
        }

    }

}

 

 

3. Repository 생성

Repository : ViewModel 과 상호작용하기 위해 정리된(Clean) 데이터 API 를 들고 있는 클래스

VIewModel 이 직접 DB 나 서버에 접근하지 않고, Repository 에 접근하여 앱의 데이터를 관리하여 필요한 데이터를 가져옵니다.

 

3-1. TodoRepository.kt

TodoRepository 클래스 에서는 TodoModel, TodoDao, TodoDatabase 를 각각 초기화하고 ViewModel 에서 DB 접근을 요청할 때 수행할 함수를 만듭니다.

package com.eun.mytodokotlin_mvvm_01.data.repository

import android.app.Application
import androidx.lifecycle.LiveData
import com.eun.mytodokotlin_mvvm_01.data.dao.TodoDao
import com.eun.mytodokotlin_mvvm_01.data.database.TodoDatabase
import com.eun.mytodokotlin_mvvm_01.data.model.TodoModel
import java.lang.Exception


/*
* 데이터베이스 혹은 네트워크 통신을 통하여 데이터를 얻는 기능을 분리
* ViewModel 에서는 이 Repository 를 통해 데이터를 얻는다
* */
class TodoRepository(application: Application) {

    //database, dao todoItems 를 각각 초기화
    private var todoDatabase: TodoDatabase = TodoDatabase.getInstance(application)!!
    private var todoDao: TodoDao = todoDatabase.todoDao()
    private var todoItems: LiveData<List<TodoModel>> = todoDao.getTodoListAll()
    
    fun getTodoListAll(): LiveData<List<TodoModel>> {
        return todoItems
    }
    
    fun insert(todoModel: TodoModel) {
        try {
            val thread =
                Thread(Runnable {  //별도 스레드를 통해 Room 데이터에 접근해야한다. 연산 시간이 오래 걸리는 작업은 메인 쓰레드가 아닌 별도의 쓰레드에서 하도록 되어있다. 그렇지 않으면 런타임에러 발생.
                    todoDao.insert(todoModel)
                }).start()
        } catch (e: Exception) {
            e.printStackTrace()
        }
    }

    fun delete(todoModel: TodoModel) {
        try {
            val thread = Thread(Runnable {
                todoDao.delete(todoModel)
            })
            thread.start()
        } catch (e: Exception) {
        }
    }

}

 

 

4. ViewModel 생성

ViewModel : View 를 위한 데이터를 가지고 있다.

액티비티의 생명주기에서 자유로울 순 없지만 ViewModel 은 View 와 ViewModel 의 분리로 액티비티가 destroy 되었다가 다시 create 되어도 종료되지 않고 가지고 있다.

 

4-1. TodoViewModel.kt

데이터를 가져오는 TodoViewModel 클래스를 AndroidViewModel 를 상속하는 클래스로 생성합니다. 

Repository 를 통해서 Room 데이터베이스의 인스턴스를 만들 때 Context 가 필요합니다.

만약 ViewModel 의 Context 를 사용하면 액티비티가 destroy 되는 경우 메모리 에러가 발생할 수 있어서 Application Context 를 사용하기 위해 application 을 인자로 받습니다.

package com.eun.mytodokotlin_mvvm_01.viewmodel

import android.app.Application
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.LiveData
import com.eun.mytodokotlin_mvvm_01.data.model.TodoModel
import com.eun.mytodokotlin_mvvm_01.data.repository.TodoRepository


class TodoViewModel(application: Application) : AndroidViewModel(application) {

    private val todoRepository = TodoRepository(application)
    private var todoItems =
        todoRepository.getTodoListAll()  //액티비티(View) 에서 ViewModel 의 todoItems 리스트를 observe 하고 리스트를 갱신 

    /* repsitory 에 넘겨 viewModel 의 기능 함수 구현 */
    
    fun getTodoListAll(): LiveData<List<TodoModel>> {
        return todoItems
    }

    fun insert(todoModel: TodoModel) {
        todoRepository.insert(todoModel)
    }

    fun delete(todoModel: TodoModel) {
        todoRepository.delete(todoModel)
    }
}

 

 

5. View 생성

VIew : UI Controller 를 담당하는 Activity, Fragment

사용자와 상호작용하고 데이터의 변화를 감지하기 위한 observer 를 가지고 있다.

 

5-1. MainActivity.kt

package com.eun.mytodokotlin_mvvm_01.view

import android.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import android.widget.Button
import android.widget.EditText
import androidx.lifecycle.ViewModelProvider
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.eun.mytodokotlin_mvvm_01.R
import com.eun.mytodokotlin_mvvm_01.data.model.TodoModel
import com.eun.mytodokotlin_mvvm_01.viewmodel.TodoViewModel
import java.util.*
import kotlin.collections.ArrayList


class MainActivity : AppCompatActivity() {
    val TAG: String = MainActivity::class.java.name;

    private lateinit var todoViewModel: TodoViewModel  //TodoViewModel 인스턴스를 만들고, 이를 관찰

    private lateinit var todoListAdapter: TodoListAdapter
    private val todoItems: ArrayList<TodoModel> = ArrayList()

    private val recyclerview_list: RecyclerView by lazy {
        findViewById<RecyclerView>(R.id.recyclerview_list)
    }

    private val btn_add: Button by lazy {
        findViewById<Button>(R.id.btn_add)
    }


    override fun onCreate(savedInstanceState: Bundle?) {
        Log.d(TAG, "== onCreate ");
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

//        todoItems.run {
//            add(
//                TodoModel(
//                    null,
//                    1,
//                    "방청소 1 ",
//                    "책상정리",
//                    Date().time
//                )
//            )
//            add(
//                TodoModel(
//                    null,
//                    2,
//                    "방청소 2 ",
//                    "옷장정리",
//                    Date().time
//                )
//            )
//        }

        initViewModel()
        initRecyclerview()
        initBtnAdd()
    }


    /*
    * ViewModel 설정
    * View 에서 ViewModel 을 관찰하여 데이터가 변경될 때 내부적으로 자동으로 알 수 있도록 한다.
    * ViewModel 은 View 와 ViewModel 의 분리로 액티비티가 destroy 되었다가 다시 create 되어도 종료되지 않고 가지고 있다.
    * */
    private fun initViewModel() {
        todoViewModel = ViewModelProvider.AndroidViewModelFactory.getInstance(application)
            .create(TodoViewModel::class.java)
        todoViewModel.getTodoListAll().observe(this, androidx.lifecycle.Observer {
            todoListAdapter.setTodoItems(it)
        })
    }


    /*
   * Recyclerview 설정
   * Recyclerview adapter 와 LinearLayoutManager 를 만들고 연결
   * */
    private fun initRecyclerview() {
        todoListAdapter =
            TodoListAdapter({ todo -> deleteDialog(todo) })  //adapter 에 click 시 해야할 일을 (todo) -> Unit 파라미터로 넘겨준다
        recyclerview_list.run {
            setHasFixedSize(true)
            layoutManager = LinearLayoutManager(this@MainActivity)
            adapter = todoListAdapter
        }
    }


    /*
    * btn_add 설정
    * */
    private fun initBtnAdd() {
        btn_add.setOnClickListener {
            showAddTodoDialog()
        }
    }


    /*
    * Todo 리스트를 추가하는 Dialog 띄우기
    *  TodoModel 을 생성하여 리스트 add 해서 리스트를 갱신
    * */
    private fun showAddTodoDialog() {
        val dialogView = layoutInflater.inflate(R.layout.dialog_add, null)
        val et_add_title: EditText by lazy {
            dialogView.findViewById<EditText>(R.id.et_add_title)
        }
        val et_add_content: EditText by lazy {
            dialogView.findViewById<EditText>(R.id.et_add_content)
        }
        var builder = AlertDialog.Builder(this)
        val dialog = builder.setTitle("Todo 아이템 추가하기").setView(dialogView)
            .setPositiveButton(
                "확인"
            ) { dialogInterface, i ->
                var id: Long? = null
                val title = et_add_title.text.toString()
                val content = et_add_content.text.toString()
                val createdDate = Date().time
                val todoModel = TodoModel(
                    id,
                    todoListAdapter.getItemCount() + 1,
                    title,
                    content,
                    createdDate
                )
                todoViewModel.insert(todoModel)
            }
            .setNegativeButton("취소", null)
            .create()
        dialog.show()
    }


    /*
    * Todo 리스트를 삭제하는 Dialog 띄우기
    * */
    private fun deleteDialog(todoModel: TodoModel) {
        val builder = AlertDialog.Builder(this)
        builder.setMessage(todoModel.seq.toString()+" 번 Todo 아이템을 삭제할까요? ")
            .setNegativeButton("취소") { _, _ -> }
            .setPositiveButton("확인") { _, _ ->
                todoViewModel.delete(todoModel)
            }
            .create()
        builder.show()
    }

}

 

5-2. activity_main.xml

MainActivity 를 구성하는 xml 입니다.

RecyclerView 에 item_list.xml 를 리스트 아이템으로 지정하였습니다.

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".view.MainActivity">

    <TextView
        android:id="@+id/tv_title"
        android:layout_width="0dp"
        android:layout_height="60dp"
        android:background="#FFE08C"
        android:elevation="10dp"
        android:gravity="center"
        android:text="My ToDo App"
        android:textColor="@color/black"
        android:textSize="20sp"
        android:textStyle="bold"
        app:layout_constraintBottom_toTopOf="@+id/recyclerview_list"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recyclerview_list"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:paddingTop="8dp"
        app:layout_constraintBottom_toTopOf="@+id/btn_add"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/tv_title"
        tools:listitem="@layout/item_list" />

    <androidx.appcompat.widget.AppCompatButton
        android:id="@+id/btn_add"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:background="#6799FF"
        android:text="추가"
        android:textSize="15dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

 

5-3. TodoListAdapter.kt

TodoModel 의 리스트를 생상자로부터 전달받으며 RecycleView.Adapter 를 상속받고 RecyclerView.TodoViewHolder 를 뷰홀더로 갖는 TodoListAdapter 클래스 를 만듭니다.

package com.eun.mytodokotlin_mvvm_01.view

import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import com.eun.mytodokotlin_mvvm_01.R
import com.eun.mytodokotlin_mvvm_01.data.model.TodoModel
import java.text.SimpleDateFormat
import java.util.*


class TodoListAdapter(val deletetItemClick: (TodoModel) -> Unit) :
    RecyclerView.Adapter<TodoListAdapter.TodoViewHolder>() {
    private var todoItems: List<TodoModel> = listOf()
    
    /*
    * 이 어뎁터가 아이템을 얼마나 가지고 있는지 얻는 함수
    * */
    override fun getItemCount(): Int {

        Log.d("MainActivity", "todoItem getItemCount !!: " + todoItems.size);
        return todoItems.size
    }

    /*
    * 현재 아이템이 사용할 뷰홀더를 생성하여 반환하는 함수
    * item_list 레이아웃을 사용하여 뷰를 생성하고 뷰홀더에 뷰를 전달하여 생성된 뷰홀더를 반환
    * */
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TodoViewHolder {
        val view = LayoutInflater.from(parent.context).inflate(R.layout.item_list, parent, false)
        val viewHolder = TodoViewHolder(view)
        return viewHolder
    }

    /*
    * 현재 아이템의 포지션에 대한 데이터 모델을 리스트에서 얻고
    * holder 객체를 TodoViewHolder 로 형변환한 뒤 bind 메서드에 이 모델을 전달하여 데이터를 바인딩하도록 한다
    * */
    override fun onBindViewHolder(holder: TodoViewHolder, position: Int) {
        val todoModel = todoItems[position]
        todoModel.seq = position + 1;
        val todoViewHolder = holder as TodoViewHolder
        todoViewHolder.bind(todoModel)
    }

    /* 데이터베이스가 변경될 때마다 호출 */
    fun setTodoItems(todoItems: List<TodoModel>) {
        this.todoItems = todoItems
        Log.d("MainActivity", "todoItem setTodoItems !!: " + todoItems.size);
        notifyDataSetChanged()
    }
    
    /*
    * 뷰홀더는 리스트를 스크롤하는 동안 뷰를 생성하고 다시 뷰의 구성요소를 찾는 행위를 반복하면서 생기는 
    * 성능저하를 방지하기 위해 미리 저장 해 놓고 빠르게 접근하기 위하여 사용하는 객체
    * */
    inner class TodoViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {

        private val tv_seq = itemView.findViewById<TextView>(R.id.tv_seq)
        private val tv_title = itemView.findViewById<TextView>(R.id.tv_title)
        private val tv_content = itemView.findViewById<TextView>(R.id.tv_content)
        private val tv_date = itemView.findViewById<TextView>(R.id.tv_date)
        private val iv_delete = itemView.findViewById<ImageView>(R.id.iv_delete)

        fun bind(todoModel: TodoModel) {
            
            tv_seq.text = todoModel.seq.toString()
            tv_title.text = todoModel.title
            tv_content.text = todoModel.content
            tv_date.text = todoModel.createDate.convertDateToString("yyyy.MM.dd HH:mm")

            iv_delete.setOnClickListener {
                deletetItemClick(todoModel)
            }

        }

    }
    
}


/* createDate 을 Date to String  */
fun Long.convertDateToString(format: String): String {
    val simpleDateFormat = SimpleDateFormat(format)
    return simpleDateFormat.format(Date(this))
}

 

5-4. item_list.xml

RecyclerView 에 들어가는 아이템 xml 입니다.

<?xml version="1.0" encoding="utf-8"?>

<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_margin="8dp"
    app:cardCornerRadius="20dp">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="horizontal"
        android:weightSum="100">

        <LinearLayout
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_marginVertical="10dp"
            android:layout_marginLeft="10dp"
            android:layout_weight="85"
            android:orientation="horizontal"
            android:weightSum="100">

            <!-- seq -->
            <LinearLayout
                android:layout_width="0dp"
                android:layout_height="match_parent"
                android:layout_weight="15"
                android:gravity="top"
                android:orientation="vertical"
                android:weightSum="100">
                <TextView
                    android:id="@+id/tv_seq"
                    android:layout_width="match_parent"
                    android:layout_height="0dp"
                    android:layout_margin="6dp"
                    android:layout_weight="45"
                    android:background="@drawable/bg_round"
                    android:elevation="4dp"
                    android:gravity="center"
                    android:text="12"
                    android:textColor="@color/black"
                    android:textSize="12sp" />
            </LinearLayout>

            <!-- title, content, date -->
            <LinearLayout
                android:layout_width="0dp"
                android:layout_height="match_parent"
                android:layout_weight="85"
                android:orientation="vertical"
                android:paddingLeft="10dp"
                android:weightSum="100">

                <TextView
                    android:id="@+id/tv_title"
                    android:layout_width="match_parent"
                    android:layout_height="0dp"
                    android:layout_weight="44"
                    android:text="myTodo Title"
                    android:textColor="@color/black"
                    android:textSize="18sp"
                    android:textStyle="bold" />

                <TextView
                    android:id="@+id/tv_content"
                    android:layout_width="match_parent"
                    android:layout_height="0dp"
                    android:layout_marginBottom="8dp"
                    android:layout_weight="30"
                    android:text="myTodo content"
                    android:textColor="@color/dark_gray_1"
                    android:textSize="15sp" />

                <TextView
                    android:id="@+id/tv_date"
                    android:layout_width="match_parent"
                    android:layout_height="0dp"
                    android:layout_weight="26"
                    android:text="2021. 08. 03"
                    android:textColor="@color/dark_gray_2"
                    android:textSize="12sp" />
                
            </LinearLayout>

        </LinearLayout>

        <!-- delete btn -->
        <LinearLayout
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_margin="10dp"
            android:layout_weight="15"
            android:gravity="center"
            android:orientation="vertical"
            android:weightSum="100">

            <ImageView
                android:id="@+id/iv_delete"
                android:layout_width="match_parent"
                android:layout_height="0dp"
                android:layout_weight="50"
                android:src="@drawable/ic_baseline_delete_forever_24" />
        </LinearLayout>
        
    </LinearLayout>

</androidx.cardview.widget.CardView>

iv_delete 의 src 이미지는 벡터(Vector) 이미지를 사용하였습니다. 아래 포스팅을 참고해주세요!

https://eunoia3jy.tistory.com/122

 

[안드로이드/Android] 안드로이드 스튜디오(Android Studio) 에서 벡터(Vector) 이미지/아이콘 추가

안녕하세용! 안드로이드 스튜디오(Android Studio) 에서 벡터(Vector) 이미지/아이콘 을 추가하여 사용하는 방법입니다. 벡터(Vector) 이미지/아이콘 은 png 이미지 처럼 각 픽셀별로 컬러값을 갖지 않고

eunoia3jy.tistory.com

 

5-5. dialog_add.xml

메인화면에서 todo 리스트를 추가하기 위해 추가버튼 클릭 시 표시되는 dialog 의 View 입니다.

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:padding="16dp">

    <EditText
        android:id="@+id/et_add_title"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:hint="제목을 입력하세요. "
        android:textSize="15dp"
        android:textColor="#000000"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <EditText
        android:id="@+id/et_add_content"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginTop="16dp"
        android:hint="내용을 입력하세요. "
        android:textSize="15dp"
        android:textColor="#000000"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/et_add_title" />

</androidx.constraintlayout.widget.ConstraintLayout>

 

 

결과 화면

 

감사합니당~!

 

728x90
반응형