1×1のバッテリーウィジェットのBatteryCrystalをリリースしました。
バッテリー残量によってクリスタルの色が変わるようになってます。
中央にはバッテリー残量が半透明の文字で表示されるので、
何気にちょっとおしゃれ感を出してます。
まぁ素材自体は本を見ながら作ったまんまなんですが…。
(グラデーションとかいじっただけ…)

例によってリリースしたのでサポートページ作っとりますので、
そちらをご確認ください。
サイドバーのプロフィールのところにリンク付けてます(うわ、超手抜き…
2010 年 9 月 4 日 土曜日
1×1のバッテリーウィジェットのBatteryCrystalをリリースしました。
バッテリー残量によってクリスタルの色が変わるようになってます。
中央にはバッテリー残量が半透明の文字で表示されるので、
何気にちょっとおしゃれ感を出してます。
まぁ素材自体は本を見ながら作ったまんまなんですが…。
(グラデーションとかいじっただけ…)

例によってリリースしたのでサポートページ作っとりますので、
そちらをご確認ください。
サイドバーのプロフィールのところにリンク付けてます(うわ、超手抜き…
2010 年 8 月 26 日 木曜日
AndroidSDK開発のレシピのレシピ61「音声ファイルを再生する」を読みながら、Androidでボタンが押されたタイミングで音を出そうとプログラムしたところ、出ない…。SoundPoolクラスを使ってoggファイルで音を出そうとしました。
// HogeというActivity内
btn.setOnClickListener(new OnClickListener(){
public void onClick(View v){
int[] sounds = new int[5];
SoundPool soundPool = new SoundPool(5, AudioManager.STREAM_MUSIC, 0);
sounds[0] = soundPool.load(Hoge.this, R.raw.switch, 1);
soundPool.play(sounds[0], 1.0f, 1.0f, 0, 0. 1.0f);
soundPool.release();
}
});
DDMSでログを見たところ、WARN sample 1 not READYというメッセージが。準備できてへんと。sample 1 not READYでググったら、英語のサイトで「初期化するのに時間がかかるからちょっと時間を置け」って書かれてた。どうやって…?
ボタンを押したタイミングで鳴ればいいわけだから、onResumeのタイミングでロードしておき、onPauseのタイミングでリリースするようにしておけばいいのかなと思ってやってみたらビンゴでした。
// HogeというActivity内
private SoundPool mSoundPool;
private int[] mSounds = new int[5];
protected void onCreate(Bundle savedInstanceState) {
// ... ゴニョゴニョと処理をしとく
btn.setOnClickListener(new OnClickListener(){
public void onClick(View v){
mSoundPool.play(mSounds[0], 1.0f, 1.0f, 0, 0. 1.0f);
}
});
}
protected void onResume(){
// 音をロードしておく
mSoundPool = new SoundPool(5, AudioManager.STREAM_MUSIC, 0);
mSounds[0] = mSoundPool.load(this, R.raw.switch, 1);
}
protected void onPause(){
// 音をリリース
mSoundPool.release();
}
以上、備忘録でしたー!
2010 年 8 月 25 日 水曜日
コメントにて、Androidに元々入っているギャラリーのような動きをさせるその後はどうなったんだ!?という質問を受けましたので、公開してしまおうと思います。ただ、当然ながらこれは私の独自のやり方なので、はっきりいって正しいかどうかはわかりません。
あと、画像の拡大・縮小にはAnimationを使っております。ドラッグ移動はできますが、ビヨーンといった跳ね返りとかは実装していません(そこまで精神がもたなかった)。やろうと思えばできるんでしょうけどねぇ〜。
イベントはダブルタップとかドラッグとか使うので、OnGestureListenerとかOnDoubleTapListenerは必須。implementsしておいてください。
で、画像の拡大・縮小はそんなに難しくないので、省略するとして(オィ)、問題はドラッグ移動なわけです。
画像のドラッグについては、下のメソッドで動くはず。なんで動くのか?を説明するのは結構難儀ですが。
ちなみに、拡大率は2倍固定です(面積は4倍)。
// private Rect mRect
// private Matrix mMatrix = new Matrix();
// private float[] mValues = new float[9];
private void imageMove(ImageView iv, MotionEvent e) {
if (e.getHistorySize() > 0) {
int x = (int) e.getHistoricalX(0) - (int) e.getX();
int y = (int) e.getHistoricalY(0) - (int) e.getY();
iv.scrollBy(x, y); // ①
int new_x = iv.getScrollX();
int new_y = iv.getScrollY();
mRect = iv.getDrawable().getBounds();
mMatrix = iv.getImageMatrix();
mMatrix.getValues(mValues);
// ②
// 画面上の画像の横サイズ
int iw = (int) ((int) mRect.width() * mValues[Matrix.MSCALE_X]);
// 画面上の画像の縦サイズ
int ih = (int) ((int) mRect.height() * mValues[Matrix.MSCALE_Y]);
// 画像の横サイズの半分
int iw_harf = iw / 2;
// 画像の縦サイズの半分
int ih_harf = ih / 2;
// 縦方向の黒くなっているところの高さ
int black_out_w = (iv.getWidth() / 4) - iw_harf;
// 縦方向の黒くなっているところの高さ
int black_out_h = (iv.getHeight() / 4) - ih_harf;
Log.d("imageMove", "new_x=" + Integer.toString(new_x) + ", new_y="
+ Integer.toString(new_y));
Log.d("imageMove", "_x=" + Integer.toString(iw) + ", _y="
+ Integer.toString(ih));
Log.d("imageMove", "black_out_w=" + Integer.toString(black_out_w)
+ ", black_out_h=" + Integer.toString(black_out_h));
// 拡大画像がはみ出ないようにする処理
if (Math.abs(new_x) > black_out_w || Math.abs(new_y) > black_out_h) {
// new_x, new_yが0の場合を考慮する必要あり
if (new_x == 0 && new_y == 0) {
// 何もしない ③
} else if (new_x == 0) {
iv.scrollTo(0, new_y
/ Math.abs(new_y)
* (int) Math.min(Math.abs(new_y), Math
.abs(black_out_h))); // ④
} else if (new_y == 0) {
iv.scrollTo(new_x
/ Math.abs(new_x)
* (int) Math.min(Math.abs(new_x), Math
.abs(black_out_w)), 0); // ⑤
} else {
iv.scrollTo(new_x
/ Math.abs(new_x)
* (int) Math.min(Math.abs(new_x), Math
.abs(black_out_w)), new_y
/ Math.abs(new_y)
* (int) Math.min(Math.abs(new_y), Math
.abs(black_out_h))); // ⑥
}
}
}
}
では説明しまーす。
まず、①で、普通のドラッグ移動を行います。
ヒストリーが取れるので、前の場所から移動した分だけスクロールするよって処理です。
②で、画像の大きさや画面からはみ出る範囲や背景が黒くなっている部分の大きさを計算・取得します。正直ここが一番面倒というか、わかると簡単なんですが、辿り着くのに数日かかりました。アホです。図にして考えるとやりやすい(手書きでガシガシ書いたので、残ってない…)。
Matrixから画像の拡大率を取ります。これは何故かというと、画面よりもでかい画像を読んだら縮小して表示されるんですが、Rect.width()に本来の画像の横サイズが入っているので、それに拡大率をかけることによって画面内における画像の横幅を取得します。縦幅も同様に。
で、画像が画面とまったく同じ大きさでもない限り、黒抜きにされるから、その黒抜きの幅を取得します。これは表現として合ってないかもしれませんが。
③です。これ以降が、画像どこかしらが画面の端っこにきている場合の処理です。普通の場合はscrollByをしていましたが、それ以外の場合はscrollToで上書きする感じになります。
iv.getScrollX()は、親要素に対してスクロールした量だったかな…。割り算をやるので、0の場合は処理しないように制御しないと、例外が発生してアプリが落ちますので、ご注意を。それらが、③、④、⑤。基本は⑥の処理。
という感じです。すみません、眠い頭で書いてるので、説明がグダグダかもしれませんが、メソッド自体は合ってると思います。(但しアニメーションで画像を2倍にしたときに限る)
以上です。
2010 年 8 月 23 日 月曜日
Androidアプリの第二弾として、組み計算というアプリをリリースしました。
色々な組み合わせで計算をしたい場合などに使えるようなアプリです。まだちょっと完成度は低いかもしれませんが…(だからバージョン0.1.0です)。
早速、紹介ページを作りました。
サイドバーのアプリのところか、以下のリンクからどうぞ!!
2010 年 8 月 19 日 木曜日
@yanzmさんのブログで紹介されているまんまなんですが、備忘録に書かせてもらいます。
まずは何故、俺がこれを実装する必要があったかの経緯を書きます。
いろいろあって、GridViewで画像の一覧を表示する必要があるのですが、その前のアクティビティでは検索フォームがあり、その検索用アクティビティでソフトウェアキーボードを開いた状態で検索ボタンを押して画像一覧アクティビティに来たら、Out of Memoryでアプリが落ちました。ええ、HT-03Aでです。Desireだと落ちなかったのでわからなかった…。
HT-03Aでもずっとテストしていたのですが、画像一覧アクティビティで画像がたくさん出ても落ちないかどうかのテストはやっていたのですが、面倒だったので検索条件なしでずっと実験していたため、ソフトウェアキーボードを開いた状態で画像GridViewにきたら落ちるなんて夢にも思ってませんでした。
なので、検索ボタンを押したらソフトウェアキーボードを消すようにして実験してみたところ、HT-03Aでもアプリが落ちなくなりました。恐るべし、ソフトウェアキーボード!!
参考にしたyanzmさんのブログ記事は以下のURL。
http://y-anz-m.blogspot.com/2009/12/android.html
// 検索ボタンを押したときのイベント
this.btnSearch.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
// キーボードを消す
InputMethodManager inputMethodManager = (InputMethodManager)getSystemService(Context.INPUT_METHOD_SERVICE);
inputMethodManager.hideSoftInputFromWindow(v.getWindowToken(), 0);
// 次のアクティビティに渡すIntentの呼び出しなど
}
}
2010 年 8 月 12 日 木曜日
Twitterのほうで先に報告しましたが、私個人の開発アプリとしての第一弾として、音声エディタをAndroid Marketにリリースしました。


どういうアプリかといいますと、Googleの音声検索アプリを使って、自分でしゃべった言葉を文字列に変換します。
ただ、音声検索は文脈を理解できないため、句読点や、「〜」「…」「!」「?」「・」という日本人がよく使う記号をソフトウェアキーボードなしで入力できるようにしました(これだけのために毎回ソフトウェアキーボード開くのが鬱陶しいため)。
入力した文字列は、コピーボタンを押してクリップボードにコピーするのはもちろんできますが、連携ボタンを押すことでAndroidならではの機能「他のアプリにデータを渡す」ということができます。
これはどういうことかというと、自分でしゃべった内容をすぐにメールにしたり、ツイッタークライアントに渡して本当に呟けるということです。WordPressとか入れてるとしゃべった内容でブログの更新もできます!(但し、タイトルは手入力で)
対応機種というか、対応OSはバージョン1.5以上にしていますが、まぁ日本のAndroidOSはほとんどが1.6以上なので、実質ほとんど大丈夫だと思います。
■検証端末
・Softbank X06HT(Desire) 私のケータイ
・Docomo HT-03A 会社の先輩のケータイ
HT-03Aで動作したので、他でも問題ないはず。
問題がありましたら、フィードバックかTwitter上で報告お願いします。
twitter id: patorash
もしAndroidケータイを使っていましたら、Androidのブラウザで以下のリンクをクリックするか、Android Marketで「音声エディタ」で検索して、インストールしてみてください!ヨロシクお願いします。
Androidアプリ 音声エディタ
(PCからクリックしたら、Not Foundに移動するのでご注意を)
2010 年 8 月 11 日 水曜日
個人でAndroidアプリを思いつきで作ったのですが(まだリリースしてない)、そのときに他のアプリを呼び出したいなぁと思ったのだけれど、やり方がわからなかったんで調べました。自分で作ったアクティビティだけに限りませんが、特定のアクティビティを呼び出すのを明示的Intent呼び出しというのに対して、とりあえずIntentを作ってデータを放って、ユーザ側にアプリを選択させるのを暗黙的Intent呼び出しというらしいですね。
参考にさせてもらったのは以下のサイトです。
Intent(インテント)連携をまとめてみる – コードを貼り付けながら。
今回はEditTextに入力された内容を、メーラーやtwitterクライアントに対して渡すというのを実装してみました。
// ActivityのonCreate内
// 他のインテントを呼び出す
btnCollaboration.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
String data = editText.getText().toString();
if (data.length() > 0) {
try {
// メーラーやtwitterクライアントなどを呼び出す
Intent intent = new Intent();
intent.setAction(Intent.ACTION_SEND);
intent.setType("text/plain");
intent.putExtra(Intent.EXTRA_TEXT, data);
startActivity(Intent.createChooser(intent, getString(R.string.txt_please_select)));
} catch (ActivityNotFoundException e) {
e.printStackTrace();
// 呼び出せるActivityが存在しない
Toast.makeText(VoiceEditor.this, R.string.txt_no_collaboration_found, Toast.LENGTH_SHORT).show();
}
} else {
// EditTextにデータがないのでToast呼び出し
Toast.makeText(VoiceEditor.this, R.string.txt_no_text, Toast.LENGTH_SHORT).show();
}
}
});
全部を自分で実装しなくても、入り口だけ作ってあとは他のアプリにデータを渡せるってのは、便利ですねぇ。
2010 年 8 月 7 日 土曜日
MapActivityを継承したアクティビティで、ダブルタップしても地図がズームインしないので、これはGoogleMapの仕様に合わせたいなぁと試行錯誤した結果を記します。正直、しんどかった。
まぁ、似たようなことを考えている人は世の中にもおられるもので、そこを参考にさせてもらいました。
まぁ上記のサイトにも書かれているのですが、OnGestureListenerをimplementsしただけでは、MapActivityはタップイベントを拾ってくれません。理由はMapViewが先に拾っちゃうからです。原因がわかったとしても、じゃあどうすればいいか。それがなかなかわからなかったんですが、上記のサイトでヒントを書いてくれてるので、それを参考にします(上記のサイトのは俺がいうのも何だけど、完璧ではなかった…)
下のソースは実際に使ってるものの、処理をかなりパッサリと落として抽出したものです。
public class Place extends MapActivity implements LocationListener,
OnGestureListener,
OnDoubleTapListener{
private boolean mZoom = false;
private boolean mDoubleTap = false;
private boolean mSingleTap = true;
private MapView mMapView;
private MapController mMapController;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.place);
mGDetector = new GestureDetector(this, this);
mMapView = (MapView)findViewById(R.id.mapview);
mMapView.setBuiltInZoomControls(true);
ZoomButtonsController zbc = mMapView.getZoomButtonsController();
zbc.setOnZoomListener(new OnZoomListener() {
@Override
public void onZoom(boolean zoomIn) {
mZoom = true;
if(zoomIn){
mMapController.zoomIn();
} else {
mMapController.zoomOut();
}
}
@Override
public void onVisibilityChanged(boolean visible) {
}
});
// ...略
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
super.dispatchTouchEvent(ev);
return onTouchEvent(ev);
}
@Override
public boolean onDoubleTap(MotionEvent e) {
mDoubleTap = true;
return false;
}
@Override
public boolean onDoubleTapEvent(MotionEvent event) {
if (mDoubleTap) {
mDoubleTap = false;
GeoPoint gp = getGeoPointByPoint((int)event.getX(), (int)event.getY());
GeoPoint cgp = mMapView.getMapCenter();
GeoPoint point = new GeoPoint(
(gp.getLatitudeE6() + cgp.getLatitudeE6()) / 2,
(gp.getLongitudeE6() + cgp.getLongitudeE6()) / 2);
if (mMapController.zoomIn()) {
mMapController.setCenter(point);
}
}
return false;
}
@Override
public boolean onDown(MotionEvent event) {
mSingleTap = false;
return false;
}
@Override
public boolean onSingleTapConfirmed(MotionEvent event) {
mSingleTap = true;
if (mZoom) {
// zoomIn, zoomOutしていたら、処理をしない
mZoom = false;
} else {
GeoPoint gp = getGeoPointByPoint((int)event.getX(), (int)event.getY());
mMapController.animateTo(gp);
}
return false;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
if (mGDetector.onTouchEvent(event)) {
return true;
}
return super.onTouchEvent(event);
}
private GeoPoint getGeoPointByPoint(int x, int y) {
Projection projection = mMapView.getProjection();
return projection.fromPixels(x, y);
}
}
まず、イベントを全部横取りするために、dispatchTouchEventメソッドを設定します。最初にMapActivityが持っているイベント類を全て実行させるために、super.dispatchTouchEventを実行します。その後、実行したいthis.onTouchEventにイベントを渡します。
this.onTouchEventの中で、GestureDetector.onTouchEventをやっておきます。
重要になるのが、onSingleTapConfirmedメソッド。これは簡単にいうと、シングルタップイベントに該当します。シングルタップで実行したい処理をここに書いてしまいます。これでシングルタップイベントは完成っぽく見えるけど、実は違います。あとで書きますが、罠が潜んでます。
次に、onDoubleTapEvent。これはダブルタップイベントという名前だけあって、そういうイベントなんですが、何故かonDoubleTapメソッドの後に数回コールされてしまうので、onDoubleTap内で現在ダブルタップ中というフラグを立てて、onDoubleTapEventでフラグが経っていたら処理するというふうにします。これで、ダブルタップイベントは完成。
これで、GoogleMapをダブルタップしたら、地図は拡大。シングルタップしたところが画面中央に来てハッピーかと思いきや、なんと、ZoomControlをクリックしたら、シングルタップイベントが走ってしまい、ズームコントロールを押した場所が画面中央に来てしまいます。これはどげんかせんといけませんな。
で、onCreateの中でやっているんですが、ZoomButtonsControllerのインスタンスを生成して、ズームボタンを押したときのイベントを自分で定義します。setOnZoomListenerというやつですね。ここで、onZoomイベントに来たら、現在ズーム処理中のフラグを立てます。あとは拡大・縮小の処理を実行します。
onSingleTapConfirmedメソッドで、ズーム中のフラグが立っていたら、処理をしないようにします。そうすれば、シングルタップOK, ダブルタップOK, ズームコントロールOKになります。ふぅ〜、やれやれだぜ。
2010 年 8 月 6 日 金曜日
Nexus OneやDesireではエラーが起きなくなったのに、HT-03AではOut of Memoryによる強制終了が頻発。これをどうやったら解決できるのか?色々と考えたけれど、DDMSを使ってHEAPのメモリ使用量を見たら、圧倒的に画像が占めているぽかったので、Bitmap自体のメモリ使用率を下げること以外に方法はないのだろうと。じゃあ、どうすればいいか?読み込む画像サイズを、BitmapFactiory.decodeStream()で読み込むタイミングで大きすぎる画像は小さくして読み込んでやれば、使用するメモリ量は少なくて済むだろうと。なんでも、Xperiaで取った写真を読み込んだだけでOut of Memoryが発生したりするから、サイズを最適化したらいいというのを見て、ネット上の画像でもできるだろうと判断。通信は複数回になっているのかもしれんが、まぁわからん。
しかし、やってみたんだが、思ったよりもいい結果が得られなかった。
// AsyncTask.doInBackgroundの中。引数はurls
HttpGet httpRequest = new HttpGet(urls[0]);
HttpClient httpclient = new DefaultHttpClient();
HttpResponse response = (HttpResponse) httpclient.execute(httpRequest);
HttpEntity entity = response.getEntity();
BufferedHttpEntity bufHttpEntity = new BufferedHttpEntity(entity);
InputStream is = bufHttpEntity.getContent();
BitmapFactory.Options opts = new BitmapFactory.Options();
opts.inJustDecodeBounds = true;
// 画像の大きさだけ取得
image = BitmapFactory.decodeStream(is, null, opts);
is.close();
Log.d(TAG, "before opts.outWidth = " + String.valueOf(opts.outWidth));
Log.d(TAG, "before opts.outHeight = " + String.valueOf(opts.outHeight));
// DisplayMetricsをActivity側でキャッシュしておいたと仮定
DisplayMetrics metrics = MetrixCache.get();
if (metrics != null) {
Log.d(TAG, "run metrics.widthPixels = " + String.valueOf(metrics.widthPixels));
Log.d(TAG, "run metrics.heightPixels = " + String.valueOf(metrics.heightPixels));
int zoomWidth = (int)Math.floor(opts.outWidth / metrics.widthPixels);
int zoomHeight = (int)Math.floor(opts.outHeight / metrics.heightPixels);
opts.inSampleSize = Math.max(zoomWidth, zoomHeight);
}
// 今度は画像を読み込む
opts.inJustDecodeBounds = false;
is = bufHttpEntity.getContent();
// 最適な画像サイズで読み込む
image = BitmapFactory.decodeStream(is, null, opts);
is.close();
return image;
opts.inSampleSizeはintで指定するため、値が2になった途端に、縦横比は半分に(画像サイズは1/4)になるので、やけに荒くなる。しかもぎりぎりで2に届かないものはオリジナルで読み込まれるので、メモリ消費もでかくて役にたたんと判断。判断というか、HT-03Aの強制終了が起きたから、しゃーない。あと、inputStreamを2度使うから、やっぱり2回通信が発生しているのかもしれない。確かめてないけど。とにかく、別の手段を考える。
いったん画像を読み込んで、機種の解像度に合わせて画像をコピーし直す方式にしてみた。
HttpGet httpRequest = new HttpGet(urls[0]);
HttpClient httpclient = new DefaultHttpClient();
HttpResponse response = (HttpResponse) httpclient.execute(httpRequest);
HttpEntity entity = response.getEntity();
BufferedHttpEntity bufHttpEntity = new BufferedHttpEntity(entity);
InputStream is = bufHttpEntity.getContent();
image = BitmapFactory.decodeStream(is);
is.close();
DisplayMetrics metrics = MetrixCache.get();
if (metrics != null) {
// 画像の大きさを最適化する
Log.d(TAG, "before image.getWidth() = " + String.valueOf(image.getWidth()));
Log.d(TAG, "before image.getHeight() = " + String.valueOf(image.getHeight()));
float s_x = (float)image.getWidth() / (float)metrics.widthPixels;
float s_y = (float)image.getHeight() / (float)metrics.heightPixels;
float scale = Math.max(s_x, s_y);
if (scale > 1){
int new_x = (int)(image.getWidth() / scale);
int new_y = (int)(image.getHeight() / scale);
Log.d(TAG, "new_x = " + String.valueOf(new_x));
Log.d(TAG, "new_y = " + String.valueOf(new_y));
image = Bitmap.createScaledBitmap(
image,
new_x,
new_y,
false);
Log.d(TAG, "after image.getWidth() = " + String.valueOf(image.getWidth()));
Log.d(TAG, "after image.getHeight() = " + String.valueOf(image.getHeight()));
}
}
return image;
あれだけ頻発していたHT-03AでのOut of Memoryが影を潜めた。さらに、画面の解像度に合わせて画像サイズを最適化するため、そんなに画像も汚くはない。この方式ならば、フォトフレームなどで使うにしても、きれいな画像が表示できそうだ。今のところは、これが一番よさそうなので、採用!!
コメント
コメントはありません