20200217のAndroidに関する記事は6件です。

DialogFragmentの利用方法

プロダクトの既存コードで使われているDialogFragmentを改善したので、その修正内容をメモしておきます

前提

実装予定のダイアログは以下のようなごく一般的なものとする

スクリーンショット 2020-02-17 0.43.17.png

修正前

UpdateConfirmDialogFragment.kt
class UpdateConfirmDialogFragment : DialogFragment() {
    var mTitle: String? = null 
    private var listener: OnFragmentInteractionListener? = null

    override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
        return AlertDialog.Builder(activity!!)
            .setTitle(mTitle)
            .setPositiveButton("ok") { _, _ ->
                listener?.onClickConfirmOk(confirmType)
            }
            .setNegativeButton("cancel") { _, _ ->
                listener?.onClickConfirmCancel(confirmType)
            }
            .create()
    }

    override fun onDetach() {
        super.onDetach()
        listener = null
    }

    fun setListener(listener: OnFragmentInteractionListener){
        this.listener = listener
    }

    interface OnFragmentInteractionListener {
        fun onClickConfirmOk()
        fun onClickConfirmCancel()
    }
}

※プロダクトコードを思い出しながら修正後のコードから作ったコードなので、誤りがあるかもしれません

以下、修正内容です

1. interfaceの設定方法変更

androidのactivity/fragmentは、インスタンス破棄後の復帰を考慮してライフサイクルメソッドで値を設定するべきです。今回のコードでは
ダイアログタイトルがpublic変数、イベントコールバック用のinterfaceがpublicメソッドを使って設定されていましたが、それぞれargument及びcontextから取得するようにしました。

UpdateConfirmDialogFragment.kt
    private val mTitle by lazy {
        arguments?.getString(ARG_Title, "") ?: ""
    }

    override fun onAttach(context: Context) {
        super.onAttach(context)
        listener = context as? OnFragmentInteractionListener
    }

    companion object {
        private const val ARG_TITLE = "ARG_TITLE"
        fun newInstance(title: String) =
            ConfirmDialogFragment().apply {
                arguments = Bundle().apply {
                    putString(ARG_TITLE, title)
                }
            }
    }

2. 類似要件のダイアログも生成できるようにする

修正を進めていたところ、ダイアログ自体の要件がほぼ変わらないDelete用のダイアログがあることがわかりました。そのため、インスタンス作成時にタイトルではなくTypeを渡すようにし、タイトルはその変数として保持させるようにしました。また、それをコールバックの引数として設定し、利用側で判別できるようにしました(※同一Activity内で2種類のダイアログ表示があった際の考慮です)。Fragment名は、より汎用的にするためにConfirmDialogFragmentにリネームしています。

ConfirmDialogFragment.kt
    override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
        return AlertDialog.Builder(activity!!)
            .setTitle(confirmType?.title)
            .setPositiveButton("ok") { _, _ ->
                listener?.onClickConfirmOk(confirmType)
            }
            .setNegativeButton("cancel") { _, _ ->
                listener?.onClickConfirmCancel(confirmType)
            }
            .create()
    }

    interface OnFragmentInteractionListener {
        fun onClickConfirmOk(confirmType: ConfirmType?)
        fun onClickConfirmCancel(confirmType: ConfirmType?)
    }

    sealed class ConfirmType : Serializable {
        abstract val title: String

        object Update : ConfirmType() {
            override val title = "更新しますか?"
        }

        object Delete : ConfirmType() {
            override val title = "削除しますか?"
        }
    }

    companion object {
        private const val ARG_CONFIRM_TYPE = "ARG_CONFIRM_TYPE"
        fun newInstance(targetFragment: Fragment? = null, confirmType: ConfirmType) =
            ConfirmDialogFragment().apply {
                arguments = Bundle().apply {
                    putSerializable(ARG_CONFIRM_TYPE, confirmType)
                }
            }
    }

3. コールバック先を選択できるようにする

更に実装を進めていると、このダイアログはActivityとFragment、どちらからも呼ばれる可能性があることがわかりました。activityはこれまで通りcontextから、fragmentはargumentに設定することでtargetとして取得できるようになります。fragmentの設定をオプションとして用意し、DialogFragment内では意識しないで使えるように拡張関数を用意して利用するようにしました。

// 拡張関数 フラグメントが設定されていた場合はそちらを利用する
fun <T> DialogFragment.getTarget() = (targetFragment ?: context) as? T

    override fun onAttach(context: Context) {
        super.onAttach(context)
        listener = getTarget()
    }

    companion object {
        fun newInstance(targetFragment: Fragment? = null, confirmType: ConfirmType) =
            ConfirmDialogFragment().apply {
                arguments = Bundle().apply {
                    putSerializable(ARG_CONFIRM_TYPE, confirmType)
                }
                setTargetFragment(targetFragment, 0)
            }
    }


修正後

ごちゃごちゃ言いながらリファクタしてきましたが、最終的には以下のようになりました。変数名やConfirmTypeの役割など修正の余地はまだあるかと思いますが、ひとまず最低限の拡張性と安定性は確保できたと思います。

DialogFragment.kt
class ConfirmDialogFragment : DialogFragment() {
    private val confirmType by lazy {
        arguments?.getSerializable(ARG_CONFIRM_TYPE) as? ConfirmType
    }
    private var listener: OnFragmentInteractionListener? = null

    override fun onAttach(context: Context) {
        super.onAttach(context)
        listener = getTarget()
    }

    override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
        return AlertDialog.Builder(activity!!)
            .setTitle(confirmType?.title)
            .setPositiveButton("ok") { _, _ ->
                listener?.onClickConfirmOk(confirmType)
            }
            .setNegativeButton("cancel") { _, _ ->
                listener?.onClickConfirmCancel(confirmType)
            }
            .create()
    }

    override fun onDetach() {
        super.onDetach()
        listener = null
    }

    interface OnFragmentInteractionListener {
        fun onClickConfirmOk(confirmType: ConfirmType?)
        fun onClickConfirmCancel(confirmType: ConfirmType?)
    }

    sealed class ConfirmType : Serializable {
        abstract val title: String

        object Update : ConfirmType() {
            override val title = "更新しますか?"
        }

        object Delete : ConfirmType() {
            override val title = "削除しますか?"
        }
    }

    companion object {
        private const val ARG_CONFIRM_TYPE = "ARG_CONFIRM_TYPE"
        fun newInstance(targetFragment: Fragment? = null, confirmType: ConfirmType) =
            ConfirmDialogFragment().apply {
                arguments = Bundle().apply {
                    putSerializable(ARG_CONFIRM_TYPE, confirmType)
                }
                setTargetFragment(targetFragment, 0)
            }
    }
}

fun <T> DialogFragment.getTarget() = (targetFragment ?: context) as? T

まとめ

  • publicでsetしている変数を見つけたら警戒する
  • 拡張関数を用意したものの、そのあたりの抽象化はもっといいやり方がありそう
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Android】Adaptive Launcher Icon の作り方と注意点

Androidでは、GooglePlayストアアイコンとは別に、デバイス側で使うアプリアイコンも作成する必要があります。
Android 8.0(API レベル 26)でアダプティブランチャーアイコンが導入されました。

これがまたやっかいで、過去OSにも対応したい場合、新旧の2パターンのアダプティブアイコンを作成しなければなりません。
新旧で仕様が全く異なるのでその作成方法をメモしておきます。アイコンを作成する際の参考になれば嬉しいです。

【2/17追記】
コメントを頂いて初めて知ったのですが、アダプティブランチャーアイコンを作成するための機能がなんとAndroidStudioに元からあるとの情報を頂きました。
なんてこったい、次はこのツールで是非作成してみたいと思います(感謝!)
https://developer.android.com/studio/write/image-asset-studio.html?hl=ja#access

アダプティブランチャーアイコンの仕組み

adaptive_icon_img.jpg

Adaptive Launcher iconは、foreground、background画像と、その上にMaskがかかった3枚構成となっています。
backgroundはエンジニア側でカラーを設定し、マスクは自動でかかるので、デザイナーは基本真ん中のForegroundの画像を作成すればOKです。

アダプティブランチャーアイコンの作り方

adaptive icon rule copy.jpg

1:108pxの正方形透明レイヤーを作成する
2:72pxの正方形オブジェクトを1のレイヤーの上に重ねる
3:直径66pxの正円オブジェクトを作成し2の上に重ねる
4:アイコンを正円内に配置する(丸内が表示保証されているエリア)
5:2と3のレイヤーを非表示にし、アイコンが表示された状態で1の正方形を書き出し設定する

・foregroundの画像は直径66dpの範囲外はアニメーションで隠れる可能性がある
・マスクはランチャー次第で正円形から四角まで色々ある

【注意点】

Risky zoneはマスクによっては範囲に含まれるので、外周にデザインがある場合は72dpまでデザインを入れること。そうしないとやり直すことになるので要注意!

用意する画像サイズ

Adaptive Launcher Iconを作る時に必要な画像サイズは下記の通り。
「背面」も「前面」も画像の場合は各サイズ2つずつ作成する。
「背面」はbackground-colorで指定することも可能なので、背景がベタ塗りの場合は、Foregroundの画像を各サイズ1つずつ用意すればOK。

解像度 比率 Adaptive Launcher icon(px)
mdpi 1 108×108
hdpi 1.5 162×162
xhdpi 2 216×216
xxhdpi 3 324×324
xxxhdpi 4 432×432

ファイル名:ic_launcher_foreground.png

Google Developers公式サイトはこちら

レガシーランチャーアイコンの作り方(おまけ)

98_03__ランチャーアイコン copy 2.jpg

・書き出し画像サイズ:48dp×48dp
・inner size:44dp (上下左右に2dpずつの余白をとる)

用意する画像サイズ

解像度 比率 Adaptive icon(px)
mdpi 1 48×48
hdpi 1.5 72×72
xhdpi 2 96×96
xxhdpi 3 144×144
xxxhdpi 4 192×192

ファイル名:ic_launcher.png

参考サイト

Google Developers▼
https://developer.android.com/guide/practices/ui_guidelines/icon_design_adaptive

ソースコードなどのご参照▼
https://qiita.com/takahirom/items/696fb5ecaa230fa8f755

GooglePlayStoreのアイコンの仕様はこちら▼
https://developer.android.com/google-play/resources/icon-design-specifications

おわりに

Adaptive Launcher Iconが追加されて表現は自由になったものの、まだ旧アイコンにも対応しないといけない場合、期間限定のアイコンなどを作成するのが意外と大変だということがわかりました。

今後ここらへんが統一されていけば、もう少し楽になるのではと期待しています。

以上、Adaptive iconで苦戦したkannaでした!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Android】アプリバーの文字・メニュー・背景色変更方法まとめ

概要

Androidアプリの画面上部のアプリバー(AppBar, ActionBar, Toolbarとも)に、メニューアイコンや「保存」のようなメニューボタンを設置したり、アイコン・文字や背景の色を変えたいことがある。方法を調べてまとめた。

アプリ・アクティビティ全体のスタイルを設定する

AndroidManifestの<application>でアプリ全体のテーマを設定できる。
<activity>で設定した場合はアクティビティ全体のテーマになる。

AndroidManifest.xml
<application
    ...中略...
    android:theme="@style/AppTheme" >
    ...中略...
</application>
styles.xml
<resources>
    <!-- Base application theme. -->
    <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
        <!-- Customize your theme here. -->
        <item name="colorPrimary">@color/colorPrimary</item>
        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
        <item name="colorAccent">@color/colorAccent</item>
        <item name="actionMenuTextColor">@color/menuText</item>
    </style>
</resources>
colors.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <color name="colorPrimary">#FF0000</color>
    <color name="colorPrimaryDark">#00FF00</color>
    <color name="colorAccent">#0000FF</color>
    <color name="menuText">#00FFFF</color>
</resources>
  • colorPrimary: アプリバー(やフローティングアクションボタン)などのUIで使われる基本色を設定する
    スクリーンショット 2020-02-17 12.22.01.png

  • colorPrimaryDark: 暗い部分の基本色を設定する
    スクリーンショット 2020-02-17 12.21.52.png

  • colorAccent: テキストボックスやダイアログなどのUIで使われる色を設定する
    スクリーンショット 2020-02-17 12.21.32.pngスクリーンショット 2020-02-17 12.17.57 のコピー.png

  • actionMenuTextColor: アプリバーのメニューの文字色を設定する
    スクリーンショット 2020-02-17 12.21.42.png

Fragmentごとにスタイルを変更する

上記のスタイル設定では、Fragmentごとの設定はできない。
各Fragmentでアプリバーの背景色・メニューなどのスタイルを変更する方法を紹介する。

アプリバーの背景色を変更する

activity?.supportActionBar?.setBackgroundDrawable()で色(ColorDrawable)をセットする。
一度色を変えるとFragment遷移してもそのままであるため、色を戻したい場合は、よしななタイミング(別画面に遷移する時など)で元の色をセットする必要がある。

Fragment.kt
activity?.supportActionBar?.setBackgroundDrawable(ColorDrawable(Color.WHITE))) // アプリバーを白にする

スクリーンショット 2020-02-17 12.39.01.png

アプリバーのタイトルの文字色を変更する

ベストプラクティスが分からないが、任意の色を付けたSpannableStringをセットするのが良さそうだろうか。

HTMLでゴリ押す

HTMLタグで文字に色を付けてセットする。

Fragment.kt
val titleHtml = "<font color=\"#FF0000\">HTMLタイトル</font>"
activity?.supportActionBar?.apply {
    setTitle(Html.fromHtml(titleHtml))
    setBackgroundDrawable(ColorDrawable(Color.WHITE))
}

スクリーンショット 2020-02-17 12.44.48.png

SpannableStringで色を付ける

ForegroundColorSpanというSpanで文字に色を付けてセットする。

val title = SpannableString("SpannableStringタイトル")
title.setSpan(ForegroundColorSpan(Color.RED), 0, title.length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
activity?.supportActionBar?.apply {
    setTitle(title)
    setBackgroundDrawable(ColorDrawable(Color.WHITE))
}

スクリーンショット 2020-02-17 12.46.39.png

アプリバーにメニューを表示する

onCreateOptionsMenu()内で任意のメニューをセットし、アプリバーにメニューアイコンや文字(「保存」「閉じる」など)を表示する。

Fragment.kt
    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        super.onCreateView(inflater, container, savedInstanceState)
        this.setHasOptionsMenu(true)
    }

    override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
        super.onCreateOptionsMenu(menu, inflater)
        inflater.inflate(R.menu.hoge_menu, menu)
    }
hoge_menu.xml
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">
    <item
        android:id="@+id/action_hoge_icon"
        android:icon="@drawable/hoge_icon"
        app:showAsAction="always" />
    <item
        android:id="@+id/action_hoge_text"
        android:title="hoge"
        app:showAsAction="always" />
</menu>

hoge_icon.png←hoge_icon.png
スクリーンショット 2020-02-17 13.26.48.png

アプリバーのメニューの文字色を変更する

onCreateOptionsMenu()内で該当のメニューアイテムを取得し、ForegroundColorSpan()をセットする。
SpannableString作成の際に文字列を変更することもできる。

Fragment.kt
    override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
        super.onCreateOptionsMenu(menu, inflater)
        inflater.inflate(R.menu.hoge_menu, menu)
        val item = menu.findItem(R.id.action_hoge_text) // メニューアイテム取得
        val s = SpannableString("fuga")
        s.setSpan(ForegroundColorSpan(Color.YELLOW), 0, s.length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
        item.setTitle(s) // メニューアイテムにSpannableをセット
    }

スクリーンショット 2020-02-17 13.33.37.png

アプリバーのメニューアイコンの色を変更する

onCreateOptionsMenu()内で該当のメニューアイテムのiconDrawable)を取得し、setColorFilter()で色を変更する。
フィルターの種類についてはPorterDuff.Modeを参照されたい。

Fragment.kt
    override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
        super.onCreateOptionsMenu(menu, inflater)
        inflater.inflate(R.menu.hoge_menu, menu)
        val item = menu.findItem(R.id.action_hoge_icon)
        item.icon?.apply {
            mutate() // Drawableを変更可能にする
            setColorFilter(Color.WHITE, PorterDuff.Mode.SRC_ATOP) // アイコンを白くする
        }
    }

スクリーンショット 2020-02-17 13.41.11.png

まとめ

アプリバーの文字色・メニューアイテムの色・背景色変更について、アプリ全体・個別Fragmentでの方法を紹介した。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

僕はただAndroidで自Serviceから他Activityにタッチイベントを発生させたいだけだったのに(ラノベ調

※最初に
この仕組みは使用者(ユーザ)にも実装者にもリスクがあるものと思ってます
自身まだ完全に内容や動きを理解しているわけではなく、あくまで参考記事として受け取ってもらえると助かります。

目的

わたくしめは趣味でソシャゲを嗜んでおります。
ゲームといえども効率化って大事だよね!

みんなも昔連打コンとか使ってたよね!

以上

実際に成功した実装内容について

たどり着く前に紆余曲折あったのですが結論から先に

ユーザ補助機能を使いましょう

ただし!この機能は非常に強力でありながら大変リスキーな機能となっております。
使用の際(特にアプリを配布したりする場合)はご自身でもよく調査してから使用する事を限りなくおすすめします。

ユーザ補助機能とは

目の見えない人とか一部の操作(スワイプとかダブルタップ)が身体的理由等で使えない人に向けた補助サービス用機能とういう解釈です。

ユーザ補助機能一部でどういった事ができるようになるのかは下記の記事がわかりやすいと思います。
https://www.slideshare.net/r-ralph/accessibilityservice

ようはなんでもできます(たぶん)

実装

①AndroidManifest修正
②Manifest用のXML作成
③Activity(Fragment)で権限許可リクエストをなげる
④AccessibilityService
の立ち上げ
⑤GestureDescription.Builder().addStroke でタップイベントを発生させる

①AndroidManifest修正

<service android:name=“xxx.xxxx.xxx.xxxService”
    android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
    <intent-filter>
        <action android:name="android.accessibilityservice.AccessibilityService"/>
    </intent-filter>
    <meta-data
        android:name="android.accessibilityservice"
        android:resource="@xml/service_config" />
</service>

AndroidManifestのapplication部に記述してください

<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.BIND_ACCESSIBILITY_SERVICE"
    tools:ignore="ProtectedPermissions" />

場合によっては

<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>

が必要かも
FOREGROUND_SERVICEはServiceを使おうとおもったらついてまわってくる権限です

@xml/service_configは次の項ででるやつです

②Manifest用のXML作成

res/xml内に下記ファイルを追加

<?xml version="1.0" encoding="utf-8"?>
    <accessibility-service
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:accessibilityEventTypes="typeAllMask"
    android:accessibilityFeedbackType="feedbackAllMask"
    android:accessibilityFlags="flagDefault"
    android:canRetrieveWindowContent="true"
    android:canPerformGestures="true"
    android:description="@string/accessibility_desciription
"
    />

各設定の詳細はここ見てください
https://developer.android.com/reference/android/accessibilityservice/AccessibilityServiceInfo

タッチ挙動させる場合特に重要なのは
canPerformGestures
こやつです

③Activity(Fragment)で権限許可リクエストをなげる

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    setContentView(R.layout.activity_main)

    // OVERLAY許可
    if (!Settings.canDrawOverlays(this)) {
        val intent = Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:" + getPackageName()))
        this.startActivityForResult(intent, REQUEST_SYSTEM_OVERLAY)
    }

    val intent2 = Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS)
    intent2.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY)
    intent2.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)

    this.startActivity(intent2)

}

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
    super.onActivityResult(requestCode, resultCode, data)

    when (requestCode) {
        REQUEST_SYSTEM_OVERLAY -> {
            if (resultCode == Activity.RESULT_OK) {


            } else {
                finish()
            }
        }
        else -> {
            finish()
        }
    }
}

いろいろやっつけですまんの

いっこ注意点としてはACTION_ACCESSIBILITY_SETTINGSはstartActivityForResult
で投げても意味がありません
(たぶん
なので設定がONかOFFか調べるのも結構苦労する

④AccessibilityService

の立ち上げ

    val settingValue = Settings.Secure.getString(
            this.getContentResolver(),
            Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES
    )

    if (settingValue.isNotEmpty()) {
        val simpleStringSplitter = TextUtils.SimpleStringSplitter(':')

        simpleStringSplitter.setString(settingValue)

        val name : String = packageName + "/" +  xxxService::class.java.canonicalName
        while (simpleStringSplitter.hasNext()) {

            if (simpleStringSplitter.next().equals(name)) {
                startService(Intent(this, xxxService::class.java))
                return

            }

        }

    }

ユーザ補助の設定がONがどうか調査しつつServiceを立ち上げ

⑤GestureDescription.Builder().addStroke でタップイベントを発生させる

class xxxService :AccessibilityService() {

継承はAccessibilityServiceを継承する事。

                var point : Point = Point()

                mWindowManager.getDefaultDisplay().getSize(point)

                val gestureBuilder = GestureDescription.Builder()
                val path = Path()

                path.moveTo(tapPosX,tapPosY)

                gestureBuilder.addStroke(GestureDescription.StrokeDescription(path,0L,10L))
                val gestureDescription = gestureBuilder.build()

                val flg : Boolean = mAccessibilityService.dispatchGesture(gestureDescription,object : AccessibilityService.GestureResultCallback(){
                    override fun onCompleted(gestureDescription:GestureDescription) {
                        super.onCompleted(gestureDescription);
                    }

                    override fun onCancelled(gestureDescription: GestureDescription?) {
                        super.onCancelled(gestureDescription)
                    }
                },null)

実際のタップしたときのイベントはこんな感じです。

これとは別にVerによってはnotificationを設定しないとそもそもServiceが立ち上がらないのでご注意を

課題

・AccessibilityServiceの場合Activityから終了させる事ができない。(Service自身が自身を終了させる必要がある)
・Serviceは今表示されているActivityが何かを保証していない
・Serviceはたまにシステムから消される
・重い処理を書きづらい
・AccessibilityEventの全容がわからない

いやー!理解せず使って連打処理とか仕込んで意図せず課金しちゃったり電話しちゃったりしたら大変だね!

なので個人用には使えるけどアプリ配布まではまだまだ勉強不足かなといった感じです

ここに至るまで

超大変だった。
ぐぐって出てきたものも色々試してみたんですけど中々やりたい事に辿りつけなかったですね

ただ調査開始から試した事、最後にこれに至ったまでを話すと長くなるので割愛します。

最後に

「Serviceは今表示されているActivityが何かを保証していない」に関してはいくつか解決策を見つけたので時間があればまた記事にするかも

ご意見等ありましたらお手柔らかにお願いします

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Android アプリ公開時に必要な画像リソースをサックっと用意する手順まとめ

みなさん、Androidアプリ公開してますか?

自分はアプリを作るのは好きなのですが、それを公開するのにあれこれ手間をかけるのは正直好きではありません、、、

簡単な自作アプリはだいたい自分が使うためにつくっているのでそれでも問題ないといえばないのですが、ビルド環境やライブラリの依存関係その他最新の開発環境全般につねにキャッチアップしておく、というのがモチベーションの一つでもあるので、いつでも更新し、リリース出来るような環境を整えたいと思うことがままあります。

その昔、Androidがリリースされたばかりの頃は、ストアには審査なんてないし、特にリソースを追加しなくてもapkをサクッとアップロードすればOKだったのですが、世知辛い世の中になったものよのう。

と、言っていてもしょうがないので、アプリをつくったらサクッと公開するために必要なリソースを取得する手順をまとめておきます。

必要なリソース
AndroidアプリをGooogle Play Storeで公開する上で必須の画像リソースは以下3種類です。

  • アイコン
  • スクリーンショット
  • feature graphic

ストア用のアイコンを用意する

Google Play Storeに公開するときに、アプリ内のリソースに含まれるアイコン以外に、大きいサイズ(512*512)のアイコンをアップロードする必要があります。

こちらを作るには、アプリ内のアイコンリソースと合わせて「Android Asset Studio」を使いましょう。

Android Asset Studio - Launcher icon generator

ここでは使い方の詳細は省きますが、画像や用意されているクリップアートをもとにアイコン画像が作れるツールです。

背景色や形などを選び、ダウンロードボタンでzipファイルをダウンロードすると、アプリの'res'以下に含める画像とともに"web_hi_res_512.png"という名前のファイルが含まれていて、こちらがPlay Storeにアップロードするアイコンとして使えます。

スクリーンショットを取る

次に、アプリのスクリーンショットが最低2枚必要となります。こちらはアプリを実機もしくはエミュレータで起動し、スクリーンショットを取ればいいので一見難しいことはありません。

ところが、このスクリーンショットにはステータスバーも含まれるため、起動中の他のアプリの通知アイコンなどが写ってしまいます。ストアに公開するスクリーンショットとなると、他のアプリのアイコンが写っていていいのか、時刻が夜中だったりすると良くないんじゃないか、などいろいろと考えることが出てきてしまいます。

そんなときのために、Androidには"Demo Mode"という、ステータスバーの表示内容を自由に書き換えられる機能が用意されています。

Developer Optionを有効にした端末で、Settingsの"System -> Developer options -> System UI demo mode"でDemo ModeのON/OFFが切り替えられます。

ただし、UI上では細かな設定が出来ないことと、毎回手で操作するのも手間なので、adbを使ってCLIで実行するのがいいでしょう。

adbコマンドは以下が詳しいです。

Demo Mode for the Android System UI

さて、ここではアプリの通知アイコンを消し指定のみを行って、スクリーンショットを取ることを考えましょう。

Demo Modeの設定とともに、スクリーンショットの取得までadbで行得るので、以下をtake_release_screenshot.shなどスクリプトとして保存しておくと取り直しも楽になります。

#!/bin/sh

adb shell settings put global sysui_demo_allowed 1
adb shell am broadcast -a com.android.systemui.demo -e command notifications -e visible false
adb exec-out screencap -p > screen.png
adb shell am broadcast -a com.android.systemui.demo -e command exit

Feature Graphicを作成する

最後にFeature Graphicを作成します。Feature Graphicは1024 w x 500 hの画像で、Play Storeでオススメなどに掲載された場合に使われます。また、ストアのリンクを貼った場合にogp画像としても使われます。

そのため、アプリのユーザー獲得のためには非常に重要な画像となるのですが、そんなことよりまずは手間を最小限にして公開したくなりますよね^^

そんな方に最適なツールがこちらです。

Android Feature Graphic Generator

文字と背景を選ぶだけで、サクッと作れます。

必須ではないリソースの"promo graphic"や"TV Banner"向けのサイズも用意されています。

まとめ

以上で、AndroidアプリをGoogle Play Storeで公開する際に必須となっている画像リソースを手軽に用意できます。

画像以外にも"Title", "Short Description", "Full description"が必要になりますが、こちらはhogeでもfugaでもOKなので、困ることはないでしょう。

こうしてリリースしたのが、こちらの超シンプルは時報アプリです。しっかりFeature GraphicがOGPに使われていますね!

シンプル時報 - Apps on Google Play

個人アプリを作る理由として、オリジナリティのある名前をつけたり、ビジュアル面やプロモーションまでこだわることがモチベーションとなっている方もいると思いますので、そういう場合はもちろんこだわって作るのがいいでしょう。

一方で、動くものをサクッとつくって公開したい、というようなモチベーションの方はこのような方法でリリース作業にかかる負荷を下げるのがいいんじゃないかと思ってます。

アプリつくったけど公開めんどくさくて諦めた、、、って方はぜひ参考にしてみてください!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Flutterでコンパスアプリを作ってリリースした話

はじめに

個人開発でFlutterを使用して、コンパスアプリを作り、リリースしてみましたので、その実装方法や使用したライブラリなどを紹介してみようと思います。

作ったアプリ

作ったアプリはコンパスですが、ただのコンパスでなく、恵方巻コンパスです。
普通のコンパスとの違いは、「北を差すのでなく、恵方を差す」というだけです!

恵方巻コンパスアプリ自体は2年前にiOSアプリをリリースしていて、去年はAndroid版もリリースしていたのですが、今回はAndroid版のみFlutterで作り直してリリースしました。
スクリーンショット 2020-02-15 6.18.52.png
恵方巻コンパス2020

Flutterで作った理由

自分は普段仕事でiOSアプリの開発を行っているので、iOSは慣れているですが、Androidについてはあまり詳しくないです。
詳しくないながらも、去年kotlinで作ってリリースしたのですが、今年、恵方の方向を2020年版にアップデートしようと思ったところ、APIレベルの更新やAndroidX対応など案外とやることがありそうだったので、どうせならFlutterで作り直した方が簡単に出来るのではと思ったことが、今回、Android版のみをFlutterで作った理由です。(Flutterを触ったことがあったのも理由の一つです。)

Flutterでコンパスの実装方法

まず、Flutterでコンパスアプリを作るにはどうしたらよいか検索したところ、flutter_compassというライブラリを使用してコンパスアプリを作る記事があり、それを参考に実装しました。

参考にした記事

サンプルコード

恵方を指すコンパス自体は、50行くらいで実装できます。
参考にした記事のコードをproviderを使用して、シンプルに書き換えてみました。

class CompassPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider<Compass>(
      create: (_) => Compass(),
      child: Consumer<Compass>(
        builder: (context, compass, child) {
          return Scaffold(
            appBar: AppBar(
              title: Text('タイトル'),
            ),
            body: Center(
              child: Transform.rotate(
                angle: compass.angle,
                child: Image.asset(
                  'images/arrow.png',
                  width: 100,
                  height: 100,
                ),
              ),
            ),
          );
        },
      ),
    );
  }
}

class Compass with ChangeNotifier {
  static const ehoAngle = 255.0;

  double _angle = 0;
  double get angle => _angle;

  Compass() {
    FlutterCompass.events.listen((value) {
      _angle = -1 * pi * ((value - ehoAngle) / 180);

      notifyListeners();
    });
  }
}

githubに公開しておきました。

flutter_compass

FlutterCompassのイベントをlistenしておくと、端末の方向が変わる度に、呼び出されます。
角度が取得できるので、それをラジアンに変換し、notifyListenersで画面に通知しています。ラジアンに変換する際に、実際の角度から恵方の角度を引いた値を指定しています。(2020年の恵方は255度なので、255を引いています。)

FlutterCompass.events.listen((value) {
    _angle = -1 * pi * ((value - ehoAngle) / 180);

    notifyListeners();
});

flutter_compass

Transform.rotate

Transform.rotateを使用して、画像を傾けて表示しています。

Transform.rotate(
    angle: compass.angle,
    child: Image.asset(
        'images/arrow.png',
        width: 100,
        height: 100,
    ),
),

Transform.rotate constructor

その他、使用したライブラリ

flutter_launcher_icons

アイコンを簡単に作成できるライブラリです。
https://pub.dev/packages/flutter_launcher_icons

simple_animations

アニメーションを実装するために使用しました。
https://pub.dev/packages/simple_animations

vibration

バイブレーションを実装するために使用しました。
https://pub.dev/packages/vibration

firebase_admob

admobの広告を出すためのライブラリです。
https://pub.dev/packages/firebase_admob

最後に

今回紹介したAndorid版のアプリは2000ダウンロード程度でした。
iOS版は1日で40000ダウンロードされたので、恵方巻という完全に日本人向けのアプリでは、iOSの方がまだまだ有利なのかなと感じました。

Flutterはマルチプラットフォームのframeworkですが、iOS、Androidのどちらかのみをリリースしているアプリの場合、リリースしていない方をFlutterで開発してみるのは、学習コストや将来性の面でもありかなと思いました。(良い感じに出来たら、その後は両OSをFlutterに移行する事も可能ですし!)

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む