Android: 컨텍스트 메뉴와 스피너

Beautify.log·2022년 2월 5일
0

Android with Kotlin

목록 보기
5/17
post-thumbnail

컨텍스트 메뉴란?


주요 개념

컨텍스트 메뉴(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)
    }

}

스피너


HTML의 <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>에 해당하는 것을 각각의 객체로 만들어서 넣어줄 수 있기 때문에 텍스트 뿐만 아니라 이미지도 함께 넣어줄 수 있습니다.

Android의 Spinner

우선 스피너를 만들어주기 위해 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를 사용하여 선택된 아이템에 대해 처리해 줄 작업을 지정합니다.

이 때 AdapterViewListView, 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)

View를 객체화 하여 접근하기


펼처보기
// 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"/>
profile
tried ? drinkCoffee : keepGoing;

0개의 댓글