質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
Android Studio

Android Studioは、 Google社によって開発された、 Androidのネイティブアプリケーション開発に特化した統合開発ツールです。

Kotlin

Kotlinは、ジェットブレインズ社のアンドリー・ブレスラフ、ドミトリー・ジェメロフが開発した、 静的型付けのオブジェクト指向プログラミング言語です。

Q&A

解決済

1回答

781閲覧

ボタンを押して日付を反映させたカードをlistViewに追加したい

aNomoto

総合スコア12

Android Studio

Android Studioは、 Google社によって開発された、 Androidのネイティブアプリケーション開発に特化した統合開発ツールです。

Kotlin

Kotlinは、ジェットブレインズ社のアンドリー・ブレスラフ、ドミトリー・ジェメロフが開発した、 静的型付けのオブジェクト指向プログラミング言語です。

0グッド

0クリップ

投稿2018/10/18 14:39

編集2018/10/19 14:59

前提・実現したいこと

初めて自主製作アプリを開発しているものです。
食事記録アプリ?のようなものが作りたくて
以下のような処理を実現させたいです。

floating Action Buttonを押す。 ↓ カレンダーのダイアログが表示される。 ↓ list_item.xmlの内容がlistViewに出力される。このときダイアログで選択した日付がdate_button.textに代入されている。

発生している問題

ネットのサンプルコードや本を頼りにコーディングを行ったのですが、
以下のような処理になってしまいます。

floating Action Buttonを押す。 ↓ add(ListItem(""))の数だけlist_item.xmlの内容が一度に出力される。 ↓ カレンダーのダイアログが表示される。 ↓ ダイアログで選択した日付が一番上のカードにだけ反映される。

該当のソースコード

kotlin

1//MainActivity.kt 2package com.example.promoto.proc 3 4import android.content.Context 5import android.support.v7.app.AppCompatActivity 6import android.os.Bundle 7import android.text.format.DateFormat 8import android.view.LayoutInflater 9import android.view.View 10import android.view.ViewGroup 11import android.widget.ArrayAdapter 12import android.widget.ImageButton 13import android.widget.ListView 14import android.widget.TextView 15import kotlinx.android.synthetic.main.activity_main.* 16import kotlinx.android.synthetic.main.list_item.* 17 18 19import java.util.* 20 21 22class MainActivity : AppCompatActivity() ,DatePickerFragment.OnDateSelectedListener { 23 24 override fun onSelected(year: Int, month: Int, date: Int) { 25 val c = Calendar.getInstance() 26 c.set(year, month, date) 27 date_button.text = DateFormat.format("MM/dd", c) 28 } 29 30 override fun onCreate(savedInstanceState: Bundle?) { 31 super.onCreate(savedInstanceState) 32 setContentView(R.layout.activity_main) 33 34 floatingActionButton.setOnClickListener { 35 36 //初期のリスト項目を設定 37 val arrayAdapter = MyArrayAdapter(this, 0).apply{ 38 add(ListItem("")) 39 } 40 //ListViewにリスト項目とArrayAdapterを設定 41 val listView: ListView = findViewById(R.id.listView) 42 listView.adapter = arrayAdapter 43 44 val dialog = DatePickerFragment() 45 dialog.show(supportFragmentManager, "date_dialog") 46 } 47 } 48} 49 50 51class ListItem(val date_data : String){} 52//リスト項目を再利用するためのホルダー 53data class ViewHolder(val date_dataView: TextView,val morningIcon:ImageButton,val lunchIcon:ImageButton,val dinnerIcon:ImageButton) 54 55//自作のリスト項目データを扱えるようにしたArrayAdapter 56class MyArrayAdapter : ArrayAdapter<ListItem> { 57 private var inflater : LayoutInflater? = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE)as LayoutInflater? 58 59 constructor(context: Context,resource :Int):super(context,resource){} 60 override fun getView(position:Int, convertView: View?, parent: ViewGroup?):View{ 61 62 var viewHolder : ViewHolder? = null 63 var view = convertView 64 65 //再利用の設定 66 if(view == null){ 67 68 view = inflater!!.inflate(R.layout.list_item,parent,false) 69 70 viewHolder = ViewHolder( 71 view.findViewById(R.id.date_button), 72 view.findViewById(R.id.morning_button), 73 view.findViewById(R.id.lunch_button), 74 view.findViewById(R.id.dinner_button) 75 ) 76 view.tag = viewHolder 77 }else{ 78 viewHolder = view.tag as ViewHolder 79 } 80 81 //項目の情報設定 82 val listItem = getItem(position) 83 viewHolder.date_dataView.text =listItem!!.date_data 84 85 viewHolder.morningIcon.setOnClickListener{ 86 //削除ボタンを押したときの処理 87 this.remove(listItem) 88 this.notifyDataSetChanged() 89 } 90 return view!! 91 } 92} 93

kotlin

1//Dialogs.kt 2package com.example.promoto.proc 3 4import android.app.DatePickerDialog 5import android.app.Dialog 6import android.content.Context 7import android.os.Bundle 8import android.support.v4.app.DialogFragment 9import android.widget.DatePicker 10import java.util.* 11 12class DatePickerFragment : DialogFragment(), 13 DatePickerDialog.OnDateSetListener{ 14 15 interface OnDateSelectedListener{ 16 fun onSelected(year: Int,month: Int,date: Int) 17 } 18 19 private lateinit var listener: OnDateSelectedListener 20 21 override fun onAttach(context: Context?){ 22 super.onAttach(context) 23 if(context is OnDateSelectedListener){ 24 listener = context 25 } 26 } 27 28 override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { 29 val c = Calendar.getInstance() 30 val year = c.get(Calendar.YEAR) 31 val month = c.get(Calendar.MONTH) 32 val date =c.get(Calendar.DAY_OF_MONTH) 33 return DatePickerDialog(context,this,year,month,date) 34 35 } 36 37 override fun onDateSet(view: DatePicker,year:Int,month: Int,date: Int) { 38 listener.onSelected(year,month,date) //To change body of created functions use File | Settings | File Templates. 39 } 40}

xml

1//list_item.xml 2<?xml version="1.0" encoding="utf-8"?> 3<android.support.constraint.ConstraintLayout 4 xmlns:android="http://schemas.android.com/apk/res/android" 5 xmlns:tools="http://schemas.android.com/tools" 6 xmlns:app="http://schemas.android.com/apk/res-auto" 7 android:layout_width="match_parent" 8 android:layout_height="match_parent" 9 tools:context=".MainActivity"> 10 11 <android.support.v7.widget.CardView 12 xmlns:android="http://schemas.android.com/apk/res/android" 13 xmlns:card_view="http://schemas.android.com/apk/res-auto" 14 android:layout_width="match_parent" 15 android:layout_height="135dp" 16 card_view:cardCornerRadius="4dp" 17 android:id="@+id/cardView" 18 tools:layout_editor_absoluteX="31dp" 19 card_view:layout_constraintTop_toTopOf="parent" 20 card_view:layout_constraintBottom_toBottomOf="parent"> 21 22 <!-- カードに載せる情報 --> 23 24 <RelativeLayout 25 android:layout_width="match_parent" 26 android:layout_height="match_parent" 27 android:layout_gravity="center_horizontal" 28 android:id="@+id/cardRelative" 29 > 30 <Button 31 android:layout_width="65dp" 32 android:layout_height="match_parent" 33 android:textAllCaps="false" android:layout_alignParentStart="true" 34 android:layout_alignParentTop="true" android:layout_marginTop="0dp" 35 android:layout_marginStart="2dp" android:layout_alignParentBottom="true" 36 android:layout_marginBottom="0dp" android:textStyle="italic" 37 android:layout_toStartOf="@+id/morning_button" android:layout_marginRight="0dp" 38 android:layout_marginEnd="0dp" android:layout_toLeftOf="@+id/morning_button" 39 android:id="@+id/date_button"/> 40 <ImageButton 41 android:layout_width="100dp" 42 android:layout_height="match_parent" app:srcCompat="@drawable/morning_icon" 43 android:id="@+id/morning_button" 44 android:layout_alignParentTop="true" 45 android:layout_marginTop="0dp" android:layout_alignParentBottom="true" 46 android:layout_marginBottom="0dp" 47 android:layout_toStartOf="@+id/lunch_button" android:layout_marginRight="2dp" 48 android:layout_marginEnd="2dp" android:layout_toLeftOf="@+id/lunch_button"/> 49 <ImageButton 50 android:layout_width="100dp" 51 android:layout_height="wrap_content" app:srcCompat="@drawable/lunch_icon" 52 android:id="@+id/lunch_button" 53 android:layout_alignParentTop="true" android:layout_marginTop="0dp" 54 android:layout_alignParentBottom="true" android:layout_marginBottom="0dp" 55 android:layout_toStartOf="@+id/dinner_button" android:layout_marginRight="2dp" 56 android:layout_marginEnd="2dp" android:layout_toLeftOf="@+id/dinner_button"/> 57 <ImageButton 58 android:layout_width="100dp" 59 android:layout_height="wrap_content" app:srcCompat="@drawable/dinner_icon" 60 android:id="@+id/dinner_button" 61 android:layout_alignParentTop="true" android:layout_marginTop="0dp" 62 android:layout_alignParentBottom="true" 63 android:layout_marginBottom="0dp" 64 android:layout_alignParentEnd="true" android:layout_alignParentRight="true" 65 android:layout_marginRight="5dp" android:layout_marginEnd="5dp"/> 66 </RelativeLayout> 67 </android.support.v7.widget.CardView> 68</android.support.constraint.ConstraintLayout> 69 70

試したこと

似たような処理について書かれている記事が見つからない。Buildも問題なくできている事から原因がわからず、手詰まり状態です。

補足情報(FW/ツールのバージョンなど)

Gradle version 4.6
AndroidStudio 3.2.1

プログラミング初心者で、不慣れな点が多く、質問の説明もわかりにくいと思いますが
回答よろしくお願いします。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

shal0ne

2018/10/20 15:04

fabが宣言されていなかったりmainのxmlファイルがなかったりで、再現、検証しづらいです。簡潔な図など、
shal0ne

2018/10/20 15:08

すみません、誤タッチで記入途中なのにしかも複数回送信してしまいました。続きを書くと、視覚的にイメージしやすくなるようなものなどを資料として追加した方が回答率が上がると思います。
aNomoto

2018/10/20 16:07

ご指摘ありがとうございます。内容に変更があり新たな質問を作成しました。そちらのほうには図やmainのxmlが記述してあるのでよければご覧ください
guest

回答1

0

ベストアンサー

回答ついてないので一応解決策のアイデアだけでも。
ソースコードを補完してAndroidStudioで最初の状態を再現しようと試みましたが動かなかったので、実際にコードを直して、エミュレータで動かして確認した訳ではないことを断っておきます。

kotlin

1 2override fun onSelected(year: Int, month: Int, date: Int) { 3 val c = Calendar.getInstance() 4 c.set(year, month, date) 5 date_button.text= DateFormat.format("MM/dd", c) 6 }

のdate_buttonにはおそらく一番最初に作ったviewHolderのものがずっと保存されています。
なので2つ目を作っても、onSelectedが呼ばれた際には最初のviewHolderのdate_buttonに日付が反映されます。

kotlin

1override fun onSelected(year: Int, month: Int, date: Int) { 2 val c = Calendar.getInstance() 3 c.set(year, month, date) 4 date_button.text= DateFormat.format("MM/dd", c) 5 } 6 7override fun onCreate(savedInstanceState: Bundle?) { 8 super.onCreate(savedInstanceState) 9 setContentView(R.layout.activity_main) 10 11 floatingActionButton.setOnClickListener { 12 13 //初期のリスト項目を設定 14 val arrayAdapter = MyArrayAdapter(this, 0).apply{ 15 add(ListItem("")) 16 } 17 //ListViewにリスト項目とArrayAdapterを設定 18 val listView: ListView = findViewById(R.id.listView) 19 listView.adapter = arrayAdapter 20 21 val dialog = DatePickerFragment() 22 dialog.show(supportFragmentManager, "date_dialog") 23 } 24 }

このFABの処理だと、
FABを押してDialog表示
->(Dialogがキャンセルされたかどうかに関わらず)AdapterにListItem("")を追加
->onSelectedで一番最初に作ったdate_buttonに日付を反映
->最初に作ったdate_button.textにはDialogで選択した日付が入り、二つ目にdate_button.textには""がそのまま入った状態で表示
の流れになっていると思います。

kotlin

1override fun onSelected(year: Int, month: Int, date: Int) { 2 val c = Calendar.getInstance() 3 c.set(year, month, date) 4 5 //Dialogで選択したセットしたい日付を文字列にしてstrに代入 6 //(そのままだと型がCharSequenceで、ListItemのString型のプロパティに代入できないので、as Stringでキャスト) 7 val str=DateFormat.format("MM/dd", c) as String 8 9 //ここに移動 10 //初期のリスト項目を設定 11 val arrayAdapter = MyArrayAdapter(this, 0).apply{ 12 add(ListItem(str))//空の文字列""の代わりに先ほどの文字列を使用 13 } 14 //ListViewにリスト項目とArrayAdapterを設定 15 val listView: ListView = findViewById(R.id.listView) 16 listView.adapter = arrayAdapter 17 18 //消す 19 //date_button.text = DateFormat.format("MM/dd", c) 20 21 } 22 23 override fun onCreate(savedInstanceState: Bundle?) { 24 super.onCreate(savedInstanceState) 25 setContentView(R.layout.activity_main) 26 27 val floatingActionButton=findViewById<FloatingActionButton>(R.id.fab) 28 floatingActionButton.setOnClickListener { 29 30 //onSelectedの方に移動 31 /*//初期のリスト項目を設定 32 val arrayAdapter = MyArrayAdapter(this, 0).apply{ 33 add(ListItem("")) 34 } 35 //ListViewにリスト項目とArrayAdapterを設定 36 val listView: ListView = findViewById(R.id.listView) 37 listView.adapter = arrayAdapter*/ 38 39 val dialog = DatePickerFragment() 40 dialog.show(supportFragmentManager, "date_dialog") 41 } 42 }

なので上記のように変更することで処理を
FABを押してDialog表示
->Dialogで日付を選択すれば変数strに日付のフォーマットをString型で保存(選択しなければ全てキャンセル)
->AdapterにListItem(str)を追加
->Adapterを適用し、最初に作ったものと、二つ目に作った、文字列strがdate_button.textに適用されたものが表示
という処理の流れになると思うのでうまくいくと思います。
色々間違っているところもあると思いますが、参考までに。

###11/1追記
上記の書き方だとadapterが初期化されてしまっていそうなので、応急手当として次のことをしてもらえればいいと思います。

kotlin

1class MainActivity : AppCompatActivity() ,DatePickerFragment.OnDateSelectedListener { 2 3 //MainActivityのメンバとして追加 4 lateinit var arrayAdapter:MyArrayAdapter 5 6 override fun onCreate(savedInstanceState: Bundle?) { 7 super.onCreate(savedInstanceState) 8 setContentView(R.layout.activity_main) 9 10 //ここで初期化 11 arrayAdapter=MyArrayAdapter(this, 0) 12 ... 13 } 14 override fun onSelected(year: Int, month: Int, date: Int) { 15 val c = Calendar.getInstance() 16 c.set(year, month, date) 17 val str=DateFormat.format("MM/dd", c) as String 18 19 //メンバにMyArrayAdapterを保持しているので、その中にListItemを付け足す処理を書く 20 arrayAdapter.apply{ 21 add(ListItem((str))) 22 } 23 //消す 24 /*val arrayAdapter = MyArrayAdapter(this, 0).apply{ 25 add(ListItem(str)) 26 }*/ 27 val listView: ListView = findViewById(R.id.listView) 28 listView.adapter = arrayAdapter 29 } 30 31 ... 32}

投稿2018/10/27 02:48

編集2018/11/01 16:14
shal0ne

総合スコア51

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

aNomoto

2018/10/29 12:01

回答ありがとうございました。 お礼が遅くなって申し訳ありません。 ソースコードを参考にしましたが1つ目以降のアダプターが表示されませんでした。 追加したListItemの数だけアダプターが表示されるという風に解釈しているのですがこの考えは間違いないでしょうか?
shal0ne

2018/11/01 10:47

返信遅い人ですすみません。 おそらく仕様をちゃんと理解しておらずゴミ回答してしまいました。 あと、AdapterはList<ListItem>というListItemが入った袋を持っていて、ViewにItemを反映させてくれる人のことです。 なので、正しくは「Itemが表示される」だと思います。 本題に戻り結論から言うと、その考えは半分合ってて半分間違っていると思います。 まずはじめに、fun onSelected(){~}というのは大まかに言うと、『onSelectedとどこかでまた書かれたら、メモリ上のどこかにある()のなかの情報を元に、{}の中の~と言う処理をメモリ上のどこかに新しく書いて実行してね』と言うことです。 きっとDialogで日付を選択したらonSelectedが呼び出されるよう、Androidに書いてあると思います。 もし、onSelectedの中に val arrayAdapter = MyArrayAdapter(this, 0) と書いたなら、onSelectedが実行される度にメモリ上のどこかに新しくMyArrayAdapterが生成されます。 二回目に実行した時も新しくMyArrayAdapterがメモリ上に生成されますが、これは1回目のものとは別物なので1回目に入力した情報は持っていません。 その後 listView.adapter = arrayAdapter と書いてあるので、2回目に実行した際には、2回目に入力した情報のみを持つMyArrayAdapterがlistViewに適用されます。 したがってonSelectedの中に移した際には一つ目以降のItemが表示されないという問題が発生したのだと思います。 ところが最初はonClickListener{}の中に書いていました。 この記法はonClickListener({})の略で{}の中には処理を書きます。 print("Hello World!")であれば、fun print(){~}が別のファイルに書かれていて、アプリを起動した際にHello World!と言う文がメモリ上のどこかに保存され、~の部分を実行するときに呼び出されます。 onClickListener({})の場合は別のファイルにfun onClickListener(){~}が書かれていて、今回は()の中に{}という処理が書いてあるので、それを元に~の部分を実行するということです。 printの時にHelloWorldが保存されたように、{}もメモリ上のどこかに保存されますから、当然その中身であるMyArrayAdapterの情報も保存されます。 また、{}は保存されているので、onSelectedの時とは違い2回目3回目に実行される時も毎回新しく作り直す必要がありません。 したがってonClickListener{}の中に val arrayAdapter = MyArrayAdapter(this, 0) と書いたときには、onSelected()の時のように毎回MyArrayAdapterを生成し直すということが起きず、『追加したListItemの数だけアダプターが表示される』という状態が成り立っていたのだと思います。 毎度間違った記述を含んでいそうですが、認識としてはこれで大筋は合っていると思います。 詳しくは「関数オブジェクト」などでググってもらえれば大丈夫です。
aNomoto

2018/11/01 15:50 編集

親切に説明していただきありがとうございます。 おかげでListItemが一つしか表示されなかった理由がわかりました。 関数オブジェクトについて改めて勉強したいと思います。 追記内容を試したところアプリが起動できなくなってしまいました。 エラーメッセージは追記の通りです。 エラーケースについて調べたのですが有力な情報が得られずにいます。 見覚えのないファイルのエラーが多いのですが、この場合MainActivity内のエラーがほかのファイルに影響を及ぼしていると考えていいのでしょうか? 質問ばかりで申し訳ないです。
aNomoto

2018/11/01 16:04

文字数の関係で追記できなかったためコメント欄で記述します。 Process: com.example.promoto.proc, PID: 6913 java.lang.RuntimeException: Unable to instantiate activity ComponentInfo{com.example.promoto.proc/com.example.promoto.proc.MainActivity}: java.lang.IllegalStateException: System services not available to Activities before onCreate() at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2679) at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2856) at android.app.ActivityThread.-wrap11(Unknown Source:0) at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1589) at android.os.Handler.dispatchMessage(Handler.java:106) at android.os.Looper.loop(Looper.java:164) at android.app.ActivityThread.main(ActivityThread.java:6494) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:438) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:807) Caused by: java.lang.IllegalStateException: System services not available to Activities before onCreate() at android.app.Activity.getSystemService(Activity.java:5915) at android.view.LayoutInflater.from(LayoutInflater.java:229) at android.widget.ArrayAdapter.<init>(ArrayAdapter.java:210) at android.widget.ArrayAdapter.<init>(ArrayAdapter.java:204) at android.widget.ArrayAdapter.<init>(ArrayAdapter.java:138) at com.example.promoto.proc.MyArrayAdapter.<init>(MainActivity.kt:62) at com.example.promoto.proc.MainActivity.<init>(MainActivity.kt:22) at java.lang.Class.newInstance(Native Method) at android.app.Instrumentation.newActivity(Instrumentation.java:1174) at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2669) at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2856)  at android.app.ActivityThread.-wrap11(Unknown Source:0)  at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1589)  at android.os.Handler.dispatchMessage(Handler.java:106)  at android.os.Looper.loop(Looper.java:164)  at android.app.ActivityThread.main(ActivityThread.java:6494)  at java.lang.reflect.Method.invoke(Native Method)  at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:438)  at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:807)
shal0ne

2018/11/01 16:32 編集

あー、ごめんなさい。 java.lang.RuntimeException: Unable to instantiate activity ComponentInfo{com.example.promoto.proc/com.example.promoto.proc.MainActivity}: java.lang.IllegalStateException: System services not available to Activities before onCreate() これはonCreateが終わるまでLayoutInflaterとかが出来上がらないので、LayoutInflaterをメンバに持つMyArrayAdapterを初期化するとまずいよみたいなやつだと思います。 val arrayAdapter=MyArrayAdapter(this,0) だとonCreateの前に実行されてしまうので lateinit var arrayAdapter:MyArrayAdapter とすれば初期化を遅らせる(late initialize)ことができるので、試してみてください。 その際、onCreateの最後に arrayAdapter = MyArrayAdapter(this, 0) と書けば、onCreateが一通り終わった後に初期化されます。
kakajika

2018/11/02 00:59

横入り失礼します。 arrayAdapterの初期化を遅らせたいなら、lateinitよりもlazyを使ったほうがスマートかつ安全ですね。 private val arrayAdapter by lazy { MyArrayAdapter(this, 0) } lateinitはうっかり初期化忘れをしてバグを発生させる可能性もあるので、あまり好ましくありません。
shal0ne

2018/11/02 03:05

そんなものがあるんですね。 調べてみましたが、初めて呼ばれた時に自動で arrayAdapter=MyArrayAdapter(this, 0) としてくれるみたいですね。 いちいちonCreateの中で初期化する文を書く必要がないですし、ベストだと思います。 横入りしてもらえて助かりましたm(__)m
aNomoto

2018/11/03 21:56

返信が遅くなってしまい申し訳ないです。 無事起動、処理の確認が終わりました。 数日にわたって、まめにコメントしてくださり本当に助かりました。 また機会があればよろしくお願いします。
shal0ne

2018/11/04 05:19

よかったです。 お互い頑張りましょう。 kakajikaさんありがとうございました。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問