- 追加された行はこの色です。
- 削除された行はこの色です。
[[Software/Android/アプリケーション開発テキスト]]
## 執筆中 ##
*Chapter 4 : インテントによる外部アプリケーションとの連携 [#n2547265]
*Chapter 4 : HTTP通信を使用したアプリケーション開発 [#n2547265]
この章では画像リサイズツールの開発を通して、インテントによる外部アプリケーションとの連携について解説します。
この章では簡易RSSリーダーを開発し、HTTP通信を使用するアプリケーションについて解説します。
このアプリケーションはインテントによりギャラリーやカメラアプリを呼び出し、そこから受け取った画像を指定サイズにリサイズしてギャラリーに保存を行います。
このアプリケーションは設定したRSSを取得して、一覧と詳細表示を行います。詳細画面には外部リンクを併せて表示し、タップすることで外部ブラウザが開くようにします。
新規Androidアプリケーションプロジェクトを以下の設定で作成します。
-Project Nameに「IResizer」と入力します
-Application Nameに「IResizer」と入力します
-Package Nameに「com.beatcraft.iresizer」と入力します
-Create Activityにチェックを入れ、「IResizerActivity」と入力します
-Project Nameに「RSSReader」と入力します
-Application Nameに「RSSReader」と入力します
-Package Nameに「com.beatcraft.rssreader」と入力します
-Create Activityにチェックを入れ、「RSSReaderActivity」と入力します
~
~
**3-1. アプリケーションのレイアウト作成 [#s8ab65c3]
**4-1. アプリケーションのレイアウト作成 [#s8ab65c3]
今回作成するアプリケーションでは、縦画面時と横画面時でそれぞれのレイアウトを用意します。図3-1が縦画面、図3-2が横画面のイメージになります。
RSSReaderでは3つのアクティビティを使用します。メイン画面であり、一覧表示を行うアクティビティ(図4-1)、一覧からのタップで詳細表示を行うアクティビティ(図4-2)、取得するRSSフィードを設定するアクティビティ(図4-3)の3つです。
CENTER:&ref(./03_01.png,);
CENTER:図3-1~
CENTER:&ref(./04_01.png,);
CENTER:図4-1~
~
CENTER:&ref(./03_02.png,);
CENTER:図3-2~
CENTER:&ref(./04_02.png,);
CENTER:図4-2~
~
CENTER:&ref(./04_03.png,);
CENTER:図4-3~
~
~
黒い余白部分は他アプリケーションから受け取った画像をプレビュー表示する領域となり、その下、もしくは右に操作部分が配置されています。今回は操作部を別レイアウトとして作成し、それぞれのレイアウトで取り込んで利用することとします。
各レイアウトを作成する前に、文字列リソースを定義しておきます。strings.xmlに以下を追加して下さい。helloは不要なので削除します。
レイアウトを作成する前に利用する文字列リソースを定義しておきます。リソースのstrings.xmlをダブルクリックして開き、以下の文字列リソースを追加します。
<string name="select_image">画像を選択</string>
<string name="take_photo">写真を撮影</string>
<string name="label_w">横</string>
<string name="label_h">縦</string>
<string name="keep_ratio">比率を維持</string>
<string name="save">保存</string>
<string name="get_feed">取得</string>
<string name="config">設定</string>
<string name="regist">登録</string>
<string name="cancel">取消</string>
<string name="back">戻る</string>
<string name="label_number_of_get">取得件数:</string>
<string name="loading">読み込み中...</string>
<string-array name="number_of_get">
<item>1件</item>
<item>3件</item>
<item>5件</item>
</string-array>
~
まずは操作部のレイアウトを作成します。IResizerをクリックして選択し、メニューから「File > New > Other...」を選択します。"Select a wizard"ダイアログが表示されたら、「Android > Android XML Layout File」を選択して「Next」ボタンをクリックして下さい。
string-arrayは文字列の配列で、ドロップダウンリスト(Spinner)で使用します。
続く"New Android Layout XML File"でFileに「controller.xml」と入力し、「Finish」ボタンをクリックするとレイアウトリソースファイルが作成されます。作成したcontroller.xmlを開き、ボタンやテキストボックスを配置していきます。
リスト4-1がメイン/一覧画面のレイアウトファイルになります。
左部のPaletteからForm Widgetsをクリックして展開し、「Button」をドラッグ&ドロップで配置します。配置したボタンをクリックし、レイアウタ下部の「Properties」で以下のように設定を変更します。
-Idに「@+id/SelectImage」と入力します。
-Textに「@string/select_image」と入力します。
-Layout heightに「wrap_content」を指定します。
-Layout margin leftとLayout margin rightに「10dip」と入力します。
-Layout widthに「fill_parent」を指定します。
-リスト4-1 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:id="@+id/linearLayout1"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_margin="10dip" >
<Button
android:id="@+id/Get"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="5dip"
android:layout_marginRight="5dip"
android:layout_weight="1"
android:text="@string/get_feed" />
<Button
android:id="@+id/Config"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="5dip"
android:layout_marginRight="5dip"
android:layout_weight="1"
android:text="@string/config" />
</LinearLayout>
<ListView
android:id="@+id/android:list"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:fastScrollEnabled="true" >
</ListView>
</LinearLayout>
~
同様にボタンをもう一つ追加して、以下のように設定を変更します。
横方向のレイアウトにフィード取得ボタンと設定ボタンが並び、その下にリストビューが配置されています。リストビューの名前が「android:list」となっていますが、これは後ほど解説するListActivityでの埋め込みリストビューの名前で、必ずこの名前を使用する必要があります。
-Idに「@+id/TakePhoto」と入力します。
-Textに「@string/take_photo」と入力します。
-Layout heightに「wrap_content」を指定します。
-Layout margin leftとLayout margin rightに「10dip」と入力します。
-Layout widthに「fill_parent」を指定します。
次に一覧表示で使用するリストアイテムのレイアウトを作成します。リスト4-2がフィードのタイトルを表示するレイアウトで、グレーの背景に黒のTextViewを置いています。リスト4-3は記事タイトルと出版日を表示するレイアウトで、黒地に白のTextViewを二つ置いています。図4-1でこれらがどのように表示されるか、もう一度確認してみて下さい。
続いて横サイズの入力項目を配置していきます。「LinearLayout (Horizontal)」をドラッグ&ドロップで配置し、以下のように設定を変更します。
-Idに「@+id/WLayout」と入力します。
-Layout heightに「wrap_content」を指定します。
-Layout margin leftとLayout margin rightに「10dip」と入力します。
-Layout widthに「fill_parent」を指定します。
-リスト4-2 item_channel.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:background="#cccccc"
android:orientation="vertical" >
<TextView
android:id="@+id/FeedTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="5dip"
android:text="FeedTitle"
android:textColor="#000000"
android:textSize="16dip"
android:textStyle="bold" />
</LinearLayout>
~
TextViewを上記レイアウトに追加し、以下のように設定を変更します。
-リスト4-3 item_item.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/ArticleTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="5dip"
android:text="ArticleTitle" />
<TextView
android:id="@+id/PubDate"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="right"
android:layout_margin="5dip"
android:text="pubDate" />
</LinearLayout>
~
-Idに「@+id/WLabel」と入力します。
-Textに「@string/label_w」と入力します。
-Layout heightに「wrap_content」を指定します。
-Layout widthに「wrap_content」を指定します。
以上でメイン画面のレイアウト作成は完了です。
EditTextを上記レイアウトに追加し、以下のように設定を変更します。
続いて詳細表示画面のリストを示します(リスト4-4)。
-Idに「@+id/WSize」と入力します。
-Input typeに「number」を指定します。
-Layout heightに「wrap_content」を指定します。
-Layout weightに「1」と入力します。
-Layout widthに「wrap_content」を指定します。
-リスト4-4 description.xml :
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/ScrollView"
android:layout_width="fill_parent"
android:layout_height="fill_parent" >
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >
<Button
android:id="@+id/Back"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="5dip"
android:text="@string/back" />
<TextView
android:id="@+id/FeedTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="10dip"
android:layout_marginRight="10dip"
android:text="FeedTitle"
android:textSize="16dip"
android:textStyle="bold" />
<TextView
android:id="@+id/ArticleTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="10dip"
android:layout_marginRight="10dip"
android:text="ArticleTitle" />
<TextView
android:id="@+id/PubDate"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="right"
android:layout_marginRight="10dip"
android:text="pubDate" />
<android.webkit.WebView
android:id="@+id/Description"
android:layout_width="fill_parent"
android:layout_height="0dip"
android:layout_margin="5dip"
android:layout_weight="1" >
</android.webkit.WebView>
</LinearLayout>
</ScrollView>
~
TextViewを上記レイアウトに追加し、以下のように設定を変更します。
このレイアウトではまず、最上位のレイアウトがLinearLayoutではなくScrollViewになっていることに注目してください。これにより、情報量の多いフィードであってもスクロールして表示することが出来るようになります。~
ScrollViewの下に縦方向のLinearLayoutが配置され、前画面に戻るボタン、フィードのタイトル、記事タイトル、出版日を表示するTextView、そしてWebViewが配置されています。~
WebViewはandroid.webkitに含まれる埋め込みHTMLブラウザ用のビューで、RSSフィードのdescriptionにはHTMLタグが使用されることが多いためTextViewの代わりに使用しています。
-Idに「@+id/WSuffix」と入力します。
-Textに「px」と入力します。
-Layout heightに「wrap_content」を指定します。
-Layout widthに「wrap_content」を指定します。
最後に設定画面のレイアウトを作成します(リスト4-5)。
縦サイズの入力項目も横サイズ同様に配置していきます。「LinearLayout (Horizontal)」をドラッグ&ドロップで配置し、以下のように設定を変更します。
-リスト4-5 config.xml :
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/ScrollView"
android:layout_width="fill_parent"
android:layout_height="fill_parent" >
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >
<LinearLayout
android:id="@+id/linearLayout1"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_margin="10dip" >
<TextView
android:id="@+id/LabelNumOfGet"
android:layout_width="wrap_content"
android:layout_height="fill_parent"
android:gravity="center_vertical"
android:text="@string/label_number_of_get" />
<Spinner
android:id="@+id/NumOfGet"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
<EditText
android:id="@+id/FeedURL01"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="10dip"
android:layout_marginRight="10dip"
android:inputType="textUri" >
<requestFocus />
</EditText>
<EditText
android:id="@+id/FeedURL02"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="10dip"
android:layout_marginRight="10dip"
android:inputType="textUri" />
<EditText
android:id="@+id/FeedURL03"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="10dip"
android:layout_marginRight="10dip"
android:inputType="textUri" />
<EditText
android:id="@+id/FeedURL04"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="10dip"
android:layout_marginRight="10dip"
android:inputType="textUri" />
<EditText
android:id="@+id/FeedURL05"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="10dip"
android:layout_marginRight="10dip"
android:inputType="textUri" />
<EditText
android:id="@+id/FeedURL06"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="10dip"
android:layout_marginRight="10dip"
android:inputType="textUri" />
<EditText
android:id="@+id/FeedURL07"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="10dip"
android:layout_marginRight="10dip"
android:inputType="textUri" />
<EditText
android:id="@+id/FeedURL08"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="10dip"
android:layout_marginRight="10dip"
android:inputType="textUri" />
<EditText
android:id="@+id/FeedURL09"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="10dip"
android:layout_marginRight="10dip"
android:inputType="textUri" />
<EditText
android:id="@+id/FeedURL10"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="10dip"
android:layout_marginRight="10dip"
android:inputType="textUri" />
<LinearLayout
android:id="@+id/linearLayout2"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_margin="10dip" >
<Button
android:id="@+id/Cancel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="5dip"
android:layout_marginRight="5dip"
android:layout_weight="1"
android:text="@string/cancel" />
<Button
android:id="@+id/Regist"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="5dip"
android:layout_marginRight="5dip"
android:layout_weight="1"
android:text="@string/regist" />
</LinearLayout>
</LinearLayout>
</ScrollView>
~
-Idに「@+id/HLayout」と入力します。
-Layout heightに「wrap_content」を指定します。
-Layout margin leftとLayout margin rightに「10dip」と入力します。
-Layout widthに「fill_parent」を指定します。
設定画面もScrollViewを最上位に置き、スクロール可能にしています。上部にはフィード毎の記事取得件数を指定するドロップダウンリスト(Spinner)を配置し、フィードURL入力用EditTextを10個、設定の取消、保存ボタンを続けて配置します。
TextViewを上記レイアウトに追加し、以下のように設定を変更します。
これで全ての画面レイアウトが完成しました。
-Idに「@+id/HLabel」と入力します。
-Textに「@string/label_h」と入力します。
-Layout heightに「wrap_content」を指定します。
-Layout widthに「wrap_content」を指定します。
EditTextを上記レイアウトに追加し、以下のように設定を変更します。
**4-2. アプリケーションクラスとフィードデータクラスの実装 [#f46b5898]
-Idに「@+id/HSize」と入力します。
-Input typeに「number」を指定します。
-Layout heightに「wrap_content」を指定します。
-Layout weightに「1」と入力します。
-Layout widthに「wrap_content」を指定します。
各アクティビティを作成する前に、ApplicationクラスとRSSフィード用データクラスを実装していきます。
TextViewを上記レイアウトに追加し、以下のように設定を変更します。
Applicationクラスはアプリケーションが起動されると単一のインスタンスが生成され、アプリケーション全体でデータを共有出来ます。ここではApplicationクラスを継承していくつかの定数と設定ファイルへのアクセスを提供します。~
android.app.ApplicationをベースクラスとしてRSSReaderApplicationを生成し、いくつかの定数とメンバ変数を追加します(リスト4-6)。
-Idに「@+id/HSuffix」と入力します。
-Textに「px」と入力します。
-Layout heightに「wrap_content」を指定します。
-Layout widthに「wrap_content」を指定します。
-リスト4-6 :
public class RSSReaderApplication extends Application {
private static final String CONF_NAME = "rssreader.conf";
public static final int DEFAULT_NUM_OF_GET = 3;
public static final int NUM_OF_FEED = 10;
private int mNumOfGet = DEFAULT_NUM_OF_GET;
private String mFeedURL[];
~
続いてPaletteからForm Widgetsをクリックして展開し、「CheckBox」をドラッグ&ドロップで配置します。配置したチェックボックスの設定を以下のように変更します。
CONF_NAMEはこのアプリケーションで使用する設定ファイル名ですが、設定へのアクセスは全てこのクラスを通して行うためprivateにします。~
DEFAULT_NUM_OF_GETはフィード毎の記事取得デフォルト件数、NUM_OF_FEEDはフィードの登録可能数を表す定数値です。~
mNumOfGetとmFeedURLは現在の設定値を保存するメンバ変数で、フィード毎の取得件数と取得フィードのURL配列になっています。設定と取得にはアクセサを使用するため、privateにしてあります。
-Checkedに「true」を指定します。
-Idに「@+id/KeepRatio」と入力します。
-Textに「@string/keep_ratio」と入力します。
-Layout heightに「wrap_content」を指定します。
-Layout margin leftとLayout margin rightに「10dip」と入力します。
-Layout widthに「fill_parent」を指定します。
-リスト4-7 :
@Override
public void onCreate() {
mFeedURL = new String[NUM_OF_FEED];
loadConfig();
}
~
最後にもう一つボタンを追加します。
onCreateメソッド(リスト4-7)ではフィードURL配列mFeedURLをNUM_OF_FEED分確保し、後ほど実装するloadConfigメソッドで設定ファイルの読み込みを行なっています。
-Idに「@+id/Save」と入力します。
-Textに「@string/save」と入力します。
-Layout heightに「wrap_content」を指定します。
-Layout margin leftとLayout margin rightに「10dip」と入力します。
-Layout widthに「fill_parent」を指定します。
-リスト4-8 :
public int numberOfGet() {
return mNumOfGet;
}
public void setNumberOfGet(int numOfGet) {
mNumOfGet = numOfGet;
}
public String feedURL(int index) {
if ((index < 0) || (10 <= index)) {
return null;
}
return mFeedURL[index];
}
public void setFeedURL(int index, String url) {
if ((index < 0) || (NUM_OF_FEED <= index)) {
return;
}
mFeedURL[index] = url;
}
public void clearFeedURL() {
for (int i = 0; i < NUM_OF_FEED; ++i) {
mFeedURL[i] = "";
}
}
~
以上で操作部のレイアウトは完成です。完成後のアウトラインとレイアウトイメージ(図3-3)を下記に示します。
onCreateに続いて、フィード取得件数と取得フィードURLのアクセサを実装します(リスト4-8)。
|LEFT:|CENTER:|c
| LinearLayout&br; SelectImage&br; TakePhoto&br; WLayout&br; WLabel&br; WSize&br; WSuffix&br; HLayout&br; HLabel&br; HSize&br; HSuffix&br; KeepRatio&br; Save|&ref(./03_03.png,);&br;図3-3|
-リスト4-9 :
public void loadConfig() {
SharedPreferences pref = getSharedPreferences(CONF_NAME, MODE_PRIVATE);
mNumOfGet = pref.getInt("NumOfGet", DEFAULT_NUM_OF_GET);
for (int i = 0; i < NUM_OF_FEED; ++i) {
String key = String.format("FeedURL%02d", (i + 1));
mFeedURL[i] = pref.getString(key, "");
}
}
public void saveConfig() {
SharedPreferences pref = getSharedPreferences(CONF_NAME, MODE_PRIVATE);
Editor pe = pref.edit();
pe.putInt("NumOfGet", mNumOfGet);
for (int i = 0; i < NUM_OF_FEED; ++i) {
String key = String.format("FeedURL%02d", (i + 1));
pe.putString(key, mFeedURL[i]);
}
pe.commit();
}
~
縦画面用のレイアウトを作成していきましょう。プロジェクト作成時に自動生成されたmain.xmlを開き、「Hello World, IResizerActivity!」というテキストが設定されたTextViewを削除します。
loadConfig、saveConfigで設定ファイルの読み書きを行います(リスト4-9)。loadConfigのgetSharedPreferencesでSharedPreferencesクラスのインスタンスを取得していますが、指定のファイル(CONF_NAME)が存在しない場合はこの段階で自動的に作成されます。第二引数に渡しているMODE_PRIVATEはこの設定ファイルが本アプリケーションのみでアクセス可能であることを示し、ファイルが自動生成される際のパーミッションにも反映されます。~
プリファレンスクラスを利用すると、各設定内容をキーにより呼び出してアクセスすることが出来ます。ここではgetIntとgetStringメソッドでそれぞれフィード取得件数と取得フィードURLを取得しています。
次に左部のPaletteからImages & Mediaをクリックして展開し、「ImageView」をドラッグ&ドロップで配置します(この際に「Resource Chooser」というダイアログが表示された場合は「Clear」ボタンをクリックして下さい)。配置した画像ビューをクリックし、レイアウタ下部の「Properties」で以下のように設定を変更します。
プリファレンスクラス自体には値の書き換えメソッドは用意されていないため、saveConfigではSharedPreferencesを取得したあと、Editorクラスを利用して設定内容の書き換えを行っています。
-Adjust view boundsに「true」を指定します。
-Idに「@+id/Preview」と入力します。
-Scale typeに「fitCenter」を指定します。
-Layout heightに「0dip」と入力します。
-Layout marginに「5dip」と入力します。
-Layout weightに「1」と入力します。
-Layout widthに「fill_parent」を指定します。
続いてフィードデータ用のクラスを実装していきます。フィードデータ用クラスは個々のフィードデータを格納するFeedItemと、それをリスト管理するFeedListの二つのクラスで構成されます。また、FeedListではHTTP通信でRSSフィードを取得し、そこからFeedItemを作成する処理も組み込んでいきます。
ここで先ほど作成した操作部レイアウトを取り込みます。レイアウタ下部にある「main.xml」タブをクリックしてXMLエディタを表示し、上記ImageViewの下に以下を追加します。
FeedItemクラスの全リストが4-10になります。RSSフィード(channel)のタイトルと記事双方をこのクラスで取り扱うため、どちらのデータであるかを示すアイテムタイプと、フィードのタイトル、記事のタイトルと出版日、記事詳細と記事のリンクURLをメンバ変数に持ちます。
<include
android:layout_width="fill_parent"
android:layout_height="wrap_content"
layout="@layout/controller" />
これで先ほど作成した操作部のレイアウトが取り込まれ、図3-1のようなレイアウトになります(※)。~
※Graphical Layoutタブで確認するとcontrollerリソースが無いとエラーが出ることがあります。この場合は一度アプリケーションを実行したあとにmain.xmlを閉じ、Eclipseを再起動すると正常に表示されます。
-リスト4-10 FeedItem.java :
package com.beatcraft.rssreader;
public class FeedItem {
public static final int ITEMTYPE_FEEDCHANNEL = 0;
public static final int ITEMTYPE_FEEDITEM = 1;
private int mItemType;
private String mFeedTitle = "";
private String mArticleTitle = "";
private String mPubDate = "";
private String mDescription = "";
private String mLink = "";
public FeedItem(int itemType) {
mItemType = itemType;
}
public int itemType() {
return mItemType;
}
public String feedTitle() {
return mFeedTitle;
}
public void setFeedTitle(String title) {
mFeedTitle = title;
}
public String articleTitle() {
return mArticleTitle;
}
public void setArticleTitle(String title) {
mArticleTitle = title;
}
public String pubDate() {
return mPubDate;
}
public void setPubDate(String pubDate) {
mPubDate = pubDate;
}
public String description() {
return mDescription;
}
public void setDescription(String description) {
mDescription = description;
}
public String link() {
return mLink;
}
public void setLink(String link) {
mLink = link;
}
}
~
同様にして横画面用のレイアウトも作成します。まずはIResizerのresフォルダを右クリックし、メニューから「New > Folder」を選択し「layout-land」フォルダを作成します。このフォルダに配置されたレイアウトは、横画面時に優先的に利用されます。
アイテムタイプがITEMTYPE_FEEDCHANNELであればフィードのタイトルデータ、ITEMTYPE_FEEDITEMであれば記事データとなります。
layoutフォルダにあるmain.xmlをドラッグ&ドロップでlayout-landフォルダにコピーし、これを横画面用に変更していきます。
FeedListクラスでは配列リストクラスArrayListにFeedItemを格納して管理します。
まずは最上位にあるLinearLayoutの設定を以下のように変更します。
-リスト4-11 :
public class FeedList {
private ArrayList<FeedItem> mList = null;
public FeedList() {
mList = new ArrayList<FeedItem>();
}
public ArrayList<FeedItem> getList() {
return mList;
}
public int count() {
if (mList != null) {
return mList.size();
}
return 0;
}
~
-Orientationを「horizontal」に変更します。
FeedItemをテンプレートとしたArrayListのメンバ変数mListをコンストラクタで生成し、getList、countを追加して外部からのアクセスが行えるようにします(リスト4-11)。
続いてプレビュー用の画像ビュー(Preview)の設定を以下のように変更します。
ここまででHTTP通信によるデータの受け皿が準備出来ました。これから実装するgetメソッドで、実際にHTTP通信を行いRSSフィードの内容を取得します(リスト4-12)。AndroidでのHTTP通信にはいくつかの手段が提供されていますが、本稿ではApacheのHTTPクラスライブラリを使用します。
-Layout heightを「fill_parent」に変更します。
-リスト4-12 :
public int get(RSSReaderApplication app) {
int success = 0;
for (int i = 0; i < RSSReaderApplication.NUM_OF_FEED; ++i) {
String url = app.feedURL(i);
if (url.equals("") == true) {
continue;
}
DefaultHttpClient client = new DefaultHttpClient();
if (client != null) {
client.getParams().setParameter("http.socket.timeout", new Integer(15000));
HttpGet method = null;
try {
method = new HttpGet(url);
}
catch (Exception e) {
e.printStackTrace();
}
if (method == null) {
continue;
}
HttpResponse response = null;
try {
response = client.execute(method);
int ret = response.getStatusLine().getStatusCode();
if (ret == HttpStatus.SC_OK) {
InputStream is = response.getEntity().getContent();
if (parse(is, app.numberOfGet()) > 0) {
success++;
}
is.close();
}
}
catch (Exception e) {
e.printStackTrace();
}
finally {
client.getConnectionManager().shutdown();
}
}
}
return success;
}
~
最後に取り込んだcontrollerレイアウトの設定を以下のように変更します。
getメソッドは、引数としてRSSReaderApplicationのインスタンスを受け取ります。これを利用して取得フィードURLを一つずつ取得し(app.feedURLメソッド)、HTTP-GETメソッドによりRSSフィードを取得していきます。~
まずはDefaultHttpClientのインスタンスを生成します。生成に成功したら、HTTPクライアントのgetParamsで取得したパラメータリストに対しsetParameterを呼び出し、15秒のタイムアウト値を設定しています。
-Layout heightを「fill_parent」に変更します。
-Layout widthを「wrap_content」に変更します。
次にHTTP通信で使用するメソッドクラスのインスタンスを生成します。今回はGETメソッドを使用しますので、HttpGetクラスに取得フィードのURLを渡して生成しています。~
メソッドの生成に成功したら、HTTPクライアントのexecuteにこれを渡して呼び出します。executeからはHttpResponseクラスのインスタンスが返ってきますが、これを利用してステータスコードやGET結果を取得することが出来ます。~
まずはgetStatusLine().getStatusCodeでステータスコードを取得、これがHttpStatus.SC_OKであればgetEntity().getContentでGET結果の内容を取得しています。GET結果はInputStreamとして返ってきますので、後ほど実装するparseメソッドでこの解析を行います。parseメソッドにはInputStreamの他、フィード取得件数をアプリケーションクラスから取得して引き渡しています。~
最後にfinallyブロックでHTTPクライアントからコネクションマネージャーを取得し、shutdownメソッドでリソースの解放を行なっています。
以上で横画面用レイアウトも完成です。Graphcal Layoutタブで図3-2のようになっていることを確認してください。
RSSフィードはXMLで記述されているため、これを解析する必要があります。parseメソッド(4-13)ではXmlPullParserを使用してこの解析を行っています。XmlPullParserはXMLPULL API V1に基づいたAndroidでの実装になります。
-リスト4-13 :
private int parse(InputStream is, int max) {
int count = 0;
boolean inChannel = false;
boolean inItem = false;
FeedItem item = null;
String feedTitle = "";
XmlPullParser p = Xml.newPullParser();
try {
p.setInput(is, null);
int event = p.getEventType();
while (event != XmlPullParser.END_DOCUMENT) {
String elem = null;
String tmp = null;
switch (event) {
case XmlPullParser.START_TAG:
elem = p.getName();
if (elem.equals("channel") == true) {
inChannel = true;
item = new FeedItem(FeedItem.ITEMTYPE_FEEDCHANNEL);
}
else if (elem.equals("item") == true) {
if (inChannel == true) {
if (item != null) {
mList.add(item);
item = null;
count++;
}
inChannel = false;
}
inItem = true;
item = new FeedItem(FeedItem.ITEMTYPE_FEEDITEM);
item.setFeedTitle(feedTitle);
}
else if (elem.equals("title") == true) {
tmp = p.nextText();
if ((tmp != null) && (item != null)) {
if (inChannel == true) {
feedTitle = tmp;
item.setFeedTitle(tmp);
}
else if (inItem == true) {
item.setArticleTitle(tmp);
}
}
}
else if ((elem.equals("pubDate") == true)
|| (elem.equals("date") == true)) {
if (inItem == true) {
tmp = p.nextText();
if ((tmp != null) && (item != null)) {
item.setPubDate(tmp);
}
}
}
else if (elem.equals("description") == true) {
if (inItem == true) {
tmp = p.nextText();
if ((tmp != null) && (item != null)) {
item.setDescription(tmp);
}
}
}
else if (elem.equals("link") == true) {
if (inItem == true) {
tmp = p.nextText();
if ((tmp != null) && (item != null)) {
item.setLink(tmp);
}
}
}
break;
case XmlPullParser.END_TAG:
elem = p.getName();
if (elem.equals("channel") == true) {
if (inChannel == true) {
if (item != null) {
mList.add(item);
item = null;
count++;
}
inChannel = false;
}
}
else if (elem.equals("item") == true) {
if (inItem == true) {
if (item != null) {
mList.add(item);
item = null;
count++;
max--;
if (max == 0) {
return count;
}
}
inItem = false;
}
}
}
event = p.next();
}
}
catch (Exception e) {
e.printStackTrace();
return 0;
}
return count;
}
}
~
**3-2. ユーザーインターフェースの実装 [#q25d4056]
まずXml.newPullParserメソッドでXmlPullParserのインスタンスを生成し、渡されたInputStreamを入力としてセットしています。これにより、取得したRSSフィードの解析が可能になります。XmlPullParserでは、XMLを順次読み取って解析を行います。パーサーのgetEventTypeメソッドで現在のXMLドキュメントの読み取り状態を取得し、これがドキュメントの終了を示すXmlPullParser.END_DOCUMENTになるまでwhileループで解析を続けています。~
状態が要素の開始を示すXmlPullParser.START_TAGになったら、パーサーのgetNameメソッドで要素名を取得します。要素名が"channel"であればFeedItemをITEMTYPE_FEEDCHANNELで生成し、その後"title"要素が現れた際にフィードタイトルとして設定しています。~
要素名が"item"の場合、フィードタイトルの解析中であればこれをFeedListに追加し、新たにFeedItemをITEMTYPE_FEEDITEMで生成しています。その後"title"が現れた際に記事タイトルを、"pubDate"もしくは"date"が現れたら出版日を、"description"、"link"が現れたら記事詳細とリンクURLをそれぞれ設定します。~
状態がXmlPullParser.END_TAGになったら、解析中のFeedItemがあればこれをリストに追加します。ITEMTYPE_FEEDITEMをリストに追加する際には変数countをインクリメントし、その後引数で渡された取得件数以上であれば解析を終了します。~
最後にパーサーのnextメソッドを呼び出し、状態を次に進めてwhileループの先頭に戻ります。
前項で作成したレイアウトをアクティビティに反映し、ボタンやテキストボックスに処理を実装していきます。
以上でRSSフィードの解析は完了です。
IResizerActivity.javaを開き、IResizerActivityに以下のメンバ変数を追加します。
private ImageView mPreview;
private EditText mWSize;
private EditText mHSize;
private Bitmap mBitmap = null;
private int mOrgW = 0;
private int mOrgH = 0;
**4-3. 設定画面の実装 [#je8997f8]
ImageViewとEditText、Bitmapクラスを利用可能にするためにはパッケージのimportが必要になります。Eclipseのエディタでクラス名の上にマウスカーソルを置き、図3-4のようなポップアップメニューが表示されたら「import 'ImageView' (android.widget)」行をクリックすると自動的にそのクラスのパッケージimportが挿入されます。
取得するフィードを設定するため、設定画面アクティビティを実装します。android.app.ActivityをベースクラスとしてConfigActivityを生成し、いくつかの定数とメンバ変数を追加します(リスト4-14)。ボタンのクリック処理をこのクラスで行うため、View.OnClickListenerのimplementsも追加して下さい。
CENTER:&ref(./03_04.png,);
CENTER:図3-4~
-リスト4-14 :
public class ConfigActivity extends Activity implements View.OnClickListener {
private static final int NUM_OF_GET_LIST[] = {1, 3, 5};
private Spinner mNumOfGet;
private EditText mFeedURL[];
~
mBitmap、mOrgWとmOrgHの三つのメンバ変数は他のアプリケーションから取得した画像とそのサイズを保持するための物です。
NUM_OF_GET_LISTはフィード取得数として選択可能な数を格納する定数配列で、ドロップダウンリスト(Spinner)の選択内容に対応した値1/3/5が設定されています。~
mNumOfGetとmFeedURLはフィード取得数Spinnerと取得フィールドURLのEditTextウィジェットのインスタンスを保持しておくメンバ変数です。
続いて、このクラスで各ボタンのクリック処理を行うためandroid.view.ViewのimportとView.OnClickListenerのimplementsを追加します。
import android.view.View;
-リスト4-15 :
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
this.getWindow().setSoftInputMode(LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN);
setContentView(R.layout.config);
public class IResizerActivity extends Activity implements View.OnClickListener {
onCreateメソッドの後にonClickメソッドと二つのprivateメソッドを追加します(リスト3-1)。onClickメソッドの処理は後ほど実装するため、空のままにしておきます。
-リスト3-1 :
@Override
publiic void onClick(View view) {
}
RSSReaderApplication app = (RSSReaderApplication) getApplication();
private void setupUI() {
setContentView(R.layout.main);
ArrayAdapter<CharSequence> adapter;
adapter = ArrayAdapter.createFromResource(this, R.array.number_of_get,
android.R.layout.simple_spinner_item);
adapter.setDropDownViewResource(
android.R.layout.simple_spinner_dropdown_item);
mNumOfGet = (Spinner) findViewById(R.id.NumOfGet);
mNumOfGet.setAdapter(adapter);
for (int i = 0; i < 3; ++i) {
if (NUM_OF_GET_LIST[i] == app.numberOfGet()) {
mNumOfGet.setSelection(i);
break;
}
}
Button button;
button = (Button) findViewById(R.id.SelectImage);
button.setOnClickListener(this);
button = (Button) findViewById(R.id.TakePhoto);
button.setOnClickListener(this);
button = (Button) findViewById(R.id.Save);
button.setOnClickListener(this);
mPreview = (ImageView) findViewById(R.id.Preview);
mWSize = (EditText) findViewById(R.id.WSize);
mHSize = (EditText) findViewById(R.id.HSize);
setPreview();
mWSize.setOnFocusChangeListener(new View.OnFocusChangeListener() {
@Override
public void onFocusChange(View v, boolean flag) {
if (flag == false) {
CheckBox keepRatio = (CheckBox) findViewById(R.id.KeepRatio);
if (keepRatio.isChecked() == true) {
String base = mWSize.getText().toString().trim();
if ((base.equals("") == false)
&& (mOrgW != 0) && (mOrgH != 0)) {
double h = 0;
double w = Integer.parseInt(base);
double ratio = (double) mOrgH / (double) mOrgW;
h = new BigDecimal(w * ratio).setScale(0, BigDecimal.ROUND_HALF_UP).doubleValue();
if (h < 1.0) {
h = 1.0;
}
mHSize.setText(String.valueOf((int) h));
}
}
}
mFeedURL = new EditText[RSSReaderApplication.NUM_OF_FEED];
mFeedURL[0] = (EditText) findViewById(R.id.FeedURL01);
mFeedURL[1] = (EditText) findViewById(R.id.FeedURL02);
mFeedURL[2] = (EditText) findViewById(R.id.FeedURL03);
mFeedURL[3] = (EditText) findViewById(R.id.FeedURL04);
mFeedURL[4] = (EditText) findViewById(R.id.FeedURL05);
mFeedURL[5] = (EditText) findViewById(R.id.FeedURL06);
mFeedURL[6] = (EditText) findViewById(R.id.FeedURL07);
mFeedURL[7] = (EditText) findViewById(R.id.FeedURL08);
mFeedURL[8] = (EditText) findViewById(R.id.FeedURL09);
mFeedURL[9] = (EditText) findViewById(R.id.FeedURL10);
for (int i = 0; i < RSSReaderApplication.NUM_OF_FEED; ++i) {
mFeedURL[i].setText(app.feedURL(i));
}
});
mHSize.setOnFocusChangeListener(new View.OnFocusChangeListener() {
@Override
public void onFocusChange(View v, boolean flag) {
if (flag == false) {
CheckBox keepRatio = (CheckBox) findViewById(R.id.KeepRatio);
if (keepRatio.isChecked() == true) {
String base = mHSize.getText().toString().trim();
if ((base.equals("") == false)
&& (mOrgW != 0) && (mOrgH != 0)) {
double w = 0;
double h = Integer.parseInt(base);
double ratio = (double) mOrgW / (double) mOrgH;
w = new BigDecimal(w * ratio).setScale(0, BigDecimal.ROUND_HALF_UP).doubleValue();
if (w < 1.0) {
w = 1.0;
}
mWSize.setText(String.valueOf((int) w));
}
}
}
}
});
}
private void setPreview() {
if (mBitmap != null) {
mOrgW = mBitmap .getWidth();
mOrgH = mBitmap .getHeight();
mWSize.setText(String.valueOf(mOrgW));
mHSize.setText(String.valueOf(mOrgH));
mPreview.setImageBitmap(mBitmap);
Button button;
button = (Button) findViewById(R.id.Cancel);
button.setOnClickListener(this);
button = (Button) findViewById(R.id.Regist);
button.setOnClickListener(this);
}
}
~
setupUIメソッドではまず、setContentViewでmainレイアウトをアクティビティに設定したあと、「画像を選択」「写真を撮影」「保存」ボタンをfindViewByIdで取得し、このアクティビティをOnClickListenerとして登録しています。
onCreateメソッド(リスト4-15)では、requestWindowFeatureでタイトルを非表示にした後、getWindowメソッドで取得したこのアクティビティのトップレベルウィンドウのインスタンスに対しsetSoftInputModeメソッドを呼び出しています。レイアウトには最初に作成したレイアウトのうち、configレイアウトを設定します。~
通常、テキストボックス(EditText)を持つアクティビティが表示された際、フォーカスがテキストボックスに設定されるとソフトウェアキーボードが自動的に表示されてしまいます。setSoftInputModeにLayoutParam.SOFT_INPUT_STATE_ALWAYS_HIDDENを渡して呼び出すことで、この挙動を抑制出来ます。
続いて画像プレビュー用のImageView、横/縦サイズ入力用EditTextを取得して保持しておき、setPreviewメソッドを呼び出しています。setPreviewメソッドでは他のアクティビティから受け取った画像がある場合、画像プレビュー用のImageViewに設定し、オリジナル画像サイズを保持しています。
ここで現在の設定を設定画面に反映させるため、RSSReaderApplicationのインスタンスを取得しています。
その後横/縦サイズ入力用EditTextそれぞれにOnFocusChangeListenerを登録しています。ここでは「比率を維持」チェックボックスがチェックされていた場合に、リサイズ後の縦横比を可能な限り維持するための自動計算処理を行っています。~
onFocusChangeはそのウィジェットのフォーカス状態が変更される際に呼び出されるメソッドで、引数flagがfalseの場合はフォーカスを失ったことを示します。このタイミングで、入力された値にオリジナル画像サイズから算出された比率を掛け、四捨五入した物をもう一方のサイズとして設定しています。計算結果が0になることもありますが、この場合は1で上書きすることで最低限の画像サイズを確保しています。
続いてmNumOfGetに格納したSpinnerに、文字列配列リソースnumber_of_getをセットしたArrayAdapterを設定し、アプリケーションクラスを通じて取得したフィード取得数に対応するインデックスを現在の選択値にします。この際、フィード取得数に対応するSpinnerのインデックスを割り出すため、先ほど追加した定数配列NUM_OF_GET_LISTを参照しています。~
取得フィードURLのEditTextを配列mFeedURLに順次保持したあと、アプリケーションクラスを通じて取得したURLを各EditTextに設定します。
四捨五入で使用したBigDecimalと「比率を維持」チェックボックスを保持するためのCheckBoxを利用可能とするため、次のimport行を追加します。
最後に取消ボタン、登録ボタンのインスタンスを取得して、このクラスでクリックを処理するようOnClickListenerに設定しています。これにより、各ボタンをクリックするとonClickメソッド(リスト4-16)が呼び出されます。
import java.math.BigDecimal;
-リスト4-16 :
@Override
public void onClick(View view) {
int id = view.getId();
import android.widget.CheckBox;
switch (id) {
case R.id.Cancel:
finish();
break;
case R.id.Regist:
regist();
break;
}
}
~
以上の作成が完了したら、自動生成されたonCreateを以下のように変更します(リスト3-2)。
onClickメソッドでは引数で渡されたViewのIDから、取消(Cancel)ボタンであればfinishを呼び出してアクティビティを終了し、登録(Regist)ボタンであれば次に実装するregistメソッドを呼び出しています。
-リスト3-2 :
/** Called when the activity is first created. */
@Override
publiic void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setupUI();
}
リスト4-17がregistメソッドになります。
-リスト4-17 :
private void regist() {
RSSReaderApplication app = (RSSReaderApplication) getApplication();
app.setNumberOfGet(NUM_OF_GET_LIST[mNumOfGet.getSelectedItemPosition()]);
app.clearFeedURL();
for (int i = 0, j = 0; i < RSSReaderApplication.NUM_OF_FEED; ++i) {
String url = mFeedURL[i].getText().toString().trim();
if (url.equals("") == false) {
app.setFeedURL(j++, url);
}
}
app.saveConfig();
finish();
}
~
元々のコードではここで直接setContentViewを呼び出していましたが、これをsetupUI呼び出しに変更しています。また、requestWindowFeatureメソッドにWindow.FEATURE_NO_TITLEを渡して呼び出すことにより、アクティビティのタイトルバーを非表示にしています。図3-5左がタイトルバーを表示、右が非表示としたものです。
アプリケーションクラスのインスタンスを取得し、これを通して各設定を反映させます。フィード取得件数を設定する際には、ドロップダウンリストの選択インデックスでNUM_OF_GET_LISTを参照し、実際の件数をsetNumberOfGetメソッドに渡しています。~
また、取得フィードURLの設定時には各EditTextの内容をチェックし、空の場合は処理をスキップして、間を詰めるようにして保存している点に留意して下さい。~
最後にsaveConfigメソッドで設定の保存を行い、finishを呼び出してアクティビティを終了しています。
CENTER:&ref(./03_05.png,);
CENTER:図3-5~
以上で設定画面アクティビティの実装は完了です。
**4-4. メイン画面の実装 [#lad27e52]
このアプリケーションのメイン画面であり、RSSフィードを一覧するアクティビティを実装していきます。
今回はアイテムタイプによってリストビューの各行に設定するレイアウトを切り替えるため、独自のArrayAdapterを作る必要があります。
ベースクラスをArrayAdapter、テンプレートをFeedItemとしてFeedAdapterクラスを生成し、メンバ変数とコンストラクタを実装します(リスト4-18)。
-リスト4-18 :
public class FeedAdapter extends ArrayAdapter<FeedItem> {
private LayoutInflater mInflate;
public FeedAdapter(Context context, List<FeedItem> obj) {
super(context, 0, obj);
mInflate = (LayoutInflater) context.getSystemService(
Context.LAYOUT_INFLATER_SERVICE);
}
~
ここまでで一度アプリケーションを実行してみましょう。Android端末を傾けて、縦画面表示と横画面表示を切り替えてみて下さい。エミュレータで実行している場合はCtrl+F12で縦横切替を行うことが出来ます(Windowsの場合)。
メンバ変数にLayoutInflaterクラスを追加し、コンストラクタでインスタンスを取得しています。LayoutInflaterはレイアウトXMLを読み込んで動的にViewを生成するクラスです。今回作成するアダプターではフィードのアイテムタイプ(フィードタイトルか記事か)によって使用するレイアウトを切り替えるため、LayoutInflaterを使用します。
続いてisEnabledメソッドを追加します(リスト4-19)。
**3-3. インテントによる外部アプリケーション呼び出し [#vfaa30af]
-リスト4-19 :
@Override
public boolean isEnabled(int pos) {
FeedItem item = getItem(pos);
if (item.itemType() == FeedItem.ITEMTYPE_FEEDCHANNEL) {
return false;
}
return true;
}
~
ここからは「画像を選択」ボタンと「写真を撮影」ボタンの処理を実装し、インテントで外部アプリケーションを呼び出す手順を解説します。
isEnabledメソッドはリストビューの各行に対して呼び出されるメソッドで、falseを返すとその行は無効扱いとなり、行タップなどに反応しなくなります。フィードタイトル行では詳細画面を開くことがなく、無効扱いとするためアイテムタイプがFeedItem.ITEMTYPE_FEEDCHANNELの場合にfalseを返しています。
まずは外部アプリケーション呼び出しに使用する二つの定数と、カメラからのデータ受け取りに使用するメンバ変数をIResizerActivityクラスに追加します。
public static final int REQUEST_SELECT_IMAGE = 1234;
public static final int REQUEST_TAKE_PHOTO = 1235;
-リスト4-20 :
public View getView(final int pos, View convView, ViewGroup parent) {
View view = convView;
FeedItem item = getItem(pos);
private Uri mUriFromCamera;
switch (item.itemType()) {
case FeedItem.ITEMTYPE_FEEDCHANNEL:
view = buildChannel(item);
break;
case FeedItem.ITEMTYPE_FEEDITEM:
view = buildItem(item);
break;
}
return view;
}
~
続いて前項で追加したonClickメソッドを以下のように変更します(リスト3-3)。
getViewメソッド(リスト4-20)はリストの各行がスクロールなどによって表示された際に呼び出されます。引数に渡されたposでgetItemを呼び出すと、アダプタに登録されたリストから該当位置のデータを取得出来ます。これがITEMTYPE_FEEDCHANNELであればフィードタイトル用のViewをbuildChannel呼び出しにより生成、ITEMTYPE_FEEDITEMであれば記事用のViewをbuildItem呼び出しにより生成して返します。
-リスト3-3 :
@Override
publiic void onClick(View view){
int id = view.getId();
リスト4-21がbuildChannel/buildItemメソッドになります。
-リスト4-21 :
private View buildChannel(FeedItem item) {
View view = null;
view = mInflate.inflate(R.layout.item_channel, null);
switch (id) {
case R.id.SelectImage:
selectImage();
break;
TextView tv;
tv = (TextView) view.findViewById(R.id.FeedTitle);
tv.setText(item.feedTitle());
case R.id.TakePhoto:
takePhoto();
break;
}
}
return view;
}
private View buildItem(FeedItem item) {
View view = null;
view = mInflate.inflate(R.layout.item_item, null);
TextView tv;
tv = (TextView) view.findViewById(R.id.ArticleTitle);
tv.setText(item.articleTitle());
tv = (TextView) view.findViewById(R.id.PubDate);
tv.setText(item.pubDate());
return view;
}
~
引数に渡されたviewからgetIdメソッドでIDを取得し、「画像を選択」ボタンであればselectImage、「写真を撮影」ボタンであればtakePhotoメソッドの呼び出しを行っています。
それぞれitem_channelレイアウト、item_itemレイアウトを使用してLayoutInflaterによりViewを生成したあと、FeedItemからタイトルや出版日、詳細などを設定しています。
これらのメソッドで使用する文字列リソースを追加しておきます。strings.xmlを開き以下を追加して下さい。
メイン画面ではFeedListを介してHTTP通信を行うことになりますが、これをメインスレッドで行うとANRが発生する可能性があります。これを避けるため、バックグラウンドで行うためのAsyncTaskクラスを作成し使用します。~
まずはAsyncTaskから呼び出すメソッドのインターフェースを作成します。Package ExplorerでRSSReaderのsrc/com.beatcraft.rssreaderを右クリックし、メニューから「New > Interface」を選択して下さい。"New Java Interface"ダイアログが表示されたら、Nameに「ITaskEntity」と入力して「Finish」ボタンをクリックします。
<string name="app_notfound">対応アプリケーションが見つかりません。</string>
生成されたITaskEntityにbackgroundProcとpostProcという二つのメソッドインターフェースを宣言します(リスト4-22)。
リスト3-4がselectImageメソッドになります。Intent、AlertDialog、DialogInterfaceを利用するためパッケージのimportも忘れずに行なって下さい。
-リスト3-4 :
private void selectImage() {
try {
Intent intent = new Intent(Intent.ACTION_PICK);
intent.setType("image/*");
startActivityForResult(intent, REQUEST_SELECT_IMAGE);
}
catch (Exception e) {
AlertDialog.Builder builder = null;
builder = new AlertDialog.Builder(this);
builder.setMessage(getString(R.string.app_notfound));
builder.setNeutralButton("OK",
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
}
}
);
builder.show();
}
-リスト4-22 :
package com.beatcraft.rssreader;
public interface ITaskEntity {
void backgroundProc();
void postProc();
}
~
tryブロックで囲まれた部分では、まずIntent.ACTION_PICKを引数にしたインテントを生成しています。コンテキストとクラスを直接指定したものと違い、"データ取得のため"といったような曖昧な指定になっていますが、こうして生成されたインテントでは対応可能なコンポーネントをAndroidが自動的に探し出してくれます。両者を区別するため、前者を「明示的なインテント」、後者を「暗黙的なインテント」と呼びます。
続いてAsyncTaskを作成します。ベースクラスをAsyncTask、テンプレートをITaskEntity, Integer, VoidとしてHttpAccessTaskを生成し、必要なメソッドを実装していきます(リスト4-23)。
今回のアプリケーションでは画像データを必要とするため、インテントのsetTypeメソッドに「image/*」を渡して対象を絞り込んでいます。呼び出し先からのデータを受け取るため、startActivityではなくstartActivityForResultメソッドを使用し、この呼び出しに対する結果が返ってきたかどうかを判断するために先程追加した定数 "REQUEST_SELECT_IMAGE"を渡しています。
-リスト4-23 :
package com.beatcraft.rssreader;
import android.app.Activity;
import android.app.ProgressDialog;
import android.os.AsyncTask;
public class HttpAccessTask extends AsyncTask<ITaskEntity, Integer, Void> {
private Activity mActivity;
private ProgressDialog mDialog;
private ITaskEntity mITaskEntity;
public HttpAccessTask(Activity activity) {
mActivity = activity;
}
@Override
protected void onPreExecute() {
mDialog = new ProgressDialog(mActivity);
mDialog.setMessage(mActivity.getString(R.string.loading));
mDialog.show();
}
@Override
protected Void doInBackground(ITaskEntity... params) {
mITaskEntity = params[0];
mITaskEntity.backgroundProc();
return null;
}
@Override
protected void onPostExecute(Void v) {
mITaskEntity.postProc();
mDialog.dismiss();
mDialog = null;
}
}
~
指定したインテントに対応可能なアクティビティをAndroidが見つけられなかった場合、ActivityNotFound例外が発生します。これに対応するため、catchブロックではAlertDialog.Builderを利用して警告ダイアログを生成し表示しています。
コンストラクタでは引数で受け取ったアクティビティをメンバ変数に保持し、onPreExecuteでプログレスダイアログを表示する際に使用しています。~
doInBackgroundでITaskEntityを受け取って保持し、バックグラウンド処理を呼び出します。バックグラウンド処理が終了し、onPostExecuteが呼び出されたらITaskEntityを介して後処理を実行、プログレスダイアログの消去を行います。
続いてtakePhotoメソッドも実装していきます(リスト3-5)。新たに使用するContentValues、MediaStoreのパケージimportも忘れずに行なって下さい。
これでメイン画面を作成する準備が整いました。自動生成されたRSSReaderActivityに必要なメンバ変数などを追加していきます(リスト4-24)。
-リスト3-5 :
private void takePhoto() {
try {
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
ContentValues values = new ContentValues();
values.put(MediaStore.Images.Media.MIME_TYPE, "image/*");
mUriFromCamera = getContentResolver().insert(
MediaStore.Images.Media. EXTERNAL_CONTENT_URI, values);
intent.putExtra(MediaStore.EXTRA_OUTPUT, mUriFromCamera);
startActivityForResult(intent, REQUEST_TAKE_PHOTO);
-リスト4-24 :
public class RSSReaderActivity extends ListActivity implements View.OnClickListener, ITaskEntity {
private FeedList mList = null;
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.main);
Button button;
button = (Button) findViewById(R.id.Get);
button.setOnClickListener(this);
button = (Button) findViewById(R.id.Config);
button.setOnClickListener(this);
}
catch (Exception e) {
AlertDialog.Builder builder = null;
builder = new AlertDialog.Builder(this);
builder.setMessage(getString(R.string.app_notfound));
builder.setNeutralButton("OK",
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
}
}
);
builder.show();
}
}
~
カメラを呼び出すインテントにはMediaStore.ACTION_IMAGE_CAPTUREを指定します。Intent.ACTION_PICKでは選択した画像をUriとして返してくれるのですが、カメラからはサムネイルのデータが拡張データとして返ってきます。実際の撮影データを同様に取り扱うため、"image/*"をMIMEタイプに設定したContentValuesをContentResolverに追加し、割り当てられたUriをインテントの拡張データMediaStore.EXTRA_OUTPUTとして追加すると、撮影した画像がこのUriに保存されるようになります。~
これを先程追加したメンバ変数mUriFromCameraに保持しておくことで、カメラから画像を取得する場合に参照出来るようにしています。結果がカメラから返ってきたことを判断するため、startActivityForResultには定数"REQUEST_TAKE_PHOTO"を渡しています。
自動生成されたRSSReaderActivityはベースクラスがActivityとなっているので、まずはこれをListActivityに変更します。app.android.Activityをimportしている行も併せてListActivityに変更して下さい。また、このアクテビティでボタンクリックとHTTP通信を処理するため、View.OnClickListenerとITaskEntityをimplementsします。~
メンバ変数mListは、HTTP通信により取得したフィードデータを保持するFeedListです。~
onCreateで取得ボタンと設定ボタンをOnClickListenerとして登録し、以下のonClickメソッドで処理を行います(リスト4-25)。
再びアプリケーションを実行して「画像を選択」「写真を撮影」ボタンの動作を確認してみましょう。対応可能なコンポーネントが複数見つかった場合、図3-6のようなダイアログが表示されますが、基本的にどちらを選んでも問題ありません。
-リスト4-25 :
@Override
public void onClick(View view) {
int id = view.getId();
switch (id) {
case R.id.Get:
mList = null;
HttpAccessTask task = new HttpAccessTask(this);
task.execute(this);
break;
case R.id.Config:
showConfig();
break;
}
}
~
取得(Get)ボタンではリストを一旦クリアして、HttpAccessTaskを実行しています。これにより、ITaskEntityで宣言されたメソッドbackgroundProcとpostProcが呼び出されます(リスト4-26)。一方の設定(Config)ボタンは後ほど実装するshowConfigメソッド(リスト4-27)により設定画面アクティビティの呼び出しを行います。
CENTER:&ref(./03_06.png,);
CENTER:図3-6~
-リスト4-26 :
@Override
public void backgroundProc() {
RSSReaderApplication app = (RSSReaderApplication) getApplication();
mList = new FeedList();
mList.get(app);
}
@Override
public void postProc() {
if (mList.count() > 0) {
ArrayList<FeedItem> list = mList.getList();
if (list != null) {
FeedAdapter adapter = new FeedAdapter(this, list);
setListAdapter(adapter);
}
}
else {
Toast.makeText(this, "* NOT FOUND *", Toast.LENGTH_SHORT).show();
}
}
~
backgroundProcはHttpAccessTaskによりバックグラウンドスレッドから呼び出されます。ここでFeedListを生成して、getメソッドによりRSSフィードを取得します。取得するフィードのURLを得るために、RSSReaderApplicationクラスのインスタンスをgetメソッドに渡しています。~
postProcはバックグラウンド処理が終了したあと、後処理として呼び出されます。FeedListに登録された件数をチェックし、一件でもデータがあればFeedItemのArrayListをFeedListから取得し、これを渡して生成したFeedAdapterをsetListAdapterメソッドに渡しています。~
setListAdapterはListActivityのメソッドで、統合されたリストビューに対してアダプターを設定します。これはListViewに対して直接setAdapterする代わりに利用出来ます。~
HTTP通信の結果、フィードが一件も取得出来なかった場合には「* NOT FOUND *」というToastを表示しています。
**3-4. 外部アプリケーションからのデータ取得 [#o5a43409]
-リスト4-27 :
private void showConfig() {
Intent intent = new Intent(getApplicationContext(),
com.beatcraft.rssreader.ConfigActivity.class);
startActivity(intent);
}
~
前項までで外部アプリケーションを呼び出すことが出来ました。ここからは外部アプリケーションから渡されたデータを受け取り、画面に反映するまでを実装していきます。
showConfigメソッドでは設定画面アクティビティ(ConfigActivity)を、明示的なインテントで呼び出しています。
startActivityForResultで呼び出したアプリケーションからの結果はonActivityResultメソッドで受け取ることが出来ます(リスト3-6)。
これでメイン画面の実装はひとまず完了です。RSSReaderのマニフェストファイル(AndroidManifest.xml)を開き、アプリケーションクラスとアクティビティ、パーミッションの追加を行います(リスト4-28)。
-リスト3-6 :
protected void onActivityResult(int reqCode, int result, Intent data) {
super.onActivityResult(reqCode, result, data);
-リスト4-28 :
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.beatcraft.rssreader"
android:versionCode="1"
android:versionName="1.0" >
Uri uri = null;
<uses-sdk android:minSdkVersion="7" />
if (result == RESULT_OK) {
if (reqCode == REQUEST_SELECT_IMAGE) {
uri = data.getData();
}
else if (reqCode == REQUEST_TAKE_PHOTO) {
uri = mUriFromCamera;
}
mBitmap = getImage(uri);
setPreview();
}
else if (reqCode == REQUEST_TAKE_PHOTO) {
getContentResolver().delete(mUriFromCamera, null, null);
}
}
<application
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:name="com.beatcraft.rssreader.RSSReaderApplication" >
<activity
android:label="@string/app_name"
android:name=".RSSReaderActivity" >
<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=".ConfigActivity" />
</application>
<uses-permission android:name="android.permission.INTERNET" >
</uses-permission>
</manifest>
~
superクラスのonActivityResultを呼び出した後、resultをチェックしています。これがRESULT_OKの場合、呼び出したアプリケーションがデータを返してくれたことを示します。~
reqCodeにはstartActivityResultを呼び出した際のリクエストコードが入っています。これが"REQUEST_SELECT_IMAGE"であれば「画像を選択」での呼び出しとなり、渡されたインテントからgetDataメソッドでUriを取得出来ます。~
一方"REQUEST_TAKE_PHOTO"の場合は「写真を撮影」での呼び出しになりますので、予め保持しておいたmUriFromCameraを使用しています。また、resultがRESULT_OKでなかった場合はContentResolverのdeleteメソッドでこのUriを削除しています。
まずはapplication要素にRSSReaderApplicationを追加するため、属性android:name="com.beatcraft.rssreader.RSSReaderApplication"を追加しています。~
それから設定画面を呼び出し可能とするためのConfigActivityのactivity要素、android.permission.INTERNETというパーミッションをそれぞれ追加すれば完了です。
取得したUriを後ほど実装するgetImageメソッドに引き渡してビットマップ画像を取得し、setPreviewメソッドでプレビュー用ImageViewへの設定と画像サイズの取得を行います。
それでは一度実行してみましょう。設定画面で取得フィードのURLを登録し、メイン画面で「取得」ボタンをクリックすれば、図4-1のように一覧表示が行われるはずです。
リスト3-7がgetImageメソッドになります。InputStream、BitmapFactoryパッケージのimportも併せて行います。
-リスト3-7 :
private Bitmap getImage(Uri uri) {
try {
InputStream is = getContentResolver().openInputStream(uri);
return BitmapFactory.decodeStream(is);
**4-5. 詳細画面の実装 [#u8e15cd5]
続いて詳細画面アクティビティを作成し、一覧から記事をタップして遷移するよう変更を加えます。android.app.ActivityをベースクラスとしてDescActivityを生成し、いくつかの定数とonCreateメソッドを追加します(リスト4-29)。ボタンのクリック処理をこのクラスで行うため、View.OnClickListenerのimplementsも追加して下さい。
-リスト4-29 :
public class DescActivity extends Activity implements View.OnClickListener {
private static final String WEBVIEW_BEGIN =
"<html><head><meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\" /></head><body bgcolor=\"#f6f6f6\">";
private static final String WEBVIEW_LINK = "<p><a href=\"%s\">%s</a></p>";
private static final String WEBVIEW_END = "</body></html>";
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.description);
Button button = (Button) findViewById(R.id.Back);
button.setOnClickListener(this);
Intent intent = getIntent();
if (intent != null) {
String tmp = "";
String desc = "";
TextView tv;
tv = (TextView) findViewById(R.id.FeedTitle);
tmp = intent.getStringExtra("FeedTitle");
tv.setText(tmp);
tv = (TextView) findViewById(R.id.ArticleTitle);
tmp = intent.getStringExtra("ArticleTitle");
tv.setText(tmp);
tv = (TextView) findViewById(R.id.PubDate);
tmp = intent.getStringExtra("PubDate");
tv.setText(tmp);
WebView wv = (WebView) findViewById(R.id.Description);
desc = WEBVIEW_BEGIN;
tmp = intent.getStringExtra("Link");
desc += String.format(WEBVIEW_LINK, tmp, tmp);
tmp = intent.getStringExtra("Description");
desc += tmp + WEBVIEW_END;
wv.loadDataWithBaseURL("about:blank", desc, "text/html", "utf-8", null);
}
}
catch (Exception e) {
e.printStackTrace();
return null;
}
}
~
ContentResolverのopenInputStreamメソッドにUriを渡すと、そのUriにあるファイルのInputStreamが取得出来ます。これをBitmapFactoryのdecodeStreamに引渡し、Bitmapオブジェクトをデコードして返しています。指定のUriにファイルがなかった場合にopenInputStreamメソッドがFileNotFoundException例外を発行するので、catchブロックではこれを受けてnullを返します。
このアクティビティもrequestWindowFeatureでタイトル行を非表示にし、レイアウトには最初に作成したレイアウトのうちdescriptionレイアウトを指定します。~
戻る(Back)ボタンのOnClickListenerを設定したあと、getIntentメソッドでこのアクティビティを呼び出したインテントを取得しています。このインテントに含まれる拡張データがこのアクティビティで表示する記事詳細になります。
以上でデータ受け取りの実装は完了ですが、Androidのバージョンによってはインテントの発行後アプリケーションで画面の縦横切替が起きると、元のアクティビティが結果を受け取れなくなることがあります。これは縦横切替によって一度アクティビティが破棄される(onDestroyが呼ばれる)ためで、これを避けるには縦横切替時にアクティビティの破棄ではなく、コンフィギュレーション変更が発生するようにします。
インテントから「FeedTitle(RSSフィードのタイトル)」「ArticleTitle(記事のタイトル)」「PubDate(出版日時)」という文字列データを取得し、同名のTextViewに設定します。~
次にこのクラスに追加した定数を利用して、WebView「Description」に設定するHTMLを整形します。~
HTMLはWEBVIEW_BEGINで始まり、続いてWEBVIEW_LINKを追加します。この際、WEBVIEW_LINKをフォーマットしてインテントから取得した「Link(記事本文へのリンクURL)」を埋め込みます。~
続いて、同様に取得した文字列データ「Description(詳細文)」を追加し、最後にWEBVIEW_ENDでHTML文書を閉じます。
IResizerのマニフェストファイル(AndroidManifest.xml)を開き、android:configChanges属性をIResizerActivityに追加します(リスト3-8)。
こうして整形したHTML文書をWebViewのloadDataWithBaseURLで設定することで、本文へのリンクを上部に持ち、HTMLタグを含むフィード詳細文が埋め込みブラウザ内に表示されます。
-リスト3-8 :
<activity
android:configChanges="orientation"
android:label="@string/app_name"
android:name=".IResizerActivity" >
-リスト4-30 :
@Override
public void onClick(View view) {
if (view.getId() == R.id.Back) {
finish();
}
}
~
続いてIResizerActivity.javaにonConfigurationChangedメソッドを追加します(リスト3-9)。
onClickメソッド(リスト4-30)で戻る(Back)ボタンの処理を追加すれば、このアクティビティは完成です。
-リスト3-9 :
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
setupUI();
}
一覧からの記事タップでこのアクティビティを呼び出すため、RSSReaderActivityのonCreateメソッドに以下の行を追加します(リスト4-31)。
-リスト4-31 :
getListView().setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int pos, long id) {
FeedItem item = (FeedItem) getListView().getItemAtPosition(pos);
Intent intent = new Intent(getApplicationContext(),
com.beatcraft.rssreader.DescActivity.class);
intent.putExtra("FeedTitle", item.feedTitle());
intent.putExtra("ArticleTitle", item.articleTitle());
intent.putExtra("PubDate", item.pubDate());
intent.putExtra("Description", item.description());
intent.putExtra("Link", item.link());
startActivity(intent);
}
});
~
superクラスのonConfigurationChangedを呼び出したあと、setupUIでレイアウトの設定を改めて行なっています。これをしないと(アクティビティが破棄されないため)切替前のレイアウトがそのままになってしまいます。
getListViewでListActivityの埋め込みリストビューを取得し、OnItemClickListenerを登録します。登録するリスナーのonItemClickメソッドでは、getItemAtPositionでタップされた行を取得出来ます。FeedAdapterのisEnabledでアイテムタイプがITEMTYPE_FEEDCHANNELの場合は無効としているため、ここで取得出来るFeedItemは必ず記事データ(ITEMTYPE_FEEDITEM)になります。~
DescActivityを呼び出す明示的なインテントを生成したあと、取得したFeedItemからRSSフィードのタイトル、記事タイトル、出版日時、詳細文、リンクURLを取得して拡張データに設定しています。~
このインテントをstartActivityに渡せば、タップした記事を表示する詳細画面アクティビティが開きます。
最後に詳細画面アクティビティをインテントで呼び出せるようにするため、マニフェストファイルに以下を追加します(リスト4-32)。
-リスト4-32 :
<activity
android:label="@string/app_name"
android:name=".DescActivity" />
~
**3-5. リサイズ画像保存処理の実装 [#wd17dccb]
以上で完成です。一覧から記事をタップすると、該当記事の詳細画面が表示されることを確認して下さい。埋め込みブラウザに記事本文へのリンクを表示しているので、こちらのタップで外部ブラウザが起動され、記事本文を表示することも可能です。
ここからは受け取った画像をリサイズし、保存するまでの処理を実装していきます。保存に際しての仕様は以下の通りとします。
-フォーマットはJPEG固定とする。
-ファイル名は現在時刻を用いて「YYYYMMDD_HHMMSS.jpg」という形式で自動生成する。
-保存先はカメラフォルダに固定とし、ギャラリーに表示されるよう登録を行う。
-保存前に確認ダイアログを表示する。
**4-6. 追加機能:お勧めリスト取得(POST/BASIC認証解説) [#n93aa169]
まずは保存処理で使用する文字列リソースを追加しておきます。strings.xmlを開き、以下を追加して下さい。
ここまでで簡易RSSリーダーは完成しましたが、RSSフィードのURLを一件ずつ手入力しなくてはならず、HTTP通信としてもGETメソッドしか使用していません。希望ジャンルのお勧めRSSを三件ずつXMLで返すサイトを用意しましたので、こちらを利用してアプリケーションを拡張していきます。
<string name="cancel">取消</string>
<string name="save_title">画像保存</string>
<string name="filename_label">ファイル名:</string>
<string name="isize_label">画像サイズ:</string>
<string name="save_desc">で保存します。</string>
<string name="save_success">保存しました</string>
<string name="save_failed">保存に失敗しました</string>
-サイト情報
URL: http://labs.beatcraft.com/ja/androidtext/recommend.php
BASICユーザー: beatandroid
BASICパスワード: sample
続いて確認ダイアログ用のレイアウトを作成していきます。IResizerのレイアウトリソースフォルダに以下の内容でdialog.xmlというレイアウトファイルを作成します(リスト3-10)。
上記URLにPOST変数genreとして、news/music/movieいずれかを渡すと、ニュース/音楽/映画ジャンルのお勧めRSSを返します。リスト4-33が呼び出し結果のサンプルになります。
-リスト3-10 :
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-リスト4-33 :
<?xml version="1.0" encoding="UTF-8"?>
<feedlist>
<genre>news</genre>
<feed>
<title>CNN</title>
<url>http://rss.cnn.com/rss/edition.rss</url>
</feed>
<feed>
<title>The Wall Street Journal</title>
<url>http://online.wsj.com/xml/rss/3_7480.xml</url>
</feed>
<feed>
<title>Reuters</title>
<url>http://feeds.reuters.com/reuters/topNews?format=xml</url>
</feed>
</feedlist>
~
まずはstrings.xmlに、お勧め選択画面で使用する文字列リソースを追加します(リスト4-34)。
-リスト4-34 :
<string name="recommend">お勧め検索</string>
<string name="search">検索</string>
<string name="addfeed">チェックしたフィードを追加</string>
<string name="label_genre">ジャンル:</string>
<string-array name="genre">
<item>ニュース</item>
<item>音楽</item>
<item>映画</item>
</string-array>
~
続いてレイアウトを作成して行きましょう。recommend.xmlというレイアウトファイルを作成し、リスト4-35のように編集します。
-リスト4-35 recommend.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:id="@+id/linearLayout1"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >
android:layout_height="wrap_content"
android:layout_margin="10dip" >
<TextView
android:id="@+id/LabelGenre"
android:layout_width="wrap_content"
android:layout_height="fill_parent"
android:gravity="center_vertical"
android:text="@string/label_genre" />
<LinearLayout
android:id="@+id/FileNameLayout"
android:layout_width="fill_parent"
<Spinner
android:id="@+id/Genre"
android:layout_width="120dip"
android:layout_height="wrap_content" />
<Button
android:id="@+id/Search"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="5dip"
android:layout_marginRight="5dip" >
android:layout_marginLeft="10dip"
android:layout_weight="1"
android:text="@string/search" />
</LinearLayout>
<TextView
android:id="@+id/FileLabel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/filename_label" />
<Button
android:id="@+id/Back"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="5dip"
android:text="@string/back" />
<TextView
android:id="@+id/FileName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="5dip"
android:layout_weight="1" />
</LinearLayout>
<CheckBox
android:id="@+id/Feed01"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_margin="5dip"
android:text="CheckBox"
android:visibility="invisible" />
<CheckBox
android:id="@+id/Feed02"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_margin="5dip"
android:text="CheckBox"
android:visibility="invisible" />
<CheckBox
android:id="@+id/Feed03"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_margin="5dip"
android:text="CheckBox"
android:visibility="invisible" />
<Button
android:id="@+id/AddFeed"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_margin="5dip"
android:text="@string/addfeed"
android:visibility="invisible" />
</LinearLayout>
~
お勧めRSSを取得し、画面に設定したものが図4-4になります。
CENTER:&ref(./04_04.png,);
CENTER:図4-4~
~
~
最上部にはジャンル選択のドロップダウンリスト(Spinner)と検索ボタンが並び、その下には設定画面に戻るボタンが置かれています。~
それに続いてお勧めRSSのチェックボックスが三件並び、最後にチェックしたフィードを取得フィードURLに追加するボタンが続きます。これらは初期状態では非表示(android:visibility="invisible")に設定されていることに注意して下さい。
続いてこのお勧め画面を呼び出すため、設定画面にボタンを一つ追加します。config.xmlを開き、取得件数ドロップダウンリストの横にお勧め検索ボタンを追加します(リスト4-36、図4-5)。
-リスト4-36 :
<LinearLayout
android:id="@+id/ImageSizeLayout"
android:id="@+id/linearLayout1"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="5dip"
android:layout_marginRight="5dip" >
android:layout_margin="10dip" >
<TextView
android:id="@+id/SizeLabel"
android:id="@+id/LabelNumOfGet"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/isize_label" />
android:layout_height="fill_parent"
android:gravity="center_vertical"
android:text="@string/label_number_of_get" />
<TextView
android:id="@+id/ImageSize"
<Spinner
android:id="@+id/NumOfGet"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="5dip"
android:layout_weight="1" />
</LinearLayout>
android:layout_height="wrap_content" />
<TextView
android:id="@+id/Description"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="5dip"
android:layout_marginRight="5dip"
android:gravity="right"
android:text="@string/save_desc" />
<LinearLayout
android:id="@+id/ButtonLayout"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dip"
android:gravity="center_horizontal" >
<Button
android:id="@+id/ExecSave"
android:id="@+id/Recommend"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/save" />
<Button
android:id="@+id/Cancel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/cancel" />
android:layout_marginLeft="10dip"
android:layout_weight="1"
android:text="@string/recommend" />
</LinearLayout>
</LinearLayout>
~
ダイアログのアウトラインとレイアウトイメージは以下のようになります(図3-7)。
|LEFT:|CENTER:|c
| LinearLayout&br; FileNameLayout&br; FileLabel&br; FileName&br; ImageSizeLayout&br; SizeLabel&br; ImageSize&br; Description&br; ButtonLayout&br; ExecSave&br; Cancel|&ref(./03_07.png,);&br;図3-7|
CENTER:&ref(./04_05.png,);
CENTER:図4-5~
~
~
保存時のファイル名とダイアログを保持するため、IResizerActivityに以下のメンバ変数を追加します。
お勧め選択画面の実装を進めていきましょう。android.app.ActivityをベースクラスとしてRecommendActivityを生成し、いくつかの定数とメンバ変数を追加します(リスト4-37)。ボタンのクリック処理とHttpAccessTaskの処理をこのクラスで行うため、View.OnClickListenerとITaskEntityのimplementsも追加して下さい。
private String mFileName = "";
private Dialog mDialog = null;
-リスト4-37 :
public class RecommendActivity extends Activity implements View.OnClickListener, ITaskEntity {
private static final String GENRE[] = {"news", "music", "movie"};
private static final String POST_DOMAIN = "labs.beatcraft.com";
private static final String POST_PATH = "/ja/androidtext/recommend.php";
private static final String POST_USER = "beatandroid";
private static final String POST_PASS = "sample";
private static final int NUM_OF_RECOMMEND = 3;
private Spinner mGenre;
private CheckBox mFeedCheck[];
private String mRecommendTitle[];
private String mRecommendURL[];
private Button mAddFeed;
private int mStat = -1;
~
IResizerActivityの「保存」ボタンに応答するため、onClickのswitchに以下のcaseを追加します。
GENREはお勧めRSSのジャンルとして選択可能な値を格納する定数配列で、ドロップダウンリスト(Spinner)の選択内容に対応したnews(ニュース)/music(音楽)/movie(映画)が設定されています。~
POST_DOMAINとPOST_PATHは今回用意したお勧めRSS取得用サイトのドメインとパスになります。BASIC認証がかかっているため、認証用のユーザー/パスワードがPOST_USERとPOST_PASSとして定義されています。~
NUM_OF_RECOMMENDは各ジャンルでお勧めとして取得可能なRSSの数で、画面のチェックボックスにあわせ3が設定されています。
case R.id.Save:
showConfirmDialog();
break;
メンバ変数のうち、mRecommendTitleは取得したお勧めRSSのタイトルを保持する配列で、同様にmRecommendURLにはお勧めRSSのURLが格納されます。
リスト3-11がshowConfirmDialogメソッドになります。DateFormat、TextViewのパッケージimportも追加して下さい。
-リスト3-11 :
private void showConfirmDialog() {
if ((mBitmap != null)
&& (mWSize.getText().toString().trim().equals("") == false)
&& (mHSize.getText().toString().trim().equals("") == false)) {
long ctime = System.currentTimeMillis();
mFileName = DateFormat.format("yyyyMMdd_kkmmss", ctime).toString() + ".jpg";
-リスト4-38 :
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.recommend);
mDialog = new Dialog(this);
mDialog.setContentView(R.layout.dialog);
mDialog.setTitle(getString(R.string.save_title));
ArrayAdapter<CharSequence> adapter;
adapter = ArrayAdapter.createFromResource(this, R.array.genre,
android.R.layout.simple_spinner_item);
adapter.setDropDownViewResource(
android.R.layout.simple_spinner_dropdown_item);
mGenre = (Spinner) findViewById(R.id.Genre);
mGenre.setAdapter(adapter);
Button button;
button = (Button) mDialog.findViewById(R.id.ExecSave);
button = (Button) findViewById(R.id.Search);
button.setOnClickListener(this);
button = (Button) mDialog.findViewById(R.id.Cancel);
button = (Button) findViewById(R.id.Back);
button.setOnClickListener(this);
TextView tv;
tv = (TextView) mDialog.findViewById(R.id.FileName);
tv.setText(mFileName);
tv = (TextView) mDialog.findViewById(R.id.ImageSize);
tv.setText(mWSize.getText().toString() + "x" + mHSize.getText().toString());
mDialog.show();
mAddFeed = (Button) findViewById(R.id.AddFeed);
mAddFeed.setOnClickListener(this);
mFeedCheck = new CheckBox[NUM_OF_RECOMMEND];
mFeedCheck[0] = (CheckBox) findViewById(R.id.Feed01);
mFeedCheck[1] = (CheckBox) findViewById(R.id.Feed02);
mFeedCheck[2] = (CheckBox) findViewById(R.id.Feed03);
mRecommendTitle = new String[NUM_OF_RECOMMEND];
mRecommendURL = new String[NUM_OF_RECOMMEND];
}
}
~
リサイズ元画像とリサイズ解像度が空でないことを確認し、現在日時から「YYYYMMDD_HHMMSS.jpg」形式のファイル名を作成、保持しておきます。~
次にDialogオブジェクトを生成して先ほどのレイアウトを割り当て、「保存」「取消」両ボタンのOnClickListenerをIResizerActivityに、生成したファイル名とリサイズ後の画像サイズをダイアログの各項目にセットしてダイアログを表示します。
onCreateメソッド(リスト4-38)でタイトル行の非表示化とrecommendレイアウトの設定を行ったあと、mGenreに格納したSpinnerに文字列配列リソースgenreをセットしたArrayAdapterを設定します。~
検索(Search)ボタン、戻る(Back)ボタン、チェックしたフィードの追加(AddFeed)ボタンのOnClickListenerをこのクラスに設定し、チェックボックスの保持とお勧め格納用配列の生成を行なっています。
ダイアログの両ボタンに応答するため、onClickのswitchに以下のcaseを追加します。
-リスト4-39 :
@Override
public void onClick(View view) {
int id = view.getId();
switch (id) {
case R.id.Search:
HttpAccessTask task = new HttpAccessTask(this);
task.execute(this);
break;
case R.id.Back:
finish();
break;
case R.id.AddFeed:
addFeed();
break;
}
}
~
case R.id.ExecSave:
case R.id.Cancel:
if (mDialog != null) {
if (id == R.id.ExecSave) {
execSave();
}
mDialog.dismiss();
mDialog = null;
}
break;
onClickメソッド(リスト4-39)では検索(Search)ボタンによるHttpAccessTaskの実行、戻る(Back)ボタンによるアクティビティの終了、チェックしたフィードを追加(AddFeed)ボタンによるaddFeedメソッド(後述)の呼び出しを行います。
続いてHttpAccessTaskの実行により呼び出されるbackgroundProcとpostProcを実装します(リスト4-40)。
-リスト4-40 :
@Override
public void backgroundProc() {
for (int i = 0; i < NUM_OF_RECOMMEND; ++i) {
mRecommendTitle[i] = "";
mRecommendURL[i] = "";
}
mStat = get(GENRE[mGenre.getSelectedItemPosition()]);
}
@Override
public void postProc() {
if (mStat == 0) {
for (int i = 0; i < NUM_OF_RECOMMEND; ++i) {
mFeedCheck[i].setText(mRecommendTitle[i]);
mFeedCheck[i].setVisibility(View.VISIBLE);
mFeedCheck[i].setChecked(false);
}
mAddFeed.setVisibility(View.VISIBLE);
}
else {
for (int i = 0; i < NUM_OF_RECOMMEND; ++i) {
mFeedCheck[i].setText("");
mFeedCheck[i].setVisibility(View.INVISIBLE);
mFeedCheck[i].setChecked(false);
}
mAddFeed.setVisibility(View.INVISIBLE);
}
}
~
「保存」「取消」両ボタンとも、ダイアログがnullでなければdismissメソッドで破棄しています。IDがExecSave、すなわち「保存」ボタンの場合には実際の保存処理を行うexecSaveを呼び出します。~
リスト3-12がexecSaveメソッドになります。Toastのパッケージimportも忘れずに行なって下さい。
backgroundProcではお勧めRSSを保持する各配列をクリアし、getメソッドを呼び出しています。この際、ドロップダウンリストmGenreで選択されたインデックスを元に文字列配列GENREを参照し、取得したいジャンルを指定しています。またgetメソッドの取得正否を判定するため、戻り値をメンバ変数mStatに格納します。~
postProcではこのmStatをチェックし、0(成功)であればチェックボックスにお勧めRSSのタイトルを設定し、追加ボタンとあわせて表示状態にしています。失敗した場合は各チェックボックスのテキストをクリアして、追加ボタンとあわせ非表示にします。
-リスト3-12 :
private void execSave() {
int w = Integer.parseInt(mWSize.getText().toString());
int h = Integer.parseInt(mHSize.getText().toString());
Bitmap bitmap = Bitmap.createScaledBitmap(mBitmap, w, h, true);
-リスト4-41 :
private int get(String genre) {
String url = "http://" + POST_DOMAIN + POST_PATH;
try {
MediaStore.Images.Media.insertImage(getContentResolver(), bitmap, mFileName, null);
Toast.makeText(this, getString(R.string.save_success), Toast.LENGTH_SHORT).show();
}
catch (Exception e) {
e.printStackTrace();
Toast.makeText(this, getString(R.string.save_failed), Toast.LENGTH_SHORT).show();
}
}
DefaultHttpClient client = new DefaultHttpClient();
if (client != null) {
client.getParams().setParameter("http.socket.timeout", new Integer(15000));
HttpPost method = null;
try {
method = new HttpPost(url);
}
catch (Exception e) {
e.printStackTrace();
}
if (method == null) {
return -1;
}
HttpResponse response = null;
try {
List<NameValuePair> pair = new ArrayList<NameValuePair>();
pair.add(new BasicNameValuePair("genre", genre));
method.setEntity(new UrlEncodedFormEntity(pair, HTTP.UTF_8));
Credentials cred = new UsernamePasswordCredentials(POST_USER, POST_PASS);
client.getCredentialsProvider().setCredentials(
new AuthScope(POST_DOMAIN, 80), cred);
response = client.execute(method);
int ret = response.getStatusLine().getStatusCode();
if (ret == HttpStatus.SC_OK) {
InputStream is = response.getEntity().getContent();
return parse(is);
}
}
catch (Exception e) {
e.printStackTrace();
}
finally {
client.getConnectionManager().shutdown();
}
}
return -1;
}
~
横/縦サイズ用EditTextからサイズを取得したら、Bitmap.createScaledBitmapに元画像とサイズを引き渡してリサイズ画像を生成します。~
生成した画像をMediaStore.Images.Media.insertImageメソッドに引き渡すとカメラフォルダに画像が保存され、ContentResolverによりギャラリーにも表示されるよう登録されます。このメソッドはFileNotFoundExeception例外を発行する可能性があるためtryブロックで囲み、catchブロックでは保存失敗時のメッセージをToastで表示するようにしています。
リスト4-41がgetメソッドになります。基本的な内容はRSSフィードを取得するFeedList.getと同様ですが、HTTP通信で使用するメソッドクラスとしてHttpGetではなく、HttpPostを使用しています。
これでリサイズ画像を保存することが出来るようになりました。図3-8のようなダイアログが表示されることを確認し、実際に保存してみましょう。
また、生成されたHttpPostのインスタンスに対し、List<NameValuePair>の変数pairを使用してPOST変数を設定しています。pairのキーとしてPOST変数名である"genre"を、値としてget呼び出し時に引数として受け取った変数genreを設定し、これを基にUrlEncodedFormEntityを生成、HttpPost.setEntityに渡しています(リスト4-42)。~
これにより、ジャンルの選択リストから選ばれたニュース(news)/音楽(music)/映画(movie)といった値がHTTP POSTの変数genreとして渡されます。
CENTER:&ref(./03_08.png,);
CENTER:図3-8~
-リスト4-42 :
List<NameValuePair> pair = new ArrayList<NameValuePair>();
pair.add(new BasicNameValuePair("genre", genre));
method.setEntity(new UrlEncodedFormEntity(pair, HTTP.UTF_8));
~
保存に失敗する場合、保存先が外部ストレージになっている可能性があります。IResizerのマニフェストファイルを開いて、次のパーミッションを追加して下さい。
続く二行で、BASIC認証のユーザーとパスワードをHTTPクライアントにセットしています(リスト4-43)。
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
-リスト4-43 :
Credentials cred = new UsernamePasswordCredentials(POST_USER, POST_PASS);
client.getCredentialsProvider().setCredentials(new AuthScope(POST_DOMAIN, 80), cred);
~
**3-6. 共有機能による他アプリケーションからの呼び出し [#rb82b967]
まずBASIC認証のユーザー名(定数POST_USER)、パスワード(定数POST_PASS)によりUsernamePasswordCredentialsを生成します。~
この認証情報をHTTPクライアントのgetCredentialsProviderで取得した認証プロバイダに渡すと、execute時にBASIC認証が行われます。この際、認証のスコープとして定数POST_DOMAINとポート番号80を渡しています。
前項まででインテントを利用して外部アプリケーションと連携し、データのやり取りを行うことが出来るようになりましたが、もう少し進めて、Androidの共有機能を利用して他の画像アプリケーションからIResizerへ画像を送信出来るようにしてみましょう。
getから呼び出されるparseメソッド(リスト4-44)では、フィードの取得同様にXmlPullParserを利用して解析を行なっています。
画像の共有先としてIResizerを選択可能にするには、アクティビティにandroid.intent.action.SENDインテントフィルターを追加する必要があります。IResizerのマニフェストファイルを開き、IResizerActivityに以下を追加して下さい。
-リスト4-44 :
private int parse(InputStream is) {
int count = 0;
boolean inFeed = false;
XmlPullParser p = Xml.newPullParser();
try {
p.setInput(is, null);
int event = p.getEventType();
while (event != XmlPullParser.END_DOCUMENT) {
String elem = null;
String tmp = null;
switch (event) {
case XmlPullParser.START_TAG:
elem = p.getName();
if (elem.equals("feed") == true) {
if (inFeed == true) {
count++;
if (count >= NUM_OF_RECOMMEND) {
return 0;
}
}
inFeed = true;
}
else if (elem.equals("title") == true) {
tmp = p.nextText();
if (tmp != null) {
mRecommendTitle[count] = tmp;
}
}
else if (elem.equals("url") == true) {
tmp = p.nextText();
if (tmp != null) {
mRecommendURL[count] = tmp;
}
}
break;
case XmlPullParser.END_TAG:
elem = p.getName();
if (elem.equals("feed") == true) {
count++;
if (count >= NUM_OF_RECOMMEND) {
return 0;
}
inFeed = false;
}
break;
}
event = p.next();
}
}
catch (Exception e) {
e.printStackTrace();
return -1;
}
return 0;
}
~
<intent-filter >
<category android:name="android.intent.category.DEFAULT" />
お勧めRSSのタイトルとURLが取得出来たら、それぞれメンバ変数mRecommendTitleとmRecommendURLに保持しています。このメソッドはバックグラウンドスレッドで呼び出されるため、直接チェックボックスにタイトルを設定することは出来ません。
最後にチェックしたフィードを追加(AddFeed)ボタンで呼び出されるaddFeedメソッド(リスト4-45)を実装します。
-リスト4-45 :
private void addFeed() {
int count = 0;
Intent result = new Intent();
<data android:mimeType="image/*" />
for (int i = 0; i < NUM_OF_RECOMMEND; ++i) {
if (mFeedCheck[i].isChecked() == true) {
String key = "";
key = String.format("Recommend%02d", (count + 1));
result.putExtra(key, mRecommendURL[i]);
count++;
}
}
result.putExtra("RecommendCount", count);
setResult(RESULT_OK, result);
finish();
}
~
addFeedではresultというインテントを生成し、チェックされたRSSのURLとその数を「RecommendXX」(XXは01〜03)、「RecommendCount」というキーで拡張データとして追加しています。~
このインテントをsetResultメソッドに定数RESULT_OKとともにセットしたあと、アクティビティを終了しています。このsetResultにより、このアクティビティを呼び出したアクティビティに呼び出し結果を返すことが出来ます。
以上でお勧め選択画面の実装は完了です。次は設定画面を修正し、これを呼び出せるようにしていきます。
ConfigActivityを開き、詳細画面呼び出しで使用する定数を追加します。
public static final int REQUEST_RECOMMEND = 1234;
~
onCreateメソッドの最後に以下を追加し、お勧め検索ボタンをonClickでハンドリング出来るようにします。
button = (Button) findViewById(R.id.Recommend);
button.setOnClickListener(this);
~
続いてonClickメソッドのswitch文に、以下のcaseを追加します。
case R.id.Recommend:
showRecommend();
break;
~
リスト4-46がshowRecommendメソッドになります。
-リスト4-46 :
private void showRecommend() {
Intent intent = new Intent(getApplicationContext(),
com.beatcraft.rssreader.RecommendActivity.class);
startActivityForResult(intent, REQUEST_RECOMMEND);
}
~
明示的インテントを生成してRecommendActivityを呼び出していますが、startActivityではなくstartActivityForResultを使用している点に注意して下さい。これにより、onActivityResultでお勧め選択画面からお勧めRSSを受け取ることが可能になります。
リスト4-47がonActivityResultになります。
-リスト4-47 :
protected void onActivityResult(int reqCode, int result, Intent data) {
super.onActivityResult(reqCode, result, data);
<action android:name="android.intent.action.SEND" />
</intent-filter>
if (result == RESULT_OK) {
if (reqCode == REQUEST_RECOMMEND) {
int count = 0;
count = data.getIntExtra("RecommendCount", 0);
for (int i = 0; i < count; ++i) {
String key = "";
String url = "";
key = String.format("Recommend%02d", (i + 1));
url = data.getStringExtra(key);
if (url.equals("") == false) {
for (int j = 0; j < RSSReaderApplication.NUM_OF_FEED; ++j) {
String tmp = mFeedURL[j].getText().toString().trim();
if (tmp.equals("") == true) {
mFeedURL[j].setText(url);
break;
}
}
}
}
}
}
}
~
これで画像アプリケーションの共有メニューにIResizerが表示されるようになります。引き続き外部からの共有を処理出来るよう、IResizerActivity.javaに変更を加えていきます。IResizerActivityのonCreateメソッドでsetupUIを呼び出した後に、以下のように追加して下さい。
結果がRESULT_OKで、リクエストコードがREQUEST_RECOMMENDだった場合、渡されたインテントからRecommendCountを取得します。~
お勧めRSSが一件でも渡されていれば、これを一つずつ取得して空いている取得フィードURLにセットします。取得フィードURLに三件分をセットする空きがなかった場合は余剰分は切り捨てられます。
if(Intent.ACTION_SEND.equals(getIntent().getAction())) {
Uri uri = getIntent().getParcelableExtra(Intent.EXTRA_STREAM);
mBitmap = getImage(uri);
setPreview();
最後にマニフェストファイルに以下を追加して、お勧め選択画面を呼び出せるようにしましょう。
<activity
android:label="@string/app_name"
android:name=".RecommendActivity" />
~
以上で全ての実装は完了です。実行して動作を確認してみて下さい。
**4-7. RSSReader全ソースコード/XMLファイル [#l297dd24]
-リスト4-48 RSSReaderApplication.java :
package com.beatcraft.rssreader;
import android.app.Application;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
public class RSSReaderApplication extends Application {
private static final String CONF_NAME = "rssreader.conf";
public static final int DEFAULT_NUM_OF_GET = 3;
public static final int NUM_OF_FEED = 10;
private int mNumOfGet = DEFAULT_NUM_OF_GET;
private String mFeedURL[];
@Override
public void onCreate() {
mFeedURL = new String[NUM_OF_FEED];
loadConfig();
}
public int numberOfGet() {
return mNumOfGet;
}
public void setNumberOfGet(int numOfGet) {
mNumOfGet = numOfGet;
}
public String feedURL(int index) {
if ((index < 0) || (10 <= index)) {
return null;
}
return mFeedURL[index];
}
public void setFeedURL(int index, String url) {
if ((index < 0) || (NUM_OF_FEED <= index)) {
return;
}
mFeedURL[index] = url;
}
public void clearFeedURL() {
for (int i = 0; i < NUM_OF_FEED; ++i) {
mFeedURL[i] = "";
}
}
public void loadConfig() {
SharedPreferences pref = getSharedPreferences(CONF_NAME, MODE_PRIVATE);
mNumOfGet = pref.getInt("NumOfGet", DEFAULT_NUM_OF_GET);
for (int i = 0; i < NUM_OF_FEED; ++i) {
String key = String.format("FeedURL%02d", (i + 1));
mFeedURL[i] = pref.getString(key, "");
}
}
public void saveConfig() {
SharedPreferences pref = getSharedPreferences(CONF_NAME, MODE_PRIVATE);
Editor pe = pref.edit();
pe.putInt("NumOfGet", mNumOfGet);
for (int i = 0; i < NUM_OF_FEED; ++i) {
String key = String.format("FeedURL%02d", (i + 1));
pe.putString(key, mFeedURL[i]);
}
pe.commit();
}
}
~
これで完成です。ギャラリーなどの共有機能を持った画像アプリを起動し、共有メニューにIResizerが表示されるのを確認してみましょう(図3-9)。
-リスト4-49 RSSReaderActivity.java :
package com.beatcraft.rssreader;
import java.util.ArrayList;
import android.app.ListActivity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.view.Window;
import android.widget.AdapterView;
import android.widget.Button;
import android.widget.Toast;
public class RSSReaderActivity extends ListActivity implements View.OnClickListener, ITaskEntity {
private FeedList mList = null;
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.main);
Button button;
button = (Button) findViewById(R.id.Get);
button.setOnClickListener(this);
button = (Button) findViewById(R.id.Config);
button.setOnClickListener(this);
getListView().setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int pos, long id) {
FeedItem item = (FeedItem) getListView().getItemAtPosition(pos);
Intent intent = new Intent(getApplicationContext(), com.beatcraft.rssreader.DescActivity.class);
intent.putExtra("FeedTitle", item.feedTitle());
intent.putExtra("ArticleTitle", item.articleTitle());
intent.putExtra("PubDate", item.pubDate());
intent.putExtra("Description", item.description());
intent.putExtra("Link", item.link());
startActivity(intent);
}
});
}
@Override
public void onClick(View view) {
int id = view.getId();
switch (id) {
case R.id.Get:
mList = null;
HttpAccessTask task = new HttpAccessTask(this);
task.execute(this);
break;
case R.id.Config:
showConfig();
break;
}
}
@Override
public void backgroundProc() {
RSSReaderApplication app = (RSSReaderApplication) getApplication();
mList = new FeedList();
mList.get(app);
}
@Override
public void postProc() {
if (mList.count() > 0) {
ArrayList<FeedItem> list = mList.getList();
if (list != null) {
FeedAdapter adapter = new FeedAdapter(this, list);
setListAdapter(adapter);
}
}
else {
Toast.makeText(this, "* NOT FOUND *", Toast.LENGTH_SHORT).show();
}
}
private void showConfig() {
Intent intent = new Intent(getApplicationContext(), com.beatcraft.rssreader.ConfigActivity.class);
startActivity(intent);
}
}
~
CENTER:&ref(./03_09.png,);
CENTER:図3-9~
-リスト4-50 DescActivity.java :
package com.beatcraft.rssreader;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.view.Window;
import android.webkit.WebView;
import android.widget.Button;
import android.widget.TextView;
public class DescActivity extends Activity implements View.OnClickListener {
private static final String WEBVIEW_BEGIN =
"<html><head><meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\" /></head><body bgcolor=\"#f6f6f6\">";
private static final String WEBVIEW_LINK = "<p><a href=\"%s\">%s</a></p>";
private static final String WEBVIEW_END = "</body></html>";
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.description);
Button button = (Button) findViewById(R.id.Back);
button.setOnClickListener(this);
Intent intent = getIntent();
if (intent != null) {
String tmp = "";
String desc = "";
TextView tv;
tv = (TextView) findViewById(R.id.FeedTitle);
tmp = intent.getStringExtra("FeedTitle");
tv.setText(tmp);
tv = (TextView) findViewById(R.id.ArticleTitle);
tmp = intent.getStringExtra("ArticleTitle");
tv.setText(tmp);
tv = (TextView) findViewById(R.id.PubDate);
tmp = intent.getStringExtra("PubDate");
tv.setText(tmp);
WebView wv = (WebView) findViewById(R.id.Description);
desc = WEBVIEW_BEGIN;
tmp = intent.getStringExtra("Link");
desc += String.format(WEBVIEW_LINK, tmp, tmp);
tmp = intent.getStringExtra("Description");
desc += tmp + WEBVIEW_END;
wv.loadDataWithBaseURL("about:blank", desc, "text/html", "utf-8", null);
}
}
@Override
public void onClick(View view) {
if (view.getId() == R.id.Back) {
finish();
}
}
}
~
**3-7. IResizer全ソースコード/XMLファイル [#b42f44c3]
-リスト4-51 ConfigActivity.java :
package com.beatcraft.rssreader;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.view.Window;
import android.view.WindowManager.LayoutParams;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Spinner;
public class ConfigActivity extends Activity implements View.OnClickListener {
public static final int REQUEST_RECOMMEND = 1234;
private static final int NUM_OF_GET_LIST[] = {1, 3, 5};
private Spinner mNumOfGet;
private EditText mFeedURL[];
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
this.getWindow().setSoftInputMode(LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN);
setContentView(R.layout.config);
RSSReaderApplication app = (RSSReaderApplication) getApplication();
ArrayAdapter<CharSequence> adapter;
adapter = ArrayAdapter.createFromResource(this, R.array.number_of_get, android.R.layout.simple_spinner_item);
adapter.setDropDownViewResource(
android.R.layout.simple_spinner_dropdown_item);
mNumOfGet = (Spinner) findViewById(R.id.NumOfGet);
mNumOfGet.setAdapter(adapter);
for (int i = 0; i < 3; ++i) {
if (NUM_OF_GET_LIST[i] == app.numberOfGet()) {
mNumOfGet.setSelection(i);
break;
}
}
mFeedURL = new EditText[RSSReaderApplication.NUM_OF_FEED];
mFeedURL[0] = (EditText) findViewById(R.id.FeedURL01);
mFeedURL[1] = (EditText) findViewById(R.id.FeedURL02);
mFeedURL[2] = (EditText) findViewById(R.id.FeedURL03);
mFeedURL[3] = (EditText) findViewById(R.id.FeedURL04);
mFeedURL[4] = (EditText) findViewById(R.id.FeedURL05);
mFeedURL[5] = (EditText) findViewById(R.id.FeedURL06);
mFeedURL[6] = (EditText) findViewById(R.id.FeedURL07);
mFeedURL[7] = (EditText) findViewById(R.id.FeedURL08);
mFeedURL[8] = (EditText) findViewById(R.id.FeedURL09);
mFeedURL[9] = (EditText) findViewById(R.id.FeedURL10);
for (int i = 0; i < RSSReaderApplication.NUM_OF_FEED; ++i) {
mFeedURL[i].setText(app.feedURL(i));
}
Button button;
button = (Button) findViewById(R.id.Recommend);
button.setOnClickListener(this);
button = (Button) findViewById(R.id.Cancel);
button.setOnClickListener(this);
button = (Button) findViewById(R.id.Regist);
button.setOnClickListener(this);
}
@Override
public void onClick(View view) {
int id = view.getId();
switch (id) {
case R.id.Recommend:
showRecommend();
break;
case R.id.Cancel:
finish();
break;
case R.id.Regist:
regist();
break;
}
}
protected void onActivityResult(int reqCode, int result, Intent data) {
super.onActivityResult(reqCode, result, data);
if (result == RESULT_OK) {
if (reqCode == REQUEST_RECOMMEND) {
int count = 0;
count = data.getIntExtra("RecommendCount", 0);
for (int i = 0; i < count; ++i) {
String key = "";
String url = "";
key = String.format("Recommend%02d", (i + 1));
url = data.getStringExtra(key);
if (url.equals("") == false) {
for (int j = 0; j < RSSReaderApplication.NUM_OF_FEED; ++j) {
String tmp = mFeedURL[j].getText().toString().trim();
if (tmp.equals("") == true) {
mFeedURL[j].setText(url);
break;
}
}
}
}
}
}
}
private void showRecommend() {
Intent intent = new Intent(getApplicationContext(), com.beatcraft.rssreader.RecommendActivity.class);
startActivityForResult(intent, REQUEST_RECOMMEND);
}
private void regist() {
RSSReaderApplication app = (RSSReaderApplication) getApplication();
app.setNumberOfGet(NUM_OF_GET_LIST[mNumOfGet.getSelectedItemPosition()]);
app.clearFeedURL();
for (int i = 0, j = 0; i < RSSReaderApplication.NUM_OF_FEED; ++i) {
String url = mFeedURL[i].getText().toString().trim();
if (url.equals("") == false) {
app.setFeedURL(j++, url);
}
}
app.saveConfig();
finish();
}
}
~
-リスト3-13 IResizerActivity.java :
package com.beatcraft.iresizer;
-リスト4-52 RecommendActivity.java :
package com.beatcraft.rssreader;
import java.io.InputStream;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.NameValuePair;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.Credentials;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.protocol.HTTP;
import org.xmlpull.v1.XmlPullParser;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.ContentValues;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.res.Configuration;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.os.Bundle;
import android.provider.MediaStore;
import android.text.format.DateFormat;
import android.util.Xml;
import android.view.View;
import android.view.Window;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
import android.widget.Spinner;
public class IResizerActivity extends Activity implements View.OnClickListener {
public static final int REQUEST_SELECT_IMAGE = 1234;
public static final int REQUEST_TAKE_PHOTO = 1235;
public class RecommendActivity extends Activity implements View.OnClickListener, ITaskEntity {
private static final String GENRE[] = {"news", "music", "movie"};
private static final String POST_DOMAIN = "labs.beatcraft.com";
private static final String POST_PATH = "/ja/androidtext/recommend.php";
private static final String POST_USER = "beatandroid";
private static final String POST_PASS = "sample";
private static final int NUM_OF_RECOMMEND = 3;
private ImageView mPreview;
private EditText mWSize;
private EditText mHSize;
private Spinner mGenre;
private CheckBox mFeedCheck[];
private String mRecommendTitle[];
private String mRecommendURL[];
private Button mAddFeed;
private int mStat = -1;
private Bitmap mBitmap = null;
private int mOrgW = 0;
private int mOrgH = 0;
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.recommend);
private Uri mUriFromCamera;
ArrayAdapter<CharSequence> adapter;
adapter = ArrayAdapter.createFromResource(this, R.array.genre, android.R.layout.simple_spinner_item);
adapter.setDropDownViewResource(
android.R.layout.simple_spinner_dropdown_item);
mGenre = (Spinner) findViewById(R.id.Genre);
mGenre.setAdapter(adapter);
private String mFileName = "";
private Dialog mDialog = null;
Button button;
button = (Button) findViewById(R.id.Search);
button.setOnClickListener(this);
button = (Button) findViewById(R.id.Back);
button.setOnClickListener(this);
mAddFeed = (Button) findViewById(R.id.AddFeed);
mAddFeed.setOnClickListener(this);
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setupUI();
mFeedCheck = new CheckBox[NUM_OF_RECOMMEND];
mFeedCheck[0] = (CheckBox) findViewById(R.id.Feed01);
mFeedCheck[1] = (CheckBox) findViewById(R.id.Feed02);
mFeedCheck[2] = (CheckBox) findViewById(R.id.Feed03);
mRecommendTitle = new String[NUM_OF_RECOMMEND];
mRecommendURL = new String[NUM_OF_RECOMMEND];
}
if (Intent.ACTION_SEND.equals(getIntent().getAction())) {
Uri uri = getIntent().getParcelableExtra(Intent.EXTRA_STREAM);
mBitmap = getImage(uri);
setPreview();
}
}
@Override
public void onClick(View view) {
int id = view.getId();
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
setupUI();
}
switch (id) {
case R.id.Search:
HttpAccessTask task = new HttpAccessTask(this);
task.execute(this);
break;
@Override
public void onClick(View view) {
int id = view.getId();
case R.id.Back:
finish();
break;
switch (id) {
case R.id.SelectImage:
selectImage();
break;
case R.id.AddFeed:
addFeed();
break;
}
}
case R.id.TakePhoto:
takePhoto();
break;
@Override
public void backgroundProc() {
for (int i = 0; i < NUM_OF_RECOMMEND; ++i) {
mRecommendTitle[i] = "";
mRecommendURL[i] = "";
}
mStat = get(GENRE[mGenre.getSelectedItemPosition()]);
}
case R.id.Save:
showConfirmDialog();
break;
@Override
public void postProc() {
if (mStat == 0) {
for (int i = 0; i < NUM_OF_RECOMMEND; ++i) {
mFeedCheck[i].setText(mRecommendTitle[i]);
mFeedCheck[i].setVisibility(View.VISIBLE);
mFeedCheck[i].setChecked(false);
}
mAddFeed.setVisibility(View.VISIBLE);
}
else {
for (int i = 0; i < NUM_OF_RECOMMEND; ++i) {
mFeedCheck[i].setText("");
mFeedCheck[i].setVisibility(View.INVISIBLE);
mFeedCheck[i].setChecked(false);
}
mAddFeed.setVisibility(View.INVISIBLE);
}
}
case R.id.ExecSave:
case R.id.Cancel:
if (mDialog != null) {
if (id == R.id.ExecSave) {
execSave();
private int get(String genre) {
String url = "http://" + POST_DOMAIN + POST_PATH;
DefaultHttpClient client = new DefaultHttpClient();
if (client != null) {
client.getParams().setParameter("http.socket.timeout", new Integer(15000));
HttpPost method = null;
try {
method = new HttpPost(url);
}
catch (Exception e) {
e.printStackTrace();
}
if (method == null) {
return -1;
}
HttpResponse response = null;
try {
List<NameValuePair> pair = new ArrayList<NameValuePair>();
pair.add(new BasicNameValuePair("genre", genre));
method.setEntity(new UrlEncodedFormEntity(pair, HTTP.UTF_8));
Credentials cred = new UsernamePasswordCredentials(POST_USER, POST_PASS);
client.getCredentialsProvider().setCredentials(new AuthScope(POST_DOMAIN, 80), cred);
response = client.execute(method);
int ret = response.getStatusLine().getStatusCode();
if (ret == HttpStatus.SC_OK) {
InputStream is = response.getEntity().getContent();
return parse(is);
}
}
catch (Exception e) {
e.printStackTrace();
}
finally {
client.getConnectionManager().shutdown();
}
}
return -1;
}
private int parse(InputStream is) {
int count = 0;
boolean inFeed = false;
XmlPullParser p = Xml.newPullParser();
try {
p.setInput(is, null);
int event = p.getEventType();
while (event != XmlPullParser.END_DOCUMENT) {
String elem = null;
String tmp = null;
switch (event) {
case XmlPullParser.START_TAG:
elem = p.getName();
if (elem.equals("feed") == true) {
if (inFeed == true) {
count++;
if (count >= NUM_OF_RECOMMEND) {
return 0;
}
}
inFeed = true;
}
mDialog.dismiss();
mDialog = null;
}
break;
}
}
else if (elem.equals("title") == true) {
tmp = p.nextText();
if (tmp != null) {
mRecommendTitle[count] = tmp;
}
}
else if (elem.equals("url") == true) {
tmp = p.nextText();
if (tmp != null) {
mRecommendURL[count] = tmp;
}
}
break;
protected void onActivityResult(int reqCode, int result, Intent data) {
super.onActivityResult(reqCode, result, data);
case XmlPullParser.END_TAG:
elem = p.getName();
if (elem.equals("feed") == true) {
count++;
if (count >= NUM_OF_RECOMMEND) {
return 0;
}
inFeed = false;
}
break;
}
Uri uri = null;
event = p.next();
}
}
catch (Exception e) {
e.printStackTrace();
return -1;
}
return 0;
}
if (result == RESULT_OK) {
if (reqCode == REQUEST_SELECT_IMAGE) {
uri = data.getData();
} else if (reqCode == REQUEST_TAKE_PHOTO) {
uri = mUriFromCamera;
}
mBitmap = getImage(uri);
setPreview();
} else if (reqCode == REQUEST_TAKE_PHOTO) {
getContentResolver().delete(mUriFromCamera, null, null);
}
}
private void addFeed() {
int count = 0;
Intent result = new Intent();
private void setupUI() {
setContentView(R.layout.main);
for (int i = 0; i < NUM_OF_RECOMMEND; ++i) {
if (mFeedCheck[i].isChecked() == true) {
String key = "";
key = String.format("Recommend%02d", (count + 1));
result.putExtra(key, mRecommendURL[i]);
count++;
}
}
result.putExtra("RecommendCount", count);
setResult(RESULT_OK, result);
finish();
}
Button button;
button = (Button) findViewById(R.id.SelectImage);
button.setOnClickListener(this);
}
~
-リスト4-53 FeedItem.java :
package com.beatcraft.rssreader;
button = (Button) findViewById(R.id.TakePhoto);
button.setOnClickListener(this);
public class FeedItem {
public static final int ITEMTYPE_FEEDCHANNEL = 0;
public static final int ITEMTYPE_FEEDITEM = 1;
button = (Button) findViewById(R.id.Save);
button.setOnClickListener(this);
private int mItemType;
private String mFeedTitle = "";
private String mArticleTitle = "";
private String mPubDate = "";
private String mDescription = "";
private String mLink = "";
mPreview = (ImageView) findViewById(R.id.Preview);
mWSize = (EditText) findViewById(R.id.WSize);
mHSize = (EditText) findViewById(R.id.HSize);
setPreview();
public FeedItem(int itemType) {
mItemType = itemType;
}
mWSize.setOnFocusChangeListener(new View.OnFocusChangeListener() {
@Override
public void onFocusChange(View v, boolean flag) {
if (flag == false) {
CheckBox keepRatio = (CheckBox) findViewById(R.id.KeepRatio);
if (keepRatio.isChecked() == true) {
String base = mWSize.getText().toString().trim();
if ((base.equals("") == false) && (mOrgW != 0)
&& (mOrgH != 0)) {
double h = 0;
double w = Integer.parseInt(base);
double ratio = (double) mOrgH / (double) mOrgW;
h = new BigDecimal(w * ratio).setScale(0,
BigDecimal.ROUND_HALF_UP).doubleValue();
if (h < 1.0) {
h = 1.0;
}
mHSize.setText(String.valueOf((int) h));
}
}
public int itemType() {
return mItemType;
}
public String feedTitle() {
return mFeedTitle;
}
public void setFeedTitle(String title) {
mFeedTitle = title;
}
public String articleTitle() {
return mArticleTitle;
}
public void setArticleTitle(String title) {
mArticleTitle = title;
}
public String pubDate() {
return mPubDate;
}
public void setPubDate(String pubDate) {
mPubDate = pubDate;
}
public String description() {
return mDescription;
}
public void setDescription(String description) {
mDescription = description;
}
public String link() {
return mLink;
}
public void setLink(String link) {
mLink = link;
}
}
~
-リスト4-54 FeedList.java :
package com.beatcraft.rssreader;
import java.io.InputStream;
import java.util.ArrayList;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;
import org.xmlpull.v1.XmlPullParser;
import android.util.Xml;
public class FeedList {
private ArrayList<FeedItem> mList = null;
public FeedList() {
mList = new ArrayList<FeedItem>();
}
public ArrayList<FeedItem> getList() {
return mList;
}
public int count() {
if (mList != null) {
return mList.size();
}
return 0;
}
public int get(RSSReaderApplication app) {
int success = 0;
for (int i = 0; i < RSSReaderApplication.NUM_OF_FEED; ++i) {
String url = app.feedURL(i);
if (url.equals("") == true) {
continue;
}
DefaultHttpClient client = new DefaultHttpClient();
if (client != null) {
client.getParams().setParameter("http.socket.timeout", new Integer(15000));
HttpGet method = null;
try {
method = new HttpGet(url);
}
catch (Exception e) {
e.printStackTrace();
}
if (method == null) {
continue;
}
HttpResponse response = null;
try {
response = client.execute(method);
int ret = response.getStatusLine().getStatusCode();
if (ret == HttpStatus.SC_OK) {
InputStream is = response.getEntity().getContent();
if (parse(is, app.numberOfGet()) > 0) {
success++;
}
is.close();
}
}
});
mHSize.setOnFocusChangeListener(new View.OnFocusChangeListener() {
@Override
public void onFocusChange(View v, boolean flag) {
if (flag == false) {
CheckBox keepRatio = (CheckBox) findViewById(R.id.KeepRatio);
if (keepRatio.isChecked() == true) {
String base = mHSize.getText().toString().trim();
if ((base.equals("") == false) && (mOrgW != 0)
&& (mOrgH != 0)) {
double w = 0;
double h = Integer.parseInt(base);
double ratio = (double) mOrgW / (double) mOrgH;
w = new BigDecimal(h * ratio).setScale(0,
BigDecimal.ROUND_HALF_UP).doubleValue();
if (w < 1.0) {
w = 1.0;
}
mWSize.setText(String.valueOf((int) w));
}
}
}
catch (Exception e) {
e.printStackTrace();
}
finally {
client.getConnectionManager().shutdown();
}
}
}
return success;
}
private int parse(InputStream is, int max) {
int count = 0;
boolean inChannel = false;
boolean inItem = false;
FeedItem item = null;
String feedTitle = "";
XmlPullParser p = Xml.newPullParser();
try {
p.setInput(is, null);
int event = p.getEventType();
while (event != XmlPullParser.END_DOCUMENT) {
String elem = null;
String tmp = null;
switch (event) {
case XmlPullParser.START_TAG:
elem = p.getName();
if (elem.equals("channel") == true) {
inChannel = true;
item = new FeedItem(FeedItem.ITEMTYPE_FEEDCHANNEL);
}
}
});
}
else if (elem.equals("item") == true) {
if (inChannel == true) {
if (item != null) {
mList.add(item);
item = null;
count++;
}
inChannel = false;
}
inItem = true;
item = new FeedItem(FeedItem.ITEMTYPE_FEEDITEM);
item.setFeedTitle(feedTitle);
}
else if (elem.equals("title") == true) {
tmp = p.nextText();
if ((tmp != null) && (item != null)) {
if (inChannel == true) {
feedTitle = tmp;
item.setFeedTitle(tmp);
}
else if (inItem == true) {
item.setArticleTitle(tmp);
}
}
}
else if ((elem.equals("pubDate") == true)
|| (elem.equals("date") == true)) {
if (inItem == true) {
tmp = p.nextText();
if ((tmp != null) && (item != null)) {
item.setPubDate(tmp);
}
}
}
else if (elem.equals("description") == true) {
if (inItem == true) {
tmp = p.nextText();
if ((tmp != null) && (item != null)) {
item.setDescription(tmp);
}
}
}
else if (elem.equals("link") == true) {
if (inItem == true) {
tmp = p.nextText();
if ((tmp != null) && (item != null)) {
item.setLink(tmp);
}
}
}
break;
case XmlPullParser.END_TAG:
elem = p.getName();
if (elem.equals("channel") == true) {
if (inChannel == true) {
if (item != null) {
mList.add(item);
item = null;
count++;
}
inChannel = false;
}
}
else if (elem.equals("item") == true) {
if (inItem == true) {
if (item != null) {
mList.add(item);
item = null;
count++;
max--;
if (max == 0) {
return count;
}
}
inItem = false;
}
}
}
private void selectImage() {
try {
Intent intent = new Intent(Intent.ACTION_PICK);
intent.setType("image/*");
startActivityForResult(intent, REQUEST_SELECT_IMAGE);
} catch (Exception e) {
AlertDialog.Builder builder = null;
builder = new AlertDialog.Builder(this);
builder.setMessage(getString(R.string.app_notfound));
builder.setNeutralButton("OK",
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
}
});
builder.show();
}
}
event = p.next();
}
}
catch (Exception e) {
e.printStackTrace();
return 0;
}
return count;
}
}
~
-リスト4-55 FeedAdapter.java :
package com.beatcraft.rssreader;
private void takePhoto() {
try {
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
ContentValues values = new ContentValues();
values.put(MediaStore.Images.Media.MIME_TYPE, "image/*");
mUriFromCamera = getContentResolver().insert(
MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
intent.putExtra(MediaStore.EXTRA_OUTPUT, mUriFromCamera);
startActivityForResult(intent, REQUEST_TAKE_PHOTO);
} catch (Exception e) {
AlertDialog.Builder builder = null;
builder = new AlertDialog.Builder(this);
builder.setMessage(getString(R.string.app_notfound));
builder.setNeutralButton("OK",
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
}
});
builder.show();
}
}
import java.util.List;
private void showConfirmDialog() {
if ((mBitmap != null)
&& (mWSize.getText().toString().trim().equals("") == false)
&& (mHSize.getText().toString().trim().equals("") == false)) {
long ctime = System.currentTimeMillis();
mFileName = DateFormat.format("yyyyMMdd_kkmmss", ctime).toString()
+ ".jpg";
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.TextView;
mDialog = new Dialog(this);
mDialog.setContentView(R.layout.dialog);
mDialog.setTitle(getString(R.string.save_title));
Button button;
button = (Button) mDialog.findViewById(R.id.ExecSave);
button.setOnClickListener(this);
button = (Button) mDialog.findViewById(R.id.Cancel);
button.setOnClickListener(this);
TextView tv;
tv = (TextView) mDialog.findViewById(R.id.FileName);
tv.setText(mFileName);
tv = (TextView) mDialog.findViewById(R.id.ImageSize);
tv.setText(mWSize.getText().toString() + "x"
+ mHSize.getText().toString());
mDialog.show();
}
}
public class FeedAdapter extends ArrayAdapter<FeedItem> {
private LayoutInflater mInflate;
private void execSave() {
int w = Integer.parseInt(mWSize.getText().toString());
int h = Integer.parseInt(mHSize.getText().toString());
Bitmap bitmap = Bitmap.createScaledBitmap(mBitmap, w, h, true);
public FeedAdapter(Context context, List<FeedItem> obj) {
super(context, 0, obj);
mInflate = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
try {
MediaStore.Images.Media.insertImage(getContentResolver(), bitmap,
mFileName, null);
Toast.makeText(this, getString(R.string.save_success),
Toast.LENGTH_SHORT).show();
} catch (Exception e) {
e.printStackTrace();
Toast.makeText(this, getString(R.string.save_failed),
Toast.LENGTH_SHORT).show();
}
}
@Override
public boolean isEnabled(int pos) {
FeedItem item = getItem(pos);
if (item.itemType() == FeedItem.ITEMTYPE_FEEDCHANNEL) {
return false;
}
return true;
}
private Bitmap getImage(Uri uri) {
try {
InputStream is = getContentResolver().openInputStream(uri);
return BitmapFactory.decodeStream(is);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
public View getView(final int pos, View convView, ViewGroup parent) {
View view = convView;
FeedItem item = getItem(pos);
private void setPreview() {
if (mBitmap != null) {
mOrgW = mBitmap.getWidth();
mOrgH = mBitmap.getHeight();
mWSize.setText(String.valueOf(mOrgW));
mHSize.setText(String.valueOf(mOrgH));
mPreview.setImageBitmap(mBitmap);
}
}
switch (item.itemType()) {
case FeedItem.ITEMTYPE_FEEDCHANNEL:
view = buildChannel(item);
break;
case FeedItem.ITEMTYPE_FEEDITEM:
view = buildItem(item);
break;
}
return view;
}
private View buildChannel(FeedItem item) {
View view = null;
view = mInflate.inflate(R.layout.item_channel, null);
TextView tv;
tv = (TextView) view.findViewById(R.id.FeedTitle);
tv.setText(item.feedTitle());
return view;
}
private View buildItem(FeedItem item) {
View view = null;
view = mInflate.inflate(R.layout.item_item, null);
TextView tv;
tv = (TextView) view.findViewById(R.id.ArticleTitle);
tv.setText(item.articleTitle());
tv = (TextView) view.findViewById(R.id.PubDate);
tv.setText(item.pubDate());
return view;
}
}
~
-リスト3-14 controller.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" >
-リスト4-56 ITaskEntity.java :
package com.beatcraft.rssreader;
<Button
android:id="@+id/SelectImage"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="10dip"
android:layout_marginRight="10dip"
android:text="@string/select_image" />
public interface ITaskEntity {
void backgroundProc();
void postProc();
}
~
-リスト4-57 HttpAccessTask.java :
package com.beatcraft.rssreader;
<Button
android:id="@+id/TakePhoto"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="10dip"
android:layout_marginRight="10dip"
android:text="@string/take_photo" />
import android.app.Activity;
import android.app.ProgressDialog;
import android.os.AsyncTask;
<LinearLayout
android:id="@+id/WLayout"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="10dip"
android:layout_marginRight="10dip" >
public class HttpAccessTask extends AsyncTask<ITaskEntity, Integer, Void> {
private Activity mActivity;
private ProgressDialog mDialog;
private ITaskEntity mITaskEntity;
<TextView
android:id="@+id/WLabel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/label_w" />
public HttpAccessTask(Activity activity) {
mActivity = activity;
}
<EditText
android:id="@+id/WSize"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:inputType="number" >
@Override
protected void onPreExecute() {
mDialog = new ProgressDialog(mActivity);
mDialog.setMessage(mActivity.getString(R.string.loading));
mDialog.show();
}
<requestFocus />
</EditText>
@Override
protected Void doInBackground(ITaskEntity... params) {
mITaskEntity = params[0];
mITaskEntity.backgroundProc();
return null;
}
<TextView
android:id="@+id/WSuffix"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="px" />
</LinearLayout>
@Override
protected void onPostExecute(Void v) {
mITaskEntity.postProc();
mDialog.dismiss();
mDialog = null;
}
}
~
-リスト4-58 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:id="@+id/HLayout"
android:id="@+id/linearLayout1"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="10dip"
android:layout_marginRight="10dip" >
android:layout_margin="10dip" >
<TextView
android:id="@+id/HLabel"
<Button
android:id="@+id/Get"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/label_h" />
android:layout_marginLeft="5dip"
android:layout_marginRight="5dip"
android:layout_weight="1"
android:text="@string/get_feed" />
<EditText
android:id="@+id/HSize"
<Button
android:id="@+id/Config"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="5dip"
android:layout_marginRight="5dip"
android:layout_weight="1"
android:inputType="number" >
</EditText>
<TextView
android:id="@+id/HSuffix"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="px" />
android:text="@string/config" />
</LinearLayout>
<CheckBox
android:id="@+id/KeepRatio"
<ListView
android:id="@+id/android:list"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="10dip"
android:layout_marginRight="10dip"
android:checked="true"
android:text="@string/keep_ratio" />
android:fastScrollEnabled="true" >
</ListView>
<Button
android:id="@+id/Save"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="10dip"
android:layout_marginRight="10dip"
android:text="@string/save" />
</LinearLayout>
~
-リスト3-15 layout/main.xml :
-リスト4-59 item_channel.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:background="#cccccc"
android:orientation="vertical" >
<ImageView
android:id="@+id/Preview"
android:layout_width="fill_parent"
android:layout_height="0dip"
<TextView
android:id="@+id/FeedTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="5dip"
android:layout_weight="1"
android:adjustViewBounds="true"
android:scaleType="fitCenter" />
android:text="FeedTitle"
android:textColor="#000000"
android:textSize="16dip"
android:textStyle="bold" />
<include
android:layout_width="fill_parent"
android:layout_height="wrap_content"
layout="@layout/controller" />
</LinearLayout>
~
-リスト3-16 layout-land/main.xml :
-リスト4-60 item_item.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" >
android:orientation="vertical" >
<ImageView
android:id="@+id/Preview"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
<TextView
android:id="@+id/ArticleTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="5dip"
android:layout_weight="1"
android:adjustViewBounds="true"
android:scaleType="fitCenter" />
android:text="ArticleTitle" />
<include
<TextView
android:id="@+id/PubDate"
android:layout_width="wrap_content"
android:layout_height="fill_parent"
layout="@layout/controller" />
android:layout_height="wrap_content"
android:layout_gravity="right"
android:layout_margin="5dip"
android:text="pubDate" />
</LinearLayout>
~
-リスト3-17 dialog.xml :
-リスト4-61 description.xml :
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/ScrollView"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >
android:layout_height="fill_parent" >
<LinearLayout
android:id="@+id/FileNameLayout"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="5dip"
android:layout_marginRight="5dip" >
android:layout_height="fill_parent"
android:orientation="vertical" >
<TextView
android:id="@+id/FileLabel"
<Button
android:id="@+id/Back"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/filename_label" />
android:layout_margin="5dip"
android:text="@string/back" />
<TextView
android:id="@+id/FileName"
android:id="@+id/FeedTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="5dip"
android:layout_weight="1" />
</LinearLayout>
android:layout_marginLeft="10dip"
android:layout_marginRight="10dip"
android:text="FeedTitle"
android:textSize="16dip"
android:textStyle="bold" />
<LinearLayout
android:id="@+id/ImageSizeLayout"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="5dip"
android:layout_marginRight="5dip" >
<TextView
android:id="@+id/SizeLabel"
android:id="@+id/ArticleTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/isize_label" />
android:layout_marginLeft="10dip"
android:layout_marginRight="10dip"
android:text="ArticleTitle" />
<TextView
android:id="@+id/ImageSize"
android:id="@+id/PubDate"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="5dip"
android:layout_weight="1" />
android:layout_gravity="right"
android:layout_marginRight="10dip"
android:text="pubDate" />
<android.webkit.WebView
android:id="@+id/Description"
android:layout_width="fill_parent"
android:layout_height="0dip"
android:layout_margin="5dip"
android:layout_weight="1" >
</android.webkit.WebView>
</LinearLayout>
<TextView
android:id="@+id/Description"
</ScrollView>
~
-リスト4-62 config.xml :
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/ScrollView"
android:layout_width="fill_parent"
android:layout_height="fill_parent" >
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="5dip"
android:layout_marginRight="5dip"
android:gravity="right"
android:text="@string/save_desc" />
android:layout_height="fill_parent"
android:orientation="vertical" >
<LinearLayout
android:id="@+id/linearLayout1"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_margin="10dip" >
<TextView
android:id="@+id/LabelNumOfGet"
android:layout_width="wrap_content"
android:layout_height="fill_parent"
android:gravity="center_vertical"
android:text="@string/label_number_of_get" />
<Spinner
android:id="@+id/NumOfGet"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<Button
android:id="@+id/Recommend"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="10dip"
android:layout_weight="1"
android:text="@string/recommend" />
</LinearLayout>
<EditText
android:id="@+id/FeedURL01"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="10dip"
android:layout_marginRight="10dip"
android:inputType="textUri" >
<requestFocus />
</EditText>
<EditText
android:id="@+id/FeedURL02"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="10dip"
android:layout_marginRight="10dip"
android:inputType="textUri" />
<EditText
android:id="@+id/FeedURL03"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="10dip"
android:layout_marginRight="10dip"
android:inputType="textUri" />
<EditText
android:id="@+id/FeedURL04"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="10dip"
android:layout_marginRight="10dip"
android:inputType="textUri" />
<EditText
android:id="@+id/FeedURL05"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="10dip"
android:layout_marginRight="10dip"
android:inputType="textUri" />
<EditText
android:id="@+id/FeedURL06"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="10dip"
android:layout_marginRight="10dip"
android:inputType="textUri" />
<EditText
android:id="@+id/FeedURL07"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="10dip"
android:layout_marginRight="10dip"
android:inputType="textUri" />
<EditText
android:id="@+id/FeedURL08"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="10dip"
android:layout_marginRight="10dip"
android:inputType="textUri" />
<EditText
android:id="@+id/FeedURL09"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="10dip"
android:layout_marginRight="10dip"
android:inputType="textUri" />
<EditText
android:id="@+id/FeedURL10"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="10dip"
android:layout_marginRight="10dip"
android:inputType="textUri" />
<LinearLayout
android:id="@+id/linearLayout2"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_margin="10dip" >
<Button
android:id="@+id/Cancel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="5dip"
android:layout_marginRight="5dip"
android:layout_weight="1"
android:text="@string/cancel" />
<Button
android:id="@+id/Regist"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="5dip"
android:layout_marginRight="5dip"
android:layout_weight="1"
android:text="@string/regist" />
</LinearLayout>
</LinearLayout>
</ScrollView>
~
-リスト4-63 recommend.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:id="@+id/ButtonLayout"
android:id="@+id/linearLayout1"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dip"
android:gravity="center_horizontal" >
android:layout_margin="10dip" >
<Button
android:id="@+id/ExecSave"
<TextView
android:id="@+id/LabelGenre"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/save" />
android:layout_height="fill_parent"
android:gravity="center_vertical"
android:text="@string/label_genre" />
<Spinner
android:id="@+id/Genre"
android:layout_width="120dip"
android:layout_height="wrap_content" />
<Button
android:id="@+id/Cancel"
android:id="@+id/Search"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/cancel" />
android:layout_marginLeft="10dip"
android:layout_weight="1"
android:text="@string/search" />
</LinearLayout>
<Button
android:id="@+id/Back"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="5dip"
android:text="@string/back" />
<CheckBox
android:id="@+id/Feed01"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_margin="5dip"
android:text="CheckBox"
android:visibility="invisible" />
<CheckBox
android:id="@+id/Feed02"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_margin="5dip"
android:text="CheckBox"
android:visibility="invisible" />
<CheckBox
android:id="@+id/Feed03"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_margin="5dip"
android:text="CheckBox"
android:visibility="invisible" />
<Button
android:id="@+id/AddFeed"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_margin="5dip"
android:text="@string/addfeed"
android:visibility="invisible" />
</LinearLayout>
~
-リスト3-18 AndroidManifest.xml :
-リスト4-64 AndroidManifest.xml :
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.beatcraft.iresizer"
package="com.beatcraft.rssreader"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk android:minSdkVersion="7" />
<application
android:icon="@drawable/ic_launcher"
android:label="@string/app_name" >
android:label="@string/app_name"
android:name="com.beatcraft.rssreader.RSSReaderApplication" >
<activity
android:configChanges="orientation"
android:label="@string/app_name"
android:name=".IResizerActivity" >
android:name=".RSSReaderActivity" >
<intent-filter >
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter >
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="image/*" />
<action android:name="android.intent.action.SEND" />
</intent-filter>
</activity>
<activity
android:label="@string/app_name"
android:name=".ConfigActivity" />
<activity
android:label="@string/app_name"
android:name=".DescActivity" />
<activity
android:label="@string/app_name"
android:name=".RecommendActivity" />
</application>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.INTERNET" >
</uses-permission>
</manifest>
~
----
RIGHT:内藤