-
[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
'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