あんざいゆき
2011年11月19日
Fragment 基礎講座 / ハンズオン by あんざいゆき is licensed
under a Creative Commons 表示 - 非営利 - 改変禁止 3.0 非移植 License.
docs.google.comにある作品に基づいている。
第3回名古屋android勉強会 Fragment 基礎講座 / ハンズオン |
この資料のURL : bit.ly/yanzm_fragment_fes
2. Support package (Compatibility package) の jar をプロジェクトに設定する
4. Fragment を使って XML の結果を表示しよう
5. ListFragment を使って XML のパース結果を ListView で表示しよう
6. ResultFragment を ResultActivity から MainActivity に移動してみよう
res/layout/detail_fragment.xml
8. 一覧をタップしたときにActivity を介すようにする
0. 準備 |
2.x で Fragment を使うために、Support Package (Androird Compatibility package) を準備する
Android SDK Manager を開きます。
一部のMac や Linux
Mac や Windows
Extras の中の Android Support package (一部の Mac や Linux だと Android Compatibility package) を Install してください。
install したら、
一部のMac, Linux
[sdk installed directory]/extras/android/compatibility/v4/android-support-v4.jar
Mac, Windows
[sdk installed directory]\extras\android\support\v4\android-support-v4.jar
があることを確認してください。
1. プロジェクトを作る |
2. Support package (Compatibility package) の jar をプロジェクトに設定する |
プロジェクトを選択して右クリック -> [Android Tools] -> [Add Compatibility Library...]
libs フォルダが作成されて、中に android-support-v4.jar が入っていることを確認。
3. Fragmentなしの画面を作る |
今回は ATND の API を使います。
まずは ATND のイベントを検索して、その結果を表示するだけの画面を作ってみましょう。
イベントを検索する API
http://api.atnd.org/#events-query
例)「Android」でイベントを検索するクエリー
XMLで返ってくる
http://api.atnd.org/events/?keyword=Android
JSONで返ってくる
http://api.atnd.org/events/?keyword=Android&format=json
res/layout/main.xml |
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content" >
<EditText
android:id="@+id/keyword"
android:layout_width="0dip"
android:layout_height="wrap_content"
android:layout_weight="1" />
<Button
android:id="@+id/search_btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@android:string/search_go" />
</LinearLayout>
</LinearLayout>
MainActivity.java |
package com.example.hellofragment;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
public class MainActivity extends Activity {
private Button mSearchBtn;
private EditText mEditText;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
mSearchBtn = (Button) findViewById(R.id.search_btn);
mEditText = (EditText) findViewById(R.id.keyword);
mSearchBtn.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
String keyword = mEditText.getText().toString();
if (!TextUtils.isEmpty(keyword)) {
Intent intent = new Intent(MainActivity.this, ResultActivity.class);
intent.putExtra(Intent.EXTRA_TEXT, keyword);
startActivity(intent);
}
}
});
}
}
res/layout/result.xml |
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >
<TextView
android:id="@+id/result"
android:layout_width="fill_parent"
android:layout_height="fill_parent" />
</LinearLayout>
ResultActivity.java |
package com.example.hellofragment;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URLEncoder;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.protocol.HTTP;
import android.app.Activity;
import android.app.ProgressDialog;
import android.content.Intent;
import android.os.AsyncTask;
import android.os.Bundle;
import android.text.TextUtils;
import android.widget.TextView;
public class ResultActivity extends Activity {
private static final String ATND_SEARCH_QUERY = "http://api.atnd.org/events/?keyword=";
private TextView mTextView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.result);
mTextView = (TextView)findViewById(R.id.result);
Intent intent = getIntent();
String keyword = intent.getStringExtra(Intent.EXTRA_TEXT);
if (!TextUtils.isEmpty(keyword)) {
searchEvents(ATND_SEARCH_QUERY + URLEncoder.encode(keyword));
}
}
private void searchEvents(String query) {
AsyncTask<String, Void, String> task = new AsyncTask<String, Void, String>() {
private static final String ERROR = "can not search";
private ProgressDialog mDialog;
@Override
protected void onPreExecute() {
mDialog = new ProgressDialog(ResultActivity.this);
mDialog.setMessage("Searching...");
mDialog.show();
super.onPreExecute();
}
@Override
protected String doInBackground(String... params) {
if (params.length < 1)
return ERROR;
if (TextUtils.isEmpty(params[0]))
return ERROR;
String url = params[0];
HttpClient httpClient = new DefaultHttpClient();
HttpGet hg = new HttpGet(url);
StringBuilder sb = new StringBuilder();
try {
HttpResponse httpResponse = httpClient.execute(hg);
if (httpResponse.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
HttpEntity httpEntity = httpResponse.getEntity();
InputStream in = httpEntity.getContent();
BufferedReader br = new BufferedReader(new InputStreamReader(in, HTTP.UTF_8), 8);
String line;
while ((line = br.readLine()) != null) {
sb.append(line + "\n");
}
in.close();
hg.abort();
return sb.toString();
} else {
hg.abort();
return ERROR;
}
} catch (ClientProtocolException e) {
return ERROR;
} catch (IOException e) {
return ERROR;
} catch (NullPointerException e) {
return ERROR;
}
}
@Override
protected void onPostExecute(String result) {
if (mDialog != null)
mDialog.dismiss();
if (mTextView != null) {
mTextView.setText(result);
}
super.onPostExecute(result);
}
};
task.execute(query);
}
}
AndroidManifest.xml |
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.hellofragment"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk android:minSdkVersion="7" />
<uses-permission android:name="android.permission.INTERNET"/>
<application
android:icon="@drawable/ic_launcher"
android:label="@string/app_name" >
<activity
android:label="@string/app_name"
android:name=".MainActivity" >
<intent-filter >
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:label="@string/app_name"
android:name=".ResultActivity" />
</application>
</manifest>
4. Fragment を使って検索結果を表示する |
ResultActivity (extends Activity) を ResultActivity (extends FragmentActivity) + ResultFragment (extends Fragment) に書き換える
ResultFragment を作る
パッケージを選択して右クリック -> [New] -> [Class]
Superclass のBrowse... をクリック
android.support.v4.app の Fragment を選択して OK
ResultActivity 内の searchEvents() をこっちに移す。
ResultFragment.java |
package com.example.hellofragment;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.protocol.HTTP;
import android.app.ProgressDialog;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
public class ResultFragment extends Fragment {
private TextView mTextView;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.result, container, false);
mTextView = (TextView) v.findViewById(R.id.result);
return v;
}
public void searchEvents(String query) {
AsyncTask<String, Void, String> task = new AsyncTask<String, Void, String>() {
private static final String ERROR = "can not search";
private ProgressDialog mDialog;
@Override
protected void onPreExecute() {
mDialog = new ProgressDialog(ResultActivity.this);
mDialog.setMessage("Searching...");
mDialog.show();
super.onPreExecute();
}
@Override
protected String doInBackground(String... params) {
if (params.length < 1)
return ERROR;
if (TextUtils.isEmpty(params[0]))
return ERROR;
String url = params[0];
HttpClient httpClient = new DefaultHttpClient();
HttpGet hg = new HttpGet(url);
StringBuilder sb = new StringBuilder();
try {
HttpResponse httpResponse = httpClient.execute(hg);
if (httpResponse.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
HttpEntity httpEntity = httpResponse.getEntity();
InputStream in = httpEntity.getContent();
BufferedReader br = new BufferedReader(new InputStreamReader(in, HTTP.UTF_8), 8);
String line;
while ((line = br.readLine()) != null) {
sb.append(line + "\n");
}
in.close();
hg.abort();
return sb.toString();
} else {
hg.abort();
return ERROR;
}
} catch (ClientProtocolException e) {
return ERROR;
} catch (IOException e) {
return ERROR;
} catch (NullPointerException e) {
return ERROR;
}
}
@Override
protected void onPostExecute(String result) {
if(mDialog != null) {
mDialog.dismiss();
}
if (mTextView != null) {
mTextView.setText(result);
}
super.onPostExecute(result);
}
};
task.execute(query);
}
}
ResultActivity の setContentView() に指定するレイアウトXML として、ResultFragment が入ったレイアウトを用意する。
result_fragment.xml |
<?xml version="1.0" encoding="utf-8"?>
<fragment xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/result_fragment"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:name="com.example.hellofragment.ResultFragment" />
ResultActivity では、Activity を FragmentActivity に変更(Support package を使う場合は必要)。
FragmentActivity の getSupportFragmentManager() で FragmentManager を取得する。 取得したFragmentManager の FindFragmentById で ResultFragment をとってきて、ResultFragment の searchEvents を呼ぶ。
ResultActivity.java |
package com.example.hellofragment;
import java.net.URLEncoder;
import android.content.Intent;
import android.os.Bundle;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.FragmentManager;
import android.text.TextUtils;
public class ResultActivity extends FragmentActivity {
private static final String ATND_SEARCH_QUERY = "http://api.atnd.org/events/?keyword=";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.result_fragment);
Intent intent = getIntent();
String keyword = intent.getStringExtra(Intent.EXTRA_TEXT);
if (!TextUtils.isEmpty(keyword)) {
FragmentManager manager = getSupportFragmentManager();
ResultFragment fragment = (ResultFragment) manager.findFragmentById(R.id.result_fragment);
fragment.searchEvents(ATND_SEARCH_QUERY + URLEncoder.encode(keyword));
}
}
}
5. ListFragment を使って検索結果をリストで表示しよう |
ResultFragment を ListFragment を継承するように変更する。
ResultFragment.java |
package com.example.hellofragment;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import android.app.ProgressDialog;
import android.os.AsyncTask;
import android.support.v4.app.ListFragment;
import android.text.TextUtils;
import android.util.Xml;
import android.widget.ArrayAdapter;
public class ResultFragment extends ListFragment {
private TextView mTextView;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.result, container, false);
mTextView = (TextView) v.findViewById(R.id.result);
return v;
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
ArrayAdapter<String> adapter = new ArrayAdapter<String>(getActivity(), android.R.layout.simple_list_item_1);
setListAdapter(adapter);
super.onActivityCreated(savedInstanceState);
}
public void searchEvents(String query) {
AsyncTask<String, Void, List<String>> task = new AsyncTask<String, Void, List<String>>() {
private static final String ERROR = "can not search";
@Override
protected void onPreExecute() {
setListShown(false);
super.onPreExecute();
}
@Override
protected List<String> doInBackground(String... params) {
if (params.length < 1) {
return null;
}
if (TextUtils.isEmpty(params[0])) {
return null;
}
String url = params[0];
HttpClient httpClient = new DefaultHttpClient();
HttpGet hg = new HttpGet(url);
try {
HttpResponse httpResponse = httpClient.execute(hg);
if (httpResponse.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
HttpEntity httpEntity = httpResponse.getEntity();
InputStream in = httpEntity.getContent();
ArrayList<String> data = parseXml(in);
hg.abort();
return data;
} else {
hg.abort();
return null;
}
} catch (XmlPullParserException e) {
return null;
} catch (ClientProtocolException e) {
return null;
} catch (IOException e) {
return null;
} catch (IllegalArgumentException e) {
return null;
} catch (NullPointerException e) {
return null;
}
}
private ArrayList<String> parseXml(InputStream in) throws XmlPullParserException, IOException {
ArrayList<String> titleList = new ArrayList<String>();
final XmlPullParser parser = Xml.newPullParser();
parser.setInput(new InputStreamReader(in));
String tagName;
int eventType;
// parse XML
while (true) {
eventType = parser.next();
if (eventType == XmlPullParser.END_DOCUMENT)
break;
if (eventType != XmlPullParser.START_TAG)
continue;
tagName = parser.getName();
if (tagName.equals("event")) {
while (true) {
eventType = parser.next();
tagName = parser.getName();
if (eventType == XmlPullParser.END_TAG && tagName.equals("event")) {
break;
}
if (eventType != XmlPullParser.START_TAG)
continue;
if (tagName.equals("title")) {
titleList.add(parser.nextText());
break;
}
}
}
}
in.close();
return titleList;
}
@Override
protected void onPostExecute(List<String> result) {
setListShown(true);
if(result != null) {
ArrayAdapter<String> adapter = new ArrayAdapter<String>(getActivity(),android.R.layout.simple_list_item_1, result);
setListAdapter(adapter);
} else {
Toast.makeText(getActivity(), ERROR, Toast.LENGTH_SHORT).show();
}
super.onPostExecute(result);
}
};
task.execute(query);
}
}
6. ResultFragment を ResultActivity から MainActivity に移動してみよう |
ResultFragment には一切手を入れずに、MainActivity と main.xml だけ変更する。
main.xml には ResultFragment を追加
main.xml |
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content" >
<EditText
android:id="@+id/keyword"
android:layout_width="0dip"
android:layout_height="wrap_content"
android:layout_weight="1"
android:inputType="text" />
<Button
android:id="@+id/search_btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@android:string/search_go" />
</LinearLayout>
<fragment
android:id="@+id/result_fragment"
android:layout_width="fill_parent"
android:layout_height="0dip"
android:layout_weight="1"
android:name="com.example.hellofragment.ResultFragment" />
</LinearLayout>
MainActivity では、 ResultActivity を呼び出す代わりに、ResultActivity の onCreate で行なっていた FragmentManager の取得、ResultFragment の取得、ResultFragment の searchEvents の呼び出しを行うようにする。
MainActivity.java |
package com.example.hellofragment;
import java.net.URLEncoder;
import android.os.Bundle;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.FragmentManager;
import android.text.TextUtils;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
public class MainActivity extends FragmentActivity {
private static final String ATND_SEARCH_QUERY = "http://api.atnd.org/events/?keyword=";
private Button mSearchBtn;
private EditText mEditText;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
mSearchBtn = (Button) findViewById(R.id.search_btn);
mEditText = (EditText) findViewById(R.id.keyword);
mSearchBtn.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
String keyword = mEditText.getText().toString();
if (!TextUtils.isEmpty(keyword)) {
FragmentManager manager = getSupportFragmentManager();
ResultFragment fragment = (ResultFragment)manager.findFragmentById(R.id.result_fragment);
fragment.searchEvents(ATND_SEARCH_QUERY +URLEncoder.encode(keyword));
}
}
});
}
}
7. リストをタップしたときに詳細画面に移動しよう |
まず、ATND用のデータクラスを作る。
AtndData.java |
package com.example.hellofragment;
public class AtndData {
public String title;
public String description;
public String eventUrl;
public String startedAt;
public String endedAt;
public String url;
public String limit;
public String address;
public String place;
@Override
public String toString() {
return title;
}
}
XML のパースで AtndData のリストを返すようにする
ResultFragment.java |
package com.example.hellofragment;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import android.app.ProgressDialog;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.v4.app.ListFragment;
import android.text.TextUtils;
import android.util.Xml;
import android.view.View;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.Toast;
public class ResultFragment extends ListFragment {
@Override
public void onActivityCreated(Bundle savedInstanceState) {
ArrayAdapter<AtndData> adapter = new ArrayAdapter<AtndData>(getActivity(),android.R.layout.simple_list_item_1);
setListAdapter(adapter);
super.onActivityCreated(savedInstanceState);
}
public void searchEvents(String query) {
AsyncTask<String, Void, List<AtndData>> task = new AsyncTask<String, Void,List<AtndData>>() {
private static final String ERROR = "can not search";
@Override
protected void onPreExecute() {
setListShown(false);
super.onPreExecute();
}
@Override
protected List<AtndData> doInBackground(String... params) {
List<AtndData> atndList = new ArrayList<AtndData>();
if (params.length < 1) {
return null;
}
if (TextUtils.isEmpty(params[0])) {
return null;
}
String url = params[0];
HttpClient httpClient = new DefaultHttpClient();
HttpGet hg = new HttpGet(url);
try {
HttpResponse httpResponse = httpClient.execute(hg);
if (httpResponse.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
HttpEntity httpEntity = httpResponse.getEntity();
InputStream in = httpEntity.getContent();
final XmlPullParser parser = Xml.newPullParser();
parser.setInput(new InputStreamReader(in));
ArrayList<AtndData> data = parseXml(in);
hg.abort();
return data;
} else {
hg.abort();
return null;
}
} catch (XmlPullParserException e) {
return null;
} catch (ClientProtocolException e) {
return null;
} catch (IOException e) {
return null;
} catch (IllegalArgumentException e) {
return null;
} catch (NullPointerException e) {
return null;
}
}
private ArrayList<AtndData> parseXml(InputStream in) throws XmlPullParserException, IOException {
ArrayList<AtndData> atndList = new ArrayList<AtndData>();
final XmlPullParser parser = Xml.newPullParser();
parser.setInput(new InputStreamReader(in));
String tagName;
int eventType;
// parse XML
while (true) {
eventType = parser.next();
if (eventType == XmlPullParser.END_DOCUMENT)
break;
if (eventType != XmlPullParser.START_TAG)
continue;
tagName = parser.getName();
if (tagName.equals("event")) {
AtndData atnd = new AtndData();
while (true) {
eventType = parser.next();
tagName = parser.getName();
if (eventType == XmlPullParser.END_TAG && tagName.equals("event")) {
atndList.add(atnd);
break;
}
if (eventType != XmlPullParser.START_TAG)
continue;
if (tagName.equals("title")) {
atnd.title = parser.nextText();
continue;
} else if (tagName.equals("description")) {
atnd.description = parser.nextText();
continue;
} else if (tagName.equals("eventUrl")) {
atnd.eventUrl = parser.nextText();
continue;
} else if (tagName.equals("startedAt")) {
atnd.startedAt = parser.nextText();
continue;
} else if (tagName.equals("endedAt")) {
atnd.endedAt = parser.nextText();
continue;
} else if (tagName.equals("url")) {
atnd.url = parser.nextText();
continue;
} else if (tagName.equals("limit")) {
atnd.limit = parser.nextText();
continue;
} else if (tagName.equals("address")) {
atnd.address = parser.nextText();
continue;
} else if (tagName.equals("place")) {
atnd.place = parser.nextText();
continue;
}
}
}
}
in.close();
return atndList;
}
@Override
protected void onPostExecute(List<AtndData> result) {
setListShown(true);
if (result != null) {
ArrayAdapter<AtndData> adapter = new ArrayAdapter<AtndData>(getActivity(),
android.R.layout.simple_list_item_1, result);
setListAdapter(adapter);
} else {
Toast.makeText(getActivity(), ERROR, Toast.LENGTH_SHORT).show();
}
super.onPostExecute(result);
}
};
task.execute(query);
}
@Override
public void onListItemClick(ListView l, View v, int position, long id) {
super.onListItemClick(l, v, position, id);
AtndData data = (AtndData) l.getAdapter().getItem(position);
Intent intent = new Intent(getActivity(), DetailFragment.class);
intent.putExtra("data", data);
startActivity(intent);
}
}
Intent を介して渡せるように AtndData を Parcelable にしておく
AtndData.java |
package com.example.hellofragment;
import android.os.Parcel;
import android.os.Parcelable;
public class AtndData implements Parcelable {
public String title;
public String description;
public String eventUrl;
public String startedAt;
public String endedAt;
public String url;
public String limit;
public String address;
public String place;
@Override
public String toString() {
return title;
}
public int describeContents() {
return 0;
}
public void writeToParcel(Parcel out, int flags) {
out.writeString(title);
out.writeString(description);
out.writeString(eventUrl);
out.writeString(startedAt);
out.writeString(endedAt);
out.writeString(url);
out.writeString(limit);
out.writeString(address);
out.writeString(place);
}
public static final Parcelable.Creator<AtndData> CREATOR = new Parcelable.Creator<AtndData>() {
public AtndData createFromParcel(Parcel in) {
return new AtndData(in);
}
public AtndData[] newArray(int size) {
return new AtndData[size];
}
};
private AtndData(Parcel in) {
title = in.readString();
description = in.readString();
eventUrl = in.readString();
startedAt = in.readString();
endedAt = in.readString();
url = in.readString();
limit = in.readString();
address = in.readString();
place = in.readString();
}
public AtndData() {
}
}
詳細画面用のActivity (DetailActivity) と Fragment (DetailFragment) を作る
res/layout/detail_fragment.xml |
<?xml version="1.0" encoding="utf-8"?>
<fragment xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/detail_fragment"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:name="com.example.hellofragment.DetailFragment" />
Intent を介して渡された AtndData を DetailFragment に渡す。
DetailFragment は FragmentManager の findFragmentById() で取得する。
DetailActivity.java |
package com.example.hellofragment;
import android.content.Intent;
import android.os.Bundle;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.FragmentManager;
public class DetailActivity extends FragmentActivity {
@Override
protected void onCreate(Bundle arg0) {
super.onCreate(arg0);
setContentView(R.layout.detail_fragment);
Intent intent = getIntent();
AtndData data = (AtndData)intent.getParcelableExtra("data");
if(data != null) {
FragmentManager manager = getSupportFragmentManager();
DetailFragment fragment = (DetailFragment)manager.findFragmentById(R.id.detail_fragment);
fragment.setAtndData(data);
}
}
}
res/layout/detail.xml |
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent" >
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >
<TextView
android:id="@+id/title"
android:layout_width="fill_parent"
android:layout_height="wrap_content" />
<TextView
android:id="@+id/description"
android:layout_width="fill_parent"
android:layout_height="wrap_content" />
<TextView
android:id="@+id/date"
android:layout_width="fill_parent"
android:layout_height="wrap_content" />
<TextView
android:id="@+id/limit"
android:layout_width="fill_parent"
android:layout_height="wrap_content" />
<TextView
android:id="@+id/place"
android:layout_width="fill_parent"
android:layout_height="wrap_content" />
</LinearLayout>
</ScrollView>
DetailFragment.java |
package com.example.hellofragment;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
public class DetailFragment extends Fragment {
private TextView mTitleView;
private TextView mDescriptionView;
private TextView mDateView;
private TextView mLimitView;
private TextView mPlaceView;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.detail, container, false);
mTitleView = (TextView) v.findViewById(R.id.title);
mDescriptionView = (TextView) v.findViewById(R.id.description);
mDateView = (TextView) v.findViewById(R.id.date);
mLimitView = (TextView) v.findViewById(R.id.limit);
mPlaceView = (TextView) v.findViewById(R.id.place);
return v;
}
public void setAtndData(AtndData data) {
mTitleView.setText(data.title);
mDescriptionView.setText(data.description);
mDateView.setText(data.startedAt + " - " + data.endedAt);
mLimitView.setText("定員 : " + data.limit + "人");
mPlaceView.setText(data.address + ", " + data.place);
}
}
onCreateView で、各データをセットするための TextView を保持しておいて、DetailActivity から呼ばれる setAtndData でそれぞれに文字列をセットする。
AndroidManifest.xml |
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.hellofragment"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk android:minSdkVersion="7" />
<uses-permission android:name="android.permission.INTERNET"/>
<application
android:icon="@drawable/ic_launcher"
android:label="@string/app_name" >
<activity
android:label="@string/app_name"
android:name=".MainActivity" >
<intent-filter >
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:label="@string/app_name"
android:name=".ResultActivity" />
<activity
android:label="@string/app_name"
android:name=".DetailActivity" />
</application>
</manifest>
8. 一覧をタップしたときにActivity を介すようにする |
直接 Fragment から 遷移するActivity を呼び出す (startActivity() する)のではなく、 Fragment を持っている Activity にタップされたことを伝えるようにする。
こうすることで、Fragmentを持っている Activity によってタップされたときの動作を変えることができる。
例えば、Fragmentを持っている Activity がハンドセット用なら startActivity() して、タブレット用なら Fragment を置き換える、というような場合分けができる。
ResultFragment.java |
package com.example.hellofragment;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import android.app.ProgressDialog;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.v4.app.ListFragment;
import android.text.TextUtils;
import android.util.Xml;
import android.view.View;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.Toast;
public class ResultFragment extends ListFragment {
@Override
public void onActivityCreated(Bundle savedInstanceState) {
ArrayAdapter<AtndData> adapter = new ArrayAdapter<AtndData>(getActivity(), android.R.layout.simple_list_item_1);
setListAdapter(adapter);
super.onActivityCreated(savedInstanceState);
}
public void searchEvents(String query) {
AsyncTask<String, Void, List<AtndData>> task = new AsyncTask<String, Void, List<AtndData>>() {
private static final String ERROR = "can not search";
@Override
protected void onPreExecute() {
setListShown(false);
super.onPreExecute();
}
@Override
protected List<AtndData> doInBackground(String... params) {
List<AtndData> atndList = new ArrayList<AtndData>();
if (params.length < 1) {
return null;
}
if (TextUtils.isEmpty(params[0])) {
return null;
}
String url = params[0];
HttpClient httpClient = new DefaultHttpClient();
HttpGet hg = new HttpGet(url);
try {
HttpResponse httpResponse = httpClient.execute(hg);
if (httpResponse.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
HttpEntity httpEntity = httpResponse.getEntity();
InputStream in = httpEntity.getContent();
final XmlPullParser parser = Xml.newPullParser();
parser.setInput(new InputStreamReader(in));
String tagName;
int eventType;
// parse XML
while (true) {
eventType = parser.next();
if (eventType == XmlPullParser.END_DOCUMENT)
break;
if (eventType != XmlPullParser.START_TAG)
continue;
tagName = parser.getName();
if (tagName.equals("event")) {
AtndData atnd = new AtndData();
while (true) {
eventType = parser.next();
tagName = parser.getName();
if (eventType == XmlPullParser.END_TAG && tagName.equals("event")) {
atndList.add(atnd);
break;
}
if (eventType != XmlPullParser.START_TAG)
continue;
if (tagName.equals("title")) {
atnd.title = parser.nextText();
continue;
} else if (tagName.equals("description")) {
atnd.description = parser.nextText();
continue;
} else if (tagName.equals("eventUrl")) {
atnd.eventUrl = parser.nextText();
continue;
} else if (tagName.equals("startedAt")) {
atnd.startedAt = parser.nextText();
continue;
} else if (tagName.equals("endedAt")) {
atnd.endedAt = parser.nextText();
continue;
} else if (tagName.equals("url")) {
atnd.url = parser.nextText();
continue;
} else if (tagName.equals("limit")) {
atnd.limit = parser.nextText();
continue;
} else if (tagName.equals("address")) {
atnd.address = parser.nextText();
continue;
} else if (tagName.equals("place")) {
atnd.place = parser.nextText();
continue;
}
}
}
}
in.close();
hg.abort();
return atndList;
} else {
hg.abort();
return null;
}
} catch (XmlPullParserException e) {
return null;
} catch (ClientProtocolException e) {
return null;
} catch (IOException e) {
return null;
} catch (IllegalArgumentException e) {
return null;
} catch (NullPointerException e) {
return null;
}
}
@Override
protected void onPostExecute(List<AtndData> result) {
setListShown(true);
if (result != null) {
ArrayAdapter<AtndData> adapter = new ArrayAdapter<AtndData>(getActivity(),
android.R.layout.simple_list_item_1, result);
setListAdapter(adapter);
} else {
Toast.makeText(getActivity(), ERROR, Toast.LENGTH_SHORT).show();
}
super.onPostExecute(result);
}
};
task.execute(query);
}
private OnAtndListSelectedListener mOnAtndListSelectedListener;
public interface OnAtndListSelectedListener {
public void onAtndListSelected(AtndData data);
}
public void setOnAtndListSelected(OnAtndListSelectedListener l) {
mOnAtndListSelectedListener = l;
}
@Override
public void onListItemClick(ListView l, View v, int position, long id) {
super.onListItemClick(l, v, position, id);
AtndData data = (AtndData) l.getAdapter().getItem(position);
if (mOnAtndListSelectedListener != null) {
mOnAtndListSelectedListener.onAtndListSelected(data);
}
}
}
一覧がタップされたときの Listener (ここでは OnAtndListSelectedListener) を定義して、Fragment を持つ Activity 側でこの Listener をセットする。
MainActivity.java |
package com.example.hellofragment;
import java.net.URLEncoder;
import android.content.Intent;
import android.os.Bundle;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.FragmentManager;
import android.text.TextUtils;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
public class MainActivity extends FragmentActivity {
private static final String ATND_SEARCH_QUERY = "http://api.atnd.org/events/?keyword=";
private Button mSearchBtn;
private EditText mEditText;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
FragmentManager manager = getSupportFragmentManager();
final ResultFragment fragment = (ResultFragment)manager.findFragmentById(R.id.result_fragment);
fragment.setOnAtndListSelected(new ResultFragment.OnAtndListSelectedListener() {
public void onAtndListSelected(AtndData data) {
Intent intent = new Intent(MainActivity.this, DetailActivity.class);
intent.putExtra("data", data);
startActivity(intent);
}
});
mSearchBtn = (Button) findViewById(R.id.search_btn);
mEditText = (EditText) findViewById(R.id.keyword);
mSearchBtn.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
String keyword = mEditText.getText().toString();
if (!TextUtils.isEmpty(keyword)) {
fragment.searchEvents(ATND_SEARCH_QUERY + URLEncoder.encode(keyword));
}
}
});
}
}
9. 一覧をタップしたときにDialogで表示しよう |
Fragment で Dialog を表示するための DialogFragment というのがあります。
タップしたときにこの DialogFragment で詳細を表示してみましょう。
DetailDialogFragment.java |
package com.example.hellofragment;
import android.os.Bundle;
import android.support.v4.app.DialogFragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
public class DetailDialogFragment extends DialogFragment {
private TextView mTitleView;
private TextView mDescriptionView;
private TextView mDateView;
private TextView mLimitView;
private TextView mPlaceView;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.detail, container, false);
mTitleView = (TextView) v.findViewById(R.id.title);
mDescriptionView = (TextView) v.findViewById(R.id.description);
mDateView = (TextView) v.findViewById(R.id.date);
mLimitView = (TextView) v.findViewById(R.id.limit);
mPlaceView = (TextView) v.findViewById(R.id.place);
return v;
}
public void setAtndData(AtndData data) {
mTitleView.setText(data.title);
mDescriptionView.setText(data.description);
mDateView.setText(data.startedAt + " - " + data.endedAt);
mLimitView.setText("定員 : " + data.limit + "人");
mPlaceView.setText(data.address + ", " + data.place);
}
}
ここで、はて。どうやって外部から AtndData をセットしたものか?と思うのです。。。
例えば、
MainActivity.java |
…
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
FragmentManager manager = getSupportFragmentManager();
final ResultFragment fragment = (ResultFragment) manager.findFragmentById(R.id.result_fragment);
fragment.setOnAtndListSelected(new ResultFragment.OnAtndListSelectedListener() {
public void onAtndListSelected(AtndData data) {
FragmentManager manager = getSupportFragmentManager();
FragmentTransaction ft = manager.beginTransaction();
Fragment prev = manager.findFragmentByTag("dialog");
if (prev != null) {
ft.remove(prev);
}
ft.addToBackStack(null);
DetailDialogFragment dialogFragment = new DetailDialogFragment();
dialogFragment.setAtndData(data);
dialogFragment.show(manager, "dialog");
}
});
….
}
}
のように、DialogFrgment のインスタンスを作成したあとに setAtndData() を呼ぶと、NullPointerException になります。
11-19 05:34:18.864: E/AndroidRuntime(1275): java.lang.NullPointerException
11-19 05:34:18.864: E/AndroidRuntime(1275): at com.example.hellofragment.DetailDialogFragment.setAtndData(DetailDialogFragment.java:42)
dialogFragment.show() のところで初めて DialogFragment の onCreateView() が呼ばれるので、その前に setAtndData() を呼ぶと、文字列をセットする TextView がまだ生成されていないため(= nullなので)、NullPointerException になってしまうということです。
ということは、onCreateView() が呼ばれる前に DialogFragment に AtndData を保持させておけばいいことになります。
例えば、
DetailDialogFragment.java |
package com.example.hellofragment;
import android.os.Bundle;
import android.support.v4.app.DialogFragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
public class DetailDialogFragment extends DialogFragment {
private TextView mTitleView;
private TextView mDescriptionView;
private TextView mDateView;
private TextView mLimitView;
private TextView mPlaceView;
private AtndData mData;
public DetailDialogFragment() {
}
public DetailDialogFragment(AtndData data) {
mData = data;
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.detail, container, false);
mTitleView = (TextView) v.findViewById(R.id.title);
mDescriptionView = (TextView) v.findViewById(R.id.description);
mDateView = (TextView) v.findViewById(R.id.date);
mLimitView = (TextView) v.findViewById(R.id.limit);
mPlaceView = (TextView) v.findViewById(R.id.place);
return v;
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
if(mData != null)
setAtndData(mData);
}
public void setAtndData(AtndData data) {
mTitleView.setText(data.title);
mDescriptionView.setText(data.description);
mDateView.setText(data.startedAt + " - " + data.endedAt);
mLimitView.setText("定員 : " + data.limit + "人");
mPlaceView.setText(data.address + ", " + data.place);
}
}
MainActivity.java |
package com.example.hellofragment;
import java.net.URLEncoder;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentTransaction;
import android.text.TextUtils;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
public class MainActivity extends FragmentActivity {
...
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
FragmentManager manager = getSupportFragmentManager();
final ResultFragment fragment = (ResultFragment) manager.findFragmentById(R.id.result_fragment);
fragment.setOnAtndListSelected(new ResultFragment.OnAtndListSelectedListener() {
public void onAtndListSelected(AtndData data) {
FragmentManager manager = getSupportFragmentManager();
FragmentTransaction ft = manager.beginTransaction();
Fragment prev = manager.findFragmentByTag("dialog");
if (prev != null) {
ft.remove(prev);
}
ft.addToBackStack(null);
DetailDialogFragment dialogFragment = new DetailDialogFragment(data);
dialogFragment.show(manager, "dialog");
}
});
...
}
}
このように、コンストラクタで AtndData を渡すのも手の1つですが、これには問題があります。
画面回転時に Fragment が自動で再生成されるときに、空のコンストラクタが呼ばれます(そのため、Fragment は必ず空のコンストラクタが必要)。つまり、この方法だと、画面回転したときに AtndData が渡されないため、空のダイアログが表示されていまいます。
onCreateView() や onActivityCreated() よりも先に値を渡しておき、画面回転時などのにその値が保持されるようにするためのメソッドが Fragment に用意されています。それが
setArguments(Bundle args)
http://developer.android.com/reference/android/app/Fragment.html#setArguments(android.os.Bundle)
と
getArguments()
http://developer.android.com/reference/android/app/Fragment.html#getArguments()
です。
では、これらのメソッドを使った方法に変えてみましょう。
DetailDialogFragment.java |
package com.example.hellofragment;
import android.os.Bundle;
import android.support.v4.app.DialogFragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
public class DetailDialogFragment extends DialogFragment {
private TextView mTitleView;
private TextView mDescriptionView;
private TextView mDateView;
private TextView mLimitView;
private TextView mPlaceView;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.detail, container, false);
mTitleView = (TextView) v.findViewById(R.id.title);
mDescriptionView = (TextView) v.findViewById(R.id.description);
mDateView = (TextView) v.findViewById(R.id.date);
mLimitView = (TextView) v.findViewById(R.id.limit);
mPlaceView = (TextView) v.findViewById(R.id.place);
return v;
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
Bundle arguments = getArguments();
if (arguments != null) {
AtndData data = (AtndData) arguments.getParcelable("data");
if (data != null)
setAtndData(data);
}
}
public void setAtndData(AtndData data) {
mTitleView.setText(data.title);
mDescriptionView.setText(data.description);
mDateView.setText(data.startedAt + " - " + data.endedAt);
mLimitView.setText("定員 : " + data.limit + "人");
mPlaceView.setText(data.address + ", " + data.place);
}
}
MainActivity.java |
package com.example.hellofragment;
import java.net.URLEncoder;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentTransaction;
import android.text.TextUtils;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
public class MainActivity extends FragmentActivity {
…
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
FragmentManager manager = getSupportFragmentManager();
final ResultFragment fragment = (ResultFragment) manager.findFragmentById(R.id.result_fragment);
fragment.setOnAtndListSelected(new ResultFragment.OnAtndListSelectedListener() {
public void onAtndListSelected(AtndData data) {
FragmentManager manager = getSupportFragmentManager();
FragmentTransaction ft = manager.beginTransaction();
Fragment prev = manager.findFragmentByTag("dialog");
if (prev != null) {
ft.remove(prev);
}
ft.addToBackStack(null);
DetailDialogFragment dialogFragment = new DetailDialogFragment();
Bundle bundle = new Bundle();
bundle.putParcelable("data", data);
dialogFragment.setArguments(bundle);
dialogFragment.show(manager, "dialog");
}
});
...
}
}
arguments をセットする部分を DetailDialogFragment の方に移して、データのセットも onActivityCreated() から onCreateView() に移しちゃいましょう。
DetailDialogFragment.java |
package com.example.hellofragment;
import android.os.Bundle;
import android.support.v4.app.DialogFragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
public class DetailDialogFragment extends DialogFragment {
static DetailDialogFragment newInstance(AtndData data) {
DetailDialogFragment f = new DetailDialogFragment();
Bundle args = new Bundle();
args.putParcelable("data", data);
f.setArguments(args);
return f;
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.detail, container, false);
Bundle arguments = getArguments();
if (arguments != null) {
AtndData data = (AtndData) arguments.getParcelable("data");
if (data != null) {
TextView titleView = (TextView) v.findViewById(R.id.title);
TextView descriptionView = (TextView) v.findViewById(R.id.description);
TextView dateView = (TextView) v.findViewById(R.id.date);
TextView limitView = (TextView) v.findViewById(R.id.limit);
TextView placeView = (TextView) v.findViewById(R.id.place);
titleView.setText(data.title);
descriptionView.setText(data.description);
dateView.setText(data.startedAt + " - " + data.endedAt);
limitView.setText("定員 : " + data.limit + "人");
placeView.setText(data.address + ", " + data.place);
}
}
return v;
}
}
MainActivity.java |
package com.example.hellofragment;
import java.net.URLEncoder;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentTransaction;
import android.text.TextUtils;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
public class MainActivity extends FragmentActivity {
...
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
FragmentManager manager = getSupportFragmentManager();
final ResultFragment fragment = (ResultFragment)manager.findFragmentById(R.id.result_fragment);
fragment.setOnAtndListSelected(new ResultFragment.OnAtndListSelectedListener() {
public void onAtndListSelected(AtndData data) {
FragmentManager manager = getSupportFragmentManager();
FragmentTransaction ft = manager.beginTransaction();
Fragment prev = manager.findFragmentByTag("dialog");
if (prev != null) {
ft.remove(prev);
}
ft.addToBackStack(null);
DetailDialogFragment dialogFragment = DetailDialogFragment.newInstance(data);
dialogFragment.show(manager, "dialog");
}
});
...
}
}
10. 画面回転時に一覧データを保持しよう |
DialogFragment では setArguments(), getArguments() を使って、インスタンスの生成時に渡したデータを保持するようにしましたが、ResultFragment では、生成後にネットから取得したデータを保持しておきたいので、onSaveInstanceState() を使います。
ResultFragment.java |
…
public class ResultFragment extends ListFragment {
private ArrayList<AtndData> mDataList;
@Override
public void onActivityCreated(Bundle savedInstanceState) {
if (savedInstanceState != null) {
mDataList = savedInstanceState.getParcelableArrayList("data");
}
if (mDataList == null) {
mDataList = new ArrayList<AtndData>();
}
ArrayAdapter<AtndData> adapter = new ArrayAdapter<AtndData>(getActivity(), android.R.layout.simple_list_item_1,
mDataList);
setListAdapter(adapter);
super.onActivityCreated(savedInstanceState);
}
@Override
public void onSaveInstanceState(Bundle outState) {
outState.putParcelableArrayList("data", mDataList);
super.onSaveInstanceState(outState);
}
…
public void searchEvents(String query) {
AsyncTask<String, Void, ArrayList<AtndData>> task = new AsyncTask<String, Void, ArrayList<AtndData>>() {
…
@Override
protected void onPostExecute(ArrayList<AtndData> result) {
setListShown(true);
if (result != null) {
mDataList = result;
ArrayAdapter<AtndData> adapter = new ArrayAdapter<AtndData>(getActivity(),
android.R.layout.simple_list_item_1, result);
setListAdapter(adapter);
} else {
Toast.makeText(getActivity(), ERROR, Toast.LENGTH_SHORT).show();
}
super.onPostExecute(result);
}
};
task.execute(query);
}
….
}
11. 横画面のときは一覧データの隣に詳細を表示しよう |
最初に例として、横画面のレイアウトに <fragment> で追加した場合をやってみます。
まず、横画面用のレイアウトXMLファイルを作ります。
res/layout-land/main.xml |
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="horizontal" >
<LinearLayout
android:layout_width="0dip"
android:layout_height="fill_parent"
android:layout_weight="1"
android:orientation="vertical" >
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content" >
<EditText
android:id="@+id/keyword"
android:layout_width="0dip"
android:layout_height="wrap_content"
android:layout_weight="1"
android:inputType="text" />
<Button
android:id="@+id/search_btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@android:string/search_go" />
</LinearLayout>
<fragment
android:id="@+id/result_fragment"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:name="com.example.hellofragment.ResultFragment" />
</LinearLayout>
<fragment
android:id="@+id/detail_fragment"
android:layout_width="0dip"
android:layout_height="fill_parent"
android:layout_weight="1"
android:name="com.example.hellofragment.DetailFragment" />
</LinearLayout>
一覧をタップしたときにダイアログがでるようになっている(ダイアログを出したまま画面回転してもちゃんとレイアウトが変わります)ので、
横画面のときはダイアログではなく、右の領域に表示するようにします。
MainActivity.java |
public class MainActivity extends FragmentActivity {
...
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
FragmentManager manager = getSupportFragmentManager();
final ResultFragment fragment = (ResultFragment)manager.findFragmentById(R.id.result_fragment);
fragment.setOnAtndListSelected(new ResultFragment.OnAtndListSelectedListener() {
public void onAtndListSelected(AtndData data) {
FragmentManager manager = getSupportFragmentManager();
DetailFragment detailFragment = (DetailFragment)manager.findFragmentById(R.id.detail_fragment);
if(detailFragment != null) {
// main.xml に DetailFragment がある = 横画面
detailFragment.setAtndData(data);
}
else {
// 縦画面
FragmentTransaction ft = manager.beginTransaction();
Fragment prev = manager.findFragmentByTag("dialog");
if (prev != null) {
ft.remove(prev);
}
ft.addToBackStack(null);
DetailDialogFragment dialogFragment = DetailDialogFragment.newInstance(data);
dialogFragment.show(manager, "dialog");
}
}
});
...
}
}
この方法は一見うまくいくように見えますが、
[横画面で詳細一覧をタップ] -> [横に詳細がでる] -> [縦画面にする] -> [詳細を一覧をタップ]
で落ちます。
一度横画面にすると、DetailFragment が生成され、縦画面にしてもそれが残るため、縦画面で detailFragment が null にならず、setAtndData() が呼ばれます。しかし、 setAtndData() 内で文字列をセットする TextView は null になっているので、ここで NullPointerException になります。
これを避けるために、main.xml に <fragment> で DetailFragment を入れるのではなく、FrameLayout を入れておき、一覧がタップされたときにここに DetailFragment を入れるようにします。
res/layout-land/main.xml |
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="horizontal" >
<LinearLayout
android:layout_width="0dip"
android:layout_height="fill_parent"
android:layout_weight="1"
android:orientation="vertical" >
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content" >
<EditText
android:id="@+id/keyword"
android:layout_width="0dip"
android:layout_height="wrap_content"
android:layout_weight="1"
android:inputType="text" />
<Button
android:id="@+id/search_btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@android:string/search_go" />
</LinearLayout>
<fragment
android:id="@+id/result_fragment"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:name="com.example.hellofragment.ResultFragment" />
</LinearLayout>
<FrameLayout
android:id="@+id/detail_fragment_container"
android:layout_width="0dip"
android:layout_height="fill_parent"
android:layout_weight="1" />
</LinearLayout>
MainActivity.java |
public class MainActivity extends FragmentActivity {
...
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
FragmentManager manager = getSupportFragmentManager();
final ResultFragment fragment = (ResultFragment)manager.findFragmentById(R.id.result_fragment);
fragment.setOnAtndListSelected(new ResultFragment.OnAtndListSelectedListener() {
public void onAtndListSelected(AtndData data) {
FragmentManager manager = getSupportFragmentManager();
FragmentTransaction ft = manager.beginTransaction();
View v = findViewById(R.id.detail_fragment_container);
if(v != null) {
// main.xml に DetailFragment を入れるための FrameLayout がある = 横画面
DetailFragment detailFragment = new DetailFragment();
detailFragment.setAtndData(data);
ft.replace(R.id.detail_fragment_container, detailFragment);
ft.commit();
}
else {
// 縦画面
Fragment prev = manager.findFragmentByTag("dialog");
if (prev != null) {
ft.remove(prev);
}
ft.addToBackStack(null);
DetailDialogFragment dialogFragment = DetailDialogFragment.newInstance(data);
dialogFragment.show(manager, "dialog");
}
}
});
...
}
}
のようにすればいいように思いますが、これも NullPointerException になります。
理由は DetailDialogFragment のときと同じで、ft.commit() で onCreateView() が呼ばれるため、その前に setAtndData() しても TextView が生成されていないためです。
DetailDialogFragment と同じように setArguments(), getArguments() を使うようにします。
DetailDialogFragment.java |
package com.example.hellofragment;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
public class DetailFragment extends Fragment {
private TextView mTitleView;
private TextView mDescriptionView;
private TextView mDateView;
private TextView mLimitView;
private TextView mPlaceView;
static DetailFragment newInstance(AtndData data) {
DetailFragment f = new DetailFragment();
Bundle args = new Bundle();
args.putParcelable("data", data);
f.setArguments(args);
return f;
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.detail, container, false);
mTitleView = (TextView) v.findViewById(R.id.title);
mDescriptionView = (TextView) v.findViewById(R.id.description);
mDateView = (TextView) v.findViewById(R.id.date);
mLimitView = (TextView) v.findViewById(R.id.limit);
mPlaceView = (TextView) v.findViewById(R.id.place);
Bundle arguments = getArguments();
if (arguments != null) {
AtndData data = (AtndData) arguments.getParcelable("data");
if (data != null) {
setAtndData(data);
}
}
return v;
}
public void setAtndData(AtndData data) {
mTitleView.setText(data.title);
mDescriptionView.setText(data.description);
mDateView.setText(data.startedAt + " - " + data.endedAt);
mLimitView.setText("定員 : " + data.limit + "人");
mPlaceView.setText(data.address + ", " + data.place);
}
}
MainActivity.java |
public class MainActivity extends FragmentActivity {
...
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
FragmentManager manager = getSupportFragmentManager();
final ResultFragment fragment = (ResultFragment)manager.findFragmentById(R.id.result_fragment);
fragment.setOnAtndListSelected(new ResultFragment.OnAtndListSelectedListener() {
public void onAtndListSelected(AtndData data) {
FragmentManager manager = getSupportFragmentManager();
FragmentTransaction ft = manager.beginTransaction();
View v = findViewById(R.id.detail_fragment_container);
if(v != null) {
// main.xml に DetailFragment を入れるための FrameLayout がある = 横画面
DetailFragment detailFragment = DetailFragment.newInstance(data);
ft.replace(R.id.detail_fragment_container, detailFragment);
ft.commit();
}
else {
// 縦画面
Fragment prev = manager.findFragmentByTag("dialog");
if (prev != null) {
ft.remove(prev);
}
ft.addToBackStack(null);
DetailDialogFragment dialogFragment = DetailDialogFragment.newInstance(data);
dialogFragment.show(manager, "dialog");
}
}
});
...
}
}
縦画面のとき、DetailActivity を呼び出すように戻しましょう。
MainActivity.java |
public class MainActivity extends FragmentActivity {
...
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
FragmentManager manager = getSupportFragmentManager();
final ResultFragment fragment = (ResultFragment)manager.findFragmentById(R.id.result_fragment);
fragment.setOnAtndListSelected(new ResultFragment.OnAtndListSelectedListener() {
public void onAtndListSelected(AtndData data) {
FragmentManager manager = getSupportFragmentManager();
FragmentTransaction ft = manager.beginTransaction();
View v = findViewById(R.id.detail_fragment_container);
if(v != null) {
// main.xml に DetailFragment を入れるための FrameLayout がある = 横画面
DetailFragment detailFragment = DetailFragment.newInstance(data);
ft.replace(R.id.detail_fragment_container, detailFragment);
ft.commit();
}
else {
// 縦画面
Intent intent = new Intent(MainActivity.this, DetailActivity.class);
intent.putExtra("data", data);
startActivity(intent);
}
}
});
...
}
}
DetailActivity の画面で横に回転すると、DetailActivity のままなので、横画面のときは画面を終了して一覧画面に戻るようにしましょう。
DetailActivity.java |
package com.example.hellofragment;
import android.content.Intent;
import android.content.res.Configuration;
import android.os.Bundle;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.FragmentManager;
public class DetailActivity extends FragmentActivity {
@Override
protected void onCreate(Bundle arg0) {
super.onCreate(arg0);
if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {
finish();
return;
}
setContentView(R.layout.detail_fragment);
Intent intent = getIntent();
AtndData data = (AtndData) intent.getParcelableExtra("data");
if (data != null) {
FragmentManager manager = getSupportFragmentManager();
DetailFragment fragment = (DetailFragment) manager.findFragmentById(R.id.detail_fragment);
fragment.setAtndData(data);
}
}
}
Intentから取り出した AtndData を setAtndData() でセットしているが、setArguments() で生成時にセットできるようにしたので、ここもレイアウトからではなく、動的に DetailFragment を生成するようにしましょう。
DetailActivity.java |
package com.example.hellofragment;
import android.content.Intent;
import android.content.res.Configuration;
import android.os.Bundle;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentTransaction;
public class DetailActivity extends FragmentActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {
finish();
return;
}
if (savedInstanceState == null) {
Intent intent = getIntent();
AtndData data = (AtndData) intent.getParcelableExtra("data");
if (data != null) {
DetailFragment details = DetailFragment.newInstance(data);
FragmentManager manager = getSupportFragmentManager();
FragmentTransaction ft = manager.beginTransaction();
ft.add(android.R.id.content, details);
ft.commit();
}
}
}
}
DetailFragment.java |
package com.example.hellofragment;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
public class DetailFragment extends Fragment {
static DetailFragment newInstance(AtndData data) {
DetailFragment f = new DetailFragment();
Bundle args = new Bundle();
args.putParcelable("data", data);
f.setArguments(args);
return f;
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.detail, container, false);
Bundle arguments = getArguments();
if (arguments != null) {
AtndData data = (AtndData) arguments.getParcelable("data");
if (data != null) {
TextView titleView = (TextView) v.findViewById(R.id.title);
TextView descriptionView = (TextView) v.findViewById(R.id.description);
TextView dateView = (TextView) v.findViewById(R.id.date);
TextView limitView = (TextView) v.findViewById(R.id.limit);
TextView placeView = (TextView) v.findViewById(R.id.place);
titleView.setText(data.title);
descriptionView.setText(data.description);
dateView.setText(data.startedAt + " - " + data.endedAt);
limitView.setText("定員 : " + data.limit + "人");
placeView.setText(data.address + ", " + data.place);
}
}
return v;
}
}
さて、縦画面で DetailActivity を表示した状態で横画面にすると、詳細部分が空になってしまっています。現在表示しているデータのインデックスを保持して、横画面にしたときに縦画面で表示していた詳細がでるようにしましょう。
ResultFragment.java |
package com.example.hellofragment;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import android.app.ProgressDialog;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.v4.app.ListFragment;
import android.text.TextUtils;
import android.util.Xml;
import android.view.View;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.Toast;
public class ResultFragment extends ListFragment {
private int mCurrentPosition = -1;
private ArrayList<AtndData> mDataList;
@Override
public void onActivityCreated(Bundle savedInstanceState) {
if (savedInstanceState != null) {
mDataList = savedInstanceState.getParcelableArrayList("data");
mCurrentPosition = savedInstanceState.getInt("index");
}
if (mDataList == null) {
mDataList = new ArrayList<AtndData>();
}
ArrayAdapter<AtndData> adapter = new ArrayAdapter<AtndData>(getActivity(),android.R.layout.simple_list_item_1,
mDataList);
setListAdapter(adapter);
if(mCurrentPosition != -1) {
listItemClick(mCurrentPosition);
}
super.onActivityCreated(savedInstanceState);
}
@Override
public void onSaveInstanceState(Bundle outState) {
outState.putParcelableArrayList("data", mDataList);
outState.putInt("index", mCurrentPosition);
super.onSaveInstanceState(outState);
}
…
@Override
public void onListItemClick(ListView l, View v, int position, long id) {
super.onListItemClick(l, v, position, id);
listItemClick(position);
}
private void listItemClick(int position) {
mCurrentPosition = position;
AtndData data = (AtndData) getListView().getAdapter().getItem(position);
if (mOnAtndListSelectedListener != null) {
mOnAtndListSelectedListener.onAtndListSelected(data);
}
}
}
これで、縦画面で選択した詳細が横画面にしたときにも保持されます。
おつかれさまでした。
パチパチパチ。
おまけ.
AsyncTask ではなく AsyncTaskLoader を使うようにしてみようね!
第3回名古屋android勉強会 Fragment基礎講座/ハンズオン /