ListView 와 연동되는 Adapter 는 다양하게 구성할 수 있지만 실무에서는 REST API 를 통한 JSON 연동이나 SQLite 와 연동하는 경우가 매우 흔합니다. 이번 포스팅에서는 Cursor 와 연동하는 경우를 간단히 처리할 수 있는 AdapterCursor 를 만들어봅니다.
SimpleCursorAdapter보다 더 심플하게!
우선 BaseAdapter를 근간으로 뼈대를 잡아보죠. 무엇이 우리를 귀찮게 하는지와 무엇을 구상해야 하는지 생각해보기엔 좋은 시작점입니다.
//커서를 가져온다. final Cursor c = getQuery() BaseAdapter adapter = new BaseAdapter(){ @Override public int getCount(){return c == null ? 0 : c.getCount();} @Override public Object getItem( int i ){return null;} @Override public long getItemId( int i ){return i;} @Override public View getView( int idx, View view, ViewGroup viewGroup ){ if( view == null ){ //1.레이아웃로딩---------- } c.moveToPosition(idx); //커서를 그 위치로 //2.data처리----------- return view; } };
특별한 기능을 안쓴다고 봤을 때 이 정도로 생각됩니다. 이 중에 특히 중요한 1번과 2번을 잘 처리하는 것이고 그 외에는 크게 문제는 없는 듯합니다.
기본 추상클래스 구성
getItem이랑 getItemId는 필요할 때만 구상하게 하고 getView처리를 정리하면 다음과 같은 추상클래스를 생각해볼 수 있습니다.
abstract class AdapterCursor extends BaseAdapter{ private Cursor cursor; public AdapterCursor( Cursor $cursor ){ cursor = $cursor; } @Override public View getView( int idx, View view, ViewGroup viewGroup ){ if( view == null ){ //1.레이아웃로딩---------- } cursor.moveToPosition(idx); //커서를 그 위치로 //2.data처리----------- return view; } @Override public int getCount(){return _c == null ? 0 : _c.getCount();} @Override public Object getItem( int $i ){return null;} @Override public long getItemId( int $i ){return $i;} @Override }
실무환경에 따라 다르겠지만 이 정도를 구성해두면 제 경우는 손델 일이 크게 없었습니다. 추상클래스니 필요하면 오버라이드해서 사정에 맞게 getCount, getItem 등을 구현하시면 될 듯합니다.
템플릿메서드 후크
이제 getView에서 주석으로 걸려있던 두 개의 후크를 구성해보죠. 레이아웃용 후크는 View를 반환하면 될테고, data처리는 사실 반환한 것도 없습니다. 구성의 용이성을 위해 커서를 포함해서 전부 인자로 던집니다.
public abstract View getRowView(); public abstract void setRow( Cursor cursor, int idx, View view, ViewGroup viewGroup ); @Override public View getView( int idx, View view, ViewGroup viewGroup ){ if( view == null ) view = getRowView(); cursor.moveToPosition(idx); setRow( cursor, idx, view, viewGroup ); return view; }
getRowView, setRow 두개의 후크로 템플릿메서드가 완성되었으므로 이제 커서용 어뎁터를 매우 간단히 구상할 수 있게 되었습니다.
Cursor cursor = getQuery("select title, userid, regdate from bbs"); AdapterCursor adapter = new AdapterCursor(cursor){ @Override public View getRowView(){ return getLayoutInflater().inflate( R.layout.row ); } @Override public void setRow( Cursor cursor, int idx, View view, ViewGroup viewGroup ){ ( (TextView)view.findViewById(R.id.title) ).setText( cursor.getString(0) ); ( (TextView)view.findViewById(R.id.userid) ).setText( cursor.getString(1) ); ( (TextView)view.findViewById(R.id.regdate) ).setText( cursor.getString(2) ); } }; listView.setAdapter( adapter );
업데이트의 문제
커서에 의존하는 어뎁터다보니 쿼리의 결과를 뷰에 반영해줘야합니다.
기본적인 어뎁터의 업데이트 메서드는 notifyDataSetChanged() 입니다만 이 경우 커서 자체가 새로운 객체이므로 소용없습니다.
따라서 다음과 같은 업데이트 메서드를 생각해보죠.
public void update( Cursor $cursor ){ cursor = $cursor; notifyDataSetChanged(); }
커서 갱신과 동시에 업데이트를 하는 방식입니다. 이렇게 되면 호스트 코드를 더욱 효율적으로 나눌 수 있게 됩니다.
//필드에 adapter를 잡아둔다 AdapterCursor adapter = null; void setList( String $query ){ Cursor c = getQuery($query); if( adapter == null ){ //어뎁터가 없으면 만든다 adapter = new AdapterCursor(cursor){ @Override public View getRowView(){ return getLayoutInflater().inflate( R.layout.row ); } @Override public void setRow( Cursor cursor, int idx, View view, ViewGroup viewGroup ){ ( (TextView)view.find.. ).setText( cursor.getString(0) ); ( (TextView)view.find.. ).setText( cursor.getString(1) ); ( (TextView)view.find.. ).setText( cursor.getString(2) ); } }; listView.setAdapter( adapter ); }else{ //이미 있으면 커서를 보내 갱신 adapter.update(c); } }
결론
Adapter작업이 지루하고 귀찮기 때문에 이러한 형태로 다양한 추상 클래스군을 만들어두고 사용하고 있습니다. 기회가 나는대로 다른 녀석들도 공개해보죠.
recent comment