大学院生のアプリ開発奮闘記

将来への不安を抱く大学院生二人がアプリ開発に奮闘する困難と過程を綴っていくブログです.現在androidアプリ開発中

Andriod : xmlで光る文字を表示する

皆さんこんにちは!

大学院生のきゅうりです。

 

アプリの次回作は絶賛開発中ですので、

しばしお待ちください!

 

光る文字を表示する

 

本日はandroidアプリで

光る文字を

表示させる方法を

TIPSとして紹介します!

 

画像のような

光るように見える文字

を表示する方法です!

 
f:id:cheerapps:20180506134635p:plain:w300

 
 

さて、androidで開発する際に

文字の色やサイズを変更する方法として、

以下の二つの方法があります。

 

・プログラムファイル(.java)を編集

・レイアウトファイル(.xml)を編集

 

後者の方が見た目に分かりやすく、

初心者でも編集しやすくなっています!

というわけで、今回は

xmlファイルを編集することによって

行います!!

 

 

新規プロジェクト作成

 

まず、Android Studio

Empty Activityの新規プロジェクトを作成します。

(方法がわからない人はググってください><)

 

 

activity_main.xmlのTextivewの<>内は

次のようになっていると思います!

<TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="Hello World!"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintLeft_toLeftOf="parent"
    app:layout_constraintRight_toRightOf="parent"
    app:layout_constraintTop_toTopOf="parent"
    />


実行するとこんな感じです!

 
f:id:cheerapps:20180506134152p:plain:w250


 

下準備

 

違いを見やすくするために

①文字を大きくする

②文字色を緑色にする

③背景を黒にする

以上の作業を行います

Text Viewの<>内にこれらを追加します。

 

android:textSize="64sp"
android:textColor="#95CC1F"
android:background="#000000"

 
実行するとこうなります!


f:id:cheerapps:20180506131037p:plain:w250

 

これで準備が整いました!

 

光らせよう!

 

さて、ここから文字を光らせていきたいと思います!

本来は影を付けるために使用する属性を
使用します!

先ほど同様、
以下の部分をTextviewの<>内に追加します!

android:shadowColor="#BAFF26"
android:shadowRadius="40.0"
android:shadowDx="0.0"
android:shadowDy="0.0"

shadowColorは影の色、

shadowRadiusは影のぼやけ具合、

shadowDx,shadowDyは元の文字の座標からの移動位置を

それぞれ表します。

 

これを実行すると、、、

 
f:id:cheerapps:20180506131018p:plain:w250
 

どうでしょう?

不思議と文字が光っているように、

蛍光色に輝いているように見えます!

 

仕掛けとしては、

文字の色より明度の高い色影として設定し、

同じ位置でぼやかす

光って見えるといった感じです!

 

shaowRadiusの値は、

文字の大きさやフォントに応じて

調整してください。

 

また、今回影の色を決める際には

こちらのサイトを参考にしました。

明度の違う色を簡単に選べて便利でした!

配色探し!テーマカラーにあう色を検索

 

まとめ

 

以上いかがだったでしょうか!

影をうまく活用することで

文字を光ったように見せることができました!

 

僕たちCheerAppsが開発したアプリ

WidgetCounterの、テーマ”デジタル”でも

光る文字が表示されるように工夫しています!

(アプリでは別の方法によって実現しています)

是非アプリをインストールしてご確認ください!

ウィジェトカウンター
インストールはこちら 


 

 

AppWidgetProviderで複数のクリックイベントを扱う

はじめに

皆さんこんにちは,shiitaです.
今回は,ウィジェット実装で私が詰まった部分についての話をしたいと思います.

ウィジェットのクリックイベントの処理は特殊で,ブロードキャストを行うことで実装します.この記事では複数のクリックイベントを扱う際の注意点と,Oreoからの暗黙的なブロードキャストの制限についての対策を書いていきます.

ウィジェットのクリックイベントの処理方法をある程度知っていることが前提の記事です

PendingIntentの作成

ここで注意すべきことは次の3つです.

  • Intentを明示的にする
  • requestCodeを一意にする
  • ブロードキャスト受け取り時の処理分岐を可能にする

以下のコードでIntentrequestCodeの作成をします.

private enum class ClickType {
     TYPE1, TYPE2, TYPE3
}

private fun createIntentAndRequestCode(context: Context, appWidgetId: Int, type: ClickType): Pair<Intent, Int>{
    val intent = Intent(context, WidgetCounter::class.java).apply {
        putExtra(APP_WIDGET_ID, appWidgetId)
        putExtra(CLICK_TYPE, type)
    }
    val requestCode = appWidgetId * ClickType.values().size + type.ordinal
    return Pair(intent, requestCode)
}

private const val CLICK_TYPE = "clickType"
private const val APP_WIDGET_ID = "appWidgetId"

その後,PendingIntent.getBroadcast()でブロードキャストするPendingIntentを生成し,RemoteViews.setOnClickPendingIntent()でクリックが発生した時の処理をセットします.

val (intent, code) = createIntentAndRequestCode(context, appWidgetId, ClickType.TYPE1)
// viewsはウィジェット本体(RemoteViews)
views.setOnClickPendingIntent(R.id.layout_id, // クリックイベントを設定するviewのID
    PendingIntent.getBroadcast(context, code, intent, PendingIntent.FLAG_UPDATE_CURRENT))

Intentを明示的にする

Oreoから暗黙的なブロードキャストに制限がかかるため,自身(AppWidgetProvider)に対して明示的なブロードキャストする必要があります.Intent(context, WidgetCounter::class.java)の様にクラスを直接指定します.

requestCodeを一意にする

ブロードキャストするPendingIntentには一意なrequestCodeを割り当てる必要があります.ウィジェットの一意なIDであるappWidgetIdを利用することで,一意なrequestCodeを生成することが出来ます.

val requestCode = appWidgetId * ClickType.values().size + type.ordinal

後述するClickTypeが異なればappWidgetIdが同じでもrequestCodeが区別出来るようにするため,ClickTypeごとにtype.ordinalで値をずらしています.

ブロードキャスト受け取り時の処理分岐を可能にする

処理分岐のためにenum classであるClickTypeを定義します.Intの定数を複数定義して,その値によって処理を分岐させても良いのですが,enum classの方が便利なのでこちらを利用しました.IntentputExtra(CLICK_TYPE, type)ClickTypeを渡して,ブロードキャスト受け取り時に分岐が出来るようにします.

onReceiveでイベント処理

onReceive では,クリックイベント以外のブロードキャストも受け取ってしまう点に注意します.ウィジェットの更新を行うACTION_APPWIDGET_UPDATEなどのIntentも受け取り,スーパークラスで処理されるため,フィルタリングをする必要があります.

override fun onReceive(context: Context, intent: Intent) {
    super.onReceive(context, intent) // ACTION_APPWIDGET_UPDATEなどのIntentが処理される
    if (!intent.hasExtra(APP_WIDGET_ID) || !intent.hasExtra(CLICK_TYPE)) return

    val appWidgetId = intent.getIntExtra(APP_WIDGET_ID, 0)
    when (intent.getSerializableExtra(CLICK_TYPE) as ClickType) {
        ClickType.TYPE1 -> {
            // 処理1
        }
        ClickType.TYPE2 -> {
            // 処理2
        }
        ClickType.TYPE3 -> {
            // 処理3
        }
    }
}

おわりに

明示的なブロードキャストという不思議なことをしていますが,これでOreoでも動くようになります.同じrequestCodeを利用したために,処理分岐が出来ないというミスで数時間無駄にしてしまったので,同様の原因で詰まっている人の助けになればいいなと思います.

質問などありましたら,コメントして頂けると嬉しいです.
この記事の内容を活用してCheerAppsが作成した「ウィジェットカウンター」は こちらからダウンロードできます!

play.google.com

ではでは.

AndroidとiOSどっちで開発した方がおトクか?

皆さん、こんにちは!

ご無沙汰してます!

大学院生のきゅうりです!

 

更新お待たせしました!!

 

本日は個人でアプリを開発する際に、

お金の面で見ると

AndroidiOSiPhoneとかiPad

どちらのOSで開発したほうがお得なのか??

ってことについてまとめていきます!

f:id:cheerapps:20180426005255p:plain

なお今回は、

多くの個人の開発者がとるであろう、

広告収入型の無料アプリのケースを想定しています!

 

 

エンジニア特化型Q&Aサイト【teratail】

 

 

アカウント登録料

 アプリを公開するためにはアカウントに登録する必要があります

 

Androidでは「Google Play デベロッパーアカウント」、

iOSでは「iOS Developer Proglam」に登録する必要があります。

 

それぞれの費用は、、、

 

Google Play デベロッパーアカウント

 登録料$25(約2750円)初回のみ

iOS Developer Proglam

 参加費$99(約10900円)/年

 

となっています。

Androidの場合初めの登録料のみ支払えば良いですが、

iOSだと毎年参加費が必要です。

圧倒的にAndroidの方が低コストで開発できますね!

 

 

勝者:Android

 

 

シェアについて

OSの使用者が多い方が、当たり前ですが、

アプリのユーザも増え、広告収入も増えます。

 

ではシェアはどちらの方が高いのでしょうか??

 

2017年12月時点で

日本:Android 44.8%   iOS 55.2%

世界:Android 73.5%   iOS 19.91%

 

となっています。

参考:

https://www.kantarworldpanel.com/global

http://gs.statcounter.com/os-market-share/mobile/

 

日本国内ではほぼ半々ですが、

世界的にみるとAndroid方が圧倒的に高いシェアです。

 

 

日本限定のアプリなら:引き分け

世界でリリースするなら:Android

 

 

 広告単価

1広告あたりの収入が高い方が

アプリの広告による収入に関する指標として

eCPMというものがあります。

 

eCPMとは、、、広告1000回表示あたりの収益

 

です!

使用する広告によっても異なりますが、

今回は最もメジャーな広告であるAdMobのケースで

eCPMを比較してみます!

 

2018年3月地点で

Android $2.01(約220円)

iOS $4.45(約490円)

 

となっています

参考:http://ecpm.adtapsy.com/

 

貼られている広告の種類によって価格が異なるので、

(例えば、動画広告の方が画像の広告より単価が高いなど)

一概には言えませんがこの指標で判断するとiOSの方が

1ダウンロードあたりの広告収入は期待できそうですね。

 

勝者:iOS

 

 

まとめ

以上のように、個人で無料アプリを作るなら、、、どっちのOS??

という話について

金銭的な観点からAndroidiOSどちらがおトクか比較してみました!!

総合的に判断すると

Androidの方が敷居は低いのかな!って印象です。

僕らも、まずはAndroidで開発をしています!

CheerApps開発アプリ”ウィジェットカウンター”

 

 

美しいホームページを作るなら Z.com

 

 

6行書くだけで誰でもできるウィジェット開発

皆さん,はじめまして.
CheerAppsで主にAndroidアプリ開発をしているshiitaです.
Androidアプリ開発で役立つTIPSなどについて、
伝えていけたらいいなと思います!

しばらくの間は、ウィジェットカウンターの開発で学んだことについて,
記事をいくつか書いていきます!


今回は簡単なウィジェットの開発について話したいと思います.
具体的には付箋のようにメモができるウィジェットを作ります!
使用するプログラミング言語は,私の一番好きなKotlinで書いていきたいと思います.

AndroidStudioで自動生成されるサンプルのプログラムをうまく活用して,
数行のコード編集だけで作れるものを紹介します.




プロジェクトの作成

今回作るウィジェットは次の2つの機能を持ちます.

  1. ホーム画面に貼り付けられるウィジェットの本体
  2. ウィジェットの内容を書き換える設定画面

アプリを起動すると最初に表示される画面は不要なので,
下の画像のようにAdd No Activityを選択します.


f:id:cheerapps:20180417003635p:plain




ウィジェットの追加

右クリックをして出てくるメニューから,Widgetを選択し,追加します.


f:id:cheerapps:20180417003643p:plain


下の画像のように,クラス名やウィジェットのサイズなどを設定していくのですが,
ここで1つポイントがあります.


f:id:cheerapps:20180417003651p:plain


Configuration Screenにチェック


これを行うことで,ウィジェットの設定画面もAndroidStudioが自動生成してくれます!
できるだけ簡単にウィジェットを作って行きたいため,
楽できるところは楽して行きましょうwww




コードの編集

付箋のようにメモができるウィジェットを作るために,
コードの編集をしていきます.
編集する箇所はたったの6行です!!!

エラーの除去

AndroidStudioで自動生成されるコードはJavaで書かれたもので,
その後にKotlinへのコンバートが行われます.
このときに入り込んでしまうエラーを解消していきます.

class NoteConfigureActivity : Activity() {
    internal var mAppWidgetId = AppWidgetManager.INVALID_APPWIDGET_ID
    internal lateinit var mAppWidgetText: EditText      // onCreateで初期化するのでlateinitをつける
    internal var mOnClickListener: View.OnClickListener = View.OnClickListener {
        val context = this@NoteConfigureActivity

        // When the button is clicked, store the string locally
        val widgetText = mAppWidgetText.text.toString()
        saveTitlePref(context, mAppWidgetId, widgetText)

        // It is the responsibility of the configuration activity to update the app widget
        val appWidgetManager = AppWidgetManager.getInstance(context)
        Note.updateAppWidget(context, appWidgetManager, mAppWidgetId)

        // Make sure we pass back the original appWidgetId
        val resultValue = Intent()
        resultValue.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, mAppWidgetId)
        setResult(Activity.RESULT_OK, resultValue)
        finish()
    }

    public override fun onCreate(icicle: Bundle?) {
        super.onCreate(icicle)

        // Set the result to CANCELED.  This will cause the widget host to cancel
        // out of the widget placement if the user presses the back button.
        setResult(Activity.RESULT_CANCELED)

        setContentView(R.layout.note_configure)
        mAppWidgetText = findViewById<View>(R.id.appwidget_text) as EditText
        findViewById<View>(R.id.add_button).setOnClickListener(mOnClickListener)

        // Find the widget id from the intent.
        val intent = intent
        val extras = intent.extras
        if (extras != null) {
            mAppWidgetId = extras.getInt(
                    AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID)
        }

        // If this activity was started with an intent without an app widget ID, finish with an error.
        if (mAppWidgetId == AppWidgetManager.INVALID_APPWIDGET_ID) {
            finish()
            return
        }

        mAppWidgetText.setText(loadTitlePref(this@NoteConfigureActivity, mAppWidgetId))
    }

    companion object {

        private val PREFS_NAME = "jp.cheerapps.note.Note"
        private val PREF_PREFIX_KEY = "appwidget_"

        // Write the prefix to the SharedPreferences object for this widget
        internal fun saveTitlePref(context: Context, appWidgetId: Int, text: String) {
            val prefs = context.getSharedPreferences(PREFS_NAME, 0).edit()
            prefs.putString(PREF_PREFIX_KEY + appWidgetId, text)
            prefs.apply()
        }

        // Read the prefix from the SharedPreferences object for this widget.
        // If there is no preference saved, get the default from a resource
        internal fun loadTitlePref(context: Context, appWidgetId: Int): String {
            val prefs = context.getSharedPreferences(PREFS_NAME, 0)
            val titleValue = prefs.getString(PREF_PREFIX_KEY + appWidgetId, null)
            return titleValue ?: context.getString(R.string.appwidget_text)
        }

        internal fun deleteTitlePref(context: Context, appWidgetId: Int) {
            val prefs = context.getSharedPreferences(PREFS_NAME, 0).edit()
            prefs.remove(PREF_PREFIX_KEY + appWidgetId)
            prefs.apply()
        }
    }
}


コードを全て載せましたが,この部分で変更した箇所は

internal var mAppWidgetText: EditText

internal lateinit var mAppWidgetText: EditText

のようにlateinitを追加しただけです!
mAppWidgetTextの初期化はonCreate()で行うため,
後から初期化をすることを,明示的に示す必要があるのです.


設定画面の呼び出し

現在,設定画面はウィジェット追加時だけしか表示されません.
そこで,ウィジェットに記述したテキストを後から編集できるようにします.
具体的な処理は,ウィジェットをタップすると設定画面を呼び出すようにしたいと思います.

class Note : AppWidgetProvider() {

    override fun onUpdate(context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray) {
        // There may be multiple widgets active, so update all of them
        for (appWidgetId in appWidgetIds) {
            updateAppWidget(context, appWidgetManager, appWidgetId)
        }
    }

    override fun onDeleted(context: Context, appWidgetIds: IntArray) {
        // When the user deletes the widget, delete the preference associated with it.
        for (appWidgetId in appWidgetIds) {
            NoteConfigureActivity.deleteTitlePref(context, appWidgetId)
        }
    }

    override fun onEnabled(context: Context) {
        // Enter relevant functionality for when the first widget is created
    }

    override fun onDisabled(context: Context) {
        // Enter relevant functionality for when the last widget is disabled
    }

    companion object {

        internal fun updateAppWidget(context: Context, appWidgetManager: AppWidgetManager,
                                     appWidgetId: Int) {

            val widgetText = NoteConfigureActivity.loadTitlePref(context, appWidgetId)
            // Construct the RemoteViews object
            val views = RemoteViews(context.packageName, R.layout.note)
            views.setTextViewText(R.id.appwidget_text, widgetText)

            // クリックで設定画面を開く
            val intent = Intent(context, NoteConfigureActivity::class.java).apply {
                putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId)
            }
            val pendingIntent = PendingIntent.getActivity(context, appWidgetId, intent, PendingIntent.FLAG_UPDATE_CURRENT)
            views.setOnClickPendingIntent(R.id.appwidget_text, pendingIntent)

            // Instruct the widget manager to update the widget
            appWidgetManager.updateAppWidget(appWidgetId, views)
        }
    }
}


追加部分は以下の5行です.

val intent = Intent(context, NoteConfigureActivity::class.java).apply {
    putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId)
}
val pendingIntent = PendingIntent.getActivity(context, appWidgetId, intent, PendingIntent.FLAG_UPDATE_CURRENT)
views.setOnClickPendingIntent(R.id.appwidget_text, pendingIntent)

設定画面であるNoteConfigureActivityに対するPendingIntentを作成し,
ウィジェットのViewであるRemoteViewsに対してsetOnClickPendingIntent()
でクリック時の処理を追加します.
RemoteViewsはクリックイベントの処理は,通常のViewと異なることに注意が必要です.




実行結果

ウィジェット追加 ウィジェット選択
f:id:cheerapps:20180417003719p:plain f:id:cheerapps:20180417003733p:plain
ウィジェット設定 ウィジェット
f:id:cheerapps:20180417003744p:plain f:id:cheerapps:20180417003505p:plain

実行結果はこの表のようになっています.
ウィジェットの設定画面で書いたテキストが,
ウィジェットにちゃんと追加されていることが確認できます.




default activity not found エラー

default activity not foundのエラーが出て,以下の画像の様に実行できない場合があります.


f:id:cheerapps:20180417003658p:plain


これはConfigurationのLaunchをNothingにすることで解消することができます.

f:id:cheerapps:20180417003706p:plain




おわりに

ここまで読んでいただきありがとうございました!
いかがでしたか?記事の感想,コードへの指摘など頂けると嬉しいです.
CheerAppが開発した最初のアプリ,「ウィジェットカウンター」は
こちらからダウンロードできます!

play.google.com


ではでは.

gitについてのとても基礎的な話②〜アプリ開発への道 その3〜

皆さん、こんにちは!

大学院生のきゅうりです!

 

本日も読んでいただいてありがとうございます!!

 

 

 

今日は前回に引き続いて、

 

git

 

に関するごくごく簡単な話です!!

 

今日の目標は

 

「Aさんはlayoutのブランチを切ってレイアウトに関する作業を、

 Bさんはmasterブランチでシステムに関する作業しよう」

 

マージするときにコンフリクトしてないか確認してください」

 

これら二つが何を言っているのか、理解することです!

 

 

この2回の記事を読めば、

gitの基礎の基礎については

マスターしたと言っても過言ではないです!!(多分)

 

 

 

では、それぞれの用語について解説していきます!

 

 

 

 

 

ブランチ:

 元のリポジトリに対して全く同じ複製を作成し、分岐を作ることです。

簡単にいうと、並行作業をする場所の作成です。

複数の作業を並列的に行いたい時に重宝します。

Aというブランチでは、レイアウトに関する作業を進める、

Bというブランチでは、システムに関する作業を進める

といった感じです。

gitでは初めから存在しているメインのブランチをmasterブランチと呼びます。

ウィジェットカウンターの場合

図で説明します。

f:id:cheerapps:20180416162640p:plain

 

この場合はlayoutブランチを作成し、

レイアウトやフォントに関する変更をlayoutブランチで、

カウンター機能に関する作業をmasterブランチで、

並列的に行っています。

最後のマージについては次で説明します、

 

新たなブランチを作ることをブランチを切るとも言います。

 

 

 

マージ:

複数のブランチのプログラムを統合することです。

複数のブランチそれぞれでの変更した部分を共に反映したリポジトリが作成されます。

具体的には、上の図のマージの部分ですね!

 

 

 

 

コンフリクト:

マージをする際に、

二つのブランチでそれぞれ同じ部分を変更してしまっていて

どっちをとったらいいのかわからない!!

という状況のことです。

つまり、衝突です(直訳ですが汗)。

 

ウィジェットカウンターを例にとって

図で説明すると次のような感じです!!

f:id:cheerapps:20180416165546p:plain

 

この場合は、

masterブランチでもlayoutブランチでも

カウンター機能の同じ部分を変更してしまったために、

コンフリクトが起きてしまったといった状況です。

 

 

コンフリクトしてしまった場合は、

自動でマージすることができず、

手動でマージすることになります。 

 

 

以上です!

 

 

 

2回に分けて、gitを最近学んだ初心者が初心者に向けて

記事を書いてみました。

 

僕なりに分かりやすく説明したつもりですが、

いかがだったでしょうか?

コメント等々お待ちしています!

 

 

また、今回gitGitHubについて学ぶきっかけなり、

例として挙げたウィジェットカウンターについては

google play で先日リリースしていますので、

是非ダウンロードお願いします!!

play.google.com

ウィジェットカウンターリリースの記事はコチラです 

 

 

本日も読んでいただいて、

ありがとうございました!

 

 

それでは!