Android/Android ( JAVA )

Android (다섯번째 수업 - Custom ListView 와 adapter pattern)

Bin's. 2018. 8. 25. 11:24



Custom ListView 와 adapter pattern


먼저 ListView 에 대해 알아보자.


보통의 ListView 를 생각하면

연락처 나 메시지 나 목록을 표시해야할때(SMS) 앱 등

필요한 위젯입니다.


Google play (구 버전)



하지만 ListView 는 선택 위젯입니다. 

(일반 위젯이 아니다.)


안드로이드에서는 리스트뷰처럼 여러 개의 아이템 중에 

하나를 선택할 수 있는 위젯들을 특별히 '선택 위젯'이라고 부름


선택위젯은 직접 데이터를 설정 할 수가 없습니다.

선택위젯에 데이터를 설정하기위해 사용하는게

Adapter pattern 이다.


우리는 이 Adapter 에서 만들어주는 getView() 메서드를 이용해 아이템을 표시해준다.



이해를 위한 예시


ListView 를 정의해보자면

Adapter를 사용해 데이터를 표시하는 View 입니다.





만든 CustomListView 예제를 살펴보자면

메모장처럼 제목 과 내용을 적고 저장 해주면

제목을 누르면 내용이 Toast로 띄어주는 그러한 예제이다. 


먼저 Layout을 살펴보자.


일단 ListView 를 띄어줄 MainLayout 에는

간단히 ListView 만 추가해주면 된다

(나는 데이터를 잠깐 저장하기위한 FloatingActionButton 을 사용해줬다.)


MainActivity

<?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:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity">

<ListView
android:id="@+id/listView"
android:layout_width="match_parent"
android:layout_height="match_parent">
</ListView>
<android.support.design.widget.FloatingActionButton
android:layout_width="wrap_content"
android:layout_height="wrap_content" />

</LinearLayout>


이런식으로 MainActivity에 추가 해주면된다.



하지만 우리는 리스트뷰에 표시할 아이템을 위해

item_Layout 을 만들어 줘야한다



item_default_data

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

<TextView
android:id="@+id/titleText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="#000000"
android:textAppearance="@style/Base.TextAppearance.AppCompat.Large"
android:text="제목"
android:layout_margin="10dp"/>
<View
android:layout_width="match_parent"
android:layout_height="3dp"
android:background="@android:color/darker_gray">
</View>

</LinearLayout>

 



이런식으로 우리가 보여줄 item 을위한 

Layout이 구현됬으면 이제부터




ListAdapter 의 코드를 보자.

import android.content.Context;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.TextView;
import android.widget.Toast;

import java.util.List;

public class ListAdapter extends ArrayAdapter<Data> {

private List<Data> dataList;
private Context context;
private int layout;


public ListAdapter(@NonNull Context context, int resource, @NonNull List<Data> objects) {
super(context, resource, objects);
this.dataList = objects;
this.context = context;
this.layout = resource;
}

@NonNull
@Override
public View getView(final int position, @Nullable View convertView, @NonNull ViewGroup parent) {
View view = convertView;
if(view == null){
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
view = inflater.inflate(layout, null);
}

TextView titleText = view.findViewById(R.id.titleText);

Data data = dataList.get(position);


view.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(context, dataList.get(position).getContent(), Toast.LENGTH_SHORT).show();
}
});
titleText.setText(data.getTitle());



return view;
}

public void setDataList(List<Data> dataList){
this.dataList = dataList;
}
}



이 Adapter를 통해 ListView 의

한 item 에 표시될 정보를 설정할수 있습니다.


먼저 생성자를 만들어준다.

그리고 여기서 살펴볼점은

1) LayoutInflater란?


xml 에 정의된 Resource(자원) 들을 View 형태로 변환시켜줍니다.

우리가 보통 Activity를 만들고 자바 코드안에

onCreate() 메서드에 기본으로 추가되는 

setContentView(R.layout.activity_main); 와 같은 원리라고 생각하시면 됩니다.


2) ListView에 view 값이 Null 이라면?

ListView 에 아이템을 표시해줄 getView() 메서드를 보자면

view 값이 Null 이라면 view 가 없게 되닌까

View 가 없으니 inflater 해줘서 할당을 해줍니다.


xml에 정의된 레이아웃을 자바코드로 동적 할당해서 매칭시킨다고 보시면됩니다


그리고 view 의 아이템이 눌리게되면 onClick 이벤트가 실행되

Toast 를 띄어주게 되는데요 Toast 내용은 Content 인 즉 내용이 띄어지고

 titleText.setText(data.getTitle());

즉 제목인 Title 을 ListView 에 텍스트로 띄어줍니다.


먼저 이렇게만 작성한다면 .


    private List<Data> dataList;

이 코드에 Data 가 빨간밑줄 일 것 입니다.

우리는 저 Data class 를 만들어줘야합니다.




그래서 저는


import java.io.Serializable;

public class Data implements Serializable {
private String title;
private String content;

public String getTitle() {
return title;
}

public void setTitle(String title) {
this.title = title;
}

public String getContent() {
return content;
}

public void setContent(String content) {
this.content = content;
}

public Data(String title, String content) {
this.title = title;
this.content = content;
}
}


이런식으로 Data class를 만들어 줬는데요.

이 class 는 아이템에 출력될 데이터를 위한 클래스 입니다.


이렇게 Data를 위한 클래스, Adapter, item_default_data 까지 모두 완성해준다면 


이제 상단 액션바에 Action(추가)인 menu xml 코드를 작성해주자


<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/action"
android:title="추가"
app:showAsAction="always">
</item>
</menu>

이런식으로 작성하게 되면 액션바에 추가라는 이름이 적히는데요

app:showAsAction="always" 라는 속성은

항상 보이게 설정해줍니다


이 코드는 이예제의 WriteActivity 에 넘어가서 작성할수 있도록 버튼같은 역활을합니다.



이제 WriteActivity 를 봅시다.


<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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"
tools:context=".WriteActivity"
android:orientation="vertical"
android:weightSum="360">

<EditText
android:id="@+id/titleEditText"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="40"
android:hint="제목"
android:layout_margin="10dp"/>
<EditText
android:id="@+id/contentEditText"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="300"
android:layout_margin="10dp"
android:hint="내용"
android:lines="10"/>

<android.support.design.widget.FloatingActionButton
android:id="@+id/saveFab"
android:src="@drawable/ic_save_black_24dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginRight="30dp"
android:layout_gravity="right"/>


</LinearLayout>

간단하게 

제목을 적을 EditText 와 내용을적을 EditText랑

데이터를 저장해줄 FloatingActionButton

이 구성되 있습니다.



이제 본격적으로 




WriteActivity의 xml 코드를 보자면


import android.content.Intent;
import android.support.design.widget.FloatingActionButton;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.EditText;
import android.widget.Toast;

public class WriteActivity extends AppCompatActivity {

private EditText titleEditText;
private EditText contentEditText;
private FloatingActionButton saveFab;


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

init();
setListeners();

}

private void init(){

titleEditText = findViewById(R.id.titleEditText);
contentEditText = findViewById(R.id.contentEditText);
saveFab = findViewById(R.id.saveFab);

}

private void setListeners(){
saveFab.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String title = titleEditText.getText().toString();
String content = contentEditText.getText().toString();
if(!title.equals("") && !content.equals("")){
Data data = new Data(titleEditText.getText().toString(), contentEditText.getText().toString());
Toast.makeText(getApplicationContext(), "성공적으로 작성하셨습니다.", Toast.LENGTH_SHORT).show();
Intent intent = new Intent();
intent.putExtra("data", data);
setResult(RESULT_OK,intent);
finish();

}
}
});
}
}

간단하게 해석하자면 각각의 EditText의 id를 찾아주고

saveFab은 FloatingActionButton의 id 이다.


saveFab을 눌르면 onClick 이벤트가 실행된다.

내용은 titleEditText 내용을 가져와String 형태로 title 에 담고

contentEditText의 내용도 가져와 String 형태로 Content에 담습니다.


if문 은 title 과 content 의 값이 있을때 실행되는데요

먼저 Data class의 객체를 만들어줘 titleEditText 내용, contentEditText의 내용

을 가져와 data 에 저장해줍니다.


그뒤에 Toast로 "성공적으로 작성하셨습니다." 라는 Toast를 띄어줍니다.


그뒤에 Intent 객체를 만들어주고

.putExtra() 로 "data" 라는 키로 data를 넘겨줍니다.


그리고

setResult(RESULT_OK,intent);
finish();

이코드로 Activity를 종료해줍니다.




이제 마지막으로 data값을 ListView 에 띄어주는 MainActivity.java code 를보자.


        import android.content.Intent;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.ListView;

import java.util.ArrayList;
import java.util.List;

public class MainActivity extends AppCompatActivity {

private List<Data> dataList;
private ListView listView;
private ListAdapter listAdapter;

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

init();
setListView();
}

private void setListView(){
listAdapter = new ListAdapter(getApplicationContext(), R.layout.item_default_data, dataList);
listView.setAdapter(listAdapter);
}

private void init(){
dataList = new ArrayList<>();
listView = findViewById(R.id.listView);

}

public void changeData(Data data){
dataList.add(data);
}



@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.menu, menu);
return true;
}

@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()){
case R.id.action:
Intent intent = new Intent(this, WriteActivity.class);
startActivityForResult(new Intent(this, WriteActivity.class), 3000);
return true;
default:
return false;
}
}

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if(resultCode == RESULT_OK && requestCode == 3000){
Log.d("asdf", "asdf");
Data receiveData = (Data) data.getSerializableExtra("data");
dataList.add(receiveData);
setListView();
}
}
}

먼저 onCreate 에 호출된 init(); 과 setListView(); 를 보면

init(); 은 dataList 에 변수에 arrayList 형식으로 데이터를 저장합니다.

그리고 listView 의 id 를 listView에 저장해줍니다.


그리고 setListView();를 보면 

Adapter 의 객체를 만들어주는데 Layout은 아까 만들어준

item_default_data 을 사용해주고 내용은 datalist 입니다.

그리고 listView.setAdapter(listAdapter); 

Adapter의 값을 정해주고


이제 다른 코드를 보자면

public void changeData(Data data){
dataList.add(data);
}

를 보면 매개변수는 data를 받아오고 

dataList.add(data); 를 이용해 데이터를 더해준다.


그리고


@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.menu, menu);
return true;
}


보게 되면 Inflater 를 이용해

xml 로 정의된 menu 를 실제 객체화를 해줍니다.

이 코드를 써준이유는

미리 xml 을 만들어준 뒤 java code 에서

inflater 을 활용해 바로 view 를 생성할 수있기 때문이다.


그리고

 @Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()){
case R.id.action:
Intent intent = new Intent(this, WriteActivity.class);
startActivityForResult(new Intent(this, WriteActivity.class), 3000);
return true;
default:
return false;
}
}


이 코드를 보자

switch 문을 사용했다. 먼저 item 의 id 를 얻어 

id 가 action 인 item 은 아까 추가해준

item_default_data 이 xml 이다.


item 의 id 가 action 이면 추가 버튼을 눌르게된다면 WriteActivity 로 intent 로 넘어가고

startActivityForResult() 를 이용해 WriteActivity 를 시작하고 그 Activity로부터 결과를 수신할 수도 있습니다.

값은 3000으로 지정해줬습니다.

만약 id 가 action 아니면 false를 return 해줍니다.


그리고 마지막으로


@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if(resultCode == RESULT_OK && requestCode == 3000){
Log.d("asdf", "asdf");
Data receiveData = (Data) data.getSerializableExtra("data");
dataList.add(receiveData);
setListView();
}
}
}

이 코드를 해석하자면 WriteActivity의 data를 받는 메서드 이다.

처리된 결과 코드 resultCode 가 RESULT_OK  이거나 requestCode 가 3000 인 이유는 위에


startActivityForResult(new Intent(this, WriteActivity.class), 3000);

이코드를 이용해  WriteActivity 를 시작하고 그 Activity로부터 결과를 수신 했습니다

 

Data receiveData = (Data) data.getSerializableExtra("data");

이 코드로 아까 putExtra() 의 키값인 data로 넘겨준 data를 받는다.

그리고 dataList.add(receiveData); 로 data를 더해준다.

그리고 마지막으로 

setListView(); 를 이용해세 

private void setListView(){
listAdapter = new ListAdapter(getApplicationContext(), R.layout.item_default_data, dataList);
listView.setAdapter(listAdapter);
}

을 호출해 준다.

이렇게 되면 ListView가 띄어지는걸 볼수있다.


여기까지 adapter pattern 을 이용한 CustomListView 사용법 이였다.


굳!