'[Android] - 개념/RecyclerView'에 해당되는 글 3건

  1. 2017.01.18 RecyclerView와 CardView 사용하기
  2. 2017.01.18 RecyclerView, Adapter, ViewHolder 개념
  3. 2017.01.18 RecyclerView 기본 개념

** RecyclerView와 CardView 사용하기 **


참고 URL : http://horajjan.blog.me/220739886248


'핵심만 골라 배우는 안드로이드 스튜디오 & 프로그래밍, 34, 35장'을 인용하였다 (예제 첨부) 

 
RecyclerView는 기존의 ListView보다 리스트를 구성하는 뷰를 관리하는 방법이 훨씬 더 효율적이다. 즉, 리스트 항목을 구성하는 기존 뷰가 스크롤되어 화면에서 벗어났을 때 새로운 뷰를 생성하는 대신 그것을 재사용한다(그래서 이름이 'recycler'다)
ListView와 다르게 RecyclerView에서는 다음의 세 가지 레이아웃 매니저를 선택할 수 있다. 따라서 리스트 항목이 사용자에게 보이는 방법을 더 다양하게 제어할 수 있다
  • LinearLayoutManager - 리스트 항목들이 수평 또는 수직의 스크롤 가능한 리스트로 나타난다
  • GridLayoutManager - 리스트 항목들이 격자(grid) 형태로 나타난다. 리스트 항목들이 균일한 크기일 때는 이 레이아웃 매니저를 사용하는 것이 좋다
  • StaggeredGridLayoutManager - 리스트 항목들이 일정하지 않은 크기의 격자 형태로 나타난다. 리스트 항목들의 크기가 동일하지 않을 때는 이 레이아웃 매니저를 사용하는 것이 좋다
  •  
     
RecyclerView에 나타나는 각 리스트 항목은 ViewHolder 클래스의 인스턴스로 생성된다.
ViewHolder 인스턴스는 RecyclerView에서 리스트 항목을 보여주는 데 필요한 모든 것을 포함한다. 즉, 보여줄 정보와 항목을 보여주는 데 사용할 뷰 레이아웃이다
ListView와 마찬가지로 RecyclerView도 어댑터가 필요하다. 어댑터는 사용자에게 보여줄 데이터와 RecyclerView 인스턴스 간의 중개자 역할을 한다. RecyclerView.Adapter 클래스의 서브 클래스로 생성되며, 최소한 다음의 메서드를 구현해야 한다.
이 메서드들은 어댑터가 지정된 RecyclerView 객체에 의해 다양한 시점에 호출된다
  • getItemCount() - 이 메서드에서는 리스트에 보여줄 항목의 개수를 반환해야 한다
  • onCreateViewHolder() - 이 메서드는 데이터를 보여주는 데 사용되는 뷰를 갖도록 초기화된 ViewHolder 객체를 생성하고 반환한다. 이때 그 뷰는 XML 레이아웃 파일을 인플레이트하여 생성된다
  • onBindViewHolder() - 이 메서드에서는 두 개의 인자를 받는다. onCreateViewHolder() 메서드에서 생성된 ViewHolder 객체와 보여줄 리스트 항목을 나타내는 정숫값이다. 이 메서드에서는 지정된 항목의 텍스트와 그래픽 데이터를 레이아웃의 뷰에 넣은 후, 그 객체를 RecyclerView에 반환한다. 그럼으로써 RecyclerView가 사용자에게 보여줄 수 있다​
 
다음으로 CardView 클래스는 카드 형태로 보여 줄 수 있는 사용자 인터페이스 뷰다. 일반적으로 RecyclerView 인스턴스를 같이 사용해서 리스트 형태로 보여준다. 이때 각 카드마다 그림자 효과와 둥근 모서리를 갖도록 구성할 수 있다
CardView 레이아웃 안에는 표준 레이아웃 매니저(RelativeLayout이나 LinearLayout 등)를 사용하는 어떤 복잡한 레이아웃도 포함할 수 있다
스크롤이 가능한 카드 리스트를 생성하기 위해 RecyclerView와 CardView를 같이 사용할 때는 RecyclerView의 onCreateViewHolder() 메서드에서 카드의 레이아웃 리소스 파일을 인플레이트한다
특정 리스트 카드 클릭에 반응하려면 CardView 인스턴스 속성을 android:clickable="true"로 설정하고 클릭 리스너를 단다. 클릭 리스너 안에서 어떤 카드가 선택되었는지 식별하기 위해서
는 RecyclerView.ViewHolder 클래스의 getAdapterPosition() 메서드를 호출하면 얻을 수 있다 


'[Android] - 개념 > RecyclerView' 카테고리의 다른 글

RecyclerView, Adapter, ViewHolder 개념  (0) 2017.01.18
RecyclerView 기본 개념  (0) 2017.01.18
Posted by 농부지기
,

** RecyclerView, Adapter, ViewHolder 개념 **


참고 URL : http://horajjan.blog.me/220745854967


'실무에 바로 적용하는 안드로이드 프로그래밍, 9장'을 인용하였다  (예제 첨부)

RecyclerView는 ViewGroup의 서브 클래스로, 자식 View 객체들의 리스트를 보여준다

위 그림처럼 100개의 View를 생성하는 대신 ​한 화면을 채우는 데 충분한 12개만 생성한다. 그리고 화면이 스크롤되면서 View가 화면을 벗어날 때 RecyclerView는 그 View를 버리지 않고 재활용한다

RecyclerView는 위 TextView들을 재활용하고 화면에 보여주는 책임만 갖는다. Adapter의 서브 클래스와 ViewHolder의 서브 클래스가 함께 동작해야 한다

ViewHolder는 한 가지 일, 즉 하나의 View를 보존하는 일을 한다

RecyclerView는 자신이 ViewHolder를 생성하지 않는다. 대신에 그 일을 어댑터(adapter)에 요청한다

어댑터는 다음과 같은 책임을 갖는다

  • 필요한 ViewHolder 객체를 생성한다
  • 모델 계층의 데이터를 ViewHolder와 결합한다​

​우선 RecyclerView에서 구현하는 어댑터의 getItemCount() 메서드를 호출하여 리스트에 보여줄 객체 개수를 요청한다

그 다음에 RecyclerView는 어댑터의 onCreateViewHolder(ViewGroup, int) 메서드를 호출하여 ViewHolder 객체를 받는다. 그리고 RecyclerView는 onBindViewHolder(ViewHolder, int)를 호출하며, 이때 리스트 항목의 위치와 함께 ViewHolder 객체를 인자로 전달한다. 그 다음에 어댑터는 그 위치 모델 데이터를 찾은 후 그것을 ViewHolder의 View에 결합한다

여기서 주목할 점은 ​onCreateViewHolder(ViewGroup, int) 메서드가 onBindViewHolder(ViewHolder, int)보다 적게 호출될 수 있다는 것이다. 충분한 개수의 ViewHolder 객체가 생성되면 RecyclerView가 ​onCreateViewHolder(...)의 호출을 중단하기 때문이다. 그리고 기존에 생성된 ViewHolder 객체를 재사용하여 시간과 메모리를 절감한다


'[Android] - 개념 > RecyclerView' 카테고리의 다른 글

RecyclerView와 CardView 사용하기  (0) 2017.01.18
RecyclerView 기본 개념  (0) 2017.01.18
Posted by 농부지기
,

** RecyclerView 기본 개념 **


1. 도입과정 및 장.단점

   1. Android 5.0에 처음 소개된 RecyclerView는 안드로이드 ListView의 장/단점을 보완한 고급 위젯이다.

   2. Android Lollipop과 함께 나온 이 위젯은 SupportLibrary에 포함되어 Android Version 7 이상에서 사용이 가능하다.


2. Gradle

dependencies {
    compile 'com.android.support:cardview-v7:21.0.+'
    compile 'com.android.support:recyclerview-v7:21.0.+'
}


3. API 문서

   1. Creating Lists and Cards

   2. RecyclerView

   3. RecyclerView.Adapter



샘플 코드는?

GitHub에 올려두었으며, RecyclerView 샘플입니다.

base로 구분된 부분이 있는데 이는 나중에 supportLibrary 형태로 배포할 예정입니다.

Base는 Kotlin을 기준으로 작성하였고, 샘플은 Java/Kotlin 모두가 존재합니다. 각각 살펴보시면 되겠습니다.


Create Lists

Creating Lists and Cards에 정의된 List 표현입니다.

RecyclerView

Widget인 RecyclerView는 LayoutManager를 통해서 View 그리는 방법을 정의합니다.

RecyclerView.Adapter에서 Data의 ViewHolder 정의에 따라서 UI가 선택되고, 이를 표현하게 됩니다.

  • ViewHolder의 적용으로 View의 재사용을 가능하게 합니다.
  • LayoutManager의 추가로 아이템의 배치 방법을 정의할 수 있다.
    • LinearLayoutManager : 가로/세로 형태로 아이템을 배치한다.

      LinearLayoutManager

    • GridLayoutManager : 한 줄에 1개 이상의 이미지를 표시할 수 있지만, 아이템의 크기는 줄의 첫 번째 아이템의 크기에 따라서 달라질 수 있다.(고정시에는 모두 고정)

      GridLayoutManager

    • StaggeredGridLayoutManager : 그리드 형태에 아이템의 크기를 다양하게 적용할 수 있다.

      StaggeredGridLayoutManager

    • Custom LayoutManager : 3개의 레이아웃 매니저를 상속받아 커스텀 할 수 있다.

  • 많은 데이터의 리스트 형태로 제공이 가능하다.
  • RecyclerView.ItemAnimator을 이용하여 Item의 Animator을 정의할 수 있습니다.


ListView의 장점

  • ListView는 간단하게 리스트를 만드는 부분에 있어서는 장점입니다.
  • 간단한 아이템 형태를 만드는 경우는 빠르게 적용이 가능한 ArrayAdapter을 제공합니다.


ListView의 단점

  • 아이템의 애니메이션 처리가 쉽지 않습니다.
  • 리스트에는 한 개 이상의 View가 필요한 경우가 있지만 커스텀으로 작업하기 쉽지 않습니다.

    • 다음은 한 개 이상의 ViewHolder를 가진 샘플입니다. 위쪽에는 사진이 표시되고, 아래에는 Footer이라는 새로운 ViewHolder가 노출된 상태입니다.

    ViewHolderSample

  • RecyclerView에는 Header/Footer 추가를 직접 구현해야 하지만, ListView는 기본으로 제공합니다.
  • ViewHolder 패턴을 강제적으로 사용하지는 않으므로 고비용의 findViewById가 매번 호출될 수 있다.

구글에서 추천하는 ViewHolder 패턴을 사용하지 않게되면 다음과 같은 코드를 타게 됩니다.

@Override
public View getView(final int position, View convertView, ViewGroup parent) {
    Holder holder = new Holder();
    View rowView = inflater.inflate(R.layout.item_list, null);
    holder.tv = (TextView) rowView.findViewById(R.id.text);
    holder.tv.setText(result[position]);
    return rowView;
}

ItemCount에 따라서 매번 getView을 호출하게 됩니다. 이때 위와 같은 코드는 holder부터 inflater.inflate을 초기화하고, findViewById역시 매번 생성하게 됩니다.

고비용의 findViewById을 매번 하는 것은 성능상 좋지 않고, 메모리의 영향도 받을 수 있습니다.

그래서 다음과 같은 ViewHolder 패턴을 사용할 수 있습니다.

@Override
public View getView(final int position, View convertView, ViewGroup parent) {
    if (convertView == null) {
        rootView = inflater.inflate(R.layout.item_list, null);
        Holder holder = new Holder(); // ViewHolder을 생성
        holder.tv = (TextView) rowView.findViewById(R.id.text);
        rootView.setTag(viewHolder); // setTag
    } else {
        rootView = convertView;
        holder = (Holder) rootView.getTag(); // rootView에서 holder을 꺼내온다
    }

    holder.tv.setText(result[position]);
    return rootView;
}

위와 같은 형태로 제공이 되는데 매번 구현하는 건 귀찮고, 서로 다른 ViewHolder을 여러 개 만들어서 사용하기 쉽지 않습니다.(ItemView의 View가 여러 개 생성될 수 있는 형태)


RecyclerView을 사용하기 위해서

RecyclerView는 supportLibrary와 Android 5.0(API 21 이상)에서 사용이 가능합니다.

API 7부터 사용이 가능한 supportLibrary - recyclerview-v7은 다음과 같이 정의할 수 있습니다.

dependencies {
    compile 'com.android.support:recyclerview-v7:24.2.1'
}


RecyclerView View 정의

RecyclerView의 XML 정의는 다음과 같습니다.

v7의 RecyclerView을 정의합니다.

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


layoutManager 정의하기

View의 형태를 정의하기 위해 LayoutManager을 정의해야 합니다.

RecyclerView는 기본 정의가 존재하지 않기 때문에 꼭! 최소 하나씩 설정해주어야 합니다.

설정하지 않으면 화면이 구성되지 않으므로, 헤매지 말고 꼭! 정의 부분을 살펴보시기 바랍니다.

xml에서 정의하기

다음의 app 정의를 사용하기 위해서는 아래의 코드가 추가되어야 합니다.

xmlns:app="http://schemas.android.com/apk/res-auto"

  • app:layoutManager : xml에서 layoutManager을 정의할 수 있습니다.
  • app:spanCount : xml에서 layoutManager에서 사용할 spanCount을 정의할 수 있습니다.

LinearLayoutManager

<android.support.v7.widget.RecyclerView
    app:layoutManager="LinearLayoutManager" />

GridLayoutManager

<android.support.v7.widget.RecyclerView
    app:layoutManager="GridLayoutManager"
    app:spanCount="2" />

LinearLayoutManager

<android.support.v7.widget.RecyclerView
    app:layoutManager="StaggeredGridLayoutManager"
    app:spanCount="3" />

LayoutManager 코드를 통한 정의

// use a linear layout manager
mLayoutManager = new LinearLayoutManager(this);
mRecyclerView.setLayoutManager(mLayoutManager);
// use a staggered grid layout manager
mGridLayoutManager = new new GridLayoutManager(this, 3);
mRecyclerView.setLayoutManager(mGridLayoutManager);
// use a staggered grid layout manager
mStgaggeredGridLayoutManager = new StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL);
mRecyclerView.setLayoutManager(mStgaggeredGridLayoutManager);
  • 상속받아 Custom 처리


ViewHolder 정의

구글에서 정의해주는 간단한 ViewHolder입니다.

RecyclerView.ViewHolder을 상속받아서 정의합니다.

// Provide a reference to the views for each data item
// Complex data items may need more than one view per item, and
// you provide access to all the views for a data item in a view holder
public class ViewHolder extends RecyclerView.ViewHolder {
    // each data item is just a string in this case
    public TextView mTextView;
    public ViewHolder(TextView v) {
        super(v);
        mTextView = v;
    }
}

제가 주로 사용하는 ViewHolder는 다음과 같습니다.

Context와 ViewGroup을 생성자로 전달받아서 LayoutInflater.form을 통해서 View를 생성합니다.

그리고 itemView를 통해서 View들을 findViewById을 제공합니다.

public class ViewHolder extends RecyclerView.ViewHolder {

  private TextView textView;

  public ViewHolder(Context context, ViewGroup parent) {
      super(LayoutInflater.from(context).inflate(R.layout.item_large_view, parent, false));

      textView = (TextView) itemView.findViewById(R.id.tv_message);
  }
}

코틀린을 통한 베이스를 다음과 같이 작성해두었습니다.

abstract class BaseViewHolder<ITEM>(
        open val adapter: RecyclerView.Adapter<*>, itemView: View) :
        RecyclerView.ViewHolder(itemView) {

    constructor(@LayoutRes layoutRes: Int,
                parent: ViewGroup?, adapter: RecyclerView.Adapter<*>)
    : this(adapter, LayoutInflater.from((adapter as? AbstractRecyclerAdapter<*, *>)?.context).inflate(layoutRes, parent, false))

    init {
        ButterKnife.bind(BaseRecyclerViewHolder@this, itemView)
    }

    // ViewHolder의 View를 정의합니다.
    abstract fun onViewHolder(item: ITEM?, position: Int)

    open protected val context: Context?
        get() = (adapter as? AbstractRecyclerAdapter<*, *>)?.context
}


Adapter 정의

RecyclerView의 Adapter는 ListView의 ArrayAdapter처럼 List<Object>을 기본적으로 가지고 있지 않습니다.

그래서 원하는 형태의 Data 형태도 직접 구현하여야 합니다.

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

    // 아이템 리스트
    private String[] mDataset;

    // Provide a suitable constructor (depends on the kind of dataset)
    public MyAdapter(String[] myDataset) {
        mDataset = myDataset;
    }

    // Create new views (invoked by the layout manager)
    @Override
    public MyAdapter.ViewHolder onCreateViewHolder(ViewGroup parent,
                                                   int viewType) {
        // create a new view
        View v = LayoutInflater.from(parent.getContext())
                               .inflate(R.layout.my_text_view, parent, false);
        // set the view's size, margins, paddings and layout parameters
        ...
        ViewHolder vh = new ViewHolder(v);
        return vh;
    }

    // Replace the contents of a view (invoked by the layout manager)
    @Override
    public void onBindViewHolder(ViewHolder holder, int position) {
        // - get element from your dataset at this position
        // - replace the contents of the view with that element
        holder.mTextView.setText(mDataset[position]);
    }

    // Return the size of your dataset (invoked by the layout manager)
    @Override
    public int getItemCount() {
        return mDataset.length;
    }
}

item 정의

RecyclerView는 아이템에 대해서 직접 정의를 해야 합니다.

해당 Adapter에서 사용할 아이템을 직접 정의해주면 됩니다.

// 아이템 리스트
private String[] mDataset;

그리고 getItemCount을 상속을 기본적으로 받아야 하며, size를 다음과 같이 return 해주면 됩니다.

// Return the size of your dataset (invoked by the layout manager)
@Override
public int getItemCount() {
    return mDataset.length;
}

View를 정의

onCreateViewHolder을 기본적으로 상속받습니다.

이를 통해 ViewHolder을 생성하게 됩니다.

viewType에 따라서 최소 1회만 생성합니다.

만약 viewType이 1개 이상이라면 onCreateViewHolder 역시 1번 이상이 호출됩니다.

// Create new views (invoked by the layout manager)
@Override
public MyAdapter.ViewHolder onCreateViewHolder(ViewGroup parent,
                                               int viewType) {
    // create a new view
    View v = LayoutInflater.from(parent.getContext())
                           .inflate(R.layout.my_text_view, parent, false);
    // set the view's size, margins, paddings and layout parameters
    ...
    ViewHolder vh = new ViewHolder(v);
    return vh;
}

view를 표현하기

onCreateViewHolder을 통해서 생성되면, onBindViewHolder에서 해당 holder의 View에 데이터를 노출을 정의하면 됩니다.

RecyclerView는 ViewHolder을 재사용할 수 있도록 설계되어 있으므로, ViewType이 한번 생성된 이후로는 onBindViewHolder만 호출되게 됩니다.

// Replace the contents of a view (invoked by the layout manager)
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
    // - get element from your dataset at this position
    // - replace the contents of the view with that element
    holder.mTextView.setText(mDataset[position]);
}

viewType을 정의하기

기본 상속을 받는 메소드는 아니지만 viewType을 정의할 수 있습니다.

기본값은 0으로 초기화되지만, 사용에 따라서 getItemViewType을 정의할 수 있습니다.

ViewType이 1개 이상이라면 기본적으로 다음의 getItemViewType을 정의해주시면 되겠습니다.

@Override
public int getItemViewType(int position) {
    return super.getItemViewType(position);
}


RecyclerView에 setAdapter()

RecyclerView에 setAdapter을 정의하여야 합니다.

생성된 Adapter을 다음과 같이 추가해주시면 됩니다.

mRecyclerView = (RecyclerView) findViewById(R.id.my_recycler_view);

  // use this setting to improve performance if you know that changes
  // in content do not change the layout size of the RecyclerView
  mRecyclerView.setHasFixedSize(true);

  // use a linear layout manager
  mLayoutManager = new LinearLayoutManager(this);
  mRecyclerView.setLayoutManager(mLayoutManager);

  // specify an adapter (see also next example)
  mAdapter = new MyAdapter(myDataset);
  mRecyclerView.setAdapter(mAdapter);


Kotlin으로 작성한 BaseAdapter

kotlin으로 작성한 BaseAdapter입니다. itemList에 대해서 정의를 하였고,

getItem/removeItem/setItem/clear을 처리하는 부분을 model 형태로 처리하였습니다.

abstract class AbstractRecyclerAdapter<ITEM, VIEW_TYPE : RecyclerView.ViewHolder?>(open val context: Context) :
        RecyclerView.Adapter<VIEW_TYPE>(), BaseRecyclerModel<ITEM> {

    private val itemList: MutableList<ITEM> = ArrayList()

    abstract fun onItemViewType(position: Int): Int

    override fun getItemViewType(position: Int)
            = onItemViewType(position)

    override fun getItemCount()
            = itemList.size

    override fun addItem(item: ITEM) {
        itemList.add(item)
    }

    override fun addItem(position: Int, item: ITEM) {
        itemList.add(position, item)
    }

    override fun addItems(items: List<ITEM>) {
        itemList.addAll(items)
    }

    override fun clear() {
        itemList.clear()
    }

    override fun removeItem(item: ITEM) {
        itemList.remove(item)
    }

    override fun removeItem(position: Int) {
        itemList.removeAt(position)
    }

    /**
     * GetItem null or ITEM
     */
    override fun getItem(position: Int)
            = itemList.getOrNull(position)

    override fun getItems() = itemList
}


마무리

저는 BaseRecyclerAdapter을 만들어서 사용하고 있습니다.

ListView의 ArrayAdapter을 최대한 유사하게 사용하기 위해서 만들었습니다.

Header/Footer을 포함하는 RecyclerView도 추가로 만들어서 사용 중입니다.

ListView는 Header/Footer을 간단하게 추가할 수 있지만, RecyclerView는 제공하지 않습니다. 대신 직접 구현해서 사용할 수 있습니다.

처음에 만들기가 귀찮긴 하지만.. 만들어두면 사용하기 편합니다.

간단하게 사용하기에는 ListView가 편하지만, View의 커스텀이 많아지면 질수록 RecyclerView가 관리하기 편합니다.


샘플 코드는?

GitHub에 올려두었으며, RecyclerView 샘플입니다.

base로 구분된 부분이 있는데 이는 나중에 supportLibrary 형태로 배포할 예정입니다.

Base는 Kotlin을 기준으로 작성하였고, 샘플은 Java/Kotlin 모두가 존재합니다. 각각 살펴보시면 되겠습니다.



 

참고 url : http://thdev.tech/androiddev/2016/11/01/Android-RecyclerView-intro.html


'[Android] - 개념 > RecyclerView' 카테고리의 다른 글

RecyclerView와 CardView 사용하기  (0) 2017.01.18
RecyclerView, Adapter, ViewHolder 개념  (0) 2017.01.18
Posted by 농부지기
,