2011年7月31日日曜日

カッコいい時計ウィジェットをリリースしました。

Band O'clock




複数並べて世界時計のようにしたり



好きなフォントにしてみたり



できます。



ウィジェット本体よりも設定画面にかなりこだわってます。

デフォルトの PreferenceActivity を使って簡単に作ることもできますが、それって使いにくくないですか?

・その設定によってウィジェットがどう変わるのかわからない
・設定をやり直すのに毎回ダイアログを開かないといけない

このあたりを改善したくて、

・設定画面にウィジェットのプレビューをつけて設定によってリアルタイムで変わるように
・複数の選択肢から1つを選ぶのに Spinner を使わない

というポリシーで、こんな感じの画面になってます。
各画面にはフリックで移動できます。



フォントも変えられます。
フォントインポート機能を購入すると、自分の用意した好きなフォントも使うことができます。



色を変えたり




都市名やタイムゾーンを設定できます。




タイムゾーンの設定中もプレビューが隠れないようになってます。


よかったらぜひ使ってみてくださいませ〜。





  

Android 3.2 Platform

Android 3.2 Platform

API Level: 13

Android 3.2 へようこそ!

Android 3.2 はユーザーと開発者むけの新しい機能を追加したインクリメンタルプラットフォームリリースです。このセクションでは、新しい機能と developer APIs のオーバービューを紹介します。

開発者は Android SDK のコンポーネントとして Android 3.2 platform をダウンロードできるようになりました。このプラットフォームには Android library と system image, emulator skin などが含まれていますが、外部のライブラリは含まれていません。

Android 3.2 向けの開発とテストを始めるには、Android SDK Manager を使って自分の SDK にプラットフォームをダウンロードします。より詳しい情報は Adding SDK Components を見てください。Android が初めての人は、まず download the SDK Starter Package から始めてください。

リマインダー: すでに Android application を公開している場合、できるだけ早く Android 3.2 上であなたのアプリケーションのテストと最適化をしてください。最新の Android-powered デバイスでベストな体験を提供していることを確認するべきです。そのためにあなたができることについての情報は Optimizing Apps for Android 3.x を見てください。

---
Platform Highlights

New user features

  • ワイドレンジのタブレット用の最適化
    Android 3.2 はワイドレンジのタブレットデバイスでユーザー体験をすばらしいものにするためのシステム全体にわたるさまざまな最適化を含んでいます。

  • 固定サイズのアプリ用ズーム互換性
    Android 3.2 は、大画面のデバイス上の固定サイズアプリを見る新しい方法である、compatibility zoom mode (ズーム互換性モード)をサポートします。新しいモードは、タブレットのような大画面サイズ上で動くようにデザインされていない標準の UI ストレッチに代わって pixel-scaled を提供します。ユーザーは、互換性サポートが必要なアプリに対して、システムバーのメニューアイコンから新しいモードにアクセスできます。

  • SD card からの Media sync
    SD card をサポートしているデバイスで、ユーザーはメディアファイルを SD card からそれを使用するアプリへ直接ロードできるようになりました。システムの機能によって、アプリがシステムメディアストアからファイルにアクセスできるようになりました。



New developer features

  • 画面サポートを管理する API の拡張
    Android 3.2 は、さまざまな Android-powered device でのアプリケーション UI の管理方法に新しいものを追加した、プラットフォームスクリーンサポート API の拡張を提供します。API は新しいリソース識別子と manifest 属性を含みます。これにより異なるサイズ上でのアプリの表示を、一般的なサイズのカテゴリに頼るよりも正確にコントロールできます。
    fixed-sized アプリと、いくつかのスクリーンサイズむけの限定的なサポートをしているアプリに対しては、最も可能性のあるディスプレイを確認してください。プラットフォームは、UI を小さいスクリーン領域に描画して、それをディスプレイの可能な領域に拡大する、新しいズーム互換性モードも提供します。スクリーンサポート API のより詳しい情報とその使い方は以降のセクションをみてください。



---
API Overview

Screens Support APIs

Android 3.2 は新しいスクリーンサポート API を紹介します。これにより異なる画面サイズに渡ってどのようにアプリケーションが表示されるかをより細かくコントロールできます。新しい API は、プラットフォームの一般的なスクリーンサイズモデルを含む既存のスクリーンサポート API 上に作られ、一般的なスクリーンサイズ (large や xlarge) よりも、"dpi" 単位で測定されたサイズ (600dp や 720dp) で指定したスクリーン範囲に正確にターゲットします。

アプリケーションの UI をデザインする際、プラットフォームが提供する抽象化された解像度に頼ることももちろんできます。これはつまり、アプリケーションがさまざまなデバイスの実際のピクセル密度の違いを補う必要がないということです。アプリケーションの UI を横および縦の利用可能な領域によってデザインすることができます。プラットフォームは利用可能な領域の量を新しい3つの特徴で表現します:smallestWidth, width, height


  • スクリーンの smallestWidth は "dp" 単位での基礎的な最小サイズです。スクリーンの高さおよび幅の短い方です。縦向きのスクリーンでは、smallestWidth は通常横幅に基づき、横向きでは縦幅に基づきます。全ての場合で、smallestWidth は固定の画面特性から導き出され、画面の向きによって値は変化しません。smallestWidth はアプリケーション UI が描画に必要とする最も短い幅(システムによって予約されている領域を含まない)を表すので、アプリケーションにとって重要です。

  • 対照的に、スクリーンの width と height は現在の横および縦のアプリケーションのレイアウトで利用可能な領域("dp"単位、システムによって予約されている領域を含まない)を表します。width と height はユーザーが画面の向きを縦向きや横向きに変更すると変わります。


新しいスクリーンサポート API は現在の画面の smallestWidth によってアプリケーションの UI を管理するようデザインされています。さらに必要に応じて、現在の横幅と縦幅によっても UI を管理することができます。これらの目的のため API は次のツールを提供します:

  • 最小 smallestWidth, width, height にレイアウトや他のリソースをターゲットするための新しい識別子

  • アプリの互換性を持つ範囲の最大スクリーンサイズを指定する新しいマニフェスト属性

加えて、アプリケーションはプラットフォームの以前のバージョンの起動時のリソース読み込みや UI の管理やシステムへの問い合わせもそのままできます。

新しい API では smallestWidth, width, height を使ってより直接的に画面サイズをターゲットできます。そのため、典型的なスクリーンタイプの異なる特徴を理解することは訳に立ちます。次の表でいくつかの例を "dp" 単位で載せました。

典型的なデバイスの解像度と画面サイズ

TypeDensity(generalized)Dimensions(dp)smallestWidth(dp)
Baseline phonemdpi320x480320
Small tablet/large phonemdpi480x800480
7-inch tabletmdpi600x1024600
10-inch tabletmdpi800x1280800


次のセクションでは新しいスクリーン識別子とマニフェスト属性のより詳しい情報を提供します。スクリーンサポート API の完全な情報については Supporting Multiple Screens を見てください。


New resource qualifiers for screens support

Android 3.2 の新しいリソース識別子によってさまざまなスクリーンサイズに対してレイアウトをよりうまくターゲットさせることができます。識別子を使って特定の最小 smallestWidth, 現在の横幅もしくは縦幅、に対してデザインされたリソース設定を作ることができます。

新しい識別子は:

  • swNNNdp — 最小の smallestWidth を指定する。 smallestWidth はリソースが使用されるべき "dp" 単位でのサイズ。上記で述べたように、スクリーンの smallestWidth は画面の向きによらず定数である。例えば: sw320dp, sw720dp, sw720dp

  • wNNNdp and hNNNdp — リソースが使用されるべき "dp" 単位での最小の横幅もしくは縦幅を指定する。上記で述べたように、スクリーンの横幅と縦幅は画面の向きに応じて変わる。例えば: w320dp, w720dp, h1024dp

複数にオーバーラップするリソース設定を作成することもできます。例えば、いくつかのリソースを画面が 480dp より広い場合にタグ付し、その他では 600dp より大きいもの、その他で 720dp より大きいもののようにできます。複数のリソース設定がスクリーン対して識別子と付けられている場合、システムは最も一致する設定を選択します。与えられたスクリーンに対してどのリソースが割り当てられるか正確にコントロールするために、リソースを1つの識別子、もしくは複数の識別子の組み合わせにタグ付することができます。

すでに表記した典型的なサイズに基づいた新しい識別子を使った例です:

# For phones
res/layout/main_activity.xml

# For 7” tablets
res/layout-sw600dp/main_activity.xml

# For 10” tablets
res/layout-sw720dp/main_activity.xml

# Multi-pane when enough width
res/layout-w600dp/main_activity.xml

# For large width
res/layout-sw600dp-w720dp/main_activity.xml


platform の古いバージョンでは新しい識別子は無視されます。そのため、すべてのデバイスに対応するために必要に応じてこれらをミックスすることができます。

# For phones
res/layout/main_activity.xml

# For pre-3.2 tablets
res/layout-xlarge/main_activity.xml

# For 3.2 and up tablets
res/layout-sw600dp/main_activity.xml

どのように新しい識別子を使うかの完全な情報は Using new size qualifiers を見てください。


New manifest attributes for screen-size compatibility

framework は新しい <supports-screens> マニフェスト属性のセットを提供します。これにより異なるスクリーンサイズに対するアプリのサポートを管理することができます。特に、動くようにデザインされた最大と最小のスクリーンサイズを指定することができ、また最大サイズはシステムの新しい screen compatibility mode が必要としないようデザインされたサイズです。上記で説明したリソース識別子のように、新しいマニフェスト属性はアプリケーションがサポートするスクリーンの範囲を指定します。

スクリーンサポートの新しいマニフェスト属性は:

  • android:compatibleWidthLimitDp="numDp" — 最大の largestWidth を指定します。largestWidth はアプリケーションが compatibility mode を必要としないで動作できるサイズです。現在のスクリーンが指定した値より大きい場合、システムはアプリケーションを通常のモードで表示します。しかし、ユーザーはオプションとしてシステムバーの設定から compatibility mode に切り替えることが可能です。

  • android:largestWidthLimitDp="numDp" — 最大の largestWidth を指定します。largestWidth はアプリケーションが動作するようデザインされたサイズです。現在のスクリーンが指定した値より大きい場合、現在のスクリーン上でベストな表示を行うために、システムは強制的にアプリケーションを compatibility mode で表示します。

  • android:smallestWidthLimitDp="numDp" — 最小の smallestWidth を指定します。smallestWidth はアプリケーションが動作するようデザインされたサイズです。現在のスクリーンが指定した値より小さい場合、システムはアプリケーションがこのデバイスと非互換だと考えますが、インストールして実行することを防いだりはしません。

注意: 現状では、Android Market は上記の属性に基づいてフィルターしません。フィルタリングのサポートは後の platform リリースで追加される予定です。スクリーンサイズに基づいたフィルタリングが必要なアプリケーションは既存の <supports-screens> 属性を使うことができます。

新しい属性をどのように使うかの完全な情報は Declaring screen size support を見てください。


Screen compatibility mode

Android 3.2 から新しいスクリーン互換性モードが提供されるようになりました。このモードは、走っているなかでできるだけ大きいスクリーンをサポートしないことを明示的に宣言します。この新しい "zoom" モードはピクセルスケールです。つまり、アプリケーションを小さい画面領域に描画して、それを現在の画面を覆うようにピクセルをスケールします。

デフォルトでは、システムはスクリーン互換性モードを必要としているアプリに対して、このモードをユーザーオプションとして提供します。ユーザーはシステムバーから利用可能なコントロールでズームモードのオンオフを切り替えることができます。

新しいスクリーン互換性モードは全てのアプリケーションに対して適切ではないため、プラットフォームはアプリケーションがマニフェストファイルを使ってそれを無効にすることを許可しています。アプリによって無効にされた場合、システムはアプリが走っている間ユーザーに対して "zoom" 互換性モードをオプションとして提供しません。

注意:どのように互換性モードをアプリケーションでコントロールするかの重要な情報については、Android Developer Blog の New Mode for Apps on Large Screens 記事を参照してください。


New screen density for 720p televisions and similar devices

720p のテレビや、それに似た解像度のデバイス上でアプリケーションを動かすために Android 3.2 から新しい一般的な解像度 tvdpi が追加されました。これはおよそ 213 dpi です。アプリケーションは新しい解像度を densityDpi で問い合わせることができ、また tvdpi 識別子をテレビや似たようなデバイス向けのタグとして使うことができます。
例えば

# Bitmap for tv density
res/drawable-tvdpi/my_icon.png

一般的には、アプリケーションはこの解像度で動くことはありません。720p のスクリーンに出力する必要がある場合、UI エレメントは platform によって自動的にスケールされます。


UI framework
  • Fragments

    • 新しい Fragment.SavedState クラスが saveFragmentInstanceState() を通して Fragment のインスタンスから取得した状態情報を保持します。
    • 新しいメソッド saveFragmentInstanceState() が与えられた Fragment の現在のインスタンス状態を保存します。状態は、現在の状態と一致する Fragment の新しいインスタンスが後で生成されたときに使うことができます。
    • 新しいメソッド setInitialSavedState() が最初に生成された Fragment に対して初期の保存状態をセットします。
    • 新しい onViewCreated() コールバックメソッド では、onCreateView() が返されたことを Fragment に知らせますが、以前の保存状態はその View に再保存されています。
    • isDetached() メソッドは Fragment が明示的に UI から切り離されているかどうかを決定します。
    • 新しい attach()detach() メソッドによってアプリケーションは UI に Fragment を再取り付け、取り離しすることができます。
    • 新しい setCustomAnimations() オーバーロードメソッドによって、指定したアニメーションリソースをエンター/イグジット操作およびバックスタックをポップするときの操作で走るよう指定できます。既存の実装はバックスタックをポップするときの Fragment のデフォルトの振る舞いには寄与しません。

  • Screen size information in ActivityInfo and ApplicationInfo

  • WindowManager から画面サイズを取得するためのヘルパー

    • 新しいメソッド getSize()getRectSize() によってアプリケーションは画面の生のサイズを取得できるようになりました。

  • 新しい公開 "holographic" スタイル

    • text, actionbar widgets, tabs などに対する多くの公開 "holographic" スタイル提供されるようになりました。完全なリストは R.style を見てください。

  • LocalActivityManager, ActivityGroup, LocalActivityManager が deprecated になりました。

    • 新しいアプリケーションはこれらのクラスの代わりに Fragment を使ってください。古いバージョンのプラットフォームでも動くようにするために、Android SDK で利用できる v4 Support Library (compatibility library) を使うことができます。v4 Support Library は Android 1.6 (API level 4) まで互換性のある Fragment API のバージョンを提供します。
    • Android 3.0 (API level 11) 以上に対して開発しているアプリでは、タブをアクションバー領域に置くための新しい ActionBar.newTab() や関連する API を使って、典型的にはタブをアクションバー内に表示させます。


Media framework
  • platform のメディアプロバイダー(MediaStore)を使うアプリケーションはデバイスによってサポートされている取り外しできる SD カードから直接メディアデータを読むことができるようになりました。また、MTP API を使って直接 SD カードのファイルとやりとりすることもできるようになりました。


Graphics


IME framework
  • 修飾キーの現在の状態を取得するための新しいメソッド getModifiers() が追加されました。


USB framework
  • デバイスの生の USB デスクリプタを取得するために新しく getRawDescriptors() メソッドが追加されました。ハイレベルの API 介したアクセスがサポートされていないデスクリプタにこのメソッドを使ってアクセスできます。


Network


Telephony
  • 新しい NETWORK_TYPE_HSPAP ネットワークタイプ定数が追加されました。


Core utilities
  • Parcelable utilities

  • Binder and IBinder

    • BinderIBinder の新しいメソッド dumpAsync() によって、アプリケーションは指定されたファイルへのダンプを非同期に実行することができます。
    • 新しい IBinder プロトコルトランザクションコード TWEET_TRANSACTION によってアプリケーションはターゲットオブジェクトにツイートを送ることができます。


New feature constants

Android マニフェストで宣言できるハードウェア機能の定数が追加されました。これを宣言することで Android Market のような外部のエンティティに、必要なハードウェア・ソフトウェアの機能を知らせることができます。これらと他の機能定数は <uses-feature> マニフェストエレメントに宣言します。

Android Market はこれら <uses-feature> 属性に基づいてアプリケーションをフィルターします。これにより、必要条件を満たしたデバイスでのみ利用可能にすることができます。

  • landspace (横向き), portrait (縦向き) が必要条件の場合の機能定数
    Android 3.2 でアプリケーションが横向き、縦向き、もしくは両方の画面向きを必要としているかを指定できる機能定数が増えました。これらの定数を宣言することは、アプリケーションが求める画面向きに対応しないデバイスにはインストールされないことを意味します。逆に、1つもしくは両方の定数が宣言されていないのであれば、宣言されていない向きに対する設定をアプリケーションが持っていないことを意味し、それらを提供しないデバイスにもインストールされます。

    横と縦両方の向きに対して適切に動く典型的なアプリケーションでは通常は画面向きの必要条件を宣言する必要はありません。テレビ向けにデザインされているような1つの向きにだけ主にデザインされているアプリケーションでは、その向きを提供していないデバイスで利用できないようにするために定数のいずれかを宣言することができます。
    もしアプリケーションが API Level 12 以下をターゲットとしている場合、プラットフォームは portrait, landscape いずれかを必要と指定していなければ、両方の向きが必要とされていると仮定します。

  • Other feature constants


API Differences Report

Android 3.2(API Level 13) の全ての API 変更の詳細については、API Differences Report を見てください。


---

API Level

Android 3.2 platform は framework API のアップデートバージョンとして提供されます。Android 3.2 API には整数値の識別子 - 13 - が割り当てられ、システム自身に保存されます。この識別子は "API Level" と呼ばれ、アプリケーションをインストールする前に、そのアプリケーションがシステムと互換性があるかどうかシステムが正しく決定するために利用されます。

Android 3.2 で紹介された API をあなたのアプリケーションで使用するには、Android 3.2 SDK platform で提供されている Android library に対してアプリケーションをコンパイルする必要があります。あなたの必要に応じて、アプリケーションのマニフェストの <uses-sdk> エレメントに android:minSdkVersion="13" 属性を追加する必要があるかもしれません。

API Level を利用するためのより詳しい情報は、API Levels ドキュメントを見てください。

2011年7月26日火曜日

Android resource で integer を定義する

strings.xml で


<string name="one">1</string>


と定義して


int one = Integer.parseInt(getString(R.string.one));


とかやっちゃダメですよ!
コードみせたら笑われry

ちゃんと integer で定義してくださいまし。
More Resource Types | Android Developers

res/values/hoge.xml の <resources> の中に
こうですよ!


<integer name="one">1</integer>


と定義して


int one = getResources().getInteger(R.integer.one);



 

2011年7月12日火曜日

Android AppWidget の PendingIntent で putExtra するときの注意

AppWidget 上のボタンをタップしたときに編集画面を開いたりする場合、RemoteViews を使った次のようなコードがよく紹介されています。


RemoteViews remoteView = new RemoteViews(context.getPackageName(), R.layout.appwidget);
Intent intent = new Intent(context, WidgetConfigure.class);

PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, 0);
// or
// PendingIntent pendingIntent = PendingIntent.getService(context, 0, intent, 0);
// or
// PendingIntent pendingIntent = PendingIntent.Broadcast(context, 0, intent, 0);

remoteView.setOnClickPendingIntent(R.id.button, pendingIntent);


画面に同じアプリのウィジェットが複数個ある場合、AppWidgetId を Intent の extras に詰めて渡したくなります。なるでしょう?

そこで


Intent intent = new Intent(context, WidgetConfigure.class);
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent,0);


とやるとうまくいきません。画面にウィジェットが複数ある場合、どれをタップしてもどれか1つのボタンが押された挙動になってしまいます。

正しく動かすには、こう


PendingIntent pendingIntent = PendingIntent.getActivity(context, appWidgetId, intent, 0);


---
注意:

PendingIntent pendingIntent = PendingIntent.getActivity(context, appWidgetId, intent, PendingIntent.FLAG_UPDATE_CURRENT);
// or
PendingIntent pendingIntent = PendingIntent.getActivity(context, appWidgetId, intent, PendingIntent.FLAG_CANCEL_CURRENT);

でも動きますが、今回の目的では第4引数フラグを指定する必要はありません。
詳しくはこの後で説明します。
---

PeindingIntent の getActivity() (getService(), getBroadcast() も同じ) の第2引数に AppWidget の ID (同じアプリの各 AppWidget で一意になるならなんでもよい)を指定します!

ちょっと、ちゃんと PendingIntent の説明を読んでみましょう。

---
A description of an Intent and target action to perform with it. Instances of this class are created with getActivity(Context, int, Intent, int), getBroadcast(Context, int, Intent, int), getService(Context, int, Intent, int); the returned object can be handed to other applications so that they can perform the action you described on your behalf at a later time.

By giving a PendingIntent to another application, you are granting it the right to perform the operation you have specified as if the other application was yourself (with the same permissions and identity). As such, you should be careful about how you build the PendingIntent: often, for example, the base Intent you supply will have the component name explicitly set to one of your own components, to ensure it is ultimately sent there and nowhere else.

A PendingIntent itself is simply a reference to a token maintained by the system describing the original data used to retrieve it. This means that, even if its owning application's process is killed, the PendingIntent itself will remain usable from other processes that have been given it. If the creating application later re-retrieves the same kind of PendingIntent (same operation, same Intent action, data, categories, and components, and same flags), it will receive a PendingIntent representing the same token if that is still valid, and can thus call cancel() to remove it.
---

Intent と、その Intent を実行したい Action の記述。このクラスのインスタンスは getActivity(Context, int, Intent, int), getBroadcast(Context, int, Intent, int), getService(Context, int, Intent, int); で生成されます。後で他のアプリケーションが記述されたアクションを実行できるように、これらのメソッドで返されるオブジェクトは他のアプケーションに受け渡すことができます。

PendingIntent を他のアプリケーションに渡すことで、他のアプリケーションが自分自身であるかのように(同じパーミッションとIDで)指定した操作を実行する権限を付与します。
このようなことから、PendingIntent をどのように作成するのかについて注意するべきです。よく起こることで、例えば、あなたが渡す base Intent が他のところではなく最終的にそこに届けられることを確認するために、あなた自身のコンポーネントの1つとして明示的に設定された component name を持つようにしてください。

PendingIntent 自身は、単にシステムによって管理されるトークンへのリファレンスです。これは、PendingIntent を受信するために使用されるオリジナルデータを記述しています。
これはつまり、自身のアプリケーションプロセスが殺されても、PendingIntent 自身は他のプロセス(すでに受け取っている)から使用可能な状態のまま残ることを意味しています。もし作成したアプリケーションがあとで同じ種類の PendingIntent を再取得した場合(同じ操作、同じアクション、データ、カテゴリ、コンポーネント、フラグ)、同じトークンがまだ有効な場合、その同じトークンを代表する PendingIntent を受信します。したがって、cancel() を呼んでそれを削除することができます。

---

ふむふむ。

---

public static PendingIntent getActivity (Context context, int requestCode, Intent intent, int flags)

Since: API Level 1
Context.startActivity(Intent) を呼ぶように、新しい Activity を開始する PendingIntent を取得する。既存の activity の context の外で新しい activity が開始されるので、Intent に Intent.FLAG_ACTIVITY_NEW_TASK launch flag を使わなければならない。

Parameters

context : この PendingIntent が activity を開始する context
requestCode : sender 用のプライベートな request code
intent : 起動される Activity の Intent
flags : FLAG_ONE_SHOT, FLAG_NO_CREATE, FLAG_CANCEL_CURRENT, FLAG_UPDATE_CURRENT のいずれか。もしくは Intent.fillIn() でサポートされる全てのフラグ。intent の指定されてない部分のコントロール用。実際の送信が起こったときに intent に適用される。

Returns

与えられたパラメータにマッチする既存の、もしくは新しい PendingIntent を返す。FLAG_NO_CREATE が与えられたときのみ null が返る場合がある。

---

ふむふむふむ。
flags に指定する。FLAG_ONE_SHOT, FLAG_NO_CREATE, FLAG_CANCEL_CURRENT, FLAG_UPDATE_CURRENT の説明も見てみるか。

---

Constants

public static final int FLAG_CANCEL_CURRENT

Since: API Level 1
getActivity(Context, int, Intent, int), getBroadcast(Context, int, Intent, int), getService(Context, int, Intent, int) 用のフラグ : 記述された PendingIntent がすでに存在していた場合、新しいのが生成される前に現在のはキャンセルされる。Intent の extra data だけを変更する場合に新しい PendingIntent を取得するのに使える。以前の pending intent をキャンセルすることによって、新しいデータを与えられた実体だけが起動できることを保証する。この保証が問題はない場合、FLAG_UPDATE_CURRENT を検証する。

public static final int FLAG_NO_CREATE

Since: API Level 1
getActivity(Context, int, Intent, int), getBroadcast(Context, int, Intent, int), getService(Context, int, Intent, int) 用のフラグ : 記述された PendingIntent がまだ存在していない場合、生成せずに単に null を返す。

public static final int FLAG_ONE_SHOT

Since: API Level 1
getActivity(Context, int, Intent, int), getBroadcast(Context, int, Intent, int), getService(Context, int, Intent, int) 用のフラグ : この PendingIntent は1度だけ使える。このフラグがセットされた場合、send() が呼ばれた後に試みた send は自動的にキャンセルされる。

public static final int FLAG_UPDATE_CURRENT

Since: API Level 3
getActivity(Context, int, Intent, int), getBroadcast(Context, int, Intent, int), getService(Context, int, Intent, int) 用のフラグ : 記述された PendingIntent がすでに存在している場合、それをキープして extra data を新しい Intent のものに置き換える。extras だけを変えた intent を作成し、以前の PendingIntent が受け取った実体が、明示的に与えなくても新しい extras で起動できるのを気にしない場合に使える。

---

FLAG_UPDATE_CURRENT で問題ない場合は、こっちを考えてねって FLAG_CANCEL_CURRENT にあるので、FLAG_UPDATE_CURRENT の方がいいのかな?


第2引数についても検証してみた。
リファレンスには

---

requestCode Private request code for the sender (currently not used).

---

(currently not used) = 現在は使っていない

って書いてあるけど、どうもそうではないみたい。

例えば、同じ AppWidget を画面に2個配置して、それぞれに


Intent intent = new Intent(context, WidgetConfigure.class);
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT);
views.setOnClickPendingIntent(R.id.widgetbase, pendingIntent);


を指定すると、先に追加した方の PendingIntent はキャンセルされるので、先に張り付けた AppWidget のボタンをタップしても反応しなくなります(PedingIntent が実行されなくなる)。

これは、extras 以外が同じ PendingIntent なので、以前のがキャンセルされるからです。
これを


Intent intent = new Intent(context, WidgetConfigure.class);
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
PendingIntent pendingIntent = PendingIntent.getActivity(context, appWidgetId, intent, PendingIntent.FLAG_CANCEL_CURRENT);
views.setOnClickPendingIntent(R.id.widgetbase, pendingIntent);


にすると、先に張り付けた AppWidget のボタンもちゃんと反応します。
extras 以外に appWidgetId も違うので、同じ PendingIntent だと判定されないのでキャンセルされないからです。

FLAG_UPDATE_CURRENT も同じことです。


Intent intent = new Intent(context, WidgetConfigure.class);
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
views.setOnClickPendingIntent(R.id.widgetbase, pendingIntent);


を指定すると、先に追加した方の PendingIntent はキャンセルされないので起動できますが、新しい Extras が適用されてしまう(FLAG_UPDATE_CURRENT のところに説明されている通り) ので、どの AppWidget でも同じ Extras の Activity が起動されることになります。

これを


Intent intent = new Intent(context, WidgetConfigure.class);
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
PendingIntent pendingIntent = PendingIntent.getActivity(context, appWidgetId, intent, PendingIntent.FLAG_UPDATE_CURRENT);
views.setOnClickPendingIntent(R.id.widgetbase, pendingIntent);


にすれば、同じ PendingIntent と判定されないので、以前の Extras が新しいのに置き換わることはなくなります。


。。。あれれ、結局第2引数で同じ PendingIntent と判定されないようにするなら、第4引数いらなくね?

てことで、


Intent intent = new Intent(context, WidgetConfigure.class);
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
PendingIntent pendingIntent = PendingIntent.getActivity(context, appWidgetId, intent, 0);
views.setOnClickPendingIntent(R.id.widgetbase, pendingIntent);


でいいじゃん!ちゃんと意図した通り動きました。


ちなみに、コードをちょっと追ってみました。

PendingIntent.java#189

189 public static PendingIntent getActivity(Context context, int requestCode,
190 Intent intent, int flags) {
191 String packageName = context.getPackageName();
192 String resolvedType = intent != null ? intent.resolveTypeIfNeeded(
193 context.getContentResolver()) : null;
194 try {
195 IIntentSender target =
196 ActivityManagerNative.getDefault().getIntentSender(
197 IActivityManager.INTENT_SENDER_ACTIVITY, packageName,
198 null, null, requestCode, intent, resolvedType, flags);
199 return target != null ? new PendingIntent(target) : null;
200 } catch (RemoteException e) {
201 }
202 return null;
203 }


ActivityManagerNative.java#2233

2233 public IIntentSender getIntentSender(int type,
2234 String packageName, IBinder token, String resultWho,
2235 int requestCode, Intent intent, String resolvedType, int flags)
2236 throws RemoteException {
2237 Parcel data = Parcel.obtain();
2238 Parcel reply = Parcel.obtain();
2239 data.writeInterfaceToken(IActivityManager.descriptor);
2240 data.writeInt(type);
2241 data.writeString(packageName);
2242 data.writeStrongBinder(token);
2243 data.writeString(resultWho);
2244 data.writeInt(requestCode);
2245 if (intent != null) {
2246 data.writeInt(1);
2247 intent.writeToParcel(data, 0);
2248 } else {
2249 data.writeInt(0);
2250 }
2251 data.writeString(resolvedType);
2252 data.writeInt(flags);
2253 mRemote.transact(GET_INTENT_SENDER_TRANSACTION, data, reply, 0);
2254 reply.readException();
2255 IIntentSender res = IIntentSender.Stub.asInterface(
2256 reply.readStrongBinder());
2257 data.recycle();
2258 reply.recycle();
2259 return res;
2260 }


requestCode もちゃんと Parcel に入れられてます = 使ってるじゃん!


2244 data.writeInt(requestCode);



   

2011年7月6日水曜日

Android In-app Billing Overview

In-app Billing Architecture


  • 請求リクエストと請求レスポンスのやりとりを非同期メッセージループを使って、アプリと Android Market サーバ間で行う
  • 実際にはアプリは Android Market サーバと直接やりとりすることはない
  • アプリは請求リクエストを Android Market application に interprocess communication (IPC) を介して送り、Android Market application から購入レスポンスを非同期の broadcast intent として受け取る
  • コンテンツを配送したり、トランザクションを検証するためのプライベートなリモートサーバを使うアプリ内課金の実装もあるが、in-app billing の実装にリモートサーバは必須ではない
  • メディアファイルや写真等をユーザーのデーバイスに配送する必要があるデジタルコンテンツを売る場合、リモートサーバーは役に立つ
  • ユーザーのトランザクション履歴を保存したり、署名検証などのアプリ内課金用セキュリティタスクを実行するためにリモートサーバを使うこともある
  • セキュリティに関連した全てのタスクをアプリ内で処理することも可能だが、セキュリティアタックへの脆弱性を減らすためにこれらのタスクをリモートサーバで行うことが推奨される


典型的な in-app billing の実装は3つのコンポーネントに依存している

  • Service
    (サンプルアプリでは BillingService という名前)
    アプリからの購入メッセージを処理し、Android Market の in-app billing service に請求リクエストを送る

  • BroadcastReceiver
    (サンプルアプリでは BillingReceiver という名前)
    Android Market application からの全ての非同期請求レスポンスを受け取る

  • セキュリティコンポーネント
    (サンプルアプリでは Security という名前)
    署名検証やワンタイムトークンの生成などセキュリティに関連したタスクを実行する
    in-app billing のセキュリティに関するより詳しい情報は Security controls を参照する


必要に応じて in-app billing をサポートする以下のコンポーネントを組み込む

  • response Handler
    (サンプルアプリでは ResponseHandler という名前)
    購入の通知、エラー、その他ステータスメッセージのアプリケーション固有の処理を提供する

  • observer
    (サンプルアプリでは PurchaseObserver という名前)
    アプリへのコールバック送信に責任をもち、これによりアプリは購入情報やステータスなどで、ユーザーのインタフェースをアップデートする


これらのコンポーネントに加えて
・ユーザーの購入情報を保存する方法
・ユーザーが購入アイテムを選択できるユーザーインタフェース
を提供しなければならない

アプリは checkout 用のユーザーインタフェースを用意する必要はない(Android Market application が提供する)
ユーザーの checkout プロセスが完了したら、アプリが再開される



from http://developer.android.com/guide/market/billing/billing_overview.html

---

In-app Billing Messages

  • ユーザーが購入を開始したら、アプリは Android Market の in-app billing service (MarketBillingService という名前) に単一の IPC メソッド呼び出しと使って請求メッセージを送る。
  • Android Market application は全ての請求リクエストに対して同期にレスポンスを返し、ステータス通知と他の情報をアプリに提供する
  • Android Market アプリはいくつかの請求リクエストに対して非同期でもレスポンスを返し、エラーメッセージと詳細なトランザクション情報をアプリに提供する

In-app billing requests

  • アプリは MarketBillingService インタフェースで公開されている single IPC method (sendBillingRequest()) を呼び出すことで請求リクエストを送る
  • このインタフェースは Android Interface Definition Language ファイル (IMarketBillingService.aidl) で定義されている
  • この AIDL ファイルは in-app billing のサンプルアプリと一緒にダウンロードできる

  • sendBillingRequest() メソッドは引数として Bundle パラメータを一つ持つ
  • アプリが渡すこの Bundle には、リクエストのパラメータを指定するための、いくつかの key-value ペアが含まれていなければならない
  • リクエストで送る Bundle keys の詳細は In-app Billing Service Interface を参照する

  • 一番重要な key は各リクエスト Bundle で必須な BILLING_REQUEST key
  • この key で作成する請求リクエストのタイプを指定する

Android Market の in-app billing service は次の 5 タイプの請求リクエストをサポートしている:

  • CHECK_BILLING_SUPPORTED
    Android Market application が in-app billing をサポートしているかどうか確かめるリクエスト
    アプリケーションの開始時にこのリクエストを送って in-app billing が利用できるときのみ UI を有効にするべき

    その他の必須な key-value pairs
    ・API_VERSION : 1
    ・PACKAGE_NAME : package name

    同期レスポンス Bundle のキー
    ・RESPONSE_CODE

    RESPONSE_CODE の可能な値
    ・RESULT_OK
    ・RESULT_BILLING_UNAVAILABLE
    ・RESULT_ERROR
    ・RESULT_DEVELOPER_ERROR

  • REQUEST_PURCHASE
    Android Market application に購入メッセージを送るリクエスト
    in-app billing の基盤
    ユーザーがアプリのアイテムを購入したいことを示したときにこのリクエストを送る
    Android Market は checkout のユーザーインタフェースを表示して購入のトランザクションを処理する

    その他の必須な key-value pairs
    ・API_VERSION : 1
    ・PACKAGE_NAME : package name
    ・ITEM_ID : 有効な製品(アイテム)の識別子

    同期レスポンス Bundle のキー
    ・RESPONSE_CODE
    ・PURCHASE_INTENT
    ・REQUEST_ID

    RESPONSE_CODE の可能な値
    ・RESULT_OK
    ・RESULT_ERROR
    ・RESULT_DEVELOPER_ERROR

    非同期レスポンス
    ・IN_APP_NOTIFY(broadcast intent)
       ・a notification ID(データ)

  • GET_PURCHASE_INFORMATION
    購入状態の変化の詳細を取得するためのリクエスト
    リクエストした購入の支払いが成功した場合やユーザーが checkout 中にトランザクションをキャンセルした場合に購入の状態が変わる
    事前の購入がリファンドされたときにも購入状態が変わる
    購入状態が変更になったとき Android Market はアプリに通知するので、取得したいトランザクション情報がある場合だけこのリクエストを送ればいい

    その他の必須な key-value pairs
    ・API_VERSION : 1
    ・PACKAGE_NAME : package name
    ・NONCE : 有効な long
    ・NOTIFY_IDS : 有効な long 値の配列

    同期レスポンス Bundle のキー
    ・RESPONSE_CODE
    ・REQUEST_ID

    RESPONSE_CODE の可能な値
    ・RESULT_OK
    ・RESULT_ERROR
    ・RESULT_DEVELOPER_ERROR

    非同期レスポンス
    ・PURCHASE_STATE_CHANGED(broadcast intent)

  • CONFIRM_NOTIFICATIONS
    購入状態変更の詳細をアプリが取得したこと知らせるためのリクエスト
    Android Market はアプリが購入状態変更の通知を受け取ったことを確認するまで通知を送る

    その他の必須な key-value pairs
    ・API_VERSION : 1
    ・PACKAGE_NAME : package name
    ・NOTIFY_IDS : 有効な long 値の配列

    同期レスポンス Bundle のキー
    ・RESPONSE_CODE
    ・REQUEST_ID

    RESPONSE_CODE の可能な値
    ・RESULT_OK
    ・RESULT_ERROR
    ・RESULT_DEVELOPER_ERROR

  • RESTORE_TRANSACTIONS
    購入を管理する(managed purchases)ためのユーザーのトランザクションを取得するリクエスト
    ユーザーのトランザクション状態を取得する必要があるときだけこのリクエストを送る
    通常はアプリケーションがデバイスに最初にインストールされたときか、再インストールされたとき

    その他の必須な key-value pairs
    ・API_VERSION : 1
    ・PACKAGE_NAME : package name
    ・NONCE : 有効な long

    同期レスポンス Bundle のキー
    ・RESPONSE_CODE
    ・REQUEST_ID

    RESPONSE_CODE の可能な値
    ・RESULT_OK
    ・RESULT_ERROR
    ・RESULT_DEVELOPER_ERROR

    非同期レスポンス
    ・RESPONSE_CODE(broadcast intent)
    ・PURCHASE_STATE_CHANGED(broadcast intent)



---

In-app Billing Responses

Android Market application からの in-app billing リクエストに対するレスポンスには、同期・非同期両方のレスポンスがある
同期的なレスポンスは Bundle で、次の 3 つのキーの値をもつ

  • RESPONSE_CODE
    リクエストのステータス情報とエラー情報を提供するキー
  • PURCHASE_INTENT
    checkout activit を起動するための PendingIntent を提供するキー
  • REQUEST_ID
    リクエストと非同期のレスポンスを一致させるためのリクエスト識別子を提供するキー


いくつかのキーは全てのリクエストで使えるわけではない
より詳しい情報は、Messaging sequence を参照する

非同期レスポンスはそれそれが broadcast intent の形式で送られる

  • com.android.vending.billing.RESPONSE_CODE
    Android Market server の response code を含む
    in-app billing リクエストを作成した後に送られる
    レスポンスコードによって、Android Market に billing request が正しく送れたか、もしくはエラーがリクエスト中に起こったかを判別できる
    このレスポンスは購入状態の変化(リファンドや購入の情報)をレポートするものではない
    レスポンスコードの詳細は Server Response Codes for In-app Billing を参照する

    Extras
    request_id : long : request ID を表す
    response_code : int : Android Market server のレスポンスコードを表す

    response_code extras に含まれるレスポンスコード

    • RESULT_OK
      値 : 0
      リクエストが正しくサーバーに送れたことを意味する
      このコードが CHECK_BILLING_SUPPORTED のレスポンスとして返された場合は、billing がサポートされていることを意味する

    • RESULT_USER_CANCELED
      値 : 1
      checkout ページでユーザーがアイテムを購入しようとせずに戻るボタンを押したことを意味する

    • RESULT_SERVICE_UNAVAILABLE
      値 : 2
      ネットワークコネクションがダウンしていることを意味する

    • RESULT_BILLING_UNAVAILABLE
      値 : 3
      指定した API_VERSION が Android Market application に認識されなかった、もしくはユーザーが in-app billing に不適格(例えば、ユーザーが in-app purchase が禁止されている国にいるなど)なことにより in-app billing が使えないことを意味する

    • RESULT_ITEM_UNAVAILABLE
      値 : 4
      Android Market がリクエストされたアイテムをアプリケーションの product list に見つけられなかったことを意味する。REQUEST_PURCHASE で product ID をミススペルしている場合、もしくはアイテムがアプリケーションの product list にパブリッシュされていない場合におこる

    • RESULT_DEVELOPER_ERROR
      値 : 5
      アプリケーションが in-app billing リクエストをしようとしたが、アプリケーションが com.android.vending.BILLING パーミッションを manifest で宣言していないことを意味する。アプリケーションが適切に署名されていない、もしくは変なリクエスト(例えば、request type を識別するための Bundle keys がないリクエストなど)を送ったことも意味しうる

    • RESULT_ERROR
      値 : 6
      予測できないサーバーエラーを意味する。自分で自分のアイテムを購入しようとした場合、このエラーが起こる


  • com.android.vending.billing.IN_APP_NOTIFY
    購入状態(購入に成功した、キャンセルされた、リファンドされた)が変わったことを意味するレスポンス
    このレスポンスには 1つ以上の notification ID が含まれる
    ID は特定のサーバサイドメッセージとひもづいており、各メッセージは1つ上のトランザクションに関する情報を含む
    IN_APP_NOTIFY broadcast intent を受け取った後、アプリケーションは受け取った ID でGET_PURCHASE_INFOMATION リクエストを送ってメッセージの詳細を受け取るべき

    Extras
    notification_id : String : notification ID を表す

  • com.android.vending.billing.PURCHASE_STATE_CHANGED
    1つ以上のトランザクションに関する詳細状態を含むレスポンス
    トランザクション情報は JSON string に含まれる
    JSON string は署名され、JSON string と一緒に signature が送られる (暗号化はされていない)
    あなたの in-app billing メッセージのセキュリティを確保するために、この JSON string の signature を使ってアプリケーションを検証する

    Extras
    inapp_signed_data : String : signed された JSON string
    inapp_signature : String : signature を表す String

    JSON string の例

    { "nonce" : 1836535032137741465,
    "orders" :
    { "notificationId" : "android.test.purchased",
    "orderId" : "transactionId.android.test.purchased",
    "packageName" : "com.example.dungeons",
    "productId" : "android.test.purchased",
    "developerPayload" : "bGoa+V7g/yqDXvKRqq+JTFn4uQZbPiQJo4pf9RzJ",
    "purchaseTime" : 1290114783411,
    "purchaseState" : 0 }
    }

    JSON string のフィールドの情報は In-app Billing Broadcast Intents を参照する


    • nonce
      1回だけ使われる数字。自分のアプリケーションで nonce を生成して GET_PURCHASE_INFOMATION と一緒に送る。Android Market は JSON string の一部として nonce を送り返す。これによりメッセージを識別できる。

    • notificationId
      IN_APP_NOTIFY broadcast intent と一緒に送られるユニークな識別子。各 notificationId は Android Market server に受信されるのを待っている特定のメッセージと対応する。notificationIdGET_PURCHASE_INFORMATION メッセージと一緒に送り返すことで、アプリケーションがメッセージを受信したかどうかを Android Market が判断できる。

    • orderId
      トランザクション用のユニークなオーダー識別子。Google Checkout Order ID とひもづいている。

    • packageName
      購入が発生したアプリケーションのパッケージ名

    • productId
      アイテムの product 識別子。各アイテムは Android Market publisher site でアプリケーションの product list として指定された product ID をもたなければならない。

    • purchaseTime
      product が購入された時間。epoch (Jan 1, 1970) からの経過ミリ秒数

    • purchaseState
      オーダーの購入状態。可能は値は 0 (purchased), 1 (canceled), 2 (refunded) のいずれか。

    • developerPayload
      開発者が指定した文字列。オーダーの付加情報を含む。REQUEST_PURCHASE リクエストを発行するときにこのフィールドを指定することができる。

Android android:duplicateParentState

子View の状態(クリックされた、フォーカスがあたった、選択されたなど)を親と共有したい場合、

android:duplicateParentState="true or false"

setDuplicateParentStateEnabled(boolean)

が使えます。

これを設定すると、自身の状態よりも親の状態が優先され、親の状態と同じ状態をとるようになります。

例えば、ダッシュボードで

(ボタン1)
-----------
|画像|画像|
-----------
(ボタン2)
-----------
|画像|画像|
-----------
(ボタン3)
-----------
|画像|画像|
-----------
・・・

があって、どっちの画像を押しても、両方の画像が状態に応じて変わるようにするには、両方の画像の親Viewにクリック処理をさせ、子View に android:duplicateParentState="true" を設定します。


<LinearLayout
android:orientation="horizontal"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:clickable="true"
android:focusable="true"
>
<ImageView
android:background="@android:drawable/btn_default"
android:layout_width="50dip"
android:layout_height="wrap_content"
android:duplicateParentState="true"
/>
<ImageView
android:background="@android:drawable/btn_default"
android:layout_width="100dip"
android:layout_height="wrap_content"
android:duplicateParentState="true"
/>
</LinearLayout>

...


この場合、LinearLayout 部分をクリックすると、2つの ImageView 両方が LinearLayout の状態(押された、フォーカスがあたったなど)に応じて変わります。



ちなみに、これを使ったダッシュボードの例が「Android Layout Cookbook」の 8.4 レイアウトサンプル4 です。
ここから、Android Layout Cookbook デモあぷりを落として実際の動きをみることができます。

Android Layout CookBook デモあぷり - Android マーケット -

本もよろしくね!



実は、親 View の enabled/disabled も伝えることができます。
例えば、


<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/container"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<Button
android:text="Button"
android:id="@+id/button1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:duplicateParentState="true"
/>
<Button
android:text="Button"
android:id="@+id/button2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:duplicateParentState="false"
/>
<Button
android:text="Button"
android:id="@+id/button3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:duplicateParentState="true"
/>
<Button
android:text="Button"
android:id="@+id/button4"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:duplicateParentState="true"
/>
<Button
android:text="Button"
android:id="@+id/button5"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:duplicateParentState="true"
/>
</LinearLayout>



@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);

View container = findViewById(R.id.container);
container.setEnabled(false);

View button4 = findViewById(R.id.button4);
button4.setEnabled(true);

View button5 = findViewById(R.id.button5);
button5.setDuplicateParentStateEnabled(false);
button5.setEnabled(true);
}


の結果は次のようになります。



setDuplicateParentStateEnabled(false) を指定しないと、子 View だけ setEnabled(true) しないと親 View が disabled の場合は enabled になりません。

このパラメータは一度に有効/無効を切り替えるのに使うにはちょっと向いてないかなー。

2011年7月5日火曜日

Android ListFragment でカスタムレイアウトを使うと setEmptyText() が使えない

もともとは adakoda さんのブログのエントリ

[Android] ListFragment の View をカスタマイズする方法 - adakoda - http://goo.gl/UBgyu

で、onCreateView() を override してカスタムレイアウトを指定すると setEmptyText() で落ちるという話があって、正しい ID を指定してないからでは〜?的な流れになったのですが、どうもそうではないようなので調べてみました。

まず、setEmptyText(CharSequence text) は API Level 11 (Android 3.0) で追加された API です。

結果をまとめると

・ListActivity
  ・setEmptyView() : 使える
  ・getEmptyView() : 使える
  ・@android:id/list, @android:id/empty を使った
   カスタムレイアウト : 使える

・ListFragment
  ・@android:id/list, @android:id/empty を使った
   カスタムレイアウト : 使える

  ・カスタムレイアウトを使わない場合
    ・getListView().setEmptyView() : なにもおこらない
    ・getListView().getEmptyView() : 使える
    ・setEmptyText() : 使える

  ・カスタムレイアウトを使った場合
    ・getListView().setEmptyView() : なにもおこらない
     (@android:id/empty がそのまま表示される)
    ・getListView().getEmptyView() : 使える
    ・setEmptyText() : 落ちる

ListFragment で、カスタムレイアウトで setEmptyText() を使いたい場合は、次のように override しておくこと

@Override
public void setEmptyText(CharSequence text) {
TextView tv = (TextView)getListView().getEmptyView();
tv.setText(text);
}


ちなみに、このときのカスタムレイアウトはこんな感じ

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

<ListView
android:id="@android:id/list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:drawSelectorOnTop="false" />

<TextView
android:id="@android:id/empty"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="No data"
android:textSize="30sp"/>
</FrameLayout>


さて、結論をいったので、それまでの過程もちょっと紹介。

まずは、ListFragment の onCreateView() で生成される View のなかから empty 用っぽい View を見つけて、それの ID から resourceName とればわかるんじゃね?と思ってやってみました。


@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {

View v = super.onCreateView(inflater, container, savedInstanceState);
Log.d("NoteListFragment", v.getClass().getName() + ", " + v.getId());

if (v instanceof ViewGroup) {
ViewGroup vg = (ViewGroup) v;
for (int i = 0; i < vg.getChildCount(); i++) {
View child = vg.getChildAt(i);

String resourcename = getResourceNameByID(android.R.id.class, child.getId());

Log.d("NoteListFragment", child.getClass().getName() + ", "
+ child.getId() + ", " + resourcename);

if(child instanceof ViewGroup) {
ViewGroup vg2 = (ViewGroup) child;
for (int j = 0; j < vg2.getChildCount(); j++) {
View child2 = vg2.getChildAt(j);

String resourcename2 = getResourceNameByID(android.R.id.class, child2.getId());

Log.d("NoteListFragment", child2.getClass().getName() + ", "
+ child2.getId() + ", " + resourcename2);
}
}
}
}
return v;
}

public String getResourceNameByID(Class<?> clazz, int resourceId)
throws IllegalArgumentException {

Field[] fields = clazz.getFields();
for (Field f : fields) {
try {
if (resourceId == f.getInt(null)) {
return f.getName();
}
} catch (Exception e) {
e.printStackTrace();
throw new IllegalArgumentException();
}
}
return null;
}


リフレクション。リフレクション。
結果はこんな感じ

07-05 13:46:40.090: DEBUG/NoteListFragment(30597): android.widget.FrameLayout, -1
07-05 13:46:40.090: DEBUG/NoteListFragment(30597): android.widget.LinearLayout, 16908904, null
07-05 13:46:40.090: DEBUG/NoteListFragment(30597): android.widget.ProgressBar, -1, null
07-05 13:46:40.090: DEBUG/NoteListFragment(30597): android.widget.TextView, -1, null
07-05 13:46:40.090: DEBUG/NoteListFragment(30597): android.widget.FrameLayout, 16908905, null
07-05 13:46:40.090: DEBUG/NoteListFragment(30597): android.widget.ListView, 16908298, list
07-05 13:46:40.090: DEBUG/NoteListFragment(30597): android.widget.TextView, 16908906, null

ListView には list (= android.R.id.list) が割当てられているのに、その下の TextView (EmptyView だと思われる) には null が入っている。つまり、android.R.id.** に対象の ID がなかったということです。コードで動的に生成してるのかな?

念のためこの TextView が EmptyView なのかチェック


@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
...

setEmptyText(getActivity().getString(R.string.no_notes));
Log.d("NoteListFragment", getListView().getEmptyView().getId() + "");
}


結果は

07-05 13:56:22.510: DEBUG/NoteListFragment(30953): 16908906

ビンゴ。

ちなみに setEmptyText() する前に getListView().getEmptyView().getId() するとヌルポになります。
EmptyView が生成されてないんですね。このことからも EmptyView が動的に生成されてるのがわかります。

なので、レイアウトをカスタムしないで setEmptyText を override するときは super.setEmpty() を忘れずに最初にしてください。そうしないとその後の getListView().getEmptyView() には null が返ってきてしまいます。


@Override
public void setEmptyText(CharSequence text) {
super.setEmptyText(text); // don't forget
TextView tv = (TextView)getListView().getEmptyView();
tv.setText(text);
}



カスタムを使っていてもいなくても、カスタムレイアウトに android:empty が入っていてもいなくても動くようにするには、こんな感じかなぁ。。。


@Override
public void setEmptyText(CharSequence text) {
try {
super.setEmptyText(text);
}
catch (IllegalStateException e1){
e1.printStackTrace();
Log.d("NoteListFragment", e1.getClass().getName() + " : " + e1.getMessage());

try {
TextView tv = (TextView)getListView().getEmptyView();
tv.setText(text);
}
catch(NullPointerException e2) {
e2.printStackTrace();
Log.d("NoteListFragment", e2.getClass().getName() + " : " + e2.getMessage());

TextView tv = new TextView(getActivity());
tv.setText(text);
tv.setId(android.R.id.empty);

ViewParent v = getListView().getParent();
if(v instanceof ViewGroup) {
((ViewGroup)v).addView(tv);
}
getListView().setEmptyView(tv);
}
}
}





  

2011年7月4日月曜日

Android Developer Lab Tokyo 2011 のノートパッドをカスタマイズしてみた。

7月2日と3日に 「Android デベロッパーラボ 東京 2011」が開催されました。

「Android デベロッパーラボ 東京 2011」開催のお知らせ - Google Japan Developer Relations Blog -

私は両日ともチューターとして参加してきました。



参加者は80人 x 2 くらい



午前中に Android の Advocate の方のセッションがあって、
午後から実際に手を動かすという内容でした。

ハッシュタグは #adl11jp

すでに Togetter にまとめがあります。ありがとう @mrshiromi さん。
Togetter - 「#adl11jp Android Developer Lab 2011 7/2-3」 -


午後のコードラボの資料は

Android Developer Lab — Tokyo 2011

にあります。(http://j.mp/adltokyo2011)

Android developers のサイトにある Notepad Tutorial を Honeycomb 用にカスタマイズするというコンテンツです。
もともとは I/O Bootcamp Honeycomb Codelab で行われたものなのですが、これよりも簡単になっています。

最後までいくと、こんな感じになります。



せっかくなので、カスタマイズしてみました。

・全体的に余白をいれる
・背景を変える
・レイアウトを変える
  ・保存ボタンの位置を変える
  ・Body 部分を上揃えにする
  ・Title 部分を1行固定にする
  ・保存ボタンのスタイルを変える
  ・リストの区切り線を変える
  ・Title と Body の文字の大きさを変える
・アクションバーをカスタマイズする
  ・背景を変える
  ・ロゴを使う
  ・メニューボタンの文字色を変える
  ・メニューボタンを押したときの色を変える
・ノートがないときにリスト部分に "No Notes Yet" とでるようにする
・開いているノートがどれかわかるように
  ・リストのアイテムを CheckedTextView にする
  ・チェックマークを変える
・タイトルなしで保存しようとしたとき、日時の入ったタイトルを自動でつける


やったのはこのくらいかなー。

ソースは github で公開しました。
yanzm/CustomizedHoneycombNotepad - GitHub -

できあがりはこんな感じ。

まだノートがない場合


リスト部分にチェックマークでるようにしてみた。


アクションバーのボタンを押したとき。赤くしてみた。


保存ボタンを押したとき。これも赤くしてみた。




個々のカスタマイズ tips もいずれブログに書きたいなー。。。

 

2011年7月3日日曜日

Android Action Bar をカスタマイズする

Android Developers Blog: Customizing the Action Bar

Action Bar のデザインパターンを紹介してから、多くのアプリケーションが Action Bar を使うようになりました。
そして、このデザインパターンは Android 3.0 でデフォルトの機能になりました。
Honeycomb をターゲットとしたアプリで Action Bar を使う方法は using Action Bar を参照してください。

Theme.Holo.* を継承した独自スタイルを定義して、ここで Action Bar をカスタマイズしていきます。


<style name="Theme.MyTheme" parent="android:style/Theme.Holo.Light">

</style>



■ Icon

Android Asset Studio を使って Action Bar 用のアイコンを作ることができます。
また、ブランド化するためにスタートポイントに次のようなロゴを使ったりします。



ロゴ (android:logo) を Action Bar に使うには
setDisplayUseLogoEnabled (boolean useLogo)
を使います。


■ Navigation

Action Bar のナビゲーションセクションには3つの異なる mode があります。
NAVIGATION_MODE_STANDARD
NAVIGATION_MODE_LIST
NAVIGATION_MODE_TABS

ナビゲーションのモードを設定するには
setNavigationMode(int)
を使います。

  • Standard

    Action Bar のスタンダートナビゲーションモードは単純に Activity のタイトルを表示します。

  • List

    左側が標準のリストドロップダウン、右側が実現したいエフェクト



    デフォルトのナビゲーションモードは青色のカラースキームでスタイルされています。
    これは、トップバーと拡張リストの選択ハイライトの両方で、タッチされたことを表すために青色になります。

    android:actionDropDownStyle

    をオーバーライドすることでこのエレメントのテーマをすきなカラースキームにカスタマイズすることができます。


    <!-- style the list navigation -->
    <style name="MyDropDownNav" parent="android:style/Widget.Holo.Light.Spinner.DropDown.ActionBar">
    <item name="android:background">drawable/ad_spinner_background_holo_light</item>
    <item name="android:popupBackground">@drawable/ad_menu_dropdown_panel_holo_light</item>
    <item name="android:dropDownSelector">@drawable/ad_selectable_background</item>
    </style>


    spinner の背景、拡張する list の背景, ハイライトカラー用に指定している drawable には state list drawables9 patch images を使っています。

  • Tabs

    上:カスタマイズ前、下:カスタマイズ後


    タブナビゲーションコントロールには標準の青色が使われています。
    android:actionBarTabStyle にカスタムスタイルを指定することで、好きな drawable を設定できます。


    <!-- style for the tabs -->
    <style name="MyActionBarTabStyle" parent="android:style/Widget.Holo.Light.ActionBarView_TabView">
    <item name="android:background">@drawable/actionbar_tab_bg</item>
    <item name="android:paddingLeft">32dp</item>
    <item name="android:paddingRight">32dp</item>
    </style>



Actions

・Selectable item background

上:カスタマイズ前、下:カスタマイズ後


個々の Action item は選択できるコンポーネントの背景として、デフォルトの青色を継承しています。
android:selectableItemBackground を好きな色を使った shape drawable で上書きすることで、この色や振る舞いをカスタマイズできます。


<item name="android:selectableItemBackground">@drawable/ad_selectable_background</item>



・ Overflow menu

overflow メニューが開かれたことを示すために、リストのトップに青いバーを表示するようになっています。
android:popupMenuStyle にカスタマイズした drawable を指定することで overflow menu のトップを変えることができます。


<!-- style the overflow menu -->
<style name="MyPopupMenu" parent="android:style/Widget.Holo.Light.ListPopupWindow">
<item name="android:popupBackground">@drawable/ad_menu_dropdown_panel_holo_light</item>
</style>


overflow menu 内のアイテムを選択したときもデフォルトの選択カラーが使われます。android:dropDownListViewStyle にカスタマイズした選択色を設定することで、この色を変えることができます。


<!-- style the items within the overflow menu -->
<style name="MyDropDownListView" parent="android:style/Widget.Holo.ListView.DropDown">
<item name="android:listSelector">@drawable/ad_selectable_background</item>
</style>


この変更でほとんどの部分を切り替えることができますが、より詳細な部分に注意する必要があります。overflow 内の チェックボックスとラジオボタンが付いた menu item を選択したときのハイライトには、いまだにデフォルトの青色がつかわれています。次のように設定して、自分のテーマに合うようにします。


<item name="android:listChoiceIndicatorMultiple">@drawable/ad_btn_check_holo_light</item>
<item name="android:listChoiceIndicatorSingle">@drawable/ad_btn_radio_holo_light</item>


左;カスタマイズ前、右:カスタマイズ後



Background

アクションバーの背景を変更するには、単に android:actionBarStyle の item で android:background を上書きします。


<style name="MyActionBar" parent="android:style/Widget.Holo.Light.ActionBar">
<item name="android:background">@drawable/action_bar_background</item>
</style>



Bringing it all together

これまでに定義したコンポーネントを合わせて自分のカスタムスタイルをつくります。


<style name="Theme.AndroidDevelopers" parent="android:style/Theme.Holo.Light">
<item name="android:selectableItemBackground">@drawable/ad_selectable_background</item>
<item name="android:popupMenuStyle">@style/MyPopupMenu</item>
<item name="android:dropDownListViewStyle">@style/MyDropDownListView</item>
<item name="android:actionBarTabStyle">@style/MyActionBarTabStyle</item>
<item name="android:actionDropDownStyle">@style/MyDropDownNav</item>
<item name="android:listChoiceIndicatorMultiple">@drawable/ad_btn_check_holo_light</item>
<item name="android:listChoiceIndicatorSingle">@drawable/ad_btn_radio_holo_light</item>
</style>


個々の Activity、もしくはアプリケーション全体にこのスタイルを適用します。


<activity android:name=".MainActivity"
android:label="@string/app_name"
android:theme="@style/Theme.AndroidDevelopers"
android:logo="@drawable/ad_logo">


上記で上書き指定したシステムスタイルのいくつかは Action Bar 以外にも影響します。例えば、android:selectableItemBackground を上書きすると、選択状態をサポートする多くのウィジェットに影響します。これはアプリケーション全体にスタイルを適用したい場合に便利ですが、カスタマイズが全体の一貫性に適っているかテストして確かめてください。


■ Familiar but styled

Action Bar をカスタマイズすることはアプリケーションのブランディングを標準のコントロールコンポーネントに拡張するすばらしい方法です。この方法を使うことは大きな責任が伴います。ユーザーインタフェースをカスタマイズ場合、アプリケーションが読みやすく使いやすい状態を保てるように細心の注意を払う必要があります。
特に、関連する全ての状態に対してテキストと画像のコントラストが不十分になっているハイライトカラーがないか注意してください。


  

2011年7月2日土曜日

Honeycomb 3.0 で xml 指定だと tileMode が効かない

ActionBar をカスタマイズしようと思って、

res/drawable/actionbar_bg.xml

<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
android:src="@drawable/bg2"
android:tileMode="repeat" />


res/values/styles.xml

<resources>
<style name="MyTheme" parent="@android:style/Theme.Holo.Light">
<item name="android:windowBackground">@drawable/bg</item>
<item name="android:actionBarStyle">@style/MyActionBar</item>

</style>

<style name="MyActionBar" parent="@android:style/Widget.Holo.Light.ActionBar">
<item name="android:titleTextStyle">@style/MyActionBarTitle</item>
<item name="android:subtitleTextStyle">@style/MyActionBarSubTitle</item>
<item name="android:background">@drawable/actionbar_bg</item>
</style>

<style name="MyActionBarTitle" parent="@android:style/TextAppearance.Holo.Widget.ActionBar.Title">
<item name="android:textColor">#fff</item>
</style>

<style name="MyActionBarSubTitle" parent="@android:style/TextAppearance.Holo.Widget.ActionBar.Subtitle">
<item name="android:textColor">#fff</item>
</style>
</resources>


としたら、リピートされなくて引き伸ばしたようになってしましました。
調べたら、

[Honeycomb / Android 3.0] ActionBar background image - Stack Overflow -

に書いてありました。

honeycomb / Android 3.0 の既知のバグだそうです。3.1 では直ってるのかな?

現状での解決方法はコードから指定する。です。

Activity の onCreate() で


final ActionBar actionBar = getActionBar();
BitmapDrawable background = (BitmapDrawable)getResources().getDrawable(R.drawable.bg2);
background.setTileModeXY(android.graphics.Shader.TileMode.REPEAT, android.graphics.Shader.TileMode.REPEAT);
actionBar.setBackgroundDrawable(background);


のように指定するとちゃんとリピートされます。

Android detecting devices

デバイスを検出する方法が Google I/O 2011 の

"Android Protips: Advanced Topics for Expert Android App Developers"

で紹介されていたので、メモしておきます。

---

なぜデバイスを検出するのか?

  • デバイスが通話機能をサポートしていない場合、TelephonyManager.getDeviceId() は null を返す。
  • Bluetooth や WiFi が見つからない(もしくはオフの)場合、MAC アドレスは使えないことがある
  • デバイスがワイプされたときに変わらないので、新しいユーザーでも同じデバイス ID になってしまう。
  • Settings.Secure.ANDROID_ID はワイプ時にリセットされるが、Android 2.2 以前では信頼性がない。


UUID.randomUUID().toString()

を使う


private static String uniqueID = null;
private static final String PREF_UNIQUE_ID = "PREF_UNIQUE_ID";

public synchronized static String id(Context context) {
if (uniqueID == null) {
SharedPreferences sp = context.getSharedPreferences(PREF_UNIQUE_ID,
Context.MODE_PRIVATE);
uniqueID = sp.getString(PREF_UNIQUE_ID, null);

if(uniqueID == null) {
uniqueID = UUID.randomUUID().toString();
Editor editor = sp.edit();
editor.putString(PREF_UNIQUE_ID, uniqueID);
editor.commit();
}
}
return uniqueID;
}



 

2011年7月1日金曜日

AppWidget の ProgressBar の色を変える

# 某MLで AppWidget 上のプログレスバー (ProgressBar) の色を
# 変えたいという質問があったのでやってみたらできたので、
# せっかくだからブログに書きます。

注意 その1 : API Level 7 (2.1) 以上です。
注意 その2 : 最低限のことしかやってないので、あとは自分でなんとかしてね。

---

キーは RemoteView クラスの addView() メソッドです。

最初の設定画面でどの色を選択したのかを Preference に保存しておき、その値によって AppWidget の update で addView() で挿入するレイアウトを変えます。
レイアウトや背景画像の XML を設定で選択できる数用意しないといけないのがちょっとかっこ悪いですが、Drawable は RemoteView で指定できないのでしょうがないです。。。




AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="yanzm.example.progressbar.appwidget"
android:versionCode="1"
android:versionName="1.0">
<uses-sdk android:minSdkVersion="7" />

<application android:icon="@drawable/icon" android:label="@string/app_name">
<receiver android:name=".WidgetProvider"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>
<meta-data android:name="android.appwidget.provider"
android:resource="@xml/appwidget"/>
</receiver>

<activity android:name=".WidgetConfigure"
android:screenOrientation="portrait"
android:windowSoftInputMode="adjustPan|stateAlwaysHidden">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_CONFIGURE"/>
</intent-filter>
</activity>

</application>
</manifest>


res/xml/appwidget.xml

<?xml version="1.0" encoding="UTF-8"?>
<appwidget-provider
xmlns:android="http://schemas.android.com/apk/res/android"
android:minWidth="294dip"
android:minHeight="72dip"
android:initialLayout="@layout/main"
android:updatePeriodMillis="0"
android:configure="yanzm.example.progressbar.appwidget.WidgetConfigure"
/>


WidgetConfigure.java

package yanzm.example.progressbar.appwidget;

import android.app.Activity;
import android.appwidget.AppWidgetManager;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.view.View;
import android.widget.Spinner;

public class WidgetConfigure extends Activity {

private int mAppWidgetId = AppWidgetManager.INVALID_APPWIDGET_ID;
private static final String PREFS_NAME
= "yanzm.example.progressbar.appwidget";
private static final String PREF_PREFIX_KEY = "prefix_";

public void onCreate(Bundle icicle) {
super.onCreate(icicle);

setResult(RESULT_CANCELED);

setContentView(R.layout.config);

// Find the widget id from the intent.
Intent intent = getIntent();
Bundle extras = intent.getExtras();
if (extras != null) {
mAppWidgetId = extras.getInt(
AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID);

Intent resultValue = new Intent();
resultValue.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, mAppWidgetId);
setResult(RESULT_CANCELED, resultValue);
}

// If they gave us an intent without the widget id, just bail.
if (mAppWidgetId == AppWidgetManager.INVALID_APPWIDGET_ID) {
finish();
}
}

/**
* called when OK button is clicked
*
* @param v
*/
public void onClickOk(View v) {
finishConfigure();
}

/**
* called when Cancel button is clicked
*
* @param v
*/
public void onClickCancel(View v) {
finish();
}

/**
* Store some settings to application preferences.
*/
private void finishConfigure() {
final Context context = WidgetConfigure.this;

Spinner spinner = (Spinner) findViewById(R.id.spinner);

int colorIndex = spinner.getSelectedItemPosition();

saveSettings(context, mAppWidgetId, colorIndex);

// Push widget update to surface with newly set prefix
AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
WidgetProvider.updateAppWidget(context, appWidgetManager,
mAppWidgetId, colorIndex);

// Make sure we pass back the original appWidgetId
Intent resultValue = new Intent();
resultValue.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, mAppWidgetId);
setResult(RESULT_OK, resultValue);
finish();
}

/**
* save current settings to preference
*
* @param colorIndex
*/
static void saveSettings(Context context, int appWidgetId, int colorIndex) {
SharedPreferences.Editor prefs = context.getSharedPreferences(PREFS_NAME, MODE_PRIVATE).edit();
prefs.putInt(PREF_PREFIX_KEY + appWidgetId + "_color", colorIndex);
prefs.commit();
}

/**
* load color setting from preference
*
* @param context
* @param appWidgetId
* @return color
*/
static int loadColorIndex(Context context, int appWidgetId) {
SharedPreferences prefs = context.getSharedPreferences(PREFS_NAME, MODE_PRIVATE);
int color = prefs.getInt(PREF_PREFIX_KEY + appWidgetId + "_color", 0);
return color;
}
}


res/layout/config.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<Spinner
android:id="@+id/spinner"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:entries="@array/color_name"/>

<!-- OK, Cancel Button -->
<LinearLayout
android:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="wrap_content">

<Button
android:layout_width="0dip"
android:layout_height="wrap_content"
android:layout_weight="1"
android:onClick="onClickOk"
android:text="OK" />

<Button
android:layout_width="0dip"
android:layout_height="wrap_content"
android:layout_weight="1"
android:onClick="onClickCancel"
android:text="Cancel" />

</LinearLayout>
</LinearLayout>




WidgetProvider.java

package yanzm.example.progressbar.appwidget;

import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider;
import android.content.Context;
import android.widget.RemoteViews;

public class WidgetProvider extends AppWidgetProvider {

@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager,
int[] appWidgetIds) {

int color = WidgetConfigure.loadColorIndex(context, appWidgetIds[0]);

updateAppWidget(context, appWidgetManager, appWidgetIds[0], color);
}


static void updateAppWidget(Context context, AppWidgetManager appWidgetManager,
int appWidgetId, int colorIndex) {

RemoteViews container = new RemoteViews(context.getPackageName(), R.layout.main);
RemoteViews nestedView = null;
switch(colorIndex) {
case 0:
nestedView = new RemoteViews(context.getPackageName(), R.layout.child1);
break;
case 1:
nestedView = new RemoteViews(context.getPackageName(), R.layout.child2);
break;
case 2:
nestedView = new RemoteViews(context.getPackageName(), R.layout.child3);
break;
case 3:
nestedView = new RemoteViews(context.getPackageName(), R.layout.child4);
break;
}

if(nestedView != null) {
container.removeAllViews(R.id.container);
container.addView(R.id.container, nestedView);
}

// Tell the widget manager
appWidgetManager.updateAppWidget(appWidgetId, container);
}
}


res/layout/main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/container"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical"
android:padding="10dip"
/>


res/layout/child1.xml

<?xml version="1.0" encoding="utf-8"?>
<ProgressBar xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/progressBar"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:progress="500"
android:max="1000"
android:progressDrawable="@drawable/seekbar_bg1"
/>

child2.xml, child3.xml, child4.xml は progressDrawable が違うだけ

res/drawable/seekbar_bg1.xml

<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@android:id/background">
<shape>
<corners android:radius="5dip" />
<gradient
android:startColor="#ff666666"
android:centerColor="#ffaaaaaa"
android:centerY="0.3"
android:endColor="#ff444444"
android:angle="270" />
</shape>
</item>
<item android:id="@android:id/progress">
<clip>
<shape>
<corners android:radius="5dip" />
<gradient
android:startColor="#ff000000"
android:centerColor="#ff000000"
android:centerY="0.3"
android:endColor="#ff000000"
android:angle="270"
/>
</shape>
</clip>
</item>
</layer-list>

seekbar_bg2.xml, seekbar_bg3.xml, seekbar_bg4.xml は色が違うだけ


# 時間がなくて GitHub にあげられなかった。そのうちあげるかも。。。


 

'},ClipboardSwf:null,Version:'1.5.1'}};dp.SyntaxHighlighter=dp.sh;dp.sh.Toolbar.Commands={ExpandSource:{label:'+ expand source',check:function(highlighter){return highlighter.collapse;},func:function(sender,highlighter) {sender.parentNode.removeChild(sender);highlighter.div.className=highlighter.div.className.replace('collapsed','');}},ViewSource:{label:'view plain',func:function(sender,highlighter) {var code=dp.sh.Utils.FixForBlogger(highlighter.originalCode).replace(/'+code+'');wnd.document.close();}},CopyToClipboard:{label:'copy to clipboard',check:function(){return window.clipboardData!=null||dp.sh.ClipboardSwf!=null;},func:function(sender,highlighter) {var code=dp.sh.Utils.FixForBlogger(highlighter.originalCode).replace(/</g,'<').replace(/>/g,'>').replace(/&/g,'&');if(window.clipboardData) {window.clipboardData.setData('text',code);} else if(dp.sh.ClipboardSwf!=null) {var flashcopier=highlighter.flashCopier;if(flashcopier==null) {flashcopier=document.createElement('div');highlighter.flashCopier=flashcopier;highlighter.div.appendChild(flashcopier);} flashcopier.innerHTML='';} alert('The code is in your clipboard now');}},PrintSource:{label:'print',func:function(sender,highlighter) {var iframe=document.createElement('IFRAME');var doc=null;iframe.style.cssText='position:absolute;width:0px;height:0px;left:-500px;top:-500px;';document.body.appendChild(iframe);doc=iframe.contentWindow.document;dp.sh.Utils.CopyStyles(doc,window.document);doc.write('

'+highlighter.div.innerHTML+'

');doc.close();iframe.contentWindow.focus();iframe.contentWindow.print();alert('Printing...');document.body.removeChild(iframe);}},About:{label:'?',func:function(highlighter) {var wnd=window.open('','_blank','dialog,width=300,height=150,scrollbars=0');var doc=wnd.document;dp.sh.Utils.CopyStyles(doc,window.document);doc.write(dp.sh.Strings.AboutDialog.replace('{V}',dp.sh.Version));doc.close();wnd.focus();}}};dp.sh.Toolbar.Create=function(highlighter) {var div=document.createElement('DIV');div.className='tools';for(var name in dp.sh.Toolbar.Commands) {var cmd=dp.sh.Toolbar.Commands[name];if(cmd.check!=null&&!cmd.check(highlighter)) continue;div.innerHTML+=''+cmd.label+'';} return div;} dp.sh.Toolbar.Command=function(name,sender) {var n=sender;while(n!=null&&n.className.indexOf('dp-highlighter')==-1) n=n.parentNode;if(n!=null) dp.sh.Toolbar.Commands[name].func(sender,n.highlighter);} dp.sh.Utils.CopyStyles=function(destDoc,sourceDoc) {var links=sourceDoc.getElementsByTagName('link');for(var i=0;i');} dp.sh.Utils.FixForBlogger=function(str) {return(dp.sh.isBloggerMode==true)?str.replace(/
|<br\s*\/?>/gi,''):str;} dp.sh.RegexLib={MultiLineCComments:new RegExp('/\\*[\\s\\S]*?\\*/','gm'),SingleLineCComments:new RegExp('//.*$','gm'),SingleLinePerlComments:new RegExp('#.*$','gm'),DoubleQuotedString:new RegExp('"(?:\\.|(\\\\\\")|[^\\""\\n])*"','g'),SingleQuotedString:new RegExp("'(?:\\.|(\\\\\\')|[^\\''\\n])*'",'g')};dp.sh.Match=function(value,index,css) {this.value=value;this.index=index;this.length=value.length;this.css=css;} dp.sh.Highlighter=function() {this.noGutter=false;this.addControls=true;this.collapse=false;this.tabsToSpaces=true;this.wrapColumn=80;this.showColumns=true;} dp.sh.Highlighter.SortCallback=function(m1,m2) {if(m1.indexm2.index) return 1;else {if(m1.lengthm2.length) return 1;} return 0;} dp.sh.Highlighter.prototype.CreateElement=function(name) {var result=document.createElement(name);result.highlighter=this;return result;} dp.sh.Highlighter.prototype.GetMatches=function(regex,css) {var index=0;var match=null;while((match=regex.exec(this.code))!=null) this.matches[this.matches.length]=new dp.sh.Match(match[0],match.index,css);} dp.sh.Highlighter.prototype.AddBit=function(str,css) {if(str==null||str.length==0) return;var span=this.CreateElement('SPAN');str=str.replace(/ /g,' ');str=str.replace(/');if(css!=null) {if((/br/gi).test(str)) {var lines=str.split(' 
');for(var i=0;ic.index)&&(match.index/gi,'\n');var lines=html.split('\n');if(this.addControls==true) this.bar.appendChild(dp.sh.Toolbar.Create(this));if(this.showColumns) {var div=this.CreateElement('div');var columns=this.CreateElement('div');var showEvery=10;var i=1;while(i<=150) {if(i%showEvery==0) {div.innerHTML+=i;i+=(i+'').length;} else {div.innerHTML+='·';i++;}} columns.className='columns';columns.appendChild(div);this.bar.appendChild(columns);} for(var i=0,lineIndex=this.firstLine;i0;i++) {if(Trim(lines[i]).length==0) continue;var matches=regex.exec(lines[i]);if(matches!=null&&matches.length>0) min=Math.min(matches[0].length,min);} if(min>0) for(var i=0;i

Blogger Syntax Highliter

Version: {V}

http://www.dreamprojections.com/syntaxhighlighter

©2004-2007 Alex Gorbatchev.

'},ClipboardSwf:null,Version:'1.5.1'}};dp.SyntaxHighlighter=dp.sh;dp.sh.Toolbar.Commands={ExpandSource:{label:'+ expand source',check:function(highlighter){return highlighter.collapse;},func:function(sender,highlighter) {sender.parentNode.removeChild(sender);highlighter.div.className=highlighter.div.className.replace('collapsed','');}},ViewSource:{label:'view plain',func:function(sender,highlighter) {var code=dp.sh.Utils.FixForBlogger(highlighter.originalCode).replace(/'+code+'');wnd.document.close();}},CopyToClipboard:{label:'copy to clipboard',check:function(){return window.clipboardData!=null||dp.sh.ClipboardSwf!=null;},func:function(sender,highlighter) {var code=dp.sh.Utils.FixForBlogger(highlighter.originalCode).replace(/</g,'<').replace(/>/g,'>').replace(/&/g,'&');if(window.clipboardData) {window.clipboardData.setData('text',code);} else if(dp.sh.ClipboardSwf!=null) {var flashcopier=highlighter.flashCopier;if(flashcopier==null) {flashcopier=document.createElement('div');highlighter.flashCopier=flashcopier;highlighter.div.appendChild(flashcopier);} flashcopier.innerHTML='';} alert('The code is in your clipboard now');}},PrintSource:{label:'print',func:function(sender,highlighter) {var iframe=document.createElement('IFRAME');var doc=null;iframe.style.cssText='position:absolute;width:0px;height:0px;left:-500px;top:-500px;';document.body.appendChild(iframe);doc=iframe.contentWindow.document;dp.sh.Utils.CopyStyles(doc,window.document);doc.write('

'+highlighter.div.innerHTML+'

');doc.close();iframe.contentWindow.focus();iframe.contentWindow.print();alert('Printing...');document.body.removeChild(iframe);}},About:{label:'?',func:function(highlighter) {var wnd=window.open('','_blank','dialog,width=300,height=150,scrollbars=0');var doc=wnd.document;dp.sh.Utils.CopyStyles(doc,window.document);doc.write(dp.sh.Strings.AboutDialog.replace('{V}',dp.sh.Version));doc.close();wnd.focus();}}};dp.sh.Toolbar.Create=function(highlighter) {var div=document.createElement('DIV');div.className='tools';for(var name in dp.sh.Toolbar.Commands) {var cmd=dp.sh.Toolbar.Commands[name];if(cmd.check!=null&&!cmd.check(highlighter)) continue;div.innerHTML+=''+cmd.label+'';} return div;} dp.sh.Toolbar.Command=function(name,sender) {var n=sender;while(n!=null&&n.className.indexOf('dp-highlighter')==-1) n=n.parentNode;if(n!=null) dp.sh.Toolbar.Commands[name].func(sender,n.highlighter);} dp.sh.Utils.CopyStyles=function(destDoc,sourceDoc) {var links=sourceDoc.getElementsByTagName('link');for(var i=0;i');} dp.sh.Utils.FixForBlogger=function(str) {return(dp.sh.isBloggerMode==true)?str.replace(/
|<br\s*\/?>/gi,'\n'):str;} dp.sh.RegexLib={MultiLineCComments:new RegExp('/\\*[\\s\\S]*?\\*/','gm'),SingleLineCComments:new RegExp('//.*$','gm'),SingleLinePerlComments:new RegExp('#.*$','gm'),DoubleQuotedString:new RegExp('"(?:\\.|(\\\\\\")|[^\\""\\n])*"','g'),SingleQuotedString:new RegExp("'(?:\\.|(\\\\\\')|[^\\''\\n])*'",'g')};dp.sh.Match=function(value,index,css) {this.value=value;this.index=index;this.length=value.length;this.css=css;} dp.sh.Highlighter=function() {this.noGutter=false;this.addControls=true;this.collapse=false;this.tabsToSpaces=true;this.wrapColumn=80;this.showColumns=true;} dp.sh.Highlighter.SortCallback=function(m1,m2) {if(m1.indexm2.index) return 1;else {if(m1.lengthm2.length) return 1;} return 0;} dp.sh.Highlighter.prototype.CreateElement=function(name) {var result=document.createElement(name);result.highlighter=this;return result;} dp.sh.Highlighter.prototype.GetMatches=function(regex,css) {var index=0;var match=null;while((match=regex.exec(this.code))!=null) this.matches[this.matches.length]=new dp.sh.Match(match[0],match.index,css);} dp.sh.Highlighter.prototype.AddBit=function(str,css) {if(str==null||str.length==0) return;var span=this.CreateElement('SPAN');str=str.replace(/ /g,' ');str=str.replace(/');if(css!=null) {if((/br/gi).test(str)) {var lines=str.split(' 
');for(var i=0;ic.index)&&(match.index/gi,'\n');var lines=html.split('\n');if(this.addControls==true) this.bar.appendChild(dp.sh.Toolbar.Create(this));if(this.showColumns) {var div=this.CreateElement('div');var columns=this.CreateElement('div');var showEvery=10;var i=1;while(i<=150) {if(i%showEvery==0) {div.innerHTML+=i;i+=(i+'').length;} else {div.innerHTML+='·';i++;}} columns.className='columns';columns.appendChild(div);this.bar.appendChild(columns);} for(var i=0,lineIndex=this.firstLine;i0;i++) {if(Trim(lines[i]).length==0) continue;var matches=regex.exec(lines[i]);if(matches!=null&&matches.length>0) min=Math.min(matches[0].length,min);} if(min>0) for(var i=0;i

ページビューの合計