u-ryo's blog

various information for coding...

Android Petite Tips

| Comments

お仕事で極悪Androidアプリを改修していて、 今日得た知見をば。

getTextSize/setTextSize

あるActivityの画面で、 本文とボタンのtext sizeを揃えようとして、 TextView#getTextSizeしてからsetTextSizeしたら、 大きくなるんですよね。何でだろう、調べると、 TextView#getTextSizesetTextSizeのデフォルト単位が違う のだそう。びっくりです。

1
renewalButton.setTextSize(TypedValue.COMPLEX_UNIT_PX, caution.getTextSize());

というように、単位を明示する必要があるそう。

サイズ自動調整TextView

TextViewで、指示通りに改行位置を固定しようと思って。 指示中の、禁則処理に失敗しているところも含めて忠実に再現しようと。 そのためにはtext sizeを随意にせねばならず。 【Android】横幅に合わせてテキストサイズを調整するTextViewそのままで上手く行きました。 あーでもonLayout()の最初の引数changedtrueの時だけ resize()すればよかとです。

今はAutosizing TextViewsというのがあるそう。 ただ、API 26からなのでまだなかなか使えないでしょうか。

onLayout後の値の取得

上記のようにtext sizeを変えてから、 その結果のtext sizeに合わせて他のViewのtext sizeを 決定しようとすると、 onLayout()が呼ばれ終わってからでないと 目的の値が取得出来ないんですね。 そこで、 How to know when an activity finishes a layout pass? にあるように、 myView.getViewTreeObserver().addOnGlobalLayoutListener(() -> {...}); とすれば良いです。

補足

RobolectricでUnit Test書いてたら、 このclass、testが終わらないんです。 何でかなー、とbreakpointで追ってみると、 延々とonGlobalLayout()が呼ばれ続けてるんですね。 えーっと思って。 RxJavaHooks.setOnIOScheduler(s -> Schedulers.immediate()); しても、 ShadowApplication.runBackgroundTasks(); しても効き目はなく。 androidでheightやwidthが0になって取得できない時 を見ると、用が済んだらすぐremoveするんですね。そっか。 というわけで、Android SDKのversionによって分けて、 removeOnGlobalLayoutListener(this)removeGlobalOnLayoutListener(this)でremoveするように したんですけど、今度はthisが効かない。 なるほど、lambdaだとthisは外側のclass instanceになるんですね。 じゃぁっていうんでlambda自体をListener instanceとして名前付けて、 lambdaの中でthisじゃなくてその名前で参照しようとしたんですが、 might not have been initializedとか言われ、 nullで初期化すると今度はeffectively finalじゃないと言われて、 うー、とか思って仕方なく諦めて、 初めてlambdaを解いてinner classの記述に戻しました。

Rx Androidにmaxはない?

探したんですけど見つからなかったので自分で集計しました。

1
2
3
4
5
6
float textWidth = rx.Observable
        .from(getText().toString().split("\n"))
        .map(paint::measureText)
        .reduce(Math::max)
        .toBlocking()
        .single();

TextViewで白枠

ある段落を白枠で囲って欲しいと言われました。 調べると、[android]xmlで枠を指定するというのがあり、 それ用のdrawable XMLを作ってやってandroid:background="@drawable/..."で、 それを指定すれば、望み通りのものが得られました。 背景色は、これも書いてありますが#00ffffffで透明になります。

1
2
3
4
5
6
7
8
9
10
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle">
    <!-- background -->
    <solid android:color="#00ffffff" />
    <!-- rounded corners -->
    <stroke android:width="1dp"
        android:color="@color/white" />
    <corners android:radius="1dp" />
</shape>

Robolectricで次のActivityへの遷移の確認

shadowOf(activity).peekNextStartedActivity()Intentを取得、 getComponent().getClassName()が目的のclass nameかをassert。

1
2
3
Intent intent = shadowOf(activity).peekNextStartedActivity();
assertThat(Objects.requireNonNull(intent.getComponent()).getClassName(),
        is(MainActivity.class.getName()));

Local PushでNotification

Local PushでNotificationをして欲しい、と言われました。 調べてみると、要するに、 AlarmManagerPendingIntentをsetして、 それがset時の引数のUnix Time(millisec)になると、 これもset時引数のBroadcastReceiverの子classの onReceive(context, intent)が呼ばれるので、 そこでNotificationManager.notify()をする、と。

AndroidのNotificationについては、 sample applicationを作って色々と試してみました。

  1. uninstall/端末再起動すれば登録済みのalarmは解除される
  2. 多重登録してもPendingIntent.FLAG_UPDATE_CURRENTなら最後のNotificationに上書きされる
  3. 過去の時日のalarmを登録するとすぐNotifyされてしまう
  4. 機種によっては挙動が違う(Huaweiでは、アプリが起動していない時/Sleep時にAlarmを発動させるには「保護されたアプリ」でないとならない、等)
  5. 長いtextは全文出ないで端折られる。出したいなら、.setStyle(new NotificationCompat.BigTextStyle().bigText("..."))する。但し.setBigContentTitle(intent.getStringExtra("..."))も同時に加えるとダメっぽい。

Comments