UroA 개발 블로그

[Android] ListView 개념 & 예제 본문

Programming/Android

[Android] ListView 개념 & 예제

UroA 2015. 11. 21. 00:56

ListView 사용하기

안드로이드에서 가장 많이 사용되는 위젯 중의 하나가 바로 ListView이다. 안드로이드에서는 ListView 처럼 여러 개의 아이템 중에 하나를 선택할 수 있는 위젯들을 특별히 '선택 위젯'이라고 부른다.

* 선택 위젯 : 일반 위젯과는 달리 선택 위젯은 어댑터(Adapter) 패턴을 사용하므로 직접 위젯에 데이터를 설정할 수 없을 뿐만 아니라, 어댑터에서 만들어주는 뷰를 이용해 리스트뷰의 한 아이템으로 보여주는 방식을 사용한다.

선택할 수 있는 여러 개의 아이템이 표시되는 선택위젯은 어댑터(Adpater)를 통해 각각의 아이템을 화면에 디스플레이 한다. 따라서 원본 데이터는 어댑터에 설정해야 하며 어댑터가 데이터 관리 기능을 담당한다. 선택 위젯에 보이는 각각의 아이템이 화면에 디스플레이되기 전에 어댑터의 getView() 메소드가 호출된다. 이 메소드는 어댑터에서 가장 중요한 메소드로 이 메소드에서 리턴하는 뷰가 하나의 아이템으로 호출된다.

* getView()

[Reference]

public View gettView (int position, View converView, ViewGroup parent)

posittion : 아이템의 인덱스를 의미, 리스트 뷰에서 보일 아이템의 위치 정보라고 할 수 있다. 0부터 시작하여 아이템의 개수만큼 파라미터로 전달된다.

converView : 현재 인덱스에 해당하는 뷰 객체를 의미, 안드로이드에서는 선택 위젯이 데이터가 많아 스크롤될 때 뷰를 재활용 하는 메커니즘을 가지고 있어 한 번 만들어진 뷰가 화면 상에 그대로 다시 보일 수 있도록 되어 있다. (이미 만들어진 뷰들을 그대로 사용하면서 데이터만 바꾸어 보여주는 방식)

parent  이 뷰를 포함하고 있는 부모 컨테이너 객체이다.

 

다음은 하나의 아이템에 여러 정보를 담아 리스트뷰로 보여줄 때 해야 할 일들을 나열한 것이다.

 속성

설명 

아이템을 위한 XML 레이아웃 정의하기 

리스트뷰에 들어갈 각 아이템의 레이아웃을 XML로 정의한다. 

아이템을 위한 뷰 정의하기 

리스트뷰에 들어갈 각 아이템을 하나의 뷰로 정의한다. 이 뷰는 여러개의 뷰를 담고 있는 뷰 그룹이어야 한다. 

어댑터 정의하기 

데이터 관리 역할을 하는 어댑터 클래스를 만들고 그 안에 각 아이템으로 표시할 뷰를 리턴하는 gettView() 메소드를 정의한다. 

리스트뷰 정의하기

화면에 보여줄 리스트를 만들고 그 안에 데이터가 선택되었을 때 호출될 리스너 객체를 정의한다.

 


실습 ( GitHub : https://github.com/kdhx92/Android_ListView_Test )

① < activity_main.xml > : main의 레이아웃 

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent" android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:paddingBottom="@dimen/activity_vertical_margin" tools:context=".MainActivity">


<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content">


<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1">

<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1">


<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="제목 : "
android:textSize="20sp"
android:id="@+id/txtTitle" />

<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/editTitle"
android:singleLine="true"
android:maxLength="20"/>
</LinearLayout>

<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1">

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="설명 : "
android:textSize="20sp"
android:id="@+id/txtDescription" />

<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/editDescription"
android:singleLine="true"
android:maxLength="20"/>

</LinearLayout>
</LinearLayout>

<Button
android:layout_width="wrap_content"
android:layout_height="80dp"
android:text="Submit"
android:id="@+id/btnSubmit" />
</LinearLayout>

<ListView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/listView" />

</LinearLayout>

② < list_item.xml > : 리스트뷰에 들어갈 각 아이템의 레이아웃

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent" android:layout_height="match_parent"
android:orientation="vertical"
android:padding="5dp">


<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="25sp"
android:text="Title"
android:textColor="#f000"
android:id="@+id/txtTitle_item" />

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="15sp"
android:text="Description"
android:textColor="#9000"
android:id="@+id/txtDescription_item" />
</LinearLayout>

③ < ItemData.java > : ListView의 아이템 Class


//ListView의 아이템에 필요한 데이터(Model)
public class ItemData {
String Title;
String Description;
}

④ < ViewHolder.java > : ViewHolder Class 

import android.widget.TextView;

//ListView의 아이템을 담아두기 위한 클래스
public class ViewHolder {

TextView TextView_Title;
TextView TextView_Description;
}

⑤ < CustumAdapter.java > : 데이터 관리 역할을 하는 Adapter Class

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.TextView;

import java.util.ArrayList;

/**
* Created by JongMin on 2015-11-20.
*/
public class CustomAdapter extends BaseAdapter {

private ArrayList<ItemData> itemDatas = null; //인자로 받아온 itemDatas를 저장하기 위한 어댑터 내의 ArrayList<ItemData> 객체
private LayoutInflater layoutInflater = null; //ListView 아이템 레이아웃을 가져오기 위한 클래스


/*생성자를 통해서 itemDatas를 받아옵니다.
ctx는 BaseAdapter에 내재되어 있지 않으므로 인자로 받아오니다.*/

public CustomAdapter(ArrayList<ItemData> itemDatas, Context ctx){
this.itemDatas = itemDatas;
this.layoutInflater = (LayoutInflater)ctx.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}

//생성자를 통해서가 아닌 메소드를 통해 itemDatas를 받아오기 위한 메소드
private void setItemDatas(ArrayList<ItemData> itemDatas){
this.itemDatas = itemDatas;
this.notifyDataSetChanged();
}

//int getCount 메소드 -> ListView의 아이템 개수를 int형으로 반환
@Override
public int getCount() {
return (itemDatas != null) ? itemDatas.size() : 0;
}

//Object getItem 메소드 -> position에 해당하는 아이템을 객체의 형태로 반환
@Override
public Object getItem(int position) {
return (itemDatas != null && (0 <= position && position < itemDatas.size()) ? itemDatas.get(position) : null ); // A ? B : C 아시조??
}

//long getItemId 메소드 -> position에 해당하는 아이템의 Id를 반환
@Override
public long getItemId(int position) {
return (itemDatas != null && (0 <= position && position < itemDatas.size()) ? position : 0);
}

//View getView 메소드 -> 받아온 데이터(itemDatas)를 ListView의 아이템으로 만들어주는 메소드
//convertView는 재활용이 가능한 View
@Override
public View getView(int position, View convertView, ViewGroup parent) {

ViewHolder viewHolder = new ViewHolder(); //화면에서 사라진 convertView를 담아두기 위한 용도로 viewHolder를 사용

if(convertView == null){ //'convertView == null' = 'convertView가 한번도 생성이 된 적 없다면'

convertView = layoutInflater.inflate(R.layout.list_item, parent, false); //TextView 2개를 포함하고 있는 list_item을 inflate해서 convertView에 대입

//viewHolder를 초기화. 한번 해두면 이 position의 convertView는 다시 findViewById를 할 필요가 없어집니다.
viewHolder.TextView_Title = (TextView)convertView.findViewById(R.id.txtTitle_item);
viewHolder.TextView_Description = (TextView)convertView.findViewById(R.id.txtDescription_item);

convertView.setTag(viewHolder); //convertView에 viewHolder를 태그로 달아줍니다.이렇게 해두면 화면에서 ListView의 아이템(convertView)가 사라져도 메모리에서 지워지지 않습니다.
}

else{
viewHolder = (ViewHolder)convertView.getTag();//convertView가 존재한다면(한번이라도 생성이 되었다면) viewHolder가 존재. 이를 다시 가져옵니다.
}

ItemData itemData = itemDatas.get(position); //position에 해당하는 itemData를 받아옵니다.

viewHolder.TextView_Title.setText(itemData.Title); //받아온 itemData의 데이터를 viewHolder의 TextView에 띄웁니다.
viewHolder.TextView_Description.setText(itemData.Description);

return convertView;
}
}

⑥ < MainActivity.java >

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.text.TextUtils;
import android.view.View;
import android.widget.AdapterView;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ListView;
import android.widget.Toast;

import java.util.ArrayList;

public class MainActivity extends AppCompatActivity {

/*ListView에 들어갈 ArrayList<ItemData>형의 객체 선언
*ArrayList<ItemData> -> Title과 Description 두 가지의 String 객체를 데이터로 갖는 ItemData라는 객체들을 배열의 형태로 저장하는 클래스
*/
private ArrayList<ItemData> itemDatas = null;

//사용자와 직접 상호작용 하는 View들 선언
Button btnSubmit; //ListView에 데이터를 띄우기 위한 버튼
EditText editTitle, editDescription; //사용자로부터 Title과 Description을 받아오기 위한 EditText
ListView listView; //데이터들을 list의 형태로 보여 주기 위한 view

//ListView와 Model(itemDatas)을 연결해주는 CustomAdapter 선언
private CustomAdapter adapter;

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

initModel(); //ListView에 들어갈 데이터들(위에서 ArrayList<ItemData>의 형태로 선언한 itemDatas)를 초기화하는 모듈
initView(); //Button, EditText, ListView 3가지 view들을 초기화시키고 Listener를 달아주는 모듈
makeList(); //Model(itemDatas)와 View(ListView)를 이어주는 역할을 하는 모듈
}

private void initModel() {
itemDatas = new ArrayList<ItemData>(); //itemDatas 데이터 생성.
}

private void initView() {

editTitle = (EditText)findViewById(R.id.editTitle);
editDescription = (EditText)findViewById(R.id.editDescription);
btnSubmit = (Button)findViewById(R.id.btnSubmit);
listView = (ListView)findViewById(R.id.listView);

btnSubmit.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {

/*만약 사용자가 EditText에 아무것도 입력하지 않은 채 submit 버튼을 누를 경우
이를 위해 예외 처리를 해주도록 하겠습니다.
*/

if(TextUtils.isEmpty(editTitle.getText())
|| TextUtils.isEmpty(editDescription.getText())){

Toast.makeText(getApplicationContext(),"데이터를 입력하셔야 합니다.",Toast.LENGTH_SHORT).show();

return;
}

else {

/*EditText를 통해 사용자로부터 String 형태의 Title과 Description을 받아오면 이것들을 저장할 변수 또는 객체가 필요합니다.
String 형태로 각각 Title과 Description을 저장할까요? 아니죠.
String 형의 Title과 Description 둘을 동시에 저장하기 위해 ItemData라는 클래스를 만들었으니 활용해야죠.
ItemData 객체를 생성해서 거기에 저장을 하는 겁니다.*/

ItemData itemData_submit = new ItemData(); //ItemData 객체 itemData_submit를 생성. submit될 것이므로 이름이 헷갈리지 않게 _submit를 덧붙였습니다.
itemData_submit.Title = editTitle.getText().toString(); //itemData_submit의 Title 멤버변수에 EditText의 내용 저장
itemData_submit.Description = editDescription.getText().toString(); //itemData_submit의 Description 멤버변수에 EditText의 내용 저장

/*itemData_submit에 사용자가 입력했던 Title과 Description이 저장되었습니다.
이제 이 녀석을 ArrayList<ItemData> 객체인 itemDatas에 하나의 배열 데이터로 추가합니다.
*/

itemDatas.add(0, itemData_submit);
adapter.notifyDataSetChanged(); //itemDatas에 데이터가 추가되었으니 이를 adapter에 알립니다.

editTitle.setText("");
editDescription.setText("");

listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
ItemData itemData_temp = (ItemData)adapter.getItem(position); //getItem메소드는 Object형으로 반환을 하므로 ItemData형으로 캐스팅을 해주어야 합니다.
Toast.makeText(getApplicationContext(), itemData_temp.Title + "\n" + itemData_temp.Description, Toast.LENGTH_SHORT).show();
}
});

listView.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() {
@Override
public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {

ItemData itemData_temp = (ItemData)adapter.getItem(position);
itemDatas.remove(itemData_temp); //itemDatas에서 해당 데이터를 가진 부분을 삭제합니다.
adapter.notifyDataSetChanged(); //itemDatas에 데이터가 삭제되었으니 이를 adapter에 알려야겠죠?

return false;
}
});
}
}

});

}

private void makeList() {

adapter = new CustomAdapter(itemDatas, getApplicationContext()); //CustomAdapter 객체인 adapter에 itemDatas와 Context를 인자로 넘겨주었습니다!!
listView.setAdapter(adapter); //listView에 우리가 만든 CustomAdapter 객체인 adapter를 적용시켰습니다. 이제 Model(itemDatas와 listView가 연결되었습니다!!)

}
}
Comments