본문 바로가기

프로그래밍/안드로이드

[안드로이드] 리스트뷰(ListView)

※ 저는 안드로이드 프로그래밍 정복(김상형 著, 한빛미디어) 책을 이용해 공부하고 있으며

예제와 코드는 이 책을 통해 공부중임을 밝힙니다.

개인적인 공부를 하면서 정리한 형식이기 때문에 심각한 오류가 있을 수 있습니다. 피드백 주시면 정말 감사하겠습니다.


※ 안드로이드 프로그래밍 정복(549p~567p) 참조


<리스트뷰>


리스트뷰 형태를 갖고 있다.


레이아웃을 이렇게 준비하고, 아래처럼 코드를 구성한다

데이터 준비 - 어댑터 준비 - 어댑터 연결의 과정을 거친다는 것을 알 수 있다.


1. 데이터 준비 단계 

 ArrayList 객체를 생성하고, 이름 문자열을 추가했다.


2. 어댑터 준비 단계

데이터와 리스트뷰를 연결할 "어댑터(Adapter"를 생성한다.

또한, 항목(list)들이 모두 문자열이고 배열 형태로 저장되어 있으므로

ArrayAdapter를 쓰는 것이 적절하다.


배열을 원본으로 사용할 떄는 다음 생성자로 어댑터를 초기화한다. (생성자 링크)


ArrayAdapter(Context context, int textViewResourceId, List<T> objects)

ArrayAdapter(Context context, int textViewResourceId, T[] objects)


1번쨰 인수 :  현재 컨텍스트이며 액티비티를 넘겨준다.

2번째 인수 : 항목을 표시할 레이아웃 리소스의 id

simple_list_item_1 : 하나의 텍스트뷰로 구성된 레이아웃

simple_list_item_2 : 두 개의 텍스트뷰로 구성된 레이아웃

simple_list_item_checked : 오른쪽에 체크 표시가 나타난다.

simple_list_item_single_choice : 오른쪽에 라디오 버튼이 나타난다.

simple_list_item_multiple_choice : 오른쪽에 체크 버튼이 나타난다.

3번째 인수 : 어댑터로 공급될 데이터의 원본(오버로딩 되어있음)

List<T> objects : List 인터페이스를 지원(LinkedList, Stack 등의 컬렉션)

T[] objects : 단순 배열을 원본으로 사용한다.


위 예제를 String[] pocketmon = { "피카츄", "파이리", "꼬부기", "또가스" } 로 수정할 수 있다.

단, 배열은 초기화할 때 크기가 결정되어버리므로 더 이상 데이터를 추가하거나 삭제하지 못한다.

또, 정적 데이터인 경우는 배열보다 XML 리소스를 사용하는 것이 더 유리하다.


3. 어댑터 연결


어댑터 객체가 준비되었으면 리스트뷰의 다음 메서드를 호출하여 연결한다.

예제에서는 list.setAdatper (pocketmonAdapter) 이다.

void setAdapter ( T adapter )



<출처 : http://sunghodev.tistory.com/2>




코드에서 문자열 배열을 직접 정의하는 것은 효율적이지 못하며 관리에도 불리하다.

고정적인 문자열이라면 컬렉션이나 배열을 쓰는 것보다 리소스에 정의하는 것이 바람직하다.


예를 들어 도서 장르에 대한 예제를 살펴보자.



arrays.xml에 저장된  문자열배열(string-array)을 원본으로 하는 어댑터를 "생성"할 때는

다음 정적 메서드를 호출한다.


static ArrayAdapter <CharSequence> createFromResource (Context context, int textArrayResId, int textViewResId)


두 번째 인수 : 문자열 배열의 ID를 지정 (문자열 배열의 ID는 R.array.genre가 된다.)

세 번째 인수 : 레이아웃을 지정



이렇게 리소스에 배열이 정의되어 있으면 "데이터 원본 준비" 파트가 없어도 된다.

어댑터 준비 과정에서 R.array.genre를 통해 출력이 가능하다.




위 과정을 요약하면


1. 어댑터뷰에 출력할 데이터는 "코드(java)"에서 정의한 배열일 수도 있고

2. XML 문서(arrays.xml)에서 정의한 정적 리소스일 수도 있다.


어댑터는 위 데이터 원본을 리스트뷰, 스피너, 그리드뷰 등의 어댑터뷰와 연결할 수도 있다.



전기 규격이 다르더라도 어댑터처럼 다양한 변환을 지원한다.



<리스트뷰의 속성>


choiceMode

none (CHOICE_MODE_NONE) : 항목을 선택할 수 없다. (디폴트값)

singleChoice (CHOICE_MODE_SINGLE) : 하나의 항목만 선택할 수 있다.

multipleChoice (CHOICE_MODE_MULTIPLE) : 여러 개의 항목을 선택할 수 있다.


코드에서 선택 모드를 조사하거나 변경할 때는 get(set)ChoiceMode 메서드를 사용한다.

선택을 허용하려면 사용자가 누를 수 있는 체크 박스나 라디오 버튼의 레이아웃이 배치되어야 한다.


divider

항목 사이의 구분선을 지정한다. 코드에서는 setDivider 메서드를 사용한다.


dividerHeight

구분선의 높이를 지정한다.


entries

리스트 뷰에 표시할 배열을 지정한다. 정적인 배열은 이 속성으로 간편하게 지정할 수 있다.    


이렇게 코드 내에서도 리스트뷰의 모양을 꾸밀 수 있다.



<어댑터뷰의 항목 선택>



어댑터뷰의 항목 하나를 클릭하면 OnItemClickListener의 다음 메서드가 호출된다.

항목선택시의 동작을 처리하고 싶다면 이 메서드를 구현한다.


void OnItemClick(AdapterView<?> parent, View view, int position, long id)

리스너 준비단계 : 이 메서드로 어떤 항목이 클릭되었는지 알 수 있다.


void setOnItemClickListener(AdapterView.OnItemClickListener listener)

리스너 등록단계 : 위 메서드로 리스너를 지정한다.


void setOnItemLongClickListener (AdapterView.OnItemLongClickListener listener)

void setOnItemSelectedListener (AdapterView.OnItemSelectedListener listener)

롱클릭이나, 선택에 대해서도 리스너를 지정할 수 있지만, 통상 손가락으로 탭하여 

선택하는 것이 보통이므로 클릭 리스너를 많이 사용한다.



<어댑터뷰의 항목 편집>


리스트뷰의 항목이 ArrayList나 데이터베이스처럼 동적으로 변하는 자료라면 실시간으로 반영할 수 있다.


한 번에 한 개씩 삭제할 수 있는 리스트뷰


public class ExerciseExam extends AppCompatActivity {

ArrayList<String> Items;
ArrayAdapter<String> Adapter; // 어댑터 준비
ListView list;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_excercise_exam);

//데이터 준비
Items = new ArrayList<String>();
Items.add("FIrst");
Items.add("Second");
Items.add("Third");

//어댑터 준비
Adapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_single_choice, Items);

//어댑터 연결
list = (ListView) findViewById(R.id.list);
list.setAdapter(Adapter);
list.setChoiceMode(ListView.CHOICE_MODE_SINGLE); // 하나의 항목만 선택할 수 있다.
}

public void mOnClick(View v){
EditText ed = (EditText)findViewById(R.id.newitem);
switch (v.getId()) {
case R.id.add: // add 버튼 클릭시
String text = ed.getText().toString(); // EditText에 입력된 String값을 읽어옴
if (text.length() != 0) { // 입력된 text 길이가 0이 아니라면
Items.add(text); // items배열 (ArrayList<String>) 에 add메서드로 text를 추가함.
ed.setText("");
Adapter.notifyDataSetChanged();
}
break;

case R.id.delete:                          // delete 버튼 클릭시
int pos;
pos = list.getCheckedItemPosition(); // int getCheckedItemPosition()으로 현재 선택된 항목의 첨자를 조사
if (pos != ListView.INVALID_POSITION) { // 선택된 항목이 있으면
Items.remove(pos);                  // remove 메서드로 배열에서 삭제하고
list.clearChoices();                // clearChoice 메서드를 호출하여 선택도 해제
Adapter.notifyDataSetChanged();     // notifyDataSetChange 메서드로 원본 데이터가 변경됨을 알려야 함.
}
break;
}
}
}



그런데 위 예제는 한 번에 한 개씩만 삭제가 가능해서 다소 불편하다.

선택된 모든 항목을 조사해서 삭제하려고 한다면 코드를 수정해줘야 한다.



빨간 네모로 체크된 부분을 다중(multiple)로 고쳐준다.



주목할 점은 제거 메서드는 "역순"으로 진행된다는 점이다.

앞쪽에서부터 삭제하면 2번, 3번 항목이 체크되어있을 때 2번을 지우면 3번이 2번이 되므로

3번을 지울 때는 4번이 삭제된다. 따라서 3번, 2번 순서로 지우게 된다.