Android実戦技術:ListViewリフレッシュの手順問題

9017 ワード

背景
典型的なListViewでは、各ItemはTaskを表すTextViewを表示し、2つの編集方法を実装する必要があります.1つはCheckBoxでタスクが完了したことを識別し、もう1つはタスクを削除することです.完了したCheckBoxについてはレイアウトにそのまま置けばよいが、削除についてはContextMenuを使用して編集を実現したくないため、iOSのようなリストについては、リスト内の各項目に対するジェスチャーで削除がトリガーされる.これを実現するのは難しくなく、1つのViewSwitcher、Checkboxと削除ボタンを入れることができ、ViewSwitcherにどれを表示するかを制御させ、通常はCheckboxを表示し、削除ボタンを非表示にし、Itemをクリックすると削除ボタンを表示し、Checkboxを非表示にすることができ、このように操作習慣にも合致し、1つ1つの項目の削除ができる.
実現する方法は次のとおりです.
public class ListOrderActivity extends Activity {
    private ListView mTaskList;
    private EditText mAddTaskEditor;
    private LayoutInflater mFactory;
    
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.list_activity);
        
        mFactory = LayoutInflater.from(getApplication());
        mTaskList = (ListView) findViewById(R.id.task_list);
        final View headerView = mFactory.inflate(R.layout.header_view, null);
        mTaskList.addHeaderView(headerView);
        mAddTaskEditor = (EditText) headerView.findViewById(R.id.task_editor);
        mAddTaskEditor.setOnKeyListener(new OnKeyListener() {
            @Override
            public boolean onKey(View view, int keycode, KeyEvent event) {
        	if (keycode == KeyEvent.KEYCODE_DPAD_CENTER || keycode == KeyEvent.KEYCODE_ENTER) {
        	    // finish editing
        	    final InputMethodManager inputManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
        	    inputManager.hideSoftInputFromWindow(getCurrentFocus().getWindowToken(), 0);
        	    final String text = mAddTaskEditor.getText().toString();
        	    if (!TextUtils.isEmpty(text)) {
        		final ContentValues values = new ContentValues(1);
        		values.put(TaskColumns.TASK, text);
        		values.put(TaskColumns.TYPE, Task.TYPE_TODAY);
        		getContentResolver().insert(Task.CONTENT_URI, values);
        	    }
        	    mAddTaskEditor.setText("");
        	}
        	return false;
            }
        });
        final Cursor cursor = getContentResolver().query(Task.CONTENT_URI, Task.PROJECTION, TaskColumns.TYPE + " = " + Task.TYPE_TODAY, null, null);
        final TaskAdapter adapter = new TaskAdapter(getApplication(), cursor);
        mTaskList.setAdapter(adapter);
    }
    
    private class TaskAdapter extends CursorAdapter {
        private Cursor mCursor;
        
        public TaskAdapter(Context context, Cursor c) {
            super(context, c);
            mCursor = c;
        }
        
        @Override
        public void bindView(View view, Context context, Cursor cursor) {
            if (view  == null) {
                view = mFactory.inflate(R.layout.today_task_item, null);
            }

            final ViewSwitcher switcher = (ViewSwitcher) view.findViewById(R.id.action_switcher);
//            if (switcher.getDisplayedChild() == 1) {
//        	switcher.clearAnimation();
//        	switcher.showPrevious();
//        	switcher.clearAnimation();
//            }
            final CheckBox toggle = (CheckBox) view.findViewById(R.id.action_toggle_done);
            final short done = cursor.getShort(ProjectionIndex.DONE);
            final int id = cursor.getInt(ProjectionIndex.ID);
            toggle.setOnCheckedChangeListener(null);
            toggle.setChecked(done != 0);
            toggle.setOnCheckedChangeListener(new OnCheckedChangeListener() {
        	@Override
        	public void onCheckedChanged(CompoundButton view, boolean checked) {
        	    final Uri uri = ContentUris.withAppendedId(Task.CONTENT_URI, id);
        	    final ContentValues values = new ContentValues(1);
        	    values.put(TaskColumns.DONE, checked ? 1 : 0);
        	    getContentResolver().update(uri, values, null, null);
        	}

            });
            view.setOnClickListener(new OnClickListener() {
	        @Override
	        public void onClick(View v) {
	            switcher.showNext();
	            if (switcher.getDisplayedChild() == 0) {
	        	switcher.getInAnimation().setAnimationListener(null);
	        	return;
	            }
	            final ImageView delete = (ImageView) v.findViewById(R.id.action_delete_task);
	            delete.setOnClickListener(new OnClickListener() {
		        @Override
		        public void onClick(View v) {
		            switcher.getInAnimation().setAnimationListener(new AnimationListener() {
		        	@Override
		        	public void onAnimationEnd(Animation animation) {
		        	    switcher.getInAnimation().setAnimationListener(null);
		        	    final Uri uri = ContentUris.withAppendedId(Task.CONTENT_URI, id);
		        	    getContentResolver().delete(uri, null, null);
		        	}
		        	
		        	@Override
		        	public void onAnimationRepeat(Animation animation) {
		        	}
		        	
		        	@Override
		        	public void onAnimationStart(Animation animation) {
		        	}
		            });
		            switcher.showPrevious();
		        }
		    });
	        }
	    });
            TextView task = (TextView) view.findViewById(R.id.task);
            final String taskContent = cursor.getString(ProjectionIndex.TASK);
            if (done != 0) {
        	final Spannable style = new SpannableString(taskContent);
        	style.setSpan(new StrikethroughSpan(), 0, taskContent.length(), Spannable.SPAN_INCLUSIVE_INCLUSIVE);
        	style.setSpan(new StyleSpan(Typeface.ITALIC) , 0, taskContent.length(), Spannable.SPAN_INCLUSIVE_INCLUSIVE);
        	task.setText(style);
        	task.setTextAppearance(getApplication(), R.style.done_task_item_text);
            } else {
        	task.setText(taskContent);
        	task.setTextAppearance(getApplication(), R.style.task_item_text);
            }
        }

        @Override
        public View newView(Context context, Cursor cursor, ViewGroup parent) {
            View view = mFactory.inflate(R.layout.today_task_item, null);
            return view;
        }
        
        @Override
        public void onContentChanged() {
            mCursor.requery();
        }
    }
}


    


    
	    
	    
    
	

に質問
しかし、あるエントリが削除されている場合、新しいタスクを追加したり、別のエントリのCheckboxをクリックしたりすると、エントリの状態が乱れ、本来正常なエントリが削除されているという問題があります.
原因分析
最初はデータの問題だと思っていましたが、イベントの処理は匿名のクラスなので、正しくない外部データを指す可能性があり、印刷デバッグですべてのデータが正しいことがわかりました.最後にbindViewメソッドにLOG情報を加えることで,ListViewがbindViewをリフレッシュするたびに順序が異なり,元々3番目のサブViewにあり,リフレッシュ後に1番目の位置に置かれる可能性があることが分かった.ViewSwitcherの表示状態は、自身が維持している、つまり、表示すべき状態がViewの外部に保存されていないため、データが変化して(Checkboxがデータの変化を引き起こす)リストがリフレッシュされると、元の削除状態にあったサブView(4番目の位置にある可能性がある)が2番目の位置になり、2番目が削除状態になり、4番目が正常な状態になる可能性がある.
ソリューション
この問題は完璧な解決方法がなく、Workaroundの方法しかできません.それはbindViewをリフレッシュするたびに削除状態をクリアし、デフォルト状態に変えることで、少なくとも状態が混乱しないようにすることです.しかし、削除すると点滅するのが見えます.
この問題を完全に解決するには、他の方法でデータが変化する場合にこのような設計を使用することを避けることです.この設計は、リスト全体が削除されるだけで、リストがリフレッシュされる他の方法がない場合にのみ適用されます.この場合、削除するたびに、サブViewをリストViewから直接削除すると、混乱することはありません.
また、bindViewを作成するたびにデータを追加するのではなく、サブViewにデータを追加する理由も説明します.bindViewをリフレッシュするたびに順序が以前の順序とは限らないので、必ずデータを再追加しなければなりません.一方、データは通常、Viewと共有されたり、データベース内に存在したり、他の形式で存在したりします.これは、Viewのリフレッシュによって変更されることはありません.そのため、ユーザーがステータスの乱れを感じないように、データの順序に従ってViewにデータを再入力する必要があります.