[Android] 커스텀 리스트뷰 : Custom ListView - Layout : 레이아웃


오랜만에 리스트뷰에 대한 얘기를  다시 하게 되는군요.

사실 리스트뷰는 안드로이드의 거의 모든 뷰를 그려낼 수 있는 녀석입니다.
특히 모바일의 특성상 목록에 대한 부분들이나, 여러가지를 선택하는 것들에 대해서 리스트 형식으로 보여주어야 하는 경우가 많기 때문이죠.

 

아이폰에는 테이블뷰 라는 녀석이 있죠. 그에 비견되는 녀석이 안드로이드의 리스트뷰입니다.
물론 아직은 인터페이스 빌더에 비해서 UI 작성쪽에서 후달리는^^; 경우가 많이 있지만, 그래도 이 놈 여러가지를 할 수 있어서 매력적입니다.


여기서는 간단하게 리스트뷰의 한 row(또는 cell)을 커스텀해서 구현하는 것에 대해 이야기 하려고 합니다.

결과적으로는 아래 스크린샷과 같은 프로젝트를 만들어 낼 것입니다.





먼저 중요한 몇가지 개념에 대해서 알아보도록 하겠습니다. ( Data, Adapter, Custom Layout, LayoutInflater )








 필수개념 : Custom Layout - 커스텀 레이아웃

일반적으로 ListView는 안드로이드에서 제공하는 것들을 사용하면,
텍스트 하나를 출력하거나, 텍스트와 체크박스를 같이 두거나, 라디오버튼을 가지고 선택을 할 수 있다거나 하는 등의 레이아웃을 가질 수 있습니다.

여기서는 프로그래머가 직접 만든 Custom Layout을 리스트뷰에 사용할 것입니다.

하나의 레이아웃을 만들고, 데이터를 받아섯 세팅해주면,
각 아이템(리스트뷰의 한 아이템-row)마다 같은 레이아웃, 다른 데이터를 사용하게 될 것입니다.

간단하게는 여러 오픈 API 중 트위터 같이 트윗들을 모아서 보여주는 데이터가 있을 수 있습니다.
여기서는 트윗들을 보여주는것에 대해 이야기 합니다.

트위터를 보여줄 것이므로 이미지뷰(ImageView)와 여러개의 텍스트뷰(TextView)가 필요합니다.
간단한 예제를 만들것이므로, 유저의 스크린네임(ScreenName)과 텍스트(Text), 그리고 프로필 이미지만 보여줄 수 있는 레이아웃 만듭니다.

결과적으로는 아래의 이미지와 같은 row를 만들것입니다.



위의 레이아웃을 위한 코드는 다음과 같습니다.

/res/layout/list_view_row.xml






 필수개념 : Layout Inflater - 레이아웃 인플레이터 : 레이아웃 객체로 뽑아내기

안드로이드에서는 Layout Inflater 라는 것을 이용해 xml 로 만들어둔 layout 을 자바 객체로 만들어서 사용할 수 있습니다.

번외적인 얘기로 잠깐 언급하고 가자면, 안드로이드에서 UI를 구성하는 방법은 3가지가 있습니다.
1.  자바 코드로 UI 만들기
2. XML로 UI 만들기
3. 자바 코드 및 XML을 함께 사용해 UI 만들기

1번과 2번을 사용하는것은 각각 장단점을 가지고 있기때문에, 저는 3번의 방법을 많이 사용합니다.
여기서도 3번 비스무례한 방법을 사용할 것입니다.

이미 XML로 만든 UI는 위의 Custom Layout에서 만들어두었습니다.
이제 이 레이아웃 리소스을 Inflater로 뽑아서 객체화 시켜 사용할 것입니다.



코드는 정말 간단합니다.

LayoutInflater 를 만들어줍니다.




이제 이 인플레이터 인스턴스를 통해 inflate 해서 객체를 뽑아내기만 하면 됩니다.



convertView 는 R.layout.list_view_row 이라는 녀석을 UI로 하는 View 객체로 탄생했습니다.
이 녀석이 바로 우리가 만들려던 XML 레이아웃의 뽑아내진 객체입니다.

위의 커스텀 레이아웃에서 보았던 그런 모양을 하고 있는 뷰죠.

이 커스텀 레이아웃과 어댑터를 결합해 사용자들에게 보여줄 리스트를 만들것입니다.





 필수개념 : Data - 데이터

이번에는 리스트뷰에 넣을 데이터들을 만들 차례입니다.
여러가지 데이터들이 있겠지만, 위에서 언급했듯이, 트윗들을 보여줄 것입니다.

요즘 대세인 아이유에 대해 어떤 얘기들이 오가는지 트위터로 검색해 봅니다.

트윗을 검색하기 위해 아래와 같은 트윗 검색 API를 사용합니다.
http://search.twitter.com/search.json?

쿼리 스트링을 지정해 주어야 하는데, 한글은 검색이 제대로 이루어 지지 않으므로,
아이 이유 를 동시에 검색해서 아이유에 대한 트윗을 검색하도록 해봅니다.
http://search.twitter.com/search.json?q=아이+이유&page=1

이 API를 통해 검색된 결과는 더보기를 통해 확인 가능합니다.

트윗 검색 결과 JSON 보기




이 데이터들을 코드상으로 받아오기 위해서는 간단한 절차가 필요합니다.

인터넷 통신을 하기 위해서는 퍼미션 하나가 필요합니다. AndroidManifest.xml 에 아래의 퍼미션을 추가해 줍니다.


우선 HttpClient 가 필요하고, HttpGet, 또는 HttpPost 등의 인스턴스가 필요합니다.
또 결과값을 받아주는 HttpResponse 도 필요하죠.
( 통신하는 방식은 여러가지가 있으니, 편한 코드를 사용하면 됩니다. )


 2011.11.04 18:28 추가내용

 아래의 코드는 Honeycomb에서 제대로 실행이 되지 않을 수 있습니다. 
 android.os.NetworkOnMainThreadException이 발생하게 된다는군요.
 위의 예외가 발생한 경우 참고하세요. ---> 강철삽질님의 블로그 : 48번글
 




이렇게 받아온 결과를 통해서 실제로 리스트를 만들 차례입니다.


이 검색 결과를 JSON 파싱을 통해 리스트로 만들어 주어야 하죠.







 필수개념 : Adapter / Custom Adapter - 어댑터 / 커스텀 어댑터

어댑터는 매우 중요합니다. 데이터를 어떤 방식으로 보여줄지를 결정할 수 있기때문이죠.
데이터는 어댑터를 거치고나서 리스트뷰에 뿌려지게 됩니다.

어떻게, 어떤 모양으로, 어떤 방식을 거쳐서 화면에 보여질지는 Adapter에서 결정하면 됩니다.
모든 일들은 어댑터에서 일어나지요.


여기서는 BaseAdapter가 아닌, ArrayAdapter를 상속받아서 사용합니다.
(BaseAdapter를 상속받아서 사용해도 무방합니다.)

ArrayAdapter 의 generic 으로 TweetInfo 를 사용해 CustomAdapter 라는 어댑터를 만들어 줍니다.



여러 생성자들이 있지만, (Context , int , List<TweetInfo> ) 라는 생성자를 사용해 봅니다.
생성자의 파라미터들을 약간 고쳐 사용합니다.

어댑터는 입력된 리스트의 전체 길이만큼을 getView 라는 메소드를 통해 화면에 그려줍니다.
모든 처리는 getView() 에서 하면 됩니다.
getView() 의 파라미터를 보면 이번에 그려줄 녀석이 몇번째 position 인지등도 나와있으므로, 손쉽게 그릴 수 있습니다.

getItem 등의 여러 메소드들이 있지만, 이 예제에서는 중요하지 않으므로 override 하지 않습니다.

간단하게 다시 생각해보자면,
어댑터는 데이터를 리스트로 입력 받았고,
이 리스트의 길이만큼 getView를 호출해서 화면에 그려준다.
이때 기존에 xml로 만들어둔 layout을 객체화 하여 사용한다.
그리고 각 포지션에 맞는 데이터들을 불러내 convertView를 통해 화면에 세팅해준다.
입니다.

커스텀 어댑터의 전체 코드는 아래와 같습니다.
// 주석에도 써있지만, 논제에 맞지 않는 코드들은 배제하였습니다.








주제에 맞지 않는 부분은 최대한 배제하고 설명하려 했지만, 꼭 필요한 부분들은 추가적으로 들어가있습니다.

마지막으로 정리하면서 아래 세장의 그림들로 마무리를 지어 봅니다.


데이터를 리스트로 만들어 줍니다.



리스트뷰의 한 row에 사용될 레이아웃을 xml로 만들어 두고, LayoutInflater 를 통해 객체화 합니다.


위에서 만든 리스트와  객체화된 레이아웃을 통해 Adapter의 getView에서는 각 row를 만들어 주고,
이를 ListView에 보여줍니다.


끝으로 예제 프로젝트를 첨부합니다.

CustomListView.zip


부족하다고 생각되는 부분이나, 여러 의견들은 댓글이나 방명록으로 알려주세요 ^^


Posted by croute

댓글을 달아 주세요

  1. 이전 댓글 더보기
  2. 장미와나 2011.06.17 12:00 신고  댓글주소  수정/삭제  댓글쓰기

    감사합니다, ^^;
    처음읽고 댓글달아요 ,저도 초보인지라 100%이해못해서 여러번 읽어야겠어요'0'ㅋ
    그림도 넣어주시고 대단하심!

  3. 김동현 2011.06.19 15:37 신고  댓글주소  수정/삭제  댓글쓰기

    이미지 읽어오는 부분이 참 궁금합니다. 도움좀 주실수있으신가요..ㅠ
    코드가 있으시다면 heatsunshine@nate.com 으로 부탁드립니다 ㅠㅠ 아니면 설명이라도...

    • Favicon of https://croute.me BlogIcon croute 2011.06.20 21:19 신고  댓글주소  수정/삭제

      URL(String)으로 되어있는 정보를 가지고 Bitmap을 만들어야 합니다.

      그리고 그 Bitmap 객체를 이미지뷰에 뿌려주는거지요.

      통신 모듈을 하나 간단하게 만드셔서
      해당 이미지 유알엘을 가지고 Bitmap 객체를 먼저 만들어보세요.
      그리고 그 Bitmap 객체를 이미지뷰에 뿌리면 끝!

      이때, 리스트는 스크롤 되고는 하니,
      비동기로 처리하기 위해 Handler를 사용하는 등의 방법을 쓰면,
      좋겠죠~?

  4. STAM 2011.06.28 11:34 신고  댓글주소  수정/삭제  댓글쓰기

    커스텀 뷰쪽을 보고 있었는데,
    자세한 설명 및 소스코드 감사합니다^^

  5. kanais 2011.08.02 11:28 신고  댓글주소  수정/삭제  댓글쓰기

    좋은글 정말 감사합니다 ^^
    custom Listview에 대해서 좀 애먹었는데 이렇게 좋은 글을 통해 또 하나를 배우게 되네요 ^^
    감사합니다~

  6. KoA 2011.08.16 16:21 신고  댓글주소  수정/삭제  댓글쓰기

    감사합니다 !! 커스텀 뷰에서 헤메고 있었는데 설명과 풀소스 까지 ^^ 감사합니다!!

  7. 삽질중 2011.09.02 09:00 신고  댓글주소  수정/삭제  댓글쓰기

    search 화면이 아닌 개인 timeline 을 가져오려는데
    "https://api.twitter.com/1/statuses/user_timeline.json?screen_name=dandy0725&include_rts=true&count=1%22"

    이런방식으로 url 을 지정해 주면 json data 를 가져오긴 하는데 (브라우저로 확인해보았습니다.)
    JSONArray array = new JSONObject(mResult); 에서 객체를 생성하질 못하고
    JSONException 이 발생합니다.

    mResult 의 값이 뭔가 형식에 맞지 않아서 그런건지 잘 모르겠네요.
    혹시 도움좀 얻을 수 있을까 해서 질문올립니다.

    감사합니다~

    • Favicon of https://croute.me BlogIcon croute 2011.09.02 15:18 신고  댓글주소  수정/삭제

      JSONArray array = new JSONObject(mResult);
      이걸

      이렇게 바꿔보세요.
      JSONArray array = new JSONArray(mResult);

      JSONArray 타입의 변수에
      JSONObject를 넣으려고 하니 에러가 나고,

      그 이전에,
      mResult의 데이터가 JSONArray로 되어있으니,
      변환할때 형이 달라서 1차적으로 에러가 날겁니다.

  8. yap 2011.09.16 12:04 신고  댓글주소  수정/삭제  댓글쓰기

    리스트뷰의 항목마다 똑같은 커스텀 레이아웃을 넣으신것 같은데요~
    항목이 10개라면 10개에 각각 다른 커스텀 레이아웃을 넣으려면 어떻게 해야하는지 아시나요?ㅎㅎ
    커스텀 어댑터를 10개 만들어야 하는건지.. 확신이 안서서요ㅠ 혹시 아시면 어떤식으로 해야하는지 설명좀 부탁드릴게요^^

    자세한 포스팅 감사합니다~ 덕분에 많은 도움이 됐어요^^!!

    • Favicon of https://croute.me BlogIcon croute 2011.09.16 20:56 신고  댓글주소  수정/삭제

      기본적으로 어댑터는 convertView를 재활용합니다.
      yap님께서 말씀하신건 재활용을 하지 않는다는 전제가 깔려있어야 가능할것 같구요,

      Data 클래스에서 어떤 레이아웃을 선택할지에 대한 정보만 있으면,
      getView에서 처리할 수 있을 것 같네요.

  9. 녹이쓴추억 2011.10.28 00:39 신고  댓글주소  수정/삭제  댓글쓰기

    자료 감사합니다! 졸업작품 중인데 정말 요긴하게 쓰고 있습니다..
    그런데 리스트뷰의 구분선을 1(default)로 설정하면 왜 굵기가 다르게
    나타나는지 모르겠네요 2이상을 주면 일정한데 말이죠..
    그리고 제 실력상 예제를 보고 응용하는 정도 밖에 안되서 그러는데
    Bitmap이미지를 적용하는 소스를 받을 수 있을까요..?
    있다면 parity@naver.com좀 부탁드립니다!

  10. tesla 2012.02.03 14:12 신고  댓글주소  수정/삭제  댓글쓰기

    감사합니다.
    커스텀리스트 예제 까지 만들어주시고...
    많이 배우고 갑니다.

  11. 놀라움 2012.03.06 22:15 신고  댓글주소  수정/삭제  댓글쓰기

    좋은 강의 잘 보고 있습니다. 커스텀 리스트 뷰의 각 항목별 fontSize를 런타임시 조절할 수 있는지요? 고견 주시면 감사하겠습니다.

    • Favicon of https://croute.me BlogIcon croute 2012.03.07 11:10 신고  댓글주소  수정/삭제

      특정 row의 fontSize를 임시로 조절할순 있겠지만, 재활용하게 되는 단계에서 다른 row에 영향을 미치게됩니다.
      예외처리를 잘 해준다면 문제는 없겠지만요 ㅎ

  12. BlogIcon 놀라움 2012.03.16 16:32 신고  댓글주소  수정/삭제  댓글쓰기

    ListView를 scroll시킬 때 smoothscroll()을 쓰는데 속도를 아주 천천히 하고 싶습니다. 방법이 있는지요?
    또 런타임시 선택된 하나의 뷰만 하이라이트로 하거나 해당 항목의 텍스트 글자색, 또는 백그라운드의 색을 지정할 수 있는지요 고견을 바랍니다.

  13. Favicon of http://www.facebook.com/profile.php?id=100002479240033 BlogIcon 이수완 2012.07.19 17:26 신고  댓글주소  수정/삭제  댓글쓰기

    감사합니다^^

  14. 뚬양 2012.11.23 14:08 신고  댓글주소  수정/삭제  댓글쓰기

    좋은 자료 감사드립니다.
    올려주신 예제 소스들을 수정하여 리스트뷰에 클릭이벤트를 걸어서 다음 엑티비티를 실행하고 있습니다. 다만 궁금한것이 클릭이 될 경우에 리스트뷰가 눌린 효과가 없는데요. xml에서 background를 지우니 효과는 나지만 보기가 좋지 않네요. 어떻게 해결해야 할까요?

  15. cosrah 2013.02.15 07:34 신고  댓글주소  수정/삭제  댓글쓰기

    정말 좋은 글 감사합니다.
    머리 아프던 걸 해결해주셨네요.

  16. eunjeong 2013.03.15 09:44 신고  댓글주소  수정/삭제  댓글쓰기

    좋은 글 감사히 읽구 갑니닷!(^^)(__)

  17. BlogIcon 힝2 2013.05.30 14:55 신고  댓글주소  수정/삭제  댓글쓰기

    왜 안불어와지지 ㅠ

  18. Favicon of https://dev.twitter.com/docs/api/1/get/search BlogIcon althjs 2013.07.02 23:34 신고  댓글주소  수정/삭제  댓글쓰기

    좋은포스트 감사합니다.
    트위터 1.0 api가 동작하지 않고 있네요.
    https://dev.twitter.com/docs/api/1/get/search 에 json 파일 참조하니 잘 도네요 :)

  19. ggeuing 2014.06.06 12:42 신고  댓글주소  수정/삭제  댓글쓰기

    공부하려고 예제를 다운받아봤는데 실행이 안되는건 무엇때문일까요?ㅠㅠ 저 파싱링크를 주소창에 쳐보니까 아무것도 나오지 않는데 그 이유에서 일까요?ㅠㅠ

    • Favicon of https://croute.me BlogIcon croute 2014.06.06 14:14 신고  댓글주소  수정/삭제

      트위터가 Open API 정책을 변경했던것 같네요. 몇년전에 작성한 글이라 동작하지 않을 수 있을것 같습니다. 지금 바깥이라 들어가면 확인해봐야겠네요 ㅠㅠ

  20. BlogIcon 빙글빙글 2015.03.02 23:40 신고  댓글주소  수정/삭제  댓글쓰기

    와 정말 깔끔하고 군더더기 없는 좋은 글이네요
    감사합니다

  21. 고길동 2018.11.06 01:15 신고  댓글주소  수정/삭제  댓글쓰기

    호호아줌마와후후아저씨