AppCompat 25.1.0 では効かない Android 4.4.x の backgroundTint

最初の私のイメージとしては,

サポートライブラリが充実してきているので ボタンの色は, banckgroundTint で簡単に変更できる!

でしたが...

おさらい

まず, ボタンを設置します.


    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textColor="@android:color/white"
        android:text="Button" />

灰色のボタンが表示されて押したら, それなりにエフェクトが効いて「押されました感」が見て分かります.

ボタンの色を変えます.


    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textColor="@android:color/white"
        android:text="Button"
        android:background="@color/colorAccent" />

ボタンの色が変わりましたが, 押したときにエフェクトが効きません.

background 属性のかわりに backgroudTint を使います.


    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textColor="@android:color/white"
        android:text="Button"
        android:backgroundTint="@color/colorAccent" />

これで, 背景色も変わりかつエフェクトも効きます.

background ではなく, backgroundTint で色を指定する

ということですね.

Button と AppCompatButton の記述

StackOverflow などを見ているとさまざまな記述が見えます.

android - Lollipop's backgroundTint has no effect on a Button - Stack Overflow

ただ「ボタンの色を変えたいだけ」なのですが, なんだか混乱しています.

backgroudTint を使った記述にも似たようなものいくつかあるようです.


    <android.support.v7.widget.AppCompatButton
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textColor="@android:color/white"
        android:text="AppCompatButton"
        android:backgroundTint="@color/colorAccent" />


    <android.support.v7.widget.AppCompatButton
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textColor="@android:color/white"
        android:text="AppCompatButton"
        app:backgroundTint="@color/colorAccent" />

Button のかわりに AppCompatButton を使っていたり, backgroundTint の prefix が android: となっていたり app: になっています.

とりあえず, すべてをレイアウトに記述して表示してみました.

以下3つの挙動は予想通りで同じです.


<Button
    android:backgroundTint="@color/colorAccent"
    ...


<AppCompatButton
    android:backgroundTint="@color/colorAccent"
    ...


<AppCompatButton
    app:backgroundTint="@color/colorAccent"
    ...

Button と記述した場合でも, 内部でいい感じに入れ替えてくれているようですね.

[Tool]-[Android]-[Layout Inspector]

AppCompat を意識すること無く, 昔からの Button を使って記述していけばサポートライブラリが対応してくれるということですね!

AppCompat は えらい!!

Android 4.4 KITKAT で確認する

結果 : 全てダメ.

え,

色とエフェクト両方きちんと反映されているものがない...

なんなんすかね これ...

AppCompat のバージョンを下げてみます.

//compile 'com.android.support:appcompat-v7:25.1.0'
compile 'com.android.support:appcompat-v7:25.0.1'

Android 4.4 では, backgroudTint が ひとつの記述だけしか意図通りに表示してくれません.

AppCompat はクソ!!

そら混乱もしますわ.

まとめ

backgroundTint についての
OSバージョンと appcompat-v7 バージョンの関係

現状では,

AppCompat-v7 を 25.0.1 で


   <android.support.v7.widget.AppCompatButton
	....
        app:backgroundTint="@color/colorAccent" />

と書いておくのが吉.


Android 7.1 ( API25 / N_MR1 ) の通知の設定

今や購入時のままの設定のままだと各アプリからの通知が大量に溜まってくる.

Android 7.1 では, 端末側の設定画面が変わっている.

「設定」-「通知」-「右上の歯車マーク」から設定できるのは以下.

- 光を点滅させて通知 (ON/OFF)

- ロック画面の通知 (通知をすべて表示しない/すべての通知内容を表示する)

- Power notification controls (ON/OFF)

画面上部のスピナーからも設定状態別にアプリを抽出表示できる.

- すべてのアプリ
- ブロック中
- マナーモードで表示
- ロック画面にプライベートな内容を表示しない
- ロック画面に表示しない
- [通知を非表示]をオーバーライド

「ブロック中」を選択してみると, いつのまにか Evernote をブロックしていた.

「重要度」というスライダーがあるのだが, 7段階の設定ができる.

- このアプリからの通知を表示しない

- 全画面表示, ポップアップ, 音, バイブレーションを使用しない。通知リストの一番下に表示する。ロック画面やステータスバーには表示しない。

- 全画面表示, ポップアップ, 音, バイブレーションを使用しない。通知リストの一番下に表示する。ロック画面には表示しない。

- 全画面表示, ポップアップ, 音, バイブレーションを使用しない。

- 全画面やポップアップを使用しない。

- 常にポップアップし, 全画面は表示しない。

- 常にポップアップし, 全画面表示も許可する。通知リストの一番上に表示する。

あるいは, 重要度と書いてる下の「A」を押すと, スライダーは利用できなくなり以下となる

- アプリが通知ごとに重要度を識別する。

スライダーの下にはスイッチ.

- [通知を非表示]をオーバーライドする。(ON/OFF)

その下には,「通知音の最少間隔」の設定。

- 無制限
- 10秒
- 30秒
- 1分
...
- 30分

アプリによっては, このアプリごと画面の右上から, アプリ内の設定画面に遷移できる.

と, かなり設定項目多いです. 多すぎません?

まとめ

通知の内容や頻度はアプリを公開している側の考え方に依存していますので, まずは単純にアプリ別に「ブロックする」方法を覚えておくといいのだろうと思います.

「設定」-「通知」で通知してきたアプリを選択.

重要度の下の「A」をタップして, スライダーでこのアプリからの通知をブロック.

これまでの「フィルター」機能 消してしまった通知の確認 はもうできないのか・・・

しかし, 最近のスマホてややこしすぎません? いや, ほんと.

通知が表示されない とかどうせブロックの設定だろ! → 原因「フィルター」

消してしまった不要な通知を2度とこないように「通知履歴」から非表示設定する


JSR-310 で Date/Calendar を避けて通る

Android上で既存のフォーマットを利用して日時を表示しようとすると, android.text.format.DateUtils は利用したい.

「ThreeTenABP」で捨てれないのか android.text.format.DateUtils

JSR-310系の LocalDate などと Unixタイム間を自在に変換できない.

LocalDateTime系は時差情報を持たないので、時刻の起点が定まっていない(システム依存)という特徴があります。一方でInstantとjava.util.Dateは暗黙的にUTCという時差情報をもっており、時刻の起点が定まっています。これがLocalDateTime.toInstantなるメソッドがない理由です

Java8日付時刻APIの使いづらさと凄さ - きしだのはてな

「Local* は時刻の起点をもっていない」

ので, それを追加した ZonedDateTime/OffsetDateTime に一度変換して Instant を経由して Epoc へ, 逆も同じ要領で.


long epochSecondF = 1234567890;
long epochMilliF = epochSecondF * 1000L;

Instant instantF = Instant.ofEpochMilli(epochMilliF);
//Instant instantF = Instant.ofEpochSecond(epochSecond);

ZonedDateTime zonedDateTime = instantF.atZone(ZoneOffset.systemDefault());
//OffsetDateTime offsetDateTime = instantF
//    .atOffset(ZoneOffset.systemDefault().getRules().getOffset(Instant.EPOCH));

zonedDateTime = zonedDateTime
    .plusDays(123)
    .plusHours(123)
    .plusMinutes(123)
    .plusMonths(123)
    .plusWeeks(123)
    .minusDays(123)
    .with(TemporalAdjusters.lastDayOfMonth())
    .with(TemporalAdjusters.previousOrSame(DayOfWeek.SUNDAY));

//offsetDateTime = offsetDateTime
//    .plusDays(123);
//    .plusHours(123)
//    .plusMinutes(123)
//    .plusMonths(123)
//    .plusWeeks(123)
//    .minusDays(123)
//    .with(TemporalAdjusters.lastDayOfMonth())
//    .with(TemporalAdjusters.previousOrSame(DayOfWeek.SUNDAY));

Instant instantR = zonedDateTime.toInstant();
//Instant instantR = offsetDateTime.toInstant();

long epochMilliR = instantR.toEpochMilli();
//long epochSecondR = instantR.getEpochSecond();

のように, タイムゾーンやオフセットの情報は, Android端末の設定側から取得する.


ZoneOffset.systemDefault()


ZoneOffset.systemDefault().getRules().getOffset(Instant.EPOCH)

利用者の属するタイムゾーンを利用してカレンダー周りの計算をして, Epoch と行き来するなら,


  public static ZonedDateTime toZonedDateTime(long epochMilli) {
    return Instant.ofEpochMilli(epochMilli).atZone(ZoneId.systemDefault());
  }

  public static long toEpocMilli(ZonedDateTime zonedDateTime) {
    return zonedDateTime.toInstant().toEpochMilli();
  }

そして表示の前にフォーマットする.


String formatted = DateUtils.formatDateTime(context, toEpocMilli(zonedDateTime), 
       FORMAT_SHOW_YEAR | FORMAT_SHOW_DATE | FORMAT_SHOW_WEEKDAY | FORMAT_SHOW_TIME | FORMAT_ABBREV_ALL);

// 2016年12月14日(水) 10:13
// Wed, 14 Dec 2016 00:13
// Tue, Dec 13, 2016, 20:13
// 2016年12月14日週三 09:13
// 2016년 12월 14일 (수) 10:13
// ...

「ThreeTenABP」で捨てれないのか android.text.format.DateUtils


【本当に32ビット?】Android端末の CPU の ABI を特定する

なんとなく選択していた OpenGApps の「Platform」.

The Open GApps Project

この「ARM」とか「x86」ての結局何なのか.

調べてみました.

概要

「ABI (Application Binary Interface)」とは, Android のプロセッサーにセットされた命令の種類で, mylib.so のようなネイティブCライブラリをアプリに組み込むときにABIを特定してコンパイルする必要がある.

app/libs/armeabi/mylib.so
app/libs/x86/mylib.so

大きく3つのカテゴリに分けられる.


ARM
	- armeabi
	- armeabi-v7a
	- arm64-v8a
X86
	- x86
	- x86_64
MIPS
	- mips
	- mips64

それぞれのカテゴリ内は下のもののほうが新しく一般的に下位互換なので, arm64-v8 では armeabi-v7a 向けのアプリを使うことができる.

armeabi-v7a が多いが最近は arm64-v8a が増えてきている雰囲気.

Arm Arm64 x86 Selection Guide for Android -iTechify

Genymotionなどのエミュレータ や ASUS Zenphone 2 は x86 らしい.

端末のABIを特定する

コマンドラインから眺めてみたりしてましたが,

shell@mako:/ $ cat /proc/cpuinfo
Processor : ARMv7 Processor rev 2 (v7l)
processor : 0
BogoMIPS : 13.53
...

こっちの方が良さげ.

shell@mako:/ $ getprop | grep cpu
[ro.product.cpu.abi]: [armeabi-v7a]
[ro.product.cpu.abi2]: [armeabi]
[ro.product.cpu.abilist]: [armeabi-v7a,armeabi]
[ro.product.cpu.abilist32]: [armeabi-v7a,armeabi]
[ro.product.cpu.abilist64]: []

How to identify the android device is 32-bit or 64-bit ? | Intel® Software

コード内で取得するにはそれぞれ以下で.

Build.CPU_ABI | Android Developers

Build.CPU_ABI2 | Android Developers

Build.SUPPORTED_ABIS | Android Developers

まとめ

私のAndroidには「ARM」を選択しますわ!


「ThreeTenABP」で捨てれないのか android.text.format.DateUtils

Androidで日付表記をお手軽に国際化する « LINE Engineers' Blog

読んでみると, なんかいろいろややこしそう.

イギリス式: Fri, 18 Nov 2016
アメリカ式: Fri, Nov 18, 2016

イギリス英語: Fri, 18 Nov 2016
アメリカ英語: Fri, Nov 18, 2016
日本語: 2016年11月18日(金)

イギリス英語: 18/11/2016
アメリカ英語: 11/18/2016
日本語: 2016/11/18

Android 6.0: 2016年11月18日(金)
Android 4.1: 2016/11/18 (金)

Android 6.0: 2016年11月18日金曜日
Android 4.1: 2016年11月18日 (金)

日本語: 3 日前
英語: 3 days ago
フランス語: Il y a 3 jours

日本語: 11月18日
英語: on Nov 18
フランス語: le 18 nov.

挙動にぶれがあります(エミュレータにて動作確認)。小さいTextViewに表示する際にはOS versionによって見切れてしまっていないかなどの検証をしましょう。

OS version間での挙動差分や、ビジネス上アプリで必要な言語をAPIが対応してくれるかの検証は必要

Android 7.0でのICU4Jの導入と共に下記クラスがICU4JでのDateUtilsに対応するクラスとして案内されています。

うーむ...「日付表示」て, 書いときゃ世界のどこでもまあまあいけるやつはねえのかなあ, と.

ThreeTenABP で DateTimeFormatter を使う

「ThreeTenABP」て何なのかは, 作者であるぼくらの Jake Wharton神 の書いていることを読めば分かります.

JakeWharton/ThreeTenABP: An adaptation of the JSR-310 backport for Android.

なぜ JSR-310 なのか?

JSR-310 は, java.time.* パッケージとして Java8 に含まれています. これは, Java と Android の両方で Date/Calendar API群をすべて置き換えるものです.
Java6 の開発者であるStepahen Colebourne によって調整され, それにバックポートされました.

なぜ ThreeTenBP を使わないのか?

Android 上で Joda-Time を使う問題のように ThreeTenBP はタイムゾーン情報の読込みにjar リソースを利用しています. これが Android 上では非常に非効率です.
このライブラリでは, タイムゾーン情報を Android標準で使えるようして, 効率的にパースできるローダーを提供しています.

なぜ Joda-Time を使わないのか?

Joda-Time は非常に大きいバイナリサイズと多くのメソッド数をもつ非常に大きいAPIです. また, JSR-310 と Joda-Time の作者は「壊れてはいないが, 設計に欠陥がある」と言っています.
すでに Joda-Time を使っているなら, サイズとメソッド数を除けば, 置き換える理由はほとんどありません. 新しいプロジェクトのために, このライブラリはバイナリサイズとメソッド数だけでなくAPIの数をかなり削減したJava8の標準APIの形 で提供しています.

Joda Time's Memory Issue in Android

Date/Calendar API + JodaTime の機能をJava8 で合成した java.time.* (JSR-310) を 現Android開発向けに適正化し利用できるようにしたものが「TheeTenABP」ということになります.

コードの記述は Java8 の記述と同じなので Java8 Date and Time APIドキュメントや記事が参考にできます.

続・今日から始めるJava8 - JSR-310 Date and Time API - Taste of Tech Topics

13 章 : Date and Time API · Java Study

Java 8 の DateTimeFormatter の曜日等のフォーマットについて - tokuhirom's blog

Javaで日時を扱う(Java8) - Qiita

同梱されている国際化可能そうな日時フォーマットを試してみる.

...
DateTimeFormatter formatter =
    DateTimeFormatter
        .ofLocalizedDateTime(formatStyle, formatStyle)
        .withLocale(Locale.getDefault());
Timber.d(zonedDateTime.format(formatter) + " : " + formatStyle.name());
...

日付と時刻, それぞれ FormatStyle.SHORTからFULLまでの4種類ある.


2016年12月14日水曜日 10時13分36秒 日本標準時 : FULL
2016年12月14日 10:13:36 JST : LONG
2016/12/14 10:13:36 : MEDIUM
2016/12/14 10:13 : SHORT

Wednesday, 14 December 2016 01:13:36 Greenwich Mean Time : FULL
14 December 2016 01:13:36 GMT+00:00 : LONG
14 Dec 2016 01:13:36 : MEDIUM
14/12/2016 01:13 : SHORT

Tuesday, December 13, 2016 8:13:36 PM Eastern Standard Time : FULL
December 13, 2016 8:13:36 PM EST : LONG
Dec 13, 2016 20:13:36 : MEDIUM
12/13/16 20:13 : SHORT

2016年12月14日星期三 香港標準時間上午9時13分36秒 : FULL
2016年12月14日 GMT+08:00上午9時13分36秒 : LONG
2016年12月14日 09:13:36 : MEDIUM
2016/12/14 09:13 : SHORT

2016년 12월 14일 수요일 오전 10시 13분 36초 대한민국 표준시 : FULL
2016년 12월 14일 오전 10시 13분 36초 GMT+09:00 : LONG
2016. 12. 14. 10시 13분 36초 : MEDIUM
16. 12. 14. 10:13 : SHORT

なんか微妙...

FormatStyle.MEDIUM くらいが良さげだが, 曜日が含まれてないし.

別個, 曜日の文字列としては別に


localDateTime.getDayOfWeek()
    .getDisplayName(TextStyle.FULL, Locale.getDefault());

のようにして文字列として取れるっちゃあとれるが並び順やセパレータの件がある.

カスタムで言語別にフォーマットつくるのもなあ.

まとめ

android.text.format.DateUtils で.


DateUtils.formatDateTime(
       context,
       timestamp, 
       FORMAT_SHOW_YEAR | FORMAT_SHOW_DATE | FORMAT_SHOW_WEEKDAY | FORMAT_SHOW_TIME | FORMAT_ABBREV_ALL);


2016年12月14日(水) 10:13
Wed, 14 Dec 2016 00:13
Tue, Dec 13, 2016, 20:13
2016年12月14日週三 09:13
2016년 12월 14일 (수) 10:13

結局, これが簡単で自然,

表示言語やタイムゾーンの設定は context から取得してくれるし.

モバイル画面上の表示としてその他海外で自然かどうかは実際知らんけども.

日時を多言語化するというのは思ったより複雑.

モバイル端末というのは, いろいろな制限の中で動いているのだなあと実感.

Date/Calendar のことはもう忘れていいのだよな?

(つづく)