반응형

안드로이드 스튜디오에서 코틀린(Kotlin)으로 RecyclerView를 이용하여 연락처 화면을 만들어 스크롤할 수 있고, 터치하면 전화가 걸리도록 하는 앱 만들기 실습을 진행해 볼게요.

 

1. New Project → Empty Views Activity를 선택하고  Title을 정해주세요.  여기서는 RecycleView3라고 하였습니다.

 

2. 그래들(Gradle)과 메인액티비티(Main Activity)에서 뷰 바인딩(ViewBinding) 설정하기.

프로젝트 탐색창에서 "Gradle Scripts" 항목을 펼쳐보면 "build.gradle.kts (Module :app)" 이 보이고 더블클릭해 열어서 아래 이미지처럼 viewBinding.isEnabled = true를  android { ...  } 사이에 넣어주세요.  그리고 변경사항이 적용될 수 있도록 "Sync Now"를 클릭해 주세요.

Main Activity에는 아래처럼 기본 설정 코드가 들어 있을 텐데요, 

이를 아래와 같이 필요 없는 항목은 지우고 ViewBinding을 사용하기 위한 형태로 바꾸어 주세요. 

val binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)

 

3. 전화번호 프로필(profile) 화면 구성하기. 

프로필의 화면 구성은 아래와 같이 해볼게요.

 

프로젝트 탐색창에서  res 》 layout 에서 마우스 우클릭(이후로는 RMB로 표현)해서 New 》 Layout Resource File을 클릭하세요.

File name : 에  list_item이라 적고 OK를 클릭해 주세요.

그럼, 아래와 같이 layout 폴더에 list_item.xml이라는 파일이 생성된 것을 볼 수 있어요.

 

4. 액티비티 메인(activity_main.xml) 화면 구성하기.

추가한 ConstraintLayout이 클릭되어 있는 상태에서 오른쪽에 있는 Constraint Widget의 ⊕ 표시를 눌러 상위에 있는 부모 Constrait Layout과 연결을 시켜주세요. 

그리고 아래 이미지처럼 각각의 마진(magin)을 8씩 주세요.

우선, 전화번호 목록에 쓰일 사람들의 사진을 대신하여 이미지 아이콘을 다운로드하여 사용해 볼 텐데요,  flaticon.com에 접속하여 " profile " 로 검색해서 다운로드하여 볼게요.

검색된 아이콘들 중에 맘에 드는 것으로 몇 가지를 다운로드하여 보세요.

다운로드 표시 부분을 클릭하지 말고 이미지 가운데를 클릭하면, 가로 세로 원하는 사이즈를 선택해서 받을 수 있어요.

128픽셀을 선택해서 다운로드하세요.  512픽셀로 받아도 되지만, 기본크기가 너무 커지므로 줄여서 받아볼게요.

 

이 사이트의 경우 무료로는 하루 10개 정도 받을 수 있기 때문에 5개는 남자 이미지 5개는 여자 이미지로 총 10개를 다운로드하여서 아래와 같이 man1~5  ,  woman1~5로 네이밍 합니다.

그럼, 윈도 탐색창에서 위의 다운로드한 파일을 모두 선택해서 Ctrl + C(복사) 한 후,
안-스에서 프로젝트 탐색창의  res 》 drawable  폴더를 선택하고  Ctrl + V(붙여 넣기) 하세요.

그리고 OK 버튼을 클릭하면 아래처럼 해당 폴더에 붙여 넣기 됩니다.

물론 RMB 하여 Explorer 창을 열어 윈도 탐색창에 직업 붙여 넣기 해도 동일합니다. (해당 폴더를 바로 찾아 열어 볼 수 있음)

그럼, list_item.xml 파일에서 Widgets(위젯) 항목에 있는 ImageView를 편집화면에다 끌어다 놓기 하세요. 

그리고 바로 나타나는 창에서 man1을 선택 후 OK 버튼을 클릭합니다. 

그리고 Attributes 속성 설정창에 보이는 Constrait Widget에서 화살표로 표시한 부분만 클릭해서 부모 Constraint Layout과 각각 연결시켜 주세요.  이때 연결되는 3곳 각각의 magin은 8 정도로 세팅할게요.

 그리고 삽입한 man 이미지의 가로 폭(layout_width)과 세로 높이(layout_height)를 60dp로 설정해 주세요.

다음,  Component Tree창에서 두 번째 ConstraintLayout을 선택하고  오른쪽 Constraints항목에 있는 부분에서 layout_height 만 wrap_content로 바꾸어 주세요.

다음, 제일 위에 있는 부모 ConstraintLayout을 선택해서 동일하게 layout_height를 wrap_content로 감싸주게 되면 아래 이미지처럼 이미지가 위쪽으로 정렬이 됩니다.

 

그다음, 이미지를 선택한 상태에서 id를 이미지 뷰를 줄인 약자(iv)로 해서 iv_profile로 설정해 볼게요.

id를 변경하고 엔터를 치면 , 아래와 같이 창이 나타나고 OK를 한 번 더 눌러주면 됩니다. 

 

① 텍스트뷰를 끌어다 놓고, id = tv_name   ,   text = 홍길동 입력하고, 아래처럼 Constraint Widget의 연결을 해주세요. 

다음, text 속성에서 textSize = 16sp ,   textColor = #040404  ,  textStyle = Bold로 변경합니다.

② "전화번호"에 대한 TextView항목도 아래처럼 추가해 주세요.

Component Tree 창에서 tv_name을 Ctrl+C  ,  Ctrl+V 하면 동일한 속성으로 복사가 됩니다.   

그럼, id = tv_number로 바꾸고, 전화번호를 입력해 주세요.   색상도 적당한 색으로 바꾸고, Constraint Widget의 연결을 왼쪽 이름에 연결하고 정당한 magin을 입력해 주세요.

③ 동일한 방법으로 이메일에 대한 textView도 추가해서 아래처럼 적당할게 설정해 주세요. 

④ 동일하게 생년월일에 대한 tv_birthday texView도 아래처럼 추가해 볼게요. 

정리된 모습

 

5.  list_item.xml에 대한 객체 모듈의 클래스 만들어주기

app 》 kotlin+java 》 com.example.recyclerview3   <RMB> 해서  New 》 Kotlin Class/File을 클릭하세요.

그리고 이름에  Profile이라고 입력하고 엔터 하면

Profile이라는 확장자가 KT인 코틀린 Class 파일이 생성된 것을 볼 수 있어요.

 

클래스 파일 내용은 아래와 같이 입력해서 "사진이미지, 이름, 전화번호, 이메일, 생일" 각각에 대한 인자와 받아들일 속성에 대해 정의해 주세요. 

이때 이미지의 경우 숫자로 처리할 수 있기 때문에 Int형으로 정의합니다.

class Profile ( val picture : Int , val name : String , val telNum : String , val email : String , val birthDay : String )

 

6.  어댑터 만들기 (Adapter)

 어댑터는 뷰 홀더에서 만든 뷰 객체에 적절한 데이터를 대입해서 항목을 완성하는 역할을 하는데요,  이어서 레이아웃 매니저가(LayoutManager) 어댑터가 만든 항목들을 어떻게 배치할지 결정하여 리사이클러 뷰에 출력합니다. 

리사이클러뷰의 전체적인 구조는 아래와 같습니다.

이미지 출처 : s2choco.tistory.com
이미지 출처 : recipes4dev.tistory.com

 

그럼,  뷰홀더를 만들 때처럼,  app 》 kotlin+java 》 com.example.recyclerview3   <RMB> 해서  New 》 Kotlin Class/File을 클릭하세요.  파일 이름은 ProfileAdapter로 할게요.

클래스 파일이 생성되면 아래와 같이 우선 작성하고, 만약 RecyclerView 글자가 빨간색으로 뜬다면, 리사이클러 뷰 위젯을 자동으로 삽입되도록 Alt+Enter 하세요. 

그리고 아래처럼 코드를 이어서 작성하면, class ProfileAdapter 부분이 붉은색 밑줄이 그어지는데 마우스를 올리고 나타나는 팝업창에서 Implement members를 클릭해서 필요한 구성요소들이 자동으로 생성될 수 있도록 해줍니다. 또는 Alt+Shift+Enter 

이어서, CustomViewHolder 부분이 빨간 글자로 되는데, 이 부분도 마우스를 올리고 기다리고 있으면, 클래스를 자동으로 생성할 수 있도록 해줍니다. 

그럼, 뷰홀더(View Holder) 클래스가 만들어지는데요,

아래와 같이 코드를 완성해 보세요. TODO 라고 되어 있는 부분들은 삭제하고, 

list_item.xml에 있는 각각의 객체들을 findViewById로 변수명을 만들어 가져옵니다. 
getItemCount( ) 함수는 Main Activity에 입력되는 데이터의 총개수를 구해오는 함수입니다.

package com.example.recyclerview3
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

class ProfileAdapter ( val profileList: ArrayList<Profile> ) : RecyclerView.Adapter<ProfileAdapter.CustomViewHolder> ( )
{
    class CustomViewHolder (itemView: View) : RecyclerView.ViewHolder(itemView){    // : RecyclerView... 를 상속 받아 CustomViewHolder class를 작성함.
        val picture = itemView.findViewById<ImageView>(R.id.iv_profile)    // 인물 사진
        val name = itemView.findViewById<TextView>(R.id.tv_name)           // 이름
        val telNum = itemView.findViewById<TextView>(R.id.tv_number)       // 전화번호
        val email = itemView.findViewById<TextView>(R.id.tv_email)         // 이메일
        val birthday = itemView.findViewById<TextView>(R.id.tv_birthday)   // 생일
    }

    override fun onCreateViewHolder( parent: ViewGroup, viewType: Int ): ProfileAdapter.CustomViewHolder {
        val view = LayoutInflater.from(parent.context).inflate(R.layout.list_item, parent, false)   // parent에 있는 모든 정보(context)를 가져오는 설정 함.
        return CustomViewHolder(view)   // view 형태를 첨부 하여 위에서 정의한 CustomViewHolder( )를 리턴(호출) 함.
    }

    override fun onBindViewHolder(holder: ProfileAdapter.CustomViewHolder, position: Int) {
        holder.picture.setImageResource(profileList.get(position).picture)   // (MainActivity에서 데이터를 받아서) profileList에서 손으로 클릭(터치)되는 위치에 대한 정보를 가져옴.
        holder.name.text = profileList.get(position).name             // position 값은 데이터 0 부터 시작 됨.
        holder.telNum.text = profileList.get(position).telNum         // 만약 유형이 Int 숫자라면, 코드 마지막에 숫자를 문자열로 변환하는 코드를( '.toString()' ) 삽입해주면 에러나지 않음.
        holder.email.text = profileList.get(position).email
        holder.birthday.text = profileList.get(position).birthDay
    }

    override fun getItemCount(): Int {
        return profileList.size    // profileList의 총 데이터 갯수를 반환함.
    }
}

 

7.  메인 레이아웃 구성하기 (activity_main.xml)

MainActivity.kt 파일을 열고서, 기본 주어지는 "HolloWorld!" textView를 지우고,  아래처럼, Common 》 RecyclerView를 끌어다 놓으세요.

그리고, Constrait Wideget의 4곳을 모두 클릭해서 parant에 모두 연결시켜 주세요.

리사이클러 뷰의 id는 rv_profile로 할게요. 

 

8.  메인 액티비티 구성하기  (MainActivity.kt)

먼저, findViewById 대신 바인딩을 사용하기 위해, onCreate ( ) 함수 내에 바인딩 선언을 해주세요. 

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)

그리고 profileList라는 변수를 만들고 arrayListOf ( )라는 리스트 함수를 사용하여 앞서 정의한 데이터들의 리스트를 담을 거예요. 

이때 앞서 만든 Profile 클래스의 데이터 형식에 맞추어 입력해 주면 됩니다.  인물 Icon 이미지를 10개 준비했으니, 데이터도 우선 10개를 채워볼게요.  class Profile ( 사진, 이름, 전화번호, 이메일, 생일 )  형태로 작성해 주면 됩니다.

val profileList = arrayListOf(
    Profile(R.drawable.woman1, "유관순", "010-0808-1515", "korea@korea.com" , "08.15"),
    Profile(R.drawable.man1, "홍길동", "010-1234-5678", "gildong@chosun.com" , "03.05"),
    Profile(R.drawable.man2, "유재석", "010-7777-8888", "leejs@naver.com" , "07.07"),
    Profile(R.drawable.woman2, "엘사", "010-3333-9999", "elsa@gmail.com" , "12.25"),
    Profile(R.drawable.man3, "모피어스", "010-2222-3434", "morpheus@gmail.com" , "07.12"),
    Profile(R.drawable.man4, "네오", "010-0505-4040", "gildong@chosun.com" , "28.05"),
    Profile(R.drawable.woman3, "지예은", "010-2323-9090", "yeeun@daum.net" , "03.08"),
    Profile(R.drawable.man5, "정상훈", "010-5555-7878", "jeong@snlkorea.com" , "11.30"),
    Profile(R.drawable.woman4, "이수지", "010-3030-7080", "suji@snlkorea.com" , "06.20"),
    Profile(R.drawable.woman5, "김슬기", "010-4747-5656", "seulgi@snlkorea.com" , "28.05"),
)

그리고 레이아웃매니저(layoutManager) 함수를 통해 어댑터가 만든 항목들을 어떻게 배치할지? (가로:HORIZONTAL, 세로:VERTICAL) 정해줍니다. 주로 위아래로 스크롤하는 형태가 되기 때문에, VERTICAL로 해주면 되고요.
이제 제일 중요한, 어댑터를 최초로 사용하게 되는데,  ProfileAdapter( )를 가져와서 위에서 작성한 profileList를 담아 activity_main.xml  레이아웃의 id를 rv_profile이라고 이름 지은 RecyclerView로 바인딩하여 전달(보여주기) 해 주면 됩니다.
이 코드들은 아래와 같아요.

binding.rvProfile.layoutManager = LinearLayoutManager(this, LinearLayoutManager.H VERTICAL, false)
binding.rvProfile.setHasFixedSize(true)  // 리사이클러뷰에 대한 성능개선 관련
binding.rvProfile.adapter = ProfileAdapter(profileList)

그럼,  MainActivity.kt 파일의 완성된 전체 코드를 한 번에 보여드리면 아래와 같아요. 

package com.example.recyclerview3

import android.os.Bundle
import androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.recyclerview.widget.LinearLayoutManager
import com.example.recyclerview3.databinding.ActivityMainBinding

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)

        val profileList = arrayListOf(
            Profile(R.drawable.woman1, "유관순", "010-0808-1515", "korea@korea.com" , "08.15"),
            Profile(R.drawable.man1, "홍길동", "010-1234-5678", "gildong@chosun.com" , "03.05"),
            Profile(R.drawable.man2, "유재석", "010-7777-8888", "leejs@naver.com" , "07.07"),
            Profile(R.drawable.woman2, "엘사", "010-3333-9999", "elsa@gmail.com" , "12.25"),
            Profile(R.drawable.man3, "모피어스", "010-2222-3434", "morpheus@gmail.com" , "07.12"),
            Profile(R.drawable.man4, "네오", "010-0505-4040", "gildong@chosun.com" , "28.05"),
            Profile(R.drawable.woman3, "지예은", "010-2323-9090", "yeeun@daum.net" , "03.08"),
            Profile(R.drawable.man5, "정상훈", "010-5555-7878", "jeong@snlkorea.com" , "11.30"),
            Profile(R.drawable.woman4, "이수지", "010-3030-7080", "suji@snlkorea.com" , "06.20"),
            Profile(R.drawable.woman5, "김슬기", "010-4747-5656", "seulgi@snlkorea.com" , "28.05"),
        )
            binding.rvProfile.layoutManager = LinearLayoutManager(this, LinearLayoutManager.H VERTICAL, false)
            binding.rvProfile.setHasFixedSize(true)  // 리사이클러뷰에 대한 성능개선 관련
            binding.rvProfile.adapter = ProfileAdapter(profileList)
    }
}

 

자, 그럼 이 상태로 실행을 해서 확인을 해볼게요. 

에뮬레이터에 뜬 화면을 마우스로 위아래 스크롤 해보면, 준비된 10개의 데이터가 스크롤되면서(VERTICAL) 잘 보이는 걸 알 수 있어요.

네, 대부분 잘 보이는데요,  몇몇 데이터가 Constrait (제약) 연결된 상태에 따라 이름의 길이가 짧거나 하여 이메일 주소와 생일이 겹치는 부분이 보이는데요, 데이터에 따라 크게 겹치지 않도록 Constrait Wedjet에 있는 항목들의 간격을 조정해 보세요. 
 생일의 TextView의 오른쪽이 전화번호의 오른쪽에 연결(연동) 시켜 놓았기 때문에  이름이 짧은 사람은 전화번호가 앞으로 당겨지게 되고 이로인하서 생일도 전화번호의 오른쪽라인으로 기준이 당겨지기 때문에 이메일과 겹치는 부분이 발생하였습니다.

 따라서,  해결방법은 전화번호의 왼쪽이 연결된 이름과의 여백(magin)을 충분히 띄워 주면 됩니다. 

또는 이름의 오른쪽과 연결시킨 부분을 사진의 왼쪽 부분으로 바꾸어 연결시키고 간격을 90sp로 적정하게 벌어지도록 하면,  이름의 길이에 따라 전화번호가 앞뒤는 움직이는 일은 없어서 깔끔하게 정렬된 출력을 볼 수 있어요. 

이렇게 조정 후, 다시 실행시켜 보면, 깔끔하게 정렬된 것을 볼 수 있습니다. 

 

9. 목록 클릭하여 정보 표시하기 

 해당 목록을 클릭하면 Toast기능을 사용하여 간단한 메시지를 출력해 보도록 할게요.

예를 들어, itemView 목록에서 엘사를 터치하면, 엘사의 "이름 [전화번호] , 이메일"을 짧은 메시지 형태로 보여주도록 할게요.

ProfileAdapter.kt  파일을 열고, onCreateViewHolder ( )  함수 부분을 아래와 같이 수정(추가) 해 주세요. 

override fun onCreateViewHolder( parent: ViewGroup, viewType: Int ): ProfileAdapter.CustomViewHolder {
    val view = LayoutInflater.from(parent.context).inflate(R.layout.list_item, parent, false)   // parent에 있는 모든 정보(context)를 가져오는 설정 함.
    return CustomViewHolder(view).apply {
        itemView.setOnClickListener {
            val cursorPosition : Int = adapterPosition
            val profile : Profile = profileList.get(cursorPosition)
            Toast.makeText(parent.context, "-이름:${profile.name} [전화번호: ${profile.telNum}] \n-E메일: ${profile.email}", Toast.LENGTH_SHORT).show()
        }
    }
}

그리고 실행해서 터치해 보면, 해당하는 정보를 띄워주는 것을 볼 수 있습니다.

 

10. 목록 클릭하여 전화걸기

마지막으로, 해당 목록을 클릭하면 해당 연락처로 전화를 걸 수 있도록 해서 좀 더 효용성을 높여 봤습니다.

동일한 코드에 아랫부분만 수정해 보세요.

override fun onCreateViewHolder( parent: ViewGroup, viewType: Int ): ProfileAdapter.CustomViewHolder {
    val view = LayoutInflater.from(parent.context).inflate(R.layout.list_item, parent, false)   // parent에 있는 모든 정보(context)를 가져오는 설정 함.
    return CustomViewHolder(view).apply {
        itemView.setOnClickListener {
            val cursorPosition : Int = adapterPosition
            val profile : Profile = profileList.get(cursorPosition)
            val intent = Intent(Intent.ACTION_DIAL)
            intent.data = Uri.parse("tel:${profile.telNum}")
            itemView.context.startActivity(intent)
        }
    }
}

앱을 실행시켜 보면,  서로 다른 항목을 클릭할 때마다 그 항목에 있는 전화번호로 전화 걸기가 잘 실행 되는 것을 볼 수 있네요.

※ 주의!  임의로 넣은 번호이지만 실제 해당 번호를 소유하고 있는 사람이 있을 수 있으므로, 실제 테스트하실 때는 가족이나 자신의 번호로 테스트해 보시길 바랍니다!

【 앱 실행 결과 】

① 엘사 터치 후 전화걸기가 실행되는 모습

② 홍길동 터치 후 전화걸기가 실행되는 모습

 

감사합니다.

반응형