2012-02-24 2 views
11

나는 커스텀 CursorAdapter의 코드가 만족 스러울 때까지는 리뷰를 작성하지 않고 오랫동안 나를 괴롭혔던 약간의 문제를 수정했다. (흥미롭게도, 내 앱 사용자 중 누구도 그런 문제를보고하지 않았다.).안드로이드 용으로 적절히 코딩 된 ListView에 대한이 커스텀 커서 어댑터입니까?

내 사용자 정의 CursorAdapter 내가 볼 예제들은 newView()bindView() 대신 getView() 오버라이드 (override) :

여기에 내 질문의 작은 설명입니다. 나는이 두 가지 방법 사이에서 ViewHolder 패턴을 사용한다. 하지만 내 주요 문제는 내가 각 목록 항목에 사용하고있는 사용자 정의 레이아웃과 함께, 그것은 ToggleButton가 포함되어 있습니다.

목록 항목보기가 스크롤되지 않고 스크롤 된 다음보기로 돌아갈 때 단추 상태가 유지되지 않는 것이 문제였습니다. 이 문제는 cursorToggleButton을 눌렀을 때 데이터베이스 데이터가 변경되었음을 전혀 알지 못했기 때문에 존재했으며 항상 동일한 데이터를 가져오고있었습니다. ToggleButton을 클릭 할 때 커서를 다시 쿼리하려고 시도했지만 문제가 해결되었지만 속도가 매우 느려졌습니다.

나는이 문제를 해결했으며 리뷰를 위해 전체 수업을 게시하고 있습니다. 필자는 코딩 결정을보다 잘 설명하기 위해이 특정 질문에 대한 코드를 철저히 설명했습니다.

이 코드가 좋게 보입니까? 어떻게 개선 시키거나 최적화 시키거나 변경할 것입니까?

P.S : CursorLoader가 분명히 개선되었음을 알고 있지만 당분간 큰 코드 재 작성을 처리 할 시간이 없습니다. 그것은 내가 로드맵에서 가지고있는 것입니다.

여기에 코드입니다 :

public class NotesListAdapter extends CursorAdapter implements OnClickListener { 

    private static class ViewHolder { 
     ImageView icon; 
     TextView title; 
     TextView description; 
     ToggleButton visibility; 
    } 

    private static class NoteData { 
     long id; 
     int iconId; 
     String title; 
     String description; 
     int position; 
    } 

    private LayoutInflater mInflater; 

    private NotificationHelper mNotificationHelper; 
    private AgendaNotesAdapter mAgendaAdapter; 

    /* 
    * This is used to store the state of the toggle buttons for each item in the list 
    */ 
    private List<Boolean> mToggleState; 

    private int mColumnRowId; 
    private int mColumnTitle; 
    private int mColumnDescription; 
    private int mColumnIconName; 
    private int mColumnVisibility; 

    public NotesListAdapter(Context context, Cursor cursor, NotificationHelper helper, AgendaNotesAdapter adapter) { 
     super(context, cursor); 

     mInflater = LayoutInflater.from(context); 

     /* 
     * Helper class to post notifications to the status bar and database adapter class to update 
     * the database data when the user presses the toggle button in any of items in the list 
     */ 
     mNotificationHelper = helper; 
     mAgendaAdapter = adapter; 

     /* 
     * There's no need to keep getting the column indexes every time in bindView() (as I see in 
     * a few examples) so I do it once and save the indexes in instance variables 
     */ 
     findColumnIndexes(cursor); 

     /* 
     * Populate the toggle button states for each item in the list with the corresponding value 
     * from each record in the database, but isn't this a slow operation? 
     */ 
     for(mToggleState = new ArrayList<Boolean>(); !cursor.isAfterLast(); cursor.moveToNext()) { 
      mToggleState.add(cursor.getInt(mColumnVisibility) != 0); 
     } 
    } 

    @Override 
    public View newView(Context context, Cursor cursor, ViewGroup parent) { 
     View view = mInflater.inflate(R.layout.list_item_note, null); 

     /* 
     * The ViewHolder pattern is here only used to prevent calling findViewById() all the time 
     * in bindView(), we only need to find all the views once 
     */ 
     ViewHolder viewHolder = new ViewHolder(); 

     viewHolder.icon = (ImageView)view.findViewById(R.id.imageview_icon); 
     viewHolder.title = (TextView)view.findViewById(R.id.textview_title); 
     viewHolder.description = (TextView)view.findViewById(R.id.textview_description); 
     viewHolder.visibility = (ToggleButton)view.findViewById(R.id.togglebutton_visibility); 

     /* 
     * I also use newView() to set the toggle button click listener for each item in the list 
     */ 
     viewHolder.visibility.setOnClickListener(this); 

     view.setTag(viewHolder); 

     return view; 
    } 

    @Override 
    public void bindView(View view, Context context, Cursor cursor) { 
     Resources resources = context.getResources(); 

     int iconId = resources.getIdentifier(cursor.getString(mColumnIconName), 
       "drawable", context.getPackageName()); 

     String title = cursor.getString(mColumnTitle); 
     String description = cursor.getString(mColumnDescription); 

     /* 
     * This is similar to the ViewHolder pattern and it's need to access the note data when the 
     * onClick() method is fired 
     */ 
     NoteData noteData = new NoteData(); 

     /* 
     * This data is needed to post a notification when the onClick() method is fired 
     */ 
     noteData.id = cursor.getLong(mColumnRowId); 
     noteData.iconId = iconId; 
     noteData.title = title; 
     noteData.description = description; 

     /* 
     * This data is needed to update mToggleState[POS] when the onClick() method is fired 
     */ 
     noteData.position = cursor.getPosition(); 

     /* 
     * Get our ViewHolder with all the view IDs found in newView() 
     */ 
     ViewHolder viewHolder = (ViewHolder)view.getTag(); 

     /* 
     * The Html.fromHtml is needed but the code relevant to that was stripped 
     */ 
     viewHolder.icon.setImageResource(iconId); 
     viewHolder.title.setText(Html.fromHtml(title)); 
     viewHolder.description.setText(Html.fromHtml(description)); 

     /* 
     * Set the toggle button state for this list item from the value in mToggleState[POS] 
     * instead of getting it from the database with 'cursor.getInt(mColumnVisibility) != 0' 
     * otherwise the state will be incorrect if it was changed between the item view scrolling 
     * out of view and scrolling back into view 
     */ 
     viewHolder.visibility.setChecked(mToggleState.get(noteData.position)); 

     /* 
     * Again, save the note data to be accessed when onClick() gets fired 
     */ 
     viewHolder.visibility.setTag(noteData); 
    } 

    @Override 
    public void onClick(View view) { 
     /* 
     * Get the new state directly from the toggle button state 
     */ 
     boolean visibility = ((ToggleButton)view).isChecked(); 

     /* 
     * Get all our note data needed to post (or remove) a notification 
     */ 
     NoteData noteData = (NoteData)view.getTag(); 

     /* 
     * The toggle button state changed, update mToggleState[POS] to reflect that new change 
     */ 
     mToggleState.set(noteData.position, visibility); 

     /* 
     * Post the notification or remove it from the status bar depending on toggle button state 
     */ 
     if(visibility) { 
      mNotificationHelper.postNotification(
        noteData.id, noteData.iconId, noteData.title, noteData.description); 
     } else { 
      mNotificationHelper.cancelNotification(noteData.id); 
     } 

     /* 
     * Update the database note item with the new toggle button state, without the need to 
     * requery the cursor (which is slow, I've tested it) to reflect the new toggle button state 
     * in the list because the value was saved in mToggleState[POS] a few lines above 
     */ 
     mAgendaAdapter.updateNote(noteData.id, null, null, null, null, visibility); 
    } 

    private void findColumnIndexes(Cursor cursor) { 
     mColumnRowId = cursor.getColumnIndex(AgendaNotesAdapter.KEY_ROW_ID); 
     mColumnTitle = cursor.getColumnIndex(AgendaNotesAdapter.KEY_TITLE); 
     mColumnDescription = cursor.getColumnIndex(AgendaNotesAdapter.KEY_DESCRIPTION); 
     mColumnIconName = cursor.getColumnIndex(AgendaNotesAdapter.KEY_ICON_NAME); 
     mColumnVisibility = cursor.getColumnIndex(AgendaNotesAdapter.KEY_VISIBILITY); 
    } 

} 

답변

4

귀하의 솔루션은 최적 인 나는 어쩌면, 나는 데이터베이스에 대한 호출에 대한 약간의 최적화를 가지고하려고합니다 :) 내 무기에 추가합니다. 때문에 작업 조건의 사실

는이 세 개의 솔루션입니다 :

  1. 업데이트 한 행, 다시 쿼리 커서와 모든 항목을 다시 그립니다. (직진, 무차별 대결).
  2. 행을 업데이트하고 결과를 캐시하고 도면 항목에 캐시를 사용합니다.
  3. 결과를 캐시하고 도면 항목에 캐시를 사용합니다. 그리고이 액티비티/프래그먼트를 떠날 때 데이터베이스에 결과를 커밋합니다.

세 번째 해결 방법은 SparseArray를 사용하여 변경 사항을 찾는 것입니다.

private SparseArray<NoteData> mArrayViewHolders; 

public void onClick(View view) { 
    //here your logic with NoteData. 
    //start of my improve 
    if (mArrayViewHolders.get(selectedPosition) == null) { 
     // put the change into array 
     mArrayViewHolders.put(selectedPosition, noteData); 
    } else { 
     // rollback the change 
     mArrayViewHolders.delete(selectedPosition); 
    } 
    //end of my improve 
    //we don't commit the changes to database. 
} 

다시 한번 :이 배열은 처음부터 비어 있습니다. 변경 사항이있는 버튼을 처음으로 토글하면 배열에 NoteData를 추가합니다. 버튼을 두 번째로 전환하면 (롤백이 있음) 배열에서 NoteData가 제거됩니다. 등등.

완료하면 어레이를 요청하고 변경 사항을 데이터베이스로 푸시합니다.

+0

나는 SparseArray라는 아이디어를 좋아했고, 그 클래스에 대해 몰랐다. 이것은 버튼 상태를 모두'List'에 저장하는 대신에보다 효율적으로 캐싱하는 방법입니다. 하지만 사용자가 작업을 떠날 때 결과를 데이터베이스에 커밋하기를 좋아하지 않습니다. 그 상황을 처리하려면 추가 코드가 필요합니다. 결국, 당신이 열거 한 두 번째 해결책을 선택하고있는 것 같습니다. 기본적으로 내가 처음에 무엇을했는지. 나는 아직도 당신의 대답을 좋아하지만 아마도 1 ~ 2 일 더 가고 있습니다 :) –

1

귀하가보고있는 것은 Android의 View 재사용입니다. 나는 커서를 다시 쿼리함으로써 당신이 뭔가 잘못하고 있다고 생각하지 않는다. 그냥 cursor.requery() 함수를 사용하지 마십시오.

대신 항상 처음부터 토글을 false로 설정 한 다음 커서를 사용하여 요청해야합니다.

어쩌면 당신이 그 일을하고 있었는데 뭔가 오해했을 수도 있습니다.하지만 느린 결과를 가져야한다고 생각하지 않습니다.

의사 코드 :

getView(){ 
setToggleFalse(); 
boolean value = Boolean.valueOf(cursor.getString('my column')); 
if (value){ 
    setToggleTrue(); 
} 
} 
1

나는 CursorLoader에 가기 전에 기다릴 것입니다. 마찬가지로 CursorLoader 파생 CursorLoader와 함께 worl하지 않습니다.

관련 문제