Canvas#drawTextするときのメモ
どの値が何を表しているかよく忘れるのでメモ
原点
Canvas#drawText() に指定する座標はここになる。
高さ
Top | 一番上 |
Ascent | 文字の上限 |
Leading | 原点のyと同じ位置 |
Descent | 文字の下限 |
Bottom | 一番下 |
文字のそれぞれのy位置が取得できる。
Paint#getFontMetrics()で取得できるFontMetrixから、フィールドの値を取り出せる。
bottom - topをすれば高さが取得できる。
値(追記)
Paint.setTextSize(:int)に96を設定した場合のそれぞれの値
leadingは必ず0になる
他はleadingからの相対的な位置の差
top | -100.59375 |
ascent | -89.109375 |
leading | 0.0 |
descent | 22.640625 |
bottom | 26.015625 |
ブラウザからlogcatを見る(アップデートしました)
前回記事
ブラウザからlogcatを見る - 明日の鍵 http://d.hatena.ne.jp/tomorrowkey/20110514/1305379371
前回作ったブラウザから見るlogcatをデ部にて発表しました。
ブラウザからlogcatを見る http://d.hatena.ne.jp/tomorrowkey/files/20111001_slide.pdf
発表ついでに以下を変更しました。
- 外観を整えました。
- 普通のSocketにも対応しました。
実行ファイルたち
・Androidアプリ(Server) http://tomorrowkey.googlecode.com/svn/trunk/LogcatOnBrowser/LogcatSocketServer/bin/LogcatSocketServer.apk ・jarファイル(Client) http://tomorrowkey.googlecode.com/svn/trunk/LogcatOnBrowser/LogcatSocketClient/jar/wifilogcat.jar ・htmlファイル(Client) http://tomorrowkey.googlecode.com/svn/trunk/LogcatOnBrowser/Client/logcat.html
WebSocketを使ったバージョンはそのうち使えなくなります。
すでにchromeでは動かなくなりました。
現在動作確認がとれているのはMac版Safariのみです。
Socketで接続する場合、引数にIPアドレスとポート番号を指定してあげる必要があります。
コマンド例は以下です。
java -jar wifilogcat.jar -ipaddress 192.168.0.2 -port 10008
コマンドラインで実行なので、grepできたりcoloredlogcatが使えたりしてさらにべんり!!
Androidで簡単にWi-Fiに接続する方法
Wi-Fiの設定ってめんどうですね。
長く複雑なパスワードをandroidで入力するのは、正直ストレスです。
ZXingのソースコードを読んでいたのですが、Wi-Fiに接続するための機能がついていました。
ZXingはバーコードをエンコード/デコードするためのライブラリです。
zxing - Multi-format 1D/2D barcode image processing library with clients for Android, Java - Google Project Hosting http://code.google.com/p/zxing/
ZXingはさまざまなプラットフォーム向けにクライアントが作られています。
Android向けにはQRコードスキャナーという名前で公開されています。
QRコードスキャナー - Android マーケット https://market.android.com/details?id=com.google.zxing.client.android&feature=search_result
Wi-Fiに接続するためのQRコードは、このサイトで作れます。
もちろんこれもZXingのプロジェクトの一部です。
QR Code Generator http://zxing.appspot.com/generator/
ここでQRコードを作って印刷しておくと便利ですね。
すごいよlibandrotranslation
libandrotranslationというAndroidアプリの翻訳を助けるライブラリがあります。
libandrotranslation - Android user translation library. - Google Project Hosting http://code.google.com/p/libandrotranslation/
導入方法についてはこちらをどうぞ
Description - libandrotranslation - LibAndroTranslationの説明 - Android user translation library. - Google Project Hosting http://code.google.com/p/libandrotranslation/wiki/Description
ユーザが翻訳をして、翻訳したstring.xmlファイルをデベロッパにメールで送られてくる仕組みです。
導入はすごく簡単でした。
こんな感じで送られてきます。
私はこれを使ってスクリーンキャプチャショートカットの翻訳を募集していました。
成果はというと、1ヶ月間で65件のメールが来ました。
- アラビア語 3件
- ドイツ語 5件
- 英語 14件
- スペイン語 4件
- フランス語 1件
- ハンガリー語 2件
- インドネシア語 1件
- イタリア語 2件
- 日本語 1件
- 韓国語 17件
- オランダ語 2件
- ノルウェー語 1件
- 中国語 1件
韓国語が多いのはアプリの特性でGalaxySのみ対応しているからです。
これを使ってスクリーンキャプチャーショートカットのアップデートをしました。
Version 1.2.0 - Androidアプリ スクリーンキャプチャーショートカット https://sites.google.com/site/screencaptureshortcut/what-s-new/version120
機械翻訳ではなく人力翻訳を1ヶ月でこれだけ集まったのはすごいです!
端末の振りを検知する
加速度を使って端末の振りを検知します。
簡単そうだけど、考えてみると難しいでした。
端末が振られた時に、加速度の平均値と、加速度の差が大きくなる事を利用して振りを検知しています。
ソースコード
package jp.tomorrowkey.android.shakelistener; import java.util.Arrays; import java.util.LinkedList; import android.hardware.Sensor; import android.hardware.SensorEvent; import android.hardware.SensorEventListener; import android.hardware.SensorManager; /** * 端末が振られたことを検知するクラス * * @author tomorrowkey at gmail.com */ public class ShakeListener implements SensorEventListener { public static final String LOG_TAG = ShakeListener.class.getSimpleName(); /** * X方向に振られた時に{@link ShakeListener.OnShakeListener#onShaked(int)}でこのフラグが立ちます */ public static final int DIRECTION_X = 0x0001; /** * Y方向に振られた時に{@link ShakeListener.OnShakeListener#onShaked(int)}でこのフラグが立ちます */ public static final int DIRECTION_Y = 0x0010; /** * Z方向に振られた時に{@link ShakeListener.OnShakeListener#onShaked(int)}でこのフラグが立ちます */ public static final int DIRECTION_Z = 0x0100; /** * 加速度の判定をするために最大いくつのデータを保持するか設定します */ private static final int SAMPLING_SIZE = 50; /** * 端末が振られた事を検知するコールバックインターフェイスです */ public interface OnShakeListener { /** * 端末が振られた時に呼ばれるメソッドです * * @param direction 端末が振られた方向を表します * @see ShakeListener#DIRECTION_X * @see ShakeListener#DIRECTION_Y * @see ShakeListener#DIRECTION_Z */ void onShaked(int direction); } /** * コールバッククラス */ private OnShakeListener mOnShakeListener = null; /** * 振りが検知されなくてもコールバックを返すかを保持します */ private boolean mIsCallbackAlways = false; /** * 加速度の平均値と、絶対値の差がどれだけ差があると振られた事にするか保持します */ private int mDifferenceThreshold = 500; /** * 加速度の値を保持しておくリスト */ private LinkedList<float[]> mAccelerometerList; /** * 加速度の値の合計<br> * サイズはx,y,z分で3つ<br> * 加速度センサーの反応の度に計算するとコストが高いのでメモリに保持しておく */ private float[] mAccelerometerSamplingSums; /** * コンストラクタ */ public ShakeListener() { mAccelerometerList = new LinkedList<float[]>(); mAccelerometerSamplingSums = new float[] { 0f, 0f, 0f }; } /** * センサーを登録します<br> * センサー検知の頻度は{@link SensorManager#SENSOR_DELAY_FASTEST } が設定されます。 * 振りを検知した時のみにコールバックメソッドが呼び出されます。 * * @param sensorManager * @param l コールバックリスナ */ public void registerListener(SensorManager sensorManager, OnShakeListener l) { registerListener(sensorManager, l, SensorManager.SENSOR_DELAY_FASTEST, false); } /** * センサーを登録します<br> * センサー検知の頻度は{@link SensorManager#SENSOR_DELAY_FASTEST } が設定されます。 * * @param isCallbackAlways true を設定した場合、振りを検知しない場合でもコールバックメソッドを呼び出します。false * を設定した場合、振りを検知した場合のみコールバックメソッドを呼び出します。 * @param sensorManager * @param l コールバックリスナ */ public void registerListener(SensorManager sensorManager, OnShakeListener l, boolean isCallbackAlways) { registerListener(sensorManager, l, SensorManager.SENSOR_DELAY_FASTEST, isCallbackAlways); } /** * センサーを登録します<br> * 振りを検知した時のみにコールバックメソッドが呼び出されます。 * * @param sensorManager * @param l コールバックリスナ * @param rate センサー検知の頻度を設定します。{@link SensorManager#SENSOR_DELAY_NORMAL} * {@link SensorManager#SENSOR_DELAY_UI} * {@link SensorManager#SENSOR_DELAY_GAME} * {@link SensorManager#SENSOR_DELAY_FASTEST} */ public void registerListener(SensorManager sensorManager, OnShakeListener l, int rate) { registerListener(sensorManager, l, rate, false); } /** * センサーを登録します。 * * @param sensorManager * @param l コールバックリスナ * @param rate センサー検知の頻度を設定します。{@link SensorManager#SENSOR_DELAY_NORMAL} * {@link SensorManager#SENSOR_DELAY_UI} * {@link SensorManager#SENSOR_DELAY_GAME} * {@link SensorManager#SENSOR_DELAY_FASTEST} * @param isCallbackAlways true を設定した場合、振りを検知しない場合でもコールバックメソッドを呼び出します。 false * を設定した場合、振りを検知した場合のみコールバックメソッドを呼び出します。 */ public void registerListener(SensorManager sensorManager, OnShakeListener l, int rate, boolean isCallbackAlways) { if (l == null) throw new IllegalArgumentException("OnShakeListener is required"); mOnShakeListener = l; mIsCallbackAlways = isCallbackAlways; mAccelerometerList.clear(); mAccelerometerSamplingSums = new float[] { 0f, 0f, 0f }; sensorManager.registerListener(this, sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER), rate); } /** * センサー登録を解除します。 * * @param sensorManager */ public void unregisterListener(SensorManager sensorManager) { sensorManager.unregisterListener(this); mOnShakeListener = null; } /** * 現在センサーが登録されているか判定します。 * * @return true の場合、登録されている。falseの場合、登録されていない。 */ public boolean isRegisteredListener() { return mOnShakeListener != null; } /** * 加速度の平均値と、絶対値の差がどれだけ差があると振られた事にするかを設定します<br> * この値を設定することで振りの検知の強弱を設定できます。 <br> * 初期値は500です。<br> * 最適な値は端末または使う人によって異なります * * @param differenceThreshold */ public void setDifferenceThreshold(int differenceThreshold) { mDifferenceThreshold = differenceThreshold; } // @Override public void onAccuracyChanged(Sensor sensor, int accuracy) { } // @Override public void onSensorChanged(SensorEvent event) { mAccelerometerSamplingSums[0] += event.values[SensorManager.DATA_X]; mAccelerometerSamplingSums[1] += event.values[SensorManager.DATA_Y]; mAccelerometerSamplingSums[2] += event.values[SensorManager.DATA_Z]; mAccelerometerList.add(Arrays.copyOf(event.values, event.values.length)); if (mAccelerometerList.size() > SAMPLING_SIZE) { float[] removedValues = mAccelerometerList.removeFirst(); mAccelerometerSamplingSums[0] -= removedValues[SensorManager.DATA_X]; mAccelerometerSamplingSums[1] -= removedValues[SensorManager.DATA_Y]; mAccelerometerSamplingSums[2] -= removedValues[SensorManager.DATA_Z]; } float xAverage = mAccelerometerSamplingSums[0] / mAccelerometerList.size(); float yAverage = mAccelerometerSamplingSums[1] / mAccelerometerList.size(); float zAverage = mAccelerometerSamplingSums[2] / mAccelerometerList.size(); float xAbsTotal = 0; float yAbsTotal = 0; float zAbsTotal = 0; for (int i = 0; i < mAccelerometerList.size(); i++) { float[] values = mAccelerometerList.get(i); xAbsTotal += Math.abs(values[SensorManager.DATA_X] - xAverage); yAbsTotal += Math.abs(values[SensorManager.DATA_Y] - yAverage); zAbsTotal += Math.abs(values[SensorManager.DATA_Z] - zAverage); } int direction = 0; if (xAbsTotal > mDifferenceThreshold) direction |= DIRECTION_X; if (yAbsTotal > mDifferenceThreshold) direction |= DIRECTION_Y; if (zAbsTotal > mDifferenceThreshold) direction |= DIRECTION_Z; if (mOnShakeListener != null && (mIsCallbackAlways || direction != 0)) mOnShakeListener.onShaked(direction); } }
フッタにボタンを表示する
ボタン2つ
この画面がどういう構成になっているかソースコードを読む。
uninstall_confirm.xml
android.git.kernel.org Git - platform/packages/apps/PackageInstaller.git/blob - res/layout/uninstall_confirm.xml
<LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_alignParentBottom="true" android:orientation="horizontal" style="@android:style/ButtonBar" > <Button android:id="@+id/ok_button" android:layout_width="0dip" android:layout_height="wrap_content" android:layout_weight="1" android:text="@string/ok" /> <Button android:id="@+id/cancel_button" android:layout_width="0dip" android:layout_height="wrap_content" android:layout_weight="1" android:text="@string/cancel" /> </LinearLayout>
layout_widthに0dp、layout_weightに1を設定することで、ボタン大きさが画面の半分ずつになるようにしている。
フッタのViewGroupにLinearLayoutを使用しており、styleに@android:style/ButtonBarを設定している。
styles.xml
android.git.kernel.org Git - platform/frameworks/base.git/blob - core/res/res/values/styles.xml
<style name="ButtonBar"> <item name="android:paddingTop">5dip</item> <item name="android:paddingLeft">4dip</item> <item name="android:paddingRight">4dip</item> <item name="android:paddingBottom">1dip</item> <item name="android:background">@android:drawable/bottom_bar</item> </style>
ボタンがセンタリングされるようパディングを設定している。
背景色の灰色は@android:drawable/bottom_barで設定しているようだ。
bottom_bar.png
android.git.kernel.org Git - platform/frameworks/base.git/blob - core/res/res/drawable-hdpi/bottom_bar.png
9patchかxmlだとおもいきや普通のpng画像だった。
hdpi/mdpi/ldpiが用意されている。
一番上に境界線を表す明るい色が使われ、縦にグラデーションがかかっている。
高さが伸びるとグラデーションが荒くなるが、横に伸びる分には問題ないので、landscapeとportlateどちらでも使えそうだ。
ボタン1つ
この画面がどういう構成になっているかソースコードを読む。
android.git.kernel.org Git - platform/packages/providers/GoogleSubscribedFeedsProvider.git/blob - res/layout/manage_accounts_screen.xml
<LinearLayout android:layout_width="fill_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:background="@android:drawable/bottom_bar"> <View android:layout_width="0dip" android:layout_height="fill_parent" android:layout_weight="1"/> <Button android:id="@+id/add_account_button" android:layout_width="0dip" android:layout_height="wrap_content" android:layout_weight="2" android:layout_marginTop="5dip" android:text="@string/add_account_label" /> <View android:layout_width="0dip" android:layout_height="fill_parent" android:layout_weight="1"/> </LinearLayout>
Viewをおく事で適度な大きさになるようにしているようだ。
左右のViewのweightを1に、中央のボタンのweightを2にすることで中央に画面半分分のボタンを表示している。
左右のパディングは必要ないので、styleを使わずにボタンにマージンを設定して余白を調節している。
背景色はボタン2つの場合と同じで@android:drawable/bottom_barを使用している。
ボタン1つ(違うパターン)
以前、[twitter:@R246] さんに教えていただいた書き方
<LinearLayout android:orientation="horizontal" android:background="@android:drawable/bottom_bar" android:gravity="center_horizontal" android:weightSum="2" android:layout_width="match_parent" android:layout_height="wrap_content"> <Button android:id="@+id/doneButton" android:text="@string/done" android:layout_weight="1" android:layout_marginTop="5dip" android:layout_width="0dip" android:layout_height="wrap_content" /> </LinearLayout>
weightSumに2を指定することでLinerLayoutの全体のweightが2になり、ボタンのweightに1を指定することで、ボタンの大きさが画面の半分になる。
さらにgravityにcenter_horizontalを指定するとボタンがセンタリングされる。
このほうがViewが少なくなり、すっきり。
所感
やっと書き方覚えたぞ