Material Design データのアプリ内導入について

はじめに

  • 企画/デザインの方が Mockアプリサービス でデザインと挙動提示
  • そのMockと同じ挙動をするようにしてよ! がよくあるんだけど、素材パーツが提供されないことがよく有り。

という事がよくあって、作業備忘録的に手順を整理して見たメモ

動作環境

導入手順

qiita.com

なコードを改変して使っている感じ。

  • app/build.gradle
android {
    ...
    dataBinding {
        enabled = true
    }
}

dependencies {
    ...
    compile "com.mikepenz:iconics-core:2.8.7@aar"
    compile 'com.mikepenz:fontawesome-typeface:4.7.0.0@aar'
}
  • CustomApplication.java
protected void attachBaseContext(Context base) { 
       if(base instanceof Activity){ 
            super.attachBaseContext(IconicsContextWrapper.wrap(base)); 
       } else{ 
            super.attachBaseContext(base); 
       }
} 

Qittaの記載より、こう書くほうが楽。

ただし適応できるのはActivityのみでソレ以外に適応するとNullPointerExceptionで落ちる

  • CommonAdapter.java
public class CommonAdapter {

    @BindingAdapter(value = { 
          "drawable_icon_font", 
          "drawable_icon_font_color" 
    },requireAll = false)
    public static void setDrawableIconFont(final View view, final String icon,Integer hint_color) 
   {
        if(hint_color != null){
                hint_color = android.R.color.holo_orange_light; //hintのカラー指定
        }

        final Context context = view.getContext();
        final IconicsDrawable iconicsDrawable = new IconicsDrawable(context)
                .icon(FontAwesome.Icon.valueOf(icon))
                .color(hint_color)
                .sizePx((int)(view.getWidth()));
        setBackground(view,iconicsDrawable);
    }

    public static void setBackground(View view, Drawable drawable) {
       if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
            view.setBackground(drawable);
       } else {
           view.setBackgroundDrawable(drawable);
       }
    }
}

自分が使ってるのは FontAwesome で

  • ただしfaw系は - を _ にする必要があり

ここらへんはまずはじめに戸惑う箇所かな・・*1

一応こういうルールっぽい

<TextView
            android:text="{faw-android} android"    //★fontデータとして使う場合はそのまま
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginBottom="16dp"
            android:textColor="@color/colorAccent"
            android:textSize="30sp"
            />

        <TextView
            android:text="android"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textColor="@color/colorAccent"
            android:textSize="30sp"
            android:drawablePadding="4dp"
            app:drawable_icon_font_left="@{`faw_android`}" //名称検索する場合は - => _変換が必要
            />

Qittaの例をそのままもってきた時に動かなかったケース

  • textColorをdata-bindingで連動させて hint_color を変更させようとした

改変前(NG)

   <TextView
            tools:text=">"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textColor="@{ status.isToday() ? @color/colorGray : @color/colorAccent }"
            android:textSize="10sp"
            android:drawablePadding="2dp"
            app:drawable_icon_font_left="@{`faw_android`}"
            />
  • BindingAdapterManager.java
public class BindingAdapterManager {
    @BindingAdapter("drawable_icon_font_left")
    public static void setDrawableIconFontLeft(final TextView tx, final String icon) {
        final Context context = button.getContext();
        final IconicsDrawable iconicsDrawable = new IconicsDrawable(context)
                .icon(FontAwesome.Icon.valueOf(icon))
                .color(tx.getCurrentTextColor())
                .sizePx((int)(tx.getTextSize() * 1.25));
        tx.setCompoundDrawables(iconicsDrawable, null, null, null);
    }
}

改変後(OK)

   <TextView
            tools:text=">"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textColor="@color/colorAccent"
            android:textSize="10sp"
            android:drawablePadding="2dp"
            app:drawable_icon_font_left="@{`faw_android`}"
            app:drawable_icon_font_color="@{ status.isToday() ? @color/colorGray : @color/colorAccent }"
            />
  • BindingAdapterManager.java
public class BindingAdapterManager {

    @BindingAdapter("drawable_icon_font_left")
    @BindingAdapter(value = { 
          "drawable_icon_font_left", 
          "drawable_icon_font_left_color" 
    },requireAll = false)
    public static void setDrawableIconFontLeft(TextView tx,String icon,Integer hint_color) {
        if(color == null){
              hint_color = tx.getCurrentTextColor();
        }
        final Context context = button.getContext();
        final IconicsDrawable iconicsDrawable = new IconicsDrawable(context)
                .icon(FontAwesome.Icon.valueOf(icon))
                .color(hint_color)
                .sizePx((int)(tx.getTextSize() * 1.25));
        tx.setCompoundDrawables(iconicsDrawable, null, null, null);
    }
}

因みに、

  • final指定してるコードをよく見るが、まずいらない・・
  • Integer hint_color を int hint_color と指定してしまうと省略するとエラーで落ちる

問題点

  • fontデータ同梱なのでapkサイズが増える
  • download Fontの機能みたいなのできないと厳しいよな・・(汗

speakerdeck.com

使えないパターン

これ Prefrences用のxml/setting.xml とかには使えないので、

その場合は Material Design のサイトに行って検索、下記プラグインでresource配置

  • Android Material Design Icon Generator
    • Material Designのサイトから色指定して res/drawable に変換取り込みしてくれる
    • 基本24dpと指定されている画像を使わないと駄目みたい
  • Android Drawable Viewer
    • res/drawable 配下の画像確認用
    • AS3.0(AS2.4でも)では動かない

あたりで凌いでる感じですかね・・・。中々厳しい・・。

それ以外でハマるケース(運用面?)

ここらへんは運用バグとしか言えない気もする・・

  • デザイナーさんからもらうデータ
    • 画像に余白があるものが多い
  • Material Design系のデータ
    • 基本余白なし

まあ余白なしの方が調整しやすいんだけど、デザインは基本別工数、別発注扱いだしなー。

ちゃんと管理しているというなら計算してほしいよ・・・

で、具体的にコードで書くと下記なイメージになるわけです

@BindingAdapter("drawable_icon_font")
    public static void setDrawableIconFont(final View view, final String icon) {
        final Context context = view.getContext();

        int color = R.color.colorGray;
        if(context instanceof DetailActivity){
            color = R.color.colorAliceBlue;
        }

        //[TODO] playアイコンは画像に変更
        if(icon.equals("faw_play")){
            final Drawable drawable = ContextCompat.getDrawable(context,R.drawable.btn_play);
            final int size = DisplayUtils.pixelFormat(context,72);
            //final int size = (int)(view.getWidth());

            //sizePx(size); same
            drawable.setBounds(0, 0, size, size);
            drawable.invalidateSelf();

            view.post(new Runnable() {
                @Override
                public void run() {
                    //画像リサ;イズ
                    ViewGroup.LayoutParams params = view.getLayoutParams();
                    params.width = size;
                    params.height = size;
                    view.setLayoutParams(params);

                    setBackground(view, drawable);
                }
            });
            return;
        }

        final int size = DisplayUtils.pixelFormat(context,48);

        final IconicsDrawable iconicsDrawable = new IconicsDrawable(context)
                .icon(FontAwesome.Icon.valueOf(icon))
                .color(ContextCompat.getColor(context,color))
                .sizePx(size);

        view.post(new Runnable() {
            @Override
            public void run() {
                ViewGroup.LayoutParams params = view.getLayoutParams();
                params.width = size;
                params.height = size;
                view.setLayoutParams(params);
                setBackground(view, iconicsDrawable);
            }
        });
    }
  • View自身のsetBounds
  • Viewの親レイアウトのLayoutParams

でサイズ調整するという形。どっちか片方だけだと駄目。

view.post にしているのは UIThreadで実行されていないエラーがたまに出るため。

タイミング的な問題だけど、ココらへんの割り込みタイミングってG様黒魔術っぽいからなー(遠い目 *2

public class DisplayUtils {
    public DisplayUtils() {
    }

    public static int pixelFormat(Context context, int dp) {
        float density = context.getResources().getDisplayMetrics().density;
        int px = (int)((float)dp * density + 0.5F);
        return px;
    }

    public static int getDisplayWidthPixels(Context context) {
        DisplayMetrics metrics = context.getResources().getDisplayMetrics();
        return metrics.widthPixels;
    }

    public static int getDisplayHeightPixels(Context context) {
        DisplayMetrics metrics = context.getResources().getDisplayMetrics();
        return metrics.heightPixels;
    }

    public static int getDisplayStatusBar(Context context) {
        DisplayMetrics metrics = context.getResources().getDisplayMetrics();
        return (int)Math.ceil((double)(25.0F * metrics.density));
    }
}

*1:文字列指定なので、エラーでないので実行時クラッシュする

*2:時間のかかる処理ほど、この対応が必須みたい