|
ファイル名「MainActivityjava」 package com.example.checkboxtest; /* * 3つのデータにチェックボックスがあり * それぞれの行でチェックをオンオフすると * どのデータのチェックをオンオフしたか * 確認するプログラムです。 */ import java.util.ArrayList; import java.util.List; import android.app.Activity; import android.graphics.PixelFormat; import android.os.Bundle; import android.view.Display; import android.view.Menu; import android.view.MenuItem; import android.view.Window; import android.view.WindowManager; import android.widget.CheckBox; import android.widget.CompoundButton; import android.widget.CompoundButton.OnCheckedChangeListener; import android.widget.TextView; import android.widget.Toast; public class MainActivity extends Activity implements OnCheckedChangeListener{ /* * implements OnCheckedChangeListenerはチェックをタップした時に * 呼び出されるタップ処理を効率よく簡単に実装させる * ためのものです。 */ /* * メニューボタン用 * 終了だけ実装してます。 */ private static final int MENU_ITEM0=0; //今回は使用しませんが画面の大きさを取得するためのもの float disp_w; float disp_h; //使用するデータが入っているクラスの登録 Hensu hensu = new Hensu(); /* * データは3つなので個別にインスタンスしてもいいのですが * やりたいプログラムとなるべく同じ変数を使ってわかりやすく * しています。 */ static List<Object> objList = new ArrayList<Object>(); //使用する部品 static CheckBox check1; static TextView text1; static CheckBox check2; static TextView text2; static CheckBox check3; static TextView text3; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); requestWindowFeature(Window.FEATURE_NO_TITLE); getWindow().setFormat(PixelFormat.TRANSLUCENT); Window window = getWindow(); window.addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); WindowManager manager = window.getWindowManager(); Display disp = manager.getDefaultDisplay(); disp_w = disp.getWidth(); disp_h = disp.getHeight(); init(); } //初期設定メソッド public void init() { /* * 3データを登録 */ for(int i=0;i<hensu.check.length;i++) { objList.add(new Object(hensu.check[i],hensu.Name[i])); } findView(); } /* * 画面に表示させるためのメソッド。 * 個別に設定しています。 */ public void findView() { setContentView(R.layout.activity_main); /* * findViewByIdでそれぞれの部品をイヂクルことが * できるようになります。 */ check1 = (CheckBox)this.findViewById(R.id.checkbox1); check2 = (CheckBox)this.findViewById(R.id.checkbox2); check3 = (CheckBox)this.findViewById(R.id.checkbox3); text1 = (TextView)this.findViewById(R.id.textview1); text2 = (TextView)this.findViewById(R.id.textview2); text3 = (TextView)this.findViewById(R.id.textview3); setFindViewObj(); } /* * ここで部品をイヂクッテます */ public void setFindViewObj() { /* * オブジェクトからチェック状態を取り出して部品に設定 * そのチェックボックスが押されたらOnCheckedChangeListenerに * オブジェクトからテキストを取り出し部品に設定 * * これをデータ3つ分やっています。 */ check1.setChecked(objList.get(0).isCheck()); check1.setOnCheckedChangeListener(this); text1.setText(objList.get(0).getName()); check2.setChecked(objList.get(1).isCheck()); check2.setOnCheckedChangeListener(this); text2.setText(objList.get(1).getName()); check3.setChecked(objList.get(2).isCheck()); check3.setOnCheckedChangeListener(this); text3.setText(objList.get(2).getName()); } /* * implements をやってるのとやってないのとでの違いは、メリットデメリット(リンスではない) * があり、implements をやってないと上記setOnCheckedChangeListener(this)で実装することに * なります。当然3つあるので3つ実装します。ごちゃごちゃしますね。そのかわりその場でやり * たいことができます。変数のスコープとか関係なしにできたりします。 * implementsをやっている今回みたいな場合は、スッキリしますね。そのかわりしっかり変数同士 * の関係性や引数で何を渡されているかなどを知っておく必要がある感じですかね。 */ public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { String str1 = ""; String str2 = ""; /* * CompoundButton buttonView はCheckBoxしたCheckBoxそのものと思って下さい。 * boolean isChecked はチェックがはいったのかはいっていないのかです。 * * ここではまずどのCheckBoxかを直接比べてみて、それぞれで動作を決定。 * あとで使うstr1にtextを設定、部品が参照しているObjListにチェック具合 * を設定しています。 * */ if(buttonView == check1) { str1 = text1.getText().toString(); objList.get(0).setCheck(isChecked); } if(buttonView == check2) { str1 = text2.getText().toString(); objList.get(1).setCheck(isChecked); } if(buttonView == check3) { str1 = text3.getText().toString(); objList.get(2).setCheck(isChecked); } /* * チェック状態でstr2を設定 */ if(isChecked == true) str2 = "ON!"; if(isChecked == false) str2 = "OFF!"; /* * 便利なトーストメソッドで表示 */ makeTost(str1+" "+str2,1); //これを呼び出すことで画面を更新 findView(); } //オプションメニューに表示させるアイテム登録 @Override public boolean onCreateOptionsMenu(Menu menu) { super.onCreateOptionsMenu(menu); MenuItem item0 = menu.add(0,MENU_ITEM0,1,"終了"); return true; } //オプションメニューを選択した時の処理 public boolean onOptionsItemSelected(MenuItem item){ switch (item.getItemId()){ case MENU_ITEM0: finish(); return true; } return true; } /* * エラーなど確認用などに便利。おまけ */ public void makeTost(String str,int id){ int tost = Toast.LENGTH_LONG; if(id==1) tost = Toast.LENGTH_SHORT; Toast toast = Toast.makeText(this.getApplicationContext(), str, tost); toast.show(); } }
|
ファイル名「Hensu.ava」,「Object.ava」 package com.example.checkboxtest; public class Hensu { /* * オブジェクトに入れ込むデータ群 */ String[] Name = { "アイアンマン","スタートレック","インデペンデンスデイ" }; boolean[] check = { false,false,false }; } package com.example.checkboxtest; public class Object { /* * メンバは2つ * チェックされてるかいないかと * 映画名 */ private boolean check; private String name; Object(boolean check,String name) { this.setCheck(check); this.setName(name); } public boolean isCheck() { return check; } public void setCheck(boolean check) { this.check = check; } public String getName() { return name; } public void setName(String name) { this.name = name; } } |
「問題はどこに!?」 どうでしょうか。こんな感じです。しかしこれだけ見るとすごい簡単にみえます。応用みききそうだし、構造も簡単だし。 しかし考えてみたら、データが少なくそれをチェックする場合よりも、データがスクロールするほどあり、その中からデータ をチェックしてなにかしたい。というほうが多いのです。 そんな場合、やはりListView。しかもアダプターを使用して自作の行を作ったりしますそれをrowとします。 ここを見るかたなら簡単に作れるでしょう。みなさんの問題も同じかと思います。 そうです、スクロールするほどのデータがあり、rowにチェックボックス部品を設定して、いざ稼働させると・・・ 「チェックがキエル!」「へんなとこにチェックハイットル!」「そういやどこにタップ処理かけばいいんだ・・」 みたいな問題がいっぺんにたくさんでます。 問題は沢山の場所でいっぺんに起きています。まず考えるのは色々な問題がどこにどういうふうに起きているかを見えるようにしたい。デバック作業をしたいわけです。で考えるとチェックした時におかしなことになるのなら、チェックした瞬間を見えるようにして変数の変動などみればいいわけです。 さて、ここで問題が起こります。さきほど作成したプログラムでならそれは可能です。がアダプターを使うとrowは別のクラスに作ることが多いですね。row部品はアダプターで設定するということはアダプター側でタップ感知しなければいけません。 ではそうすればいいじゃないか!なのですが 、確かにやってみてもいいかもしれません。やるのが正しいでしょう。んで私もやってみました。するとどうでしょう。そして変数の変動などをみます。結果、ワケワカラン。ナンダコレ。ナンデコウナルンダYO!状態です。 色々なサイトを参考にしました。同じ症状になるので解決してみた!というサイトもありました。で考察してみますと、 タップした時の画面の状態がかならずしもそのチェックボタンを参照して変数に設定されているとは限らない・・・・ なにをいっているんだかわかりませんが、ようは、さきほど作ったプログラムと作ろうとしているプログラムの違いを見てみればわかります。 さきほどのプログラムは、明示的に、的確に、確実に、わかりやすく、3つのデータがそれぞれのCheckBoxに設定していてされています。当たり前ですね。 しかしアダプターを使用すると、よく見ると、1つのCheckBoxをArrayListやList分だけふやしてListViewに登録します。つまり、rowをたくさん勝手に増やすわけです。それぞれのrowに違いはありません。でもCheckBoxはそれぞれ違いがあるはずなので増やした分のCheckBoxを個別に記憶させておき参照すればいいかも・・・ それができたとします。しかし次に問題が、データ分だけのCheckBoxがいっぺんには登録されません。画面にでている分だけ登録されている感じになっています。 そうです。アダプターでは、画面に出現する分しか登録されていないっぽいのです。(つд・) しかも作られたら別でCheckBoxを登録して、チェックしたら登録分だけすべて参照比較してみたのですが、期待したものと違います。確実に比較できるようにしたのですがそれでもだめ。 public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { if(i == checkbox1.size()) makeTost(str+" i="+i,0); checkbox1が記憶させたはずのCheckBoxです。これでやると、スクロールなしなら、表示されている分だけの判定は完璧でした。しかしスクロールさせると、以前登録されているCheckBoxと同じものが判定されてしまいます。いわゆる「使い回し」をしているわけです。なので渡された 値 = チェックされた場所のポジション = オブジェクトのポジション とは な ら な い わけです。 しかもですね、ここで想像するとわかると思うのですが、そもそもrowのポジション、いわゆる設定するオブジェクトのポジションを表す、この二つを一致させるためのメソッドを設定していません。rowで一番上のチェックをしてもそれがどのポジションかなんて、このアダプターではわからないのです。 わかるのは、オブジェクトのポジションのものを画面に表示させているということだけです。 なのでまずはこのポジションを保存させておくことが必要になります。 わかりにくいですがたとえば、オブジェクトの1,2,3,4,5番目まで表示させます、となるとCheckBoxは No1,2,3,4,5、を使おう。そしてスクロールして、オブジェクト15,16,17,18、19番目を表示させる時にもNo1,2,3,4,5、を使うということなので、まずは、今どのポジションが表示されているかを設定し、CheckBoxでの直接の判定はせずに、オブジェクトそのもので判定するべきなのかも・・ってことですね。そういう色々かぶっていることが、1個チェックしたはずなのにたくさんチェックされたり、スクロールしたらチェックが消えたりの要因と思われます。なので、チェック状態をオブジェクトごとに保存し、確実にチェックポジションとオブジェクトポジションをつなぎ合わせ保存、そこで更新。が一番いいのかもしれません。 でもこれだと更新のたびに画面が上部に移ってしまうので問題なんですよね・・(つд・) でも改善点はみえてきました。しかしながらめんどくさそうなので、別の観点でそれを克服しました。 これまたメリットデメリット(リンスではない)があります。考え方は簡単で、 「ListViewタップでチェックを入れる」 これです。すごい簡単でした。作ってみました。 |
ファイル名「MainActivity.ava」 package com.example.checkboxtest3; import java.util.ArrayList; import java.util.List; import android.os.Bundle; import android.app.Activity; import android.graphics.PixelFormat; import android.view.Display; import android.view.MenuItem; import android.view.View; import android.view.Window; import android.view.WindowManager; import android.widget.AdapterView; import android.widget.AdapterView.OnItemClickListener; import android.widget.ListView; import android.widget.Toast; public class MainActivity extends Activity implements OnItemClickListener{ private static final int MENU_ITEM0=0; float disp_w; float disp_h; Hensu hensu = new Hensu(); static ListAdapter objlistadapter; ListView objlistview; static List<Object> objList = new ArrayList<Object>(); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); requestWindowFeature(Window.FEATURE_NO_TITLE); getWindow().setFormat(PixelFormat.TRANSLUCENT); Window window = getWindow(); window.addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); WindowManager manager = window.getWindowManager(); Display disp = manager.getDefaultDisplay(); disp_w = disp.getWidth(); disp_h = disp.getHeight(); init(); } public void init(){ for(int i=0;i<hensu.Name.length;i++) { objList.add(new Object(hensu.check[i],hensu.Name[i])); } findView(); } public void findView(){ setContentView(R.layout.listview); objlistview = (ListView)findViewById(R.id.listView1); objlistview.setOnItemClickListener(this); objlistadapter = new ListAdapter(this,objList); objlistview.setAdapter(objlistadapter); } public void onItemClick(AdapterView<?> arg0, View arg1, int position, long id) { if(objList.get(position).isCheck() == false) { objList.get(position).setCheck(true); findView(); } else if(objList.get(position).isCheck() == true) { objList.get(position).setCheck(false); findView(); } } //オプションメニューを選択した時の処理 public boolean onOptionsItemSelected(MenuItem item){ switch (item.getItemId()){ case MENU_ITEM0: finish(); return true; } return true; } /* * エラーなど確認用などに便利。おまけ */ public void makeTost(String str,int id){ int tost = Toast.LENGTH_LONG; if(id==1) tost = Toast.LENGTH_SHORT; Toast toast = Toast.makeText(this.getApplicationContext(), str, tost); toast.show(); } } ファイル名「ListAdapter.ava」 package com.example.checkboxtest3; import java.util.List; import android.content.Context; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.BaseAdapter; import android.widget.CheckBox; import android.widget.TextView; import android.widget.Toast; class ListAdapter extends BaseAdapter{ private Context context; private List<Object> list; public MainActivity mk; public ListAdapter(Context context,List obj) { super(); this.context = context; list = obj; mk = (MainActivity)context; } public int getCount() {return list.size();} public Object getItem(int position) {return list.get(position);} public long getItemId(int position) {return position;} public View getView(int position, View convertView, ViewGroup parent) { ViewHolder_Kouka holder; Object ob = (Object) getItem(position); if (convertView == null) { LayoutInflater inflater = LayoutInflater.from(context); convertView = inflater.inflate(R.layout.listview_row, null); holder = new ViewHolder_Kouka(); holder.h_checkbox = (CheckBox) convertView.findViewById(R.id.checkBox1); holder.h_name = (TextView) convertView.findViewById(R.id.textView1); convertView.setTag(holder); } else { holder = (ViewHolder_Kouka) convertView.getTag(); } holder.h_checkbox.setChecked(ob.isCheck()); holder.h_name.setText(ob.getName()); return convertView; } class ViewHolder_Kouka { CheckBox h_checkbox; TextView h_name; } /* * エラーなど確認用などに便利。おまけ */ public void makeTost(String str,int id){ int tost = Toast.LENGTH_LONG; if(id==1) tost = Toast.LENGTH_SHORT; Toast toast = Toast.makeText(mk.getApplicationContext(), str, tost); toast.show(); } } ファイル名「Hensu.ava」 package com.example.checkboxtest3; public class Hensu { String[] Name = { "アイアンマン","スタートレック","インデペンデンスデイ","アバター","スカイキャプテン", "トリプルX","ミッション・トゥ・マーズ","エボリューション","プルートナッシュ","フィフスエレメント", "リディック","トランスフォーマー","マイノリティリポート","ハリーポッター","バビロンAD" }; boolean[] check = { false,false,false,false,false, false,false,false,false,false, false,false,false,false,false }; } ファイル名「Object.ava」 package com.example.checkboxtest3; public class Object { private boolean check; private String name; Object(boolean check,String name) { this.setCheck(check); this.setName(name); } public boolean isCheck() { return check; } public void setCheck(boolean check) { this.check = check; } public String getName() { return name; } public void setName(String name) { this.name = name; } } ファイル名「activity_main.xml」 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="fill_parent" android:layout_height="fill_parent" tools:context=".MainActivity" > <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerHorizontal="true" android:layout_centerVertical="true" android:text="@string/hello_world" /> </RelativeLayout> ファイル名「listview.xml」 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="fill_parent" android:layout_height="fill_parent" tools:context=".MainActivity" > <ListView android:id="@+id/listView1" android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_alignParentLeft="true" android:layout_alignParentTop="true" > </ListView> </RelativeLayout> ファイル名「listview_row」 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="fill_parent" android:layout_height="fill_parent" tools:context=".MainActivity" > <CheckBox android:id="@+id/checkBox1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentLeft="true" android:layout_alignParentTop="true" android:checked="false" android:enabled="false" android:focusable="false" android:focusableInTouchMode="true" /> <TextView android:id="@+id/textView1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignBaseline="@+id/checkBox1" android:layout_alignBottom="@+id/checkBox1" android:layout_alignParentRight="true" android:layout_toRightOf="@+id/checkBox1" android:gravity="center" android:text="TextView" /> </RelativeLayout> |
|
listview_rowのCheckBox部品のとこでandroid:enabled="false"としタップが効かないようにしています。ListViewの行をタップすることでチェックするようにしました。メリットは色々なことを考えないでチェックできるということ。デメリットは行タップでの動作がチェックしかできなくなることです。 ちなみにこのプログラムは完成していません。スクロールしてチェックするとわかりますが、スクロールが初期位置に戻ってしまいます。これをなんとかしなければいけないのもデメリットですね。 ListViewの位置を記憶できるメソッドがあります。それを使います。 listview_position = objlistview.getFirstVisiblePosition(); objlistview.setSelectionFromTop(listview_position, listview_y);で先ほどの値を渡すことでListviewを記憶した位置に設定することができます。これはアダプターの設定などの後に書いておくといい感じになります。 |
ファイル名「MainActivity.java」 package com.example.checkboxtest3; import java.util.ArrayList; import java.util.List; import android.os.Bundle; import android.app.Activity; import android.graphics.PixelFormat; import android.view.Display; import android.view.MenuItem; import android.view.View; import android.view.Window; import android.view.WindowManager; import android.widget.AdapterView; import android.widget.AdapterView.OnItemClickListener; import android.widget.ListView; import android.widget.Toast; public class MainActivity extends Activity implements OnItemClickListener{ private static final int MENU_ITEM0=0; float disp_w; float disp_h; Hensu hensu = new Hensu(); static ListAdapter objlistadapter; ListView objlistview; static List<Object> objList = new ArrayList<Object>(); int listview_position = 0; int listview_y = 0; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); requestWindowFeature(Window.FEATURE_NO_TITLE); getWindow().setFormat(PixelFormat.TRANSLUCENT); Window window = getWindow(); window.addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); WindowManager manager = window.getWindowManager(); Display disp = manager.getDefaultDisplay(); disp_w = disp.getWidth(); disp_h = disp.getHeight(); init(); } public void init(){ for(int i=0;i<hensu.Name.length;i++) { objList.add(new Object(hensu.check[i],hensu.Name[i])); } findView(); } public void findView(){ setContentView(R.layout.listview); objlistview = (ListView)findViewById(R.id.listView1); objlistview.setOnItemClickListener(this); objlistadapter = new ListAdapter(this,objList); objlistview.setAdapter(objlistadapter); //ここでスクロール位置を設定 objlistview.setSelectionFromTop(listview_position, listview_y); } public void setObjList(){ setContentView(R.layout.listview); } public void onItemClick(AdapterView<?> arg0, View arg1, int position, long id) { if(objList.get(position).isCheck() == false) { objList.get(position).setCheck(true); } else if(objList.get(position).isCheck() == true) { objList.get(position).setCheck(false); } //ここでスクロール位置を記憶 listview_position = objlistview.getFirstVisiblePosition(); listview_y = objlistview.getChildAt(0).getTop(); findView(); } //オプションメニューを選択した時の処理 public boolean onOptionsItemSelected(MenuItem item){ switch (item.getItemId()){ case MENU_ITEM0: finish(); return true; } return true; } /* * エラーなど確認用などに便利。おまけ */ public void makeTost(String str,int id){ int tost = Toast.LENGTH_LONG; if(id==1) tost = Toast.LENGTH_SHORT; Toast toast = Toast.makeText(this.getApplicationContext(), str, tost); toast.show(); } } |
これを実行してみて下さい。ウマくいくはずです。しかし ウスウス気づいている人もいるかもしれません・・・そうですこれは所詮逃げです!! とりあえずもう一回勉強しなおすことにします(つд・) < |