본문 바로가기
Study Log/Software Engineering

데이그램 따라하기 - 2. RecyclerView 다루기

by HZie 2020. 8. 9.

[하단 네비게이션바 만들기 (1) RecyclerView 다루기]

공부한 것 정리

ButterKnife 라이브러리: 뷰를 보다 편리하게 바인딩할 때 사용된다.
    - 라이브러리를 사용하기 위해서는 build.gradle(Module:app)파일에 dependency를 추가해야 한다.
      (추가한 후 sync를 해야 적용된다.)

implementation 'com.jakewharton:butterknife:8.6.0'

    - gradle에 추가한 후 사용하려는 액티비티와 버터나이프를 바인드해준다.
      (레이아웃과 java파일을 연결해주는 setContentView( )함수 아래에서 바인드해야 한다.)

    // 이 예시의 경우 MainActivity.java 아래에 있는 onCreate함수이다
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ButterKnife.bind(this);
    }

    - 그러면 다음 코드와 같이 OnClick을 원하는대로 assign해주는 등 바인딩이 훨씬 쉬워진다.

    @OnClick(R.id.main_month)
    public void onClickMonth(){
	// on click codes goes here
    }

관련 문서: http://jakewharton.github.io/butterknife/

!!! androidx 부터는 버터나이프가 지원되지 않는다. 대신 구글에서 제공하는 ViewBinding을 이용해야 한다. !!!
ViewBinding: Android Studio 3.6 Canary 11 이상에서 사용할 수 있는 뷰 바인드 기능
View Binding 사용하기
  1. build.gradle (Module)에서 다음과 같이 설정한다. (gradle을 바꾼 후에는 반드시 sync할 것)

android{
	...
	viewBinding true
    ...
}

// 만약 sync시 오류가 발생한다면
android{
	buildFeatures{
    	viewBinding true
    }
}

  2. 만약 특정 레이아웃의 바인딩을 원하지 않는다면 다음과 같이 설정한다.

tools:viewBindingIgnore="true"

  3. java 코드에서 다음과 같이 변수를 생성하는데,
    클래스는 사용할 레이아웃에서 언더바를 없애고 뒤에 바인딩을 붙인다.
    ex 1) activity_main.xml을 바인딩할 경우, 클래스 이름은 ActivityMainBinding
    ex 2) list_item_year.xml을 바인딩할 경우, 클래스 이름은 ListItemYearBinding
 
4. onCreate에서 다음 코드를 추가하면 클래스 객체로 뷰를 참조할 수 있다.

    private ActivityMainBinding binding;    
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        binding = ActivityMainBinding.inflate(getLayoutInflater());
        View view = binding.getRoot();
        setContentView(view);
    }


[뷰 바인드 사용 예제]

binding.button(레이아웃에 있는 아이디).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // onClick codes goes here
            }
        });


관련 문서: https://developer.android.com/topic/libraries/view-binding?hl=ko

(before) 버터나이프/뷰바인드를 알기 전에는 다음과 같이 onClick을 구현했었다. 그러나 findViewById가 비효율적인 코드이기 때문에 뷰바인드를 사용하는 것이 좋다.

Button btnVar;
btnVar = (Button)findViewById(R.id.btnWidget);
        btnVar.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // onClick codes goes here
            }
        });
더보기

(api 문서에서 비교한 findViewById와 뷰 바인드의 차이점)

findViewById와의 차이점

뷰 결합에는 findViewById를 사용하는 것에 비해 다음과 같은 중요한 장점이 있습니다.

  • Null 안전: 뷰 결합은 뷰의 직접 참조를 생성하므로 유효하지 않은 뷰 ID로 인해 null 포인터 예외가 발생할 위험이 없습니다. 또한 레이아웃의 일부 구성에만 뷰가 있는 경우 결합 클래스에서 참조를 포함하는 필드가 @Nullable로 표시됩니다.
  • 유형 안전: 각 바인딩 클래스에 있는 필드의 유형이 XML 파일에서 참조하는 뷰와 일치합니다. 즉, 클래스 변환 예외가 발생할 위험이 없습니다.

이러한 차이점은 레이아웃과 코드 사이의 비호환성으로 인해 런타임이 아닌 컴파일 시간에 빌드가 실패하게 된다는 것을 의미합니다.

출처: https://developer.android.com/topic/libraries/view-binding?hl=ko#java

 

 

 

DateFormatSymbols: 자바에서 제공하는 캡슐화된 로컬라이즈할 수 있는 날짜 관련 포맷 데이터 클래스
    - 월별 이름, 요일별 이름, 타임존 데이터와 같은 것들이 들어있다.
    - SimpleDateFormat이 이 정보를 캡슐화하기 위해 위 클래스를 사용한다.
    - getTimeInstance, getDateInstance, getDateTimeInstance와 같은 메소드를 사용해서 이 클래스를 사용하는 것이 좋다. 위의 메소드들은 formatter를 위한 DateFormatSymbols를 생성한다.
    - setPattern메소드를 통해 원하는대로 패턴을 수정할 수 있다. DateForamt Class api 문서)
      (DateFormat의 메소드를 사용해 formatter를 만든다면 다음의 문서를 참조하면 좋다. 
    - 특정 로케일에서 특정 패턴의 date-time formatter를 만들때는 다음과 같이 만들 수 있다.

 new SimpleDateFormat(aPattern, DateFormatSymbols.getInstance(aLocale)).

 

[DateFormatSymbols 클래스를 이용해 month이름 리스트 가져오기]

// get shortened month name in English
String[] months = new DateFormatSymbols(Locale.ENGLISH).getShortMonths();

관련 api 문서 링크: https://docs.oracle.com/javase/8/docs/api/java/text/DateFormatSymbols.html

 

LinearLayoutManager: 레이아웃을 세로 혹은 가로로 설정하기 위해 사용하는 레이아웃 매니저
    - RecyclerView.LayoutManager에 속해있다.
        - 가로/세로의 레이아웃을 위한 LinearLayoutManager
        - 그리드 레이아웃을 위한 GridLayoutManager
        - 불규칙한 그리드 레이아웃을 위한 StaggeredGridLayoutManager 등이 있다 
    - 생성자
        LinearLayoutManager(Context context): vertical LinearLayoutManager 생성
        LinearLayoutManager(Context context, int orientation, boolean reverseLayout)
        LinearLayoutManager(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)
          : 생성자가 recyclerview attribute인 layoutManager에 의해 xml 안에서 사용될 때
      - Constraints
        HORIZONTAL: int
        INVALID_OFFSET: int
        VERTICAL: int
      - LayoutManager를 생성한 후 리사이클러뷰에 생성한 매니저를 set한다.

@BindView(R.id.main_list)
RecyclerView mainList;

LinearLayoutManager mainListManager = new LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL,false);
mainList.setLayoutManager(mainListManager);

 

관련 api 문서 링크: https://developer.android.com/reference/androidx/recyclerview/widget/LinearLayoutManager

 

ViewHolder: 화면에 표시될 아이템 뷰를 저장하는 클래스
    - Adapter에 생성된다.
    - RecyclerView의 경우 이미 생성된 뷰홀더가 있을 경우 그 뷰홀더를 삭제하지 않고 재활용한다.
      (이 때는 데이터가 뷰홀더에 바인딩되는 형태)    

[ViewHolder 예시 코드]

 public static class ViewHolder extends RecyclerView.ViewHolder {
        private final View myView;
        private final TextView year;

        public ViewHolder(View view){
            super(view);
            myView = view;
            year = (TextView) view.findViewById(R.id.tv_year);
        }
    }


관련 api 문서 링크: https://developer.android.com/reference/androidx/recyclerview/widget/RecyclerView.ViewHolder

 

Adapter: RecyclerView에 표시될 아이템 뷰를 생성한다
Adpater Class를 만들기 이전에 아이템이 들어갈 장소가 될 레이아웃을 생성한다.
    - onCreateViewHolder(ViewGroup parent, int viewType): 아이테뷰 레이아웃을 뷰홀더에 연결한다.
        - LayoutInflater: 특정 뷰에 해당하는 xml파일을 객체화한다.
           - Threadsafe하지 않기 때문에 single thread에서만 사용해야한다.
           관련 api 링크: https://developer.android.com/reference/android/view/LayoutInflater
    - onBindViewHolder(ViewHolder holder, final int position): 뷰홀더에 데이터를 바인드한다
    - getItemCount( ): 아이템 사이즈를 반환한다.
    - Adapter 내부에 반드시 ViewHolder 클래스가 존재해야 한다.

[Adpater 예시 코드]

public class RecyclerViewAdapter extends RecyclerView.Adapter<RecyclerViewAdapter.ViewHolder> {

    // 어댑터 생성자
    public RecyclerViewAdapter(List<String> val){
        this.items = val;
    }

    private List<String> items;

    // 아이템뷰 레이아웃을 뷰홀더에 연결
    @NonNull
    @Override
    public RecyclerViewAdapter.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        // LayoutInflater: 특정 뷰에 해당하는 xml파일을 객체화한다. Thread-safe하지 않아 싱글 thread에서만 사용해야 한다.
        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item_year,parent, false);
        return new RecyclerViewAdapter.ViewHolder(view);
    }

    // 뷰홀더에 데이터 바인드
    @Override
    public void onBindViewHolder(RecyclerViewAdapter.ViewHolder holder, final int position) {
        holder.year.setText(items.get(position));
    }

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


    // 아이템을 리스트뷰에 접근할 수 있도록 함
    public static class ViewHolder extends RecyclerView.ViewHolder {
        private final View myView;
        private final TextView year;

        public ViewHolder(View view){
            super(view);
            myView = view;
            year = (TextView) view.findViewById(R.id.tv_year);
        }
    }

}

관련 api 문서 링크: https://developer.android.com/reference/androidx/recyclerview/widget/RecyclerView.Adapter?hl=ko

 

[RecyclerView 정리]
  * 전체 Architecture
 

이미지 출처: https://openclassrooms.com/

 
  * 워크플로우
    1. 사용할 레이아웃 xml에 리사이클러뷰 추가 (/res/layout/activity_main.xml)
    2. 아이템뷰 레이아웃 추가 (/res/layout/recyclerview_item.xml)
    3. 어댑터 구현 (extends RecyclerView.Adapter)
    4. 어댑터, 레이아웃매니저 세팅 (setAdapter(), setLayoutManager())
    참고 블로그: https://recipes4dev.tistory.com/154

 

Animation: 애니메이션으로 뷰를 이동하는 것
관련 문서 링크: https://developer.android.com/training/animation

 

애니메이션 및 전환  |  Android 개발자  |  Android Developers

Content and code samples on this page are subject to the licenses described in the Content License. Java is a registered trademark of Oracle and/or its affiliates. Last updated 2020-06-20 UTC.

developer.android.com

 

현재까지 전체 코드

[MainActivity.java]

package com.example.daygram_clone;

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

import android.os.Bundle;
import android.view.View;
import android.view.animation.AlphaAnimation;
import android.view.animation.Animation;

import java.text.DateFormatSymbols;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;

import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnClick;


public class MainActivity extends AppCompatActivity {

    private List<String> months = new ArrayList<String>();
    private List<String> years = new ArrayList<String>();
    @BindView(R.id.main_list)
    RecyclerView mainList;

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

        // get shortened month name in English
        String[] month_list = new DateFormatSymbols(Locale.ENGLISH).getShortMonths();
        for(int i = 0; i < month_list.length; i++){
            months.add(month_list[i]);
        }

        int sYear = 2010;
        for(int i = 0; i < 10; i++){
            years.add(""+(sYear++));
        }

        LinearLayoutManager mainListManager = new LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL,false);
        mainList.setLayoutManager(mainListManager);
    }

    // main month 클릭 시 관련 아이템을 보여줌
    @OnClick(R.id.main_month)
    public void onClickMonth(){
        onClickSupp(3);
    }

    @OnClick(R.id.main_year)
    public void onClickYear(){
        onClickSupp(2);
    }

    // option: 1: month, 2: year
    public void onClickSupp(int option){
        Animation animation = new AlphaAnimation(0,1);
        animation.setDuration(1000);

        switch(option){
            case 1:
                mainList.setAdapter(new RecyclerViewAdapter(months));
                break;
            case 2:
                mainList.setAdapter(new RecyclerViewAdapter(years));
                break;
            default:
                break;
        }
        mainList.setVisibility(View.VISIBLE);
        mainList.setAnimation(animation);
    }
}

 

[RecyclerViewAdapter.java]

package com.example.daygram_clone;

import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

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

import java.util.List;

public class RecyclerViewAdapter extends RecyclerView.Adapter<RecyclerViewAdapter.ViewHolder> {

    // 어댑터 생성자
    public RecyclerViewAdapter(List<String> val){
        this.items = val;
    }

    private List<String> items;

    // 아이템뷰 레이아웃을 뷰홀더에 연결
    @NonNull
    @Override
    public RecyclerViewAdapter.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        // LayoutInflater: 특정 뷰에 해당하는 xml파일을 객체화한다. Thread-safe하지 않아 싱글 thread에서만 사용해야 한다.
        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item_year,parent, false);
        return new RecyclerViewAdapter.ViewHolder(view);
    }

    // 뷰홀더에 데이터 바인드
    @Override
    public void onBindViewHolder(RecyclerViewAdapter.ViewHolder holder, final int position) {
        holder.year.setText(items.get(position));
    }

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


    // 아이템을 리스트뷰에 접근할 수 있도록 함
    public static class ViewHolder extends RecyclerView.ViewHolder {
        private final View myView;
        private final TextView year;

        public ViewHolder(View view){
            super(view);
            myView = view;
            year = (TextView) view.findViewById(R.id.tv_year);
        }
    }

}

 

[activity_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="com.example.daygram_clone.MainActivity">

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/main_list"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:background="@color/colorAccent"
        android:scrollbars="vertical"
        android:visibility="visible"></androidx.recyclerview.widget.RecyclerView>
    <FrameLayout
        android:layout_width="match_parent"
        android:layout_height="80dp">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:background="@color/colorPrimaryDark"
            android:orientation="horizontal"
            android:visibility="visible">
            <LinearLayout
                android:layout_width="0dp"
                android:layout_height="match_parent"
                android:layout_weight="1"
                android:gravity="center_vertical"
                android:orientation="horizontal">
                <ImageView
                    android:layout_width="10dp"
                    android:layout_height="30dp"
                    android:layout_gravity="center_vertical"
                    android:layout_marginLeft="10dp"
                    android:background="@color/colorAccent"/>

                <TextView
                    android:id="@+id/main_month"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_marginLeft="20dp"
                    android:text="August"
                    android:textSize="20dp"/>

                <TextView
                    android:id="@+id/main_year"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_marginLeft="20dp"
                    android:text="2020"
                    android:textSize="20dp"/>
            </LinearLayout>

            <TextView
                android:id="@+id/today"
                android:layout_width="40dp"
                android:layout_height="30dp"
                android:layout_gravity="center_vertical"
                android:background="@color/colorAccent"
                android:text="Today"/>

            <LinearLayout
                android:layout_width="0dp"
                android:layout_height="match_parent"
                android:layout_weight="1"
                android:gravity="center_vertical">

                <ImageView
                    android:layout_width="80dp"
                    android:layout_height="10dp"
                    android:layout_marginLeft="30dp"
                    android:background="@color/colorAccent"/>

                <ImageView
                    android:layout_width="30dp"
                    android:layout_height="20dp"
                    android:layout_marginLeft="20dp"
                    android:background="@color/colorAccent"/>

            </LinearLayout>

        </LinearLayout>

    </FrameLayout>

</LinearLayout>

 

[list_item_year.xml]

<?xml version="1.0" encoding="utf-8"?>

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="80dp"
    android:orientation="vertical"
    android:background="@color/colorPrimaryDark"
    android:gravity="center_vertical">

    <TextView
        android:id="@+id/tv_year"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="@color/colorAccent"
        android:text="2017"
        android:textSize="20dp"/>

</LinearLayout>



따라하기 출처: https://codeasy.tistory.com/28?category=751342

 

댓글