안드로이드 리스트뷰에 버튼 넣기. (Android ListView with Button)
1. Button이 들어간 ListView
안드로이드 ListView에는 다양한 종류의 위젯들이 사용될 수 있습니다. 안드로이드 커스텀 리스트뷰 만드는 방법에서는 ImageView와 TextView로 구성된 ListView를 만드는 방법을 살펴봤구요.
이번에는 ListView에 Button을 넣는 방법에 대해 알아보겠습니다. Button에 대한 이벤트 처리 부분을 제외하곤 일반적인 커스텀 리스트뷰 만드는 방법과 동일한 과정이 수행됩니다.
1.2 ListView 아이템 구성도
ListView 아이템은 한개의 이미지(ImageView)와 문자열(TextView) 그리고 두 개의 버튼(Button)으로 구성합니다.
그리고 아래와 같이 동작하도록 구현합니다.
2. ListView에 Button 넣기
2.1 워크 플로우
2.2 ListView가 표시될 위치 결정. (Layout 리소스 XML에 ListView 추가.)
MainActivity에 ListView를 생성합니다. "activity_main.xml" 파일(또는 "content_main.xml")에 아래의 내용을 작성합니다.
[STEP-1] "activity_main.xml - MainActivity에 ListView 생성.
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
tools:context="com.example.madwin.listviewbuttonexample.MainActivity"
tools:showIn="@layout/activity_main">
<ListView
android:id="@+id/listview1"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</RelativeLayout>
2.3 ListView 아이템에 대한 Layout 구성.
간단하게 설계한 예제 화면에 대한 Layout 리소스를 작성합니다. 파일 이름은 "listview_btn_item.xml"로 지정합니다.
[STEP-2] "/res/layout/listview_btn_item.xml" - 아이템에 대한 Layout 리소스 작성.
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal" android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:id="@+id/imageView1"
android:layout_weight="1" />
<TextView
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:text=""
android:id="@+id/textView1"
android:textSize="16dp"
android:textColor="#000000"
android:gravity="center_vertical"
android:layout_weight="4" />
<Button
android:id="@+id/button1"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:text="번호 선택"
android:layout_weight="1" />
<Button
android:id="@+id/button2"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:text="Toast에서 확인"
android:layout_weight="1" />
</LinearLayout>
2.4 ListView 아이템 데이터에 대한 클래스 정의.
안드로이드 커스텀 리스트뷰 만드는 방법에서 Custom ListView를 만들 때 아이템 데이터 클래스를 새로 만든 것 기억하시죠? 비슷한 방법으로 ListViewBtnItem이라는 이름으로 데이터 클래스를 새롭게 생성하도록 하죠.
[STEP-3] "ListViewBtnItem.java" - ListView 아이템 데이터 클래스 생성
import android.graphics.drawable.Drawable;
public class ListViewBtnItem {
private Drawable iconDrawable ;
private String textStr ;
public void setIcon(Drawable icon) {
iconDrawable = icon ;
}
public void setText(String text) {
textStr = text ;
}
public Drawable getIcon() {
return this.iconDrawable ;
}
public String getText() {
return this.textStr ;
}
}
2.5 Adapter 구현
안드로이드 커스텀 리스트뷰 만드는 방법에서는 BaseAdapter 클래스에서 상속받은 Adapter를 만드는 방법을 사용하였습니다. 이번에는 조금 달리하여 ArrayAdapter를 사용해보도록 하죠.
사실 ArrayAdapter 또한 BaseAdapter를 상속하여 만들어졌습니다.
아이템이 TextView로 이루어진 경우를 위해 만들어놓은 것이죠.
그러므로 Custom Adapter를 만들 때는 ArrayAdapter 대신에 BaseAdapter를 사용하시기 바랍니다.
[STEP-4] "ListViewBtnAdapter.java" - ArrayAdapter에서 상속한 Custom Adapter 생성
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;
import java.io.ByteArrayOutputStream;
import java.util.ArrayList;
public class ListViewBtnAdapter extends ArrayAdapter implements View.OnClickListener {
// 버튼 클릭 이벤트를 위한 Listener 인터페이스 정의.
public interface ListBtnClickListener {
void onListBtnClick(int position) ;
}
// 생성자로부터 전달된 resource id 값을 저장.
int resourceId ;
// 생성자로부터 전달된 ListBtnClickListener 저장.
private ListBtnClickListener listBtnClickListener ;
// ListViewBtnAdapter 생성자. 마지막에 ListBtnClickListener 추가.
ListViewBtnAdapter(Context context, int resource, ArrayList<ListViewBtnItem> list, ListBtnClickListener clickListener) {
super(context, resource, list) ;
// resource id 값 복사. (super로 전달된 resource를 참조할 방법이 없음.)
this.resourceId = resource ;
this.listBtnClickListener = clickListener ;
}
// 새롭게 만든 Layout을 위한 View를 생성하는 코드
@Override
public View getView(int position, View convertView, ViewGroup parent) {
final int pos = position ;
final Context context = parent.getContext();
// 생성자로부터 저장된 resourceId(listview_btn_item)에 해당하는 Layout을 inflate하여 convertView 참조 획득.
if (convertView == null) {
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
convertView = inflater.inflate(this.resourceId/*R.layout.listview_btn_item*/, parent, false);
}
// 화면에 표시될 View(Layout이 inflate된)로부터 위젯에 대한 참조 획득
final ImageView iconImageView = (ImageView) convertView.findViewById(R.id.imageView1);
final TextView textTextView = (TextView) convertView.findViewById(R.id.textView1);
// Data Set(listViewItemList)에서 position에 위치한 데이터 참조 획득
final ListViewBtnItem listViewItem = (ListViewBtnItem) getItem(position);
// 아이템 내 각 위젯에 데이터 반영
iconImageView.setImageDrawable(listViewItem.getIcon());
textTextView.setText(listViewItem.getText());
// button1 클릭 시 TextView(textView1)의 내용 변경.
Button button1 = (Button) convertView.findViewById(R.id.button1);
button1.setOnClickListener(new Button.OnClickListener() {
public void onClick(View v) {
textTextView.setText(Integer.toString(pos + 1) + "번 아이템 선택.");
}
});
// button2의 TAG에 position값 지정. Adapter를 click listener로 지정.
Button button2 = (Button) convertView.findViewById(R.id.button2);
button2.setTag(position);
button2.setOnClickListener(this);
return convertView;
}
// button2가 눌려졌을 때 실행되는 onClick함수.
public void onClick(View v) {
// ListBtnClickListener(MainActivity)의 onListBtnClick() 함수 호출.
if (this.listBtnClickListener != null) {
this.listBtnClickListener.onListBtnClick((int)v.getTag()) ;
}
}
}
2.6 사용자 데이터 로드.
지난 예제에서 사용자 데이터를 다룰 때, Custom Adapter에 만들어 놓은 addItem()이라는 함수를 직접 호출하여 사용하여 데이터를 추가하였습니다. 이번 예제에서는 데이터 로드 방식에 대한 기초적인 이해를 돕기 위해 MainActivity에 데이터 로드를 위한 loadItemsFromDB라는 함수를 하나 추가하고, 그 함수에서 아이템에 대한 리스트를 구성하는 방식을 사용하겠습니다. 물론 직접 DB를 다루진 않고 리스트에 값을 저장해서 리턴하도록 하겠습니다.
[STEP-5] "MainActivity.java" - ArrayList에 데이터를 로드하는 loadItemsFromDB() 함수 추가.
public boolean loadItemsFromDB(ArrayList<ListViewBtnItem> list) {
ListViewBtnItem item ;
int i ;
if (list == null) {
list = new ArrayList<ListViewBtnItem>() ;
}
// 순서를 위한 i 값을 1로 초기화.
i = 1 ;
// 아이템 생성.
item = new ListViewBtnItem() ;
item.setIcon(ContextCompat.getDrawable(this, R.drawable.ic_account_box_black_36dp)) ;
item.setText(Integer.toString(i) + "번 아이템입니다.") ;
list.add(item) ;
i++ ;
item = new ListViewBtnItem() ;
item.setIcon(ContextCompat.getDrawable(this, R.drawable.ic_account_circle_black_36dp)) ;
item.setText(Integer.toString(i) + "번 아이템입니다.") ;
list.add(item) ;
i++ ;
item = new ListViewBtnItem() ;
item.setIcon(ContextCompat.getDrawable(this, R.drawable.ic_language_black_36dp)) ;
item.setText(Integer.toString(i) + "번 아이템입니다.") ;
list.add(item) ;
i++ ;
item = new ListViewBtnItem() ;
item.setIcon(ContextCompat.getDrawable(this, R.drawable.ic_lightbulb_outline_black_36dp)) ;
item.setText(Integer.toString(i) + "번 아이템입니다.") ;
list.add(item) ;
return true ;
}
2.7 Adapter 생성 후 ListView에 지정.
Adapter를 생성하고 ListView에 지정합니다.
[STEP-6] "MainActivity.java" - onCreate() 함수에서 Adapter 및 ListView 생성
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 코드 계속 ...
ListView listview ;
ListViewBtnAdapter adapter;
ArrayList<ListViewBtnItem> items = new ArrayList<ListViewBtnItem>() ;
// items 로드.
loadItemsFromDB(items) ;
// Adapter 생성
adapter = new ListViewBtnAdapter(this, R.layout.listview_btn_item, items, this) ;
// 리스트뷰 참조 및 Adapter달기
listview = (ListView) findViewById(R.id.listview1);
listview.setAdapter(adapter);
}
2.8 ListView 아이템 클릭 이벤트 처리.
ListView 아이템 클릭 이벤트에 대한 처리를 해줍니다.
[STEP-7] "MainActivity.java" - onCreate()에 아이템 클릭 이벤트 리스너 등록.
@Override
protected void onCreate(Bundle savedInstanceState) {
// 기본 생성 코드 및 ListView와 Adapter 생성 코드
// ...
// 위에서 생성한 listview에 클릭 이벤트 핸들러 정의.
listview.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView parent, View v, int position, long id) {
// TODO : item click
}
}) ;
}
2.9 Button 클릭 이벤트 처리.
예제 작성의 마지막 과정으로 두 번째 Button("TOAST에서 확인") 클릭 시, MainActivity에서 호출될 이벤트 리스너를 구현합니다.
MainActivity에 ListViewBtnAdapter.ListBtnClickListener를 상속하고, onListBtnClick() 함수를 오버라이드하여 MainActivity가 이벤트 리스너 역할을 수행하도록 만듭니다.
[STEP-8] "MainActivity.java" - ListViewBtnAdapter.ListBtnClickListener를 implements.
public class MainActivity extends AppCompatActivity implements ListViewBtnAdapter.ListBtnClickListener {
// 코드 계속 ...
@Override
public void onListBtnClick(int position) {
Toast.makeText(this, Integer.toString(position+1) + " Item is selected..", Toast.LENGTH_SHORT).show() ;
}
}
참고로 아래의 소스 코드는 첫 번째 Button("번호 선택")의 클릭 이벤트 처리와, 두 번째 Button("TOAST에서 확인")의 어댑터 구현 내용에 대한 설명입니다. 이미 [STEP-4]에서 구현한 코드를 포함하고 있으므로, 추가적으로 작성하지 않아도 됩니다.
먼저, 첫 번째 Button("번호 선택")은 클릭 시 ListView 아이템 내에 있는 다른 위젯의 속성을 변경하는 방법은 아래와 같습니다.
[BUTTON-1] "ListViewBtnAdapter.java" - Button 클릭 시 아이템 내에 있는 다른 위젯 속성 변경
// button1 클릭 시 TextView(textView1)의 내용 변경.
Button button1 = (Button) convertView.findViewById(R.id.button1);
button1.setOnClickListener(new Button.OnClickListener() {
public void onClick(View v) {
textTextView.setText(Integer.toString(pos + 1) + "번 아이템 선택.");
}
});
두 번째 Button("TOAST에서 확인")은 클릭 이벤트 발생 시 ListView를 소유한 MainActivity에서 해당 이벤트를 어떻게 전달받는지 알아보는 예제입니다. 정확히 말하자면 Adapter에서 실행되는 Button 이벤트 함수에서 MainActivity에 구현된 이벤트 Listener 함수를 호출하는 형태인데, 자식-부모 간 이벤트 전달 시 사용되는 일반적인 방법입니다.
먼저 이를 위해 부모 Adapter에 Listener를 위한 인터페이스를 작성합니다.
[BUTTON-2.1] "ListViewBtnAdapter.java" - 버튼 클릭 이벤트를 위한 Listener 인터페이스 정의.
public class ListViewBtnAdapter extends ArrayAdapter implements View.OnClickListener {
// 버튼 클릭 이벤트를 위한 Listener 인터페이스 정의.
public interface ListBtnClickListener {
void onListBtnClick(int position) ;
}
// 생성자로부터 전달된 ListBtnClickListener 저장.
private ListBtnClickListener listBtnClickListener ;
// ...코드 계속
}
[BUTTON-2.2] "ListViewBtnAdapter.java" - 생성자에 Listener 추가
public class ListViewBtnAdapter extends ArrayAdapter implements View.OnClickListener {
// 코드 계속...
ListViewBtnAdapter(Context context, int resource, ArrayList<ListViewBtnItem> list, ListBtnClickListener clickListener) {
super(context, resource, list) ;
// resource id 값 복사. (super로 전달된 resource를 참조할 방법이 없음.)
this.resourceId = resource ;
this.listBtnClickListener = clickListener ;
}
// 코드 계속...
}
ListViewBtnAdapter의 button2("TOAST에서 확인") 버튼 클릭 이벤트 핸들러에서 ListBtnClickListener의 onListBtnClick() 함수를 호출합니다.
[BUTTON-2.3] "ListViewBtnAdapter.java" - ListBtnClickListener의 onListBtnClick() 함수 호출
// button2가 눌려졌을 때 실행되는 onClick함수.
public void onClick(View v) {
// ListBtnClickListener(MainActivity)의 onListBtnClick() 함수 호출.
if (this.listBtnClickListener != null) {
this.listBtnClickListener.onListBtnClick((int)v.getTag()) ;
}
}
3. Custom ListView 예제 실행 화면
위의 예제 소스를 순서대로 작성한 뒤 실행하면 다음과 같은 화면이 출력됩니다.
아래 그림은 각 버튼을 클릭했을 때 실행 결과 화면입니다.
4. 참고
- Activity 대신 Fragment 사용 시 에러 발생할 때.
- Fragment에서 ListView 사용하기의 내용을 확인하세요.
- ArrayAdapter에 대한 개발 가이드라인.
- [안드로이드 개발 참조문서 ArrayAdapter 항목]을 참조하세요.
- 입력 이벤트 처리 방법.
- [안드로이드 API가이드 입력 이벤트 항목]을 참조하세요.
.END.