Android/Android ( JAVA )

Android (여섯번째 수업 - RecyclerView)

Bin's. 2018. 9. 17. 10:07


RecyclerView


오늘은 RecyclerView 에 대해서 알아보자 


먼저 RecyclerView 란? 

Android 5.0 에서 처음 소개되었습니다.
Support-Library-v7 에 포함 되었습니다
RecyclerView 는 ListView 의 장/단점을 보완한 고급 위젯입니다.

그렇다면 이 위젯은 어떻게 사용할까요?
이 위젯 을 사용하기 위해서 dependencies 에 추가 해줘야합니다.
그리고 또한 appcompat 버전과 recyclerview의 버전이 같아야합니다.

먼저 RecyclerView 에 대해 간략히 보자면

이렇게 ListView 와 비슷합니다.

제목과 내용을 적어 제목을 눌르면 내용을 Toast로 출력해주는 예제입니다

망고를 눌렀을때 mango라는 토스트를 띄어줍니다.


이제 이 RecyclerView 를 사용하기 위해 먼저 Library를 추가 해줍니다.

이런식으로 말이죠.

dependencies {
    implementation 'com.android.support:recyclerview-v7:27.1.1'
}





이제 사용법에 대해 알아봅시다.


xml code 에는


main.xml

<?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">

<android.support.v7.widget.RecyclerView
android:id="@+id/RecyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent">
</android.support.v7.widget.RecyclerView>

<android.support.design.widget.FloatingActionButton
android:layout_width="wrap_content"
android:layout_height="wrap_content" />

</LinearLayout>

이런식으로 추가를 해줬습니다.

RecyclerView와 추가하기위한 FloatingActionButton을 적어줬습니다.


RecyclerView 를 추가해줬으니 RecyclerView 에 띄어줄 iteml 을 만들어 줘야하는데요

Layout 폴더에 Layout resource file 을 추가 해줍니다



item_default_data.xml

<?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 은 RecyclerView에 담을 하나 하나의 아이템들입니다.

이미지나 버튼등 개발자의 필요에따라 추가/수정 하면 됩니다.

저는 여기서 간단히 TextView 를 사용해 title 을 보여주는게 목적이기때문에 

TextView를 사용해줬고 View 태그를 사용해줘서 line 구분을 해주었다.



이제 data를 담아줄 Data클래스를 만들어 줍시다.


Data.class

package com.example.qlsl7.test0816;

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;
}

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

저는 제목과 내용을 사용 할 것 이니

Title과 content 의 getter/setter 만들어 줬습니다.

이 class 는 RecyclerView 에 출력될 값들입니다.


아까 간략히 본 RecyclerView 예제는

제목과 내용을 적어 제목을 눌르면 내용을 Toast로 출력해주는 예제입니다.

그러니 이제 만들어줘야하는건 내용을 적을곳을 만들어줍시다.


Write.xml

<?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>


xml code를 간단히 설명하자면 

Title 과 Content 를 적어줄 

EditText를 만들어주고 이제 저장버튼을 대신해줄

FloatingActionButton 을 만들어 줍시다.


그럼 이제 이 java code 를 보자면


wirteActivity.class

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();

}
}
});
}
}

이 코드를 해석해보면 

사용될 위젯의 id를 각각의 변수에 담아줍니다.

그리고 svaeFab = FloatingActionButton 을 눌렀을때

onClick 이벤트가 실행되면서

title 과 content 에 EditText에서 적은내용을 문자열로 저장을해줍니다.


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

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

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


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


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

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

setResult(RESULT_OK,intent);
finish();

이코드로 종료를 시켜줍니다.



그럼 이제 적은 data를 RecyclerView에 뿌려줄 Adepter 를 만들어줍시다.


RecyclerAdapter.class


package com.example.qlsl7.test0816;

import android.content.Context;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v7.widget.RecyclerView;
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 RecyclerAdapter extends RecyclerView.Adapter<RecyclerAdapter.ViewHolder> {

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


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


@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View view = inflater.inflate(layout, null);
return new ViewHolder(view);
}

@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
data = dataList.get(position);


holder.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(context, data.getContent(), Toast.LENGTH_SHORT).show();
}
});
holder.tiTextView.setText(data.getTitle());
}

@Override
public int getItemCount() {
return dataList.size();
}

class ViewHolder extends RecyclerView.ViewHolder{
TextView tiTextView;
ViewHolder(View itemView){
super(itemView);
tiTextView = itemView.findViewById(R.id.titleText);
}

}
}

먼저 ListView 와 RecyclerView 의 차이점 이라면

ListView 에서는 getView 형식으로 data를 뿌려줬다면.

RecyclerView 에서는 ViewHolder 라는 디자인 패턴을 이용해

View 객체를 ViewHolder 에 홀드 시키는 것입니다.

이러한 원리를 이용해 data를 RecyclerView 에 뿌려주는데요.


이 코드를 보면 각각의 타입의 변수를 선언해주고  

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

생성자로 값을 받아옵니다 


@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View view = inflater.inflate(layout, null);
return new ViewHolder(view);
}

이 코드는 RecyclerView 에 띄어줄 View 을 inflater 해주는 코드입니다 

쉽게 설명하면 RecyclerView 에 사용될 ViewHoder 를 생성하는 코드입니다.

  그리고

@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
data = dataList.get(position);


holder.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(context, data.getContent(), Toast.LENGTH_SHORT).show();
}
});
holder.tiTextView.setText(data.getTitle());
}

이 코드는 매개변수로 holder 와 position 을 받고

data 변수에 dataList.get(position) 의 값을 대입해줍니다.

그리고 hoder 안에 있는 itemView 가 눌렸을경우에

onClick 이벤트가 실행되어

Toast 메세지를 띄어주는데요 내용은 context의 내용을 짧게 띄어줍니다.

쉽게 설명하자면 ViewHolder에 데이터를 넣는 작업을 수행을 하는 code 입니다.

그리고 


class ViewHolder extends RecyclerView.ViewHolder{
TextView tiTextView;
ViewHolder(View itemView){
super(itemView);
tiTextView = itemView.findViewById(R.id.titleText);
}

}

Inner class를 만들어줍니다 이 class에 RecyclerView.ViewHolder를 상속해줍니다

그리고 TextView 변수를 만들어주고

생성자를 만들어줍니다 이 생성자의 매개변수는 View 타입의 itemView 입니다.

그리고 super() 메서드를 이용해 매개변수에 접근합니다

그리고 tiTextView 에 findViewById() 를 이용해 

titleText의 id 를 넣어줍니다.

그리고

holder.tiTextView.setText(data.getTitle());

이제 이코드를 해석할수있는데요 이코드를 해석하자면

tiTextView에 data.class에 저장된 Title의 값을 반환해서 세팅을해줍니다.

@Override
public int getItemCount() {
return dataList.size();
}

이 코드 는 사용하지않지만 오버라이드 해줘야하기때문에

dataList.size() = data 갯수를 반환만 시켜줍니다.


여기서 중요한점은 RecyclerView를 사용할때 ViewHolder라는 디자인패턴을 사용했습니다.

이제 정확히 ViewHolder 패턴에 대해 알아봅시다.


ViewHolder pattern 이란?


각 View 들을 보관하는 홀더 객체라고 볼수있습니다.


왜 이런 디자인 패턴이 등장했을까

바로 ListView 에서 살펴볼수있습니다.

RecyclerView 는 ListView 의 장/단점을 보완한 고급위젯이라고 위에서 설명했습니다.


ListView 에서는 스크롤을 할시 findViewById()를 호출을 해줍니다.

Adapter가 inflate된 View를 반환할때도 findViewById()를 호출해주기떄문에

리스트에 표시해야할 데이터가 많을경우 성능이 저하하는 그러한 단점을 보완하기 위해 나온게

ViewHolder 패턴입니다.


ViewHolder 객체는 findViewById 를 반복 호출을 않해도 RecyclerView 를 스크롤 했을시

즉시 사용 가능합니다.


자 이제 mainActivity의 code를 봅시다


mainActivity.class


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

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

public class MainActivity extends AppCompatActivity {

private List<Data> dataList;
private RecyclerView recyclerView;
private RecyclerAdapter recyclerAdapter;

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

init();
setRecyclerView();
}

private void setRecyclerView(){
LinearLayoutManager layoutManager = new LinearLayoutManager(getApplicationContext());
recyclerView.setLayoutManager(layoutManager);
recyclerAdapter = new RecyclerAdapter(getApplicationContext(), R.layout.item_default_data, dataList);
recyclerView.setAdapter(recyclerAdapter);
}

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

}

@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);
setRecyclerView();
}
}
}

mainActivity 클래스파일을 설명하자면 

일단 dataList 객체와 Recycler의 Adapter 와 View 변수를 만들어줍니다.


그리고  onCreate안에 호출해준 init()메서드와 RecyclerView를 위한 setRecyclerView()

메서드를 만들어줍니다.


먼저 setRecyclerView() 메소드를 보자면 LayoutManager 객체를 만들어줍니다.

제가 사용하는 xml 의 Layout은 LinearLayout 이여서 저는

LinearLayoutManager 객체를 만들어줬습니다.


그리고 지정된 레이아웃매니저를 RecyclerView에 Set 해줍니다

그뒤에 RecyclerAdapter에 itemlayout 을 셋팅해주고 recyclerView 에 adapter를 Set 해줍니다.


그리고 init 메소드를 보면

datalist를 ArrayList로 바꿔줍니다.

그리고 RecyclerView 의 id를 찾아와 recyclerView에 저장해줍니다


그리고 저는 WriteActivity.xml 로 가기위해 액션바 에 추가버튼을 만들어 줬는데요

일단 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>

이런식으로 추가 버튼을 만들어주기 전에 menu를 만들어 id 와 title을 정해줬습니다

app:showAsAction 속성은 "always" 는 계속 Activity에 보입니다.

이제 onCreateOptionsMenu 를 오버라이드 해줍시다

매개변수는 menu 가 들어갈거고 화면에 띄어주기위해서 우리는

inflate 해줘서 xml 에 정의된 Resource 를 View 형태로 변환시켜줍니다.


그뒤에 onOptionsItemSelected 도 오버라이드 시켜줍시다.

switch 문을 이용해 id가 acton 인 menu 일때 

Intent 객체를 만드는데 Intent 는 WriteActivity 로 인텐트를해줍니다.


그리고 

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

이 코드를 설명하자면 밑에 WriteActivity를 단순히 시작하는게 아니라 requestCode 를 확인하기 위해 3000이라는 상수를 지정해줬습니다.

@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);
setRecyclerView();
}
}

그리고 마지막으로 이코드를 설명하겠습니다

onActivityResult는 오버라이드 해줘야하는데요 

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

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

requestCode를 확인하기 위해 3000 으로 지정해줬기때문입니다


그리고 

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

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

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


그리고 dataList에 .add() 메소드를 이용해 receiveData 에있는 data를 datalist에 더해줍니다

그리고 setRecyclerView(); 메서드를 실행시켜주게 됩니다.


이렇게되면 RecyclerView 가 정상작동하게 됩니다!

여기까지 ViewHolder 패턴을 이용한 RecyclerView 예제였습니다.



ㅠㅠ!