ListViewの中のEditTextで編集できない

ListViewの中に編集可能なTextViewを入れると、IMEが表示されるタイミングでListViewのサイズが変わり、編集中のTextViewからフォーカスが外れる。そして、1文字しか入力されない。

ひとまず、AndroidManifest.xmlのActivityの属性に

android:windowSoftInputMode = "adjustPan"

を加えると、解決しているっぽい。

以前作った同じようなListViewはたまたまサイズが小さくて、IMEが表示されてもリサイズされていないだけだった。

NotificationChannelで通知音が消えない

        val channel = NotificationChannel(channelID, title, NotificationManager.IMPORTANCE_DEFAULT)
        channel.setSound(null, null)

このように、setSound(null, null)で通知音が消えるとあったので試したが、どうしても消えない。通知の優先度を落としても消えない。かなり悩んだが、いったんアプリをアンインストールしたらうまくいった。いったん作ったチャンネルを削除しなければいけないのかな?

アプリ公開

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

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

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

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

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

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

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
    }

いや、できた気がする。

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じゃなくて、なにがしかの中身のあるテンプレートから作ってたから自動で付いていた。

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

ListViewのヘッダー文字

例えば、次のようなlayoutを作る。

group_list_header.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">

<TextView
android:text="group name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/textView2"
android:layout_weight="2"/>
<TextView
android:text="show"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/textView3"
android:layout_weight="1"
android:gravity="right"/>
</LinearLayout>

次に、ActivityのonCreateで次のように設定

val headerView = layoutInflater.inflate(R.layout.group_list_header, groupListView, false) as ViewGroup
groupListView.addHeaderView(headerView)

これでできるけど、groupListViewの中にswitchがあったため、ヘッダーの文字の位置がうまく合わなくていまいちだった。 groupListView の中身が文字だけならまぁまぁうまくいきそう。
ということで、今回は使わなかった。せっかく調べたので、記録として残す。

Fragmentの中のListViewで文字色が白くなった

Activityの中身をFragmentに移したら、ListViewの文字色が白くなって見えなくなった。

val adapter = SummaryListAdapter(activity!!.applicationContext)
summaryListView.adapter = adapter


val adapter = SummaryListAdapter(activity!!)
summaryListView.adapter = adapter

にしたら直った。アプリケーションのcontextでの色と、Activityの色が違う(?)らしい。今回、なぜわざわざapplicationContextを使ったかは謎。