Android Group List 만들기 (개선-아이폰 리스트처럼 만들자)

안녕하세요
오늘은 지난번에 만들었던 그룹 리스트를 개선해보겠습니다.

아이폰 앱을 보면 리스트에서 그룹 제목이 리스트 최 상단에 항상 붙어 있는 것을 볼수 있습니다.
리스트를 스크롤 해서 그룹이 바뀌게 되면 리스트 최 상단의 그룹 명도 같이 바뀌게 되구요
아이폰은 그 기능을 컴포넌트가 제공해 주고 있습니다만... 아쉽게도 안드로이드는 구현을 해 줘야 합니다.
아래 그림의 붉은색 박스에 있는 헤더가 스크롤시에도 계속 보이는거죠 그리고 내용은
스크롤되는 내용에 따라 바뀌게 되구요



그래서 지난번 그룹 리스트를 개선해서 위의 기능을 구현해보도록 하겠습니다.
아이폰과 완전히 같지는 않지만 그래도 비슷하게 폼을 낼 수는 있겠죠 ^^

위 기능을 구현하기 위해 참고로 한 소스는 구글 안드로이드 Apidemo 에 있는 리스트 9번
소스입니다.

먼저 레이아웃 xml 파일을 수정합니다.
수정된 레이아웃 파일을 보시면 아시겠지만 전체를 감싸는 레이아웃이
이전의 LinearLayout에서 RelativeLayout 으로 변경되었습니다.

RelativeLayout 을 쓴 이유는 이 레이아웃이 레이아웃 내부의
컴포넌트를 중첩시킬 수 있기 때문입니다.

아래 붉은 글씨 부분을 보시면 아시겠지만 리스트와 텍스트 뷰가 모두
같은 컴포넌트(llSearchBarLayout) 아래에 위치하고 있습니다.
이렇게 위치시키게 되면 먼저 위치시킨 LinearLayout 위로
TextView가 겹친 상태로 배치되게 됩니다.
*************************** main.xml *****************************
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
 xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:gravity="center_vertical"
    android:orientation="vertical"
    android:fillViewport="true" >

   <!-- 검색 바 -->
   <LinearLayout
    android:id="@+id/llSearchBarLayout"
     android:layout_width="fill_parent"
     android:layout_height="wrap_content"
     android:orientation="horizontal"
     android:paddingTop="5px"
     android:background="#E3E3E3"
     android:layout_alignParentTop="true">
     <EditText
       android:id="@+id/etSearchTxt"
       android:layout_width="wrap_content"
       android:layout_height="45dip"
       android:layout_gravity="center_vertical"
       android:layout_weight="1"
       android:textSize="18sp"
       android:singleLine="true"/>
     <ImageButton
       android:id="@+id/ibtnSearch"
       android:layout_height="45dip"
       android:layout_width="45dip"
       android:layout_gravity="right|center_vertical"
       android:scaleType="centerCrop"
       android:src="@drawable/btn_search"
     />
   </LinearLayout>
  
   <!-- 목록 -->
   <LinearLayout
     android:orientation="vertical"
     android:layout_width="fill_parent"
     android:layout_height="fill_parent"
     android:background="#FFFFFF"
     android:layout_weight="1"
     android:layout_below="@+id/llSearchBarLayout"
     android:visibility="visible">
     <ListView
       android:id="@android:id/list"
       android:layout_width="fill_parent"
       android:layout_height="fill_parent"
       android:cacheColorHint="#00000000"
       android:drawSelectorOnTop="false"
     />
     <TextView
       android:id="@android:id/empty"
       android:layout_width="fill_parent"
       android:layout_height="fill_parent"
       android:gravity="center_horizontal|top"
       android:textSize="20sp"
       android:text="No Data"
     />
   </LinearLayout>  

   <TextView
    android:id="@+id/tvHeaderTitle"
    android:layout_width="fill_parent"
     android:layout_height="30dp"
     android:textSize="18sp"
     android:textColor="#FFFFFF"
     android:background="#8696A5"
     android:paddingLeft="5dp"
     android:gravity="left"
     android:layout_below="@+id/llSearchBarLayout" />

</RelativeLayout>
******************************************************************

다음은 GroupList.java 파일을 수정해보겠습니다.
먼저 ListView.OnScrollListener 를 implements 받아야 합니다.
스크롤에 따라 리스트 최상단 그룹 제목이 변해야 하기때문입니다.
중요한 부분은 onscroll 이벤트를 처리하는 onScroll 메서드가 될것입니다.

GroupList.java의 전체 소스는 다음과 같습니다.
수정된 부분은 그리 많지 않습니다. onScroll 메서드가 추가되었고
나머지는 소소한 수정입니다.

onScroll 메서드에서는 스크롤이 발생할때 발생하는 이벤트인데
onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount)
위와 같은 인자들을 넘겨 받습니다. 로그로 찍어 보시면 아시겠지만
firstVisibleItem 는 현재 스크롤 상태에서 리스트의 최 상단 아이템의
위치를 int 값으로 넘겨 받습니다. 이 위치값은 리스트의 전체 데이터에서의
절대 위치값이기때문에 이 위치값을 이용해 커서를 움직이면
실제로 데이터를 가져올 수 있게 됩니다.

우리는 이 firstVisibleItem 인자를 가지고 커서를 움직여서
현재 리스트의 최 상단에 나와야 할 그룹명을 찾을 것입니다.
************************* GroupList.java ***************************
package com.android.grouplist;

import com.android.grouplist.GroupListInfo.TbNote;

import android.app.ListActivity;
import android.database.Cursor;
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;
import android.view.View;
import android.view.animation.AlphaAnimation;
import android.view.animation.Animation;
import android.view.animation.AnimationSet;
import android.view.animation.LayoutAnimationController;
import android.view.animation.TranslateAnimation;
import android.widget.AbsListView;
import android.widget.EditText;
import android.widget.ImageButton;
import android.widget.ListView;
import android.widget.TextView;

public class GroupList extends ListActivity implements View.OnClickListener, ListView.OnScrollListener
{
  private GroupListDAO mGroupListDAO;
  private ImageButton mIbtnSearch;
  private EditText mEtSearchTxt;
  private TextView mTvHeaderTitle;
  private Cursor mTbNoteCursor;
  private NoteCursorAdapter mAdapter;
  private ListView mListView;
  private boolean mReady;
 
  @Override
  public void onCreate(Bundle savedInstanceState)
  {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);
    mListView = getListView();
    mListView.setTextFilterEnabled(true);
    mListView.setOnScrollListener(this);
   
    mIbtnSearch = (ImageButton) findViewById(R.id.ibtnSearch);
    mEtSearchTxt = (EditText) findViewById(R.id.etSearchTxt);
    mTvHeaderTitle = (TextView) findViewById(R.id.tvHeaderTitle);
   
    mIbtnSearch.setOnClickListener(this);
   
    mGroupListDAO = new GroupListDAO(getApplicationContext());   
  }
 
  @Override
  protected void onResume()
  {
    super.onResume();
    mReady = true;
  }

  @Override
  protected void onPause()
  {
    super.onPause();
    mReady = false;
  }

  @Override
  protected void onDestroy()
  {
    super.onDestroy();
    mReady = false;
   
    try
    {
      if (mTbNoteCursor != null)
        mTbNoteCursor.close();
     
      mGroupListDAO.close();
    }
    catch (Exception e)
    {
      e.printStackTrace();
    }
  }

  public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount)
  {
    try
    {
      if (mReady)
      {
        mTbNoteCursor.moveToPosition(firstVisibleItem);
        String firstString = mTbNoteCursor.getString(mTbNoteCursor.getColumnIndex(TbNote.DATE));
        mTvHeaderTitle.setText(firstString);
      }
    }
    catch (Exception e)
    {}
  }

  public void onScrollStateChanged(AbsListView view, int scrollState)
  {}

 
  // 버튼의 OnClick 이벤트 처리
  @Override
  public void onClick(View v)
  {
    switch(v.getId())
    {
      case R.id.ibtnSearch: // Search Button
        findNoteList();
        break;
      default:
        break;
    }
  }
 
  private void findNoteList()
  {
    String searchTxt = mEtSearchTxt.getText().toString();
    mTbNoteCursor = mGroupListDAO.selectNoteList(searchTxt);
   
    if (mTbNoteCursor != null)
      Log.v("GroupList", "mTbNoteCursor.count >>>>>>>>>>>>>>>" + mTbNoteCursor.getCount());
   
    mAdapter = new NoteCursorAdapter(this, R.layout.note_list_item_w_header, mTbNoteCursor, new String[] {TbNote.NOTE}, null);
    setListAdapter(mAdapter);
   
    AnimationSet set = new AnimationSet(true);
    Animation animation = new AlphaAnimation(0.0f, 1.0f);
    animation.setDuration(100);
    set.addAnimation(animation);
   
    animation = new TranslateAnimation(Animation.RELATIVE_TO_SELF, 0.0f, Animation.RELATIVE_TO_SELF, 0.0f, Animation.RELATIVE_TO_SELF, -1.0f, Animation.RELATIVE_TO_SELF, 0.0f);
    animation.setDuration(200);
    set.addAnimation(animation);
   
    LayoutAnimationController controller = new LayoutAnimationController(set, 0.5f);
    mListView.setLayoutAnimation(controller);
  }
}
*******************************************************************

소스 코드는 위와 같고 이를 실행시키면 다음처럼 실행됩니다.
이제 스크롤을 해보면 최상단 그룹명(붉은색 박스)은 고정된 상태로
스크롤 되면서 고정된 헤더의 내용은 현재 데이터의 그룹명으로 계속
변경될 것입니다.


이상으로 GroupList를 개선해 보았습니다.

by 선지헌 | 2011/03/26 02:36 | Android | 트랙백 | 덧글(4)

트랙백 주소 : http://jeehun.egloos.com/tb/4005935
☞ 내 이글루에 이 글과 관련된 글 쓰기 (트랙백 보내기) [도움말]
Commented by 홍진 at 2011/04/22 15:24
지헌님 정말 많이배우고있습니다
정리 너무 잘해주셔서 이해하기도 쉽고
항상잘보고있어요 ㅋ 화이팅!!
Commented by 선지헌 at 2011/04/22 15:53
ㅠㅠ 감사합니다. 앞으로도 쓸만한 정보 계속 올려보도록 노력하겠습니다.
Commented by 초보 개발자 at 2011/05/12 11:25
저번에도 필요한 정보 많이 얻어갔는데

또 필요한 정보가....ㅠㅠㅠㅠㅠㅠㅠ

많이 배워 갑니다ㅎㅎ
Commented by 그룹 리스트뷰 at 2013/04/03 13:18
test000@hanmail.net 로 샘플소스 부탁드립니다..ㅠㅠ;

:         :

:

비공개 덧글

◀ 이전 페이지다음 페이지 ▶