ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [UI] Contextual App bar (ActionMode)
    Android/UI 2021. 10. 4. 19:14

    사용되는 클래스, 인터페이스에 대해 설명하고, 마지막에 코드를 통해 확인해본다.

     

    Contextual App bar

     

    컨텍스트에 맞게 App bar가 변경되는 것을 단순히 Contextual App bar라고 부르는 것 같다.

    기존에 사용되던 Appbar가 ActionMode가 실행되면 Contextual App bar로 변경된다.

     

    android.view.ActionMode의 종류는 두가지 타입이 있다. 

    ActionMode.TYPE_PRIMARY가 Contextual App bar의 동작에 해당된다.

     

    1. Primary
    appears as a contextual action bar

    2. Floating (API 23이상 사용가능)
    appears as a floating toolbar
    back button으로 무시되지 않고, 코드상으로 무시시켜야 한다.


     

    View, Activity의 startActionMode( ActionMode.Callback구현체) 메서드로 ActionMode를 시작하면

    ActionMode.Callback구현체의 메서드 내용에 따라 ActionMode가 동작한다.

     

    ActionMode.Callback인터페이스 추상 메서드

    onCreateActionMode

    액션 모드가 시작될때 호출된다

    기존 옵션 메뉴 구현처럼 menu리소스를 inflate하고 , 제목, 부제목등등을 설정한다.

     

    onActionItemClicked

    메뉴 아이템이 선택되면 호출된다.

     

    onDestroyActionMode

    액션 모드가 종료될 때 호출된다

     

    onPrepareActionMode

    액션 모드가 업데이트 될때 호출된다. 


    기존에 View, Activity의 startActionMode메서드는 android.view.ActionMode를 사용한다.



    Activity 클래스


    --ActionMode를 시작시킴


    added in api 11
    open fun startActionMode(callback: ActionMode.Callback!): ActionMode?


    Start an action mode of the default type ActionMode#TYPE_PRIMARY
    parameter- Callback that will manage lifecycle events for this action mode
    return- The ActionMode that was started, or null if it was canceled

    added in api 23( ActionMode.TYPE_FLOATING을 위해 추가됨 )
    open fun startActionMode( callback: ActionMode.Callback!, type: Int): ActionMode?


    Int: One of ActionMode#TYPE_PRIMARY or ActionMode#TYPE_FLOATING

    --ActionMode가 시작되었을 때, 끝났을 때 통지받을 수 있는 메서드

    added in api 11
    open fun onActionModeStarted(mode: ActionMode!): Unit
    open fun onActionModeFinished(mode: ActionMode!): Unit
    두 메서드 모두 super class의 메서드를 먼저 호출시켜야 한다.



    View 클래스

    --ActionMode를 시작하는 메서드

    added in api 11
    open fun startActionMode(callback: ActionMode.Callback!): ActionMode!

    기본 ActionMode.TYPE_PRIMARY

    added in api 23
    open fun startActionMode(callback: ActionMode.Callback!, type: Int): ActionMode! 

    type 액션모드 타입선택


    예제 코드

     

    activity_main.xml의 버튼을 누르면 ActionMode가 시작되어 Contextual App bar로 변경된다.

    R.menu.contextual_menu.xml의 카운트 메뉴 아이템을 10번 누르면

    ActionMode가 종료되어 기존 ActionBar로 돌아간다.

     

     

    android.view.ActionMode부터 테스트 시작

     

    • activity_main.xml
    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout 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"
        android:orientation="vertical"
        tools:context=".MainActivity">
    
        <Button
            android:id="@+id/btn_startActionMode"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:onClick="onClick"
            android:text="액션 모드 시작" />
    </LinearLayout>

     

    • MainActivity

    버튼 클릭 리스너 설정

    class MainActivity : AppCompatActivity() {
    
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_main)
        }
    
        fun onClick(view: View) {
            when (view.id) {
                R.id.btn_startActionMode -> {
                    //액션 모드 시작
                }
            }
    
        }
    
    }

     

    • R.menu.contexul_menu.xml  (ActionMode를 시작시킬 때 Top App bar에 사용될 메뉴 리소스 파일)

    <?xml version="1.0" encoding="utf-8"?>
    <menu xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto">
        <item
            android:id="@+id/item_count"
            android:title="카운트"
            app:showAsAction="ifRoom"/>
    </menu>

     

    • 카운팅에 맞게 ActionMode.Callback인터페이스 구현 

    일회성 객체 생성대신 MainActivity에서 구현하도록 하였다.

    우선 android.view.ActionMode로 구현

     

    import androidx.appcompat.app.AppCompatActivity
    import android.os.Bundle
    import android.util.Log
    import android.view.ActionMode
    import android.view.Menu
    import android.view.MenuItem
    import android.view.View
    
    class MainActivity : AppCompatActivity(), ActionMode.Callback {
    
        private var mCount: Int = 0 //버튼 카운트 횟수
        private var mActionMode: ActionMode? = null //startActionMode로 리턴된 ActionMode를 저장
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_main)
        }
    
        fun onClick(view: View) {
            when (view.id) {
                R.id.btn_startActionMode -> {
                    mActionMode = view.startActionMode(this)
                    //ActionMode 시작시킨 후 리턴된 ActionMode를 멤버 변수에 저장
                    //또는 this.startActionMode(this)
                }
            }
    
        }
    
        override fun onCreateActionMode(mode: ActionMode?, menu: Menu?): Boolean {
            Log.d("ActionMode", "onCreateActionMode- 액션모드 시작")
            mode?.menuInflater?.inflate(R.menu.contextual_menu, menu)
            mode?.title = "ActionMode 시작됨"
            return true
        }
    
        override fun onPrepareActionMode(mode: ActionMode?, menu: Menu?): Boolean {
            Log.d("ActionMode", "onPrepareActionMode- 액션모드 업데이트")
            mode?.title = "클릭 횟수: ${mCount}"
            return true
        }
    
        override fun onActionItemClicked(mode: ActionMode?, item: MenuItem?): Boolean {
            Log.d("ActionMode", "onActionItemClicked- 메뉴 아이템 선택됨")
    
            if (item?.itemId == R.id.item_count) {
                mCount++
            }
    
            if (mCount == 10) {
                mActionMode?.finish() //10회 클릭 ActionMode종료
                mCount = 0
            } else {
                mActionMode?.invalidate() //onPrepareActionMode 호출
            }
            return true
        }
    
        override fun onDestroyActionMode(mode: ActionMode?) {
            Log.d("ActionMode", "onDestroyActionMode- 액션모드 종료")
            mActionMode = null
        }
    
    }

     

     


    ActionMode.TYPE_FLOATING 을 사용해보기 위해서 다음과 같이 설정하였는데 

    메뉴 아이템과 오버플로우 메뉴이외의 여러 것들은 무시되었다.

     

    ActionMode.TYPE_FLOATING로 액션모드를 시작시 사각형 영역이 자동으로 정해지는데 

    적절하게 위치를 정하려면 ActionMode.Callback2 추상 클래스의 일반 메서드를 오버라이딩하면된다.

     

    추상클래스이기 때문에 AppCompatActivity를 상속받고 있어 다중 상속이 불가능하다.

     

    ActionMode.Callback2 멤버변수 생성 및 일회성 객체를 사용하여 Callback2의 추상 메서드 오버라이딩, Callback 추상 메서드 4개 오버라이딩 

     

    변경된 부분

    mCallback2 참조변수 선언,초기화

    메뉴 아이템 클릭시 startActionMode(mCallback2, ActionMode.TYPE_FLOATING)

    import android.graphics.Rect
    import androidx.appcompat.app.AppCompatActivity
    import android.os.Bundle
    import android.util.Log
    import android.view.ActionMode
    import android.view.Menu
    import android.view.MenuItem
    import android.view.View
    
    class MainActivity : AppCompatActivity() {
    
        private var mCount: Int = 0 //버튼 카운트 횟수
        private var mActionMode: ActionMode? = null
    
        private lateinit var mCallback2: ActionMode.Callback2
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_main)
    
            mCallback2 = object : ActionMode.Callback2() {
    
                override fun onGetContentRect(mode: ActionMode?, view: View?, outRect: Rect?) {
                    super.onGetContentRect(mode, view, outRect) //기본 위치
                }
    
                override fun onCreateActionMode(mode: ActionMode?, menu: Menu?): Boolean {
                    Log.d("ActionMode", "onCreateActionMode- 액션모드 시작")
                    mode?.menuInflater?.inflate(R.menu.contextual_menu, menu)
                    mode?.title = "ActionMode 시작됨"
                    return true
                }
    
                override fun onPrepareActionMode(mode: ActionMode?, menu: Menu?): Boolean {
                    Log.d("ActionMode", "onPrepareActionMode- 액션모드 업데이트")
                    mode?.title = "클릭 횟수: ${mCount}"
                    return true
                }
    
                override fun onActionItemClicked(mode: ActionMode?, item: MenuItem?): Boolean {
                    Log.d("ActionMode", "onActionItemClicked- 메뉴 아이템 선택됨")
    
                    if (item?.itemId == R.id.item_count) {
                        mCount++
                    }
    
                    if (mCount == 10) {
                        mActionMode?.finish() //10회 클릭 ActionMode종료
                        mCount = 0
                    } else {
                        mActionMode?.invalidate() //onPrepareActionMode 호출
                    }
                    return true
                }
    
                override fun onDestroyActionMode(mode: ActionMode?) {
                    Log.d("ActionMode", "onDestroyActionMode- 액션모드 종료")
                    mActionMode = null
                }
    
    
            }
        }
    
        fun onClick(view: View) {
            when (view.id) {
                R.id.btn_startActionMode -> {
                    mActionMode = view.startActionMode(mCallback2, ActionMode.TYPE_FLOATING) 
                    //현재 minSDK 24
                }
            }
    
        }
    
    }

    Action.TYPE_FLOATING일 경우 View, Activity의 startActionMode에 따라 표시되는 위치가 다르게 된다.

     

    onGetContentRect의 View파라메터는

    startActionMode를 호출한 View에 대해서 위치가 정해진다.

    Activity에서 startActionMode를 호출하였으면

    윈도우의 decor뷰가 ActionMode를 시작시켜서 onGetContentRect의 view파라메터로 decor뷰가 넘어온다.


    androidx의 경우 

     

    androidx.appcompat.view.ActionMode, ActionMode.Callback을 사용. 메서드 사용은 android와 같다.

    플로팅 타입에 해당하는 것은 다른 인터페이스로 정의되어있다.

    startActionMode는 android꺼라 AppCompatActivity의 startSupportActionMode메서드를 사용한다.


    액티비티에 NoActionBar테마로 액션바를 설정하지 않은 후 Toolbar를 액티비티의 기본 ActionBar로 설정한 뒤 

    android나 androidx로 해본 결과  Toolbar가 존재하는 상태에서 Contextual App Bar가 새로 생성되었다.

     

    이유가 뭔지 잘모르겠다... 한시간 넘게 검색중인데 안나온다.

     

    ActionMode가 시작될때 mActionBar.show(),  종료될때 mActionBar.hide()를 사용하여 개선하긴 하였다.......

     


    참고 - https://medium.com/over-engineering/using-androids-actionmode-e903181f2ee3

     

    Using Android’s ActionMode

    Implementing reusable contextual menus for Views and Activities

    medium.com

     

    'Android > UI' 카테고리의 다른 글

    [UI] BottomNavigationView  (0) 2021.11.10
    [UI] ActionMode, OptionMenu 재설정  (0) 2021.10.05
    [UI] RatingBar  (0) 2021.09.27
    [UI] 스타일과 테마 - 1  (0) 2021.09.24
    [UI] Context Menu 사용법  (0) 2021.09.18

    댓글

Designed by Tistory.