すごいよ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が少なくなり、すっきり。
所感
やっと書き方覚えたぞ
ブラウザからlogcatを見る
こちらが最新です ブラウザからlogcatを見る(アップデートしました) - 明日の鍵 http://d.hatena.ne.jp/tomorrowkey/20111001/1317451235
充電していない状態でlogcatを確認したくて、どうすればいいか悩んでました。
思いついたのが、表示するアプリを作ればいいんじゃね!って事。
しかし、端末に表示するのでは画面が小さすぎる、読みづらいので
Socketでパソコンとつないで表示することに
ついでに気になってたWebSocketを使うようにすれば、新しい事もできて一石二鳥!わーい!
構成
通常のSocketについて調べる
Socketはさんざん触ったことあるので、大丈夫。
勉強したい人はTECHSCOREがオススメ
ソケットネットワークプログラミング-TECHSCORE- http://legacy.techscore.com/tech/J2SE/Network/2.html
そういや本も一冊買ってた
Socketの他にStreamについても勉強になった
- 作者: Kenneth L. Calvert,Michael J. Donahoo,小高知宏
- 出版社/メーカー: オーム社
- 発売日: 2003/05/01
- メディア: 単行本
- 購入: 2人 クリック: 36回
- この商品を含むブログ (10件) を見る
WebSocketを調べる
The WebSocket API http://www.w3.org/TR/2011/WD-websockets-20110419/
WebSocketの一次情報
draftと書かれているので、仕様が変わるかもしれないってヤツですね。
英語分からん。
W3C - 『The WebSocket API』日本語訳 - HTML5.JP http://www.html5.jp/trans/w3c_websockets.html
日本語訳バージョン
日本語分からん。
WebSocket - Wikipedia http://ja.wikipedia.org/wiki/WebSocket
Wikipedia
通常のSocketみたく、繋げたら何してもおっけーってわけではなく
ハンドシェイクというやりとりが必要という情報が
WebSocketのプロトコル ― ありえるえりあ http://dev.ariel-networks.com/Members/inoue/websocket/
1年くらい前の記事だけど
WebSocketについてまとまってる
ハンドシェイクのやり方も丁寧に解説してある
意味あんのかって突っ込んでるけどw
http://www.google.co.jp/codesearch/p?hl=ja#CwgqP48SYDw/trunk/shared/J2SE/jWebSocketServer/org/jwebsocket/netty/engines/NettyEngineHandler.java
google code searchでjavaでのサーバ側の実装を見つけた
constructHandShakeResponseというメソッドでごにょごにょしてる
一番参考になった。コードが一番分かりやすい。
jWebSocket - The Open Source Java WebSocket Server http://jwebsocket.org/
どうやらこれのソースみたいだ
HTML5 WebSockets Tutorial http://www.tutorialspoint.com/html5/html5_websocket.htm
クライアントサイドはここを参照
簡単すなぁ…
作った
適当な実装だけど動くものはできた
試すには
http://tomorrowkey.googlecode.com/svn/tags/LogcatSocketServer/1.0.0/bin/LogcatSocketServer.apk
このアプリをAndroid端末にインストール(※クリックするとapkファイルがダウンロードされます)
アプリ起動するとIPアドレスとポート番号が表示されます。*1
http://tomorrowkey.googlecode.com/svn/trunk/LogcatOnBrowser/Client/logcat.html
同じLAN内につながってるパソコンにこのHTMLを保存する
ブラウザから保存したhtmlファイルにアクセスする*2
IPアドレスとポート番号を入れるテキストがあるので、アプリに表示されているものを入力
connectボタンを押せばlogcatが読み込まれます
動くようには作りましたが、けっこう適当実装です。
自己責任で実行してください。責任は一切負いません。
やってみて
javascriptとかhtmlとか普段ぜんぜんやらないから楽しかった!
今は情報がたくさんあって、知識がなくてもなんとなくで作れてしまうね
なんか実装の悪いところあったら指摘ください。
WebSocketはすごく楽しい。
可能性を感じる。
9patchを覚えよう!
9patchを使おう!
そこで9patchの出番です。
9patchは、画像よりモノが大きかった場合(今回は画像よりボタンが大きかった)
引き伸ばす部分を指定することで、画像の崩れを無くす事ができるのです!
Fireworksでいう所の9スライスという機能に似ています。
先ほどのボタン画像に9patchの処理を施してみます。
できました!
上側と左側に黒い1ピクセルが見えますでしょうか?
ちょっと拡大します。
拡大したのでボケてますが、これなら分かりますね。
9patchは上下左右に、アルファ無しの黒(#000000)の印を付けることで伸ばす部分を指定します。
上下左右それぞれ意味があります。
上と左の印は必須です。
下と右の印は省略することができます。
下と右の印の説明は後でやります。
図解9patch
さきほどの画像を解説すると
上に指定したピクセルの列(赤)が
横に伸びる時にコピーされます。
左に指定したピクセルの行(青)が
縦に伸びる時にコピーされます。
こうやって角丸部分が拡大されないようにします。
端末に設定してみましょう。
綺麗に角丸が表示されました。
さらに、どの部分にコピーされているのか確認したいため、こんな画像も用意してみました。
端末で表示してみましょう。
なるほどー、さっきのサンプルみたいにコピーされました。
さらに挙動を調べる
通常9patchの上と左の指定は1ピクセルだけで済みますが、実際には複数ピクセル指定することができます。
端末で表示させて挙動をたしかめてみましょう。
たとえば2ピクセル指定した場合はどうなるんでしょう。
端末で表示して見てみます。
半分ずつコピーされました。
おもしろいですね。
次はこんな画像はどうでしょう。
各々近いところを補うんですかね。
さらに活用する
挙動が分かれば、それを利用します。
この画像を使って
こんな風に表示したり*1
この画像を使って
こんな風に表示したり*2
グラデーションなら左側をほとんど塗っちゃってもいいんじゃないでしょうか
段差が見えちゃってますが、多少の拡大ならごまかせます。
ベタ塗りなのにこういう9patchの指定の仕方をしている人がいて、本当に理解しているのかなーって心配になります…。
ちゃんと表示はされるんだけどね…。
文字が変な位置に表示される
ボタンにはラベルが必要です。
ボタンを押した時にどんな機能が働くのかを、ラベルの文字によって表現しましょう。
今回は"こんにちは!"というラベルにします。
きっと押したら"ぽぽぽぽーん!"と音が鳴るに違いありません。
さっき作ったボタンにラベルを表示するよう変更してみます。
こんにちは!って表示されたけど、位置がおかしいです。
下に表示しているデフォルトのボタンではセンタリングされているのに、今回作ったボタンは変な位置に表示されています。
さらに9patch
これも9patchの機能を使って解決します。
上と左の印を使う事によって伸ばす部分を指定しましたが、
下と右の印を使う事によってコンテンツの位置を指定することができます。
ボタンの画像に対してコンテンツの位置を指定してみます。
できました!
例によって拡大します。
下と右に黒いピクセルがたくさんあるかと思います。
解説すると
下の黒いピクセルの列(赤)と
右の黒いピクセルの行(青)の
交わった場所(紫)がコンテンツの入る場所になります。
今回だと文字列の入る場所です。
端末で表示してみましょう。
ちゃんとセンタリングされました。
さっきの赤/青の画像でも表示してみましょう。
ちゃんと紫のところにラベルが表示されています。
さらに挙動をしらべる
調べようかと思ったのですが
何もできません。
下と右はこれですべてみたいです。
例えばこんな画像を用意して端末で表示してみようかと思ったのですが
eclipseさんに怒られてしまい、コンパイルが通りません。
9patchが苦手な画像
9patchには苦手な画像があります。
たとえば
ストライプ*3
グラデーション
例に出しましたが、段差が見えてしまうのでオススメできません。
その他連続した柄*4
他にもいろいろパターンはあると思うので、想像しながら作りましょう。
AndroidSDKの中に入ってるdraw9patchというツールを使うと、どういう風に表示されるか見ながら9patchのマークを付ける事ができます。
慣れないうちはそれを使うのも手でしょう。*5
さらに9patchを理解する
読むだけだと完全に理解はできません。
これ以上は実際に作って、端末に表示させてみて、挙動を自分で確かめてみるといいと思います。
その他9patch記事
Draw 9-patch | Android Developers http://developer.android.com/guide/developing/tools/draw9patch.html 公式の9patch解説
Draw 9-patch - ソフトウェア技術ドキュメントを勝手に翻訳 https://sites.google.com/a/techdoctranslator.com/jp/android/developing/tools/draw9patch 公式の9patch解説の日本語翻訳
9-patch - 3156note https://sites.google.com/site/3156note/home/android/9-patch draw9patchの使い方を詳しく解説
チュートリアル:9patchで画像を作る « Tech Booster http://techbooster.jpn.org/andriod/environment/3996/ draw9patchの使い方を詳しく解説
draw9patch で NinePatch をつくる方法 - Hacking My Way 〜 itogのhack日記 http://d.hatena.ne.jp/itog/20100209/1265684439 draw9patchの使い方を詳しく解説
まとめ
- 単純に拡大されたくない時は9patchを使う。
- コピーされる場所を指定するには上と左に9patchのマークをつける。
- 中に何かコンテンツを入れる時は下と右に9patchのマークをつける。
- どんな画像になるか想像しながら作る。
*1:xmlで指定することにより、ボタンとアイコン(ハート、星)を分離することができます。分離できた方がデザイナさんもプログラマーさんも幸せなので、そちらを使った方がいいです。
*2:xmlで指定することにより、ボタンとアイコン(ハート、星)を分離することができます。分離できた方がデザイナさんもプログラマーさんも幸せなので、そちらを使った方がいいです。
*3:9patchではなく、これを使うと解決できるかもしれません http://developer.android.com/guide/topics/resources/drawable-resource.html#Bitmap
*4:9patchではなく、これを使うと解決できるかもしれません http://developer.android.com/guide/topics/resources/drawable-resource.html#Bitmap
キーリピートを実装する
Buttonクラスを拡張して、長押しされている場合クリック動作を呼び出すようにします。
RepeatButton.java
import android.content.Context; import android.os.Handler; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.View; import android.view.View.OnLongClickListener; import android.widget.Button; public class RepeatButton extends Button implements OnLongClickListener { /** * 連続してボタンを押す間隔(ms) */ private static final int REPEAT_INTERVAL = 100; /** * 連打フラグ */ private boolean isContinue = true; /** * ハンドラ */ private Handler handler; public RepeatButton(Context context, AttributeSet attrs) { super(context, attrs); setOnLongClickListener(this); handler = new Handler(); } @Override public boolean onTouchEvent(MotionEvent event) { super.onTouchEvent(event); // キーから指が離されたら連打をオフにする if (event.getAction() == MotionEvent.ACTION_UP) { isContinue = false; } return true; } @Override public boolean onLongClick(View v) { isContinue = true; // 長押しをきっかけに連打を開始する handler.post(repeatRunnable); return true; } Runnable repeatRunnable = new Runnable() { @Override public void run() { // 連打フラグをみて処理を続けるか判断する if (!isContinue) { return; } // クリック処理を実行する performClick(); // 連打間隔を過ぎた後に、再び自分を呼び出す handler.postDelayed(this, REPEAT_INTERVAL); } }; }
使用例
こんな感じで使えばNumberPicker的に使えます
MainActivity.java
import android.app.Activity; import android.os.Bundle; import android.view.View; import android.view.View.OnClickListener; import android.widget.TextView; public class MainActivity extends Activity implements OnClickListener { private TextView txtNumber; private RepeatButton btnIncrement; private RepeatButton btnDecrement; private int number = 0; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); txtNumber = (TextView)findViewById(R.id.txtNumber); txtNumber.setText(String.valueOf(number)); btnIncrement = (RepeatButton)findViewById(R.id.btnIncrement); btnIncrement.setOnClickListener(this); btnDecrement = (RepeatButton)findViewById(R.id.btnDecrement); btnDecrement.setOnClickListener(this); } @Override public void onClick(View v) { switch (v.getId()) { case R.id.btnIncrement: { increment(); break; } case R.id.btnDecrement: { decrement(); break; } } } private void increment() { number++; txtNumber.setText(String.valueOf(number)); } private void decrement() { number--; txtNumber.setText(String.valueOf(number)); } }
main.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:gravity="center_vertical|center_horizontal" android:layout_width="fill_parent" android:layout_height="fill_parent"> <view class="jp.tomorrowkey.android.repeatpushbutton.RepeatButton" android:id="@+id/btnIncrement" android:text="Increment" android:layout_width="wrap_content" android:layout_height="wrap_content" /> <TextView android:id="@+id/txtNumber" android:text="0" android:textSize="32sp" android:layout_width="wrap_content" android:layout_height="wrap_content" /> <view class="jp.tomorrowkey.android.repeatpushbutton.RepeatButton" android:id="@+id/btnDecrement" android:text="Decrement" android:layout_width="wrap_content" android:layout_height="wrap_content" /> </LinearLayout>
独自Viewの定義の仕方
独自ViewのXMLの定義の仕方は2つあります
viewタグを使う場合
<view class="jp.tomorrowkey.android.repeatpushbutton.RepeatButton" android:layout_width="wrap_content" android:layout_height="wrap_content" />
Androidで縦書きを実現する
androidのTextViewは縦書きには対応していません。
縦書きを実現するためには自分で実装するしかありません。
縦書きについてさっぱり知らない状態から実装しました。
twitterでのやりとり
Togetter - 「Android縦書き」 http://togetter.com/li/92001
誤っている箇所があれば指摘お願いします!
完成はこんな感じです。
参考にしたサイト
字体 - Wikipedia http://ja.wikipedia.org/wiki/%E5%AD%97%E4%BD%93
グリフがフォント1文字分、グリフがたくさん集まってフォントという単位になる
禁則処理 - Wikipedia http://ja.wikipedia.org/wiki/%E7%A6%81%E5%89%87%E5%87%A6%E7%90%86
行の最初に「、」「。」などの文字が配置される場合、前行の最後に表示するべきという処理
*1
ハイフネーションとは【hyphenation】 - 意味/解説/説明/定義 : IT用語辞典 http://e-words.jp/w/E3838FE382A4E38395E3838DE383BCE382B7E383A7E383B3.html
英語版禁則処理みたいなもの
行をまたいで英単語を描画する場合ハイフンを使って繋ぐ
*2
ルビ - Wikipedia http://ja.wikipedia.org/wiki/%E3%83%AB%E3%83%93
ふりがなに使うルビ
*3
[SOLVED] Android: Using Custom Fonts « RussenReaktor's Weblog http://russenreaktor.wordpress.com/2010/04/29/solved-android-using-custom-fonts/
自分で用意したフォントを使う例
assetsには1MBまでしか入りません。この例ではassetsにフォントを入れていますが、これは英字フォントのみだからこそできることです。
日本語のフォントが入っていてると1MBなんて余裕で超えちゃうので別途SDカードに入れてあげる必要があります。
他のAndroid縦書きアプリでは大抵アプリインストール後にダウンロードする仕組みになっています。
テキストの描画(FontMetrics) - Android Wiki* http://wikiwiki.jp/android/?%A5%C6%A5%AD%A5%B9%A5%C8%A4%CE%C9%C1%B2%E8(FontMetrics)
FontMetrixについて
Canvas.drawText()ではbaseLineを基準に描かれます。
簡易禁則入ってるソース - inuchin104がiPhoneと格闘したメモ http://d.hatena.ne.jp/inuchin104/20110121
ちゃんと読めていませんが、
[twitter:@inuchin] さんの作ったDirectXを使ったiPhone用縦書きコードらしいです。
ルビと簡易禁則処理がついているそうです。
Typeface | Android Developers http://developer.android.com/reference/android/graphics/Typeface.html
フォントのクラスです。
Paint | Android Developers http://developer.android.com/reference/android/graphics/Paint.html
getFontから始まるメソッドを見ておくといいです。
基本的な作り
独自Viewを作り、onDrawでCanvas#drawText()を使って文字を縦に並べていきます。
ひらがなや、漢字などのだいたいの文字の表示は大丈夫ですが、横書き用フォントなので「ー」や「、」や「ぁ」などを使った場合表示が狂った様に見えます。特別な小文字や記号などには別途設定を設けて位置を調整するようにします。
横フォント問題の解決法は縦書きビューワを作られている[twitter:@2SC1815J] さんにもご意見頂きました。
実装
独自フォントのロード
Typeface typeface = Typeface.createFromFile(FONT_PATH); Paintm paint = new Paint(Paint.ANTI_ALIAS_FLAG); paint.setTextSize(FONT_SIZE); paint.setColor(Color.BLACK); paint.setTypeface(typeface); canvas.drawText("こんにちは!", x, y, paint);
このコードを使えば独自フォントを使った文字列を表示する事ができます。
フォントはIPA明朝を使用しました。
IPAフォントのダウンロード || OSS iPedia http://ossipedia.ipa.go.jp/ipafont/index.html
こんにちはの文字は横に表示されていますが、縦書きにするために1文字ずつ描画します。
文字サイズ
テキストを縦に並べるためには1文字のサイズを取得する必要があります。
文字サイズはPaint#getFontSpacing()でピクセルサイズを取得する事ができます。
これを使えば固定ピッチで表示する事が可能です。
行間を1文字分、最初の行を1文字落とす設定で走れメロスの冒頭を表示してみました。
この状態でもけっこう様になってます。
しかし「、」「。」「っ」の位置がおかしいです。
横用フォントのため、位置がおかしく表示されます。
文字位置を調整する
文字位置調整の対象はだいたい思いつくもので
- 記号
- ひらがな小文字
- カタカナ小文字
- アルファベット半角大文字
- アルファベット半角小文字
- アルファベット全角大文字
- アルファベット全角小文字
などがあります。
ひらがな・カタカナ小文字については位置の調整でいいですが、
アルファベット・記号については90度回転する必要があります。
管理が楽になるように設定用のクラスを作っておいてexcelで生成できるようにします。
public class CharSetting { /** * 文字 */ public final String charcter; /** * 回転角度 */ public final float angle; /** * xの差分 * Paint#getFontSpacing() * xが足される * -0.5fが設定された場合、1/2文字分左にずれる */ public final float x; /** * xの差分 * Paint#getFontSpacing() * yが足される * -0.5fが設定された場合、1/2文字分上にずれる */ public final float y; public CharSetting(String charcter, float angle, float x, float y) { super(); this.charcter = charcter; this.angle = angle; this.x = x; this.y = y; } public static final CharSetting[] settings = { new CharSettings("、", 0.0f, 0.7f, -0.6f), new CharSettings("。", 0.0f, 0.7f, -0.6f), new CharSettings("「", 90.0f, -1.0f, -0.3f), new CharSettings("」", 90.0f, -1.0f, 0.0f), new CharSettings("ぁ", 0.0f, 0.1f, -0.1f), new CharSettings("ぃ", 0.0f, 0.1f, -0.1f), new CharSettings("ぅ", 0.0f, 0.1f, -0.1f), new CharSettings("ぇ", 0.0f, 0.1f, -0.1f), new CharSettings("ぉ", 0.0f, 0.1f, -0.1f), new CharSettings("っ", 0.0f, 0.1f, -0.1f), // 略 new CharSettings("V", 90.0f, 0.0f, -0.1f), new CharSettings("W", 90.0f, 0.0f, -0.1f), new CharSettings("X", 90.0f, 0.0f, -0.1f), new CharSettings("Y", 90.0f, 0.0f, -0.1f), new CharSettings("Z", 90.0f, 0.0f, -0.1f), new CharSettings(":", 90.0f, 0.0f, -0.1f), new CharSettings(";", 90.0f, 0.0f, -0.1f), new CharSettings("/", 90.0f, 0.0f, -0.1f), new CharSettings("", 90.0f, 0.0f, -0.1f), new CharSettings(":", 90.0f, 0.0f, -0.1f), new CharSettings(";", 90.0f, 0.0f, -0.1f), new CharSettings("/", 90.0f, 0.0f, -0.1f), new CharSettings(".", 90.0f, 0.0f, -0.1f), }; public static CharSetting getSetting(String character) { for (int i = 0; i < settings.length; i++) { if (settings[i].charcter.equals(character)) { return settings[i]; } } return null; } }
文字を描画する前にCharSetting#getSetting()を呼び、設定があったらその設定通りに描画します。
nullが帰ってきた場合、回転も位置調整もせずに描画します。
コードで表すとこんな感じ
CharSettings setting = CharSettings.getSetting(s); if (setting == null) { canvas.drawText(s, x, y, paint); } else { canvas.save(); canvas.rotate(setting.angle, x, y); canvas.drawText(s, x + fontSpacing * setting.x, y + fontSpacing * setting.y, paint); canvas.restore(); }
また、CharSettingsをたくさん生成しているところはexcelで管理して生成できるようにしました。
CharSettings作成シート https://spreadsheets.google.com/ccc?key=0AmA_8rkF2TwodHIyUWpMdldicVhhMGxZWVItNmQ1UkE&hl=ja&authkey=CK_XjqsK
android縦書き完成
あとは表示位置がおかしい文字の設定を随時追加していったり、
行末に「、」「。」などがくるよう禁則処理したり、
ルビの表示を実装したりすればいいと思います。
特にルビはデータをどのように保持するか、またパースのやり方を考えないといけないので大変かもしれません。