3. Create New Activities and Navigate Apps with Intents


ListView Item Select


우리는 목록 중에 하나를 선택해서 자세하게 보고 싶습니다

어떤 method를 이용해야 할까요?

1
2
3
4
5
6
7
8
9
10
1. setOnTouchListener(View.OnTouchListener l)

2. setOnClickListener
(AdapterView.OnClickListener l)

3. setOnItemSelectedListener
(AdapterView.OnItemSelectedListener l)

4. setOnItemClickListener
(AdapterView.OnItemClickListener l)

ListView

정답 : setOnItemClickListener


Toast


Android에서 Toast Message는 몇 초간 표시하는 간단한 메시지 입니다
간단한 메시지를 표시하기 위해서 유용합니다

1
2
3
4
5
6
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
//Toast 삽입
}
});

Toast Document

1
2
3
4
5
6
7
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
String forecast = mForecastAdapter.getItem(position);
Toast.makeText(getActivity(), forecast, Toast.LENGTH_SHORT).show();
}
});

Adapter에서 항목을 가져오기 위해서는 getItem을 사용합니다
Toast를 만들기만 하고 보여주고 만드는 거까지 진행을 합니다
한번 테스트를 통해서 보면 Toast Message가 잘 나오는 걸 확인 할 수 있습니다


Activity


왼쪽 화면과 같은 Activity를 만들어 봅시다
화면을 잘 살펴보세요 < 버튼이 있습니다
< 버튼은 항상 부모로 이동을 해야 합니다
우리가 만든 Application의 Activity를 트리로 표현하면 MainActivity가 현재 만드는 Activity의 부모 Activity가 됩니다
천천히 따라서 해봅시다


일단 파일을 정리 합시다 (여기서 없는 파일은 넘어가시면 됩니다)

  • DetailActivityFragment.java 파일을 삭제합니다
  • Content_detail.xml 삭제합니다
  • activity_detail.xml 수정합니다
1
2
3
4
5
6
7
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".DetailActivity" />
  • fragment_detail.xml 수정합니다
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context=".DetailActivity$PlaceholderFragment">

<TextView
android:id="@+id/detail_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />

</RelativeLayout>
  • DetailActivity.java 수정합니다
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v7.app.AppCompatActivity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

public class DetailActivity extends AppCompatActivity {

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_detail);
if (savedInstanceState == null) {
getSupportFragmentManager().beginTransaction()
.add(R.id.container, new PlaceholderFragment())
.commit();
}
}

@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.menu_detail, menu);
return true;
}

@Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();

if (id == R.id.action_settings) {
return true;
}

return super.onOptionsItemSelected(item);
}

public static class PlaceholderFragment extends Fragment {

public PlaceholderFragment() {
}

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {

View rootView = inflater.inflate(R.layout.fragment_detail, container, false);
return rootView;
}
}
}
  • mainfests.xml 수정합시다
1
2
3
4
5
6
7
8
9
<activity
android:name=".DetailActivity"
android:label="@string/title_activity_detail"
android:parentActivityName=".MainActivity"
android:theme="@style/AppTheme">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="com.study.sunshine.MainActivity" />
</activity>

화면을 전환해서 하는 경우를 StartActivity를 이용해서 진행합니다
Intent를 이용해서 포장을 해서 진행을 합니다
우리가 서로 Activity간의 통신이나 시스템과 통신을 위해서는 Intent를 이용해서 진행이 되는 것입니다

클래스 이름을 이용하여서 대상을 명백히 나타내는 Intent를 Explicit Intent라고 합니다
반대 개념으로 Implicit Intent가 있습니다

Intent를 쉽게 예시를 통해서 설명을 하겠습니다

Intent는 편지와 같다고 생각하면 됩니다
내가 전달하고자 하는 구성요소를 포함하고 있는 편지입니다
작은 공간이 있어서 내가 전달하고 싶은 데이터를 넣을 수 있습니다
그래서 확실한 수취인의 이름을 지정합니다
지금까지 설명한 내용은 Explicit Intent입니다

또 다른 Intent가 존재합니다 Implicit Intent입니다
아까와 마찬가지로 편지에 모든 구성요소가 포함 되어있고, 약간의 데이터도 포함이 되어있습니다
하진만 수취인이 적혀있지 않습니다
그러나 데이터에서 내가 지정한 액션을 수행할 능력이 있는 액티비티를 찾아서 진행이 됩니다
웹사이트를 본다던지, 전화를 건다던지, SMS를 보낸다던지 하는 역활을 하게 됩니다
참고 링크를 통해서 Native Application의 Implicit Intent를 확인하세요

참고


아까전에 했던 Toast Message를 대신해서 Intent를 이용해서 데이터를 전달해서 표시해 봅시다

explicit intent를 참고하여서 진행을 하겠습니다

1
2
3
4
5
6
7
8
9
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
String forecast = mForecastAdapter.getItem(position);
Intent intent = new Intent(getActivity(), DetailActivity.class);
intent.putExtra(Intent.EXTRA_TEXT, forecast);
startActivity(intent);
}
});

Intent에 Data를 전달하기 위해서는 putExtra를 사용하면 됩니다
key, value 구조로 이루어져있습니다

코드를 잘 작성하고 실행을 했다면 화면이 바뀌는걸 볼 수 있습니다

화면에 Hello World만 표시됩니다
우리는 간단한 코드 작성으로 방금 전에 전달받은 Data를 읽을 수 있습니다

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public static class PlaceholderFragment extends Fragment {

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {

View rootView = inflater.inflate(R.layout.fragment_detail, container, false);
Intent intent = getActivity().getIntent();
if (intent != null && intent.hasExtra(Intent.EXTRA_TEXT)) {
String forecast = intent.getStringExtra(Intent.EXTRA_TEXT);
((TextView)rootView.findViewById(R.id.detail_text)).setText(forecast);
}
return rootView;
}
}

UX(User Experience) - Setting

Setting 을 구성하는 방법은 여러가지가 있습니다
같이 Application을 만든 사람과 토론을 통해서 진행을 하세요

토론을 통해서 결정이 나는것은 아니지만 그래도 모든것을 Setting에 넣는 것은 하지 마세요!!!!

Design Setting

Setting에 무엇을 넣고 무엇을 표시하고 UI/UX 선택이 끝났으면 만들어봅시다

Setting GudeLine을 참고해 보세요

다음과 같은 화면 구성은 자동으로 할 수 있습니다


모든 설정 값은 Preferences로 저장됩니다
Preferences는 같은 구글 계정을 쓰는 Android Device와 공유 됩니다
그래서 Device를 바꿔도 유지가 되는 것입니다 당연히 Application이 삭제 되기 전까지 입니다

최근에는 자동으로 Preference값이 Google Cloud안에 데이터가 저장이 됩니다.
만약 저장을 하기 싫다면 AndroidMainfests.xml 파일에 android:allowBackup=”false” 을 추가하면 됩니다

SettingsActivity를 만들기 위해서 New Empty Activity를 클릭해서 SettingsActivity라고 만듭니다 Layout은 제외하고여

이제 코드로 SettingsActivity를 완성합시다

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public class SettingsActivity extends PreferenceActivity
implements Preference.OnPreferenceChangeListener {
@Override
public void onCreate(Bundle savedInstanceState) {
}

private void bindPreferenceSummaryToValue(Preference preference) {
preference.setOnPreferenceChangeListener(this);

onPreferenceChange(preference,
PreferenceManager
.getDefaultSharedPreferences(preference.getContext())
.getString(preference.getKey(), ""));
}

@Override
public boolean onPreferenceChange(Preference preference, Object value) {
String stringValue = value.toString();

if (preference instanceof ListPreference) {
ListPreference listPreference = (ListPreference) preference;
int prefIndex = listPreference.findIndexOfValue(stringValue);
if (prefIndex >= 0) {
preference.setSummary(listPreference.getEntries()[prefIndex]);
}
} else {
preference.setSummary(stringValue);
}
return true;
}
}

AndroidManifest.xml

1
2
3
4
5
6
7
8
<activity
android:name=".SettingsActivity"
android:label="@string/title_activity_settings"
android:parentActivityName=".MainActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="com.study.sunshine.MainActivity" />
</activity>

기본적인 Setting Activity에 대한 구성은 끝났습니다
그러나 우리가 원하는 내용의 Setting은 아닙니다
또 각 Activity에서 Menu에 있는 Settings를 눌러도 아무 작동을 안합니다
코드에 추가를 해서 작동하게 합시다

MainActivity, DetailActivity

1
2
3
4
5
6
7
8
9
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();

if (id == R.id.action_settings) {
startActivity(new Intent(this, SettingsActivity.class));
return true;
}
return super.onOptionsItemSelected(item);
}

아직 Setting Activity 화면을 설정을 안했습니다
일단 res 폴더에 xml 폴더를 만들고 진행합시다
pref_general.xml 를 만듭니다

pref_general.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">

<EditTextPreference
android:defaultValue="@string/pref_location_default"
android:inputType="text"
android:key="@string/pref_location_key"
android:singleLine="true"
android:title="@string/pref_location_label" />

</PreferenceScreen>

strings.xml

1
2
3
4
<!-- Preference Data-->
<string name="pref_location_default" translatable="false">Seoul</string>
<string name="pref_location_key" translatable="false">Location</string>
<string name="pref_location_label">Location</string>

우리가 방금 만든 perf_general.xml을 SettingsActivity에 연결합시다

SettingActivity.java

1
2
3
4
5
6
7
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
addPreferencesFromResource(R.xml.pref_general);
bindPreferenceSummaryToValue(
findPreference(getString(R.string.pref_location_key)));
}

실제로 Setting을 이용해서 Data를 바꾸면 앱을 다시 실행해도 남아있습니다.
이제 바뀐 Data를 가지고 날씨를 가져오도록 합시다

ForecastFragment.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
if (id == R.id.action_refresh) {
FetchWeatherTask fetchWeatherTask = new FetchWeatherTask();
SharedPreferences sharedPreferences =
PreferenceManager.getDefaultSharedPreferences(getActivity());
String location = sharedPreferences.getString(
getString(R.string.pref_location_key),
getString(R.string.pref_location_default));
fetchWeatherTask.execute(location);
return true;
}
return super.onOptionsItemSelected(item);
}

바뀐 위치에 맞게 데이터를 가져옵니다
잘못된 데이터를 넣으면 잘못된 데이터를 가져오겠지요

이제 매번 Refresh 버튼 클릭하기 귀찮습니다
이걸 바꿔 봅시다. 그리고 약간의 Refactoring 합시다

방금전 코드를 메서드를 만들어 봅시다

1
2
3
4
5
6
7
8
9
private void updateWeather() {
FetchWeatherTask fetchWeatherTask = new FetchWeatherTask();
SharedPreferences sharedPreferences =
PreferenceManager.getDefaultSharedPreferences(getActivity());
String location = sharedPreferences.getString(
getString(R.string.pref_location_key),
getString(R.string.pref_location_default));
fetchWeatherTask.execute(location);
}

그리고 onStart method를 Override 합시다
onOptionsItemSelected method는 Refactoring 합시다

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Override
public void onStart() {
super.onStart();
updateWeather();
}

@Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
if (id == R.id.action_refresh) {
updateWeather();
return true;
}
return super.onOptionsItemSelected(item);
}

이제 MainActivity가 보여질때마다 날씨를 직접 가져오기때문에
Fake Data를 지워도 되겠네요

1
2
3
4
5
6
7
8
9
10
11
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
mForecastAdapter = new ArrayAdapter<String>(
getActivity(),
R.layout.list_item_forecast,
R.id.list_item_forecast_textview,
new ArrayList<String>()
);
// 생략
}

이제 쓸만한 Application이 되었습니다
아직 Setting이 끝난것이 아닙니다!!

아직 하나더 Setting 할 것이 남았습니다
temperature units를 설정해야합니다

strings.xml

1
2
3
4
5
6
7
<!-- Preference UNITS Data-->
<string name="pref_units_label">Temperature Units</string>
<string name="pref_units_label_metric">Metric</string>
<string name="pref_units_label_imperial">Imperial</string>
<string name="pref_units_key" translatable="false">units</string>
<string name="pref_units_metric" translatable="false">metric</string>
<string name="pref_units_imperial" translatable="false">imperial</string>

value 폴더 안에 arrays.xml 을 만드세요

1
2
3
4
5
6
7
8
9
10
11
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string-array name="pref_units_options">
<item>@string/pref_units_label_metric</item>
<item>@string/pref_units_label_imperial</item>
</string-array>
<string-array name="pref_units_values">
<item>@string/pref_units_metric</item>
<item>@string/pref_units_imperial</item>
</string-array>
</resources>

pref_general.xml

1
2
3
4
5
6
<ListPreference
android:defaultValue="@string/pref_units_metric"
android:entries="@array/pref_units_options"
android:entryValues="@array/pref_units_values"
android:key="@string/pref_units_key"
android:title="@string/pref_units_label" />

SettingActivity.java

1
2
3
4
5
6
7
8
9
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
addPreferencesFromResource(R.xml.pref_general);
bindPreferenceSummaryToValue(
findPreference(getString(R.string.pref_location_key)));
bindPreferenceSummaryToValue(
findPreference(getString(R.string.pref_units_key)));
}

ForecastFragment.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
private String formatHighLows(double high, double low) {
SharedPreferences sharedPreferences =
PreferenceManager.getDefaultSharedPreferences(getActivity());
String unitType = sharedPreferences.getString(
getString(R.string.pref_units_key),
getString(R.string.pref_units_metric));

if (unitType.equals(getString(R.string.pref_units_imperial))) {
high = (high * 1.8) + 32;
low = (low * 1.8) + 32;
} else if (!unitType.equals(getString(R.string.pref_units_metric))) {
Log.d(LOG_TAG, "Unit type not found : " + unitType);
}

long roundedHigh = Math.round(high);
long roundedLow = Math.round(low);

String highLowStr = roundedHigh + "/" + roundedLow;
return highLowStr;
}

Implicit Intent

게으른 프로그래머는 더 적은 실수를 한다

지도에서 내가 날씨를 얻는곳을 보고 싶어요
일단 메뉴를 추가해 봅시다

menu_main.xml

1
2
3
4
<item
android:id="@+id/action_map"
android:title="@string/action_map"
app:showAsAction="never" />

strings.xml

1
<string name="action_map은" translatable="false">Map Location</string>

MainActivity.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
@Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();

if (id == R.id.action_settings) {
startActivity(new Intent(this, SettingsActivity.class));
return true;
}

if (id == R.id.action_map) {
openPreferredLocationInMap();
return true;
}
return super.onOptionsItemSelected(item);
}

private void openPreferredLocationInMap() {
SharedPreferences sharedPreferences =
PreferenceManager.getDefaultSharedPreferences(this);

String location = sharedPreferences.getString(
getString(R.string.pref_location_key),
getString(R.string.pref_location_default));

Uri geoLocation = Uri.parse("geo:0,0?").buildUpon()
.appendQueryParameter("q", location)
.build();

Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(geoLocation);

if(intent.resolveActivity(getPackageManager()) != null){
startActivity(intent);
}else {
Log.d(LOG_TAG, "Couldn't call" + location + ", no receiving apps installed!");
}
}

여기서 우리는 궁금합니다 - 어떻게 Map Application이 실행되는지

이것은 다른 앱의 AndroidManifest.xml의 정답이 있습니다

1
2
3
4
<intent-filter>
<action:name="android.intent.action.View"/>
<data android:scheme="geo"/>
</intent-filter>

geo라는 Data scheme를 처리 하게 됩니다

만약 Data scheme를 처리할 수 있는 Application이 여러개라면 사용자가 선택할 수 있도록 팝업창을 보여주게 됩니다


Share action provider

Share action provider 라는 것이 추가 되었습니다
뭐라고 설명해야 하지… 그 ActionBar Icon 중에서 먼가 공유할때 쓰는 버튼이 있습니다

그것을 우리도 추가를 해봅시다
날씨를 친구들에게도 알려줍시다

기본적으로 Share action provider를 이용하기 위해서는 문자열부터 추가하는것으로 시작 합니다

1
<string name="action_share">Share</string>

menu_detail_fragment라는 이름의 메뉴를 추가 하세요

1
2
3
4
5
6
7
8
9
<?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/action_share"
android:title="@string/action_share"
app:actionProviderClass="android.support.v7.widget.ShareActionProvider"
app:showAsAction="always" />
</menu>

PlaceholderFragment을 DetailFragment로 바꿉시다
class name은 막 바꾸면 안되기 때문에 그림에서 설명한 방법으로 변경합니다

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public static class DetailFragment extends Fragment {
private static final String LOG_TAG = DetailActivity.class.getSimpleName();
private static final String FORECAST_SHARE_HASHTAG = " #SunshineApp";
private String mForecastStr;

public DetailFragment() {
}

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_detail, container, false);
Intent intent = getActivity().getIntent();
if (intent != null && intent.hasExtra(Intent.EXTRA_TEXT)) {
mForecastStr = intent.getStringExtra(Intent.EXTRA_TEXT);
((TextView)rootView.findViewById(R.id.detail_text)).setText(mForecastStr);
}
return rootView;
}
}

Share intent를 만듭시다. ACTION_SEND를 이용해서 처리합니다

1
2
3
4
5
6
7
8
private Intent createShareForecastIntent() {
Intent shareIntent = new Intent(Intent.ACTION_SEND);
shareIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
shareIntent.setType("text/plain");
shareIntent.putExtra(Intent.EXTRA_TEXT,
mForecastStr + FORECAST_SHARE_HASHTAG);
return shareIntent;
}

이제 메뉴를 만들어야 합니다

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public DetailFragment() {
setHasOptionsMenu(true);
}

@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.menu_detail_fragment, menu);

MenuItem menuItem = menu.findItem(R.id.action_share);

ShareActionProvider mShareActionProvider =
(ShareActionProvider) MenuItemCompat.getActionProvider(menuItem);

if (mShareActionProvider != null) {
mShareActionProvider.setShareIntent(CreateShareForecastIntent());
} else {
Log.d(LOG_TAG, "Share Action Provider is null?");
}
super.onCreateOptionsMenu(menu, inflater);
}

이제 우리는 데이터도 전송 할 수 있습니다.


여기서 추가로 더 배워봅시다

Brodcast Intents 입니다

Android Device에서는 여러가지 방송메세지가 돌아 다닙니다
배터리가 충전이 다되었다. 문자가 왔다. 전화가 왔다. 다운로드가 완료되었다. 등등 정말 많은 메세지가 돌아다닙니다

이런 메세지는 intent filter를 가진 Brodcast Reciver가 있는 모든 Application에 전송이 됩니다

Intent Filter는 2가지 방법으로 등록 할 수 있습니다.

XML - Static

1
2
3
4
5
<receiver android:name".MyReceiver">
<intent-filter>
<action android:name="me.hoyuo.testapp.SunShine" />
</intent-filter>
</receiver>

JAVA - Dynamic

1
2
3
IntentFilter intentFilter =
new IntentFilter("me.hoyuo.testapp.SunShine");
registerReceiver(myReceiver, intentFilter);

두가지 방법 모두 Brodcast Recevier를 등록하는 것입니다
두가지 방법에는 미세한 차이가 있습니다


복습은 필수

  • ListView Item Selected
  • Toast
  • Activity
  • Setting
  • Intent
  • Share action provider
  • Brodcast