2012-02-24 78 views
11

Tôi chưa bao giờ hài lòng với mã trên CursorAdapter tùy chỉnh của mình cho đến hôm nay, tôi quyết định xem lại và khắc phục một vấn đề nhỏ khiến tôi bận tâm trong một thời gian dài (không có ai trong số các ứng dụng của tôi từng báo cáo vấn đề như vậy).Đây có phải là CursorAdapter tùy chỉnh cho một ListView được mã hóa đúng cho Android không?

Dưới đây là một mô tả nhỏ của câu hỏi của tôi:

CursorAdapter tùy chỉnh của tôi đè newView()bindView() thay vì getView() như hầu hết các ví dụ tôi thấy. Tôi sử dụng mẫu ViewHolder giữa 2 phương thức này. Nhưng vấn đề chính của tôi là với bố cục tùy chỉnh tôi đang sử dụng cho từng mục danh sách, nó chứa một số ToggleButton.

Vấn đề là trạng thái nút không được giữ khi chế độ xem mục danh sách được cuộn ra khỏi chế độ xem và sau đó cuộn lại vào chế độ xem. Vấn đề này tồn tại vì cursor chưa bao giờ biết rằng dữ liệu cơ sở dữ liệu đã thay đổi khi số ToggleButton được nhấn và nó luôn kéo cùng một dữ liệu. Tôi đã cố gắng truy vấn lại con trỏ khi nhấp vào ToggleButton và giải quyết vấn đề, nhưng nó rất chậm.

Tôi đã giải quyết vấn đề này và tôi đang đăng toàn bộ lớp học ở đây để được xem xét. Tôi đã nhận xét mã kỹ lưỡng cho câu hỏi cụ thể này để giải thích rõ hơn về các quyết định mã hóa của tôi.

Mã này có phù hợp với bạn không? Bạn sẽ cải thiện/tối ưu hóa hoặc thay đổi nó bằng cách nào đó?

P.S: Tôi biết CursorLoader là một cải tiến rõ ràng nhưng tôi không có thời gian để đối phó với các mã viết lớn như vậy trong thời gian này. Đó là điều tôi có trong lộ trình.

Dưới đây là các mã:

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); 
    } 

} 

Trả lời

4

giải pháp của bạn là tối ưu một tôi sẽ thêm nó vào vũ khí của tôi :) Có lẽ, tôi sẽ cố gắng để mang lại một chút tối ưu hóa cho các cuộc gọi đến cơ sở dữ liệu.

Thật vậy, vì điều kiện công việc, chỉ có ba giải pháp:

  1. Cập nhật chỉ có một hàng, requery con trỏ và vẽ lại tất cả các mục. (Thẳng về phía trước, sức mạnh vũ phu).
  2. Cập nhật hàng, lưu trữ kết quả và sử dụng bộ nhớ cache để vẽ các mục.
  3. Cache kết quả, sử dụng bộ nhớ cache để vẽ các mục. Và khi bạn rời khỏi hoạt động/đoạn này, hãy cam kết kết quả vào cơ sở dữ liệu.

Đối với giải pháp thứ ba, bạn có thể sử dụng SparseArray để tìm kiếm thay đổi.

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. 
} 

Một lần nữa: từ khi bắt đầu mảng này trống. Khi bạn chuyển đổi nút lần đầu tiên (có thay đổi), bạn thêm NoteData vào mảng. Khi bạn chuyển đổi nút lần thứ hai (có một rollback), bạn loại bỏ NoteData khỏi mảng. Và cứ thế.

Khi bạn hoàn tất, chỉ cần yêu cầu mảng và đẩy các thay đổi vào cơ sở dữ liệu.

+0

Tôi thích ý tưởng về 'SparseArray', không biết về lớp đó. Nó trông giống như một cách hiệu quả hơn trong việc lưu bộ nhớ đệm nút thay vì lưu tất cả chúng trong một 'Danh sách'. Nhưng tôi không thích ý tưởng chỉ cam kết kết quả vào cơ sở dữ liệu khi người dùng rời khỏi hoạt động. Điều đó sẽ cần thêm mã để xử lý tình huống đó. Vì vậy, cuối cùng, tôi đoán tôi đang chọn bằng giải pháp thứ hai mà bạn liệt kê. Đó là cơ bản những gì tôi đã làm ở nơi đầu tiên. Tôi vẫn thích câu trả lời của bạn nhưng tôi sẽ có thể 1 hoặc 2 ngày nữa :) –

1

Điều bạn đang thấy là Xem lại việc sử dụng Android. Tôi không nghĩ rằng bạn đang làm điều gì đó sai trái bằng cách truy vấn lại con trỏ. Chỉ cần không sử dụng hàm cursor.requery().

Thay vào đó, hãy đặt nút gạt thành false lúc đầu tiên và sau đó hỏi con trỏ và bật nó nếu bạn cần.

Có thể bạn đã làm điều đó và tôi đã hiểu nhầm điều gì đó, tuy nhiên tôi không nghĩ rằng bạn nên có kết quả chậm làm việc đó.

Pseudo-code:

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

tôi sẽ chờ đợi trước khi đi CursorLoader. Có vẻ như các dẫn xuất CursorLoader không lo lắng với CursorLoader.