컨텍스트 메뉴(context menu) 또는 상황에 맞는 메뉴는 그래픽 사용자 인터페이스 안에서 어떠한 항목을 클릭할 때 뜨는 팝업 메뉴로, 메뉴를 호출한 동작, 응용 프로그램의 실행, 선택된 항목의 상황에 따라 다양한 선택 사항을 나열하여 보여준다. 바로 가기 메뉴라고도 한다.
위키백과
가령 바탕화면에서 오른쪽 마우스를 눌렀을 때 바탕화면과 디스플레이에 관한 메뉴를 보여줍니다.
이를 컨텍스트 메뉴라고 합니다.
우선 컨텍스트 메뉴를 만들어주기 위해 res
디렉터리에 menu
라는 디렉터리를 만들어주고, 내부에 context_menu_main.xml
파일을 만들어줍시다.
그 파일에 아래와 같이 메뉴 아이템들을 만들어줍니다.
<!-- Context Menu -->
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/text_color"
android:title="글자색 변경"
/>
<item
android:id="@+id/text_back_color"
android:title="배경색 변경"
/>
<item
android:id="@+id/text_basic"
android:title="초기화"
/>
</menu>
컨텍스트 메뉴를 클릭할 때마다 메인 화면에서 보이는 텍스트뷰에 효과를 줄 것입니다. 그래서 텍스트뷰의 속성을 아래와 같이 작성해줍니다.
<!-- activity_main.xml -->
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Context Menu 보기"
android:textSize="30sp"
android:textColor="@color/black"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
xml 파일로 컨텍스트 메뉴를 구성하였다면, 이제 화면단에서 사용할 수 있도록 해주어야 합니다.
MainActivity
를 열어서 컨텍스트 메뉴를 적용해주겠습니다. 그 전에 각 컴포넌트에 좀 더 쉽게 접근할 수 있도록 build.gradle
에 다음과 같이 추가해주겠습니다.
android {
buildFeatures {
viewBinding true
}
}
자 이제 메인 액티비티에 ActivityMainBinding
을 선언해 주겠습니다.
import com.example.sample13.databinding.ActivityMainBinding
class MainActivity : AppCompatActivity() {
private val binding by lazy { ActivityMainBinding.inflate(layoutInflater) }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(binding.root)
}
}
그 다음으로 컨텍스트 메뉴를 생성하는 함수를 오버라이드 해줍니다.
그리고 내부에 menuInflater.inflate(R.menu.context_menu_main, menu)
를 넣어줍니다.
여기에서 menuInflater
에 대해 자세히 알아보겠습니다.
This class is used to instantiate menu XML files into Menu objects.
For performance reasons, menu inflation relies heavily on pre-processing of XML files that is done at build time. Therefore, it is not currently possible to use MenuInflater with an XmlPullParser over a plain XML file at runtime; it only works with an XmlPullParser returned from a compiled resource (R. something file.)
안드로이드 개발자 문서
클래스로 규정되어 있으며 이 클래스는 메뉴 XML 파일을 메뉴 객체로 인스턴스화 하는데 사용됩니다.
성능상의 이유로 메뉴 인플레이션은 빌드 시 수행되는 XML 파일 사전 처리에 크게 의존합니다....
import com.example.sample13.databinding.ActivityMainBinding
class MainActivity : AppCompatActivity() {
private val binding by lazy { ActivityMainBinding.inflate(layoutInflater) }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(binding.root)
}
// 생성한 컨텍스트 메뉴 적용
override fun onCreateContextMenu(menu: ContextMenu?, v: View?, menuInfo: ContextMenu.ContextMenuInfo?) {
menuInflater.inflate(R.menu.context_menu_main, menu)
}
}
다시 말해서 우리가 작성한 context_menu_main.xml
파일을 메뉴 객체로 만들어 주기 위해 사용되는 것입니다.
여기에 딸려 있는 인터페이스인 inflate
를 구현해주며, 다음과 같은 추상 메서드가 상속됩니다.
void inflate(int menuRes, Menu menu) // 메뉴xml 경로와 menu 객체
그 다음으로 onContextItemSelected
를 오버라이드하여 메뉴를 선택했을 때 바꿔 줄 내용을 선언해주겠습니다.
import com.example.sample13.databinding.ActivityMainBinding
class MainActivity : AppCompatActivity() {
private val binding by lazy { ActivityMainBinding.inflate(layoutInflater) }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(binding.root)
}
// 생성한 컨텍스트 메뉴 적용
override fun onCreateContextMenu(menu: ContextMenu?, v: View?, menuInfo: ContextMenu.ContextMenuInfo?) {
menuInflater.inflate(R.menu.context_menu_main, menu)
}
override fun onContextItemSelected(item: MenuItem): Boolean {
val textView = findViewById<TextView>(R.id.textView)
when(item.itemId) {
R.id.text_color -> {
textView.text = "글자색 변경"
textView.setTextColor(Color.parseColor("#ff0000"))
}
R.id.text_back_color -> {
textView.text = "배경색 변경"
textView.setBackgroundColor(Color.parseColor("#0000ff"))
textView.setTextColor(Color.parseColor("#ffffff"))
}
R.id.text_basic -> {
textView.text = "초기화"
textView.setTextColor(Color.parseColor("#000000"))
textView.setBackgroundColor(Color.parseColor("#ffffff"))
}
}
return super.onContextItemSelected(item)
}
}
글자색과 배경색을 바꿔주기 위해 setter
를 사용했습니다.
메뉴가 생성되었으므로 이제 초기화될 때 텍스트뷰에 이를 적용시키기 위해서 onCreate
함수에 registerForContextMenu
메서드를 통해 텍스트뷰를 매개변수로 받아 처리하게 해줍니다.
import com.example.sample13.databinding.ActivityMainBinding
class MainActivity : AppCompatActivity() {
private val binding by lazy { ActivityMainBinding.inflate(layoutInflater) }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(binding.root)
registerForContextMenu(binding.textView)
}
// 생성한 컨텍스트 메뉴 적용
override fun onCreateContextMenu(menu: ContextMenu?, v: View?, menuInfo: ContextMenu.ContextMenuInfo?) {
menuInflater.inflate(R.menu.context_menu_main, menu)
}
override fun onContextItemSelected(item: MenuItem): Boolean {
val textView = findViewById<TextView>(R.id.textView)
when(item.itemId) {
R.id.text_color -> {
textView.text = "글자색 변경"
textView.setTextColor(Color.parseColor("#ff0000"))
}
R.id.text_back_color -> {
textView.text = "배경색 변경"
textView.setBackgroundColor(Color.parseColor("#0000ff"))
textView.setTextColor(Color.parseColor("#ffffff"))
}
R.id.text_basic -> {
textView.text = "초기화"
textView.setTextColor(Color.parseColor("#000000"))
textView.setBackgroundColor(Color.parseColor("#ffffff"))
}
}
return super.onContextItemSelected(item)
}
}
<select>
html에서 <select>
라는 태그에 <option>
을 넣어 드랍다운 목록을 만들어주었습니다.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>Fruits</title>
</head>
<body>
<form method="get">
<select>
<option value="apple">사과</option>
<option value="pear">배</option>
<option value="grape">포도</option>
</select>
</form>
</body>
</html>
이 태그는 오직 문자열만 사용할 수 있다는 단점이 있기 때문에 활용성이 그렇게 높지는 않습니다.
그렇지만 안드로이드에서는 <option>
에 해당하는 것을 각각의 객체로 만들어서 넣어줄 수 있기 때문에 텍스트 뿐만 아니라 이미지도 함께 넣어줄 수 있습니다.
우선 스피너를 만들어주기 위해 activity_main.xml
에 스피너를 추가해 주겠습니다.
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:id="@+id/textView"
android:layout_width="335dp"
android:layout_height="33dp"
android:text="Hello World!"
android:layout_centerHorizontal="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintHorizontal_bias="0.501"
app:layout_constraintVertical_bias="0.537"/>
<Spinner
android:id="@+id/spinner"
android:layout_width="386dp"
android:layout_height="55dp"
app:layout_constraintBottom_toTopOf="@+id/textView"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintHorizontal_bias="0.501"
app:layout_constraintVertical_bias="0.742"/>
</androidx.constraintlayout.widget.ConstraintLayout>
그리고 스피너 내부에 들어갈 아이템을 텍스트뷰로 만들어주겠습니다.
layout
디렉터리에 item_spinner.xml
을 추가해줍니다.
<?xml version="1.0" encoding="utf-8"?>
<TextView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/tvItemSpinner"
android:layout_width="match_parent"
android:layout_height="45dp"
android:paddingTop="10dp"
android:paddingStart="30dp"
android:textColor="@android:color/black"
android:textSize="15sp"
android:paddingLeft="30dp"
/>
이 아이템 하나가 각각의 스피너 아이템 객체가 될 것입니다.
이제 MainActivity
로 돌아와 스피너를 초기화 해주겠습니다. 과일 이름이 들어있는 배열을 사용할 것입니다.
스피너를 초기화 해주기 위해 setUpSpinnerFruits
라는 함수를 만들어줍시다.
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
setUpSpinnerFruits()
}
// 스피너 아이템에 추가할 항목
val fruits = arrayOf("과일 선택", "사과", "배", "바나나", "포도")
// 스피너 초기화(값 설정) - initialize
fun setUpSpinnerFruits() {
val adapter = ArrayAdapter(this, R.layout.item_spinner, fruits) // fruits 배열의 각 인덱스마다 Adapter 적용
// spinner에 적용
val spinner = findViewById<Spinner>(R.id.spinner)
spinner.adapter = adapter
}
}
우선 스피너의 아이템이 될 배열을 하나 만들어주었습니다.
그리고 ArrayAdapter
를 사용하여 각 배열의 인덱스마다 Adapter
를 적용하여 스피너 아이템으로 만들어줍니다.
이 과정을 통해 위에서 작성한 item_spinner.xml
의 서식이 각각 지정됩니다.
Spinner
에 위에서 적용시킨 Adapter
넣어주는 과정을 거칩니다. 그리고 onCreate
함수에서 애플리케이션 실행과 동시에 초기화시켜줍니다.
그러면 이제 스피너 아이템 선택을 감지하고 기본적으로 배치되어있는 텍스트뷰에 선택한 스피너의 값이 찍히도록 해보겠습니다.
setUpSpinnerFruits
함수에 이어서 작성합니다.
// 스피너 아이템 선택 감지 및 출력
fun setUpSpinnerHandler() {
val spinner = findViewById<Spinner>(R.id.spinner)
val textView = findViewById<TextView>(R.id.textView)
spinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
override fun onItemSelected(p0: AdapterView<*>?, view: View?, position: Int, id: Long) {
textView.text = "selected: $position ${spinner.getItemAtPosition(position)}"
}
override fun onNothingSelected(p0: AdapterView<*>?) {
}
}
}
함수 내부에는 스피너와 텍스트뷰를 불러오는 변수가 있습니다.
스피너에 대해 onItemSelectedListener
를 사용하여 선택된 아이템에 대해 처리해 줄 작업을 지정합니다.
이 때 AdapterView
는 ListView
, GridView
, Spinner
, Gallery
의 상위 클래스입니다. 우리가 사용하는 스피너에 OnItemSelectedListener
로 이벤트를 등록해 줄 것입니다.
내부에 콜백함수 두개가 선언되어 있는데 onItemSelected
는 클릭된 아이템에 대해 처리할 작업이며 onNothingSelected
는 아무것도 선택되지 않았을 때 처리할 작업입니다.
우리는 아이템이 선택되었을 때 텍스트뷰의 내용을 스피너 아이템 인덱스 번호와 내용을 가져와서 뿌려줄 것입니다.
아이템 내용을 가져오는 방법은 getItemAtPosition
메서드를 사용하여 position
을 매개변수로 받아 가져옵니다.
앞에서 실습한 내용은 메인 클래스 내부에서 임의로 배열을 만들어 적용한 것입니다. 외부에서 배열을 가져올 수도 있는데 values
디렉터리에 array.xml
을 만들어주고 다음과 같이 입력해줍니다.
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string-array name="fruits">
<item>과일 선택</item>
<item>귤</item>
<item>파인애플</item>
<item>키위</item>
<item>망고</item>
</string-array>
</resources>
<string-array>
태그 안에 아이템 태그로 스피너 아이템을 만들어주었습니다.
메인으로 돌아가서 fruits
변수로 선언된 배열만 바꿔주면 됩니다.
val fruits = resources.getStringArray(R.array.fruits)
// MainActivity.kt
class MainActivity : AppCompatActivity() {
val binding by lazy { ActivityMainBinding.inflate(layoutInflater) }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// setContentView(R.layout.activity_main)
setContentView(binding.root)
var data = listOf("select", "1월", "2월", "3월", "4월", "5월", "6월")
var adapter = ArrayAdapter<String>(this, R.layout.item_spinner, data)
binding.spinner.adapter = adapter
binding.spinner.onItemSelectedListener =object : AdapterView.OnItemSelectedListener {
override fun onItemSelected(p0: AdapterView<*>?, view: View?, pos: Int, p3: Long) {
binding.result.text = data.get(pos)
}
override fun onNothingSelected(p0: AdapterView<*>?) {
}
}
}
}
<!-- activity_main.xml -->
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:id="@+id/result"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="선택 결과"
android:textSize="30sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Spinner
android:id="@+id/spinner"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintVertical_bias="0.38"/>
</androidx.constraintlayout.widget.ConstraintLayout>
<!-- item_spinner.xml -->
<?xml version="1.0" encoding="utf-8"?>
<TextView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/tvItemSpinner"
android:layout_width="match_parent"
android:layout_height="45dp"
android:paddingTop="10dp"
android:paddingStart="30dp"
android:textColor="@android:color/black"
android:textSize="15sp"
android:paddingLeft="30dp"/>