Android

Android再入門 - Twitterクライアントを作ってみよう - タイムラインを表示

More than 5 years have passed since last update.


はじめに

OAuth認証ができたのでいよいよタイムラインを表示してみましょう。


タイムラインの文字列だけ表示してみよう

まず、タイムラインの文字列だけ表示してみましょう。


ListActivityを継承する

タイムラインのように一覧データを表示するにはListViewを使います。さらに、ListViewを使うアクティビティはListActivityを継承すると簡単に作れるのでMainActivityの親クラスをListActivityに変更します。


MainActivity.java

public class MainActivity extends ListActivity {


ListActivityを使う場合は、レイアウトファイルのセットが不要なので setContentView(R.layout.activity_main); を削除します。

全体は以下のようになります。


MainActivity.java

public class MainActivity extends ListActivity {

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

if (!TwitterUtils.hasAccessToken(this)) {
...



ArrayAdapterを継承したクラスを定義する

ListViewで表示するデータはArrayAdapterを継承したクラスで管理し、どう表示するかもこのArrayAdapterで定義します。まず、文字列だけ表示したいので、以下のようにStringデータを管理するArrayAdapterを定義しましょう。


MainActivity.java

private class TweetAdapter extends ArrayAdapter<String> {

public TweetAdapter(Context context) {
super(context, android.R.layout.simple_list_item_1);
}
}



ArrayAdapterインスタンスを生成してセットする

次に、onCreate()メソッドで認証済みの場合は、ArrayAdapterインスタンスを生成してセットするようにします。ArrayAdapterインスタンスは、あとで他のメソッドからも参照したいのでフィールド変数として定義します。

ついでに、Twitterインスタンスもあとで他のメソッドから参照したいのでフィールド変数として定義して、onCreate()メソッドで生成しておきます。

合わせると以下のようになります。


MainActivity.java

public class MainActivity extends ListActivity {

private TweetAdapter mAdapter;
private Twitter mTwitter;

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

if (!TwitterUtils.hasAccessToken(this)) {
Intent intent = new Intent(this, TwitterOAuthActivity.class);
startActivity(intent);
finish();
} else {
mAdapter = new TweetAdapter(this);
setListAdapter(mAdapter);

mTwitter = TwitterUtils.getTwitterInstance(this);
}
}



タイムラインを取得してListViewに表示する

次に、タイムラインを取得してListViewに表示するメソッドを定義します。showToast()メソッドは必須ではありませんが、デバッグ中に何かと便利なので定義しました。


MainActivity.java

private void reloadTimeLine() {

AsyncTask<Void, Void, List<String>> task = new AsyncTask<Void, Void, List<String>>() {
@Override
protected List<String> doInBackground(Void... params) {
try {
ResponseList<twitter4j.Status> timeline = mTwitter.getHomeTimeline();
ArrayList<String> list = new ArrayList<String>();
for (twitter4j.Status status : timeline) {
list.add(status.getText());
}
return list;
} catch (TwitterException e) {
e.printStackTrace();
}
return null;
}

@Override
protected void onPostExecute(List<String> result) {
if (result != null) {
mAdapter.clear();
for (String status : result) {
mAdapter.add(status);
}
getListView().setSelection(0);
} else {
showToast("タイムラインの取得に失敗しました。。。");
}
}
};
task.execute();
}

private void showToast(String text) {
Toast.makeText(this, text, Toast.LENGTH_SHORT).show();
}



起動時にタイムラインを取得して表示する

reloadTimeLine()メソッドをonCreate()メソッドで認証済みの場合に呼び出すようにしましょう。


MainActivity.java

@Override

protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

if (!TwitterUtils.hasAccessToken(this)) {
Intent intent = new Intent(this, TwitterOAuthActivity.class);
startActivity(intent);
finish();
} else {
mAdapter = new TweetAdapter(this);
setListAdapter(mAdapter);

mTwitter = TwitterUtils.getTwitterInstance(this);
reloadTimeLine();
}
}



実行してみる

実行してみましょう。アプリ起動時にタイムラインの本文だけ以下のように表示されればOKです。


カスタムビューに挑戦

本文だけではあまりに寂しいので、アイコン画像・名前・スクリーンネーム(@hogeというやつ)も表示したいと思います。


レイアウトファイルを作る

まず、ListViewのアイテム1つ分のレイアウトファイルを作ります。


  1. res/layout/を右クリックして、「New」>「Other...」をクリックします。


  2. 「Android XML Layout File」を選択して、「Next」をクリックします。



  3. File:に「list_item_tweet」というファイル名を入力して、「RelativeLayout」を選択して、「Finish」をクリックします。




  4. 以下のようにXMLを変更します(説明は口頭で)。


    res/layout/list_item_tweet.xml

    <?xml version="1.0" encoding="utf-8"?>
    
    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:padding="8dp" >

    <ImageView
    android:id="@+id/icon"
    android:layout_width="40dp"
    android:layout_height="40dp"
    android:layout_alignParentLeft="true"
    android:layout_alignParentTop="true"
    android:src="@drawable/ic_launcher" />

    <TextView
    android:id="@+id/name"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_alignTop="@+id/icon"
    android:layout_marginLeft="8dp"
    android:layout_toRightOf="@+id/icon"
    android:text="名前入ります" />

    <TextView
    android:id="@+id/screen_name"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_alignTop="@+id/name"
    android:layout_marginLeft="8dp"
    android:layout_toRightOf="@+id/name"
    android:text="\uFF20gabu" />

    <TextView
    android:id="@+id/text"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_alignLeft="@+id/name"
    android:layout_below="@+id/name"
    android:text="本文本文本文本文本文本文本文本文本文本文" />

    </RelativeLayout>


    プレビューではこんな感じになるはずです。




ArrayAdapterのデータ型をStringからtwitter4j.Statusに変更する

ArrayAdapterで文字列だけではなくツイートの各データが欲しいので、管理するデータ型をStringからtwitter4j.Statusに変更します。


MainActivity.java

private class TweetAdapter extends ArrayAdapter<String> {


を、以下のように変更する。


MainActivity.java

private class TweetAdapter extends ArrayAdapter<twitter4j.Status> {


すると、インタフェースが変わってreloadTimeLine()メソッド内でエラーになるのでdoInBackground()メソッドで返す値もList型にする必要があります。さいわい、getHomeTimeline()メソッドの返り値が、ResponseList型なのでそのまま返せばOKです。ResponseListインタフェースがListインタフェースをextendsしているのでキャストも不要です。

まとめると、新しいreloadTimeLine()メソッドは、以下のようになります。


MainActivity.java

private void reloadTimeLine() {

AsyncTask<Void, Void, List<twitter4j.Status>> task = new AsyncTask<Void, Void, List<twitter4j.Status>>() {
@Override
protected List<twitter4j.Status> doInBackground(Void... params) {
try {
return mTwitter.getHomeTimeline();
} catch (TwitterException e) {
e.printStackTrace();
}
return null;
}

@Override
protected void onPostExecute(List<twitter4j.Status> result) {
if (result != null) {
mAdapter.clear();
for (twitter4j.Status status : result) {
mAdapter.add(status);
}
getListView().setSelection(0);
} else {
showToast("タイムラインの取得に失敗しました。。。");
}
}
};
task.execute();
}



ArrayAdapterクラスのgetView()メソッドをオーバーライドしてカスタムビューを返すようにする

ListViewはセットされているArrayAdapterクラスのgetView()メソッドの返り値であるViewを表示するので、このメソッドをオーバーライドしてやれば自分好みのレイアウトで表示することができます。

まず、レイアウトファイルからViewインスタンスを作るためのLayoutInflaterインスタンスをフィールド変数に用意しておきます。


MainActivity.java

private class TweetAdapter extends ArrayAdapter<Status> {

private LayoutInflater mInflater;

public TweetAdapter(Context context) {
super(context, android.R.layout.simple_list_item_1);
mInflater = (LayoutInflater) context.getSystemService(Activity.LAYOUT_INFLATER_SERVICE);
}
}


次に、getView()メソッドをオーバーライドします。


MainActivity.java

private class TweetAdapter extends ArrayAdapter<Status> {

private LayoutInflater mInflater;

public TweetAdapter(Context context) {
super(context, android.R.layout.simple_list_item_1);
mInflater = (LayoutInflater) context.getSystemService(Activity.LAYOUT_INFLATER_SERVICE);
}

@Override
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
convertView = mInflater.inflate(R.layout.list_item_tweet, null);
}
Status item = getItem(position);
TextView name = (TextView) convertView.findViewById(R.id.name);
name.setText(item.getUser().getName());
TextView screenName = (TextView) convertView.findViewById(R.id.screen_name);
screenName.setText("@" + item.getUser().getScreenName());
TextView text = (TextView) convertView.findViewById(R.id.text);
text.setText(item.getText());
return convertView;
}
}


Viewインスタンスは毎回レイアウトファイルから生成するのではなく、以下のように、再利用のためのViewインスタンスが入っているかconvertViewを調べて、nullの場合のみ生成するようにします。これで簡単に毎回生成するよりも高速化できるのでおすすめです。

    @Override

public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
convertView = mInflater.inflate(R.layout.list_item_tweet, null);
}

アイコン画像は少し面倒なのであとで対応します。


実行してみる

実行してみましょう。タイムラインに名前とスクリーンネームと本文が以下のように表示されればOKです。


アイコン画像を表示する

アイコン画像をImageViewで表示したいのですが、Twitter APIで取得できるアイコン画像のURLをImageViewで簡単に表示する方法がありません。あるにはあるのですが非常に面倒です。

そこで、画像のURLを指定するとインターネットから取得し、自動的にImageViewに表示してくれる Android Smart Image View という神ライブラリがあるのでこれを使うことにします。


Android Smart Image Viewをダウンロードしてプロジェクトに追加する


  1. http://loopj.com/android-smart-image-view/ にアクセスします。


  2. 「Download」をクリックしてダウンロードした android-smart-image-view-1.0.0.jar をプロジェクトのlibs/ディレクトリに置きます。



  3. レイアウトファイルの ImageView を com.loopj.android.image.SmartImageView に変更します。


    res/layout/list_item_tweet.xml

    <?xml version="1.0" encoding="utf-8"?>
    
    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:padding="8dp" >

    <com.loopj.android.image.SmartImageView
    android:id="@+id/icon"
    android:layout_width="40dp"
    android:layout_height="40dp"
    android:layout_alignParentLeft="true"
    android:layout_alignParentTop="true"
    android:src="@drawable/ic_launcher" />





  4. getView()メソッドの中でアイコン画像のURLをSmartImageViewにセットするように以下のコードを追加します。

    SmartImageView icon = (SmartImageView) convertView.findViewById(R.id.icon);
    
    icon.setImageUrl(item.getUser().getProfileImageURL());

    全体は以下のようになります。


    MainActivity.java

    @Override
    
    public View getView(int position, View convertView, ViewGroup parent) {
    if (convertView == null) {
    convertView = mInflater.inflate(R.layout.list_item_tweet, null);
    }
    Status item = getItem(position);
    TextView name = (TextView) convertView.findViewById(R.id.name);
    name.setText(item.getUser().getName());
    TextView screenName = (TextView) convertView.findViewById(R.id.screen_name);
    screenName.setText("@" + item.getUser().getScreenName());
    TextView text = (TextView) convertView.findViewById(R.id.text);
    text.setText(item.getText());
    SmartImageView icon = (SmartImageView) convertView.findViewById(R.id.icon);
    icon.setImageUrl(item.getUser().getProfileImageURL());
    return convertView;
    }




実行してみる

実行してみましょう。以下のようにアイコン画像が表示されればOKです。

だいぶ、Twitterクライアントっぽくなってきましたね!


メニューから更新できるようにする

まだまだ機能が足りないサンプルアプリですが最後に1つだけメニューから更新できるようにしてみましょう。


メニューアイコン画像を追加する

まず、以下からメニューアイコン画像をダウンロードして、プロジェクトのres/drawable-xhdpi/に置いてください。

https://dl.dropbox.com/u/944822/start-android-2/ic_action_refresh.png


メニュー定義を追加する


  1. res/menu/activity_main.xml を開きます。



  2. デフォルトで設定メニューが定義されていますが不要なので削除して、以下のように更新メニューを定義します。


    res/menu/activity_main.xml

    <menu xmlns:android="http://schemas.android.com/apk/res/android" >

    <item
    android:id="@+id/menu_refresh"
    android:icon="@drawable/ic_action_refresh"
    android:orderInCategory="100"
    android:showAsAction="always"
    android:title="@string/menu_refresh"/>

    </menu>





  3. @string/menu_refresh が未定義なので res/values/strings.xmlに追加します。


    res/values/strings.xml

    <?xml version="1.0" encoding="utf-8"?>
    
    <resources>
    ...
    <string name="menu_refresh">更新</string>
    ...




  4. 実行してみて以下のようにActionBarにメニューアイコン画像が表示されればOKです。




メニューが選択された時の処理を追加する

メニューが選択されると、アクティビティのonOptionsItemSelected()メソッドが呼び出されます。このメソッドをオーバーライドして、どのメニューが選択されたかを判定して処理を行います。

ということで、MainActivity.javaで以下のようにonOptionsItemSelected()メソッドをオーバーライドします。


MainActivity.java

@Override

public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_refresh:
reloadTimeLine();
return true;
}
return super.onOptionsItemSelected(item);
}


実行してみる

実行してみましょう。更新メニューをタップしてタイムラインが更新されればOKです。

以上でタイムラインの表示の基本は完了です。お疲れ様でした!

まだまだ実装するべき機能はたくさんありますよね?ぜひ引き続き実装してみてください。勉強会の都合上、資料的には次に進みたいと思います(すみませぬ・・・)。

次は、いよいよつぶやけるようにします。

目次へ戻る