8주차. FLO 앱 클론 코딩 - Login with Token

변현섭·2023년 11월 8일
1

5th UMC Android Study

목록 보기
8/10

✅ 8주차 목표

  • 사용자 회원가입 및 로그인을 구현할 수 있다.
  • Database 및 SharedPreferences를 활용하여, 사용자를 식별하고 정보를 저장할 수 있다.

1. 회원가입 및 로그인 구현하기

1) 회원가입 및 로그인 화면 구성하기

① LoginActivity를 생성한다.

class LoginActivity : AppCompatActivity() {

    lateinit var binding : ActivityLoginBinding
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityLoginBinding.inflate(layoutInflater)
        setContentView(binding.root)
    }
}

② activity_login.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">

    <ImageView
        android:id="@+id/login_close_iv"
        android:layout_width="30dp"
        android:layout_height="30dp"
        android:layout_marginTop="20dp"
        android:layout_marginEnd="5dp"
        android:src="@drawable/btn_actionbar_close"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <ImageView
        android:id="@+id/login_flo_logo_iv"
        android:layout_width="80dp"
        android:layout_height="30dp"
        android:src="@drawable/ic_flo_logo"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/login_close_iv" />

    <EditText
        android:id="@+id/login_id_et"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="20dp"
        android:layout_marginTop="80dp"
        android:layout_marginEnd="5dp"
        android:background="@null"
        android:hint="아이디(이메일)"
        android:inputType="text"
        android:textSize="16sp"
        app:layout_constraintEnd_toStartOf="@+id/login_at_sign_tv"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/login_flo_logo_iv" />

    <View
        android:id="@+id/login_id_underscore_view"
        android:layout_width="0dp"
        android:layout_height="1px"
        android:layout_marginTop="15dp"
        android:background="#a8a8a8"
        app:layout_constraintEnd_toEndOf="@+id/login_id_et"
        app:layout_constraintStart_toStartOf="@+id/login_id_et"
        app:layout_constraintTop_toBottomOf="@+id/login_id_et"/>

    <TextView
        android:id="@+id/login_at_sign_tv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="14sp"
        android:textColor="@color/black"
        android:textStyle="bold"
        android:text="\@"
        app:layout_constraintBottom_toBottomOf="@+id/login_id_et"
        app:layout_constraintEnd_toEndOf="@+id/login_flo_logo_iv"
        app:layout_constraintStart_toStartOf="@+id/login_flo_logo_iv"
        app:layout_constraintTop_toTopOf="@+id/login_id_et" />

    <EditText
        android:id="@+id/login_direct_input_et"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="5dp"
        android:layout_marginEnd="20dp"
        android:background="@null"
        android:hint="직접입력"
        android:inputType="textWebEmailAddress"
        android:textSize="16sp"
        app:layout_constraintBottom_toBottomOf="@+id/login_id_et"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toEndOf="@+id/login_at_sign_tv"
        app:layout_constraintTop_toTopOf="@+id/login_id_et" />

    <View
        android:id="@+id/login_address_underscore_view"
        android:layout_width="0dp"
        android:layout_height="1px"
        android:layout_marginTop="15dp"
        android:background="#a8a8a8"
        app:layout_constraintEnd_toEndOf="@+id/login_direct_input_et"
        app:layout_constraintStart_toStartOf="@+id/login_direct_input_et"
        app:layout_constraintTop_toBottomOf="@+id/login_direct_input_et"/>

    <ImageView
        android:id="@+id/login_email_list_iv"
        android:layout_width="15dp"
        android:layout_height="15dp"
        android:layout_marginEnd="5dp"
        android:src="@drawable/nugu_btn_down"
        app:layout_constraintBottom_toBottomOf="@+id/login_direct_input_et"
        app:layout_constraintEnd_toEndOf="@+id/login_direct_input_et"
        app:layout_constraintTop_toTopOf="@+id/login_direct_input_et" />

    <EditText
        android:id="@+id/login_password_et"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="20dp"
        android:layout_marginTop="40dp"
        android:layout_marginEnd="20dp"
        android:background="@null"
        android:hint="비밀번호"
        android:inputType="textPassword"
        android:textSize="16sp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/login_id_underscore_view" />

    <View
        android:id="@+id/login_password_underscore_view"
        android:layout_width="0dp"
        android:layout_height="1px"
        android:layout_marginTop="15dp"
        android:background="#a8a8a8"
        app:layout_constraintEnd_toEndOf="@+id/login_password_et"
        app:layout_constraintStart_toStartOf="@+id/login_password_et"
        app:layout_constraintTop_toBottomOf="@+id/login_password_et"/>

    <ImageView
        android:id="@+id/login_hide_password_iv"
        android:layout_width="30dp"
        android:layout_height="30dp"
        android:layout_marginEnd="5dp"
        android:src="@drawable/btn_input_password"
        app:layout_constraintBottom_toBottomOf="@+id/login_password_et"
        app:layout_constraintEnd_toEndOf="@+id/login_password_et"
        app:layout_constraintTop_toTopOf="@+id/login_password_et" />

    <androidx.appcompat.widget.AppCompatButton
        android:id="@+id/login_sign_in_btn"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="20dp"
        android:layout_marginTop="40dp"
        android:layout_marginEnd="20dp"
        android:background="@drawable/button_background_flo_color"
        android:text="로그인"
        android:textColor="@color/white"
        android:textSize="16sp"
        android:textStyle="bold"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/login_password_underscore_view" />

    <TextView
        android:id="@+id/login_find_id_tv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="10dp"
        android:text="아이디 찾기"
        app:layout_constraintStart_toStartOf="@+id/login_sign_in_btn"
        app:layout_constraintTop_toBottomOf="@+id/login_sign_in_btn" />

    <TextView
        android:id="@+id/login_division_tv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="5dp"
        android:text="|"
        app:layout_constraintBottom_toBottomOf="@+id/login_find_id_tv"
        app:layout_constraintStart_toEndOf="@+id/login_find_id_tv"
        app:layout_constraintTop_toTopOf="@+id/login_find_id_tv" />

    <TextView
        android:id="@+id/login_find_password_tv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="5dp"
        android:text="비밀번호 찾기"
        app:layout_constraintBottom_toBottomOf="@+id/login_division_tv"
        app:layout_constraintStart_toEndOf="@+id/login_division_tv"
        app:layout_constraintTop_toTopOf="@+id/login_division_tv" />

    <TextView
        android:id="@+id/login_sign_up_tv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="10dp"
        android:text="회원가입"
        android:textColor="@color/black"
        android:textStyle="bold"
        app:layout_constraintEnd_toEndOf="@+id/login_sign_in_btn"
        app:layout_constraintTop_toBottomOf="@+id/login_sign_in_btn" />

    <LinearLayout
        android:id="@+id/login_t_world_login_layout"
        android:layout_width="0dp"
        android:layout_height="50dp"
        android:layout_marginStart="20dp"
        android:layout_marginTop="35dp"
        android:layout_marginEnd="20dp"
        android:background="@drawable/button_background_black_color"
        android:gravity="center"
        android:orientation="horizontal"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/login_find_id_tv">

        <ImageView
            android:layout_width="25dp"
            android:layout_height="25dp"
            android:layout_marginEnd="4dp"
            android:src="@drawable/ico_20_logo_tid_white" />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="아이디로 로그인"
            android:textColor="@color/white"
            android:textSize="16sp"
            android:textStyle="bold" />

    </LinearLayout>

    <LinearLayout
        android:id="@+id/login_phone_number_login_layout"
        android:layout_width="0dp"
        android:layout_height="50dp"
        android:layout_marginStart="20dp"
        android:layout_marginTop="10dp"
        android:layout_marginEnd="20dp"
        android:background="@drawable/button_background_white_color"
        android:gravity="center"
        android:orientation="horizontal"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/login_t_world_login_layout">

        <ImageView
            android:layout_width="25dp"
            android:layout_height="25dp"
            android:layout_marginEnd="4dp"
            android:scaleType="fitXY"
            android:src="@drawable/btn_setting_phone" />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="휴대폰 번호 로그인"
            android:textColor="@color/black"
            android:textSize="16sp"
            android:textStyle="bold" />

    </LinearLayout>


    <LinearLayout
        android:id="@+id/login_social_login_layout"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="40dp"
        android:orientation="horizontal"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/login_phone_number_login_layout">

        <ImageView
            android:id="@+id/login_naver_login_iv"
            android:layout_width="45dp"
            android:layout_height="45dp"
            android:src="@drawable/naver_44" />

        <ImageView
            android:id="@+id/login_kakako_login_iv"
            android:layout_width="45dp"
            android:layout_height="45dp"
            android:layout_marginStart="15dp"
            android:layout_marginEnd="15dp"
            android:src="@drawable/kakako_44" />

        <ImageView
            android:id="@+id/login_apple_login_iv"
            android:layout_width="45dp"
            android:layout_height="45dp"
            android:src="@drawable/apple_44" />
    </LinearLayout>

</androidx.constraintlayout.widget.ConstraintLayout>

③ SignUpActivity를 생성한다.

class SignUpActivity : AppCompatActivity() {

    lateinit var binding: ActivitySignUpBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivitySignUpBinding.inflate(layoutInflater)
        setContentView(binding.root)

        
    }
}

④ activity_sign_up.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"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:id="@+id/sign_up_sign_up_tv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="50dp"
        android:text="회원가입"
        android:textColor="@color/black"
        android:textSize="18sp"
        android:textStyle="bold"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <EditText
        android:id="@+id/sign_up_id_et"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="20dp"
        android:layout_marginTop="50dp"
        android:layout_marginEnd="5dp"
        android:background="@null"
        android:hint="아이디(이메일)"
        android:inputType="text"
        android:textSize="16sp"
        app:layout_constraintEnd_toStartOf="@+id/sign_up_at_sign_tv"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/sign_up_sign_up_tv" />

    <View
        android:id="@+id/sign_up_id_underscore_view"
        android:layout_width="0dp"
        android:layout_height="1px"
        android:layout_marginTop="15dp"
        android:background="#a8a8a8"
        app:layout_constraintEnd_toEndOf="@+id/sign_up_id_et"
        app:layout_constraintStart_toStartOf="@+id/sign_up_id_et"
        app:layout_constraintTop_toBottomOf="@+id/sign_up_id_et"/>

    <TextView
        android:id="@+id/sign_up_at_sign_tv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="\@"
        android:textColor="@color/black"
        android:textSize="14sp"
        android:textStyle="bold"
        app:layout_constraintBottom_toBottomOf="@+id/sign_up_id_et"
        app:layout_constraintEnd_toEndOf="@+id/sign_up_sign_up_tv"
        app:layout_constraintStart_toStartOf="@+id/sign_up_sign_up_tv"
        app:layout_constraintTop_toTopOf="@+id/sign_up_id_et" />

    <EditText
        android:id="@+id/sign_up_direct_input_et"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="5dp"
        android:layout_marginEnd="20dp"
        android:background="@null"
        android:hint="직접입력"
        android:inputType="textWebEmailAddress"
        android:textSize="16sp"
        app:layout_constraintBottom_toBottomOf="@+id/sign_up_id_et"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toEndOf="@+id/sign_up_at_sign_tv"
        app:layout_constraintTop_toTopOf="@+id/sign_up_at_sign_tv" />

    <View
        android:id="@+id/sign_up_address_underscore_view"
        android:layout_width="0dp"
        android:layout_height="1px"
        android:layout_marginTop="15dp"
        android:background="#a8a8a8"
        app:layout_constraintEnd_toEndOf="@+id/sign_up_direct_input_et"
        app:layout_constraintStart_toStartOf="@+id/sign_up_direct_input_et"
        app:layout_constraintTop_toBottomOf="@+id/sign_up_direct_input_et"/>

    <ImageView
        android:id="@+id/sign_up_email_list_iv"
        android:layout_width="15dp"
        android:layout_height="15dp"
        android:layout_marginEnd="5dp"
        android:src="@drawable/nugu_btn_down"
        app:layout_constraintBottom_toBottomOf="@+id/sign_up_direct_input_et"
        app:layout_constraintEnd_toEndOf="@+id/sign_up_direct_input_et"
        app:layout_constraintTop_toTopOf="@+id/sign_up_direct_input_et" />

    <TextView
        android:id="@+id/sign_up_email_error_tv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textColor="@color/form_error"
        android:textSize="14sp"
        android:textStyle="bold"
        android:layout_marginTop="10dp"
        android:layout_marginStart="20dp"
        android:visibility="gone"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/sign_up_id_underscore_view"/>

    <EditText
        android:id="@+id/sign_up_name_et"
        android:layout_width="150dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="20dp"
        android:layout_marginTop="30dp"
        android:layout_marginEnd="20dp"
        android:background="@null"
        android:hint="이름"
        android:inputType="textPersonName"
        android:textSize="16sp"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/sign_up_email_error_tv" />

    <View
        android:id="@+id/sign_up_name_underscore_view"
        android:layout_width="150dp"
        android:layout_height="1px"
        android:layout_marginTop="15dp"
        android:background="#a8a8a8"
        app:layout_constraintStart_toStartOf="@+id/sign_up_password_et"
        app:layout_constraintTop_toBottomOf="@+id/sign_up_name_et"/>

    <EditText
        android:id="@+id/sign_up_password_et"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="20dp"
        android:layout_marginTop="40dp"
        android:layout_marginEnd="20dp"
        android:background="@null"
        android:hint="비밀번호"
        android:inputType="textPassword"
        android:textSize="16sp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/sign_up_name_et" />

    <View
        android:id="@+id/sign_up_password_underscore_view"
        android:layout_width="0dp"
        android:layout_height="1px"
        android:layout_marginTop="15dp"
        android:background="#a8a8a8"
        app:layout_constraintEnd_toEndOf="@+id/sign_up_password_et"
        app:layout_constraintStart_toStartOf="@+id/sign_up_password_et"
        app:layout_constraintTop_toBottomOf="@+id/sign_up_password_et"/>

    <ImageView
        android:id="@+id/sign_up_hide_password_iv"
        android:layout_width="30dp"
        android:layout_height="30dp"
        android:layout_marginEnd="5dp"
        android:src="@drawable/btn_input_password"
        app:layout_constraintBottom_toBottomOf="@+id/sign_up_password_et"
        app:layout_constraintEnd_toEndOf="@+id/sign_up_password_et"
        app:layout_constraintTop_toTopOf="@+id/sign_up_password_et" />

    <EditText
        android:id="@+id/sign_up_password_check_et"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="20dp"
        android:layout_marginTop="40dp"
        android:layout_marginEnd="20dp"
        android:background="@null"
        android:hint="비밀번호 확인"
        android:inputType="textPassword"
        android:textSize="16sp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/sign_up_password_underscore_view" />

    <View
        android:id="@+id/sign_up_password_check_underscore_view"
        android:layout_width="0dp"
        android:layout_height="1px"
        android:layout_marginTop="15dp"
        android:background="#a8a8a8"
        app:layout_constraintEnd_toEndOf="@+id/sign_up_password_check_et"
        app:layout_constraintStart_toStartOf="@+id/sign_up_password_check_et"
        app:layout_constraintTop_toBottomOf="@+id/sign_up_password_check_et"/>

    <ImageView
        android:id="@+id/sign_up_hide_password_check_iv"
        android:layout_width="30dp"
        android:layout_height="30dp"
        android:layout_marginEnd="5dp"
        android:src="@drawable/btn_input_password"
        app:layout_constraintBottom_toBottomOf="@+id/sign_up_password_check_et"
        app:layout_constraintEnd_toEndOf="@+id/sign_up_password_check_et"
        app:layout_constraintTop_toTopOf="@+id/sign_up_password_check_et" />

    <androidx.appcompat.widget.AppCompatButton
        android:id="@+id/sign_up_sign_up_btn"
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:background="@drawable/button_background_flo_color"
        android:text="가입완료"
        android:textColor="@color/white"
        android:textSize="16sp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent" />

    <ProgressBar
        android:id="@+id/sign_up_loading_pb"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginBottom="8dp"
        android:indeterminateTint="@color/flo"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        android:visibility="gone"/>
</androidx.constraintlayout.widget.ConstraintLayout>

2) 회원가입 기능 구현하기

① LockerFragment의 로그인 버튼을 클릭했을 때, LoginActivity로 전환되도록 만들어주자.

binding.lockerLoginTv.setOnClickListener {
    val intent = Intent(requireActivity(), LoginActivity::class.java)
    startActivity(intent)
}

② LoginActivity의 회원가입 버튼을 눌렀을 때, SignUpActivity로 전환되도록 만든다.

binding.loginSignUpTv.setOnClickListener {
    val intent = Intent(this, SignUpActivity::class.java)
    startActivity(intent)
}

③ 회원가입한 유저의 정보를 저장하기 위해 User 테이블을 생성한다.

@Entity(tableName = "UserTable")
data class User(
    var email : String,
    var password : String
) {
    @PrimaryKey(autoGenerate = true) var id : Int = 0
}

④ UserDao를 생성한다.

@Dao
interface UserDao {
    @Insert
    fun insert(user : User) 
    
    @Query("select * from UserTable")
    fun getUsers() : List<User>
    
    @Query("select * from UserTable where email =:email and password = :password")
    fun getUser(email : String, password : String) : User?
}

⑤ SongDatabase에 User 테이블과 UserDao를 추가한다.

@Database(entities = [Song::class, Album::class, User::class], version = 1)
abstract class SongDatabase: RoomDatabase() {
    abstract fun albumDao(): AlbumDao
    abstract fun songDao(): SongDao
    abstract fun userDao() : UserDao
    ...

⑥ SignUpActivity에 아래의 내용을 입력한다.

class SignUpActivity : AppCompatActivity() {

    lateinit var binding: ActivitySignUpBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivitySignUpBinding.inflate(layoutInflater)
        setContentView(binding.root)

        binding.signUpSignUpBtn.setOnClickListener {
            val signUpCompletion = signUp()
            if(signUpCompletion) {
                finish()
            }
        }
    }

    private fun getUser() : User {
        val email : String = binding.signUpIdEt.text.toString() + "@" + binding.signUpDirectInputEt.text.toString()
        val pwd : String = binding.signUpPasswordEt.text.toString()

        return User(email, pwd)
    }

    private fun signUp() : Boolean {
        if(binding.signUpIdEt.text.toString().isEmpty() || binding.signUpDirectInputEt.text.toString().isEmpty()) {
            Toast.makeText(this, "이메일 형식이 잘못되었습니다.", Toast.LENGTH_SHORT).show()
            return false
        }

        if(binding.signUpPasswordEt.text.toString() != binding.signUpPasswordCheckEt.text.toString()) {
            Toast.makeText(this, "비밀번호가 일치하지 않습니다.", Toast.LENGTH_SHORT).show()
            return false
        }

        val userDB = SongDatabase.getInstance(this)!!
        userDB.userDao().insert(getUser())

        val user = userDB.userDao().getUsers()
        Log.d("sign-up", user.toString())

		Toast.makeText(this, "회원가입이 완료되었습니다.", Toast.LENGTH_SHORT).show()
        return true
    }
}

이제 코드를 실행시켜보자. 예외가 잘 처리되고, 회원가입도 잘 진행된다.

3) 로그인 기능 구현하기

① LoginActivity에 아래의 내용을 입력한다.

class LoginActivity : AppCompatActivity() {

    lateinit var binding : ActivityLoginBinding
    
	override fun onCreate(savedInstanceState: Bundle?) {
		...
	    
	    binding.loginSignInBtn.setOnClickListener {
	    	login()
	    }
	}

    private fun login() {
        if (binding.loginIdEt.text.toString().isEmpty() || binding.loginDirectInputEt.text.toString().isEmpty()) {
            Toast.makeText(this, "이메일을 입력해주세요", Toast.LENGTH_SHORT).show()
            return
        }

        if (binding.loginPasswordEt.text.toString().isEmpty()) {
            Toast.makeText(this, "비밀번호를 입력해주세요", Toast.LENGTH_SHORT).show()
            return
        }

        val email : String = binding.loginIdEt.text.toString() + "@" + binding.loginDirectInputEt.text.toString()
        val pwd : String = binding.loginPasswordEt.text.toString()

        val songDB = SongDatabase.getInstance(this)!!
        val user = songDB.userDao().getUser(email, pwd)

        if (user != null) {
            Log.d("LoginActivity", user.id.toString())
            saveJwt(user.id)
            startMainActivity()
        } else {
            Toast.makeText(this, "회원 정보가 존재하지 않습니다", Toast.LENGTH_SHORT).show()
        }
    }

    private fun saveJwt(jwt : Int) {
        val spf = getSharedPreferences("auth", MODE_PRIVATE)
        val editor = spf.edit()

        editor.putInt("jwt", jwt)
        editor.apply()
    }

    private fun startMainActivity() {
        val intent = Intent(this, MainActivity::class.java)
        startActivity(intent)
    }
}

② LockerFragment에서 로그인한 유저에게는 로그아웃 버튼이, 로그인하지 않은 유저에게는 로그인 버튼이 표시되도록 만들어주자.

class LockerFragment : Fragment() {
	...
    
    override fun onStart() {
        super.onStart()
        initViews()
    }
    private fun getJwt() : Int {
        val spf = requireActivity().getSharedPreferences("auth", AppCompatActivity.MODE_PRIVATE)
        return spf!!.getInt("jwt", 0)
    }

    private fun initViews() {
        val jwt : Int = getJwt()
        if (jwt == 0) {
            binding.lockerLoginTv.text="로그인"
            binding.lockerLoginTv.setOnClickListener {
                startActivity(Intent(requireActivity(), LoginActivity::class.java))
            }
        }

        else {
            binding.lockerLoginTv.text = "로그아웃"
            binding.lockerLoginTv.setOnClickListener {
                logout()
                startActivity(Intent(requireActivity(), MainActivity::class.java))
            }
        }
    }

    private fun logout() {
        val spf = activity?.getSharedPreferences("auth", AppCompatActivity.MODE_PRIVATE)
        val editor = spf!!.edit()
        editor.remove("jwt")
        editor.apply()
    }
}

코드를 실행시켜보자. 데이터베이스에 존재하지 않는 ID/PW로 로그인하는 경우에 대해 잘 처리하고 있고, 로그인도 정상적으로 진행된다.

2. 사용자 별 좋아요 기능 구현하기

로그인 기능이 추가됨에 따라, 사용자 별로 좋아요 데이터를 저장할 필요가 생겼다. 지금부터 사용자 별로 좋아요를 저장할 수 있는 데이터베이스를 구축해보자.

1) 앨범 좋아요/좋아요 취소 기능 구현하기

① Like data class를 생성한다.

@Entity(tableName = "LikeTable")
data class Like(
    var userId : Int,
    var albumId : Int
) {
    @PrimaryKey(autoGenerate = true) var id : Int = 0
}

② AlbumDao에 아래의 쿼리문을 추가한다.

@Insert
fun likeAlbum(like : Like)

@Query("select id from LikeTable where userId =:userId and albumId = :albumId")
fun isLikedAlbum(userId : Int, albumId : Int) : Int?

@Query("delete from LikeTable where userId =:userId and albumId = :albumId")
fun dislikedAlbum(userId : Int, albumId : Int)

@Query("select at.* from LikeTable as lt left join AlbumTable as at on lt.albumId = at.id where lt.userId = :userId")
fun getLikedAlbums(userId : Int) : List<Album>

③ SongDatabase에 Like 테이블을 추가한다.

@Database(entities = [Song::class, Album::class, User::class, Like::class], version = 1)

④ AlbumFragment에 아래의 내용을 추가한다.

class AlbumFragment : Fragment() {

    lateinit var binding: FragmentAlbumBinding
    private var gson: Gson = Gson()
    
    private var isLiked : Boolean = false
    ...
    
    override fun onCreateView(
        inflater: LayoutInflater, 
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        binding = FragmentAlbumBinding.inflate(inflater,container,false)

        val albumToJson = arguments?.getString("album")
        val album = gson.fromJson(albumToJson, Album::class.java)
        isLiked = isLikedAlbum(album.id)
        setInit(album)
        setOnClickListener(album)
        ...
        
	private fun getJwt() : Int {
        val spf = requireActivity().getSharedPreferences("auth", AppCompatActivity.MODE_PRIVATE)
        return spf.getInt("jwt", 0)
    }

    private fun likeAlbum(userId : Int, albumId : Int) {
        val songDB = SongDatabase.getInstance(requireActivity())!!
        val like = Like(userId, albumId)

        songDB.albumDao().likeAlbum(like)
    }

    private fun isLikedAlbum(albumId : Int) : Boolean {
        val songDB = SongDatabase.getInstance(requireActivity())!!
        val userId = getJwt()

        val likeId : Int? = songDB.albumDao().isLikedAlbum(userId, albumId)
        return likeId != null
    }

    private fun disLikeAlbum(albumId : Int) {
        val songDB = SongDatabase.getInstance(requireActivity())!!
        val userId = getJwt()

        songDB.albumDao().dislikedAlbum(userId, albumId)
    }

    private fun setOnClickListener(album : Album) {
        val userId = getJwt()
        binding.albumLikeIv.setOnClickListener {
            if(isLiked) {
                binding.albumLikeIv.setImageResource(R.drawable.ic_my_like_off)
                disLikeAlbum(album.id)
            }

            else {
                binding.albumLikeIv.setImageResource((R.drawable.ic_my_like_on))
                likeAlbum(userId, album.id)
            }
        }

    }
}

2) 좋아요 표시한 앨범 목록 나타내기

① SavedAlbumFragment를 생성한다.

② SavedAlbumFragment를 LockerFragment의 TabLayout에 포함시키기 위해 LockerVPAdapter를 아래와 같이 수정한다.

class LockerVPAdapter (fragment : Fragment) : FragmentStateAdapter(fragment) {
    override fun getItemCount(): Int  = 3

    override fun createFragment(position: Int): Fragment {
        return when(position){
            0 -> SavedSongFragment()
            1 -> MusicFileFragment()
            else -> SavedAlbumFragment()
        }
    }
}

③ LockerFragment의 information을 아래와 같이 수정한다.

private val information = arrayListOf("저장한곡", "음악파일", "저장앨범")

④ fragment_locker.xml 파일에서 TabLayout의 width를 wrap_content로 변경한다.

⑤ SavedAlbumRVAdapter 클래스를 생성한다.

class SavedAlbumRVAdapter (): RecyclerView.Adapter<SavedAlbumRVAdapter.ViewHolder>() {
    private val albums = ArrayList<Album>()

    interface MyItemClickListener{
        fun onRemoveSong(songId: Int)
    }

    private lateinit var mItemClickListener: MyItemClickListener

    fun setMyItemClickListener(itemClickListener: MyItemClickListener){
        mItemClickListener = itemClickListener
    }

    override fun onCreateViewHolder(viewGroup: ViewGroup, viewType: Int): SavedAlbumRVAdapter.ViewHolder {
        val binding: ItemLockerAlbumBinding = ItemLockerAlbumBinding.inflate(LayoutInflater.from(viewGroup.context), viewGroup, false)

        return ViewHolder(binding)
    }

    override fun onBindViewHolder(holder: SavedAlbumRVAdapter.ViewHolder, position: Int) {
        holder.bind(albums[position])
        holder.binding.itemLockerAlbumMoreIv.setOnClickListener {
            mItemClickListener.onRemoveSong(albums[position].id)
            removeSong(position)
        }
    }

    override fun getItemCount(): Int = albums.size

    @SuppressLint("NotifyDataSetChanged")
    fun addAlbums(albums: ArrayList<Album>) {
        this.albums.clear()
        this.albums.addAll(albums)

        notifyDataSetChanged()
    }

    fun removeSong(position: Int){
        albums.removeAt(position)
        notifyDataSetChanged()
    }

    inner class ViewHolder(val binding: ItemLockerAlbumBinding) : RecyclerView.ViewHolder(binding.root){
        fun bind(album: Album){
            binding.itemLockerAlbumCoverImgIv.setImageResource(album.coverImage!!)
            binding.itemLockerAlbumTitleTv.text = album.title
            binding.itemLockerAlbumSingerTv.text = album.singer
        }
    }

}

⑥ fragment_saved_album.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">

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/locker_savedSong_recyclerView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:listitem="@layout/item_locker_album"
        android:overScrollMode="never"
        android:orientation="vertical"
        app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"/>

</androidx.constraintlayout.widget.ConstraintLayout>

⑦ SavedAlbumFragment에 아래의 내용을 입력한다.

class SavedAlbumFragment : Fragment() {
    lateinit var binding: FragmentSavedAlbumBinding
    lateinit var albumDB: SongDatabase

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        binding = FragmentSavedAlbumBinding.inflate(inflater, container, false)

        albumDB = SongDatabase.getInstance(requireContext())!!

        return binding.root
    }

    override fun onStart() {
        super.onStart()
        initRecyclerview()
    }

    private fun initRecyclerview(){
        binding.lockerSavedSongRecyclerView.layoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)

        val albumRVAdapter = SavedAlbumRVAdapter()
        //리스너 객체 생성 및 전달

        albumRVAdapter.setMyItemClickListener(object : SavedAlbumRVAdapter.MyItemClickListener{
            override fun onRemoveSong(songId: Int) {
                albumDB.albumDao().getLikedAlbums(getJwt())
            }
        })

        binding.lockerSavedSongRecyclerView.adapter = albumRVAdapter

        albumRVAdapter.addAlbums(albumDB.albumDao().getLikedAlbums(getJwt()) as ArrayList)
    }

    private fun getJwt() : Int {
        val spf = activity?.getSharedPreferences("auth" , AppCompatActivity.MODE_PRIVATE)
        val jwt = spf!!.getInt("jwt", 0)
        Log.d("MAIN_ACT/GET_JWT", "jwt_token: $jwt")

        return jwt
    }
}

코드를 실행시켜보자. 좋아요 표시한 앨범이 각 유저별로 다르게 나타나는 것을 확인할 수 있을 것이다.

3. 특정 위치로 스크롤하기

1) 지정 스크롤 구현하기

① drawable 디렉토리 하위로, selected_button.xml, not_selected_button.xml 파일을 추가한다.

  • selected_button.xml
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle">
    <corners android:radius="40dp" />
    <solid android:color="@color/select_color" />
</shape>
  • not_selected_button.xml
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle">
    <corners android:radius="40dp" />
    <solid android:color="@color/song_player_bg" />
</shape>

② fragment_look.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=".LookFragment">

    <TextView
        android:id="@+id/textView2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="20dp"
        android:layout_marginTop="20dp"
        android:gravity="center"
        android:text="둘러보기"
        android:textColor="@color/black"
        android:textSize="25sp"
        android:textStyle="bold"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <HorizontalScrollView
        android:id="@+id/horizontalScrollView"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:paddingVertical="20dp"
        android:scrollbars="none"
        app:layout_constraintTop_toBottomOf="@+id/textView2">

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

            <android.widget.Button
                android:id="@+id/look_chart_btn"
                android:layout_width="wrap_content"
                android:layout_height="40dp"
                android:layout_marginLeft="25dp"
                android:background="@drawable/selected_button"
                android:text="차트"
                android:textColor="@color/white"
                android:textSize="15sp"
                android:textStyle="bold" />

            <android.widget.Button
                android:id="@+id/look_video_btn"
                android:layout_width="wrap_content"
                android:layout_height="40dp"
                android:layout_marginLeft="10dp"
                android:background="@drawable/not_selected_button"
                android:text="비디오"
                android:textColor="@color/white"
                android:textSize="15sp"
                android:textStyle="bold" />

            <android.widget.Button
                android:id="@+id/look_genre_btn"
                android:layout_width="wrap_content"
                android:layout_height="40dp"
                android:layout_marginLeft="10dp"
                android:background="@drawable/not_selected_button"
                android:text="장르"
                android:textColor="@color/white"
                android:textSize="15sp"
                android:textStyle="bold" />

            <android.widget.Button
                android:id="@+id/look_situation_btn"
                android:layout_width="wrap_content"
                android:layout_height="40dp"
                android:layout_marginLeft="10dp"
                android:background="@drawable/not_selected_button"
                android:text="상황"
                android:textColor="@color/white"
                android:textSize="15sp"
                android:textStyle="bold" />

            <android.widget.Button
                android:id="@+id/look_audio_btn"
                android:layout_width="wrap_content"
                android:layout_height="40dp"
                android:layout_marginLeft="10dp"
                android:background="@drawable/not_selected_button"
                android:text="오디오"
                android:textColor="@color/white"
                android:textSize="15sp"
                android:textStyle="bold" />

            <android.widget.Button
                android:id="@+id/look_atmostphere_btn"
                android:layout_width="wrap_content"
                android:layout_height="40dp"
                android:layout_marginRight="20dp"
                android:layout_marginLeft="10dp"
                android:background="@drawable/not_selected_button"
                android:text="분위기"
                android:textColor="@color/white"
                android:textSize="15sp"
                android:textStyle="bold" />
        </LinearLayout>
    </HorizontalScrollView>

    <ScrollView
        android:id="@+id/look_sv"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/horizontalScrollView">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical">

            <TextView
                android:id="@+id/look_chart_tv"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginStart="20dp"
                android:layout_marginTop="30dp"
                android:text="차트"
                android:textColor="@color/black"
                android:textSize="25sp"
                android:textStyle="bold" />

            <androidx.cardview.widget.CardView
                android:id="@+id/cardView"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginHorizontal="20dp"
                android:layout_marginTop="20dp"
                app:cardCornerRadius="8dp"
                app:cardElevation="4dp"
                app:layout_constraintTop_toBottomOf="@+id/look_chart_tv"
                tools:layout_editor_absoluteX="20dp">

                <androidx.recyclerview.widget.RecyclerView
                    android:id="@+id/look_chart_song_rv"
                    android:layout_width="match_parent"
                    android:layout_height="match_parent"
                    android:layout_marginTop="10dp"
                    android:orientation="vertical"
                    android:overScrollMode="never"
                    app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
                    app:layout_constraintBottom_toBottomOf="parent"
                    app:layout_constraintTop_toTopOf="parent"
                    tools:listitem="@layout/item_locker_album" />

            </androidx.cardview.widget.CardView>

            <TextView
                android:id="@+id/look_video_tv"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginStart="20dp"
                android:layout_marginTop="20dp"
                android:text="비디오"
                android:textColor="@color/black"
                android:textSize="25sp"
                android:textStyle="bold"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toBottomOf="@+id/cardView" />

            <TextView
                android:id="@+id/look_video_contents_tv"
                android:layout_width="match_parent"
                android:layout_height="500dp"
                android:layout_marginStart="20dp"
                android:layout_marginVertical="20dp"
                android:text="비디오 섹션"
                android:gravity="center"
                android:textColor="@color/black"
                android:textSize="25sp"
                android:textStyle="bold"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toBottomOf="@+id/cardView" />

            <TextView
                android:id="@+id/look_genre_tv"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginStart="20dp"
                android:layout_marginTop="20dp"
                android:text="장르"
                android:textColor="@color/black"
                android:textSize="25sp"
                android:textStyle="bold"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toBottomOf="@+id/look_video_contents_tv" />

            <TextView
                android:id="@+id/look_genre_contents_tv"
                android:layout_width="match_parent"
                android:layout_height="500dp"
                android:layout_marginStart="20dp"
                android:layout_marginVertical="20dp"
                android:text="장르 섹션"
                android:gravity="center"
                android:textColor="@color/black"
                android:textSize="25sp"
                android:textStyle="bold"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toBottomOf="@+id/look_video_contents_tv" />

            <TextView
                android:id="@+id/look_situation_tv"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginStart="20dp"
                android:layout_marginTop="20dp"
                android:text="상황"
                android:textColor="@color/black"
                android:textSize="25sp"
                android:textStyle="bold"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toBottomOf="@+id/look_genre_contents_tv" />

            <TextView
                android:id="@+id/look_situation_contents_tv"
                android:layout_width="match_parent"
                android:layout_height="500dp"
                android:layout_marginStart="20dp"
                android:layout_marginVertical="20dp"
                android:text="상황 섹션"
                android:gravity="center"
                android:textColor="@color/black"
                android:textSize="25sp"
                android:textStyle="bold"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toBottomOf="@+id/look_genre_contents_tv" />

            <TextView
                android:id="@+id/look_audio_tv"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginStart="20dp"
                android:layout_marginTop="20dp"
                android:text="오디오"
                android:textColor="@color/black"
                android:textSize="25sp"
                android:textStyle="bold"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toBottomOf="@+id/look_genre_contents_tv" />

            <TextView
                android:id="@+id/look_audio_contents_tv"
                android:layout_width="match_parent"
                android:layout_height="500dp"
                android:layout_marginStart="20dp"
                android:layout_marginVertical="20dp"
                android:text="오디오 섹션"
                android:gravity="center"
                android:textColor="@color/black"
                android:textSize="25sp"
                android:textStyle="bold"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toBottomOf="@+id/look_genre_contents_tv" />

            <TextView
                android:id="@+id/look_atmostphere_tv"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginStart="20dp"
                android:layout_marginTop="20dp"
                android:text="분위기"
                android:textColor="@color/black"
                android:textSize="25sp"
                android:textStyle="bold"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toBottomOf="@+id/look_genre_contents_tv" />

            <TextView
                android:id="@+id/look_atmostphere_contents_tv"
                android:layout_width="match_parent"
                android:layout_height="500dp"
                android:layout_marginStart="20dp"
                android:layout_marginVertical="20dp"
                android:text="분위기 섹션"
                android:gravity="center"
                android:textColor="@color/black"
                android:textSize="25sp"
                android:textStyle="bold"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toBottomOf="@+id/look_genre_contents_tv" />

        </LinearLayout>

    </ScrollView>
</androidx.constraintlayout.widget.ConstraintLayout>

③ LookFragment에 아래의 내용을 입력한다.

  • List에 index를 부여해야 할 때 indices를 사용한다.
  • TextView나 버튼 등에서 반복적인 기능을 수행해야 할 때, array나 list를 사용하는 것이 좋다.
class LookFragment : Fragment() {

    lateinit var binding: FragmentLookBinding
    private lateinit var songDB: SongDatabase

    private lateinit var chartBtn : Button
    private lateinit var videoBtn : Button
    private lateinit var genreBtn : Button
    private lateinit var situationBtn : Button
    private lateinit var audioBtn : Button
    private lateinit var atmosphereBtn : Button

    private lateinit var buttonList: List<Button>

    private lateinit var chartTv : TextView
    private lateinit var videoTv : TextView
    private lateinit var genreTv : TextView
    private lateinit var situationTv : TextView
    private lateinit var audioTv : TextView
    private lateinit var atmosphereTv : TextView

    private lateinit var textList: List<TextView>

    lateinit var scrollView : ScrollView

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
    }

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        binding = FragmentLookBinding.inflate(inflater, container, false)
        songDB = SongDatabase.getInstance(requireContext())!!

        // 스크롤 뷰 초기화
        scrollView = binding.lookSv

        // 버튼 초기화
        chartBtn = binding.lookChartBtn
        videoBtn =  binding.lookVideoBtn
        genreBtn =  binding.lookGenreBtn
        situationBtn =  binding.lookSituationBtn
        audioBtn =  binding.lookAudioBtn
        atmosphereBtn =  binding.lookAtmostphereBtn

        buttonList = listOf(chartBtn, videoBtn, genreBtn, situationBtn, audioBtn, atmosphereBtn)

        // 텍스트 초기화
        chartTv = binding.lookChartTv
        videoTv = binding.lookVideoTv
        genreTv = binding.lookGenreTv
        situationTv = binding.lookSituationTv
        audioTv = binding.lookAudioTv
        atmosphereTv = binding.lookAtmostphereTv

        textList = listOf(chartTv, videoTv, genreTv, situationTv, audioTv, atmosphereTv)

        setButtonClickListeners()

        return binding.root
    }

    override fun onStart() {
        super.onStart()
        initRecyclerview()
    }

    private fun initRecyclerview(){
        val recyclerView = binding.lookChartSongRv
        recyclerView.layoutManager = LinearLayoutManager(requireActivity())
        val lookAlbumRVAdapter = LockerAlbumRVAdapter()

        binding.lookChartSongRv.adapter = lookAlbumRVAdapter
        lookAlbumRVAdapter.addSongs(songDB.songDao().getSongs() as ArrayList<Song>)
    }

    private fun setButtonClickListeners() {
        for (i in buttonList.indices) {
            val button = buttonList[i]

            button.setOnClickListener {
                initButton(i)
            }
        }
    }

    private fun initButton(idx : Int) {
        for(presentBtn : Button in buttonList) {
            if(presentBtn == buttonList[idx]) {
                presentBtn.setBackgroundResource(R.drawable.selected_button)
            } else {
                presentBtn.setBackgroundResource(R.drawable.not_selected_button)
            }
        }
        scrollView.smoothScrollTo(0, textList[idx].top)
    }
}

이제 코드를 실행시켜보자. 클릭한 키워드에 따라 지정 스크롤이 잘 동작하는 것을 확인할 수 있다.

profile
LG전자 Connected Service 1 Unit 연구원 변현섭입니다.

0개의 댓글