«   2025/01   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
Tags
more
Archives
Today
Total
관리 메뉴

올해는 머신러닝이다.

[펌]Android Custom CursorAdapter 사용법 본문

Android/Tip&Tech

[펌]Android Custom CursorAdapter 사용법

행복한 수지아빠 2011. 5. 25. 13:04

Android Custom CursorAdapter 사용법
 
 

데이터베이스에 있는 내용을 쿼리해서 리스트에 바인딩 할 때 SimpleCursorAdapter 를 사용하시는 건 다들 아시죠?
하지만 요 어댑터의 맹점은 모든 필드를 하나의 아이템 위젯에 밖에 연결할 수 없다는 것입니다.

 cs = new SimpleCursorAdapter(this, android.R.layout.simple_list_item_2, cursor,
                                        new String[] { "capital", "country" }, 
                                        new int[] { android.R.id.text1, android.R.id.text2 }); 

이 코드는 쿼리해 받아온 cursor 에서 capital, country 필드 두개를 리스트 아이템2 기본 템플릿에 바인딩하는 코드입니다.
인터넷 어디를 뒤져봐도 Cursor 를 어댑팅하는데 요런 예제밖에 없더라구요.

제가 하려는 것은 여러개의 필드를 제 맘껏 요리 붙이고 저리 붙이고 하여 띄우는 것이었습니다.
예를 들면, capital 필드의 내용과 country 필드의 내용을 합쳐(둘다 String이므로) 일반 리스트에 표시하는 것이죠.
실제 개발 중 너무나도 흔히 있는 일일텐데 관련된 예제가 하나도 없어 삽질을 좀 했습니다.

사용법은 의외로 간단합니다.
우선 CursorAdapter 를 상속받은 커스텀 커서 어댑터 클래스를 생성합니다.
아래의 코드에선 CombiningCursorAdapter 라고 이름 붙였습니다.

public class CombiningCursorAdapter extends CursorAdapter {
    public CombiningCursorAdapter(Context cntx, Cursor cursor)
    {
        super(cntx, cursor);
    }

    @Override
    public void bindView(View view, Context context, Cursor cursor) {
        ((TextView) view).setText(cursor.getString(0) + " / " + cursor.getString(1));
    }

    @Override
    public View newView(Context context, Cursor cursor, ViewGroup parent) {
        LayoutInflater inflater = LayoutInflater.from(context);
        View view = inflater.inflate(android.R.layout.simple_list_item_1, parent, false);
        return view.findViewById(android.R.id.text1);
    }
}

이클립스에서 클래스 추가하시면 자동으로 생성자, bindView, newView 를 오버라이드 해줍니다.
생성자는 뭐 생성자구요; 나머지 두 개 메소드 부분이 조금 특이합니다.
ArrayAdapter 의 경우는 getView 를 오버라이드 해서 사용하고, 매개변수인 convertView 의 내용의 null 값을 체크해서 할당합니다만,

@Override
    public View getView(int position, View convertView, ViewGroup parent) {
        if( convertView == null ) {
            // view 생성
        }
        // bind
        return super.getView(position, convertView, parent);
    }

CursorAdapter 의 경우는 뷰의 생성과 바인드가 분리되어 있다고 보시면 되겠습니다.
생성은 newView 메소드, 바인드는 bindView 메소드지요.

아래는 뷰를 생성하는 newView 코드입니다.

@Override
    public View newView(Context context, Cursor cursor, ViewGroup parent) {
        LayoutInflater inflater = LayoutInflater.from(context);
        View view = inflater.inflate(android.R.layout.simple_list_item_1, parent, false);
        return view.findViewById(android.R.id.text1);
    }

이 부분이 결정적인 삽질을 하게 된 부분입니다ㅠ
일단 어떤 뷰든지 리턴하게 되면 그 리턴한 뷰를 매 아이템마다 띄워주게 되어 있습니다.
이런 복잡한 코드 대신 return new TextView(context); 이렇게 사용해도 동작은 하지요.
하지만 안드로이드에서 기본 제공하는 android.R.id.xxx 류의 기본 아이템 위젯을 사용하고 싶어 이리저리 찾아봤는데 관련 예제가 도무지 없더군요;
온갖 시도 끝에 기본 제공하는 레이아웃 android.R.layout.simple_list_item_1 을 inflater 를 이용해 inflate 해서 findViewById 를 해보니 됩니다.
어째 뷰 리소스 아이디를 이용해 뷰를 생성하는 메소드는 없는걸까요 -_-
아무튼 이 부분이 어댑팅하여 띄워줄 리스트의 아이템 뷰를 생성하는 부분입니다.

생성을 했으니 이제 바인드를 해야겠지요.

@Override
    public void bindView(View view, Context context, Cursor cursor) {
        ((TextView) view).setText(cursor.getString(0) + " / " + cursor.getString(1));
    }

간단합니다. 여기서 view 에는 아까 newView 에서 리턴한 녀석이 날아옵니다.
TextView 로 캐스트해서 setText 날려주시면 되지요.
알아서 해당 위치로 포지션이 셋팅된 cursor 가 함께 날아오므로, 포지션 걱정 없이 cursor 를 마구 갖다 써주시면 되겠습니다.


생각보다는 꽤 간단한 방법이죠?
굉장히 여러가지 방법으로 커스터마이징이 가능할 것 같습니다.
위의 예제처럼 여러 필드를 합쳐 하나로 띄운다거나,
데이터베이스에 이미지URL 이 있는 경우 ImageView 를 이용해 바로 리스트에 붙인다거나 하는 작업이 가능할 듯 합니다.

ArrayAdapter 에 관련해서는 예제가 굉장히 많은데, 실 개발시 오히려 사용빈도가 높은 CursorAdapter 의 경우엔 커스텀 어댑터 예제가 보이지 않아 한번 두드려 보았습니다.
도움되셨길 바랍니다. ㅋ
 
 
2010.07.30 11:46:10
id: 그냥가자그냥가자
SimpleCursorAdapter는 일단 리스트 항목에 대한 레이아웃 리소스(android.R.layout.simple_list_item_2 대신 딴거)를 
주면 얼마든지 커스터마이징 할 수 있습니다.
ViewBinder (SimpleCursorAdapter의 inner Interface)를 구현해서 set 을 해주면 됩니다.
그리고 SimpleCursorAdapter 안의 bindView 함수를 보면 view와 데이터의 매칭을 어떻게 하면 되는가에 대한 대략적인 답이 나옵니다.
문제는 저 Adapter가 자체적으로 Memory Leak을 내포하고 있다는 거....(플젝하다가 저 그거땜시 죽는줄 알았습니다.)
그것도 회피가능합니다.
2010.07.31 00:16:56
민상k
댓글 감사합니다.
이 방법을 사용한 후에 다른 이유로 SimpleCursorAdapter 소스를 살펴보니 말씀하신 것처럼 inner interface 가 내부에 구현되어 있더군요.
하지만 ViewBinder 를 구현해서 set 하는 것보다는 일반적인 ArrayAdapter 사용법처럼 새로운 어댑터를 생성하는 것이 조금 더 코드상의 이점이 많은 것 같습니다.
하지만 때에 따라서 ViewBinder 를 사용하면 더 좋을 부분이 분명 있을 것도 같네요.

자체적인 메모리 릭이라 하심은 SimpleCursorAdapter 말씀이신가요, 아니면 CursorAdapter 자체 말씀이신가요?
혹 시간이 되신다면 이에 관한 팁 좀 부탁드릴게요.
제 코드에도 SimpleCursorAdapter, CursorAdapter 모두 엄청나게 사용되거든요. ㅋ

2010.07.31 10:06:51
id: 그냥가자그냥가자
(추천: 1 / 0)

SimpleCursorAdapter에 보면 Map<View, Map>을 이용해서 컨트롤들을 데이터와 바인드 하게 되는데요.
Cursor (Select 문을 날렸겠죠)의 requery 기능을 활용해서 다시 Select 하게 되면
기존 뷰가 날아가기 때문에 View 참조가 Map에서 지워져야 되는데 그렇게 되지 않더군요.

전 SimpleCursorAdapter(저도 배껴서 바인드를 새로 구현하긴 했습니다. 첨에는 몰랐거든요. 로직상 큰차이는 없는걸로 알구요.)
에 이미지를 띄우는데 이미지가 리소스가 아닌 외부 파일이었습니다.
메모리 퍽퍽 쌓이는게 눈에 보일정도였습니다. (새로고침 자주했거든요.)

메모리 릭 방지 법은 의외로 간단합니다.
일단 SimpleCursorAdapter이거 소스 긁어다가 새로 하나 Custom으로 만드시구요. (기존꺼는 답이 없습니다. Private....)
함수를 하나 만들어 두세요... 

1.public void removeFromMap (View v)
2.{
3.    //Map 변수 이름 생각안나서 임의로 했습니다. 찾아서 하셔야 합니다.
4.   if(v != null)  map.remove(v);
5.}


그리고 listView 초기화 할때 리스너 하나 추가해야 되는데
지금 그 리스너 이름을 제가 까먹어서 ㅠ.ㅠ ViewGroup에 있는 넘이구요.
자기 하위의 View가 Remove 될때 호출되는 이벤트 리스너가 있습니다.
거기서 위에 만든 메소드를 호출하면 릭이 해소 됩니다.

2010.07.31 11:18:42
민상k

말씀하신 이벤트 리스너를 찾아보니 ViewGroup.OnHierarchyChangeListener 네요.
시간 나는대로 테스트해보고 이곳에 팁으로 다시 올려볼게요.
좋은 답변 정말정말 감사합니다. 많이 배웠네요. ㅋ

'Android > Tip&Tech' 카테고리의 다른 글

android chart 관련  (0) 2011.05.25
DB변경시 어댑터 자동변경 코드  (0) 2011.05.25
Photoshop like color picker  (3) 2011.05.24
[펌]android trigger 참고  (0) 2011.05.24
listview 최적화하기 2/2  (0) 2011.05.19