Backgroundに設定した画像を拡大させない方法

AndroidのViewのbackgroundに画像を設定した場合にViewのサイズに合わせて画像も拡大されます
通常は特に問題ないのですがどうしても画像を拡大させたくない場合がある時の対処方法を紹介します

今回は24x24dpの以下のような画像を使いました (AndroidStudioのImageAssetで作りました)

ic_weekend.png

画像サイズを維持するDrawableを作成する

以下のようなDrawableファイルを使うことで画像サイズを維持するDrawableを作成することが可能になります

<?xml version="1.0" encoding="utf-8"?>
<bitmap
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:gravity="center"
    android:src="@drawable/ic_weekend"/>

最低限必要なのはsrcのみで表示したい画像を設定します
gravityを設定することで表示位置を変更できます

実際の表示

以下のようなレイアウトを設定して表示を確認してみます
48x48dpと12x12dpの2通り

<View
    android:layout_width="48dp"
    android:layout_height="48dp"
    android:background="@drawable/ic_weekend"
    />

<View
    android:layout_width="48dp"
    android:layout_height="48dp"
    android:background="@drawable/no_scale_ic_weekend"
    />
    
<View
    android:layout_width="12dp"
    android:layout_height="12dp"
    android:background="@drawable/ic_weekend"
    />

<View
    android:layout_width="12dp"
    android:layout_height="12dp"
    android:background="@drawable/no_scale_ic_weekend"
    />

Screen Shot 2016-12-19 at 11.57.01 PM.png

2,4番目が今回作った、サイズを維持するdrawableを表示した場合です
1番目は拡大されて表示していますが2番目では期待通り24x24dpの表示になっています

おまけですが12x12dpの場合も見てみました
ただの画像を表示した場合はサイズに合わせて縮小されていますが、今回つくってDrawableでは一部分のみしか表示されていませんでした

まとめ

固定画像のサイズをViewのBackgroundに設定する際にはbitmap要素のDrawableファイルを作成すると良いようです

画像サイズより大きいViewのbackgroundに画像を表示する場合は問題ないですが、
小さいViewに設定する際には一部分のみしか表示されないので注意する必要があります

ProgressBar用にDrawableを作成メモ

基本的にはdrawの中で描画をしてあげるだけで良い
動きをつける場合にはgetLevel()で取得できる値を元に行うと簡単
getLevel()の値の範囲は[0, 10000]
サイズの変更はonBoundsChange(Rect)で受け取れる(drawの前に最初に呼ばれるが必ずそうなのかは要調査)

public class MyDrawable extends Drawable {
    private static final int INDICATOR_COUNT = 12;
    private static final int MAX_LEVEL = 10000;

    private RectF rectF = new RectF();
    private Paint p;

    private int centerX;
    private int centerY;
    private int width;
    private int height;


    public MyDrawable() {
        p = new Paint();
    }

    @Override public void draw(@NonNull Canvas canvas) {
        int duration = MAX_LEVEL / 4;

        int currentPos = ((getLevel() % duration) / (duration / INDICATOR_COUNT)) % INDICATOR_COUNT;

        for (int i = 0; i < INDICATOR_COUNT; i++) {
            // 先頭との差によって色を変える[0xaaaaa, 0xffffff]の範囲で
            int diff = ((currentPos + INDICATOR_COUNT) - i) % INDICATOR_COUNT;
            diff = Math.max(Math.min(diff, 0x4), 0x0);
            p.setColor((0xff000000 | (0x00111111 * (0xf - diff))));

            float r = 32 * width / 100f;
            canvas.drawRoundRect(rectF, r, r, p);
            canvas.rotate(360f / INDICATOR_COUNT, centerX, centerY);
        }
    }

    @Override public void setAlpha(int i) {

    }

    @Override public void setColorFilter(ColorFilter colorFilter) {

    }

    @Override protected void onBoundsChange(Rect bounds) {
        super.onBoundsChange(bounds);
        centerX = bounds.centerX();
        centerY = bounds.centerY();
        width = bounds.width();
        height = bounds.height();
        float scale = width / 100f;

        rectF.set(centerX + 24 * scale, centerY - 4 * scale, width - 2 * scale, centerY + 4 * scale);
    }

    @Override public int getOpacity() {
        return PixelFormat.TRANSLUCENT;
    }
}

今回作ったものは以下のような表示をする(背景は別で灰色にしています)

f:id:scache:20161216102154g:plain:w100

setAlphasetColorFilterについては調べられてないので調べる必要がある
また、getLevelを使う以外にもアニメーションを行える方法があるようなので他の方法も調べたい

AndroidのRotateDrawableで回転速度を調整する

RotateDrawableとは

RotateDrawableはDrawableを回転させることができるDrawableで、以下のようなdrawableファイルを作成することで使うことが可能になります

<?xml version="1.0" encoding="utf-8"?>
<rotate
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:drawable="@drawable/ic_action_reload"
    android:pivotX="50%"
    android:pivotY="50%"
    />

今回はRotateDrawableの回転速度を変更することができないか調査してみました

drawableファイル内で速度を変更させる

drawableファイル内で速度を変更するにはfromDegreestoDegreesの値を設定することで変更できます

toDegrees=0, fromDegrees=360toDegrees=0, fromDegrees=3600を比較すると後者が10倍ほど早く回転します

<?xml version="1.0" encoding="utf-8"?>
<rotate
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:drawable="@drawable/ic_action_reload"
    android:fromDegrees="0"
    android:pivotX="50%"
    android:pivotY="50%"
    android:toDegrees="3600"
    />

API21時点ではデフォルト値は以下のようになっているようです

float mFromDegrees = 0.0f;
float mToDegrees = 360.0f;

実際の動作

今回はProgressBarを使って表示を行いました

<ProgressBar
      android:layout_width="64dp"
      android:layout_height="64dp"
      android:indeterminateDrawable="@drawable/rotate_default"
      />

結果は以下のようになりました
f:id:scache:20161215021750g:plain

(toDegrees,fromDegrees)の値は, 左から, (指定なし,指定なし), (0,360), (0, 3600)です

Android gradleで特定のvariantのみで処理を行う

以下のようなflavorやbuildTypeがある場合にproductReleaseでのみ処理を行いたい場合などがある

flavor

  • develop
  • product

buildType

  • debug
  • release

以下のようにbuild.gradleでflavorやbuildTypeが何なのかを見て特定のvariantを抽出する
抽出したvariantを使って行いたい処理を行うと良い

android {
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }

    productFlavors{
        develop{}
        product{}
    }

    variantFilter { variant ->
        def names = variant.flavors*.name

        if (names.contains("product") && variant.buildType.name == "release") {
            // do something
            // ex. variant.ignore = true
        }
    }
}

SimpleDateFormatのLocaleとタイムゾーンのメモ

Localeを指定するとタイムゾーンが変わるかと思っていたが実際は変わらなかったのでメモ

Localeを設定した時の挙動

以下のコードを3つのLocaleで実行してみた

Date date = new Date(System.currentTimeMillis());
SimpleDateFormat sdf = new SimpleDateFormat("yyyy.MMMMM.dd GGG hh:mm aaa", locale);
Log.d(TAG, "format: " + sdf.format(date));

結果は以下のような出力になる

Locale 出力
JAPAN 2016.12.08 西暦 12:07 午前
CHINA 2016.12.08 公元 12:07 上午
US 2016.D.08 AD 12:07 AM

Localeを変えてもすべて12時7分になっていた
Localeの指定では午前やAMなどの表記が異なるようだ
よく考えるとアメリカ国内でも地域によって時差が違っているのにUSのみなのはおかしい・・・

TimeZoneを設定する

実際にタイムゾーンを考慮してフォーマットするためには
DateFormat#setTimeZone(TimeZone)を使えば良い

例:東京とニューヨークの例

sdf.setTimeZone(TimeZone.getTimeZone("Asia/Tokyo"));
Log.d(TAG, "format: " + sdf.format(date));
sdf.setTimeZone(TimeZone.getTimeZone("America/New_York"));
Log.d(TAG, "format: " + sdf.format(date));

結果は以下の通り

format: 2016.12.08 西暦 00:57 午前
format: 2016.12.07 西暦 10:57 午後

adbコマンドで引数に&などの記号がある場合の対処法

adbコマンドに文字列を渡す際に、&や|が入っているとうまく文字列が渡されないという問題がある
例えば、以下のようにテキストを入力するためのコマンドのadb shell input textに'&'や'|'が入っている文字列を渡すと途中までしか入力が行われなかったりエラーになってしまう

$ adb shell input text 'example&text'

対処方法は'&'や'|'など入力できない記号を '\' (バックスラッシュ)でエスケープしてあげることです
さきほどの例の場合は以下のようにすると無事入力ができました

$ adb shell input text 'example\&text'

EditTextへのプレーンテキストのペースト

AndroidのEditTextではペーストをすると以下のようにHTMLのタグが有効な状態で表示が行われます
今回はペースト時にプレーンテキストで貼り付けをする方法がないか調べてみました
(以下のコードはAPI24のソースコードを参照しています)

Screen Shot 2016-12-01 at 1.44.59 AM.png

ペーストの実装を調査

まずはEditTextでペーストを押した時にどのようなコードが実行されているか調べてみたところ、
onTextContextMenuItemの中の以下のコードのid=ID_PASTEの場合のコードが実行されているようです

switch (id) {
    …
    case ID_PASTE:
        paste(min, max, true /* withFormatting */);
        return true;
    case ID_PASTE_AS_PLAIN_TEXT:
        paste(min, max, false /* withFormatting */);
        return true;
    …
}

id=ID_PASTEの場合はhtmlが有効な状態の表示になります
プレーンテキストで貼り付けを行うにはid=ID_PASTE_AS_PLAIN_TEXTになるようにコードを実行させると良さそうです

EditText内でID_PASTE_AS_PLAIN_TEXTが使われているのは
public boolean onKeyShortcut(int keyCode, KeyEvent event)でCtrl+Shift+Vが押された時の場合のみのようです

@Override
public boolean onKeyShortcut(int keyCode, KeyEvent event) {
    …
    } else if (event.hasModifiers(KeyEvent.META_CTRL_ON | KeyEvent.META_SHIFT_ON)) {
        …
        case KeyEvent.KEYCODE_V:
            if (canPaste()) {
                return onTextContextMenuItem(ID_PASTE_AS_PLAIN_TEXT);
        }
        …
}

ただ、これは外付けキーボードなどがないと実行することはできそうにないです・・・

ちなみに ID_PASTE_AS_PLAIN_TEXTの実態はandroid.R.id.pasteAsPlainTextとなっています
これはAPI23(Android M)から追加された値です。そのためAPI23とそれ以前ではonTextContextMenuItemの実装が変わっていました

まとめ

今回調べたところEditTextでプレーンテキストを貼り付けさせる方法は提供はされているがスマホでは簡単には実行できそうにありませんでした
onTextContextMenuItemの実装を変更すると強制的にプレーンテキストのペーストにできそうではありましたが、バージョンごとに処理を変更したり、動作確認が大変なためあまり実用的ではなさそうですね