あんざいゆき

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


0. 準備

1. プロジェクトを作る

2. Support package (Compatibility package) の jar をプロジェクトに設定する

3. まずはFragmentなしで画面を作ってみよう

res/layout/main.xml

MainActivity.java

res/layout/result.xml

ResultActivity.java

AndroidManifest.xml

4. Fragment を使って XML の結果を表示しよう

ResultFragment.xml

result_fragment.xml

ResultActivity.java

5. ListFragment を使って XML のパース結果を ListView で表示しよう

ResultFragment.java

6. ResultFragment を ResultActivity から MainActivity に移動してみよう

main.xml

MainActivity.java

7. リストをタップしたときに詳細画面に移動しよう

AtndData.java

ResultFragment.java

AtndData.java

res/layout/detail_fragment.xml

DetailActivity.java

res/layout/detail.xml

DetailFragment.java

AndroidManifest.xml

8.  一覧をタップしたときにActivity を介すようにする

ResultFragment.java

MainActivity.java

9.  一覧をタップしたときにDialogで表示しよう

DetailDialogFragment.java

MainActivity.java

DetailDialogFragment.java

MainActivity.java

DetailDialogFragment.java

MainActivity.java

DetailDialogFragment.java

MainActivity.java

10.  画面回転時に一覧データを保持しよう

ResultFragment.java

11. 横画面のときは一覧データの隣に詳細を表示しよう

res/layout-land/main.xml

MainActivity.java

res/layout-land/main.xml

MainActivity.java

DetailDialogFragment.java

MainActivity.java

MainActivity.java

DetailActivity.java

DetailActivity.java

DetailFragment.java

ResultFragment.java



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 を使います。

http://api.atnd.org/

まずは 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基礎講座/ハンズオン    /