안드로이드 앱에서 사용하는 메뉴 중 옵션 메뉴에 대해서 알아 봅니다메뉴 목록을 xml 파일로 구성하는 경우 Root 요소는 menu이고 자식 요소는 item 입니다그룹으로 묶는 경우에는 group 요소가 사용되는 경우도 있습니다.

 

메뉴 리소스 XML

요소

설명

<menu>

메뉴 항목의 컨테이너로 root node로 생성한다. <item> <group> 요소를 이용하여 메뉴를 구성 한다.

<item>

하나의 메뉴 항목을 나타낸다. 하위 메뉴를 생성하기 위해 <menu> 요소를 포함할 수 있다.

<group>

<item> 요소를 묶어 관리할 수 있는 투명 컨테이너.

 

<item> 속성

메뉴의 구현은 텍스트나 이미지로 하는데 모두 item 요소를 이용해서 표현 합니다. 여기에 설명한 속성 이외에도 많은 설정 값들이 있습니다.

속성

설명

android:id

item의 고유한 리소스 ID로 애플리케이션에서 이 ID를 통해 해당 항목을 인식 하고 사용 한다.

android:icon

메뉴에 이미지를 사용하는 경우 @drawable의 참조

android:title

메뉴에 사용할 문자열

android:showAsAction

앱 바에서 작업 항목으로 나타나는 시기와 방법을 지정

 

android:showAsAction

item의 속성 값에서 보다 다양한 설정을 하기 위해서는 showAsAction 속성을 이용 합니다.

속성

설명

ifRoom

앱 바에 표시할 공간이 있으면 표시하고 아니면 더보기 메뉴에 포함 된다.

withText

아이콘과 android:title에서 정의한 텍스트를 표시

never

앱 바에 공간이 있어도 배치하지 않고 더보기 메뉴에 포함

always

항상 앱 바에 배치. 다른 UI와 겹쳐질 수 있다.

collapseActionView

접기 기능을 사용할 수 있다.(API level 14)

 

 

Activity Method

xml로 구현된 메뉴 layoutActivity에서 다음 함수들을 이용하여 사용 합니다.

함수

설명

 onCreateOptionsMenu()

메뉴 객체를 생성하고 초기화 한다.

onPrepareOptionsMenu()

메뉴가 화면에 보여질 때 마다 호출되는 함수.

onOptionsItemSelected()

Menu Item을 선택 하였을 때 호출되어 Item 객체가 넘어온다.

onOptionsMenuClosed()

메뉴가 활성화 되어 있을 때 이전 버튼이나 다른 영역을 터치하여 메뉴를 닫을 때 호출되는 함수.

 

간단한 메뉴를 예제로 하나 만들어 보고 참고로 다음과 같은 메뉴도 만들어 봅니다.


android option menu



1. 프로젝트 생성


옵션 메뉴를 테스트 하기 위한 프로젝트를 생성 합니다.


android option menu


Empty Activity를 선택 하고


android option menu


대충 프로젝트 명을 입력하고 Finish 합니다.

 


2. 메뉴 xml 작성


프로젝트가 생성되면 메뉴를 위한 xml 파일을 생성해야 하는데 처음에는 res 폴더에 menu 폴더가 없기 때문에 xml 파일과 폴더를 동시에 생성 합니다.


android option menu


프로젝트에 마우스를 놓고 우측버튼을 클릭한 후 New > Android Resource File 을 선택 합니다.


android option menu


파일명은 자유롭게 생성하고 여기서는 menu_option로 하였습니다.  Resource TypeMenu를 선택해 줍니다이제 [OK] 버튼을 클릭하면 menu 폴더와 xml 파일이 생성 됩니다.


android option menu


menu_option.xml 파일이 생성 되었습니다.  


android option menu


처음에는 메뉴가 Design 보기로 나와 있는데 여기서 좌측에 있는 palette를 이용하여 메뉴를 구성 할 수도 있고 Code를 선택해서 텍스트로 입력을 해도 됩니다.


android option menu


간단하게 다음과 같이 메뉴 구성을 하였습니다.

 

<?xml version="1.0" encoding="utf-8"?>
<menu
xmlns:app="http://schemas.android.com/apk/res-auto"
   
xmlns:android="http://schemas.android.com/apk/res/android">
    <item
        
android:id="@+id/menu1"
       
android:title="menu1"
       
app:showAsAction="never">
    </item>
    <item
       
android:id="@+id/menu2"
       
android:title="menu2"
       
app:showAsAction="never">
    </item>
</menu>

android:showAsAction에서 빨간색으로 오류가 발생 한다면 마우스를 올려놓고


android option menu


Update to app:showAsAction을 선택해 주면 됩니다.

그리고 showAsAction 값을 always never로 설정 하는 경우의 모습은 아래와 같습니다.


showAsActionalways로 한 경우

android option menu


showAsAction never로 한 경우

android option menu


둘 중 하나를 선택해서 작성해 줍니다.

 


3. MainActivity.java 코딩


이제 메뉴 xml을 사용하여 Activity에 실제 코딩을 진행 하는데 두 개의 함수를 사용하기 위해 추가해 줍니다.


onCreateOptionsMenu()

android option menu


onOptionsItemSelected()

android option menu



실제 코딩 내용은 별거 없습니다.

 

@Override
public boolean onCreateOptionsMenu(Menu menu) {
    MenuInflater inflater = getMenuInflater()
;
   
inflater.inflate(R.menu.menu_option, menu);
    return true;
}

 

MenuInflaterxml 리소스를 메뉴 객체로 변환하는 작업을 합니다생성한 menu_option.xml을 읽어와 menu에 맵핑해 줍니다.

 

@Override
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
   
switch(item.getItemId()) {
       
case R.id.menu1:
            Toast.makeText(
this, "menu1 select", Toast.LENGTH_SHORT).show();
            break;
        case
R.id.menu2:
            Toast.makeText(
this, "menu2 select", Toast.LENGTH_SHORT).show();
            break;
   
}
   
return super.onOptionsItemSelected(item);
}

 

사용자가 선택한 메뉴 itemid를 반환해 주어 선택된 메뉴에 맞는 기능을 하도록 코딩해주는 부분 입니다

 


4. 실행


지금까지 만든 프로젝트를 실행해 봅니다.


android option menu


더보기 메뉴를 선택하면 작성한 메뉴가 나타나고 하나씩 클릭해 봅니다.


android option menu


menu1을 선택한 경우 토스트 문구가 나옵니다.


android option menu


menu2를 선택한 경우의 토스트 문구 입니다.



5. 전체 소스

전체 소스라고 해봐야 2개 밖에 없습니다.


 - menu_option.xml

<?xml version="1.0" encoding="utf-8"?>
<menu
xmlns:app="http://schemas.android.com/apk/res-auto"
   
xmlns:android="http://schemas.android.com/apk/res/android">
    <item
       
android:id="@+id/menu1"
       
android:title="menu1"
       
app:showAsAction="never">
    </item>
    <item
       
android:id="@+id/menu2"
       
android:title="menu2"
       
app:showAsAction="never">
    </item>
</menu>

  

- MainActivity.java

package copycoding.tistory.optionmenu;

import
androidx.annotation.NonNull;
import
androidx.appcompat.app.AppCompatActivity;

import
android.os.Bundle;
import
android.view.Menu;
import
android.view.MenuInflater;
import
android.view.MenuItem;
import
android.widget.Toast;

public class
MainActivity extends AppCompatActivity {

   
@Override
   
protected void onCreate(Bundle savedInstanceState) {
       
super.onCreate(savedInstanceState);
       
setContentView(R.layout.activity_main);
   
}

   
@Override
   
public boolean onCreateOptionsMenu(Menu menu) {
        MenuInflater inflater = getMenuInflater()
;
       
inflater.inflate(R.menu.menu_option, menu);
        return true;
   
}

   
@Override
   
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
       
switch(item.getItemId()) {
           
case R.id.menu1:
                Toast.makeText(
this, "menu1 select", Toast.LENGTH_SHORT).show();
                break;
            case
R.id.menu2:
                Toast.makeText(
this, "menu2 select", Toast.LENGTH_SHORT).show();
                break;
        
}
       
return super.onOptionsItemSelected(item);
   
}
}

 

 

6. 참고


앞에서 말한데로 groupsubmenu를 추가한 경우도 알아 봅니다단순히 메뉴용 xml만 수정하면 됩니다.

<?xml version="1.0" encoding="utf-8"?>
<menu
xmlns:app="http://schemas.android.com/apk/res-auto"
   
xmlns:android="http://schemas.android.com/apk/res/android">
    <item
       
android:id="@+id/menu1"
       
android:checkable="true"
       
android:title="menu1"
       
app:showAsAction="never">
    </item>
    <item
       
android:id="@+id/menu2"
       
android:enabled="false"
       
android:title="menu2"
       
app:showAsAction="never">
    </item>
   
<!-- menu group -->
   
<group android:id="@+id/group_delete">
        <item
android:id="@+id/menu_archive"
           
android:title="group menu1" />
        <item
android:id="@+id/menu_delete"
           
android:title="group menu2" />
    </group>
    <item
       
android:id="@+id/menu3"
       
android:title="menu3"
       
app:showAsAction="never">
       
<!-- "file" submenu -->
       
<menu>
            <item
               
android:id="@+id/submenu1"
               
android:checkable="true"
               
android:title="sub menu1"
               
app:showAsAction="never">
            </item>
            <item
               
android:id="@+id/submenu2"
               
android:enabled="false"
               
android:title="sub menu2"
               
app:showAsAction="never">
            </item>
        </menu>
    </item>
</menu>

 

실행 결과 입니다.


android option menu


- copy coding -


예전에 구글에서 제공하는 소스를 이용하여 구글 맵에 현재의 위치를 표시하는 앱을 만들었는데 최근에 다시 가보니 현재의 Android Studio 버전에 적용되는 library들로 source code가 변경이 되어 있네요오랜만에 다시 구현을 해보았습니다.

Android Studio 4.0을 사용해서 구현한 화면 입니다.


android place not authorized


 

모든 동작은 예전처럼 잘 되는데 이상하게 우측 [GET PLACE]를 누르면 아무런 동작도 하지 않습니다.  logcat을 살펴 보니 오류가 발생 하는 군요

 

21814-21814/copycoding.tistory.mylocation E/MainActivity: Exception: %s

 com.google.android.gms.common.api.ApiException: 9011: This API project is not authorized to use this API.

at com.google.android.libraries.places.internal.zzce.then(com.google.android.libraries.places:places@@2.3.0:6)

 

com.google.android.libraries.places를 사용할 권한이 없다는 건데 예전 소스코드에서 library의 변화가 있었나 봅니다권한이 없다고 하니 문제를 해결하러 API 관리 페이지에서 답을 찾아봅니다.


Google API 관리 사이트로 가보면 사용 설정된 API 목록을 볼 수 있습니다.


android place not authorized


현재 2개의 API를 사용하고 있다고 나오는 군요.

바로 아래쪽에 추가 API 목록이 있습니다.


android place not authorized


아무래도 여기에 있는 Places API가 원인인 것 같은데 선택을 합니다.


android place not authorized


[사용설정] 버튼을 클릭하면 권한을 받아오고 상단의 사용 설정된 API에 추가 됩니다.


android place not authorized


잘 추가가 되었군요.

이제 다시 앱을 실행해 볼까요?


android place not authorized


위치정보도 잘 가져오고 있습니다그 전에도 여기까지만 하고 손을 놓았는데 이번에는 꼭 뭔가를 만들어 보고 싶군요.


- copy coding -


안드로이드 프로젝트에서 설정 값들은 대부분 values/strings.xml에 등록해 놓는데 strings에 입력하지 않고 별도의 파일에 입력을 하지만 strings에 있는것 처럼 사용할 수 있는 방법입니다.





Properties() 함수를 이용하면 되는데 사용방법은

secure.properties 파일을 프로젝트 내 임의의 위치에 생성하고 내용에

 

MAPS_API_KEY=AIxxxxxxXXXXXXxxxxxxxXXXXXXXxxxxxxXxN_K8

 

이런 식으로 설정 값을 입력 합니다.

그리고 build.gradle(Module) 파일에 설정한 값을 읽어 올 수 있도록 Properties 함수를 추가 합니다.

 

defaultConfig {
   
applicationId
"copycoding.tistory.mylocation"
   
minSdkVersion 16
   
targetSdkVersion 29
   
versionCode 1
   
versionName "1.0"

   
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"

   
// Read the API key from ./secure.properties into R.string.maps_api_key
   
def secureProps = new Properties()
   
if (file("../secure.properties").exists()) {
        file(
"../secure.properties")?.withInputStream { secureProps.load(it) }
   
}
    resValue
"string", "maps_api_key", (secureProps.getProperty("MAPS_API_KEY") ?: "")
}

 

이렇게 하면 secure.properties 파일에 설정한 값을 가져옵니다파일 명과 파일 위치는 어디에 생성 했는가에 따라 수정을 하면 됩니다.

 

 

이제 AndroidManifest.xml 에서 설정 값을 사용하는 방법 입니다.

 

<meta-data
   
android:name="com.google.android.geo.API_KEY"
   
android:value="@string/maps_api_key" />

 

그냥 strings.xml에 입력해 놓은 것 처럼 동일한 방법으로 사용이 가능 합니다.


- copy coding -


구글에서 제공하는 안드로이드 용 지도 앱(단순히 정해준 위치의 지도를 보여주는 기능) 사용하기 예제를 이용하여 테스트 하고 사용 방법을 익혀 봅니다아주 단순하고 간단하게 만들어 볼 수 있습니다. 웹 주소는

https://developers.google.com/maps/documentation/android-sdk/start


이곳으로 여기에 가면 소스를 그대로 복사해서 테스트 해 볼 수 있습니다웹 페이지에 나와있는 순서대로 작업을 해봅니다물론 순서는 편리에 따라 바꾸어도 상관 없습니다.

 

Step 1. Download Android Studio


https://developer.android.com/studio/index.html


android google map sdk


자신의 운영체제에 맞는 패키지를 다운받아 설치 합니다. 현재 3.5버전 이고 다른 버전이라도 설치되어 있으면 패스. 저는 업데이트 했습니다.

 

Step 2. Install the Google Play services SDK


안드로이드 스튜디오 메뉴에서 팝업 창을 열고

Tools > SDK Manager > Appearance & Behavior > System Settings > Android SDK

우측에서 SDK Tools 탭을 선택 합니다.


android google map sdk


Google Play services 를 체크하여 설치 합니다기존에 설치를 했으면 패스.

 

Step 3. Create a Google Maps project


그냥 생각 나는 이름으로 프로젝트를 하나 생성 합니다.

Activityempty를 선택하고 생성 했습니다.


android google map sdk


Package nameAPI 사이트에 등록을 해야 하니 적당하게 입력 합니다저는 copycoding.tistory.mylocation으로 했습니다.


android google map sdk


Step 4. Get a Google Maps API key


구글에서 API를 사용할 수 있는 키를 생성 합니다. 그리고 package name을 이용하여 등록을 합니다.  기존에 생성한 API 키가 있다면 package name만 신규로 등록 합니다키 생성 방법은 검색하면 자세한 설명들이 바글바글 합니다저도 하나 설명한게 있습니다.

[안드로이드] google map 사용을 위한 API 생성(2018.11)


android google map sdk


Step 5. Hello Map! Take a look at the code


이제 프로젝트에 사이트에 있는 소스를 복사해서 붙여 넣기를 합니다사이트에는 두개의 소스가 있습니다.

 

activity_main.xml

<fragment xmlns:android="http://schemas.android.com/apk/res/android"
   
xmlns:tools="http://schemas.android.com/tools"
   
android:layout_width="match_parent"
   
android:layout_height="match_parent"
   
android:id="@+id/map"
   
tools:context=".MapsActivity"
   
android:name="com.google.android.gms.maps.SupportMapFragment" />

 

MainActivity.java

import android.os.Bundle;
import android.support.v4.app.FragmentActivity;
import com.google.android.gms.maps.CameraUpdateFactory;
import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.OnMapReadyCallback;
import com.google.android.gms.maps.SupportMapFragment;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.MarkerOptions;

public class MapsActivity extends FragmentActivity implements OnMapReadyCallback {

   
private GoogleMap mMap;

   
@Override
   
protected void onCreate(Bundle savedInstanceState) {
       
super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_maps);
       
SupportMapFragment mapFragment = (SupportMapFragment) getSupportFragmentManager()
                .findFragmentById(R.id.map);
        mapFragment.getMapAsync(
this);
    }

   
@Override
   
public void onMapReady(GoogleMap googleMap) {
        mMap = googleMap;

       
// Add a marker in Sydney, Australia, and move the camera.
       
LatLng sydney = new LatLng(-34, 151);
        mMap.addMarker(
new MarkerOptions().position(sydney).title("Marker in Sydney"));
        mMap.moveCamera(
CameraUpdateFactory.newLatLng(sydney));
    }
}

 

이 두가지로만 하면 오류가 발생 합니다.

사이트에 없지만 추가를 해야 하는 항목이 두개 있습니다간단해서 생략 했을 수도 있습니다.


AndroidManifest.xml

<meta-data
   
android:name="com.google.android.geo.API_KEY"
   
android:value="API 키 값" >
</
meta-data>

Step 4에서 생성한 API 키를 입력 합니다.

 

build.gradle(Module: app)

implementation 'com.google.android.gms:play-services-maps:10.2.0'

라이브러리도 추가 해줍니다.

 

Step 6. Connect an Android device


테스트를 진행하기 위해 가상 디바이스를 생성 하거나 실제 핸드폰을 연결 합니다


android google map sdk


Step 7. Build and run your app


이제 프로그램을 실행 하기만 하면 끝 입니다.


android google map sdk


GPS를 이용 하는게 아니라서 방구석 처밖혀 인터넷만 연결되면 테스트가 가능 합니다.

줌 기능을 추가하지 않아 그냥 세계지도를 보는 느낌이네요.

 

전체 소스

 

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
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"
   
tools:context=".MainActivity">

    <fragment
xmlns:android="http://schemas.android.com/apk/res/android"
       
xmlns:tools="http://schemas.android.com/tools"
       
android:layout_width="match_parent"
       
android:layout_height="match_parent"
       
android:id="@+id/map"
       
tools:context=".MapsActivity"
       
android:name="com.google.android.gms.maps.SupportMapFragment" />

</androidx.constraintlayout.widget.ConstraintLayout>

 

 

AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
   
package="copycoding.tistory.gpsmap">
    <application
       
android:allowBackup="true"
       
android:icon="@mipmap/ic_launcher"
       
android:label="@string/app_name"
       
android:roundIcon="@mipmap/ic_launcher_round"
       
android:supportsRtl="true"
       
android:theme="@style/AppTheme">

        <meta-data
           
android:name="com.google.android.geo.API_KEY"
           
android:value="API Key "/>
        <activity
android:name=".MainActivity">
            <intent-filter>
                <action
android:name="android.intent.action.MAIN" />

                <category
android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

 

 

build.gradle(Module: app)

apply plugin: 'com.android.application'

android {
    compileSdkVersion
29
   
buildToolsVersion '29.0.2'
   
defaultConfig {
        applicationId
"copycoding.tistory.gpsmap"
       
minSdkVersion 16
        
targetSdkVersion 29
       
versionCode 1
       
versionName "1.0"
       
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
   
}
    buildTypes {
        release {
           
minifyEnabled false
           
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
       
}
    }
}

dependencies {
    implementation fileTree(
dir: 'libs', include: ['*.jar'])
    implementation
'androidx.appcompat:appcompat:1.1.0'
   
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
   
implementation 'com.google.android.gms:play-services-maps:10.2.0'
   
testImplementation 'junit:junit:4.12'
   
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
   
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
}

 

 

 

 

MainActivity.java

package copycoding.tistory.mylocation;

import
androidx.appcompat.app.AppCompatActivity;
import
androidx.fragment.app.FragmentActivity;

import
android.os.Bundle;

import
com.google.android.gms.maps.CameraUpdateFactory;
import
com.google.android.gms.maps.GoogleMap;
import
com.google.android.gms.maps.OnMapReadyCallback;
import
com.google.android.gms.maps.SupportMapFragment;
import
com.google.android.gms.maps.model.LatLng;
import
com.google.android.gms.maps.model.MarkerOptions;

public class
MainActivity extends FragmentActivity implements OnMapReadyCallback {

   
private GoogleMap mMap;

   
@Override
   
protected void onCreate(Bundle savedInstanceState) {
       
super.onCreate(savedInstanceState);
       
setContentView(R.layout.activity_main);

       
SupportMapFragment mapFragment = (SupportMapFragment) getSupportFragmentManager()
                .findFragmentById(R.id.
map);
       
mapFragment.getMapAsync(this);
   
}

   
@Override
   
public void onMapReady(GoogleMap googleMap) {
       
mMap = googleMap;

       
// Add a marker in Sydney, Australia, and move the camera.
       
LatLng sydney = new LatLng(-34, 151);
       
mMap.addMarker(new MarkerOptions().position(sydney).title("Marker in Sydney"));
       
mMap.moveCamera(CameraUpdateFactory.newLatLng(sydney));
   
}
}

 

 - copy coding -


1···567891011···17

+ Recent posts