Androidのお勉強 第一回 環境構築と画面遷移
勉強会ネタは以下の順序でやっていこうと思います。
- 第一回 環境構築、AndroidPJの解説、サンプルの作成、イベントの実装、画面遷移(Intent)
- 第二回 ListViewと独自Adapterについて
- 第三回 フラグメントの使用
- 第四回 復元処理の重要性について
- 第五回 非同期通信の重要性、ANRと戦う
- 第六回 独自レイアウトを作ろう。Viewの拡張の注意点。OnMeasureやViewのライフサイクルについて学ぶ
- 第七回 API x SQLiteDB連動アプリを作成してみよう
今回取り扱う環境は以下
- Java ⇒ 1.7以上なら別になんでも。
- Android Studio ⇒ 1.2.2
環境構築
Android Studioをインストールします。
Android Studioと一緒にAndroidSDKがインストールされます。
以下からまとめて入れましょう。
⇒http://developer.android.com/sdk/index.html
※参考リンク
基本は上記の参考リンクの通りに進めれば問題ありません。
ただ、インストール先がデフォルトのProgram Files下だと色々面倒なので、
C\Android下で十分かと思います。StudioもSDKもAndroid下にそれぞれ入れればすっきりかなと。
以下のキャプチャのようなイメージです。
新規PJ作成
Stuioがinstallできたら早速立ち上げましょう。
32bitなら、
C:\Android\AndroidStudio\bin\studio.exe
で、64bitなら
C:\Android\AndroidStudio\bin\studio64.exe
を立ち上げてください。
立ち上がったら、SDKManagerを立ち上げ、AndroidSDKや、GooglePlayServiceをinstallします。
上記のキャプチャの赤枠にある項目を全てinstallしてください。
既にあるって人は飛ばしても構いません。
そして新規PJを作成します。以下のように進めてください。
サポートするAndroidOSは4.1からとします。
また、今回は1からActivity(画面)をくみ上げるお勉強であるため、「Add NoActivity」を選択します。
作成したら以下のキャプチャの右ペインのみが表示されると思いますが、
左側のメニュー(赤枠)をクリックすれば、作成されたPJの構成が表示されます。
まずはこの構成について触れていきます。
①appフォルダ…
アプリの挙動を作成する、具体的なソースコードやリソースはこちらに配置していきます。
②manifests
アプリの設定情報を格納します。
中にはAndroidManifest.xmlというxml設定ファイルがありますね。
ここにあなたが作成するアプリがどんな画面を持つのか??どんな権限を持たせるのか??(電話帳にアクセスするアプリだよ。とか、インターネットアクセスが発生する機能を持ってるよー、緯度経度をGPSから位置情報取得するとか諸々)を記載します。
作成段階のManifestを見てみましょう。
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="jp.co.kenshu">
<application android:allowBackup="true"
android:label="@string/app_name"
android:icon="@mipmap/ic_launcher"
android:theme="@style/AppTheme">
</application>
</manifest>
android:label="@string/app_name"
アプリ一覧で使用されるようなアプリの表示名です。
android:icon="@mipmap/ic_launcher"
アプリのデフォルトアイコンを指定します。
画像ファイルはデバイスの解像度に応じて画像を用意しています。
別の回で詳しく取り扱いますが、「dp」という単位でandroidでは管理しています。
今回デフォルトで指定されている「ic_launcher」は
- hdpi
- mdpi
- xhdpi
- xxhdpi
の四解像度層ごとに応じたpng画像が配置されています。
このアプリをインストールした端末に応じた解像度の画像が自動で使用されることになります。
android:allowBackup
バックアップエージェントによるバックアップを有効にするかどうかを設定。
falseだと、アプリのバックアップが行われない。
android:theme="@style/AppTheme"
文字通り、このアプリケーションのテーマを設定できる。
指定しているテーマはstyles.xmlに記載した以下のテーマとなる。
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
</style>
この「AppTheme」というものがアプリケーションに設定され、中身としては
parent="Theme.AppCompat.Light.DarkActionBar"
が反映されている。
parent
は継承を表しており、AppThemeはTheme.AppCompat.Light.DarkActionBarテーマを継承していることになる。
要はStyleはname単位で再利用可能となるわけだ。
SDKで用意されている、代表的なThemeは以下のようなものがある。
- android:Theme.Holo
- android:Theme.WithActionBar
- android:Theme.Black.NoTitleBar
などなど、結構きりがない。用途に応じて使い分けてみてください。
Theme.Lightとかは、TitleBarがデフォルトで着いたテーマになっていたため、
android:Theme.Light.NoTitleBar
にしたりして、アプリ全体のテーマとしてタイトルバーを表示させない。とかはよく使いました。
最近はActionBarが推奨されているから、
今回みたいに
Theme.AppCompat.Light.DarkActionBar
ActionBarがついたテーマがデフォルトで設定されているんですかね。
③Java
その名の通り。Javaファイルを配置するところです。
ここにActivityやら、Fragment、AsynchTaskを配置します。
今はPJ作成時に作成した、デフォルトパッケージが作成されていますね。
ちなみに、このデフォルトパッケージがGooglePlayに公開した際のこのアプリの識別子?になります。
外部アプリ連携の時などにも用いられるので、意外と重要です。
④res
リソースを配置します。
主に登場するリソースは以下
- layout(Androidの画面xml)
- dimens(dpやsp等)
- styles(themeだけでなく、背景画像や文字の大きさなどをまとまった形で指定し、それをlayoutに設定できる)
- selector(ボタン等の見せ方を独自に定義できる。角丸とか、シャドウとか。グラデーションとか)
- drawable(画像や上記のselectorもここに属する)
- values(定数等を定義する)
⑤Gradle Scripts
Gradleを語るにはここでは語りつくせないほどの長さになってしまいます。
なので簡単に。
- ソースコードをアプリとして実行するために、ビルドを行ってくれます。
- アプリに必要な外部ライブラリの管理(取得/設定)をしてくれます。
- apkファイルを作成し、デバイスにinstallしてくれます。
最初に触るであろうスクリプトはおそらく、
build.gradle(Modue: app)
これくらいだと思います。
例えば、GoogleMapを使いたいだとか、GoogleAnalyticsを使いたいだとか、
ButterKnifeやIcepickなどの外部ライブラリもGithubから落として使いたいだとかがよく使われる例でしょうかね。
あとは本格的な運用になりますが、環境別にAPIのむき先を変えたいだとか言った場合も、ここをいじれば可能になります。
AnalyticsのトラッキングIDを開発/検証/本番で分けたい!!って時も便利ですね。
画面を作成してみる
ではさっそく画面を作成して表示してみましょう
簡単な手順としては、以下の流れで行います。
- layoutをxmlでお絵かきする
- 終え書きしたxmlをActivityと紐づけて表示する
- エミュレータを作成する
- 作成したエミュレータを起動する
- 起動したエミュレータに作成したアプリをインストールする(apkをDLする)
layout.xmlの作成
まずは「res」ディレクトリ下に以下のように、layoutフォルダを作成してください。
以下の命名規則でつけます。
⇒【activity or fragment or adapter】_【画面名 or 機能名】.xml
【余談】
リソースの命名規則について…
実はAndroidではres下にサブフォルダを切ることができません。
そのため、Activityで使用するxmlなのか、Adapterで使用するxmlなのか、Fragmentで使用するxmlなのかがごちゃまぜになってすぐに見分けがつきません。
ですから、ファイル名に厳密な命名規則を設けることで、この煩雑さを防ぐのです。
命名規則って軽んじられる傾向にありますが、守らないことで将来保守・運用するときに調査時間に無駄な工数がかかることを意識してもらえれば、守ってもらえると思えます。
なので、activity_main.xmlをlayout下に作成してください。以下みたいな感じで
Layut resource fileを選択したら、
以下のように作成します。
このとき、上記キャプチャのように「Source set:」を尋ねられます。
ここは「main」にしといてください。こいつも便利な機構で、「debug(開発用)」と「release(本番用)」とでリソースを分けることができるんですね。
valuesとかでも同じことができるんで、開発中/テスト中とかは本番サーバに向けさせたくないって時も、使い分けることができます。
あとは、Android Studioの方で、「Build Variant」にdebugかreleaseか選択するだけで環境を分けられます。
※今はデフォルトで「debug」になっています。
作成したxmlファイルは以下のような開き方をしていますか??
デフォルトで赤枠の「design」タブになっていますね。
ここはGUIツールでどのような画面を作成するか??レイアウトをドラッグ&ドロップ等のマウス操作で組むことができる編集モードになります。
今回はレイアウトの構成を知るため、このGUIモードは極力使わない方向でいきます!!
赤枠にある、「Text」モードを選択してください。
するとこんなUIになります。
これは、今表示されているレイアウトのxml構成とそのプレビューが右ペインに表示されるモードになります。
xmlの構成を学んでほしいため、あえてこのTextモードでレイアウトを組んでいってもらいます。
早速、以下のようにxmlを修正してください。
※あえてLintエラーを表示させるようにしてます。後述して修正しますので、警告は無視ですよ!
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/title_text"
android:paddingLeft="5dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/name_text"
android:hint="名前を書いてね"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<Button
android:id="@+id/go_button"
android:text="GO"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</LinearLayout>
</LinearLayout>
今回は以下のレイアウト構成をとってます。
- Root要素をViewGroupのLinearLayouで大きく囲う
- RootのViewGroupの中にorientationがhorizontalなViewGroupにしたLinearLayouで更に囲う
用語に関しては、説明を以下の箇条書きに書かせてもらいます。
###ViewGroupについて…
基本的に公式を見てください。
公式(developer)
▼原文
Class Overview
A ViewGroup is a special view that can contain other views (called children.) The view group is the base class for layouts and views containers. This class also defines the ViewGroup.LayoutParams class which serves as the base class for layouts parameters.
Also see ViewGroup.LayoutParams for layout attributes.
Developer Guides
For more information about creating user interface layouts, read the XML Layouts developer guide.
Here is a complete implementation of a custom ViewGroup that implements a simple FrameLayout along with the ability to stack children in left and right gutters.
▼翻訳
クラスの概要
ViewGroupは他のビュー含めることができる特別なビューである(と呼ばれる子供たちを。 )ビュー·グループは、レイアウトとビューコンテナの基本クラスです。このクラスは、レイアウトパラメータの基本クラスとして機能ViewGroup.LayoutParamsクラスを定義します。
また、レイアウト属性のViewGroup.LayoutParamsを参照してください。
開発者ガイド
ユーザインターフェイスのレイアウトの作成の詳細については、 XMLレイアウトの開発者ガイドをお読みください。
ここでは、左右の側溝の子供たちをスタックする能力と一緒にシンプルでframeLayoutを実装するカスタムViewGroupの完全な実装です。
ようは内部にViewを複数持てる親Viewをさします。
レイアウト作成の詳細に関しては以下を読んでよって公式も言っているので、読んでみてください。
⇒http://developer.android.com/guide/topics/ui/declaring-layout.html
###android:orientationについて
ViewGroupが子供Viewを内包するときのViewの追加向きを指定できます。
・vertical…縦方向に子供Viewが追加されていく
・horizontal…横方向に子供Viewが追加されていく
RootViewでは「vertical」を指定しているため、縦方向に追加されていきます。
二番目のViewGroupでは「horizontal」が指定されているため、横方向に追加されます。
なので、TextViewとButtonが横に並んでいるわけですね。
android:layout_width / android:layout_heightについて
レイアウトの横幅と縦幅の指定です。
・match_parent…親画面いっぱいに指定します。
例えば、親画面が幅を300dpに固定していたとしたら、子供Viewの幅もMAXで300dpまでになります。
反対に親がwrap_contentで、子供が400dp幅にしていたら、親Viewの幅は子Viewの幅に準拠することになります。
・wrap_content…これを指定したViewの幅や高さは、そのViewに内包されるコンテンツの幅や高さに合わせて決まるという動きをします。つまり、中にあるView、もしくはTextViewなら、指定された文字列の幅や長さに応じて幅・高さが決まるということになります。
・fill_parent…昔match_parentの前に使われていたパラメタです。現在非推奨なので、古いWEBサイトとかから情報を取得してきた時は気を付けてください。絶対に使うことはありません。素直にmatch_parentを指定してください。
android:idについて
Viewを一意に識別するために指定します。
これを指定することで、ActivityからID名でViewをオブジェクトとして取得することができます。
例えば、今回はTextViewに以下のような指定をしています。
android:id="@+id/name_text"
これで「IDをname_textという名前で新規で登録します」って動きをします。
「@+id」を指定すると、アプリ内で使われてなければR.javaというリソースID管理クラスに新規に作成・登録し、「@id」で、既存のIDを参照しにいくって動きをとります。
Activityの作成
画面と対になる、Activityを作成していきましょう。
前回作成したレイアウトはお絵かきをしただけで、画面にはまだ表示されません。
画面はあくまで、Activityと対になっているっため、このActivityにレイアウトを認識させる必要があります。
ここで行う概要手順は以下
- java下にMainActivity.javaを作成する
- 作成したActivityをAndroidManifest.xmlに登録する
以上です。
MainActivity.javaを作成する
以下のように新規Javaクラスを作成してください。
OKを押せばjp.co.kenshu下にMainActivity.javaが作成されます。
以下のように中身を書き換えてください。
package jp.co.kenshu;
import android.app.Activity;
import android.os.Bundle;
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
TextView titleText = (TextView) findViewById(R.id.title_text);
titleText.setText(getString(R.string.app_name));
}
}
以下解説です。
extends Activity
Activityを継承しています。Activityを作成するなら、この記述が必要になります。
@Override
protected void onCreate(Bundle savedInstanceState) {
画面が作成される時に呼ばれるコールバックメソッドです。
一般的にこのメソッド内で、どのようなUIを構成するのか等の初期化処理を記述します。
Activityにはライフサイクルという概念があります。
今、そのActivity(画面)がどのような状態なのか?に応じて、呼ばれるコールバックメソッドが用意されています。
開発者は、必要に応じて、以下のようなコールバックメソッドを使い分けなくてはなりません。
例えば、アプリがホームボタンでバックグラウンドに退避された際に、復元用の保存処理を行いたい等の場合は、onSavedInstanceStateをコールするなど、実はやらなきゃいけない処理・お約束・お作法が山ほどあるのです。
ですが、今回は簡単なアプリをささっと作ってみるだけですので、詳しくは触れません。
他の回で詳細に扱います。
必読!!http://developer.android.com/reference/android/app/Activity.html
上のライフサイクル表の中で、簡単に解説を載せておきたいのが、
onResumeです。Activityは画面遷移をする際に、別の新たなActivityを呼び出して画面遷移します。
例えば、A画面からB画面に遷移する時は、A画面のActivityがonPause ⇒ onStopされ、B画面のonCreateが走り、最終的にActivityRunningとなり、画面に表示されます。
注目して欲しいこととして、画面にActivityが表示されるケースは複数あれど、必ず呼ばれる処理があるということです。
それがonResumeです。バックキーボタンでctivityを戻ったりしても、必ず呼びたい処理があるなら、それはonResumeで処理するなどが考えられます。
ここで、「じゃ、全部onResumeでやれば良いじゃん」と考えるかもしれませんが、それは安易です。
Activityは作成されたインスタンスを使いまわします。そのため、バックボタンで戻って表示されたA画面は、再度インスタンスを構築して、画面設定をするのではなく、前回作成したインスタンスを再度デバイス画面に表示しているだけなのです。
Activityにはタスクとスタックという考え方あります。詳しくは以下の公式を読んで欲しいのですが、
こんな風にActivityはスタックに積まれ、随時フォアグラウンド(最前面)に移動するという動きをとります。
毎回onResumeでレイアウトを作成し直していては、コストのかかるインスタンス作業とレイアウト作業が毎回走ってしまい、非効率です。
基のソースの解説に戻ります。
今回は
setContentView(R.layout.activity_main);
により、先ほど作成したレイアウトをこの画面に紐づけています。
これだけで、画面にはxmlで記述したお絵かきが表示されます。
TextView titleText = (TextView) findViewById(R.id.title_text);
titleText.setText(getString(R.string.app_name));
ここでは、レイアウトのactivity_main.xmlに記載されている
<TextView
android:id="@+id/title_text"
android:paddingLeft="5dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
この「android:id="@+id/title_text"」をひっかけて、TextViewインスタンスを取得しています。
あとは取得したインスタンスに対して、APIを使用し、テキストを設定しているのです。
getString(R.string.app_name))
この記述はActivityの親クラスである、Contexクラスが所有しているgetStringメソッドを使用しています。
AndridではリソースをIDという形式でRクラスというjavaクラスで管理する機構を取っています。
R.javaを見てみてください。
一部しか抜粋しませんが、以下のようになってます。
public static final int media_actions=0x7f0c0055;
public static final int middle=0x7f0c0015;
public static final int multiply=0x7f0c001e;
public static final int name_text=0x7f0c0050;
public static final int never=0x7f0c0019;
public static final int none=0x7f0c000e;
public static final int normal=0x7f0c000a;
public static final int parentPanel=0x7f0c002d;
public static final int progress_circular=0x7f0c0005;
「public static final int name_text=0x7f0c0050;」は、先ほどxmlに記載した
<TextView
android:id="@+id/name_text"
android:hint="名前を書いてね"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
この「android:id="@+id/name_text"」gastaticなintで登録されています。
Activityからは、このR.javaからリソースIDをひっかけて、TextViewオブジェクトインスタンスを取得しているのです。
作成したActivityをAndroidManifest.xmlに登録する
Activityを作成したら、AndroidManifest.xmlに登録しないと、アプリは起動しません。
AndroidManifestを以下のように修正してください。
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="jp.co.kenshu">
<application android:allowBackup="true"
android:label="@string/app_name"
android:icon="@mipmap/ic_launcher"
android:theme="@style/AppTheme">
+ <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>
+が今回追記した記述になります。
<activity android:name=".MainActivity" >
この記述でActivityを登録しています。
「.」はデフォルトパッケージを意味します。なので、今回は「jp.co.kenshu」になります。
ここにはフルパッケージで記述しても良いので、「jp.co.kenshu。MainActivity」としても問題ありません。
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
intent-filterは起動時の挙動を設定できる属性です。
<action android:name="android.intent.action.MAIN"/>
この記述が、このアプリのメイン画面、つまりアプリ起動時の入口となる画面となります。
<category android:name="android.intent.category.LAUNCHER" />
この指定は、このAcitivityが呼ばれた際に、デバイスのホーム画面にランチャーアイコンを作成するという挙動をとります。
この指定を今後追加するActivityにバカみたいに何も考えずに指定するとあなたのデバイスにどんどんアプリのアイコンが作成されていきますのでご注意を。
動かしてみる
アプリの作成はいったんこれで完了です。
さっそく動かすために、エミュレータを起動して、アプリをインストールしてみましょう。
Androidでは「AVD Manager」と呼ばれるツールを使用して、仮想デバイス = エミュレータを作成できます。作成したエミュレータにアプリをインストールする訳ですが、これが実は中々重くてストレスなんです。
なので、今回はこれを使わず、GenyMotionと呼ばれるVirtualBoxを使用した高速エミュレータを使用します!!
Genymotionでエミュレータを起動する
ではGenyMotionをインストールしてみましょう。
以下のサイトにアクセスしてください。
ここがわかり安くて、そのまま使えますんでここからの作業はエミュレータが立ち上がるまでここを参考に作業を進めてください。
実行してみる(エミュレータにapkのインストール)
エミュレータを起動したなら、あとはアプリを起動するだけです。
「Shift + F10」をAndroidStudio上で実行してみましょう。
これで実行してくれます。(画面上部の実行ボタンのショートカットです。)
ちなみに、ブレークポイントを張って、アプリをステップ実行したい場合は、
ブレークポイントを張ったうえで、「Shift + F9」で実行するだけです。
コンパイル・buildが完了したら、実行するデバイスを選択する画面が以下のように表示されます。
選択してOKを押して実行しましょう!!
こんな画面が表示されれば成功です!!
Lintエラーを潰す!!最適なレイアウト構成に保つ!!
ここまでで簡単な画面を表示するアプリが作成できました!!
しかし、実はこのままだと、「動けば良い」という最低な悪プログラマになってしまいます。
というのも、実は今のレイアウト構成だとGoogleの推奨するレイアウト構成に則っていないんですね。
Androidの最適なレイアウト構成を教えてくれるツールにLintというツールがあります。
「Analyze ⇒ Inspect Code」を選択し、
以下の画面でOKを押してください。
これでLintが走ります。
今回作成したレイアウトで改善しなさいよってものを教えてくれます。便利ですね。
この指摘に則って修正すれば、Google推奨のレイアウトが作成できる訳です!!
Badプラクティスもここで防げます。かならずLintを実行し、可能な限り修正しましょう。
面倒がればそれだけ低評価なコンテンツになっちゃいますよ!!
Lintの結果は以下のように画面下部に表示されます。
ここで表示されている、「Android Lint」を全て言われた通りに潰しましょう!!
- HardCoded Text
- Padding and margin symmetry
- Using left/right instead of start/end attributes
今回はこの三つを潰せと言われてますね。一個一個対応していきましょう。
HardCoded Text
その名の通り、ハードコーディングすんな!ってことです。
どこのファイルのどこが悪くて、何をどう直せばいいのかは、大体以下のように表示されていますので、英語を調べるなどして言われた通りに対応しましょう。
今回は
<TextView
android:id="@+id/name_text"
android:hint="名前を書いてね"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
と
<Button
android:id="@+id/go_button"
android:text="GO"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
の「名前を書いてね」と「GO」をxmlにべた書きしないで、strings.xmlに書いてそこを参照しろよ?!って怒られてます。
res/values下にstrings.xmlがあるので開いてみよう。
<resources>
<string name="app_name">Practice</string>
</resources>
これしかないので、以下のように追記してみてください。
<resources>
<string name="app_name">Practice</string>
<string name="message_name_text_hint">名前を書いてね</string>
<string name="label_go_button_go">GO</string>
</resources>
strings.xmlに記載したら、activity_main.xmlを以下のように修正します。
<TextView
android:id="@+id/name_text"
android:hint="@string/message_name_text_hint"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<Button
android:id="@+id/go_button"
android:text="@string/label_go_button_go"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
これで再び、Lintをかけてみてください。
警告が消えてますか??
HardCodedが消えてますね!!
こんな感じでLintのメッセージの通りに修正し、またLintにかけてチェックするというのを習慣づけてください。
※しかし、中には諸事情により直せないケースも出てきます。可能な限りで良いので対応する方針にしましょう。
イベント処理を実装する
今はボタンは表示されているだけで、押しても何も反応しません。
ここをボタンを押したら次の画面に遷移し、元の画面のテキストボックスの値を次画面に渡して表示するというサンプルを作ってみましょう。
- activity_next.xmlの作成
- NextActivity.javaの作成
- NextActivityのManifest登録
- MainActivity.javaの改修
です。
activity_next.xmlの作成
layout下に以下のレイアウトを作成してください。
▼res/layout/activity_next.xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="9dp"
android:padding="5dp">
<EditText
android:id="@+id/name_edit"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="text"
android:layout_marginTop="20dp"
android:textSize="24sp"
android:singleLine="true" />
<Button
android:id="@+id/clear_edit_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginRight="10dp"
android:layout_marginEnd="10dp"
android:layout_gravity="end|center_vertical"
android:background="@android:drawable/btn_dialog" />
</FrameLayout>
NextActivity.javaの作成
package jp.co.kenshu;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
public class NextActivity extends Activity implements View.OnClickListener {
EditText nameEdit;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_next);
if (savedInstanceState == null) {
nameEdit = (EditText) findViewById(R.id.name_edit);
String title = getIntent().getStringExtra("titleText");
nameEdit.setText(title);
Button clearBtn = (Button) findViewById(R.id.clear_edit_button);
clearBtn.setOnClickListener(this);
}
}
@Override
public void onClick(View v) {
if (nameEdit != null) {
nameEdit.setText("");
}
}
}
NextActivityのManifest登録
<activity android:name=".NextActivity" />
MainActivityの改修
package jp.co.kenshu;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
public class MainActivity extends Activity implements View.OnClickListener {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (savedInstanceState == null) {
TextView titleText = (TextView) findViewById(R.id.title_text);
titleText.setText(getString(R.string.app_name));
Button goButton = (Button) findViewById(R.id.go_button);
goButton.setOnClickListener(this);
}
}
@Override
public void onClick(View v) {
Intent intent = new Intent(this, NextActivity.class);
intent.putExtra("titleText", ((TextView) findViewById(R.id.title_text)).getText());
startActivity(intent);
}
}
画面遷移をする
画面を動かしてみましょう。
「GO」ボタンを押したら、こんな感じに画面遷移しましたか??
「×」ボタンを押すと、テキストが消えます。
また、端末の戻るボタンを押すと、Main画面に戻ることもできます。
解説
リスナーの登録
イベント処理は対象のViewに対して、View.OnClickListenerをセットします。
今回はActivity自身がListenerを実装し、onClickメソッドをOverrideしています。
public class MainActivity extends Activity implements View.OnClickListener {
Button goButton = (Button) findViewById(R.id.go_button);
goButton.setOnClickListener(this);
@Override
public void onClick(View v) {
Intent intent = new Intent(this, NextActivity.class);
intent.putExtra("titleText", ((TextView) findViewById(R.id.title_text)).getText());
startActivity(intent);
}
これは以下のように書き換えることもできます。
goButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// something
}
});
ようはView.OnClickListenerインターフェースを実装した匿名インナークラスを引数に渡しているだけです。
前回のは、View.OnClickListenerインターフェースを実装したMainActivity自身を渡していただけにすぎません。
EffectiveJavaにも記載がありますが、リスナーを定義する際には、安易にインナークラスを定義せず、staticなインナークラスを定義することで、Activityが参照を持ち続けてしまわずに済みます。
ここは別の項でまた詳しく。
Viewには色々なListenerが存在します。スクロールを検知したり、文字入力を検知したり…
枚挙に暇がないので、developerで色々なViewのJavaDocを見ていろいろ試して遊んでみてください。
Intentによる画面遷移
次に行ったのが
Intent intent = new Intent(this, NextActivity.class);
intent.putExtra("titleText", ((TextView) findViewById(R.id.title_text)).getText());
startActivity(intent);
こんな記述です。
Intentとは…
developerを読むのが一番確実な知識です。
ですが、簡単に言ってしまえば、
「Activity同士の連携に用いられます。同一アプリは勿論のこと、外部アプリとの連携にも使えます。そして情報の共有にも使用できます。」
今回は、ActivityからActivityに遷移する際に使用してます。
「MainActivityから、NextActivityへ移動します。だけど"titleText"というkey値にIDがtitle_textのTextViewの文字列をセットして渡すよ」って動きをしてます。
これでMainActivityはonPauseが呼ばれ、フォアグラウンドからバックグラウンドにスタックが移動します。
代わりにNextActivityがonCreateが呼ばれ、フォアグラウンドに移動していきます。
Intentから渡された情報を取得する
MainActivityでは、
intent.putExtra(key, value)
の形式で情報をkeyに紐づけて渡せます。
ではNextActivityはMainActivityから渡された情報をうけとらなければいけません。
それが
getIntent().getStringExtra("titleText")
この記述です。
Intent情報を取得し、そこから"titleText"keyから情報を取得しています。
このように、Activity(画面)間の情報共有にはIntentを使用します。
どうでしょうか。意外と簡単だったでしょ?
Intentには今回みたいなStringの受け渡し以外にも、様々な型が用意されているので、
必要に応じてAPIを使い分けてください。
これもDeveloperに記述されていますので。
練習問題
問題1
MainActivityにボタンを一つ追加し、ListSampleActivityという新規画面に遷移するよう修正してください。
UIに関しては、現段階では自由でよい。よりあえず、MainAtivityから新規に追加したActivityに遷移するという、一連の流れを復習することを目的とする。
※ただし、Lintエラーは潰すこと。
問題2
この問題は復習ではなく、新し知識を調べて、解決してもらうために出します。
MainActivityから、NextActivityに移動し、デバイスのバックキーでまたMainActivityに戻ることができます。
しかし、「Main ⇒ Next ⇒ Main ⇒ Next…」と繰り返すと、どんどんスタックにActivityが積まれていきます。
MainActivityがフォアグラウンドにいる状態で、バックキーを押すと、アプリが終了するように改修を行いなさい。
ただし以下の方法は禁止する
onKeyDownイベントを検知し、if(keyCode != KeyEvent.KEYCODE_BACK){みたいな処理を行う
これは今回はなしでお願いしします。