딥 러닝 – RecyclerView, ViewHolder, Adapter를 사용한 바로 가기 앱 0209

  • by


1.DirectAdapter

package com.yhc.app_0209;

import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.view.LayoutInflater;
import android.view.ViewGroup;

import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;

import java.lang.reflect.Array;
import java.util.ArrayList;

public class DirectAdapter extends RecyclerView.Adapter<DirectHolder>{
    private ArrayList<DirectVO> datas; //RV에 뿌릴 데이터
    private Context context; //Activity 에서 보내줄 화면 정보!
public DirectAdapter(ArrayList<DirectVO> datas, Context context) { this.datas = datas; this.context = context; } @NonNull @Override public DirectHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { return new DirectHolder(LayoutInflater.from(parent.getContext()) .inflate(R.layout.templete,parent,false)); } @Override public void onBindViewHolder(@NonNull DirectHolder holder, int position) { // holder => 이전에 만들어진 View들이 저장된 ViewHolder // position => 위치 holder.tv_title.setText(datas.get(position).getTitle()); holder.tv_address.setText(datas.get(position).getAddress()); holder.btn_go.setOnClickListener(V->{ //datas.get(position).getAddress() Intent it_url= new Intent(Intent.ACTION_VIEW, Uri.parse(holder.tv_address.getText().toString())); //새로운 테스크 만들어야됨 메인이 아니고 어뎁에서 실행해서 it_url.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); context.startActivity(it_url); }); } @Override public int getItemCount() { return datas.size(); // 꼭 고쳐야됨 } }

이 코드는 RecyclerView를 설정하는 Adapter 구현입니다.

RecyclerView는 ViewHolder 패턴을 사용합니다.

  • onCreateViewHolder(): ViewHolder 객체 만들기
  • onBindViewHolder(): 생성된 ViewHolder 객체에 데이터 바인딩
  • getItemCount(): 데이터 수를 반환합니다.

이 코드는 onCreateViewHolder() 메서드를 구현하고 있으며 ViewHolder 를 생성하기 위해 templete 레이아웃 파일을 inflate 하고 그것을 이용하여 DirectHolder 객체를 생성하여 반환합니다.

onBindViewHolder() 메서드는 DirectHolder 객체를 사용하여 데이터를 ViewHolder에 바인딩합니다.

또한 해당 ViewHolder 버튼에 클릭 이벤트를 등록하고 있으며,

클릭시에 Uri.parse() 를 이용해 Intent 객체를 작성해, 그것을 실행하고 있습니다.

getItemCount() 메서드는 데이터 수를 반환합니다.

데이터를 ViewHolder에 바인딩하는 코드는 holder의 tv_title, tv_address, btn_go 등의 View 객체에,

데이터 클래스 (DirectVO)의 getter 메서드를 사용하여 데이터를 할당합니다.

이 코드는 리사이클 뷰를 이용하여 주소록 정보를 화면에 출력하는 기능을 구현하고 있습니다.

2.DirectHolder

package com.yhc.app_0209;

import android.view.View;
import android.widget.Button;
import android.widget.TextView;

import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;

public class DirectHolder extends RecyclerView.ViewHolder {
    // 재사용할 View(TextView *2, Button) 저장하는 용도
    // 상위클래스에 생성자가 설계되어 있다면 하위클래스는 반드시 생성자 구현해야함!
TextView tv_title; TextView tv_address; Button btn_go; public DirectHolder(@NonNull View itemView) { super(itemView); //상위클래스의 생성자를 호출하는 명령!
반드시 생성자 첫째줄!
tv_title = itemView.findViewById(R.id.tv_title); tv_address = itemView.findViewById(R.id.tv_address); btn_go = itemView.findViewById(R.id.btn_go); } }

이 코드는 RecyclerView를 사용하여 화면에 표시하는 데이터를 관리하는 Adapter 클래스와 각 데이터를 표시하는 ViewHolder 클래스입니다.

좋은 점:

  • RecyclerView를 사용하면 화면에 표시되는 데이터를 효율적으로 관리하고 화면에 표시되는 항목을 효율적으로 재사용할 수 있습니다.

  • ViewHolder 클래스를 구현하여 각 항목을 표시하는 뷰를 재사용하고 findViewById를 통해 뷰를 검색하는 작업을 최소화할 수 있습니다.

개선할 점:

  • Intent.FLAG_ACTIVITY_NEW_TASK를 사용하여 새 작업을 만들고 웹 페이지를 표시하는 것은 좋은 방법이 아닙니다.


    FLAG_ACTIVITY_NEW_TASK는 활동이 다른 태스크에서 실행되도록 지시합니다.

    이렇게 하면 앱의 사용자 환경을 방해할 수 있습니다.


    대신 현재 작업에서 수행할 수 있는 FLAG_ACTIVITY_CLEAR_TASK 또는 FLAG_ACTIVITY_TASK_ON_HOME과 함께 사용하는 것이 좋습니다.

  • ViewHolder 클래스에서는 TextView 및 Button 액세스 제한을 private 로 설정하여 캡슐화를 유지하고 외부로부터의 액세스를 방지하는 것이 좋습니다.


    이러한 경우는, 데이터의 설정이나 이벤트 청취자의 설정등의 조작을 ViewHolder 클래스내에서 처리하도록 구현할 필요가 있습니다.

  • getItemCount() 메소드는 데이터의 사이즈를 돌려주도록(듯이) 작성하고 있습니다만, 이것은 데이터 사이즈가 0 의 경우에 NullPointerException 가 발생할 가능성이 있습니다.

    데이터가 null이면 0을 반환하도록 변경하는 것이 좋습니다.

3.DirectVO

package com.yhc.app_0209;

public class DirectVO {
    // VO(DTO) 구성요소
    // 1. 필드 - 저장하고 싶은 변수들
    private String title;
    private String address;
    // 2. 생성자 메소드 - 필드 초기화 용도
    public DirectVO(String title, String address) {
        this.title = title;
        this.address = address;
    }
    // 3. get (확인용), set(수정용)

    public String getTitle() {
        return title;
    }

    public String getAddress() {
        return address;
    }

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

    public void setAddress(String address) {
        this.address = address;
    }
    // 4. toString() - 객체에 저장된 모든 필드들을 문자열로 리턴~


    @Override
    public String toString() {
        final StringBuffer sb = new StringBuffer("DirectVO{");
        sb.append("title="").append(title).append("\'');
        sb.append(", address="").append(address).append("\'');
        sb.append('}');
        return sb.toString();
        // 결론!
String을 + 연산 할 때는(+) 보다 Buffer 쓰는게 // 메모리 소모도 적고 더 빠르다!
} }

이 코드는 DirectVO라는 클래스에서 VO(DTO)를 구현한 코드입니다.

코멘트를 보면서 내용을 보자.

  1. 필드 – 저장할 변수
private String title; private String address;
title과 address 변수가 선언되어 있습니다.

  1. 생성자 메서드 – 필드 초기화의 용도
public DirectVO(String title, String address) { this.title = title; this.address = address; }
title과 address를 받아 초기화하는 생성자 메소드입니다.

  1. get(확인용), set(수정용)
public String getTitle() {
    return title;
}

public String getAddress() {
    return address;
}

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

public void setAddress(String address) {
    this.address = address;
}

title 및 address 필드의 getter 및 setter 메소드가 구현되었습니다.

  1. toString() – 객체에 포함되어 있는 모든 필드를 캐릭터 라인으로서 돌려줍니다.

@Override
public String toString() {
    final StringBuffer sb = new StringBuffer("DirectVO{");
    sb.append("title="").append(title).append("\'');
    sb.append(", address="").append(address).append("\'');
    sb.append('}');
    return sb.toString();
}

toString() 메서드를 재정의하고 개체에 저장된 필드 값을 문자열로 반환하도록 구현되었습니다.

정리해 보면, DirectVO 클래스는 타이틀(title)과 주소(address)를 포함하기 위한 클래스로 구현되고 있습니다.

필드의 getter/setter 와 toString() 메소드도 구현되고 있습니다.

4.MainActivity

package com.yhc.app_0209;

import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;

import android.os.Bundle;

import java.util.ArrayList;

public class MainActivity extends AppCompatActivity {
    // 바로가기 데이터 만들기!
// => 데이터 하나에는 제목(String), 주소(String) // => DirectVO(valueObject) => 사용자(개발자) 정의 자료형 // DirectVO 를 저장할 ArrayList 생성 RecyclerView rv; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); //2. DirectVO 객체 생성해서 ArrayList에 3개 저장~ // - 제목과 주소는 여러분 하고 싶은대로 ArrayList<DirectVO> directList = new ArrayList<>(); //new DirectVO("네이버", "naver.com" 이렇게하면 객체 두개가 저장 된다 directList.add(new DirectVO("네이버", "https://www.naver.com")); directList.add(new DirectVO("유튜브", "https://www.youtube.com")); directList.add(new DirectVO("구글", "https://www.google.com")); rv = findViewById(R.id.RecyclerView); DirectAdapter adapter = new DirectAdapter(directList,getApplicationContext()); rv.setAdapter(adapter); // 리스트 형태로 할건지 그리드 형태로 할건지 빼먹으면 안됨!
!
!
!
!
!
!
!
!
!
!
rv.setLayoutManager(new LinearLayoutManager(this)); } }​

MainActivity.java

  • onCreate() 메서드에서 RecyclerView 객체를 참조합니다.

  • 세 개의 DirectVO 객체를 만들고 DirectAdapter에 전달하여 RecyclerView에 데이터를 출력합니다.

  • RecyclerView의 레이아웃 관리자로 LinearLayoutManager를 사용합니다.

DirectVO.java

  • 제목과 주소를 저장하는 title 및 address 필드가 있습니다.

  • 각 필드의 값을 반환하는 게터와 각 필드의 값을 변경하는 세터가 있습니다.

  • 오브젝트에 포함되고 있는 필드치를 캐릭터 라인으로서 돌려주는 toString() 메소드가 있습니다.

DirectHolder.java

  • 재이용하는 View(TextView *2, Button) 를 포함하는 클래스입니다.

DirectAdapter.java

  • RecyclerView.Adapter 를 상속해 구현한 클래스입니다.

  • 데이터는 ArrayList 유형으로 저장합니다.

  • onCreateViewHolder() 메서드는 ViewHolder 객체를 생성합니다.

  • onBindViewHolder() 메서드는 ViewHolder 객체를 사용하여 데이터를 출력합니다.

  • getItemCount() 메소드는 데이터의 수를 리턴합니다.

전반적으로 구현 내용은 완벽하지 않습니다.

일부 구현 내용이 부족하거나 수정이 필요한 부분도 있습니다.

예를 들어, DirectVO 클래스에는 getter와 setter가 있지만,

해당 클래스를 사용하는 코드는 setter를 사용하지 않습니다.

따라서 세터를 제거하고 필요에 따라 생성자를 통해 값을 설정하도록 코드를 변경하는 것이 좋습니다.

그리고 버튼을 클릭하면 인터넷 페이지가 열리는 코드로

Intent.FLAG_ACTIVITY_NEW_TASK 플래그를 설정했지만,

이 플래그는 활동을 새 태스크로 실행하도록 지시하는 플래그입니다.

따라서 인터넷 페이지를 열려면 이 플래그를 설정할 필요가 없습니다.

이것을 삭제하기만 하면 됩니다.

5.activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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=".MainActivity">

    <Button
        android:id="@+id/button"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:text="추가"
        android:textSize="20sp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.0"
        app:layout_constraintStart_toStartOf="parent" />

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/RecyclerView"
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:layout_constraintBottom_toTopOf="@+id/button"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

이 XML 파일은 간단한 레이아웃으로 구성됩니다.

Button과 RecyclerView가 포함되어 있습니다.

  • Button: 사용자가 목록에 새 항목을 추가할 때 사용하는 버튼입니다.

  • RecyclerView : 목록의 각 항목을 표시하는 뷰입니다.

레이아웃은 ConstraintLayout으로 구성되며,

버튼은 하단에 있고 RecyclerView는 그 위에 있습니다.

RecyclerView는 상하좌우 모두 화면에 가득 채워져 있습니다.

이 레이아웃은 MainActivity와 연관되어 있으며,

DirectAdapter를 사용하여 RecyclerView에 데이터를 표시합니다.

전반적으로 코드는 큰 문제를 볼 수 없습니다.

그러나 Button과 RecyclerView 사이의 간격을 넓게 설정하는 것이 UI에서 더 나은 것 같습니다.



6.add.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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">

    <EditText
        android:id="@+id/titletxt"
        android:layout_width="206dp"
        android:layout_height="72dp"
        android:layout_marginTop="171dp"
        android:ems="10"
        android:hint="제목을 입력하시오"
        android:inputType="textPersonName"
        android:textSize="24sp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <EditText
        android:id="@+id/editTextTextPersonName2"
        android:layout_width="206dp"
        android:layout_height="72dp"
        android:layout_marginTop="52dp"
        android:ems="10"
        android:hint="제목을 입력하시오"
        android:inputType="textPersonName"
        android:textSize="24sp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.497"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/titletxt" />
</androidx.constraintlayout.widget.ConstraintLayout>

이 코드는 제목과 내용을 입력하는 두 개의 EditText가 있는 레이아웃 파일입니다.

  • titletxt: 제목을 받는 EditText
  • editTextTextPersonName2 : 콘텐츠 받기 EditText

레이아웃 파일의 크기나 레이아웃 구성 등에는 문제가 없는 것 같습니다.

그러나 두 EditText 모두 hint 값이 ‘제목을 입력하세요’와 동일하게 지정되므로

어떤 EditText가 제목인지, 어떤 EditText가 내용인지는 구별하기 어려울 수 있습니다.

이 경우 EditText의 왼쪽에 레이블을 지정하여 구분할 수 있습니다.



7.templete.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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="wrap_content">

    <TextView
        android:id="@+id/tv_title"
        android:layout_width="83dp"
        android:layout_height="29dp"
        android:layout_marginStart="17dp"
        android:layout_marginTop="25dp"
        android:text="제목입니다"
        android:textColor="#D3B0B0"
        android:textSize="16sp"
        android:textStyle="bold"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/tv_address"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="32dp"
        android:text="주소입니다"
        android:textStyle="bold|italic"
        app:layout_constraintStart_toStartOf="@+id/tv_title"
        app:layout_constraintTop_toBottomOf="@+id/tv_title" />

    <Button
        android:id="@+id/btn_go"
        android:layout_width="92dp"
        android:layout_height="50dp"
        android:layout_marginEnd="41dp"
        android:backgroundTint="#B57E7E"
        android:paddingLeft="2dp"
        android:paddingTop="2dp"
        android:paddingRight="2dp"
        android:paddingBottom="2dp"
        android:text="바로가기"
        android:textSize="16sp"
        android:textStyle="bold"
        app:iconPadding="1dp"
        app:layout_constraintBottom_toBottomOf="@+id/tv_address"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toTopOf="@+id/tv_title" />
</androidx.constraintlayout.widget.ConstraintLayout>

MainActivity 클래스와 activity_main 레이아웃 파일,

DirectVO 클래스, DirectHolder 클래스, DirectAdapter 클래스, 템플릿 레이아웃 파일이 있습니다.

activity_main 레이아웃 파일에는 RecyclerView와 추가 버튼이 있으며,

templete 레이아웃 파일에는 각 항목을 나타내는 뷰가 있습니다.

DirectVO 클래스는, RecyclerView 의 각 아이템에 들어가는 데이터를 포함하는 클래스입니다.

두 개의 String 필드와 그것에 대한 getter와 setter 메서드, toString() 메서드가 있습니다.

DirectHolder 클래스는, 각 아이템을 나타내는 뷰를 가지는 ViewHolder 클래스입니다.

templete 레이아웃 파일에서 TextView와 Button을 바인딩합니다.

DirectAdapter 클래스는 RecyclerView와 DirectVO 데이터를 연결하는 어댑터 클래스입니다.

onCreateViewHolder() 및 onBindViewHolder() 메서드를 구현합니다.

MainActivity 클래스는 RecyclerView를 초기화하고 DirectVO 인스턴스를 ArrayList에 추가하고,

DirectAdapter를 만들고 RecyclerView로 설정합니다.

각 레이아웃 파일과 클래스는 거의 일관된 명명 규칙을 사용하며,

메소드와 필드의 용도도 명확하게 구별되고 이해하기 쉬운 코드입니다.

그러나 템플릿 레이아웃 파일의 Button과 TextView ID는 약간 혼란스러울 수 있습니다.

또한 일부 레이아웃 파일에서는 layout_height 속성의 값이 0dp로 설정되어 있으며,

이 부분은 어떤 이유로 설정되었는지 파악해야 합니다.

파일 다운링크:https://github.com/yoonhyochang/Android