ひらい ぶらり Hi-Library

ぷろぐらみんぐについて。ときどきどうでもいいことについて。

androidでfont変えると色々と弊害があるっぽい

怪奇現象が色々発生した

  1. maxLinesとか指定するとずれる

なんだか2行目の文字が1行目に食い込んだりする

  1. 消える

もはや表示されない。

  1. 端末によって挙動が違う

消えたり、消えなかったり、ずれたり、ずれなかったり

噂によるとheightをしてしたり、layout_gravityにtop|bottomを指定するといいらしいけど効果がなかった。 なんじゃこりゃ・・・

フォントサイズ自動調節TextView メモ

フォントを変更した場合には一手間加える必要がある

参考

http://dev.classmethod.jp/smartphone/android-font-fit-text-view/ https://gist.github.com/STAR-ZERO/2934490

mPaint.setFaceType(getFaceType()) // フォントをPaintオブジェクトに渡してあげる
mPaint.setTextSize(textSize);

これをやっておかないと、違うフォントでサイズを取ってしまうのでずれる。 あと高さをwrap_contentにすると、調整処理が入った場合二行以上になってからサイズが調節される関係で高さがおかしくなるのでTextViewの高さは指定しておいたほうがいい。

androidでTextViewに埋め込みフォントを使う

結論

API Level9以降(2.3)ならCaligraphyを使う

chrisjenx/Calligraphy · GitHub

疑問

マテリアルガイドラインによると、RobotoフォントとかNotoフォントを使えって書いたある。 ちょろっと調べて思い浮かんだ疑問

  1. assetsにファイルサイズ制限なかったっけ?フォントは圧縮するかダウンロードさせるみたいな記事が沢山ある
  2. TextViewを拡張してコンストラクタでTypeFaceを生成してセットする方法が良く書かれてるけど、ListViewなんか使った日には画面内に生成されるTextView分だけフォント読み込む処理がはしるんです?やだー!

調査してみる

1. assetsにファイルサイズ制限なかったっけ?フォントは圧縮するかダウンロードさせるみたいな記事が沢山ある

assetsにファイルサイズ制限があるのはAPI Level8まで。 ソースコード漁ってもAPI Levet9以降のasste.hにはUNCOMPRESS_DATA_MAXは存在しなかった。

2.TextViewを拡張してコンストラクタでTypeFaceを生成してセットする方法が良く書かれてるけど、ListViewなんか使った日には画面内に生成されるTextView分だけフォント読み込む処理がはしるんです?やだー!

こういうの

seesaakyoto.seesaa.net

この方法は間違ってないし、このTextViewが大量に生成されなければ大した問題じゃない。 が、ListViewとかで使ってしまうと大変なことに。いくらViewがリサイクルされると言っても画面内に同時に表示される分はTextViewが作られるので、その分だけassetsからフォントを読み込んでFaceTypeオブジェクトが作られまくってる(多分)。4.4の端末でOOMで落ちたの久しぶり。

できれば一度読み込んだFaceTypeはActivityかApplication辺りでキャッシュしておきたい。 各TextViewではそれらを使いまわすのが理想。っていうか後からTextViewを全部オリジナルのTextViewに書き換えるの面倒くさい。名前空間の宣言すら面倒くさい。

そこでCaligraphy

唸ってたら同僚からこんなこと書いてあるよってQiitのページを教えてもらった。

font - AndroidでNotoフォント・Robotoフォントを使う - Qiita

Caligraphy。そういうのもあるのか。 使い方を見るとすごく簡単そう。何やってるか気になったので簡単にソース読んでみたのでその内容をざっくりまとめます。 使い方は↑のサイトとかCaligraphyのgithub見るといいと思います。

Context生成時にweapしてる

attachBaseContextをオーバーライドして、CalligraphyContextWrapperでcontextをラップしてる。 これはContext.getSystemService()をオーバーライドする為にやっているようだ。 こうすることで、LAYOUT_INFLATER_SERVICEが呼ばれた時に、CalligraphyLayoutInflaterを使用するようになる。

で、どうやってTextViewにFontをセットしてるの?

CalligraphyLayoutInflaterの内部でinflateされるViewがTextViewだった場合にFontがセットされているようです。 また、TypeFaceもActivity毎にキャッシュされているので、ListViewで大量にTextViewが生成されてもロードされるのは1種類につき1回だけですね。

これだ!これを求めたいたのだ!

最悪自分で実装しようと思ってたので、こんだけドンピシャなライブラリがあったのは僥倖でした。 FAQに「カスタムIDを使うのにaarを使う必要があるから、jarでは提供しないよ、っていうかGradleつかってないの?」って書いてある点も個人的にポイント高めです。 「android TextView custom font」とかでググっても出てこなかったのが残念。っていうか古い情報ばっか出てきて混乱した。僕は全然知らなかったんですが、Calligraphyって有名なんですかね?日本じゃ有名じゃないだけなのか、僕が知らないだけなのか・・・もうちょっと情報収集頑張らねば

Volleyで302リダレクトできなかった話

どうも、バグを生み出すプロ、しんばしさんです。

なんやて工藤!

非同期のHTTP通信ライブラリのVolleyを使用していたのですが、302リダイレクトするAPIへアクセスしたところ com.android.volley.NoConnectionError: java.io.IOException: Could not retrieve response code from HttpUrlConnection とかでてうまく動きませんでした。開発機では問題なかったのですが、ロードバランサを経由する本番機では発生するという状態。ロードバランサ経由の場合HTTP/1.1でリクエストするとConnection:keep-aliveをヘッダで返してくるというのが原因でした。

解決策としては HurlStack を利用していた部分をこちらのGistを参考にOkHttpClientを使う自作のHttpStackを使うように切り替えて解決。

以下だらだらとそこまでの経緯。

せやかて工藤!

VolleyはGingerbread以前ならHttpClient、それ以降はHttpURLConnectionを内部的に使っています。今回はVolleyに含まれているHurlStackを使っていたのでHttpURLConnectionを使っていました。minSdkVersionもICS以降なのでHttpURLConnectionのバグも解消されているハズ・・・!と思っていたらそうでもありませんでした。

今回の原因はHttpURLConnection.getResponseCodeが呼ばれる時にレスポンスヘッダにConnection: keep-aliveが含まれているとそこでIOExceptionが発生してしまうことでした。なんかandroidはkeep-aliveにまつわるバグが多い割にデフォルトでkeep-aliveはtrueなのだとか。

System.setProperty("http.keepAlive", "false");

とりあえず上記のコードを呼んで見たけど効果なし。ぐぬぬ。 バージョンが上がって過去にあったバグが直った!とか言われてますが、HttpClientもHttpURLConnectionもどうも期待した動きをしてくれない模様。なので今後はOkHttpClientを使っていこうかなぁ・・・。

リクエスト時にHTTP/1.0でリクエストするようにしたHttpStackを作るというのも考えましたが、なんかわざわざ古い方に合わせるのも気持ち悪いので却下。

バージョンによってバグがあるからHttpClientとHttpURLConnectionが使い分けられるのも、今後発生するかもしれないバグの見極めがむずがしそうなので、今回はOkHttpClientを使って自作のHttpStackを使うという結論に至りましたが、なんか他にいい方法ないかなぁ・・・。

ざっくり手順

  1. OkHttpClientを読み込む

OkHttp

  1. OkHttpStackを作る

https://gist.github.com/bryanstern/4e8f1cb5a8e14c202750

  1. Volley.newRequestQueue時にOkHttpStackを渡す
OkHttpStack stack = new OkHttpStack(new OkHttpClient());
RequestQueue = Volley.newRequestQueue(context, stack);

TextViewで縦にはみ出してるかどうかを検知したい

どうもわたくしです。

TextViewの高さを超えてテキストがセットされていたら、ボタンを出したい。 ボタンを出したいんです。

f:id:shin_bashi:20150326011934p:plain

こんなかんじにTextViewを超えて長文が入力されていたら

f:id:shin_bashi:20150326011956p:plain

ボタンを出したい。

解決編

行数 > 最大行数(TextViewの高さ / 1行あたりの高さ)でよさそう

final TextView textview = findViewById(R.id.textview);
textView.post(new Runnable() {
            @Override
            public void run() {
                Layout layout = textView.getLayout();
                if (layout != null) {
                    int lines = layout.getLineCount();
                    int max = (int)(textView.getHeight() / textView.getLineHeight());
                    if (lines > max) {
                        findViewById(R.id.button).setVisivility(View.VISIBLE);
                    }
                }
            }
        });

こんな感じ。 行の高さとかViewの高さはレイアウトされてからじゃないと取得できないので、post()を使ってレイアウト後に処理を行えばおk

別にルイズコピペを使いたくてこの記事を書いたわけじゃないよ、ほんとだよ。

Android で MD5とかSHA-256とかするのにcommons-codec使おうとしたらエラーが出た話

MD5やらSHA-256でハッシュ化したい機会ってのはよくあると思うんですが、ググるとなんでかMessageDigestクラス使って長々と記述するサンプルが多く見かける気がします。 ラップしたクラスを作ってもいいんですが、せっかくだから俺はcommons-codec使うぜ!

gradleに追記

compile 'commons-codec:commons-codec:1.10'

コード

String hash = DigestUtils.sha256hex("abc");

うんうん短くてよきかなよきかな

Could not find method org.apache.commons.codec.binary.Hex.encodeHexString, referenced from method org.apache.commons.codec.digest.DigestUtils.sha256Hex

そんな気はしてた。 どうやらDigestUtilsが内部で使ってるHex.encodeHexStringが無いということらしい。どういうことなの。 試しに名前空間を変えて、org.apache.commons.codec.binary.Hexの中身をまるっとコピーして実行するとうまくいった。多分android SDK が内部的にcommons-codec使ってて、そっちにあるHexが呼ばれているっぽい。Google Playアプリのライセンスの中にもcommons-codecってあるし多分そういうことなのだろう。 とりあえず、encodeHexStringが使われないようにしてお茶を濁そう。

String hash = new String(Hex.encodeHex(DigestUtils.sha256("abc"));

敗北感ある・・・(´・ω:;.:...

フォントサイズ自動調整TextViewをListViewで使いたい

大体皆さん "android フォント 自動" "android Textview 横幅" とかでググって、TextViewを継承したクラスの実装を見かけたと思います。

参考

http://dev.classmethod.jp/smartphone/android-font-fit-text-view/ https://gist.github.com/STAR-ZERO/2934490

この2つの実装の違いはonMeasureかonLayoutでやってるかの違いですが、それはまあ置いておいて。 これらをListViewの内部で使おうとしたら残念な結果になってしまいました。 もしくはListViewじゃなくても、setTextを繰り返し行う場合には同じ結果になるかもしれません。

起きた現象

以下の手順を同じtextViewに行います

  1. 横幅を超えないテキストをセットする→期待どおり
  2. 横幅を超えるテキストをセットする→期待通り
  3. 再び横幅を超えないテキストをセットする→ 2.のサイズから戻らず小さいまま

解決策

setText後にsetWidthを呼ぶ

textView.setText(text);
textView.setWidth(getContext().getResources().getDimensionPixelSize(R.dimen.width));

理屈

テキストサイズの調整をonLayoutもしくはonMeasureで行っているので、当然onLayout, onMeasureが呼ばれなければリサイズされません。 setTextI()を呼んでonLayoutが走るのは 現在のwidthよりも大きいサイズのテキストがセットされた時 だけのようでした。そのため、現在のwidthに収まる範囲のテキストをセットしてもonLayoutは呼ばれません。 本来はレイアウトを設定し直す必要がないので当然ですが、今回は毎回呼んでもらわないと困るので、強引に元からセットしてあるwidthと同じ値を再度セットすることで強引にonLayoutを呼び出すことで解決しました。 なんか他にonLayoutを走らせるいい感じのメソッドがあるかもしれませんが調べてません。

おわり。