言いたいことはそれだけか

KotlinとかAndroidとかが好きです。調べたことをメモします。٩( 'ω' )و

Build typeによってアプリアイコンを分ける

はじめに

この記事は諸事情によりRibbonizerライブラリが使えない人たち向けに書いたものです。 Ribbonizerが使える人はそちらを使ったほうが良いです。

やりたいこと

Debug buildの時とRelease buildの時にapp idやapp nameを変えて一つの端末の中に両者を共存させていたのですが、 アイコンが同じだと分かりづらいのでアイコンも分けることに。

どうするか

Ribbonizerという素敵なライブラリがあります。これをつかえば2行ほどbuild.gradleに書き足すだけでやりたいこと達成です。

github.com

どうするか その2

以下、諸事情により↑が使えない人向けです。 自分の場合だと、ローカルでbuildする場合は何の問題もないのですが、 Circle CIでbuildするときにCircle CIの4G制限に引っかかってしまいdied unexpectedly…(´・_・`)

とりあえずライブラリをつかわず自前でどうにかする方向で考えます。 こちらの記事がとても参考になりました。

ninjinkun.hatenablog.com

typeの縛りが激しいresValueと違って、placeholderはdrawableも扱えます。

まずはこんな感じでdefaultConfigに追記。

    defaultConfig {

        manifestPlaceholders = [appIcon:"@drawable/your_app_icon"]
    }

debug版のみアイコンを変えたいのでbuildtypeがdebugの時はdebug用のアイコンを指定します。 (自分の場合はRibbonizerが生成したdebug用のアイコンをbuild/ディレクトリ下から拾ってリネームして指定しました笑)

    buildTypes {
        debug {

            manifestPlaceholders = [appIcon:"@drawable/your_debug_app_icon"]
        }
    }

最後にManifestでこれを指定すればOK.

    <application

            android:icon="${appIcon}"

この方法だとお手軽に動くのですが、debug用のアイコンリソースがapkパッケージの中に含まれてしまいます。 それが耐えられない人はやっぱりRibbonizerでどうにかする方法で頑張ってください。

何番目のRadioButtonがチェックされているかうっかり取得されてしまう話

RadioGroup#getCheckedRadioButtonId() が、チェックが入ったviewのidではなく、何番目にチェックが入っているかを取得できると勘違いして、実際その勘違い通りの振る舞いをするように見えるケースが存在した調査ログです。

結論から言ってしまうと、RadioButtonにidを振らないとView#generateViewId() が1から順に勝手に新規idを振ってしまい、それを返してしまうという話です。

現象

RadioButtonを3つ配置したRadioGroupが存在しており、そのうち1番目にチェックをつけた状態でRadioGroup#getCheckedRadioButtonId()が1を返すケースがある。 この振る舞いになる条件は、

  1. RadioButtonにidをふらない
  2. 初回起動時のみ(n回目に対象Activityを起動した際には3n-2が返ってくる)

具体的にはこんなレイアウト

    <RadioGroup
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:id="@+id/radiogroup_sample">
        <RadioButton android:layout_width="wrap_content"
                     android:layout_height="wrap_content"
                     android:text="@string/sample1"/>
        <RadioButton android:layout_width="wrap_content"
                     android:layout_height="wrap_content"
                     android:text="@string/sample2"/>
        <RadioButton android:layout_width="wrap_content"
                     android:layout_height="wrap_content"
                     android:text="@string/sample3"/>
    </RadioGroup>

何がおこったか

RadioButtonがviewのhierarchyに追加される時点で、idが存在するかチェックし 存在しない場合は新規に追加します。

RadioGroup.java

    private class PassThroughHierarchyChangeListener implements
            ViewGroup.OnHierarchyChangeListener {
        private ViewGroup.OnHierarchyChangeListener mOnHierarchyChangeListener;

        /**
         * {@inheritDoc}
         */
        public void onChildViewAdded(View parent, View child) {
            if (parent == RadioGroup.this && child instanceof RadioButton) {
                int id = child.getId();
                // generates an id if it's missing
                if (id == View.NO_ID) {
                    id = View.generateViewId();
                    child.setId(id);
                }

View.java

    private static final AtomicInteger sNextGeneratedId = new AtomicInteger(1);

    public static int generateViewId() {
        for (;;) {
            final int result = sNextGeneratedId.get();
            // aapt-generated IDs have the high byte nonzero; clamp to the range under that.
            int newValue = result + 1;
            if (newValue > 0x00FFFFFF) newValue = 1; // Roll over to 1, not 0.
            if (sNextGeneratedId.compareAndSet(result, newValue)) {
                return result;
            }
        }
    }

generateViewId() は1で初期化したAtomicIntegerから順にインクリメントした値を返すので、それぞれn番目がチェックされているかのように振舞っていたのです。(returnするのがnewValueではなくresultなので初回は1が返ってきます。) ただし、例えばActivityを再起動するなどして再度viewのhierarchyに追加されるときは1からではなく続きからインクリメントされるので、初回起動時のみn番目がチェックされているかのように見えていたというオチでした。

余談

何番目がチェックされているか返すmethodはRadioGroupを拡張したクラスでaddView() をoverrideしてやれば作れそう。 ただし、「何番目がチェックされているか」ではなく「何がチェックされているか」の方がどう考えても重要なのであまり使い道はないかもしれない。

public class CustomRadioGroup extends RadioGroup {

  private List<RadioButton> mChildren = new ArrayList<>();
  public CustomRadioGroup(Context context) {
    super(context);
  }

  public CustomRadioGroup(Context context, AttributeSet attrs) {
    super(context, attrs);
  }

  @Override
  public void addView(View child, int index, ViewGroup.LayoutParams params) {
    if (child instanceof RadioButton) {
      mChildren.add((RadioButton) child);
    }
    super.addView(child, index, params);
  }

  public int getSelectedPosition() {
    int selectedViewId = getCheckedRadioButtonId();
    if (selectedViewId == -1 || mChildren.isEmpty()) {
      return -1;
    }
    for (int i = 0; i < mChildren.size() -1; i++) {
      if (mChildren.get(i).getId() == selectedViewId) {
        return i + 1;
      }
    }
    return 0;
  }
}

読書の習慣化に成功した話

今年に入って割と効率よくインプットが進んでいるので知見を共有します。

きっかけ

バレンタインにバズったこの記事に影響され、毎日コツコツと小さなインプットをしようと決意。

www.lifehacker.jp

何をやったか

昨年からダラダラと読んでいたSoft Skillsを読み切ることに決めました。

Soft Skills: The Software Developer's Life Manual

Soft Skills: The Software Developer's Life Manual

こちらはまだ翻訳版が出ておらず、英語版を読むしかないのでなかなか読み進められずにいました。 (英語自体はとても簡単な表現を使って書かれているので、英語苦手な人にもオススメです。) こちらの本を1日1章ずつ読むことにしました。

どうやったか

まずはこれから先一週間で毎日どの章を読むか決めます。 決めたらTo Do Listに登録していきます。私はこのアプリを使っています。

play.google.com

このアプリはマテリアルデザインが割と良くでおり、シンプルで使いやすいので気に入っています。 またリマインダー機能があるのと、その日のタスクが全て終わったらちょっとほっこりメッセージをくれるのが好きです。

一週間が経過するか、もしくはその週のタスクを消化しきったらまたその日から一週間先のタスクを登録していきます。 なぜ一週間ごとにやるかというと、自分の性格上週によって進捗が異なることがわかったからです。

結果

1ヶ月ほどで無事読み終わりました。(たしか40章ほどあったので結構かかりました。) 読み終えたのももちろん良かったのですが、毎日何かしらのインプットもするという習慣がついたのがおおきな成果でした。 現在は次の本を読んでいます。

おまけ

上記To Do Listは読書だけでなく、植物の水やりなど毎日繰り返すタスクの管理も大変便利なので 我が家のパクチーも元気です。

Android N からMulti Language and Multi Locale になるらしい

※本エントリは2016/4/8 に書いたもので、Android N Preview 1 時点で公開されている情報に基づいています。

昨日「まったりAndroid Framework Code Reading #3」に参加しました。
mandroidfcr.doorkeeper.jp

結局Frameworkのソースコードを読まずにAndroid N Previewを読んだというオチ。
NからMulti Language and Multi Locale になるらしい。
間違ったこと書いてたら指摘して欲しいです。


公式資料はこちら
http://developer.android.com/intl/ja/preview/features/multilingual-support.html

概要の翻訳版はこちら
http://qiita.com/tnagao3000/items/562e81e8ac9bb581afcc#multi-locale-support-more-languages

Nから設定で複数言語選べるようになる

M以前ではラジオボタンで単一でしか選択できなかった言語設定ですが、Nからは複数言語選択できるようになるらしい。
複数言語を選択する場合、選択する言語には優先度がつきます。
(例:1.日本語、2.Englishなど)

Nから端末がサポートするLocaleが大幅に増える

残念ながら各言語が具体的にどれくらい増えるかどうかは言及されていません。
ただ、例として出てくるar_EG (Arabic - EGYPT) は27もArabicなLocaleに対応するそうです。

NからLocaleに階層の概念が持ち込まれる

例えばM以前ではLocaleクラスのen_CA(CANADA), fr_CA(CANADA_FRENCH), en(ENGLISH), fr(FRENCH)の間に階層概念はありませんでしたが、NではLocaleに階層概念が持ち込まれました。これにより、より柔軟にユーザに適切な言語を提供できるようになります。

Nからシステムがどの言語を選択するかルールが変わる

M以前

ユーザが設定できる言語は一つで、アプリ側でその言語リソースが用意されている場合はそのリソースが使われます。
(例: 日本語設定している人がFacebookを使うと日本語をサポートしているFacebookで日本語表示になる)
アプリ側がユーザが設定している言語をサポートしていない場合、アプリがサポートするデフォルトの言語表示になります。
(例: 日本語設定している人がAWS Consoleを使うと日本語をサポートしていないAWS Consoleで英語表示になる)

N以降

ユーザが設定できる言語は複数。また、Localeに階層概念が持ち込まれているため、より柔軟に言語を選択することができる。

例えば、提供するアプリのサポートする言語が以下で:
- en_US (US English) <- default
- es_ES (Spanish)

端末の設定言語が以下の場合:
- es_MX(Spanish mexico)

M以前ではen_USが選択されていましたが、Nだと es が一致するes_ESの方が選択されます。


また、複数言語設定した場合の例。
提供するアプリのサポートする言語が以下で:
- en_US (US English) <- default
- de_DE (GERMANY)
- es_ES (Spanish)
- it_IT (ITALY)

端末の設定言語が以下の場合:
- fr_CH (French - SWITZERLAND)
- it_IT (Italian - SWITZERLAND)

まず、fr_CHを探しに行って見つからず、次にfrを探しに行って見つからず、
次にfrの子供を探しに行って見つからず、次にit_ITを探しに行って見つからず、
次にitを探しに行って見つからず、次にitの子供を探しに行ってついに晴れてマッチしたと判定されます。
もしここで見つからない場合はアプリデフォルトのen_USになります。

パフォーマンス注意点

今までのLocaleが階層関係のになる場合が多いので、そこを注意しないとパフォーマンスで問題が出ます。
上の例のように、子供でぴったり一致するものがいなければ親を見て、さらにその子供を探しに行くからです。
例えば、今までen-GB(UK) だったものはen-001(international English)にリネームするべきと記載されています。
en-GBの最も一般的な親はen-001になるからです。

NからAdditional Locale を設定できるようになるらしい

Nから追加されたLocaleList.GetDefault()を使うと端末に設定されているLocaleのリストを取得できるようになります。
これによりより洗練された言語表示ができるようになるらしい。
たとえば、翻訳かける時もより適切な言語にできるとか。
// 正直どう使うのか想像できなかった…

Formattersについて

M以前だと主要な言語(en, es, ar, fr, ru)でも1,2種類しかLocaleは用意されていませんでした。
そのせいで数字や日時がハードコードされてしまうことがあり、ユーザに混乱を与えることがあったけれど
Nからは数字や日時をハードコードする代わりにformatterを使うことが推奨されています。
主要な例はアラビア言語。Nでは`ar_EG`が**27**もアラビックロケールをサポートするそうです。

formatterの使い方の例は以下の通り。

format(locale, "Choose a %d-digit PIN", 4)

// RTL言語の読めない数字周りの表示確認つらそう…

AndroidでChromeの共有メニューにアプリを追加する

やりたいこと

AndroidChromeで共有メニューを選択した際に出てくるアプリ群に自作アプリを追加したい。
最終的にChromeで開いていたページのURLを自作アプリで受け取るのがゴール。

試したこと

まずは公式ドキュメントを参照。
Receiving Simple Data from Other Apps | Android Developers

Intent FilterをAndroidManifest.xml に登録します。
URLを受け取りたかっただけなのでmimeTypeがtextの物のみ指定。

サンプルを同様の記載をしたところ、Chromeの共有メニューにテストアプリのアイコンが無事出てきました。
ただし、これだとActivity名が表示されてしまうので、アプリ名に変えるためIntentFilterにlabelを指定します。
frameworksのこのへんのコードを参考にしました。
Cross Reference: /packages/apps/UnifiedEmail/AndroidManifest.xml

        <activity
            android:name=".YourActivity"
            android:label="@string/your_app_name_lable" >
            <intent-filter android:label="@string/your_app_name">
                <action android:name="android.intent.action.SEND" />
                <category android:name="android.intent.category.DEFAULT" />
                <data android:mimeType="text/plain" />
            </intent-filter>
        </activity>

次に上記で指定したActivity側のコード。URLを受け取る側です。
`Intent.EXTRA_TEXT にリンク情報が入っています。

    if (TextUtils.equals(getIntent().getAction(), Intent.ACTION_SEND)) {
      Bundle extras = getIntent().getExtras();
      String extraText = extras.getCharSequence(Intent.EXTRA_TEXT).toString();
      if (!TextUtils.isEmpty(extraText)) {
        doSomething(extraText);
      }
    }

TextEditをタップした時にPortraitでも全画面で入力したい

先に言っておくとあまりいい方法は見つかりませんでした。

やりたいこと

タイトルの通り。Landscapeの場合は全画面入力になるので、Portraitでも同じような挙動にしたい
もっと具体的に言うと、Portrait固定かつ画面下部固定で置いているEditTextをタップした場合、

android:windowSoftInputMode="adjustPan"

とか指定するとキーボードに隠れるってことはないんだけどテキストエリアが狭すぎて入力しにくい。
できればお手軽に上記のようはattributeを指定するだけで全画面入力したい。

Landscapeだと全画面で入力できる仕組み

さっそくFrameworkのコードを読んでみましょう。
http://tools.oesf.biz/android-6.0.0_r1.0/xref/frameworks/base/core/java/android/inputmethodservice/InputMethodService.java#992

    992     /**
    993      * Override this to control when the input method should run in
    994      * fullscreen mode.  The default implementation runs in fullsceen only
    995      * when the screen is in landscape mode.  If you change what
    996      * this returns, you will need to call {@link #updateFullscreenMode()}
    997      * yourself whenever the returned value may have changed to have it
    998      * re-evaluated and applied.
    999      */
   1000     public boolean onEvaluateFullscreenMode() {
   1001         Configuration config = getResources().getConfiguration();
   1002         if (config.orientation != Configuration.ORIENTATION_LANDSCAPE) {
   1003             return false;
   1004         }
   1005         if (mInputEditorInfo != null
   1006                 && (mInputEditorInfo.imeOptions & EditorInfo.IME_FLAG_NO_FULLSCREEN) != 0) {
   1007             return false;
   1008         }
   1009         return true;
   1010     }

残念ながらPortraitの場合はメソッドの頭でreturnされます。
このクラスはInputMethodServiceなので、独自で拡張クラス作れなくはないですが、
ユーザさんにそのIMEを選択してもらう必要があるので本来のやりたいこととは離れてしまいます。

結論

EditTextを全画面に配置する別Fragmentを用意するのが正攻法かなぁ

外国語のメニューを読めるようになりたい。【イカ編】

今回は英語ネタです。

最近レストランでカラマリフライ(イカフライ)をちょこちょこ見かけるようになってきた気がするので、各国の言葉でイカをなんというのか調べてみたメモ。
適当にググっただけなので間違ってたらすみません。
海外旅行に行った時に単語レベルでもメニューが読めると楽ですよね。

日本語

イカ。いか。烏賊。

英語

Cuttle fish(甲イカなど), squids (ヤリイカなど)
食用に調理するイカはcalamari.

  • cowとbeefのような違いかな?

フランス語

seiche(甲イカなど), calmar(ヤリイカなど)

イタリア語

seppia(甲イカなど), calamaro(ヤリイカなど)

ドイツ語

tintenfisch, Kalamari

スペイン語

calamar, jibia, sepia

中国語

乌贼, 墨鱼, 鱿鱼


ちなみに、真核生物 > 動物 > 無脊椎動物 > 軟体動物門 > 頭足綱 > 十腕目のなかに
ヤリイカ属と甲イカ属がいるみたいです。