코틀린-안드로이드

50일차)알고리즘 문제(두 큐 합 같게 만들기), 숙련 1주차 강의(프래그먼트 데이터 전달, 다이얼로그, 알림)

songyooho 2024. 7. 12. 21:07

>알고리즘 문제

1. 문제

길이가 같은 두 개의 큐가 주어집니다. 하나의 큐를 골라 원소를 추출(pop)하고, 추출된 원소를 다른 큐에 집어넣는(insert) 작업을 통해 각 큐의 원소 합이 같도록 만들려고 합니다. 이때 필요한 작업의 최소 횟수를 구하고자 합니다. 한 번의 pop과 한 번의 insert를 합쳐서 작업을 1회 수행한 것으로 간주합니다.

큐는 먼저 집어넣은 원소가 먼저 나오는 구조입니다. 이 문제에서는 큐를 배열로 표현하며, 원소가 배열 앞쪽에 있을수록 먼저 집어넣은 원소임을 의미합니다. 즉, pop을 하면 배열의 첫 번째 원소가 추출되며, insert를 하면 배열의 끝에 원소가 추가됩니다. 예를 들어 큐 [1, 2, 3, 4]가 주어졌을 때, pop을 하면 맨 앞에 있는 원소 1이 추출되어 [2, 3, 4]가 되며, 이어서 5를 insert하면 [2, 3, 4, 5]가 됩니다.

다음은 두 큐를 나타내는 예시입니다.

queue1 = [3, 2, 7, 2]
queue2 = [4, 6, 5, 1]

두 큐에 담긴 모든 원소의 합은 30입니다. 따라서, 각 큐의 합을 15로 만들어야 합니다. 예를 들어, 다음과 같이 2가지 방법이 있습니다.

  1. queue2의 4, 6, 5를 순서대로 추출하여 queue1에 추가한 뒤, queue1의 3, 2, 7, 2를 순서대로 추출하여 queue2에 추가합니다. 그 결과 queue1은 [4, 6, 5], queue2는 [1, 3, 2, 7, 2]가 되며, 각 큐의 원소 합은 15로 같습니다. 이 방법은 작업을 7번 수행합니다.
  2. queue1에서 3을 추출하여 queue2에 추가합니다. 그리고 queue2에서 4를 추출하여 queue1에 추가합니다. 그 결과 queue1은 [2, 7, 2, 4], queue2는 [6, 5, 1, 3]가 되며, 각 큐의 원소 합은 15로 같습니다. 이 방법은 작업을 2번만 수행하며, 이보다 적은 횟수로 목표를 달성할 수 없습니다.

따라서 각 큐의 원소 합을 같게 만들기 위해 필요한 작업의 최소 횟수는 2입니다.

길이가 같은 두 개의 큐를 나타내는 정수 배열 queue1, queue2가 매개변수로 주어집니다. 각 큐의 원소 합을 같게 만들기 위해 필요한 작업의 최소 횟수를 return 하도록 solution 함수를 완성해주세요. 단, 어떤 방법으로도 각 큐의 원소 합을 같게 만들 수 없는 경우, -1을 return 해주세요.


제한사항

  • 1 ≤ queue1의 길이 = queue2의 길이 ≤ 300,000
  • 1 ≤ queue1의 원소, queue2의 원소 ≤ 109
  • 주의: 언어에 따라 합 계산 과정 중 산술 오버플로우 발생 가능성이 있으므로 long type 고려가 필요합니다.

2. 솔루션

class Solution {
    fun solution(queue1: IntArray, queue2: IntArray): Int {
        var answer=0

        var q1sum=queue1.map{it.toLong()}.sum()
        var target=(q1sum+queue2.map{it.toLong()}.sum())
        if(target%2!=0L) return -1
        target/=2
        
        var left=0
        var right=queue1.size
        
        val q=queue1+queue2
        
        while(left<q.size&&right<q.size){
            if(q1sum==target) return answer
            if(q1sum<target){
                q1sum+=q[right++]
                answer++
            }else{
                q1sum-=q[left++]
                answer++
            }
        }
        return -1
       
    }
}

-두 큐의 합을 각각 구했을때 둘이 같아진다는것은 한 큐의 합이 두 큐 모든 원소의 합을 구해 반으로 나눈것과 같아진다는 말이므로 queue1의 합만 살펴봐도 된다. =>한 큐의 합이 타겟값과 같아지는지 체크

-그리디 알고리즘을 이용: queue1이 될수 있는 원소의 나열은 queue1과 queue2를 이어붙인것의 부분수열이므로 새로운 q=queue1+queue2의 부분수열의 합을 체크하면 된다. 

-시작은 아무것도 poll하지 않은 상태이므로 queue1의 합에서부터 시작한다.

-이후 while문을 돌면서 타겟보다 크면 left를 올려주고, 타겟보다 작으면 right를 올려주며 부분수열의 합을 갱신한다.

 

@위 방법을 사용하는 이유

-시작 부분수열의 합을 s1, 타겟을 t라 하자

-s1>t일때 최적으로 가기위해 처음에 바로 left를 올려주는 것 말고 다른 방법이 있다고 하자

-그렇다고 하더라도 left를 언젠가는 올려줘야 함. 

-순서가 바뀌는 것을 아무 의미 없으므로 바로 left를 올려주는 것이 최적의 방법이 된다. s<t인 경우도 마찬가지

-이 후에 생성된 부분수열에 대해 합이 si라 할때 그 역시도 마찬가지이기 때문에 위 방법을 사용한다.

 

 

 

>숙련 1주차 강의

 

1. 프레그먼트의 데이터 전달

 

(1)Activity -> Fragment :companion object를 이용

-프래그먼트의 newInstance 메소드로 데이터를 번들로 전달하며 프래그먼트 생성

binding.run {
            fragment1Btn.setOnClickListener{
                // [1] Activity -> FirstFragment
                val dataToSend = "Hello First Fragment! \n From Activity"
                val fragment = FirstFragment.newInstance(dataToSend)
                setFragment(fragment)
            }

            fragment2Btn.setOnClickListener {
                // [1] Activity -> SecondFragment
                val dataToSend = "Hello Second Fragment!\n From Activity"
                val fragment = SecondFragment.newInstance(dataToSend)
                setFragment(fragment)
            }

 

-받은 정보를  프래그먼트에서 argument에 번들 저장 후 onCreate에서 번들값을 프로퍼티에 할당한  onViewCreated()에서 view에 적용시킴

private var param1: String? = null

override fun onCreate(saveInstanceState: Bundle?){
    super.onCreate(saveInstanceState)
    
    argument?.let{
    	param1 = it.getString(ARG_PARAM1)
    }
}


companion object {
        @JvmStatic
        fun newInstance(param1: String) =
            // [1] Activity -> FirstFragment
            FirstFragment().apply {
                arguments = Bundle().apply {
                    putString(ARG_PARAM1, param1)
                }
            }
}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        // [1] Activity -> FirstFragment
        binding.tvFrag1Text.text = param1     
}

-ARG_PARAM1은 클래스 바깥에 전역으로 설정한 private const val인 상수

private const val ARG_PARAM1= "param1"

 -arguments: 프래그먼트의 번들 프로퍼티라 보면 된다. 

=>Bundle()을 파라미터로 받고 번들은 put(key:String, value)로 데이터를 넣을 수 있다.

=>기본 타입뿐만 아니라 parcelable까지 포함 가능

 

-@JvmStatic: 변수나 메소드를 static처럼 사용하기 위함

<1>static: 클래스 변수에 쓰이는 키워드로 따로 메모리에서 저장해서 프로그램 종료시까지 유지된다. 객체 생성없이 접근이 가능하다.

<2>companion object와 static의 차이: 자바로 변환시 차이점이 생김

-class A안에 companion object가 있으면 자바에선 class A안에 companion이라는 클래스가 생김

-어노테이션을 사용하지 않은 변수에 대해서는 companion에만 getter,setter가 생성됨. 이때 변수는 class A의 멤버로 존재

=>이러면 static멤버에 대한 접근을 companion의 getter로만 접근할 수있게됨.

-어노테이션을 사용하면 companion뿐만 아니라 class A에서도 해당 멤버에 대한 getter,setter가 생성됨

-결과: A의 static 멤버에 접근하기위해서 A.companion.getter()로 사용해야하는 함수를 A.getter()로 접근할 수 있게해줌.

=>기존 자바에서 private static 멤버에 대한 접근을 A.getter로 하던대로 할 수 있게 해줌. 

=>완전히 static처럼 사용하게 해줌

=>즉, java와 혼용되는 경우 호환성을 위해서 이렇게 해주는 것.

 

(2)Fragment ->Fragment

// [2] Fragment -> Fragment
        binding.btnGofrag2.setOnClickListener{
            val dataToSend = "Hello Fragment2! \n From Fragment1"
            val fragment2 = SecondFragment.newInstance(dataToSend)
            requireActivity().supportFragmentManager.beginTransaction()
                .replace(R.id.frameLayout, fragment2)
                .addToBackStack(null)
                .commit()
        }

- requireActivity().supportFragmentManager 부분은 parentFragmentManager로 써도 됨

-beginTransaction().~.commit()은 commit{ ~ }으로 대체 가능

 

 

(3)Fragment -> Activity

-클래스 외부 전역에 FragmentDataListener 인터페이스를 정의하고 액티비티가 이 인터페이스를 정의했는지 onAttach에서 체크

=>액티비티가 인터페이스를 상속받고 있어야 context is FragmentDataListener 부분을 통과할 수 있음

-상속받아서 구현중이면 해당 context를 프래그먼트의 프로퍼티인 listener에 저장

-onViewCreated에서 버튼을 눌렀을시 listener에 저장된 activity의 내의 오버라이드된 onDataReceived를 호출해서 데이터를 보냄

-마지막으로 onDestroyView에서 listener객체 해제

private const val ARG_PARAM1 = "param1"

interface FragmentDataListener {
    fun onDataReceived(data: String)
}

class SecondFragment : Fragment() {

    // [3] SecondFragment -> Activity
    private var listener: FragmentDataListener? = null

    private var param1: String? = null

    private var _binding: FragmentSecondBinding? = null
    private val binding get() = _binding!!


    override fun onAttach(context: Context) {
        super.onAttach(context)

        // [3] SecondFragment -> Activity
        if (context is FragmentDataListener) {
            listener = context
        } else {
            throw RuntimeException("$context must implement FragmentDataListener")
        }
    }

 ...

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
        _binding = FragmentSecondBinding.inflate(inflater, container, false)
        return binding.root
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        // [2] Fragment -> Fragment
        binding.tvFrag2Text.text = param1

        // [3] SecondFragment -> Activity
        binding.btnSendActivity.setOnClickListener{
            val dataToSend = "Hello from SecondFragment!"
            listener?.onDataReceived(dataToSend)
        }
    }

...

    override fun onDestroyView() {
        super.onDestroyView()
        // Binding 객체 해제
        _binding = null
        listener = null
    }
}

 

-액티비티에서는 인터페이스를 상속받고 데이터를 받을 메소드를 오버라이드해줌

class MainActivity : AppCompatActivity(), FragmentDataListener {
.
.
.
 // [3] SecondFragment -> Activity
    override fun onDataReceived(data: String) {
        // Fragment에서 받은 데이터를 처리
        Toast.makeText(this, data, Toast.LENGTH_SHORT).show()
    }
}

 

 

 

2. 다이얼로그

(1)다이얼로그란

-사용자에게 결정을 내리거나 추가정보를 입력하라는 메시지를 표시하는 작은 창

-다음으로 진행전 조치를 취해야하는 모달 이벤트에 사용

@모달: 기존 화면위에 플로팅 되는 화면

 

(2)다이얼로그 구조

-AlertDialog클래스를 사용하면 여러 대화 상자 디자인 빌드 가능

-영역

<1>제목: 선택사항, 컨텐츠 영역이 채워져 있는 경우에만 사용

<2>컨텐츠 영역: 메시지, 목록, 혹은 맞춤 레이아웃 표시

<3>작업버튼: 버튼은 3개까지 가능

 

(3)사용법

<1>기본 다이얼로그

var builder = AlertDialog.Builder(this)
builder.setTitle("기본 다이얼로그 타이틀")
builder.setMessage("기본 다이얼로그 메세지")
builder.setIcon(R.mipmap.ic_launcher)

-빌더 생성 후 타이틀, 타이틀 앞에 붙는 아이콘,  컨텐츠영역의 메시지를 설정

 

val listener = object : DialogInterface.OnClickListener {
    override fun onClick(p0: DialogInterface?, p1: Int) {
        when (p1) {
            DialogInterface.BUTTON_POSITIVE ->
                binding.tvTitle.text = "BUTTON_POSITIVE"
            DialogInterface.BUTTON_NEUTRAL ->
                binding.tvTitle.text = "BUTTON_NEUTRAL"
            DialogInterface.BUTTON_NEGATIVE ->
                binding.tvTitle.text = "BUTTON_NEGATIVE"
        }
    }
}

-각 버튼 클릭별 작업을 설정

builder.setPositiveButton("Positive", listener)
builder.setNegativeButton("Negative", listener)
builder.setNeutralButton("Neutral", listener)

builder.show()

-각 버튼별 이름을 설정하고 listener를 연결시켜줌

-show()로 보여줌

 

<2>커스텀 다이얼로그

// 2. 커스텀 다이얼로그
binding.btn2Custom.setOnClickListener {
    val builder = AlertDialog.Builder(this)
    builder.setTitle("커스텀 다이얼로그")
    builder.setIcon(R.mipmap.ic_launcher)

    val v1 = layoutInflater.inflate(R.layout.dialog, null)
    builder.setView(v1)

    // p0에 해당 AlertDialog가 들어온다. findViewById를 통해 view를 가져와서 사용
    val listener = DialogInterface.OnClickListener { p0, p1 ->
        val alert = p0 as AlertDialog
        val edit1: EditText? = alert.findViewById<EditText>(R.id.editText)
        val edit2: EditText? = alert.findViewById<EditText>(R.id.editText2)

        binding.tvTitle.text = "이름 : ${edit1?.text}"
        binding.tvTitle.append(" / 나이 : ${edit2?.text}")
    }

    builder.setPositiveButton("확인", listener)
    builder.setNegativeButton("취소", null)

    builder.show()
}

-setView() 부분에 아래 코드로 대체 가능

val v1=DialogBinding.inflate(LayoutInflater.from(this),null,false)
builder.setView(v1.root)

-listner부분은 아래 코드로 대체 가능

val listener = DialogInterface.OnClickListener {_,int->
    when (int){
        DialogInterface.BUTTON_POSITIVE ->run {
            binding.tvTitle.text = "이름 : ${v1.editText.text}"
            binding.tvTitle.append(" / 나이 : ${v1.editText2.text}")
        }
        else -> return@OnClickListener
    }
}

=>지금 경우는 negative버튼에 리스너를 설정 안해서 그냥 나가지게 하여서 리스너 이벤트가 고려할 경우의 수가 한가지 뿐이므로 when없이 그냥 해도 괜찮음

val listener = DialogInterface.OnClickListener {_,_->
    binding.tvTitle.text = "이름 : ${v1.editText.text}"
    binding.tvTitle.append(" / 나이 : ${v1.editText2.text}")
}

 

@append(): 텍스트뷰의 기존 텍스트에서 새 텍스트를 이어붙여주는 역할을 한다.

 

 

<3>날짜 다이얼로그

binding.btn3Date.setOnClickListener {
    val calendar = Calendar.getInstance()
    val year = calendar.get(Calendar.YEAR)
    val month = calendar.get(Calendar.MONTH)
    val day = calendar.get(Calendar.DAY_OF_MONTH)

    val listener = DatePickerDialog.OnDateSetListener { datePicker, i, i2, i3 ->
        // i년 i2월 i3일
        binding.tvTitle.text = "${i}년 ${i2 + 1}월 ${i3}일"
    }

    var picker = DatePickerDialog(this, listener, year, month, day)
    picker.show()
}

-calendar을 이용해 오늘 날짜를 받아 DatePickerDialog에 설정함

-listener도 한번에 설정

@버튼 커스터마이징 하는 방법

-show()아래에서 버튼을 가져와 색이나 글을 바꿀 수 있다.

picker.show()
picker.getButton(DatePickerDialog.BUTTON_POSITIVE).setText("설정")
picker.getButton(DatePickerDialog.BUTTON_NEGATIVE).setText("취소")

 

 

<4>시간 다이얼로그

// 4. 시간 다이얼로그
binding.btn4Time.setOnClickListener {
    val calendar = Calendar.getInstance()
    val hour = calendar.get(Calendar.HOUR)
    val minute = calendar.get(Calendar.MINUTE)

    val listener = TimePickerDialog.OnTimeSetListener { timePicker, i, i2 ->
        binding.tvTitle.text = "${i}시 ${i2}분"
    }

    val picker = TimePickerDialog(this, listener, hour, minute, false) // true하면 24시간 제
    picker.show()
}

-calendar을 이용해 시간을 얻어서 TimePickerDialog에 설정

=>매개변수중 마지막은 24시간으로 보여줄지 12시간으로 보여줄지 여부: true가 24시간

-날짜 다이얼로그와 마찬가지로 버튼 변경가능

-TimePickerDialog()에서 두번째 인자가 생략되어있는데 이를 이용해서 다른 테마 적용가능

=>예시

val picker = TimePickerDialog(this, android.R.style.Theme_DeviceDefault_Dialog_Alert,listener, hour, minute, false)

 

<5>프로그래스 다이얼로그

binding.btn5Porgress.setOnClickListener {
    val builder = AlertDialog.Builder(this)
    builder.setTitle("프로그래스바")
    builder.setIcon(R.mipmap.ic_launcher)

    val v1 = layoutInflater.inflate(R.layout.progressbar, null)
    builder.setView(v1)

    builder.show()
}
<?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="match_parent"
    android:gravity="center"
    android:orientation="vertical">

    <ProgressBar
        android:id="@+id/progressbar"
        android:layout_width="match_parent"
        android:layout_height="76dp"
        android:layout_gravity="center"
        android:layout_marginTop="48dp"
        android:indeterminate="false"/>

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="loading..."
        android:textSize="16sp"
        android:layout_marginTop="16dp"/>
</LinearLayout>

-커스텀 다이얼로그로 만들 수 있음

-배경색 바꾸는 방법

val alertDialog=builder.create()
alertDialog.window!!.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
alertDialog.show()

-주의해야할것은 builder가 아니라 alertDialog로 show()해야 한다는 것이다.

 

@다이얼로그 배경색 설정

-일반 다이얼 로그에서는 위에서 바꾸는 배경색이 그 배경색이 맞음

-커스텀 다이얼로그의 경우 다른 레이아웃을 인플레이트 시키기 때문에 배경색은 배경 그림자 같은 느낌

-예시:여기서 하얀색이 배경

=>이 때문에 완전히 커스텀 하기 위해서는 버튼과 제목을 없애고 배경색까지 transparent로 바꿔야 한다.

 

 

(4)DialogFragment

class MyDialogFragment : DialogFragment() {

    override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
        return activity?.let {
            // Use the Builder class for convenient dialog construction
            val builder = AlertDialog.Builder(it)
            builder.setMessage("Do you like this app?")
                .setPositiveButton("Yes") { dialog, id ->
                    // Send the positive button event back to the host activity
                }
                .setNegativeButton("No") { dialog, id ->
                    // Send the negative button event back to the host activity
                }
            // Create the AlertDialog object and return it
            builder.create()
        } ?: throw IllegalStateException("Activity cannot be null")
    }
}
MyDialogFragment().show(supportFragmentManager,null)

-아래 코드에서 태그는 아무 문자열 넣으면 됨

-activity는 getActicity()와 같음

-액티비티를 context로 받아서 builder.create()를 반환함

-프래그먼트를 show()하면 다이얼로그가 뜸

 

 

3. 알림(Notification)

(1)알림이란: 단말기 상단에 표시되는 것으로 간단한 작업이 가능하고 알림을 눌러 앱 열기 가능

(2)알림 채널(안드로이드 8.0이상)

-알림 만들기 전에 알림 채널을 만들어야함

-알림을 그룹화해 알림 활성화나 방식 변경 가능

-코드

private val myNotificationID = 1
private val channelID = "default"

private fun createNotificationChannel() {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { // Android 8.0
        val channel = NotificationChannel(channelID, "default channel",
            NotificationManager.IMPORTANCE_DEFAULT)
        channel.description = "description text of this channel."
        val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
        notificationManager.createNotificationChannel(channel)
    }
}

 

(3)알림 생성

private val myNotificationID = 1

private fun showNotification() {
    val builder = NotificationCompat.Builder(this, channelID)
        .setSmallIcon(R.mipmap.ic_launcher)
        .setContentTitle("title")
        .setContentText("notification text")
        .setPriority(NotificationCompat.PRIORITY_DEFAULT)
    NotificationManagerCompat.from(this).notify(myNotificationID, builder.build())
}

-NotificationCompat.Builder로 객체 알림에 대한 UI정보와 작업 지정

-NotificationCompat.Builder.build() 호출:Notification 객체 반환

- NotificationManagerCompat.notify() 호출로 시스템에 Notification객체 전달 =>알림 

 

-builer에 들어갈 수 있는 내용

<1>setSmallIcon: 아이콘

<2>setContentTitle: 제목

<3>setContentText(): 세부내용

<4>setPriority: 중요도

[1]채널 중요도(안드로이드 8.0이상): 채널에서 중요도 설정

 val channel = NotificationChannel(channelID, "default channel",
            NotificationManager.IMPORTANCE_DEFAULT)

 

[2]알림 우선순위(안드로이드 7.1이하): 위에 코드에서처럼 NotificationCompat.Builder(this,channelID)에서 setPriority로 설정한다.

NotificationCompat.Builder(this,channelID).setPriority(NotificationCompat.PRIORITY_DEFAULT)

 

[3]중요도 순위

<5>긴 텍스트: 긴 텍스트를 추가한 확장뷰로 알림에 넣음

builder.setStyle(NotificationCompat.BigTextStyle().bigText(resources.getString(R.String.아이디)))

 

<6>그림: 우측에 작은 그림이다가 확장뷰에서 큰 그림으로 보여짐

val bitmap=BitmapFactory.decodeResource(resources, R.drawable.아이디)
builder.setStyle(NotificationCompat.BigPictureStyle().
    bigPicture(bitmap).
    bigLargeIcon(null)) /큰 아이콘을 확장뷰동안 숨기기

 

<7>버튼: 버튼을 누르면 activity나 broadcast시작

val intent=Intent(this, TestActivity::class.java)
val pendingIntent = PendingIntent.getActivity(this,0,intent,0)

builder.addAction(R.drawable.아이디, 버튼텍스트:String(),pendingIntent)

-addAction에서 첫 매개변수인 drawable리소스의 경우 안드로이드 7.0부터는 안 보이게 변함.

-PendingIntent.getActivity()

[1]첫번째 매개변수: context

[2]두번째 매개변수: Request Code=>pendingIntent를 고유하게 식별하기 위해 사용.

[3]세번째 매개변수:Intent 

[4]네번째 매개변수:Flags =>PendingIntent의 동작방식을 의미하며 0은 기본동작을 의미 

@Flags 종류

- FLAG_UPDATE_CURRENT : 이미 생성된 PendingIntent가 있다면, 덮어씌움

- FLAG_CANCLE_CURRENT : 이미 생성된 PendingIntent가 있다면, 취소 후 새로 생성

- FLAG_NO_CREATE : 이미 생성된 PendingIntent가 있다면, 그걸 사용

- FLAG_ONE_SHOT : 해당 PendingIntent를 일회성으로 사용

 

@PendingIntent

=>보류 인텐트: Intent를 바로 시작하지않고 특정 시점에 시작하도록 해주는 클래스

 

<8>프로그래스바

//스레드로 프로그래스바 업데이트
Thread{
	for(i in (1..100).step(10)){
    	Thread.sleep(1000)
        builder.setProgress(100,i,false)
        
        NotificationManagerCompat.from(this).notify(myNotificationID, builder.build())
    }
    builder.setContentText("Completed").setProgress(0,0,false) //max=0이면 프로그래스바 사라짐
    NotificationManagerCompat.from(this).notify(myNotificationID, builder.build())
}.start()

 

<9>알림에 액티비티 연결 예

class MainActivity : AppCompatActivity() {
    private val binding by lazy { ActivityMainBinding.inflate(layoutInflater) }
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(binding.root)
        binding.notificationButton.setOnClickListener{
            notification()
        }
    }

    fun notification(){
        val manager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager

        val builder: NotificationCompat.Builder
        if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O){
            // 26 버전 이상
            val channelId="one-channel"
            val channelName="My Channel One"
            val channel = NotificationChannel(
                channelId,
                channelName,
                NotificationManager.IMPORTANCE_DEFAULT
            ).apply {
                // 채널에 다양한 정보 설정
                description = "My Channel One Description"
                setShowBadge(true)
                val uri: Uri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION)
                val audioAttributes = AudioAttributes.Builder()
                    .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
                    .setUsage(AudioAttributes.USAGE_ALARM)
                    .build()
                setSound(uri, audioAttributes)
                enableVibration(true)
            }
            // 채널을 NotificationManager에 등록
            manager.createNotificationChannel(channel)

            // 채널을 이용하여 builder 생성
            builder = NotificationCompat.Builder(this, channelId)

        }else {
            // 26 버전 이하
            builder = NotificationCompat.Builder(this)
        }

		val bitmap = BitmapFactory.decodeResource(resources, R.drawable.flower)
        val intent = Intent(this, SecondActivity::class.java)
        intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
        val pendingIntent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE)
        // 알림의 기본 정보
        builder.run {
            setSmallIcon(R.mipmap.ic_launcher)
            setWhen(System.currentTimeMillis())
            setContentTitle("새로운 알림입니다.")
            setContentText("알림이 잘 보이시나요.")
            setStyle(NotificationCompat.BigTextStyle()
                .bigText("이것은 긴텍스트 샘플입니다. 아주 긴 텍스트를 쓸때는 여기다 하면 됩니다.이것은 긴텍스트 샘플입니다. 
아주 긴 텍스트를 쓸때는 여기다 하면 됩니다.이것은 긴텍스트 샘플입니다. 아주 긴 텍스트를 쓸때는 여기다 하면 됩니다."))
            setLargeIcon(bitmap)
//            setStyle(NotificationCompat.BigPictureStyle()
//                    .bigPicture(bitmap)
//                    .bigLargeIcon(null))  // hide largeIcon while expanding
            addAction(R.mipmap.ic_launcher, "Action", pendingIntent)
        }


        manager.notify(11, builder.build())
    }

}

 

 

@권한추가

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.myapp">

    <!-- API 33 이상을 위한 알림 권한 추가 -->
    <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />

    ...
</manifest>

 @권한추가 알림

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
    if (!NotificationManagerCompat.from(this).areNotificationsEnabled()) {
        // 알림 권한이 없다면, 사용자에게 권한 요청
        val intent = Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS).apply {
            putExtra(Settings.EXTRA_APP_PACKAGE, packageName)
        }
        startActivity(intent)
    }
}