Androidのアプリ開発関連のブログ

キーボード表示状態の変更

制作中のSSHクライアントで、キーボード表示状態の変更による画面リサイズ時の処理をつけるために、
メニューからキーボード状態の変更する機能を付けた。

メニューはキボード表示状態で変化するチェックボックスにしようと思ったのだが、
キーボードの表示状態を判別する機能は存在しない模様・・・
InputMethodManager.isActive() で判別できるかと思ったが、入力受付中?は常時trueになっちゃうぽい。
しかたないので、チェックボックスはナシにして、メニュー項目を選択したら状態を切り替える仕様にした。

InputMethodManager.toggleSoftInput(0,0);
でtoggleSoftInputに0を渡すと状況に応じた切り替えになる。
パラメータは表示条件と隠れる条件のフラグだが、指定すれば強制表示にすることもできる。

showSoftInput() というメソッドもあるが、こちらは引数にViewを渡す必要がある。
今回の場合はViewではなくActivityで入力監視をしているので toggleSoftInput() の方にした。


AndroidManifest.xml の <activity> にandroid:windowSoftInputMode属性をつければ起動時にキーボードを表示させることができるが、
"stateAlwaysVisible|adjustResize"としていたが、stateAlwaysVisibleでもユーザー操作で消せちゃうので、"stateVisible|adjustResize"に変更した。
stateVisibleでも問題なく表示されてる。
adjustResizeは表示状態変更時にViewのサイズが調整される。

キーボード表示状態の変更によるViewサイズの変更検知は、
View.addOnLayoutChangeListener()
でできる。
ので、これで状態変更を検知してSSH鯖にサイズ変更コマンドを送るようにした。

サイズ変更コマンドを送った場合は、特別な応答はなく、VIMの場合は画面が消去されて0行目から再度送られてきた。
スクロール範囲が変更になるが、サイズ変更コマンドを送った時に応答無しでこっちで勝手にやる必要があるぽい。

エディタ系コマンドで2日ハマった

制作中のSSHクライアントで、vi(VIM)とlessコマンドで下端と上端でのカーソル移動での新行表示が重なっちゃってハマってた。

下端と上端で原因は違うのだが、
下端の方はLFの際に最大行数を超える場合に行を挿入する処理で、行番号は0からで行数は1からなのに間違って比較してたのが原因だったぽい・・・
これで偉いハマった・・・

上端の方はCSIコマンドへの対応が必要なので、下端の方でハマっててこっちは後回しになってた。

下端と上端の件でハマって細かくログ出力したのだが、

vi(VIM)はスクロールが発生する際に"CSI[n;mr"で一時的に最下段をスクロール対象から外して、
上方向移動の際はスクロール範囲を戻してから1行目にそのまま文字を出力。
下方向移動の際は最下段が外れた状態でLFで行挿入をして文字入力。そして範囲を戻す。
ってな流れになってるぽい。
上方向移動は一旦範囲を縮小して、戻してそのまま文字が入力されてるぽいんで、
「範囲縮小 → 範囲拡大」となった場合は、上に行を挿入するぽい?

lessコマンドでは、スクロール範囲は設定されず、下方向への文字挿入は常に最下段に行われる。
なので、下方向への移動はただ文字が送られてくるだけ。
上方向に移動する際は「ESC+M」コマンドが送られてて、これは1行上にスクロールする制御コマンドぽい。


というわけで、多分理解できた。
モノの方はだいぶ出来てきた感じ。
マルチバイト文字とかはどうすっかな・・・

TextViewの高さ

制作中のSSHクライアントで画面に横方向と縦方向に何文字表示できるか測っているのだが、
レイアウトの構造は、「 ScrollView > LinearLayout > TextView 」で1行毎にTextViewが入る。
LinearLayout はmatch_parentしても高さがないようなので、全体の高さはScrollView.getHeight()で取得。
TextView.getLineHeight()で文字の高さを取得して全体の高さを割って行数を取得していた。
paddingとmarginは指定無し。

で、なんかズレているような・・・
というわけで計算値の出力と画像の解析をしたのだが、
TextView.getLineHeight()は13になるのだが、画像解析では15px程のような・・・

さらに調査してみたが、LinearLayout.getHeight()は15pxになる!
LinearLayoutに考慮してない余白か何かがあるのかと思ったが発見できず、
TextViewの方は挿入してないのでgetHeight()は0になる状態だったのだが、挿入済みのTextViewを取得してgetHeight()してみたら15になった!

TextViewの高さが予定と違っていることが確定したが、
getLineSpacingExtra() は0。
getLineSpacingMultiplier() は1.0。
getLineHeight() は13なので、Viewの高さも13になるものと思っていたのだが、謎の余白が入るんだね。
試しにpaddingとmarginを0pxに指定してやってもViewの高さは変わらず15。


というわけで、この問題は挿入済みのTextViewからgetHeight()して高さを取得することにする。

SSHクライアントを制作中

引き続きSSHクライアントを制作中。
入出力だけの機能になるのでUIとか簡単と思ってたが、制御コードの実装とかめんどい。

カーソル位置の制御とかは、サーバーからASCIIコード27の'ESC'に続けて91の'['、さらにパラメータが続いて最後に英大文字。
っていうコードがきて、それに従って制御する感じぽい。
Control Sequence Introducer(CSI)っていうものらしい。
不完全な感じがするが、座標系のコマンドは実装した。
ただ、スクロールで座標ずれてくるからそのへんの対応が必要がありそう。

あと、Ctrlキーで制御するようなコマンドの送信はどうやるんだ?
というわけでハマったが、
ASCIIコードの64'@'から95'_'までの32文字に、Ctrlが押されている場合は64を引いた値がASCIIコード0-31の制御コードに対応してる。
というわけで、対応するASCII制御コードを送れば機能する。
でもって、AndroidのキーコードはASCIIコードとは違うので対応させる必要がある。
範囲中のA-ZはASCIIコードで65-90で、Ctrlで64減算すると1-26になり、Androidのキーコードでは29-54の範囲で連番になってるので、キーコードから28を引けば対応する制御コードになる。
Ctrlが押されている場合はgetUnicodeChar()が0の印字不能文字となる。
その他のキーは連番じゃないので個別に設定する必要がありそうだが、'^'と'_'はキーボードによってShiftが必要だからと思うが定数化されてない。なので、その2つは非対応にした。


まあ、そんなわけで、Ctrl+CとかCtrl+Dなんかが送れるようになった。
あとは、
スクロール系の処理。
スクリーンサイズの送信。(フォントサイズを計算する必要があるね)
色系の処理。
ってな感じかな。

nanoとかviとかエディタコマンドに対応させるのが難易度高い部分と思うが、テスト用のSSH鯖にnanoが入ってなかった。
公開鍵認証に対応させたらnano入りの鯖に繋いで試してみるが、
スクリーンショット取るのに外部鯖だとホスト名とかユーザー名とか隠したい。
手元にテスト鯖用意してやるかな。

キー入力の取得処理でハマってる

今日はキー入力の取得処理でハマって、全く進んでない。

まず、EditTextなしでキーボードを表示してキーイベントを取得する方法で考えた。
AndroidManifest.xml で<activity>の属性にandroid:windowSoftInputMode="stateAlwaysVisible" を追加すればキーボードは常時表示される。
ActivityのonKeyDown(int keyCode, KeyEvent event)
をオーバーライドすれば押されたキーのボタン番号とKeyEventが取得できる。
のだが、ここで戻りがStringとかCharのメソッドを調べて、
「ボタンに対応する文字を取得する機能が存在しない。」と判断してしまった。


次にEditTextなしは諦めてEditTextの変更をTextWatcherで監視する方法を試みた。
普通にEditTextを使うと確定前に表示されちゃうので、TextWatcherで確定判別。
確定時にイベントが発生すれば良いのだが、そういうイベントはない模様。

この場合はソフトキーボードの強制表示は、
InputMethodManager imm=(InputMethodManager)getSystemService(this.INPUT_METHOD_SERVICE);
imm.showSoftInput(view,imm.SHOW_IMPLICIT);
てな感じでできる。
showSoftInput()の第1引数がViewだが、Viewなしでキーボード表示は動的にできないのかな?

で、TextWatcherでの確定判別だが、
public void afterTextChanged(Editable s){
   for(Object obj:s.getSpans(0,s.length(),Object.class)){
     if((s.getSpanFlags(obj)&s.SPAN_COMPOSING)==s.SPAN_COMPOSING){
       Log.d("TES","未確定"+s.toString());
       return;
     }
   }
   Log.d("TES","確定"+s.toString());
}
こんな感じで、Spanned(Editableがimplementsしてる)のフラッグを確認すると、未確定状態だとSPAN_COMPOSINGが付いているようなので、それを判別。
なのだが、キーボードをゆっくり押すと問題ないが、速く押すと未確定のはずが一瞬確定してる。
他に、未確定状態だとアンダーラインがついているはず!ということでSpannedでアンダーラインを判別しても似たようなことが出来るようなのだが、
キーボードを押すと一瞬アンダーラインが消えてるのが目視できる。結局同じぽい。



どうしたもんかと思ったが、
実は最初に試したActivityのonKeyDown()の方で、
戻り値がint型なので気づかなかったが、event.getUnicodeChar()でボタンに対応するchar値(intで返ってくるが)が取得できた。
isPrintingKey()で表示可能文字かどうかの判別もできるんで、
表示可能ならそのまま送信して、表示できない文字は個別に処理作れば良さそう。

というわけで、ActivityのonKeyDown()でキー入力を取得する方法で行こうと思うが、1byteずつ送ることになるんで、
マルチバイト文字の入力が必要な場合とかは入力エリア使ってやるしか無いかな。