관리 메뉴

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

[안드로이드] 코틀린 커스텀 다이얼로그 프래그먼트 (Custom Dialog Fragment) 본문

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

[안드로이드] 코틀린 커스텀 다이얼로그 프래그먼트 (Custom Dialog Fragment)

막무가내막내 2020. 5. 23. 19:43
728x90

 

[2021-04-13 업데이트]

 

 

 

위와 같은 프래그먼트 위에 띄워줄 다이얼로그 프래그먼트를 직접 만들어봤습니다.

자바로는 만든적이 꽤 많은데 코틀린으로는 처음이여서 간단하게 기록하는 포스팅을 해볼까 합니다. 자바했던것을 기반으로 구현한거라 최상의 방법은 아닐 수 있습니다. (구글링해보니 코틀린 다이얼로그 프래그먼트 한국 자료는 거의 안보이더라고요)

 

코드를 보면 이해가 가실겁니다!!

 

 

먼저 다이얼로그 프래그먼트 xml 입니다.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="@color/colorWhite"
    android:elevation="20dp"
    android:gravity="center"
    android:orientation="vertical">

    <TextView
        android:layout_width="match_parent"
        android:layout_height="70dp"
        android:gravity="center"
        android:padding="8dp"
        android:text="@string/dialog_favorite_add_text"
        android:textColor="@color/colorBlack"
        android:textSize="15sp"
        android:textStyle="bold" />

    <View
        android:layout_width="match_parent"
        android:layout_height="1dp"
        android:background="@color/colorWhiteGray" />

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:orientation="horizontal"
        android:weightSum="2">

        <TextView
            android:id="@+id/dialog_tv_yes"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:gravity="center"
            android:padding="8dp"
            android:text="@string/yes_text"
            android:textColor="@color/colorBlack" />

        <View
            android:layout_width="1dp"
            android:layout_height="match_parent"
            android:background="@color/colorWhiteGray" />

        <TextView
            android:id="@+id/dialog_tv_no"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:gravity="center"
            android:padding="8dp"
            android:text="@string/no_text"
            android:textColor="@color/colorBlack" />
    </LinearLayout>
</LinearLayout>

 

 

다이얼로그 프래그먼트 입니다. (Dialog Fragment)

getInstance() 통해 다이얼로그를 액비티이 또는 프래그먼트에서 다이얼로그 프래그먼트 인스턴스를 가져옵니다.

EXTRA_NOTICE_SAVE 는 원하는 태그 값을 넣어주면 됩니다. ( 추가로 번들에 전달된 값을 통해 여러 프래그먼트에서 하나의 다이얼로그를 사용하는 경우가 있을텐데 상황에 따른 텍스트 변경 같은 것도 가능합니다. => 예를들어, 위 다이얼로그처럼 모양은 같은데 질문만 달라야하는 경우 하나의 다이얼로그를 다시만들 필요가 없고 분기처리해서 텍스트만 변경해주는게 가능합니다.)

onClick() 은 다이얼로그 화면 외를 클릭 했을 때 다이얼로그 화면을 사라지게 만들기 위해 작성했습니다.

package com.mtjin.cnunoticeapp.views.dialog

import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.DialogFragment
import com.mtjin.cnunoticeapp.R
import com.mtjin.cnunoticeapp.constants.EXTRA_NOTICE_SAVE
import com.mtjin.cnunoticeapp.data.favorite.FavoriteNotice
import com.mtjin.cnunoticeapp.views.favorite.FavoriteViewModel
import kotlinx.android.synthetic.main.fragment_dialog_add.view.*
import org.koin.androidx.viewmodel.ext.android.viewModel

class DialogAddFragment : DialogFragment(), View.OnClickListener {
    private val viewModel: FavoriteViewModel by viewModel()
    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        val view = inflater.inflate(R.layout.fragment_dialog_add, container, false)
        //어느 다이어로그에서 왔는지
        val bundle = arguments
        val notice = bundle?.getParcelable<FavoriteNotice>(EXTRA_NOTICE_SAVE)
        view.dialog_tv_yes.setOnClickListener {
            notice?.let { notice ->
                viewModel.insert(notice)
                dismiss()
            }
        }
        view.dialog_tv_no.setOnClickListener {
            dismiss()
        }
        return view
    }

    fun getInstance(): DialogAddFragment {
        return DialogAddFragment()
    }

    override fun onClick(p0: View?) {
        dismiss()
    }

}

 

 

 

다음은 다이얼로그 프래그먼트를 띄워줄 부모 프래그먼트 코드입니다. 사진만 보면 됩니다.

package com.mtjin.cnunoticeapp.views.employ

import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import androidx.databinding.DataBindingUtil
import androidx.fragment.app.Fragment
import com.mtjin.cnunoticeapp.R
import com.mtjin.cnunoticeapp.constants.EXTRA_NOTICE_SAVE
import com.mtjin.cnunoticeapp.constants.TAG_DIALOG_EVENT
import com.mtjin.cnunoticeapp.data.favorite.FavoriteNotice
import com.mtjin.cnunoticeapp.databinding.FragmentEmployBinding
import com.mtjin.cnunoticeapp.utils.NetworkManager
import com.mtjin.cnunoticeapp.views.dialog.DialogAddFragment
import org.koin.androidx.viewmodel.ext.android.viewModel

class EmployNoticeFragment : Fragment() {
    private lateinit var binding: FragmentEmployBinding
    private lateinit var noticeAdapter: EmployAdapter
    private val viewModel: EmployNoticeViewModel by viewModel()
    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        binding = DataBindingUtil.inflate(inflater, R.layout.fragment_employ, container, false)
        binding.lifecycleOwner = this
        binding.vm = viewModel
        return binding.root
    }

    override fun onActivityCreated(savedInstanceState: Bundle?) {
        super.onActivityCreated(savedInstanceState)
        initAdapter()
        val networkManager: NetworkManager? = context?.let { NetworkManager(it) }
        if (!networkManager?.checkNetworkState()!!) {
            showToast(getString(R.string.network_err_toast))
        }
        viewModel.requestNotice()
    }

    private fun initAdapter() {
        noticeAdapter = EmployAdapter(
            itemClick = { item ->
                Intent(
                    Intent.ACTION_VIEW,
                    Uri.parse(getString(R.string.job_url) + item.link)
                ).run(this::startActivity)
            },
            longItemClick = {
                val bundle = Bundle()
                bundle.putParcelable(EXTRA_NOTICE_SAVE, FavoriteNotice(it.num, it.title, it.link))
                val dialog: DialogAddFragment = DialogAddFragment().getInstance()
                dialog.arguments = bundle
                activity?.supportFragmentManager?.let { fragmentManager ->
                    dialog.show(
                        fragmentManager,
                        TAG_DIALOG_EVENT
                    )
                }
            }
        )
        binding.rvBusiness.adapter = noticeAdapter
    }

    private fun showToast(msg: String) =
        Toast.makeText(context, msg, Toast.LENGTH_SHORT).show()
}

 

 

 

참고로 상수는 다음과 같이 따로 보관해놨습니다.

package com.mtjin.cnunoticeapp.constants

const val EXTRA_NOTICE_SAVE = "EXTRA_NOTICE_SAVE"
const val TAG_DIALOG_EVENT = "TAG_DIALOG_EVENT"

 

 

[P.S]

여러 프래그먼트에서 다이얼로그 프래그먼트를 사용하고 질문내용만 바꿔주고 싶다면 다음과 같이 Bundle 키 값으로 분기처리 해주면 됩니다 (EXTRA_NOTICE_DELETE 키 값으로 분기처리했습니다.)

package com.mtjin.cnunoticeapp.views.dialog

import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.DialogFragment
import com.mtjin.cnunoticeapp.R
import com.mtjin.cnunoticeapp.constants.EXTRA_NOTICE_DELETE
import com.mtjin.cnunoticeapp.constants.EXTRA_NOTICE_SAVE
import com.mtjin.cnunoticeapp.data.favorite.FavoriteNotice
import com.mtjin.cnunoticeapp.views.favorite.FavoriteViewModel
import kotlinx.android.synthetic.main.fragment_dialog_add.view.*
import org.koin.androidx.viewmodel.ext.android.viewModel

class DialogAddFragment : DialogFragment(), View.OnClickListener {
    private val viewModel: FavoriteViewModel by viewModel()
    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        val view = inflater.inflate(R.layout.fragment_dialog_add, container, false)
        processBundle(view)
        return view
    }

    private fun processBundle(view: View) {
        val bundle = arguments
        val notice = bundle?.getParcelable<FavoriteNotice>(EXTRA_NOTICE_SAVE)
        when (bundle?.getString(EXTRA_NOTICE_DELETE, "")) {
            EXTRA_NOTICE_DELETE -> {
                view.dialog_tv_question.text = getString(R.string.dialog_favorite_delete_text)
                view.dialog_tv_yes.setOnClickListener {
                    notice?.let { notice ->
                        viewModel.delete(notice)
                        dismiss()
                    }
                }
                view.dialog_tv_no.setOnClickListener {
                    dismiss()
                }
            }
            else -> {
                view.dialog_tv_yes.setOnClickListener {
                    notice?.let { notice ->
                        viewModel.insert(notice)
                        dismiss()
                    }
                }
                view.dialog_tv_no.setOnClickListener {
                    dismiss()
                }
            }
        }
    }

    fun getInstance(): DialogAddFragment {
        return DialogAddFragment()
    }

    override fun onClick(p0: View?) {
        dismiss()
    }

}

 

 

 


위와 같이 번들로 하기 귀찮은 경우 다음과 같이 고차함수를 사용해서 더 편하게 구현하시면 됩니다. (저는 이게 더 좋은 것 같습니다.)

package com.mtjin.nomoneytrip.views.dialog

import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.DialogFragment
import com.mtjin.nomoneytrip.R
import kotlinx.android.synthetic.main.fragment_dialog_yes_no.view.*

class YesNoDialogFragment(private val yesClick: (Boolean) -> Unit) : DialogFragment(),
    View.OnClickListener {
    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        val view = inflater.inflate(R.layout.fragment_dialog_yes_no, container, false)
        //어느 다이어로그에서 왔는지
//        val bundle = arguments
//        val notice = bundle.getParcelable<FavoriteNotice>(EXTRA_NOTICE_SAVE)
        view.tv_yes.setOnClickListener {
            yesClick(true)
        }
        view.tv_no.setOnClickListener {
            dismiss()
        }

        return view
    }

    companion object {
        fun getInstance(yesClick: (Boolean) -> Unit): YesNoDialogFragment {
            return YesNoDialogFragment(yesClick)
        }
    }

    override fun onClick(p0: View?) {
        dismiss()
    }
}
val dialog =
                            YesNoDialogFragment.getInstance(yesClick = {
                                if (it) viewModel.deleteReservation(reservationHistory.reservation)
                            })
                        dialog.show(requireActivity().supportFragmentManager, dialog.tag)

 

 

 


추가로 위와 코드에서 하나의 네, 아니요 다이얼로그 프래그먼트를 재활용하기 위해 질문을 매개변수로 받아 세팅하는 것을 추가한 코드입니다.

[DialogFragment]

package com.mtjin.cnunoticeapp.views.dialog

import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.DialogFragment
import com.mtjin.cnunoticeapp.R
import kotlinx.android.synthetic.main.fragment_dialog_yes_no.view.*

class YesNoDialogFragment : DialogFragment(),
    View.OnClickListener {
    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        val view = inflater.inflate(R.layout.fragment_dialog_yes_no, container, false)
        view.apply {
            tv_question.text = question
            tv_yes.setOnClickListener {
                yesClick(true)
                dismiss()
            }
            tv_no.setOnClickListener {
                dismiss()
            }
        }

        return view
    }

    companion object {
        lateinit var yesClick: (Boolean) -> Unit
        lateinit var question: String
        fun getInstance(yesClick: (Boolean) -> Unit, question: String): YesNoDialogFragment {
            this.yesClick = yesClick
            this.question = question
            return YesNoDialogFragment()
        }
    }

    override fun onClick(p0: View?) {
        dismiss()
    }
}

 

 

[DialogFragment XML]

<?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:layout_marginStart="36dp"
    android:layout_marginEnd="36dp">

    <androidx.cardview.widget.CardView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:cardCornerRadius="@dimen/radius_16dp"
        app:cardElevation="0dp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent">

        <androidx.constraintlayout.widget.ConstraintLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent">

            <androidx.appcompat.widget.AppCompatTextView
                android:id="@+id/tv_question"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginTop="36dp"
                android:gravity="center"
                android:text="추천하시겠습니까?\n한번한 추천은 취소가 불가능합니다."
                android:textColor="@color/colorBlack"
                android:textSize="@dimen/text_size_14sp"
                app:layout_constraintBottom_toTopOf="@id/view_middle"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toTopOf="parent" />

            <View
                android:id="@+id/view_middle"
                android:layout_width="match_parent"
                android:layout_height="1dp"
                android:layout_marginTop="35.3dp"
                android:background="@color/colorWhiteGray"
                app:layout_constraintBottom_toTopOf="@id/tv_no"
                app:layout_constraintTop_toBottomOf="@id/tv_question" />

            <androidx.appcompat.widget.AppCompatTextView
                android:id="@+id/tv_no"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_marginTop="@dimen/margin_12dp"
                android:layout_marginBottom="@dimen/margin_15dp"
                android:gravity="center"
                android:text="@string/no_text"
                android:textColor="@color/colorRed"
                android:textSize="@dimen/text_size_14sp"
                app:layout_constraintBottom_toBottomOf="parent"
                app:layout_constraintEnd_toStartOf="@id/view_yes_no"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toBottomOf="@id/view_middle" />

            <View
                android:id="@+id/view_yes_no"
                android:layout_width="1dp"
                android:layout_height="0dp"
                android:background="@color/colorWhiteGray"
                app:layout_constraintBottom_toBottomOf="parent"
                app:layout_constraintEnd_toStartOf="@+id/tv_yes"
                app:layout_constraintStart_toEndOf="@id/tv_no"
                app:layout_constraintTop_toBottomOf="@id/view_middle" />

            <androidx.appcompat.widget.AppCompatTextView
                android:id="@+id/tv_yes"
                android:layout_width="0dp"
                android:layout_height="0dp"
                android:layout_marginTop="@dimen/margin_12dp"
                android:layout_marginBottom="@dimen/margin_15dp"
                android:gravity="center"
                android:text="@string/yes_text"
                android:textColor="@color/colorWhiteBlue"
                android:textSize="@dimen/text_size_14sp"
                app:layout_constraintBottom_toBottomOf="parent"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintStart_toEndOf="@id/view_yes_no"
                app:layout_constraintTop_toBottomOf="@id/view_middle" />
        </androidx.constraintlayout.widget.ConstraintLayout>
    </androidx.cardview.widget.CardView>
</androidx.constraintlayout.widget.ConstraintLayout>

 

[생성]

                val dialog =
                    YesNoDialogFragment.getInstance(yesClick = {
                        if (it) viewModel.insertBoard()
                    }, question = "작성완료 하시겠습니까?\n수정 및 삭제는 불가능합니다.")
                dialog.show(supportFragmentManager, dialog.tag)

 

 

 


https://youngest-programming.tistory.com/349
바텀다이얼로그시트는 다음 링크를 참고해주세요

 

[코틀린] 안드로이드 BottomSheetDialog 콜백 구현

본 프로젝트 전에 적용하기 전 샘플 프로젝트로 잘 되나 바텀시트다이얼로그를 테스트해봤습니다. ㅎㅎ 바텀시트 다이얼로그 프래그먼트에서 아이템 선택시 기존 액티비티나 프래그먼트에서

youngest-programming.tistory.com

 

 

도움이 되셨다면 공감과 댓글은 큰 힘이 됩니다. !!

 

 

 

728x90
Comments