アプリ公開

BokuMemoというAndroid用メモ帳アプリを公開しました。

タブでメモをグループ化し管理できることと、パソコンのメーラーみたいにリスト表示とプレビューがあるのが特徴です。

windows mobileの時代に同じようなメモ帳を作っていたのですが(非公開)、ふと思い立ってAndroidで作ってみました。休日と平日の1時間くらいでほぼ1か月かかりました。

今回、初めてのAndroidアプリでかなり勉強になりました。kotlinいいですね。ちなみに、Javaはよくわかりません。普段は趣味でC#やC++やC、ごくたまにPythonな感じです。

しかし、GooglePlayで公開するまでが大変だった。今日、一日かかってしまった、、、。

池の白鳥

近所の池に2羽の白鳥が住んでいます。その池が工事中でしばらく通っていなかったのですが、工事が終わったので久しぶりに行きました。この時期になると、白鳥が巣を作っています。今年はその場所がいつもと違っていました。

いつもの場所。
今年の場所。巣作り中。
もう一羽は少し離れたところにいます。

いつもの場所にあるわらは、誰かが敷いたのかな?もう一羽が警戒中なので、ちょっと近寄りずらかった。

つくし

今日は一日休みをもらって、母親を病院へ連れて行きました。実は、しばらく母親が入院していました。今は元気です。その関係で郵便局へ保険の相談にも行きました。入院証明がいるので、また病院へ行かなければいけないですねぇ。

その後、散歩がてら趣味の(?)土筆取り。いい感じで気分転換ができました。

タブのフラグメントが欲しい

TabLayoutとViewPagerとFragmentStatePagerAdapterでタブを作っていて、Fragmentの一覧がほしかった。 FragmentStatePagerAdapter のgetItemかと思ったが、それは新規に作られてしまうのでよろしくないというか違う。調べたら以下のようにしたらできた。

    fun findFragmentByPosition(viewPager : ViewPager, position : Int) : Fragment{
        val adapter = viewPager.adapter!! as FragmentStatePagerAdapter
        return adapter.instantiateItem(viewPager, position) as Fragment
    }

いや、できた気がする。

kotlinのenumをIntに

enumをintentで使いたくて、Intに相互変換できないか調べた。結果は、以下のようにしたらできた(これが正解かはわからない)。

    enum class ListHeight {
        SMALL,
        MID,
        LARGE
    }

    var listHeight1 = ListHeight.LARGE
    var i = listHeight1.ordinal                 //i==2
    var listHeight2 = ListHeight.values()[i]     //listHeight2 == ListHeight.LARGE

ordinalで順番(?)で、values()でenum値の配列が取れる。

跳んで埼玉

跳んで埼玉を観てきました。魔夜峰央先生が最初に出てきました。自分のイメージではタモリなんだけど、いまは違う感じでした。

キャラクターが魔夜峰央の漫画は独特な感じなんだけど、それを再現しつつ全然違和感がないのがすごいと思いました。楽しかったです

ListViewの中に編集可能なTextView(EditText)

編集可能なTextViewがListViewの中にある場合が今回の問題です。

class GroupListAdapter(context: Context, var groups: MutableList<MemoGroup>) :
    ArrayAdapter<MemoGroup> (context, 0, groups) {
data class ViewHolder(val textView: TextView)
    private val layoutInflater = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater

    override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
        var view = convertView
        var holder: ViewHolder
        if (view == null) {
            view = layoutInflater.inflate(R.layout.view_group_setting, null)
            holder = ViewHolder(
                view.findViewById(R.id.textView)
            )
            view.tag = holder
            holder.textView.addTextChangedListener(object: TextWatcher {
                override fun afterTextChanged(p0: Editable?) {
                    if(p0 != null) {
                        groups[position].title = p0.toString()
                    }
                }

                override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {}

                override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {}

            })
        } else {
            holder = view.tag as ViewHolder
        }
        holder.textView.text = groups[position].title
        return view!!
    }
}

本当はもうちょっとごちゃごちゃしているのですが、多少見やすいように修正しました。

さて、これだと何度もafterTextChangedが呼ばれて、しかもpositionもp0.toString()も思った値が取れない。

ここを参考にして解決しました。

class GroupListAdapter(context: Context, var groups: MutableList<MemoGroup>) :
    ArrayAdapter<MemoGroup> (context, 0, groups) {
    inner class MutableWatcher : TextWatcher {
        var position: Int = 0
        var active: Boolean = false
        override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {}
        override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {}
        override fun afterTextChanged(s: Editable) {
            if (active) {
                groups[position].title =  s.toString()
            }
        }
    }
    data class ViewHolder(val textView: TextView, val watcher:MutableWatcher)
    private val layoutInflater = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
    override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
        var view = convertView
        var holder: ViewHolder
        if (view == null) {
            view = layoutInflater.inflate(R.layout.view_group_setting, null)
            holder = ViewHolder(
                view.findViewById(R.id.textView),
                MutableWatcher()
            )
            view.tag = holder
            holder.textView.addTextChangedListener(holder.watcher)
        } else {
            holder = view.tag as ViewHolder
        }
        holder.watcher.active = false
        holder.textView.text = groups[position].title
        holder.watcher.position = position
        holder.watcher.active = true
        return view!!
    }
}

textView.textに代入してもafterTextChangedが呼ばれるので、その時は無視するようにして、TextWatcher をそれぞれに持たせる感じなのか?まだ勉強が足りません。

onActivityResultでintentから返るデータがnull

恥ずかしいミスだけど、書いておく。

呼び出したActivityからputExtraでデータをもらって、

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == 100 && intent !=null && resultCode == RESULT_OK) {
var a = intent.getParcelableArrayListExtra<MyGroup>("groups")
}
}

これでaはnullが返る。単純すぎるんだけど、

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == 100 && data !=null && resultCode == RESULT_OK) {
var a = data.getParcelableArrayListExtra<MyGroup>("groups")
}
}

intentじゃなくてdataでしたという落ち。オーバーライドしたonActivityResultは自動生成したもので、intent部分はコピペ。単純だけど、なかなかわからなかった。

toolbar表示でエラー

アプリの設定画面を作ろうと思った。機能は単純なので、適当に空のActivityを作ったら、ツールバーがMainActivityのまま。それで独自のツールバーを表示しようと思った。慣れてきたので、適当にコピペでレイアウトコピー、コードもコピーで簡単と思いきや、例外で落ちる。

調べたら、AndroidManifest.xmlの新規に作ったActivity部分に、

android:theme="@style/AppTheme.NoActionBar"

を付けなければいけなかった。いままでは、空のActivityじゃなくて、なにがしかの中身のあるテンプレートから作ってたから自動で付いていた。

あわせてタイトルも設定した。勉強にはなったが、最初から中身のあるテンプレート使えば楽だったかも。