[Android/Flutter 교육] 24일차

MSU·2024년 1월 30일

Android-Flutter

목록 보기
25/85
post-thumbnail

EX09 툴바 추가

강사님은 fun initToolbar() 함수로 따로 정의하심
툴바의 뒤로가기 버튼을 클릭할때는 setResult에 RESULT_CANCELED를 담고 finish()함

                // 뒤로가기
                setNavigationIcon(R.drawable.arrow_back_24px)
                setNavigationOnClickListener {
                    setResult(RESULT_CANCELED)
                    finish()
                }

Context Menu

  • view를 길게 누르고 있으면 나오는 메뉴(pc의 오른쪽 클릭메뉴 같은)
  • 길게 눌러보지 않는 이상 사용자는 모르기 때문에 직관적이지 않음
    사용자에게 길게 눌러볼 수 있게 알려줘야 함

setOnCreateContextMenuListener

  • Context Menu가 등록된 View를 길게 누르면 호출되는 메서드(메뉴를 구성하는 곳)
  • view를 기준으로 분기가 가능
  • 매개변수
    menu : 메뉴를 구성하기 위해 필요한 객체
    v : 사용자가 길게 누른 View의 주소값
    menuInfo : 메뉴에 대한 부가 정보를 가지고 있는 객체. RecyclerView에서 사용할 때 사용자가 길게 누른 항목의 순서값을 가져올 수 있다.
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        activityMainBinding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(activityMainBinding.root)

        activityMainBinding.apply {

            // textView의 컨텍스트 메뉴
            // Context Menu가 등록된 View를 길게 누르면 호출되는 메서드
            // menu : 메뉴를 구성하기 위해 필요한 객체
            // v : 사용자가 길게 누른 View의 주소값
            // menuInfo : 메뉴에 대한 부가 정보를 가지고 있는 객체. RecyclerView에서 사용할 때 사용자가 길게 누른 항목의 순서값을 가져올 수 있다.
            textView.setOnCreateContextMenuListener { menu, view, menuInfo ->
                // 메뉴의 헤더
                menu?.setHeaderTitle("TextView의 메뉴입니다")
                // res/menu/textview_menu.xml 을 통해 메뉴를 구성해준다.
                menuInflater.inflate(R.menu.textview_menu, menu)
                
                // 각 메뉴 아이템을 추출하여 리스너를 설정해준다.
                menu?.findItem(R.id.textview_menu_item1)?.setOnMenuItemClickListener {
                    textView.text = "텍스트뷰의 메뉴 항목1 선택"
                    true
                }
                menu?.findItem(R.id.textview_menu_item2)?.setOnMenuItemClickListener {
                    textView.text = "텍스트뷰의 메뉴 항목2 선택"
                    true
                }
            }
        }

    }

아래의 onContextItemSelected 메서드는 이전 버전의 방법임

  • Context Menu의 항목을 눌렀을 때 호출되는 메서드
  • onCreateContextMenu와 다르게 view를 매개변수로 받지 않는다
  • 어떤 view에서 나온 메뉴 항목인지를 모르기 때문에 menuitem의 id를 전부 다르게 해줘야 함
    (menuitem의 id만으로도 구분할 수 있게)
// Context Menu의 항목을 눌렀을 때 호출되는 메서드
    // 주의할점 : 이 메서드 내부에서는 어떤 뷰의 메뉴인지를 구분할 수가 없다.
    // 메뉴 아이템의 아이디를 정해줄 때 모두 다르게 정해주고
    // 이 메서드 내부에서는 꼭 주석으로 누구의 메뉴인지를 명시해준다
    override fun onContextItemSelected(item: MenuItem): Boolean {

        // 사용자가 선택한 메뉴의 아이디로 분기한다.
        when(item.itemId){
            // TextView의 메뉴
            R.id.textview_menu_item1 -> activityMainBinding.textView.text = "TextView의 메뉴 1을 선택했습니다"
            R.id.textview_menu_item2 -> activityMainBinding.textView.text = "TextView의 메뉴 2를 선택했습니다"
            
            // RecyclerView의 메뉴
            R.id.recyclerview_menu_item1 -> {
                // AdapterContextMenuInfo 객체를 추출한다.
                val menuInfo = item.menuInfo as AdapterContextMenuInfo
                activityMainBinding.textView.text = "${menuInfo.position}번째 항목의 메뉴 항목1"
            }
            R.id.recyclerview_menu_item2 -> {
                // AdapterContextMenuInfo 객체를 추출한다.
                val menuInfo = item.menuInfo as AdapterContextMenuInfo
                activityMainBinding.textView.text = "${menuInfo.position}번째 항목의 메뉴 항목2"
            }
        }

        return super.onContextItemSelected(item)
    }

리사이클러뷰에 컨텍스트 메뉴 설정

리사이클러뷰의 항목에 대한 정보를 가져올 때 onCreateContextMenu의 menuInfo 매개변수를 사용한다



    // RecyclerView의 어댑터
    inner class RecyclerViewAdapter : RecyclerView.Adapter<RecyclerViewAdapter.ViewHolderClass>(){
        // ViewHolder
        inner class ViewHolderClass(rowBinding: RowBinding) : RecyclerView.ViewHolder(rowBinding.root) {
            val rowBinding:RowBinding

            init {
                this.rowBinding = rowBinding

                rowBinding.root.layoutParams = ViewGroup.LayoutParams(
                    ViewGroup.LayoutParams.MATCH_PARENT,
                    ViewGroup.LayoutParams.WRAP_CONTENT
                )

                // 항목에 컨텍스트 메뉴를 설정해준다.
                rowBinding.root.setOnCreateContextMenuListener { menu, v, menuInfo ->
                    menu?.setHeaderTitle("${adapterPosition}번째 항목의 메뉴")
                    menuInflater.inflate(R.menu.recyclerview_menu, menu)

					// RecyclerView 항목에 컨텍스트 메뉴를 설정해준다.
                    menu?.findItem(R.id.recyclerview_menu_item1)?.setOnMenuItemClickListener {
                        activityMainBinding.textView.text = "${adapterPosition}번째 항목의 메뉴1"
                        true
                    }
                    menu?.findItem(R.id.recyclerview_menu_item2)?.setOnMenuItemClickListener {
                        activityMainBinding.textView.text = "${adapterPosition}번째 항목의 메뉴2"
                        true
                    }
                }

            }
        }
    }



전체 코드

// Context Menu : View를 길게 누르면 나타나는 메뉴

class MainActivity : AppCompatActivity() {

    lateinit var activityMainBinding: ActivityMainBinding

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

        activityMainBinding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(activityMainBinding.root)

        activityMainBinding.apply {
            recyclerView.apply {
                adapter = RecyclerViewAdapter()
                layoutManager = LinearLayoutManager(this@MainActivity)
            }

            // textView의 컨텍스트 메뉴
            // Context Menu가 등록된 View를 길게 누르면 호출되는 메서드
            // menu : 메뉴를 구성하기 위해 필요한 객체
            // v : 사용자가 길게 누른 View의 주소값
            // menuInfo : 메뉴에 대한 부가 정보를 가지고 있는 객체. RecyclerView에서 사용할 때 사용자가 길게 누른 항목의 순서값을 가져올 수 있다.
            textView.setOnCreateContextMenuListener { menu, view, menuInfo ->
                // 메뉴의 헤더
                menu?.setHeaderTitle("TextView의 메뉴입니다")
                // res/menu/textview_menu.xml 을 통해 메뉴를 구성해준다.
                menuInflater.inflate(R.menu.textview_menu, menu)
                // 각 메뉴 아이템을 추출하여 리스너를 설정해준다.
                menu?.findItem(R.id.textview_menu_item1)?.setOnMenuItemClickListener {
                    textView.text = "텍스트뷰의 메뉴 항목1 선택"
                    true
                }
                menu?.findItem(R.id.textview_menu_item2)?.setOnMenuItemClickListener {
                    textView.text = "텍스트뷰의 메뉴 항목2 선택"
                    true
                }
            }

        }

    }

    // RecyclerView의 어댑터
    inner class RecyclerViewAdapter : RecyclerView.Adapter<RecyclerViewAdapter.ViewHolderClass>(){

        // ViewHolder
        inner class ViewHolderClass(rowBinding: RowBinding) : RecyclerView.ViewHolder(rowBinding.root) {
            val rowBinding:RowBinding

            init {
                this.rowBinding = rowBinding

                rowBinding.root.layoutParams = ViewGroup.LayoutParams(
                    ViewGroup.LayoutParams.MATCH_PARENT,
                    ViewGroup.LayoutParams.WRAP_CONTENT
                )

                // 항목에 컨텍스트 메뉴를 설정해준다.
                rowBinding.root.setOnCreateContextMenuListener { menu, v, menuInfo ->
                    menu?.setHeaderTitle("${adapterPosition}번째 항목의 메뉴")
                    menuInflater.inflate(R.menu.recyclerview_menu, menu)

                    // RecyclerView 항목에 컨텍스트 메뉴를 설정해준다.
                    menu?.findItem(R.id.recyclerview_menu_item1)?.setOnMenuItemClickListener {
                        activityMainBinding.textView.text = "${adapterPosition}번째 항목의 메뉴1"
                        true
                    }
                    menu?.findItem(R.id.recyclerview_menu_item2)?.setOnMenuItemClickListener {
                        activityMainBinding.textView.text = "${adapterPosition}번째 항목의 메뉴2"
                        true
                    }
                }

            }
        }

        override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolderClass {
            val rowBinding = RowBinding.inflate(layoutInflater)
            val viewHolderClass = ViewHolderClass(rowBinding)
            return viewHolderClass
        }

        override fun getItemCount(): Int {
            return 20
        }

        override fun onBindViewHolder(holder: ViewHolderClass, position: Int) {
            holder.rowBinding.textViewRow.text = "항목 $position"
        }
    }
}

  • 개발자가 코드를 통해 원하는 View에 띄우는 메뉴

  • popup_menu.xml에서 팝업메뉴 만든 후
    버튼을 클릭하면 텍스트뷰 밑에 팝업메뉴가 나타나게 코드 작성

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

        activityMainBinding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(activityMainBinding.root)

        activityMainBinding.apply {

            // 팝업 메뉴
            button.setOnClickListener {
                // 팝업 메뉴를 생성한다.
                // 두번째 매개 변수 : 메뉴를 띄울 View를 지정한다.
                val popupMenu = PopupMenu(this@MainActivity, textView)
                // 메뉴를 구성한다.
                menuInflater.inflate(R.menu.popup_menu, popupMenu.menu)
                // 메뉴의 항목을 눌렀을 때 동작할 리스너를 지정해준다.
                popupMenu.setOnMenuItemClickListener {
                    // 메뉴 항목의id로 분기한다.
                    when(it.itemId){
                        R.id.popup_menu_item1 -> textView.text = "팝업 메뉴1을 선택했습니다"
                        R.id.popup_menu_item2 -> textView.text = "팝업 메뉴2을 선택했습니다"
                    }
                    
                    true
                }
                // 메뉴를 보여준다.
                popupMenu.show()
            }

        }

    }

Sheets

요새는 context,popup 대신 sheets를 사용한다고 함

Messaging

Toast

  • Toast : 잠깐 보여줬다가 사라지는 메시지
  • 어플 화면이 떠있지 않을 때 보여질때 주로 사용한다.
  • 지금은 toast대신 snackbar 사용을 권장함(추가 버튼 배치 가능)
class MainActivity : AppCompatActivity() {

    lateinit var activityMainBinding: ActivityMainBinding

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

        activityMainBinding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(activityMainBinding.root)

        activityMainBinding.apply {
            buttonToast.setOnClickListener {
                // Toast 객체를 생성한다.
                // 두 번째 : 메시지
                // 세 번째 : 표시할 시간
                val t1 = Toast.makeText(this@MainActivity, "토스트 메시지 입니다", Toast.LENGTH_SHORT)
                // 메시지를 보여준다.
                t1.show()
            }
        }
    }
}

Snackbar

  • SnackBar : 잠깐 보여줬다가 사라지는 메시지
  • 어플 화면이 떠있을 경우 사용한다.
  • Toast와 다르게 지속적으로 띄을 수 있으며 Action을 누어 이벤트를 설정할 수 있다.
class MainActivity : AppCompatActivity() {

    lateinit var activityMainBinding: ActivityMainBinding

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

        activityMainBinding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(activityMainBinding.root)

        activityMainBinding.apply {
            buttonSnackBar.setOnClickListener {

                // SnackBar 객체를 생성한다.
                // 마지막 매개변수 : 메시지가 보여질 시간
                // LENGTH_SHORT : 적당히 짧은 시간
                // LENGTH_LONG : 적당히 긴 시간
                // LENGTH_INDEFINITE : 개발자가 코드로 없애거나 다른 스낵바 메시지가 뜰 때까지 보여준다.
                // 보통 Action을 줄 경우 사용한다.
                // val snackbar = Snackbar.make(it,"SnackBar", Snackbar.LENGTH_SHORT)
                val snackbar = Snackbar.make(it,"SnackBar", Snackbar.LENGTH_INDEFINITE)

                // 메시지 색상
                snackbar.setTextColor(Color.RED)
                // 배경색
                snackbar.setBackgroundTint(Color.BLUE)
                // 애니메이션 종류
                // snackbar.animationMode = Snackbar.ANIMATION_MODE_FADE // 기본값임
                snackbar.animationMode = Snackbar.ANIMATION_MODE_SLIDE
                
                // Action설정
                snackbar.setAction("Action"){
                    textView.text = "Action을 눌렀습니다"
                }

                // SnackBar를 보여준다.
                snackbar.show()

            }
        }
    }
}

Dialog

Basic dialog

버튼을 최대 세개까지 배치 가능

class MainActivity : AppCompatActivity() {

    lateinit var activityMainBinding: ActivityMainBinding

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

        activityMainBinding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(activityMainBinding.root)

        activityMainBinding.apply {
            buttonBasicDialog.setOnClickListener {
                val builder = MaterialAlertDialogBuilder(this@MainActivity).apply{

                    // 타이틀
                    setTitle("기본 다이얼로그")
                    // 메시지
                    setMessage("기본 다이얼로그 입니다")
                    // 중립 버튼
                    setNeutralButton("Neutral"){ dialogInterface: DialogInterface, i: Int ->
                        textView.text = "기본 다이얼로그 : Neutral"
                    }
                    // 긍정 버튼
                    setPositiveButton("Positive"){ dialogInterface: DialogInterface, i: Int ->
                        textView.text = "기본 다이얼로그 : Positive"
                    }
                    // 부정 버튼
                    setNegativeButton("Negative"){ dialogInterface: DialogInterface, i: Int ->
                        textView.text = "기본 다이얼로그 : Negative"
                    }

                }
                // 다이얼로그를 띄운다.
                builder.show()

            }
        }
    }
}

Full-screen dialog

class MainActivity : AppCompatActivity() {

    lateinit var activityMainBinding: ActivityMainBinding

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

        activityMainBinding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(activityMainBinding.root)

        activityMainBinding.apply {

            buttonCustomDialog.setOnClickListener {
                var builder = MaterialAlertDialogBuilder(this@MainActivity).apply {
                    setTitle("커스텀 다이얼로그")

                    // 뷰를 설정한다.
                    val customDialogBinding = CustomDialogBinding.inflate(layoutInflater)
                    setView(customDialogBinding.root)

                    setNegativeButton("취소",null)
                    setPositiveButton("확인"){ dialogInterface: DialogInterface, i: Int ->
                        textView.text = "입력1 : ${customDialogBinding.editTextDialog1.text}\n"
                        textView.append("입력2 : ${customDialogBinding.editTextDialog2.text}")
                    }

                }

                builder.show()
            }
            
        }
    }
}

Notification

  • Notification : 단말기의 알림 메시지 창에 나타나는 메시지이다.
    메시지를 통해 어플 실행을 유도하기 위해 사용한다.
  • 안드로이드 8.0부터 notification channel이 새로 생김
    채널별로 나누어 알림메시지를 만들 수 있다.

AndroidManifest.xml에 권한 추가

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

    <!-- 알림 메시지를 사용하기 위한 권한 -->
    <uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
</manifest>

MainActivity.kt

class MainActivity : AppCompatActivity() {

    lateinit var activityMainBinding: ActivityMainBinding
    
    // Notification 메시지 사용을 위한 권한
    val permissionList = arrayOf(
        Manifest.permission.POST_NOTIFICATIONS
    )

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

        activityMainBinding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(activityMainBinding.root)
        
        // 알림 메시지 사용을 위한 권한 확인
        requestPermissions(permissionList, 0)

        // Notification Message 사용을 위한 채널을 등록
        addNotificationChannel("c1", "메시지 채널 1")
        addNotificationChannel("c2", "메시지 채널 2")

        activityMainBinding.apply {
            buttonNotification1.setOnClickListener {
                // NotificationBuilder를 가져온다.
                // 채널 id를 지정한다.
                val builder = getNotificationBuilder("c1")
                // 작은 아이콘
                builder.setSmallIcon(android.R.drawable.ic_menu_add)
                // 큰 이미지
                val bitmap = BitmapFactory.decodeResource(resources, R.mipmap.ic_launcher)
                builder.setLargeIcon(bitmap)
                // 숫자 설정
                builder.setNumber(100)
                // 큰 이미지와 숫자 설정은 OS버전에따라 나올수도 있고 안나올수도 있다
                // 타이틀 설정
                builder.setContentTitle("content title 1")
                // 내용
                builder.setContentText("content text 1")

                // 메시지 객체를 생성한다.
                val notification = builder.build()
                // 알림 메시지를 관리하는 객체를 추출
                val notificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
                // 메시지를 표시한다.
                // 첫 번째 매개변수 : 메시지 번호
                // 메시지 번호를 통해 표시할 메시지를 지정할 수 있다.
                // 지정해준 메시지 번호의 메시지가 이미 보여지고 있는 상태라면 그 메시지를 없애고 새롭게 보여준다.
                // 하나의 메시지를 갱신할 때 같은 정수를 넣어주고 새로운 메시지를 보여줄 때는 다른 정수를 넣어준다.
                notificationManager.notify(10, notification)
            }

            buttonNotification2.setOnClickListener {
                val builder = getNotificationBuilder("c2")
                builder.setSmallIcon(android.R.drawable.ic_menu_add)
                val bitmap = BitmapFactory.decodeResource(resources, R.mipmap.ic_launcher)
                builder.setLargeIcon(bitmap)
                builder.setNumber(200)
                builder.setContentTitle("content title 2")
                builder.setContentText("content text 2")

                val notification = builder.build()
                val notificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
                notificationManager.notify(20, notification)
            }
        }
    }

    // Notification Channel 등록
    // 첫 번째 매개 변수 : 코드에서 채널을 관리하기 위한 이름
    // 두 번째 매개 변수 : 사용자에게 보여줄 채널의 이름
    fun addNotificationChannel(id:String, name:String){
        // 안드로이드 8.0 이상일 때만 동작하게 한다.
        if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O){
            // 알림 메시지를 관리하는 객체를 가져온다.
            val notificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
            // 해당 채널이 등록되어 있는지 확인한다.
            // 채널이 등록되어 있지 않으면 null을 반환한다.
            val channel = notificationManager.getNotificationChannel(id)

            // 등록된 채널이 없다면
            if(channel == null){
                // 채널 객체를 생성한다.
                val newChannel = NotificationChannel(id, name, NotificationManager.IMPORTANCE_HIGH)
                // 진동을 사용할 것인가.
                newChannel.enableVibration(true)
                // 채널을 등록한다.
                notificationManager.createNotificationChannel(newChannel)

            }
        }
    }
    
    // Notification 메시지를 생성하기 위한 객체를 반환하는 메서드
    fun getNotificationBuilder(id:String) : NotificationCompat.Builder{
        // 안드로이드 8.0 이상이면 마지막 매개변수에 채널 id를 설정해줘야 한다.
        if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O){
            val builder = NotificationCompat.Builder(this, id)
            return builder
        }else{
            val builder = NotificationCompat.Builder(this)
            return builder
        }
    }
}

EX10

학생 정보 입력 액티비티에서 입력완료 후 메인액티비티로 넘어가기 전에 스낵바를 사용했더니 입력액티비티가 종료되면서 스낵바도 같이 화면이 넘어가버리는 것 같다.
그래서 메인액티비티에 계약부분에서 입력값을 받아오고 리사이클러뷰 새로고침할때 스낵바를 띄우는 방법으로 수정해보았다.




※ 출처 : 멋쟁이사자 앱스쿨 2기, 소프트캠퍼스 
profile
안드로이드공부

0개의 댓글