2012년 7월 5일 목요일

[Android] 동적으로 다음페이지를 로딩하는 ListView 구현

아이폰의 수많은 UITableView를 활용하는 어플리케이션을 보면 참 퀄리티 높게 잘 만든것이 자동으로 리스트의 가장 아래로 내려가면 알아서 다음페이지를 로딩하는 기능이 아닐까 싶습니다. 안드로이드에서도 요즘은 많은 어플리케이션이 해당 기능을 구현하고 있습니다. 안드로이드에서는 리스트뷰와 데이터간에 Adapter라는 디자인패턴을 활용하고 있어 아이폰의 그것과는 같은 기능이라도 구현하는 방식이 다릅니다.

안드로이드에서는 좀 더 적극적으로 Adapter를 활용하여 이 기능을 구현해야 합니다. 어찌보면 조잡하고 어찌보면 더 쉽게 구현할 수 있습니다. 길게 이야기할것 없이 예제 소스를 보여드리겠습니다.
public class DynamicListViewActivity extends Activity implements OnScrollListener
{
  private static final String LOG = "DynamicListViewActivity";
  private CustomAdapter mAdapter;
  private ListView mListView;
  private LayoutInflater mInflater;
  private ArrayList<String> mRowList;
  private boolean mLockListView;
  
    @Override
    public void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        
        // 멤버 변수 초기화
        mRowList = new ArrayList<String>();
        mLockListView = true;
        
        // 어댑터와 리스트뷰 초기화
        mAdapter = new CustomAdapter(this, R.layout.row, mRowList);
        mListView = (ListView) findViewById(R.id.listView);
        
        // 푸터를 등록합니다. setAdapter 이전에 해야 합니다. 
        mInflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        mListView.addFooterView(mInflater.inflate(R.layout.footer, null));
        
        // 스크롤 리스너를 등록합니다. onScroll에 추가구현을 해줍니다.
        mListView.setOnScrollListener(this);
        mListView.setAdapter(mAdapter);
        
        // 데미데이터를 추가하기 위해 임의로 만든 메서드 호출
        addItems(50);
  }

  @Override
  public void onScroll(AbsListView view, int firstVisibleItem,
    int visibleItemCount, int totalItemCount)
  {
    // 현재 가장 처음에 보이는 셀번호와 보여지는 셀번호를 더한값이
    // 전체의 숫자와 동일해지면 가장 아래로 스크롤 되었다고 가정합니다.
    int count = totalItemCount - visibleItemCount;

    if(firstVisibleItem >= count && totalItemCount != 0
      && mLockListView == false)
    {
      Log.i(LOG, "Loading next items");
      addItems(50);
    }  
  }

  @Override
  public void onScrollStateChanged(AbsListView view, int scrollState)
  {
  }
  
  /**
   * 임의의 방법으로 더미 아이템을 추가합니다.
   * 
   * @param size
   */
  private void addItems(final int size)
  {
    // 아이템을 추가하는 동안 중복 요청을 방지하기 위해 락을 걸어둡니다.
    mLockListView = true;
    
    Runnable run = new Runnable()
    {
      @Override
      public void run()
      {
        for(int i = 0 ; i < size ; i++)
        {
          mRowList.add("Item " + i);
        }
        
        // 모든 데이터를 로드하여 적용하였다면 어댑터에 알리고
        // 리스트뷰의 락을 해제합니다.
        mAdapter.notifyDataSetChanged();
        mLockListView = false;
      }
    };
    
    // 속도의 딜레이를 구현하기 위한 꼼수
    Handler handler = new Handler();
    handler.postDelayed(run, 5000);
  }
}


여기서 주목할 부분은 onScroll 메서드 입니다. 스크롤이 일어날때마다 해당 메서드가 호출이 되며 위의 소스에서는 가장 마지막셀이 디스플레이 되었는지를 검사하게 됩니다. 마지막 셀이 나왔다면 현재 리스트가 Lock상태인지를 체크 합니다. 여기서 쓰이는 멤버 변수가 mLockListView 입니다.

해당 변수를 사용하여 리스트에 데이터가 변화하는 순간에는 스크롤 이벤트를 막아 이벤트의 중복 요청을 막게 됩니다. 위에서 Inflater를 활용하여 FooterView를 붙이는 과정이 있는데요 이것이 사용자로 하여금 페이지 로딩중임을 알리게 되는 중요한 요소입니다. 소스를 올려둘테니 받으셔서 테스트해보시기 바랍니다.

사용자 삽입 이미지


출처 : http://theeye.pe.kr/entry/Android-%EB%8F%99%EC%A0%81%EC%9C%BC%EB%A1%9C-%EB%8B%A4%EC%9D%8C%ED%8E%98%EC%9D%B4%EC%A7%80%EB%A5%BC-%EB%A1%9C%EB%94%A9%ED%95%98%EB%8A%94-ListView-%EA%B5%AC%ED%98%84

2012년 7월 4일 수요일

Fragment 프래그먼트 개념


프래그먼트(Fragment)의 등장 배경

안드로이드 3.0(허니컴)이 공개되면서 태블릿에 적합한 여러 UI들이 공개되었는데, 그 중에서 대표적인 것이 바로 프래그먼트(Fragment) 입니다.

그럼, 과연 왜 프래그먼트라는 것이 추가되었을까요? 간단히 말하자면, 프래그먼트는 태블릿과 같은 큰 화면을 가지는 단말에서 애플리케이션이 화면을 더 효율적으로 활용할 수 있도록 도와줍니다. 기존에는 애플리케이션 화면을 구성하는 큰 틀이 액티비티(Activity) 하나였고, 이 안을 여러 뷰로 구성하여 정보를 표시하고, 상호작용을 수행했습니다.

그런데, 뷰만을 사용해서 다양한 내용을 보여주기는 매우 어려웠습니다. 특히나 전체적인 UI 틀은 고정되어 있으면서 특정 부분만 변화하며 다른 내용을 표시하도록 하려면 매우 복잡한 구성이 필요했죠. 또한, 서로 다른 역할을 하는 코드들이 같은 곳에 있게 되어서 가독성도 떨어지고 유지보수에도 악영향을 미쳤습니다. 

이 때문에 대부분 애플리케이션에서는 뷰 처리의 어려움도 피하고, 코드도 분리하게 위해 액티비티 전환을 사용했습니다. 아래의 GMail 앱을 통해 전형적인 액티비티 구성 방식을 볼 수 있습니다.


G메일 앱의 액티비티 구성


이 방식은 상당히 깔끔했습니다. 애플리케이션의 흐름 구성도 자연스럽게 잡히게 되었구요. 하지만, 화면이 큰 태블릿 단말들이 소개되면서 이 방식의 구성이 화면을 쓸데없이 많이 차지한다는 것을 느끼게 되었습니다. 호환성 문제는 없었지만, 큰 화면을 효율적으로 활용하지 못하는 것이죠.

트위터 공식앱을 실행한 모습. 태블릿에 최적화되지 않아 공간을 낭비하고 있습니다.


즉, 이제는 화면을 어떻게 하면 효율적으로 사용할 수 있는가? 를 고민할 차례가 되었습니다. 그럼, 화면을 효율적으로 활용한다는 것은 과연 어떤 것을 의미하는 것일까요? 여러 가지 방법이 있겠지만, 가장 대표적인 것은 한 화면에 가급적 다양한 정보를 표시하는 것입니다. 다음 안드로이드 3.0 버전의 GMail 앱을 보도록 하죠.


안드로이드 3.0의 G메일. 넓은 화면을 효율적으로 사용하고 있습니다.

기존 버전에서는 메일 리스트와 내용이 각각 전체 화면으로 표시되었던 것에 반해 안드로이드 3.0용에서는 메일 리스트와 내용이 동시에 표시되는 것을 확인할 수 있습니다. 넓은 화면을 효과적으로 활용하는 것이죠. 


넓은 화면을 효율적으로 사용하기

그렇다면, 넓은 화면을 효율적으로 사용하려면 단순히 한 화면에 여러 요소들을 구겨넣으면 되는 것일까요? 그렇지 않습니다. 한 화면에 여러 요소가 표시됨과 동시에 각 요소들을 조작하는 코드들은 각각 분리되어 있어야 합니다. 어떻게 보면 기존에 액티비티로 구현되던 요소들이 한 화면에 표시되는 것이죠. 

프래그먼트(Fragment)는 이러한 요구사항들을 잘 충족합니다. 액티비티처럼 관련된 코드들을 한곳에 묶을 수도 있고, 일반 뷰처럼 애플리케이션 레이아웃에 프래그먼트를 자유롭게 배치할 수도 있습니다. 다음 화면을 통해 프래그먼트의 배치를 확인할 수 있습니다.

G메일 앱의 프래그먼트 배치 모습


이것으로 프래그먼트의 등장 배경과 간략한 특징에 대해 알아보았습니다. 다음 글에서는 프래그먼트의 더 자세한 특징 및 사용 방법에 대해 알아보도록 하겠습니다.

출처 : http://androidhuman.tistory.com/entry/%ED%94%84%EB%9E%98%EA%B7%B8%EB%A8%BC%ED%8A%B8Fragment-%EC%A0%95%EB%B3%B5-1-%ED%94%84%EB%9E%98%EA%B7%B8%EB%A8%BC%ED%8A%B8-%EB%84%88%EB%8A%94-%EB%88%84%EA%B5%AC%EB%83%90


프래그먼트를 가장 간단하게 표현하자면 '뷰(View)처럼 사용할 수 있는 액티비티(Activity)'라 할 수 있습니다. 즉, 액티비티와 뷰의 특징을 모두 가지고 있습니다. 프래그먼트는 뷰에게 레이아웃 내에 자유롭게 배치될 수 있는 특징을 물려받았는데, 그렇다면 액티비티에서는 어떤 특징을 물려받았을까요? 바로 '생애주기'를 갖는 특징입니다.

프래그먼트의 생애주기

 프래그먼트는 액티비티와 같이 프래그먼트의 상태가 계속해서 변하며, 상태가 변할 때마다 그에 해당하는 생애주기 메서드(콜백 메서드)가 호출됩니다. 프래그먼트의 생애주기 메서드 및 각 메서드의 호출 순서는 다음과 같습니다.

프래그먼트의 생애주기 메서드

각 생애주기 메서드에 대해 더 자세히 알아보도록 하겠습니다. 액티비티의 생애주기 메서드와 매우 유사한 형태를 띄고 있으며, 뷰 생성과 관련된 몇몇 메서드가 더 추가되어 있습니다.

onAttach(Activity)
프래그먼트가 액티비티 레이아웃에 포함되는 순간 호출됩니다. 액티비티 레이아웃에 프래그먼트를 정적으로 배치했다면 액티비티가 시작될 때 같이 호출되며, 동적으로 레이아웃에 추가할 땐 프래그먼트를 레이아웃에 추가하는 순간 호출됩니다.


onCreate(Bundle)
액티비티의 onCreate() 콜백 메서드와 유사하게 프래그먼트가 최초로 생성될 때 호출됩니다.


onCreateView(LayoutInflater, ViewGroup, Bundle)
프래그먼트의 UI를 구성하는 뷰(View)를 반환합니다. UI를 가지지 않는 프래그먼트일 경우 null을 반환할 수도 있습니다.


onStart()
프래그먼트가 화면에 표시될 때 호출됩니다. 하지만, 아직 사용자와 상호작용은 할 수 없는 상태입니다.


onResume()
프래그먼트가 사용자와 상호작용을 할 수 있게 되었을 때 호출됩니다. 즉, 프래그먼트가 완전히 화면에 표시되어 제 역할을 수행할 수 있게 된 상태입니다.


onPause()
액티비티의 onPause()와 유사하게 프래그먼트가 사용자와 상호작용을 할 수 없게 될 때 호출됩니다. 프래그먼트가 아직 화면에 표시되고 있는 상태이나, 다른 요소에 의해 프래그먼트가 가려져 상호작용을 하지 못하는 상태입니다.


onStop()
프래그먼트가 화면에서 보이지 않게 될 때 호출됩니다. 액티비티가 화면에서 보이지 않게 될 때 onStop() 메서드가 호출되는 것과 유사합니다.


onDestroyView()
프래그먼트가 화면에서 사라진 후, 뷰의 현재 상태가 저장된 후 호출됩니다. 여기에서 저장된 뷰의 상태는 액티비티와 유사하게 Bundle 형태로 저장되며, 저장된 뷰의 상태는 onCreate() 및 onCreateView()에서 다시 불러들일 수 있습니다.


onDestroy()
프래그먼트가 더 이상 사용되지 않을 때 호출됩니다. 


onDetach()
프래그먼트가 액티비티 레이아웃에서 제거될 때 호출됩니다. 


위와 같이 프래그먼트의 생애주기는 액티비티와 매우 유사합니다. 때문에 기존에 액티비티로 작성되어 있던 코드를 쉽게 프래그먼트로 옮겨올 수 있습니다. 

프래그먼트에는 위의 생애주기 메서드 외에도 프래그먼트를 포함하고 있는 액티비티의 생성이 완료되었을 때 호출되는 콜백 메서드도 포함하고 있으며, 그 메서드는 다음과 같습니다.

onActivityCreated(Bundle)
프래그먼트를 포함하고 있는 액티비티의 생성이 완료되었을 때, 즉 액티비티의 onCreate() 메서드가 끝났을 때 호출됩니다.


프래그먼트를 레이아웃에 추가하기 (XML 사용)

프래그먼트의 특징에 대해 간단히 알아보았으니, 우선 프래그먼트를 레이아웃에 추가하는 방법에 대해 알아보도록 하겠습니다. 프래그먼트를 레이아웃에 추가하는 방법은 여러 가지가 있지만, 그 중에서도 가장 간단한 방법인 XML 레이아웃을 사용하여 추가하는 방법을 알아보겠습니다.

[어플리케이션 정보]

액티비티
  • Main (Main.java)

레이아웃
  • main.xml (Main)
  • fragment_one.xml (FragmentOne 프래그먼트)

API Level
  • Android 3.0 (API Level 11)


우선, 프래그먼트에 표시할 레이아웃 파일을 작성합니다. 다음과 같이 fragment_one.xml 레이아웃 파일을 추가한 후,TextView 하나를 추가합니다.

[fragment_one.xml]
01.<?xml version="1.0" encoding="utf-8"?>
02.<LinearLayout
04.android:layout_width="match_parent"
05.android:layout_height="match_parent">
06. 
07.<TextView android:layout_width="wrap_content"
08.android:layout_height="wrap_content"
09.android:text="Fragment One" />
10. 
11.</LinearLayout>

액티비티 코드에 프래그먼트 클래스를 추가한 후, 프래그먼트에 표시할 뷰를 지정해주기 위해 onCreateView() 다음과 같이 메서드를 오버라이드합니다. 프래그먼트에서 표시할 뷰를 반환하기 위해 onCreateVIew()의 인자 중 하나인 LayoutInflater를 사용합니다.


[Main.java]
01.public class Main extends Activity {
02. 
03.@Override
04.public void onCreate(Bundle savedInstanceState) {
05.super.onCreate(savedInstanceState);
06.setContentView(R.layout.main);
07.}
08. 
09.public static class FragmentOne extends Fragment{
10. 
11.@Override
12.public View onCreateView(LayoutInflater inflater, ViewGroup container,
13.Bundle savedInstanceState) {
14.return inflater.inflate(R.layout.fragment_one, null);
15.}
16. 
17.}
18. 
19.}

다음, 액티비티의 레이아웃을 작성합니다. 프래그먼트를 XML 레이아웃 파일에서 선언할 때는 프래그먼트의 클래스(class)와 프래그먼트를 구분할 수 있는 id 혹은 tag를 필히 지정해야 합니다. 여기에서는 프래그먼트를 구분하기 위해 id를 지정해 주었습니다.

이 예제에서 프래그먼트 클래스가 Main 클래스의 내부 클래스로 선언되어 있기 때문에 Main$FragmentOne과 같이 클래스 이름을 지정해 주었습니다.


[main.xml]
01.<?xml version="1.0" encoding="utf-8"?>
02.<LinearLayout
04.android:orientation="vertical"
05.android:layout_width="fill_parent"
06.android:layout_height="fill_parent"
07.>
08.<fragment
09.android:id="@+id/fragment_one"
10.class="com.androidhuman.example.SimpleFragment.Main$FragmentOne"
11.android:layout_width="match_parent"
12.android:layout_height="match_parent" />
13.</LinearLayout>


이것으로 모든 구현이 끝났습니다. 생각보다 간단하지요? 예제를 실행하면 다음과 같이 프래그먼트가 표시되는 것을 확인할 수 있습니다.


프래그먼트라는 것을 처음 접할 때는 아직 익숙하지 않아 어려워 보였을지도 모릅니다. 간단한 예제를 통해 알아본 것과 같이 접해보면 그리 어렵지 않습니다.

다음 포스트에서는 프래그먼트를 사용하는 가장 큰 장점 중 하나라 할 수 있는 프래그먼트 전환에 대해 다루어보도록 하겠습니다. :)

출처 :  http://androidhuman.tistory.com/entry/%ED%94%84%EB%9E%98%EA%B7%B8%EB%A8%BC%ED%8A%B8Fragment-%EC%A0%95%EB%B3%B5-2-%ED%94%84%EB%9E%98%EA%B7%B8%EB%A8%BC%ED%8A%B8-%EC%9E%90%EC%84%B8%ED%9E%88-%EC%95%8C%EC%95%84%EB%B3%B4%EA%B8%B0

안드로이드 3.0부터 추가된 요소인 Fragment.....

이는 독립된 로직을 가지고 있는 일련의 뷰를 포함하는 액티비티를 작성할 때
매우 유용합니다. 덕분에 제가 최근 만드는 앱에서는 프래그먼트를 절찬리(?) 사용하고 있지요.

하지만, 아직 사용법이 익숙하지 않아서인지 몰라도,
사소하지만 큰 실수를 자주 하곤 합니다.

가장 많이 접하는 경우가.....

"프래그먼트를 추가했는데, 왜 프래그먼트가 보이지 않는거지??" 

이 상황일 것으로 추측됩니다. ㅎㅎ
과연, 이유가 무엇일까요????

네... 사실 별건 없습니다.
바로 commit() 메서드를 호출하지 않았기에....ㅠㅠ

프래그먼트를 화면에 추가하거나 표시하려면 FragmentTransaction 클래스의 메서드를 사용하는데,
여기에서 추가/교체/제거 등의 작업을 한 후 반드시 commit() 메서드를 호출해야 변경 사항이 적용됩니다.
그렇지 않아면.. 백날 건드려봤자 변하는 것은 없지요...

사소한 것이지만 자주 잊기 쉬운 것이라 한번 정리해 보았습니다. ^^

ps. 한가지 예외가 있따면, 액션바의 OnTabListener 의 인자로 받는 FragmentTransaction을 사용할 때는
commit() 메서드를 호출하면 안됩니다. 자동으로 commit() 메서드를 호출해주기 때문이지요. 



출처 : http://androidhuman.tistory.com/entry/%ED%94%84%EB%9E%98%EA%B7%B8%EB%A8%BC%ED%8A%B8%EA%B0%80-%ED%91%9C%EC%8B%9C%EB%90%98%EC%A7%80-%EC%95%8A%EC%95%84%EC%9A%94