본문으로 바로가기

Cursor Adapter#2 - Custom Cursor Adapter

category 개발이야기/Android 2017. 10. 19. 13:00
반응형

SimpleCursorAdapter나 SimpleTreeCusorAdapter같은 경우 정해진 layout내의 view요소에 값을 할당 하는 방식으로 동작한다.


만약, DB에서 문자를 찾아 만드는 자동완성뷰나, 스피너를 만들기 위해서는 CursorAdapter를 상속받아 직접 구현해야 한다. 이는 Adpater를 만들기 위해 BaseAdatper를 상속받아 구현하는것과 비슷하다.


Cursor Adatper의 상속

CursorAdatper를 상속할때는 아래와 같은 세가지 작업을 수행한다.

1. Constructor 생성

2. bindView() 구현

3. newView() 구현


예제) Text의 자동완성 기능을 Cursor Adapter를 이용하여 구현한다.

import android.provider.ContactsContract.Contacts;

public class AutoComplete extends Activity {
    private static final int COLUMN_DISPLAY_NAME = 1;

	@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        //Contact 정보를 읽는다.
        Cursor c = getRawContacts();
        CustomAdatper adapter = new CustomAdatper(this, c, 
                               CursorAdapter.FLAG_REGISTER_CONTENT_OBSERVER);
        
        AutoCompleteTextView textView = (AutoCompleteTextView)
                                                findViewById(R.id.edit);
        textView.setAdapter(adapter); //AutoTextView에 adapter 등록
    }

    private Cursor getRawContacts() {
        //반드시 _id를 읽어야 한다.
        String[] projection = new String[] {Contacts._ID, 
                                            Contacts.DISPLAY_NAME, };

        ContentResolver content = getContentResolver();
        Cursor cursor = content.query(Contacts.CONTENT_URI,
                projection, null, null, null);

        return cursor;
    }

    //Adapter의 구현
    public static class CustomAdatper extends CursorAdapter  {
        private final Context mContext;

       	public CustomAdatper(Context context, Cursor c, int flags) {
    	    super(context, c, flags);
            mContext = context;
        }
       	
        @Override
        //View의 생성
        public View newView(Context context, Cursor cursor, ViewGroup parent) {
            final LayoutInflater inflater = LayoutInflater.from(context);
            final TextView view = (TextView) inflater.inflate(
                 android.R.layout.simple_dropdown_item_1line, parent, false);
            // view.setText(cursor.getString(COLUMN_DISPLAY_NAME));
            return view;
        }

        @Override
        //view의 가공
        public void bindView(View view, Context context, Cursor cursor) {
           ((TextView) view).setText(cursor.getString(COLUMN_DISPLAY_NAME));
        }

        @Override
        //화면이 갱신될때마다 (문자가 변경될때마다) 캐쉬된 cursor에서 정보를 얻는다.
        public Cursor runQueryOnBackgroundThread(CharSequence constraint) {
            String[] projection = new String[] {
                                   Contacts._ID, //반드시 _id 값은 필요
                                   Contacts.DISPLAY_NAME, };
            FilterQueryProvider filter = getFilterQueryProvider();
            if (filter != null) {
                //기존에 filtering된 정보가 있다면 해당 정보를 읽는다.
                return filter.runQuery(constraint);
            }

            /**
             * Contacts.CONTENT_FILTER_URI = 
             *           "content://com.android.contacts/contacts/filter"
             */
            Uri uri = Uri.withAppendedPath(Contacts.CONTENT_FILTER_URI,
                                    Uri.encode(constraint.toString()));
            return mContext.getContentResolver().query(uri,
                                   projection,  null, null, null);
        }

        @Override
        // Filtering된 결과를 선택시 string으로 변경한다.
        // (그렇지 않으면 uri 주소가 표기된다.)
        public String convertToString(Cursor cursor) {
            return cursor.getString(COLUMN_DISPLAY_NAME);
        }
    } 
}

※ CursorAdapter.FLAG_REGISTER_CONTENT_OBSERVER: observer와 연동한다.


이미 코드상에서 필요한부분에 주석을 달았지만, 다시한번 정리하면 아래와 같다.


1. view의 생성 부분과 가공부분을 분리한다 (newView() / bindView())

2. runQueryOnBackgroundThread()를 구현해야만 글자가 바뀔때마다 결과가 갱신된다.

3. getFilterQueryProvider()를 이용하여 cursor의 결과(caching)에서 정보를 찾는다.

4. convertToString()을 통하여 검색 결과를 다시한번 가공한다. (검색결과 클릭시 uri주소로 표현되므로)


runQueryOnBackgroundThread()를 filterQueryProvider를 쓰지 않고, 매번 DB를 검색하는 부분도 생각해봤으나, 성능면에서 고민이 필요한 부분이다.


이 방법보다는 개인적으로 Loader를 사용하는 방법이 더 깔끔해 보인다.


- main.xml 첨부

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">
    <TextView  android:text="@string/autocomplete_instructions" 
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>
    <LinearLayout android:orientation="horizontal"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
        <TextView android:text="@string/autocomplete_name"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"/>
        <AutoCompleteTextView android:id="@+id/edit"
            android:completionThreshold="1"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"/>
    </LinearLayout>
    <TextView android:text="@string/autocomplete_message"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>
</LinearLayout>



반응형