2013-12-14 47 views
15

我爲做了一個應用程序,並且我使用CursorTreeAdapter作爲ExpandableListView。現在我想用搜索框來顯示已過濾的ExpandableListView項目。就像這樣:http://i.imgur.com/8ua7Mkl.png帶搜索實現的CursorTreeAdapter

下面的代碼是我到目前爲止:

MainActivity.java

package com.example.cursortreeadaptersearch; 

import java.util.HashMap; 

import android.app.SearchManager; 
import android.content.Context; 
import android.database.ContentObserver; 
import android.database.Cursor; 
import android.net.Uri; 
import android.os.Bundle; 
import android.os.Handler; 
import android.provider.ContactsContract; 
import android.support.v4.app.LoaderManager; 
import android.support.v4.app.LoaderManager.LoaderCallbacks; 
import android.support.v4.content.CursorLoader; 
import android.support.v4.content.Loader; 
import android.util.Log; 
import android.widget.ExpandableListView; 
import android.widget.SearchView; 
import android.widget.SearchView.OnCloseListener; 
import android.widget.SearchView.OnQueryTextListener; 

import com.actionbarsherlock.app.SherlockFragmentActivity; 

public class MainActivity extends SherlockFragmentActivity { 

    private SearchView search; 
    private MyListAdapter listAdapter; 
    private ExpandableListView myList; 

    private final String DEBUG_TAG = getClass().getSimpleName().toString(); 

    /** 
    * The columns we are interested in from the database 
    */ 
    static final String[] CONTACTS_PROJECTION = new String[] { 
      ContactsContract.Contacts._ID, 
      ContactsContract.Contacts.DISPLAY_NAME, 
      ContactsContract.Contacts.PHOTO_ID, 
      ContactsContract.CommonDataKinds.Email.DATA, 
      ContactsContract.CommonDataKinds.Photo.CONTACT_ID }; 

    static final String[] GROUPS_SUMMARY_PROJECTION = new String[] { 
      ContactsContract.Groups.TITLE, ContactsContract.Groups._ID, 
      ContactsContract.Groups.SUMMARY_COUNT, 
      ContactsContract.Groups.ACCOUNT_NAME, 
      ContactsContract.Groups.ACCOUNT_TYPE, 
      ContactsContract.Groups.DATA_SET }; 

    @Override 
    public void onCreate(Bundle savedInstanceState) { 
     super.onCreate(savedInstanceState); 
     setContentView(R.layout.activity_main); 

     SearchManager searchManager = (SearchManager) getSystemService(Context.SEARCH_SERVICE); 
     search = (SearchView) findViewById(R.id.search); 
     search.setSearchableInfo(searchManager 
       .getSearchableInfo(getComponentName())); 
     search.setIconifiedByDefault(false); 
     search.setOnQueryTextListener(new OnQueryTextListener() { 

      @Override 
      public boolean onQueryTextSubmit(String query) { 
       listAdapter.filterList(query); 
       expandAll(); 
       return false; 
      } 

      @Override 
      public boolean onQueryTextChange(String query) { 
       listAdapter.filterList(query); 
       expandAll(); 
       return false; 
      } 
     }); 

     search.setOnCloseListener(new OnCloseListener() { 

      @Override 
      public boolean onClose() { 
       listAdapter.filterList(""); 
       expandAll(); 
       return false; 
      } 
     }); 

     // get reference to the ExpandableListView 
     myList = (ExpandableListView) findViewById(R.id.expandableList); 
     // create the adapter 
     listAdapter = new MyListAdapter(null, MainActivity.this); 
     // attach the adapter to the list 
     myList.setAdapter(listAdapter); 

     Loader<Cursor> loader = getSupportLoaderManager().getLoader(-1); 
     if (loader != null && !loader.isReset()) { 
      runOnUiThread(new Runnable() { 
       public void run() { 
        getSupportLoaderManager().restartLoader(-1, null, 
          mSpeakersLoaderCallback); 
       } 
      }); 
     } else { 
      runOnUiThread(new Runnable() { 
       public void run() { 
        getSupportLoaderManager().initLoader(-1, null, 
          mSpeakersLoaderCallback).forceLoad(); 
        ; 
       } 
      }); 
     } 

    } 

    @Override 
    public void onResume() { 
     super.onResume(); 

     getApplicationContext().getContentResolver().registerContentObserver(
       ContactsContract.Data.CONTENT_URI, true, 
       mSpeakerChangesObserver); 
    } 

    @Override 
    public void onPause() { 
     super.onPause(); 

     getApplicationContext().getContentResolver().unregisterContentObserver(
       mSpeakerChangesObserver); 
    } 

    // method to expand all groups 
    private void expandAll() { 
     int count = listAdapter.getGroupCount(); 
     for (int i = 0; i < count; i++) { 
      myList.expandGroup(i); 
     } 
    } 

    public LoaderManager.LoaderCallbacks<Cursor> mSpeakersLoaderCallback = new LoaderCallbacks<Cursor>() { 

     @Override 
     public Loader<Cursor> onCreateLoader(int id, Bundle args) { 
      Log.d(DEBUG_TAG, "onCreateLoader for loader_id " + id); 
      CursorLoader cl = null; 

      HashMap<Integer, Integer> groupMap = listAdapter.getGroupMap(); 
      if (id != -1) { 
       int groupPos = groupMap.get(id); 
       if (groupPos == 0) { // E-mail group 
        String[] PROJECTION = new String[] { 
          ContactsContract.RawContacts._ID, 
          ContactsContract.CommonDataKinds.Email.DATA }; 
        String sortOrder = "CASE WHEN " 
          + ContactsContract.Contacts.DISPLAY_NAME 
          + " NOT LIKE '%@%' THEN 1 ELSE 2 END, " 
          + ContactsContract.Contacts.DISPLAY_NAME + ", " 
          + ContactsContract.CommonDataKinds.Email.DATA 
          + " COLLATE NOCASE"; 
        String selection = ContactsContract.CommonDataKinds.Email.DATA 
          + " NOT LIKE ''"; 
        cl = new CursorLoader(getApplicationContext(), 
          ContactsContract.CommonDataKinds.Email.CONTENT_URI, 
          PROJECTION, selection, null, sortOrder); 
       } else if (groupPos == 1) { // Name group 
        Uri contactsUri = ContactsContract.Data.CONTENT_URI; 
        String selection = "((" 
          + ContactsContract.CommonDataKinds.GroupMembership.DISPLAY_NAME 
          + " NOTNULL) AND (" 
          + ContactsContract.CommonDataKinds.GroupMembership.HAS_PHONE_NUMBER 
          + "=1) AND (" 
          + ContactsContract.CommonDataKinds.GroupMembership.DISPLAY_NAME 
          + " != '') AND (" 
          + ContactsContract.CommonDataKinds.GroupMembership.GROUP_ROW_ID 
          + " = '1'))"; // Row ID 1 == All contacts 
        String sortOrder = ContactsContract.CommonDataKinds.GroupMembership.DISPLAY_NAME 
          + " COLLATE LOCALIZED ASC"; 

        cl = new CursorLoader(getApplicationContext(), contactsUri, 
          CONTACTS_PROJECTION, selection, null, sortOrder); 
       } 
      } else { 
       // group cursor 
       Uri groupsUri = ContactsContract.Groups.CONTENT_SUMMARY_URI; 
       String selection = "((" + ContactsContract.Groups.TITLE 
         + " NOTNULL) AND (" + ContactsContract.Groups.TITLE 
         + " == 'Coworkers') OR (" 
         + ContactsContract.Groups.TITLE 
         + " == 'My Contacts'))"; // Select only Coworkers 
               // (E-mail only) and My 
               // Contacts (Name only) 
       String sortOrder = ContactsContract.Groups.TITLE 
         + " COLLATE LOCALIZED ASC"; 
       cl = new CursorLoader(getApplicationContext(), groupsUri, 
         GROUPS_SUMMARY_PROJECTION, selection, null, sortOrder); 
      } 

      return cl; 
     } 

     @Override 
     public void onLoadFinished(Loader<Cursor> loader, Cursor data) { 
      // Swap the new cursor in. 
      int id = loader.getId(); 
//   Log.d("Dump Cursor MainActivity", 
//     DatabaseUtils.dumpCursorToString(data)); 
      Log.d(DEBUG_TAG, "onLoadFinished() for loader_id " + id); 
      if (id != -1) { 
       // child cursor 
       if (!data.isClosed()) { 
        Log.d(DEBUG_TAG, "data.getCount() " + data.getCount()); 

        HashMap<Integer, Integer> groupMap = listAdapter 
          .getGroupMap(); 
        try { 
         int groupPos = groupMap.get(id); 
         Log.d(DEBUG_TAG, "onLoadFinished() for groupPos " 
           + groupPos); 
         listAdapter.setChildrenCursor(groupPos, data); 
        } catch (NullPointerException e) { 
         Log.w("DEBUG", 
           "Adapter expired, try again on the next query: " 
             + e.getMessage()); 
        } 
       } 
      } else { 
       listAdapter.setGroupCursor(data); 
      } 
     } 

     @Override 
     public void onLoaderReset(Loader<Cursor> loader) { 
      // This is called when the last Cursor provided to onLoadFinished() 
      // is about to be closed. 
      int id = loader.getId(); 
      Log.d(DEBUG_TAG, "onLoaderReset() for loader_id " + id); 
      if (id != 1) { 
       // child cursor 
       try { 
        listAdapter.setChildrenCursor(id, null); 
       } catch (NullPointerException e) { 
        Log.w(DEBUG_TAG, 
          "Adapter expired, try again on the next query: " 
            + e.getMessage()); 
       } 
      } else { 
       listAdapter.setGroupCursor(null); 
      } 
     } 
    }; 

    private ContentObserver mSpeakerChangesObserver = new ContentObserver(
      new Handler()) { 

     @Override 
     public void onChange(boolean selfChange) { 
      if (getApplicationContext() != null) { 
       runOnUiThread(new Runnable() { 
        public void run() { 
         getSupportLoaderManager().restartLoader(-1, null, 
           mSpeakersLoaderCallback); 
        } 
       }); 
      } 
     } 
    }; 
} 

MyListAdapter.java

package com.example.cursortreeadaptersearch; 

import java.util.HashMap; 

import android.content.Context; 
import android.database.Cursor; 
import android.provider.ContactsContract; 
import android.support.v4.content.Loader; 
import android.util.Log; 
import android.view.LayoutInflater; 
import android.view.View; 
import android.view.ViewGroup; 
import android.widget.CursorTreeAdapter; 
import android.widget.TextView; 

public class MyListAdapter extends CursorTreeAdapter { 

    public HashMap<String, View> childView = new HashMap<String, View>(); 

    /** 
    * The columns we are interested in from the database 
    */ 

    private final String DEBUG_TAG = getClass().getSimpleName().toString(); 

    protected final HashMap<Integer, Integer> mGroupMap; 

    private MainActivity mActivity; 
    private LayoutInflater mInflater; 

    String mConstraint; 

    public MyListAdapter(Cursor cursor, Context context) { 

     super(cursor, context); 
     mActivity = (MainActivity) context; 
     mInflater = LayoutInflater.from(context); 
     mGroupMap = new HashMap<Integer, Integer>(); 
    } 

    @Override 
    public View newGroupView(Context context, Cursor cursor, 
      boolean isExpanded, ViewGroup parent) { 

     final View view = mInflater.inflate(R.layout.list_group, parent, false); 
     return view; 
    } 

    @Override 
    public void bindGroupView(View view, Context context, Cursor cursor, 
      boolean isExpanded) { 

     TextView lblListHeader = (TextView) view 
       .findViewById(R.id.lblListHeader); 

     if (lblListHeader != null) { 
      lblListHeader.setText(cursor.getString(cursor 
        .getColumnIndex(ContactsContract.Groups.TITLE))); 
     } 
    } 

    @Override 
    public View newChildView(Context context, Cursor cursor, 
      boolean isLastChild, ViewGroup parent) { 

     final View view = mInflater.inflate(R.layout.list_item, parent, false); 

     return view; 
    } 

    @Override 
    public void bindChildView(View view, Context context, Cursor cursor, 
      boolean isLastChild) { 

     TextView txtListChild = (TextView) view.findViewById(R.id.lblListItem); 

     if (txtListChild != null) { 
      txtListChild.setText(cursor.getString(1)); // Selects E-mail or 
                 // Display Name 
     } 

    } 

    protected Cursor getChildrenCursor(Cursor groupCursor) { 
     // Given the group, we return a cursor for all the children within that 
     // group 
     int groupPos = groupCursor.getPosition(); 
     int groupId = groupCursor.getInt(groupCursor 
       .getColumnIndex(ContactsContract.Groups._ID)); 

     Log.d(DEBUG_TAG, "getChildrenCursor() for groupPos " + groupPos); 
     Log.d(DEBUG_TAG, "getChildrenCursor() for groupId " + groupId); 

     mGroupMap.put(groupId, groupPos); 

     Loader loader = mActivity.getSupportLoaderManager().getLoader(groupId); 
     if (loader != null && !loader.isReset()) { 
      mActivity.getSupportLoaderManager().restartLoader(groupId, null, 
        mActivity.mSpeakersLoaderCallback); 
     } else { 
      mActivity.getSupportLoaderManager().initLoader(groupId, null, 
        mActivity.mSpeakersLoaderCallback); 
     } 

     return null; 
    } 

    // Access method 
    public HashMap<Integer, Integer> getGroupMap() { 
     return mGroupMap; 
    } 

    public void filterList(CharSequence constraint) { 
     // TODO Filter the data here 
    } 
} 

我已經非常顯着簡化和清理了代碼(s o你們不需要這樣做)。你可以看到,我共有3個遊標(1個用於組,2個用於兒童)。數據來自ContactsContract(這是用戶的聯繫人)。 來自子項1的光標代表所有聯繫人的所有電子郵件,子項2的光標代表聯繫人的所有顯示名稱。 (大多數加載函數是從here)。

現在唯一的問題是如何實現搜索?我應該通過內容提供者還是數據庫中的原始查詢來做到這一點?我希望顯示兩個子表的結果。我覺得因爲在輸入時很容易出錯,tokenize=porter對我來說是一個選項。

我希望有人能指引我一個好方向。

編輯:

我已經試過這MyListAdapter.java(與FilterQueryProviderKyle I.的建議):

public void filterList(CharSequence constraint) { 
    final Cursor oldCursor = getCursor(); 
    setFilterQueryProvider(filterQueryProvider); 
    getFilter().filter(constraint, new FilterListener() { 
     public void onFilterComplete(int count) { 
      // assuming your activity manages the Cursor 
      // (which is a recommended way) 
      notifyDataSetChanged(); 
//   stopManagingCursor(oldCursor); 
//   final Cursor newCursor = getCursor(); 
//   startManagingCursor(newCursor); 
//   // safely close the oldCursor 
      if (oldCursor != null && !oldCursor.isClosed()) { 
       oldCursor.close(); 
      } 
     } 
    }); 
} 

private FilterQueryProvider filterQueryProvider = new FilterQueryProvider() { 
    public Cursor runQuery(CharSequence constraint) { 
     // assuming you have your custom DBHelper instance 
     // ready to execute the DB request 
     String s = '%' + constraint.toString() + '%'; 
     return mActivity.getContentResolver().query(ContactsContract.Data.CONTENT_URI, 
       MainActivity.CONTACTS_PROJECTION, 
       ContactsContract.CommonDataKinds.GroupMembership.DISPLAY_NAME + " LIKE ?", 
      new String[] { s }, 
      null); 
    } 
}; 

而這MainActivity.java

 search.setOnQueryTextListener(new OnQueryTextListener() { 

      @Override 
      public boolean onQueryTextSubmit(String query) { 
       listAdapter.filterList(query); 
       expandAll(); 
       return false; 
      } 

      @Override 
      public boolean onQueryTextChange(String query) { 
       listAdapter.filterList(query); 
       expandAll(); 
       return false; 
      } 
     }); 

     search.setOnCloseListener(new OnCloseListener() { 

      @Override 
      public boolean onClose() { 
       listAdapter.filterList(""); 
       expandAll(); 
       return false; 
      } 
     }); 

但後來我得到這些錯誤,當我嘗試搜索:

12-20 13:20:19.449: E/CursorWindow(28747): Failed to read row 0, column -1 from a CursorWindow which has 96 rows, 4 columns. 
12-20 13:20:19.449: D/AndroidRuntime(28747): Shutting down VM 
12-20 13:20:19.449: W/dalvikvm(28747): threadid=1: thread exiting with uncaught exception (group=0x415c62a0) 
12-20 13:20:19.499: E/AndroidRuntime(28747): FATAL EXCEPTION: main 
12-20 13:20:19.499: E/AndroidRuntime(28747): java.lang.IllegalStateException: Couldn't read row 0, col -1 from CursorWindow. Make sure the Cursor is initialized correctly before accessing data from it. 

我做錯了嗎?或者是因爲我只在runQuery返回1個查詢(顯示名稱)而不是2個(顯示名稱和電子郵件)?

編輯2:所有的

首先,我已經改變了我所有的數據庫實現對ContactsContract。這變得更容易維護,因此您不必編寫自己的數據庫實現。

我現在試過的是保存我的限制runQuery()FilterQueryProvider,然後在getChildrenCursor上運行一個針對該約束的查詢。(由JRaymond的建議)

private String mConstraint; 
protected Cursor getChildrenCursor(Cursor groupCursor) { 
    // Given the group, we return a cursor for all the children within that 
    // group 
    int groupPos = groupCursor.getPosition(); 
    int groupId = groupCursor.getInt(groupCursor 
      .getColumnIndex(ContactsContract.Groups._ID)); 

    Log.d(DEBUG_TAG, "getChildrenCursor() for groupPos " + groupPos); 
    Log.d(DEBUG_TAG, "getChildrenCursor() for groupId " + groupId); 

    mGroupMap.put(groupId, groupPos); 

    Bundle b = new Bundle(); 
    b.putString("constraint", mConstraint); 

    Loader loader = mActivity.getSupportLoaderManager().getLoader(groupId); 
    if (loader != null && !loader.isReset()) { 
     if (mConstraint == null || mConstraint.isEmpty()) { 
      // Normal query 
      mActivity.getSupportLoaderManager().restartLoader(groupId, 
        null, mActivity.mSpeakersLoaderCallback); 
     } else { 
      // Constrained query 
      mActivity.getSupportLoaderManager().restartLoader(groupId, b, 
        mActivity.mSpeakersLoaderCallback); 

     } 
    } else { 
     if (mConstraint == null || mConstraint.isEmpty()) { 
      // Normal query 
      mActivity.getSupportLoaderManager().initLoader(groupId, null, 
        mActivity.mSpeakersLoaderCallback); 
     } else { 
      // Constrained query 
      mActivity.getSupportLoaderManager().initLoader(groupId, b, 
        mActivity.mSpeakersLoaderCallback); 
     } 
    } 

    return null; 
} 

這裏是FilterQueryProvider

private FilterQueryProvider filterQueryProvider = new FilterQueryProvider() { 
    public Cursor runQuery(CharSequence constraint) { 
     // Load the group cursor here and assign mConstraint 
     mConstraint = constraint.toString(); 
     Uri groupsUri = ContactsContract.Groups.CONTENT_SUMMARY_URI; 
     String selection = "((" + ContactsContract.Groups.TITLE 
       + " NOTNULL) AND (" + ContactsContract.Groups.TITLE 
       + " == 'Coworkers') OR (" + ContactsContract.Groups.TITLE 
       + " == 'My Contacts'))"; // Select only Coworkers 
              // (E-mail only) and My 
              // Contacts (Name only) 
     String sortOrder = ContactsContract.Groups.TITLE 
       + " COLLATE LOCALIZED ASC"; 
     return mActivity.getContentResolver().query(groupsUri, 
       MainActivity.GROUPS_SUMMARY_PROJECTION, selection, null, 
       sortOrder); 
    } 
}; 

正如你可以看到我已經加載組的查詢,以獲得getChildrenCursor工作。我只能從MainActivity中得到什麼樣的查詢?

該項目可以下載here,你可以在Eclipse中導入。

回答

4

我查看了你的問題,很遺憾我沒有時間複製你的設置。在通用術語,但是,你應該能夠保存您的約束,然後在「getChildrenCursor」,運行鍼對該約束的查詢:

Cursor getChildrenCursor(Cursor groupCursor) { 
    if (mConstraint == null || mConstraint.isEmpty()) { 
    // Normal query 
    } else { 
    // Constrained query 
    } 

} 

我不能肯定,但我敢肯定,getChildrenCursor()將在您返回filterQueryProvider()中的光標時響應父遊標的更改而被調用。然後,您只需管理約束的空/填充狀態。

詳情:

在你filterList功能,而不是做一個複雜的過程,只需要調用runQueryOnBackgroundThread(constraint);。這會自動將數據庫工作卸載到後臺。保存在您的filterQueryProvider你的約束:

String s = '%' + constraint.toString() + '%'; 
mConstraint = s; 

對於查詢,它只是取決於你想擺脫數據庫的東西 - 一個快速的調整,您發佈的代碼運行查詢,像這樣:

String selection = ContactsContract.CommonDataKinds.Email.DATA 
    + " NOT LIKE ''"; 
if (constraint != null) { 
    selection += " AND " + ContactsContract.CommonDataKinds.Email.DATA + " LIKE ?"; 
} 
cl = new CursorLoader(getApplicationContext(), 
    ContactsContract.CommonDataKinds.Email.CONTENT_URI, 
    PROJECTION, selection, constraint, sortOrder); 

我不太確定的一件事是自動展開的事情,我的過濾器工作正常,但您需要摺疊並再次打開列表以查看更改。

+0

謝謝!我應該在'MainActivity'上運行什麼查詢,我應該如何實現?查看我的編輯瞭解更多信息。順便說一下,對於我現在已經上傳了我的項目的複製問題,您可以在此處下載(http://we.tl/ZPgvWlCwVc)。 (如果你在Android上有聯繫人,我還沒有在模擬器上測試過,它應該可以工作。) – user2784435

+0

Wooah謝謝!你可以上傳你的項目,讓我可以測試它嗎?在我的項目中,它現在也適用於你的幫助,只有我使用捆綁包才能完成。 (我不知道一個更好)。你可以在這裏下載(http://we.tl/N8TasbIRoT)。對於崩潰的事情,你嘗試過'listAdapter.notifyDataSetChanged()'? – user2784435

+0

@ user2784435我確實嘗試了notifyDataSetChanged,但這似乎讓我陷入了重新加載的永久循環。我可以嘗試把它放在某個地方的某個地方;但我也通過捆綁傳遞 - 我只是在開始加載之前將它保存在類中 – JRaymond

2

你應該做的是擴展FilterQueryProvider。這提供了一個runQuery()函數,該函數返回過濾結果的新光標(可能通過數據庫查詢完成)。

在您的CursorTreeAdapter適配器實現中,您將使用setFilterQueryProvider()方法爲您的FilterQueryProvider提供一個實例。

最後,當您要執行過濾時,您將撥打mAdapter.getFilter().filter("c")

但是,由於您並未實際使用自動填充功能,而是填充自己的列表,所以您選擇的解決方案比需要的要複雜得多。爲什麼不放棄Content Provider和CursorTreeAdapter並使用更簡單的內存列表或映射方案來備份適配器?根據需要填充內存中的數據(您的整個數據集是否適合內存?)。

+0

感謝您指出'FilterQueryProvider'。我不使用內存數據的原因是因爲我的數據集對於內存來說很「大」,它適合但是加載時間太長。我還沒有實現'SearchView'自動完成功能,因爲我的首要任務是使搜索工作。我用'FilterQueryProvider'嘗試了一些東西,但是最後我遇到了'無法讀取行'錯誤,請參閱我的編輯。我應該在這種情況下使用['MergeCursor'](http://developer.android.com/reference/android/database/MergeCursor.html)嗎? – user2784435