반응형

android.view.InflateException: Binary XML file line #14: 

Binary XML file line #14: Error inflating class <unknown>



Case 1 - 관련된 리소스를 찾지 못하는 경우


상황

특정 Layout의 android:Background 속성으로 a_selector.xml을 적용.

실제 단말에서는 문제없이 동작하였으나, Emulator에서는 에러발생하며 종료됨.

android:background="@drawable/item_selector"


원인

a_selector.xml파일이 v24 폴더에 생성되어, Emulator에서 참조하는 폴더에는 해당 파일이 없음

* 예전에 스크롤바에 아래 속성을 설정할때도 동일한 문제가 있었는데 이제 보니 같은 원인

android:scrollbarThumbVertical="@drawable/scroll_bar"

조치

어차피 이 xml은 사이즈나 버전에 영향을 받지 않는 xml이므로

drawable 폴더로 xml 파일 이동

반응형
반응형

RecyclerView #6 - ItemView를 클래스화 하기


RecyclerView #1- 구조및 기본 사용법

RecyclerView #2 - 구분선 추가, 아이템간 간격 조절

RecyclerView #3 - 컨텍스트 메뉴 처리

RecyclerView #4 - 아이템 클릭 처리

RecyclerView #5 - 아이템 선택 처리하기

RecyclerView #6 - ItemView를 클래스화 하기

RecyclerView #7 - ViewType 동적변경



RecyclerView #5까지의 샘플을 보면 

Adapter의 onBindViewHolder() 메소드에서 ViewHolder가 Hold하고 있는 위젯에 데이터를 설정하는 형태로 작성되어 있다.

public StdViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
LayoutInflater inflate = LayoutInflater.from(mContext);

View view = inflate.inflate(R.layout.list_item, parent, true);
StdViewHolder vh = new StdViewHolder(view);

return vh;
}


public void onBindViewHolder(@NonNull StdViewHolder holder, int position) {
holder.textView.setText(mdata.get(position));

}

public class StdViewHolder extends RecyclerView.ViewHolder {
public TextView textView;

public StdViewHolder(@NonNull View itemView) {
super(itemView);
this.textView = itemView.findViewById(R.id.textView);

}

}


샘플의 경우 아이템을 표시하는 위젯으로 TextView 하나만 추가된 형태이므로 큰 무리는 없겠지만

여러가지 위젯으로 복잡하게 구성된 경우, 처리하는 코드가 지저분하게 코딩될수 밖에 없다.


ViewHolder를 파라미터로 받는 함수를 작성하고 onBindViewHolder에서 호출함으로써 좀 정제화 할수는 있겠지만,

ItemView자체를 클래스화 해서 onBindViewHolder에서는 데이터만 던져주고 

클래스내부에서 나머지 처리를 하는 구조로 작성되면 좋을 것 같다.



대략적인 구현 방향은 다음과 같다.

1. ViewHolder가 Hold할 itemView로 LinearLayout 객체를 던져주고

2. 해당 LinearLayout에 우리가 작성한 Layout을 Inflate해서 붙여주자.


쉽게 말해 다음과 같은 코드로 LinearLayout 객체를 ViewHolder에 던져(?) 주면

RecyclerView는 아이템이 표시될 위치에 우리가 던져준 LinearLayout 을 배치하게 되고

우리는 해당 LinearLayout에 attach된 "Inflate된 View"를 통해 데이터를 갱신 할 수 있다.

public StdViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
//LayoutInflater inflate = LayoutInflater.from(mContext);
//View view = inflate.inflate(R.layout.list_item, parent, true);

LayoutInflater inflate = LayoutInflater.from(mContext);
View view = new LinearLayout(mContext);
View v = inflate.inflate(R.layout.list_item, view, true);
StdViewHolder vh = new StdViewHolder(view);

return vh;
}


그럼 위에서 정리된 내용대로 ItemView 클래스를 작성해 보자.


ItemView는 RecyclerView가 요구하는 View Class type이어야 할뿐 아니라, 우리가 inflate한 View를 attach할 수 있어야 하므로

일단은 제일 만만한 LinearLayout을 상속 받아 작성한다.


* Context를 파라미터로 갖는 기본적인 생성자는 구현이 필요하다.

public class ItemView extends LinearLayout {
TextView textView;
public ItemView(Context context) {
super(context);

LayoutInflater inflate = LayoutInflater.from(context);

// inflate itemLayout & attach to this LinearLayout
View v = inflate.inflate(R.layout.list_item, this, true);

// bind widget
textView = v.findViewById(R.id.textView);

v.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
}

void setContents(String contents) {
textView.setText(contents);
}
}


당연한 이야기 이지만, LinearLayout을 상속받은 이 클래스의 인스턴스와 

R.layout.list_item을 inflate한 View간의 관계가 맺어 지는 것은

inflate.inflate() 의 두번째 인자(root)와 세번째 인자(attachToRoot)에 의해서이다.



Adapter와 ViewHolder는 다음과 같이 변경될 수 있다.

public StdViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
ItemView view = new ItemView(mContext);

StdViewHolder vh = new StdViewHolder(view);

return vh;
}

@Override
public void onBindViewHolder(@NonNull StdViewHolder holder, int position) {
holder.mItemView.setContents(mdata.get(position));

}


public class StdViewHolder extends RecyclerView.ViewHolder {
public ItemView mItemView;

public StdViewHolder(@NonNull View itemView) {
super(itemView);
this.mItemView = (ItemView)itemView;
}
}

이제 ItemView Class의 setContents() 역할을 하는 메소드를 변형해서

Class 자체를 넘겨주고 ItemView에서 내용을 뿌리면 된다.


알고 있겠지만, 이러한 구조를 적용하더라도 잊지 말아야 하는 것은 

이번에 작성한 ItemView Class의 인스턴스 또한 재활용 되는 인스턴스라는 점이다.


반응형
반응형

RecyclerView #5 - 아이템 선택 처리하기

 

 

RecyclerView #1- 구조및 기본 사용법

RecyclerView #2 - 구분선 추가, 아이템간 간격 조절

RecyclerView #3 - 컨텍스트 메뉴 처리

RecyclerView #4 - 아이템 클릭 처리

RecyclerView #5 - 아이템 선택 처리하기

RecyclerView #6 - ItemView를 클래스화 하기

RecyclerView #7 - ViewType 동적변경

 

 

 

RecyclerView는 선택된 아이템의 선택상태나 선택된 아이템 리스트 관리등의 기능을 제공하지 않습니다.

 

아이템이 선택되었을때 배경색을 변경하거나, 

RecyclerView의 외부에서 선택 혹은 해제 처리를 하는 방법에 대해 정리해 보겠습니다.

 

 

특정 아이템을 클릭했을때 배경색상 변경하기

우선 생각해 볼수 있는 방법은 아이템이 클릭되었을때 onClick() 리스너에서 itemView의 배경색을 변경하는 방법입니다.

ViewHolder가 보관하는 itemView에 onClick 리스너를 설정했으므로, 

onClick()리스너에서 넘어오는 View는 ViewHolder.itemView 객체입니다.

아래와 같이 배경색을 변경해 줄수 있습니다.

public StdViewHolder(@NonNull View itemView) {
    super(itemView);
    this.textView = itemView.findViewById(R.id.textView);

    itemView.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {

            v.setBackgroundColor(Color.BLUE);

            Log.d("test", "position = "+ getAdapterPosition());
        }
    });

선택 해제 처리는?

onClick 이벤트 발생시, 클릭된 아이템의 position( ViewHolder.getAdapterPosition() )과 선택상태를 저장해 놓고 토글 시키면 될것 같습니다.

position별 선택상태를 저장하는 구조는 SparseBooleanArray를 사용하겠습니다.

public class StdRecyclerAdapter extends RecyclerView.Adapter<StdRecyclerAdapter.StdViewHolder> {

     private SparseBooleanArray mSelectedItems = new SparseBooleanArray(0);

     ....

    
    public class StdViewHolder extends RecyclerView.ViewHolder {
        public TextView textView;

        public StdViewHolder(@NonNull View itemView) {
            super(itemView);
            this.textView = itemView.findViewById(R.id.textView);

            itemView.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    int position = getAdapterPosition();

                    if ( mSelectedItems.get(position, false) ){
                        mSelectedItems.put(position, false);
                        v.setBackgroundColor(Color.WHITE);
                    } else {
                        mSelectedItems.put(position, true);
                        v.setBackgroundColor(Color.BLUE);
                    }

                }
            });

 

의도대로 잘 동작하는 것 같습니다만, 스크롤 해보면 이상한(혹은 당연한) 현상이 발생합니다.

 

 

"RecyclerView #1- 구조및 기본 사용법" 포스트에 첨부했던 이미지 인데요.

다시 한번만 정리하고 가겠습니다.

 

RecyclerView의 Adapter는 화면에 표시되는 아이템의 개수에 기반하여 적정수( 한화면에 표시되는 아이템 개수 + 스크롤시 사용할 여분의 아이템 개수)의 View를 미리생성하여 ViewHolder를 통해 관리하며,

스크롤시 화면에서 벗어나는  View를 재사용하여

화면에 새로 나타나는 데이터를 화면에 표시하는 구조입니다.

(이러한 방식으로 전체 Item 갯수보다 훨씬 작은 View만으로 Item 전체를 표시 할 수 있습니다.)

 

클릭할 당시의 View가 계속해서 동일한 데이터를 표시한다고 보장할 수 없다는 결론이죠.

즉 아이템의 선택상태는 position기반으로 관리하되, 실제 선택 상태를 표시하는 것은 ViewHolder에 데이터가

반영되는 시점에 처리 해주면 될것 같습니다.

* ViewHolder의 getAdapterPosition()은 ViewHolder의 재사용 여부와 관계 없이 아이템 배치순서상의 position( 0 base)을 리턴합니다.

DataSet내의 index값 이라고 생각해도 무방합니다.

 

그렇다면 

1. 아이템 클릭시 선택 상태 저장 및 선택 상태 표시

2. 아이템 바인딩 시에 선택 상태 표시

의 방법으로 구현해 보면 될것 같습니다.

itemView.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        int position = getAdapterPosition();
        
        if ( mSelectedItems.get(position, false) ){
            mSelectedItems.put(position, false);
            v.setBackgroundColor(Color.WHITE);
        } else {
            mSelectedItems.put(position, true);
            v.setBackgroundColor(Color.BLUE);
        }

    }
});
public void onBindViewHolder(@NonNull StdViewHolder holder, int position) {
    holder.textView.setText(mdata.get(position));

    if ( mSelectedItems.get(position, false) ){
        holder.itemView.setBackgroundColor(Color.BLUE);
    } else {
        holder.itemView.setBackgroundColor(Color.WHITE);
    }
}

 

이제 의도한 대로 동작하는 것 같습니다.

 

하지만 RecyclerView 외부에서 특정 아이템을 선택하는 경우나,

선택된 아이템 전체를 해제(Clear)하는 것은 현재 구조로는 어려워 보이네요.

그리고 선택된 아이템의 배경색 변경도 한곳으로 몰고 싶어집니다.

 

 

아이템의 선택상태가 변경되는 시점( 아이템의 클릭이벤트가 발생하거나 아이템 선택/해제 Methord가 호출되었을때)에 선택 상태를 저장(갱신)하고

데이터가 View에 바인딩되는 시점인 onBindViewHolder() 에서 배경색을 변경하도록 처리하면 될것 같습니다.

 

다만 onBindViewHolder()가 호출되는 시점은 View에 Data를 바인드해야 하는 상황이므로,

Adapter에게 데이터를 다시 바인딩 해야 함을 알려주어야 합니다.

우리가 작성한 코드에서는 setData() 호출을 통해 데이터 셋을 설정하는 코드입니다.

 

public void setData(List<String> data) {
    mdata = data;
    notifyDataSetChanged();
}

 

RecyclerView Adapter는  표시되는 데이터를 다시 바인딩해야 하는 상황에서 

Adapter에게 알려줄수 있도록 notify~ 류의 메소드를 제공합니다.

 

 

 

데이터가 변경된건 아니지만, 강제로 데이터를 다시 그리도록 처리하는 방법입니다.

일종의 트릭이겠죠?

 

public class StdRecyclerAdapter extends RecyclerView.Adapter<StdRecyclerAdapter.StdViewHolder> {

    
    @Override
    public void onBindViewHolder(@NonNull StdViewHolder holder, int position) {
        holder.textView.setText(mdata.get(position));

        if (isItemSelected(position)) {
            holder.itemView.setBackgroundColor(Color.BLUE);
        } else {
            holder.itemView.setBackgroundColor(Color.WHITE);
        }
    }

    

    public class StdViewHolder extends RecyclerView.ViewHolder {
        public TextView textView;

        public StdViewHolder(@NonNull View itemView) {
            super(itemView);
            this.textView = itemView.findViewById(R.id.textView);

            itemView.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    int position = getAdapterPosition();
                    toggleItemSelected(position);
                }
            });
        }
    }

 

아이템의 선택 상태 관리는 별도의 메소드로 분리했습니다.

private void toggleItemSelected(int position) {

    if (mSelectedItems.get(position, false) == true) {
        mSelectedItems.delete(position);
        notifyItemChanged(position);
    } else {
        mSelectedItems.put(position, true);
        notifyItemChanged(position);
    }
}

private boolean isItemSelected(int position) {
    return mSelectedItems.get(position, false);
}

public void clearSelectedItem() {
    int position;

    for (int i = 0; i < mSelectedItems.size(); i++) {
        position = mSelectedItems.keyAt(i);
        mSelectedItems.put(position, false);
        notifyItemChanged(position);
    }

    mSelectedItems.clear();
}

 

전체 선택 해제 버튼도 하나 추가했구요.

의도한 대로 잘 동작하는 것 같습니다.

 

 

 

마지막으로 

BackgroundColor 를 변경하는 부분을 XML을 통해 처리 할 수 있습니다.

Xml selector에 select된 상태와 select되지 않은 상태에 적용할 색상 혹은 이미지를 지정하고,

대상 위젯의 BackGround속성으로 해당 selector를 할당 하는 방식입니다.

 

적용하는 방법은 아래와 같습니다.

 

"drawable" 폴더 하위에 선택 상태에 따라 표시할 색상을 지정하는 item_selector.xml을 작성합니다.

( 이때 xml 파일이 drawable-hdpi, drawable-hdpi, drawable-v24 등과 같은 사이즈 종속적인 폴더에 들어가지 않도록 주의 해야 합니다.

샘플 작업시, 해당 파일을 drawable-v24 폴더에 생성하는 바람에 테스트한 실제 장비에서는 정상적으로 동작하였지만,

Emulater에서 해당 selector를 적용한 Layout에 대해 아래와 같은 inflate 에러 메시지가 발생하여  앱이 종료된바 있습니다.)

android.view.InflateException: Binary XML file line #14: Binary XML file line #14: Error inflating class <unknown>

 

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:state_selected="false">
        <color android:color="@color/normal_item_bg" />
    </item>
    <item android:state_selected="true">
        <color android:color="@color/selected_item_bg" />
    </item>
</selector>

 

사용되는 배경색으로 사용되는 색상은 color.xml에 정의 되어 있어야 합니다.

<?xml version="1.0" encoding="utf-8"?>
<resources>
    
    <color name="normal_item_bg">#FFFFFFFF</color>
    <color name="selected_item_bg">#FF0000FF</color>

</resources>

 

 

아이템을 표시할 Layout의 Root Layout에 Background를 다음과 같이 지정합니다.

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical"
    android:background="@drawable/item_selector">

 

 

onBindViewHolder의 코드를 다음과 같이 수정하면 선택 상태에 따라 자동으로 배경색이 변경되게 됩니다.

( onCreateViewHolder에서 Inflate된 View와 layout xml의 root LinearLayout이 같은 지는 모르겠으나,

적어도 Layout간에는 상위 Layout의 선택 상태가 하위 Layout에도 반영되는 것으로 보입니다.)

public void onBindViewHolder(@NonNull StdViewHolder holder, int position) {
    holder.textView.setText(mdata.get(position));

    holder.itemView.setSelected(isItemSelected(position));
}

 

 

 

 

롱클릭 이후 데이터를 선택하게 하거나, 단일 선택등의 처리는 조금 응용하면 가능할 것으로 생각됩니다.

 

<< 전체코드 >

 

StdRecyclerAdapter.java

public class StdRecyclerAdapter extends RecyclerView.Adapter<StdRecyclerAdapter.StdViewHolder> {

public interface OnListItemLongSelectedInterface {
void onItemLongSelected(View v, int position);
}

public interface OnListItemSelectedInterface {
void onItemSelected(View v, int position);
}

private OnListItemSelectedInterface mListener;
private OnListItemLongSelectedInterface mLongListener;


private SparseBooleanArray mSelectedItems = new SparseBooleanArray(0);

Context mContext;
List<String> mdata;
RecyclerView recyclerView;

public StdRecyclerAdapter(Context context
, RecyclerView recyclerView
, OnListItemSelectedInterface listener
, OnListItemLongSelectedInterface longListener) {
this.mContext = context;
this.mListener = listener;
this.mLongListener = longListener;
this.recyclerView = recyclerView;
}

public void setData(List<String> data) {
mdata = data;
notifyDataSetChanged();
}

@NonNull
@Override
public StdViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
LayoutInflater inflate = LayoutInflater.from(mContext);
View view = inflate.inflate(R.layout.list_item, parent, false);

StdViewHolder vh = new StdViewHolder(view);
return vh;
}

@Override
public void onBindViewHolder(@NonNull StdViewHolder holder, int position) {
holder.textView.setText(mdata.get(position));

holder.itemView.setSelected(isItemSelected(position));
}

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

public class StdViewHolder extends RecyclerView.ViewHolder {
public TextView textView;

public StdViewHolder(@NonNull View itemView) {
super(itemView);
this.textView = itemView.findViewById(R.id.textView);

itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
int position = getAdapterPosition();
toggleItemSelected(position);

Log.d("test", "position = " + position);
}
});

itemView.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
mLongListener.onItemLongSelected(v, getAdapterPosition());
return false;
}
});
}
}

private void toggleItemSelected(int position) {

if (mSelectedItems.get(position, false) == true) {
mSelectedItems.delete(position);
notifyItemChanged(position);
} else {
mSelectedItems.put(position, true);
notifyItemChanged(position);
}
}

private boolean isItemSelected(int position) {
return mSelectedItems.get(position, false);
}

public void clearSelectedItem() {
int position;

for (int i = 0; i < mSelectedItems.size(); i++) {
position = mSelectedItems.keyAt(i);
mSelectedItems.put(position, false);
notifyItemChanged(position);
}

mSelectedItems.clear();
}
}

 

MainActivity.java

public class MainActivity extends AppCompatActivity implements StdRecyclerAdapter.OnListItemLongSelectedInterface
        , StdRecyclerAdapter.OnListItemSelectedInterface{

    RecyclerView recyclerView;

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

        init();
    }

    private void init(){
        recyclerView = (RecyclerView) findViewById(R.id.recyclerView);

        LinearLayoutManager layoutManager = new LinearLayoutManager(this);
        recyclerView.setLayoutManager(layoutManager);

        List<String> dataSet = new ArrayList<String>();

        int row = 1;
        for ( int i = 0; i < 10 ; i++) {
            dataSet.add("<" + row++ + ">" + "C/C++");
            dataSet.add("<" + row++ + ">" + "Java");
            dataSet.add("<" + row++ + ">" + "Kotlin");
            dataSet.add("<" + row++ + ">" + "Python");
            dataSet.add("<" + row++ + ">" + "Ruby");
        }

        final StdRecyclerAdapter mAdapter = new StdRecyclerAdapter(this, recyclerView, this,this);
        recyclerView.setAdapter(mAdapter);
        mAdapter.setData(dataSet);

         // 기본 구분선 추가
        DividerItemDecoration dividerItemDecoration =
                new DividerItemDecoration(recyclerView.getContext(),new LinearLayoutManager(this).getOrientation());
        recyclerView.addItemDecoration(dividerItemDecoration);

        // 아이템간 공백 추가
        RecyclerDecoration spaceDecoration = new RecyclerDecoration(20);
        recyclerView.addItemDecoration(spaceDecoration);

        Button btnClear = (Button) findViewById(R.id.btnClear);
        btnClear.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                mAdapter.clearSelectedItem();
            }
        });
    }

    @Override
    public void onItemSelected(View v, int position) {
        StdRecyclerAdapter.StdViewHolder viewHolder = (StdRecyclerAdapter.StdViewHolder)recyclerView.findViewHolderForAdapterPosition(position);
        Toast.makeText(this, viewHolder.textView.getText().toString(), Toast.LENGTH_SHORT).show();
//        Toast.makeText(this, position + " clicked", Toast.LENGTH_SHORT).show();
    }

    @Override
    public void onItemLongSelected(View v, int position) {
        Toast.makeText(this, position + " long clicked", Toast.LENGTH_SHORT).show();
    }

}

activity_main.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=".MainActivity">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical">

        <Button
            android:id="@+id/btnClear"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="Clear Selected Item" />

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

</LinearLayout>

list_item.xml

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

    <TextView
        android:id="@+id/textView"
        android:layout_width="match_parent"
        android:layout_height="30dp"
        android:layout_margin="8dp"
        android:text="TextView" />
</LinearLayout>

item_selector.xml

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:state_selected="false">
        <color android:color="@color/normal_item_bg" />
    </item>
    <item android:state_selected="true">
        <color android:color="@color/selected_item_bg" />
    </item>
</selector>

color.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <color name="colorPrimary">#008577</color>
    <color name="colorPrimaryDark">#00574B</color>
    <color name="colorAccent">#D81B60</color>

    <color name="normal_item_bg">#FFFFFFFF</color>
    <color name="selected_item_bg">#FF0000FF</color>

</resources>
 

 

반응형
반응형

RecyclerView #4 - 아이템 클릭 처리


RecyclerView #1- 구조및 기본 사용법

RecyclerView #2 - 구분선 추가, 아이템간 간격 조절

RecyclerView #3 - 컨텍스트 메뉴 처리

RecyclerView #4 - 아이템 클릭 처리

RecyclerView #5 - 아이템 선택 처리하기

RecyclerView #6 - ItemView를 클래스화 하기

RecyclerView #7 - ViewType 동적변경


RecyclerView에 표시된 아이템을 클릭했을때의 처리 방법


#3에서 RecyclerView에 Context Menu를 연결하는 방법을 간단히 기술했습니다.

그외에도 아이템을 클릭했을때, 특정한 처리를 수행하는 것 또한

RecyclerView의 일반적인 사용패턴이므로 일단 정리해 봅니다.


ViewHolder의 ItemView에 onClick Listener 설치

ViewHolder가 보관하는 것은 결국 View Class(혹은 상속받은 객체)이기 때문에, onClick Listener 를 설치해서 처리하면 됩니다.

ViewHolder의 itemView에 onClick Listener를 설치하면 되는 것이죠.



onClick Listener를 설치 할만한 위치로는 어디가 좋을까요?


1. Apdater의 onBindViewHolder

생성된 ViewHolder에 대해 데이터가 바인딩 될때마다 호출되는 Methord로  굳이 여기서 반복해서 등록할 이유는 없습니다.

public void onBindViewHolder(@NonNull StdViewHolder holder, int position) {
holder.textView.setText(mdata.get(position));
}


2. Adapter의 onCreateViewHolder

ViewHolder를 생성하는 부분으로, inflate된 view에 Listener를 설치할 수 있겠습니다.

public StdViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
LayoutInflater inflate = LayoutInflater.from(mContext);
View view = inflate.inflate(R.layout.list_item, parent, false);

StdViewHolder vh = new StdViewHolder(view);
return vh;
}

3. ViewHolder의 생성자

인자로 전달된 itemView에 Listener를 등록하는 방법도 유효하겠네요.

public StdViewHolder(@NonNull View itemView) {
super(itemView);
this.textView = itemView.findViewById(R.id.textView);


사실 onClick listener를 등록하는 위치는 2번(onCreateViewHolder)이나 3번(ViewHolder 생성자) 모두 유효해 보입니다만.


개인적으로는 View를 관리하는 ViewHolder쪽에서 처리하는 것이 좀 더 깔끔해 보입니다.

onClick이벤트 처리기 내에서 클릭된 Item의 위치를 가져올수 있는 방법을 

ViewHolder가 제공(ViewHolder.getAdapterPosition)하고 있다는 것도 또 하나의 이유일 수도 있겠습니다.

public class StdViewHolder extends RecyclerView.ViewHolder {
public TextView textView;

public StdViewHolder(@NonNull View itemView) {
super(itemView);
this.textView = itemView.findViewById(R.id.textView);

itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {

Log.d("Recyclerview", "position = "+ getAdapterPosition());
}
});

itemView.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
Log.d("Recyclerview", "position = "+ getAdapterPosition());
return false;
}
});
}
}


RecyclerView의 Item Click을 Activity나 Fragment에 전달하기

위에서 설명한 것처럼 ViewHolder에서 onClickListener를 통해 아이템 클릭 처리를 수행할 수 있습니다만,

개발을 하다보니 RecyclerView를 포함하고 있는 Activity나 Fragment에서 '아이템 클릭'에 대한 처리를 수행하는것이

수월할 경우가 많이 있습니다.


여러가지 방법이 있겠지만,

 Activity에서 Adapter에 Listenner를 전달하고 

ViewHolder의 onClickListener에서 Activity의 Listener를 호출하는 방식으로

다음과 같이 적용해 볼수 있습니다.


1. Adapter & ViewHolder

public class StdRecyclerAdapter extends RecyclerView.Adapter<StdRecyclerAdapter.StdViewHolder> {

public interface OnListItemLongSelectedInterface {
void onItemLongSelected(View v, int position);
}

public interface OnListItemSelectedInterface {
void onItemSelected(View v, int position);
}

private OnListItemSelectedInterface mListener;
private OnListItemLongSelectedInterface mLongListener;


Context mContext;
List<String> mdata;
RecyclerView recyclerView;

public StdRecyclerAdapter(Context context
, OnListItemSelectedInterface listener
, OnListItemLongSelectedInterface longListener) {
this.mContext = context;
this.mListener = listener;
this.mLongListener = longListener;
}


public class StdViewHolder extends RecyclerView.ViewHolder {
public TextView textView;

public StdViewHolder(@NonNull View itemView) {
super(itemView);

itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
int position = getAdapterPosition();
mListener.onItemSelected(v, getAdapterPosition());

Log.d("test", "position = "+ getAdapterPosition());
}
});

itemView.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
mLongListener.onItemLongSelected(v, getAdapterPosition());
return false;
}
});
}
}
}

2. MainActivity

public class MainActivity extends AppCompatActivity implements StdRecyclerAdapter.OnListItemLongSelectedInterface
, StdRecyclerAdapter.OnListItemSelectedInterface{


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


StdRecyclerAdapter mAdapter = new StdRecyclerAdapter(this, this,this);
recyclerView.setAdapter(mAdapter);
mAdapter.setData(dataSet);

}

@Override
public void onItemSelected(View v, int position) {
StdRecyclerAdapter.StdViewHolder viewHolder = (StdRecyclerAdapter.StdViewHolder)recyclerView.findViewHolderForAdapterPosition(position);
Toast.makeText(this, viewHolder.textView.getText().toString(), Toast.LENGTH_SHORT).show();
}

@Override
public void onItemLongSelected(View v, int position) {
Toast.makeText(this, position + " long clicked", Toast.LENGTH_SHORT).show();
}


 


반응형
반응형

RecyclerView #3 - 컨텍스트 메뉴 처리


RecyclerView #1- 구조및 기본 사용법

RecyclerView #2 - 구분선 추가, 아이템간 간격 조절

RecyclerView #3 - 컨텍스트 메뉴 처리

RecyclerView #4 - 아이템 클릭 처리

RecyclerView #5 - 아이템 선택 처리하기

RecyclerView #6 - ItemView를 클래스화 하기

RecyclerView #7 - ViewType 동적변경


CreateContextMenu 리스너 설정


public static class StdViewHolder extends RecyclerView.ViewHolder
implements View.OnCreateContextMenuListener{
}
public StdViewHolder(@NonNull View itemView) {
super(itemView);
this.textView = itemView.findViewById(R.id.textView);

itemView.setOnCreateContextMenuListener(this);
}


메뉴 구성

아래와 같이 동적으로 메뉴를 구성하거나, 리소스를 통해 메뉴를 inflate시키면 된다.
다만 메뉴를 Inflate 하기 위해 필요한 Context는 어딘가 에서 넘겨받거나 구해와야....

@Override
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
MenuItem Edit = menu.add(Menu.NONE, R.id.add_menu, 1, "add");
MenuItem Delete = menu.add(Menu.NONE, R.id.delete_menu, 2, "delete");
Edit.setOnMenuItemClickListener(onMenuItemClickListener);
Delete.setOnMenuItemClickListener(onMenuItemClickListener);

}

메뉴 선택 처리

선택된 아이템에 대해 처리한다.
private final MenuItem.OnMenuItemClickListener onMenuItemClickListener = new MenuItem.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem item) {
switch (item.getItemId()) {
case R.id.add_menu:
return true;

case R.id.delete_menu:
return true;
}
return false;
}
};

전체코드

public class StdRecyclerAdapter extends RecyclerView.Adapter<StdRecyclerAdapter.StdViewHolder> {


Context mContext;
List<String> mdata;
RecyclerView recyclerView;

public StdRecyclerAdapter(Context context) {
this.mContext = context;
}

public void setData(List<String> data) {
mdata = data;
notifyDataSetChanged();
}

@NonNull
@Override
public StdViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
LayoutInflater inflate = LayoutInflater.from(mContext);
View view = inflate.inflate(R.layout.list_item, parent, false);

StdViewHolder vh = new StdViewHolder(view);
return vh;
}

@Override
public void onBindViewHolder(@NonNull StdViewHolder holder, int position) {
holder.textView.setText(mdata.get(position));
}

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


public static class StdViewHolder extends RecyclerView.ViewHolder
implements View.OnCreateContextMenuListener{
public TextView textView;

public StdViewHolder(@NonNull View itemView) {
super(itemView);
this.textView = itemView.findViewById(R.id.textView);

itemView.setOnCreateContextMenuListener(this);
}

@Override
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
MenuItem Edit = menu.add(Menu.NONE, R.id.add_menu, 1, "add");
MenuItem Delete = menu.add(Menu.NONE, R.id.delete_menu, 2, "delete");
Edit.setOnMenuItemClickListener(onMenuItemClickListener);
Delete.setOnMenuItemClickListener(onMenuItemClickListener);
}

private final MenuItem.OnMenuItemClickListener onMenuItemClickListener = new MenuItem.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem item) {
switch (item.getItemId()) {
case R.id.add_menu:
return true;

case R.id.delete_menu:
return true;
}
return false;
}
};
}
}


반응형
반응형

RecyclerView #2 - 구분선 추가, 아이템간 간격 조절


RecyclerView #1- 구조및 기본 사용법

RecyclerView #2 - 구분선 추가, 아이템간 간격 조절

RecyclerView #3 - 컨텍스트 메뉴 처리

RecyclerView #4 - 아이템 클릭 처리

RecyclerView #5 - 아이템 선택 처리하기

RecyclerView #6 - ItemView를 클래스화 하기

RecyclerView #7 - ViewType 동적변경


RecyclerView.ItemDecoration


기본 구분선(DividerItemDecoration) 추가

 // 기본 구분선 추가
DividerItemDecoration dividerItemDecoration =
new DividerItemDecoration(recyclerView.getContext(),new LinearLayoutManager(this).getOrientation());
recyclerView.addItemDecoration(dividerItemDecoration);


아아템 사이의 간격 조절

RecyclerDecoration.java


package com.thirteenbrains.unodir.stdrecyclerview;

import android.graphics.Rect;
import android.support.annotation.NonNull;
import android.support.v7.widget.RecyclerView;
import android.view.View;

public class RecyclerDecoration extends RecyclerView.ItemDecoration {

private final int divHeight;


public RecyclerDecoration(int divHeight) {
this.divHeight = divHeight;
}

@Override
public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
super.getItemOffsets(outRect, view, parent, state);
if (parent.getChildAdapterPosition(view) != parent.getAdapter().getItemCount() - 1)

outRect.bottom = divHeight;

}
}

MainActivity.java

RecyclerDecoration spaceDecoration = new RecyclerDecoration(20);
recyclerView.addItemDecoration(spaceDecoration);


반응형

+ Recent posts