
리사이클러뷰의 항목을 데코레이션으로 꾸밀 필요 없이 카드뷰로 구성할 수도 있다.
어떤 방법으로 보여줄지 사용
// 위에서 아래 방향
recyclerView.layoutManager = LinearLayoutManager(this@MainActivity) // 그냥 this는 activityMainBinding을 의미하게 되므로 MainActivity를 지칭할 수 있게 @를 붙여준다.
// 가로 방향
recyclerView.layoutManager = LinearLayoutManager(this@MainActivity, LinearLayoutManager.HORIZONTAL, false)
// 그리드
// 한 줄에 몇 칸을 사용할 것인지...
recyclerView.layoutManager = GridLayoutManager(this@MainActivity, 2)

// 항목 View의 크기가 다를 경우 GridLayoutManager는 같은 행의 모든 뷰가 같은 크기로 조정된다.
// StaggeredGridLayoutManager는 항목 View의 크기는 필요한 만큼만 사용하고 화면에 빈칸이 없어지도록 한다.
// 데코레이션을 사용하지 않는 것을 추천한다.
recyclerView.layoutManager = StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL)

StaggeredGridLayoutManager를 사용할 때에는 데코레이션으로 구분선을 넣었다면 위와 같이 텍스트가 길 경우 구분선과 겹칠 수 있으므로 구분선을 넣지 않는다.
구글 이미지 검색 페이지도 StaggeredGridLayout임

리사이클러 뷰 프로젝트 작성 순서
viewBinding 세팅
activity_main.xml 에서 전체 뷰 작성
res > layout > row.xml 파일 만들어서 리사이클러뷰 작성
MainActivity.kt 에서
View를 초기화하는 메서드 initView()
이벤트를 설정하는 메서드 setViewEvent()
리사이클러 뷰의 어댑터 RecyclerViewAdapter 작성
리사이클러 뷰 어댑터 안에 이너클래스로 ViewHolderClass 작성
RecyclerViewAdapter를 RecyclerView.Adapter 상속받게 하기
ViewHolderClass 내부에 init으로 rowBinding 전달받기
리사이클러 뷰 어댑터의 3가지 메서드 오버라이딩
리사이클러뷰를 구성하기 위한 데이터를 담을 mutableList를 2개 만들기
initView에서 리사이클러뷰 어댑터 객체 생성, 레이아웃 매니저 생성, 데코레이션
setViewEvent에서 버튼을 클릭하면 입력한 내용을 리스트에 담고 리사이클러 뷰 갱신

package kr.co.lion.ex07_recyclerview
import android.os.Build.VERSION_CODES.M
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.view.ViewGroup
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.divider.MaterialDividerItemDecoration
import kr.co.lion.ex07_recyclerview.databinding.ActivityMainBinding
import kr.co.lion.ex07_recyclerview.databinding.RowBinding
class MainActivity : AppCompatActivity() {
lateinit var activityMainBinding: ActivityMainBinding
// RecyclerView를 구성하기 위한 데이터를 담을 리스트
val listRow1 = mutableListOf<String>()
val listRow2 = mutableListOf<String>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
activityMainBinding = ActivityMainBinding.inflate(layoutInflater)
setContentView(activityMainBinding.root)
// setTestData()
initView()
setViewEvent()
}
// 테스트용 데이터 셋팅
fun setTestData(){
// RecyclerView 구성을 위한 임시 데이터 셋팅
listRow1.addAll(arrayOf("문자열1-1", "문자열1-2", "문자열1-3"))
listRow2.addAll(arrayOf("문자열2-1", "문자열2-2", "문자열2-3"))
}
// View를 초기화하는 메서드
fun initView(){
activityMainBinding.apply {
// RecyclerView 설정
recyclerViewResult.apply {
// 어댑터 객체 생성
adapter = RecyclerViewAdapter()
// 레이아웃 매니저 객체 생성
layoutManager = LinearLayoutManager(this@MainActivity)
// 데코레이션
val deco = MaterialDividerItemDecoration(this@MainActivity, MaterialDividerItemDecoration.VERTICAL)
addItemDecoration(deco)
}
}
}
// 이벤트를 설정하는 메서드
fun setViewEvent(){
activityMainBinding.apply {
// 버튼 이벤트
buttonSubmit.setOnClickListener {
// 사용자가 입력한 내용을 리스트에 담는다.
listRow1.add(textFieldUserId.text!!.toString())
listRow2.add(textFieldUserName.text!!.toString())
// 입력 요소를 비워준다.
textFieldUserId.setText("")
textFieldUserName.setText("")
// 리사이클러 뷰를 갱신한다.
recyclerViewResult.adapter?.notifyDataSetChanged()
}
}
}
// 리사이클러 뷰의 어댑터
inner class RecyclerViewAdapter : RecyclerView.Adapter<RecyclerViewAdapter.ViewHolderClass>(){
// ViewHolder
inner class ViewHolderClass(rowBinding: RowBinding) : RecyclerView.ViewHolder(rowBinding.root){
val rowBinding:RowBinding
init {
this.rowBinding = rowBinding
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolderClass {
val rowBinding = RowBinding.inflate(layoutInflater)
val viewHolderClass = ViewHolderClass(rowBinding)
return viewHolderClass
}
override fun getItemCount(): Int {
return listRow1.size
}
override fun onBindViewHolder(holder: ViewHolderClass, position: Int) {
holder.rowBinding.textViewRow1.text = listRow1[position]
holder.rowBinding.textViewRow2.text = listRow2[position]
}
}
}


res > layout > row.xml파일 생성할 때 MaskableFrameLayout으로 root element 셋팅
recyclerview의 높이를 wrap_content로 해버리면 실제 화면에 안나오고 match_parent로 해버리면 보이지만 recyclerview 다음에 보여질 요소가 아예 안보여진다.
따라서 recyclerview의 높이를 직접 정해줘야 한다.


package kr.co.lion.android21_carousel
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.carousel.CarouselLayoutManager
import com.google.android.material.carousel.FullScreenCarouselStrategy
import com.google.android.material.carousel.HeroCarouselStrategy
import com.google.android.material.carousel.MultiBrowseCarouselStrategy
import kr.co.lion.android21_carousel.databinding.ActivityMainBinding
import kr.co.lion.android21_carousel.databinding.RowBinding
class MainActivity : AppCompatActivity() {
lateinit var activityMainBinding: ActivityMainBinding
// RecyclerView 구성을 위한 이미지들
val imageRes = arrayOf(
R.drawable.image_1, R.drawable.image_2, R.drawable.image_3,
R.drawable.image_4, R.drawable.image_5, R.drawable.image_6,
R.drawable.image_7, R.drawable.image_8, R.drawable.image_9,
R.drawable.image_10
)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
activityMainBinding = ActivityMainBinding.inflate(layoutInflater)
setContentView(activityMainBinding.root)
activityMainBinding.apply {
// RecyclerView 셋팅
recyclerView.apply {
// 어댑터 객체 생성
adapter = RecyclerViewAdapter()
// 레이아웃 매니저
layoutManager = CarouselLayoutManager()
// 아래의 레이아웃 매니저는 보여주고자 하는 이미지의 크기가 모두 똑같을 경우에만 사용
// layoutManager = CarouselLayoutManager(MultiBrowseCarouselStrategy())
// layoutManager = CarouselLayoutManager(HeroCarouselStrategy())
// layoutManager = CarouselLayoutManager(FullScreenCarouselStrategy())
}
}
}
// Recycler Adapter
inner class RecyclerViewAdapter : RecyclerView.Adapter<RecyclerViewAdapter.ViewHolderClass>(){
inner class ViewHolderClass(rowBinding: RowBinding) : RecyclerView.ViewHolder(rowBinding.root) {
val rowBinding:RowBinding
init {
this.rowBinding = rowBinding
// 이미지를 눌렀을 때의 처리는 리사이클러 뷰의 항목을 눌렀을 때로 처리해준다.
rowBinding.root.setOnClickListener {
activityMainBinding.imageView.setImageResource(imageRes[adapterPosition])
}
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolderClass {
val rowBinding = RowBinding.inflate(layoutInflater)
val viewHolderClass = ViewHolderClass(rowBinding)
return viewHolderClass
}
override fun getItemCount(): Int {
return imageRes.size
}
override fun onBindViewHolder(holder: ViewHolderClass, position: Int) {
holder.rowBinding.carouselImageView.setImageResource(imageRes[position])
}
}
}

package kr.co.lion.android23_materialetc
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import kr.co.lion.android23_materialetc.databinding.ActivityMainBinding
class MainActivity : AppCompatActivity() {
lateinit var activityMainBinding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
activityMainBinding = ActivityMainBinding.inflate(layoutInflater)
setContentView(activityMainBinding.root)
activityMainBinding.apply {
chip1.setOnClickListener {
textView.text = "chip1을 눌렀습니다."
}
}
}
}
AndroidManifest.xml에서 권한 셋팅 후 실행만 하면 권한 등록이 안된다.
// AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<application
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.Android24_Permission"
tools:targetApi="31">
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<uses-permission android:name="android.permission.READ_CONTACTS"/>
<uses-permission android:name="android.permission.WRITE_CONTACTS"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
</manifest>

권한 등록 과정을 코드로 작성해줌
package kr.co.lion.android24_permission
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.Manifest
import android.content.pm.PackageManager
import kr.co.lion.android24_permission.databinding.ActivityMainBinding
class MainActivity : AppCompatActivity() {
// 확인할 권한의 목록
val permissionList = arrayOf(
Manifest.permission.INTERNET,
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.ACCESS_COARSE_LOCATION,
Manifest.permission.READ_CONTACTS,
Manifest.permission.WRITE_CONTACTS,
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.WRITE_EXTERNAL_STORAGE
)
lateinit var activityMainBinding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
activityMainBinding = ActivityMainBinding.inflate(layoutInflater)
setContentView(activityMainBinding.root)
// 권한을 확인한다.
requestPermissions(permissionList, 0)
activityMainBinding.apply {
button.setOnClickListener {
// 위치 사용 권한 확인
val chk1 = checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION)
if(chk1 == PackageManager.PERMISSION_GRANTED){
textView.text = "위치 권한이 허용되어 있습니다.\n"
}else if(chk1 == PackageManager.PERMISSION_DENIED){
textView.text = "위치 권한이 허용되어 있지 않습니다.\n"
}
// 연락처 권한
val chk2 = checkSelfPermission(Manifest.permission.READ_CONTACTS)
if(chk2 == PackageManager.PERMISSION_GRANTED){
textView.append("연락처 권한이 허용되어 있습니다\n")
} else {
textView.append("연락처 권한이 허용되어 있지 않습니다\n")
}
}
}
}
}
어플을 다시 실행하면 아래와 같이 권한 등록 요청이 뜬다.

허용을 안할 경우에는

허용을 한 경우에는

리사이클러뷰를 활용한 검색기능


package kr.co.lion.ex08
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.view.ViewGroup
import androidx.core.widget.addTextChangedListener
import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.divider.MaterialDividerItemDecoration
import kr.co.lion.ex08.databinding.ActivityMainBinding
import kr.co.lion.ex08.databinding.RowBinding
class MainActivity : AppCompatActivity() {
lateinit var activityMainBinding: ActivityMainBinding
// 이름을 담을 리스트
val nameStore = mutableListOf<String>()
// 검색 결과를 담을 리스트
val searchResult = mutableListOf<String>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
activityMainBinding = ActivityMainBinding.inflate(layoutInflater)
setContentView(activityMainBinding.root)
// View 초기화
initView()
// 이벤트 설정
setViewEvent()
}
fun initView(){
activityMainBinding.apply {
recyclerView.apply {
// 어댑터 객체 생성
adapter = RecyclerViewAdapter()
// 레이아웃 매니저
recyclerView.layoutManager = LinearLayoutManager(this@MainActivity)
// 구분선 데코레이션
val deco = MaterialDividerItemDecoration(this@MainActivity, DividerItemDecoration.VERTICAL)
recyclerView.addItemDecoration(deco)
}
}
}
fun setViewEvent(){
activityMainBinding.apply {
// 처음 있는 테스트 데이터를 보여준다.
searchResult.addAll(findName(editTextSearch.text.toString()))
button.setOnClickListener {
// 사용자가 입력한 이름을 리스트에 담는다.
nameStore.add(editTextName.text!!.toString())
// 입력 요소를 비워준다.
editTextName.setText("")
editTextSearch.setText("")
// 리사이클러 뷰 갱신
recyclerView.adapter?.notifyDataSetChanged()
}
editTextSearch.apply{
addTextChangedListener {
// 결과 창 먼저 비워주고
searchResult.clear()
// 검색 결과 가져오기
searchResult.addAll(findName(editTextSearch.text.toString()))
// 리사이클러 뷰 갱신
recyclerView.adapter?.notifyDataSetChanged()
}
}
}
}
fun findName(searchName:String):MutableList<String>{
// 검색 결과를 반환할 리스트
val result = mutableListOf<String>()
// 검색어가 포함된 단어만 찾아서 result 리스트에 추가
nameStore.forEach {
if(it.contains(searchName)){
result.add(it)
}
}
return result
}
inner class RecyclerViewAdapter : RecyclerView.Adapter<RecyclerViewAdapter.ViewHolderClass>(){
// ViewHolder
inner class ViewHolderClass(rowBinding: RowBinding) : RecyclerView.ViewHolder(rowBinding.root){
// 매개 변수로 들어오는 바인딩 객체를 담을 프로퍼티
var rowBinding:RowBinding
init {
this.rowBinding = rowBinding
}
}
// ViewHolderClass 객체를 생성하여 반환
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolderClass {
// View Binding
val rowBinding = RowBinding.inflate(layoutInflater)
// View Holder
val viewHolderClass = ViewHolderClass(rowBinding)
return viewHolderClass
}
override fun getItemCount(): Int {
return searchResult.size
}
// 항목에 보여주고자 하는 데이터 설정
override fun onBindViewHolder(holder: ViewHolderClass, position: Int) {
holder.rowBinding.textView.text = searchResult[position]
}
}
}
※ 출처 : 멋쟁이사자 앱스쿨 2기, 소프트캠퍼스