[Android/Flutter 교육] 21일차

MSU·2024년 1월 24일

Android-Flutter

목록 보기
21/85
post-thumbnail

RecyclerView

리사이클러뷰의 항목을 데코레이션으로 꾸밀 필요 없이 카드뷰로 구성할 수도 있다.

layoutManager

어떤 방법으로 보여줄지 사용

  • LinearLayoutManager : 가로, 세로 방향
// 위에서 아래 방향
recyclerView.layoutManager = LinearLayoutManager(this@MainActivity) // 그냥 this는 activityMainBinding을 의미하게 되므로 MainActivity를 지칭할 수 있게 @를 붙여준다.

// 가로 방향
recyclerView.layoutManager = LinearLayoutManager(this@MainActivity, LinearLayoutManager.HORIZONTAL, false)
  • GridLayoutManager : 한 줄에 몇 칸을 사용할 것인지
    항목 View의 크기가 다를 경우 GridLayoutManager는 같은 행의 모든 뷰가 같은 크기로 조정된다.
// 그리드
// 한 줄에 몇 칸을 사용할 것인지...
recyclerView.layoutManager = GridLayoutManager(this@MainActivity, 2)

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


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

구글 이미지 검색 페이지도 StaggeredGridLayout임

EX07

리사이클러 뷰 프로젝트 작성 순서

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]
        }
    }
}

CardView

  • 화면에 배치되는 뷰들을 카드라는 것으로 묶어서 표현할 수 있다.
    카드뷰를 먼저 배치하고 그 안에서 원하는 레이아웃을 배치

style

  • Outlined : 기본
  • Filled
  • Elevated

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])
        }
    }
}

Chips

  1. 주요 속성
  • text
  • style : 칩 모양
  • chipIcon : 칩에 표시할 아이콘
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을 눌렀습니다."
            }
        }
    }
}

권한

  • 안드로이드는 개인 정보, 센서, 카메라, 저장소 등 개인 정보와 관련된 기능을 사용하기 위해서는 권한을 등록해야 한다.
  • 권한 등록은 사용자가 애플리케이션을 다운로드 받거나 설치 후 애플리케이션 정보에서 확인이 가능하다.
  • 권한 등록의 목적은 사용자에게 애플리케이션이 어떠한 기능을 사용하는지 알려주는 목적으로 사용한다.
  • 안드로이드 6.0(마시멜로우) 버전 부터 개인 정보와 관련된 권한은 애플리케이션 내부에서 고지하고 사용 허가를 받아야 한다.
  • 권한 사용시 반드시 사용자에게 고지하고 이를 승인 받는 작업이 필요하다.
  • 사용 허가를 받을 필요가 없는 권한은 사용 허가를 받은 상태로 처리한다.

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")
                }
            }
        }
    }
}

어플을 다시 실행하면 아래와 같이 권한 등록 요청이 뜬다.

허용을 안할 경우에는

허용을 한 경우에는

EX08

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

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기, 소프트캠퍼스 
profile
안드로이드공부

0개의 댓글