관리 메뉴

막내의 막무가내 프로그래밍 & 일상

[안드로이드] 안드로이드 BottomSheetDialog 콜백 구현 본문

안드로이드/코틀린 & 아키텍처 & Recent

[안드로이드] 안드로이드 BottomSheetDialog 콜백 구현

막무가내막내 2020. 7. 11. 17:28
728x90

 

[2021-04-14 업데이트]

가장 하단에 PS 부분에 변경 후 코드를 참고해주세요 :) 최신코드입니다.

 

 

본 프로젝트 전에 적용하기 전 샘플 프로젝트로 잘 되나 바텀시트다이얼로그를 테스트해봤습니다. ㅎㅎ

바텀시트 다이얼로그 프래그먼트에서 아이템 선택시 기존 액티비티나 프래그먼트에서 콜백을 받아 액션을 취하게 하는 로직이 필요했습니다. 

이전에도 사용해보긴 했는데 콜백이 필요한 경우는 첨이네염 구글링 해봤는데 그런 예제가 딱히 안보여 직접 구현해봤고 샘플로 포스팅합니닷

바텀시트 다일로그 콜백구현 과정입니다.

 

 

1. BottomSheetDialog 사용을 위해 추가합니다.

implementation 'com.google.android.material:material:1.1.0'

 

2. 액티비티 코드입니다.

텍스트 클릭시 다이얼로그 프래그먼트를 띄워집니다. 이때 콜백을 받기위해 고차함수를 사용해 넘겼습니다.

package com.mtjin.bottomsheet

import android.os.Bundle
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import kotlinx.android.synthetic.main.activity_main.*

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        text.setOnClickListener {
            val orderBottomDialogFragment: OrderBottomDialogFragment = OrderBottomDialogFragment {
                when (it) {
                    0 -> Toast.makeText(this, "추천순", Toast.LENGTH_SHORT).show()
                    1 -> Toast.makeText(this, "리뷰순", Toast.LENGTH_SHORT).show()
                }
            }
            orderBottomDialogFragment.show(supportFragmentManager, orderBottomDialogFragment.tag)
        }
    }
}
<?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=".MainActivity"
    >

    <TextView
        android:id="@+id/text"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World!"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        />

</androidx.constraintlayout.widget.ConstraintLayout>

 

 

3. 다이얼로그 프래그먼트 코드입니다.

버튼 선택시 콜백을 해줍니다. 

그리고 xml에서 몇가지 바텀시트 관련한 속성들이 있는데요

app:behavior_hideable="false" 는 바텀시트를 화면에 안보일때 까지 내릴지 결정하는 플래그입니다.

app:behavior_peekHeight 는 바텀시트 기본 높이를 설정할 수 있습니다.

app:layout_behavior="com.google.android.material.bottomsheet.BottomSheetBehavior" 를 넣어줘야 바텀시트가  정상적으로 동작합니다.

package com.mtjin.bottomsheet

import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import kotlinx.android.synthetic.main.fragment_order_bottom_dialog.view.*


class OrderBottomDialogFragment(val itemClick: (Int) -> Unit) :
    BottomSheetDialogFragment() {
    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View =
        inflater.inflate(R.layout.fragment_order_bottom_dialog, container, false)

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        view.tv_recommend_order.setOnClickListener {
            itemClick(0)
            dialog?.dismiss()
        }
        view.tv_review_order.setOnClickListener {
            itemClick(1)
            dialog?.dismiss()
        }
    }
}
<?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="wrap_content"
    android:paddingBottom="24dp"
    app:behavior_hideable="true"
    app:behavior_peekHeight="56dp"
    app:layout_behavior="com.google.android.material.bottomsheet.BottomSheetBehavior">

    <androidx.appcompat.widget.AppCompatTextView
        android:id="@+id/text_order"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="28dp"
        android:layout_marginTop="24dp"
        android:text="정렬"
        android:textSize="14sp"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <androidx.appcompat.widget.AppCompatTextView
        android:id="@+id/tv_recommend_order"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="28dp"
        android:layout_marginTop="16dp"
        android:text="추천순"
        android:textSize="16sp"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@id/text_order" />

    <androidx.appcompat.widget.AppCompatTextView
        android:id="@+id/tv_review_order"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="28dp"
        android:layout_marginTop="20dp"
        android:text="리뷰순"
        android:textSize="16sp"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@id/tv_recommend_order"
        app:layout_goneMarginStart="28dp" />

    <androidx.appcompat.widget.AppCompatTextView
        android:id="@+id/tv_save_order"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="28dp"
        android:layout_marginTop="20dp"
        android:text="저장순"
        android:textSize="16sp"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@id/tv_review_order" />

    <androidx.appcompat.widget.AppCompatTextView
        android:id="@+id/tv_price_low_order"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="28dp"
        android:layout_marginTop="20dp"
        android:text="가격 낮은순"
        android:textSize="16sp"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@id/tv_save_order" />

    <androidx.appcompat.widget.AppCompatTextView
        android:id="@+id/tv_price_high_order"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="28dp"
        android:layout_marginTop="20dp"
        android:text="가격 높은순"
        android:textSize="16sp"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@id/tv_price_low_order"
        app:layout_goneMarginStart="28dp" />


</androidx.constraintlayout.widget.ConstraintLayout>

 

 

 

[결과]

 


[P.S] 

추가로 다이얼로그 위에 둥근 모서리를 달고 싶으시면 다음을 추가해주시면 됩니다.

1. R.drawable 에 radius 추가 

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle">
    <solid android:color="#FFFFFF" />
    <corners
        android:topLeftRadius="16dp"
        android:topRightRadius="16dp" />
</shape>

 

2. R.values.style 에 기본 바텀시트 다이얼로그 모달창 적용 ( 1번에서 한 거 적용해줌 됩니다.)

<resources>

    <!-- Base application theme. -->
    <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
        <!-- Customize your theme here. -->
        <item name="colorPrimary">@color/colorPrimary</item>
        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
        <item name="colorAccent">@color/colorAccent</item>
        <item name="bottomSheetDialogTheme">@style/AppBottomSheetDialogTheme</item>
    </style>

    <style name="AppBottomSheetDialogTheme" parent="Theme.Design.Light.BottomSheetDialog">
        <item name="bottomSheetStyle">@style/AppModalStyle</item>
    </style>

    <style name="AppModalStyle" parent="Widget.Design.BottomSheet.Modal">
        <item name="android:background">@drawable/bg_white_radius_8dp</item>
    </style>

</resources>

 

 

[참고]

여기서는 그렇게 안했지만 바텀시트다이얼로그 프래그먼트를 싱글톤으로 받는게 좋습니다. !! (두번이 안띄워지게)

 

댓글과 공감은 큰 힘이 됩니다. 감사합니다. !!!

 

 

 

[2020-12-18 댓글에 화면회전시 Crash 발생 에러 해결방법]

저 같은 경우 Bundle을 대신한 단순 값을 옮기는 용도가 아닌 고차함수를 사용한 클릭리스너 콜백을 인자로 넘겨주는거라 프래그먼트 사용이 아니라 FragmentFactory를 사용할 수가 없었습니다. (사용할수는 있나..? 팩토리 만들고서 클릭리스너 어케옮기지에서 막힘... 팩토리안에 전용함수 하나 더 만들어주면 될 것 같긴한데 이 방법이 더 간단한듯합니다.)

package com.mtjin.nomoneytrip.utils

import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentFactory
import com.mtjin.nomoneytrip.views.dialog.RatingBottomDialogFragment

class MyFragmentFactoryImpl : FragmentFactory() {
    override fun instantiate(classLoader: ClassLoader, className: String): Fragment {
        return when (className) {
            RatingBottomDialogFragment::class.java.name -> RatingBottomDialogFragment(고차함수를 어떻게주지..)
            else -> super.instantiate(classLoader, className)
        }
    }z
}

 

그래서 고민하다가 다음과 같이 빈생성자로 모두 만들어주고 기촌 프래그먼트 생성자로 있던 고차함수 ratingClick을 companion obejct 안으로 옮겨주어 해결하였습니다.

 

참고로 이 포스팅글과 다른 예제입니다. 

[변경 전]

package com.mtjin.nomoneytrip.views.dialog

import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.RatingBar
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import com.mtjin.nomoneytrip.R
import kotlinx.android.synthetic.main.fragment_rating_bottom_dialog.view.*

class RatingBottomDialogFragment(
    val ratingClick: (Float) -> Unit
) :
    BottomSheetDialogFragment() {
    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View =
        inflater.inflate(R.layout.fragment_rating_bottom_dialog, container, false)

    companion object {
        fun newInstance(
            ratingClick: (Float) -> Unit
        ): RatingBottomDialogFragment {
            return RatingBottomDialogFragment(ratingClick)
        }
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        view.rb_rating.onRatingBarChangeListener =
            RatingBar.OnRatingBarChangeListener { p0, rating, p2 ->
                ratingClick(rating)
                dialog?.dismiss()
            }
    }
}

 

[변경 후]

package com.mtjin.nomoneytrip.views.dialog

import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.RatingBar
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import com.mtjin.nomoneytrip.R
import kotlinx.android.synthetic.main.fragment_rating_bottom_dialog.view.*

class RatingBottomDialogFragment :
    BottomSheetDialogFragment() {

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View =
        inflater.inflate(R.layout.fragment_rating_bottom_dialog, container, false)

    companion object {
        lateinit var ratingClick: (Float) -> Unit
        fun newInstance(
            ratingClick: (Float) -> Unit
        ): RatingBottomDialogFragment {
            this.ratingClick = ratingClick
            return RatingBottomDialogFragment()
        }
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        view.rb_rating.onRatingBarChangeListener =
            RatingBar.OnRatingBarChangeListener { p0, rating, p2 ->
                ratingClick(rating)
                dialog?.dismiss()
            }
    }
}
728x90
Comments