다중보기 유형으로 RecyclerView를 만드는 방법은 무엇입니까?
에서 https://developer.android.com/preview/material/ui-widgets.html
만들 때 어댑터와 바인딩 할 RecyclerView.Adapter
것을 지정해야 ViewHolder
합니다.
public class MyAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder> {
private String[] mDataset;
public MyAdapter(String[] myDataset) {
mDataset = myDataset;
}
public static class ViewHolder extends RecyclerView.ViewHolder {
public TextView mTextView;
public ViewHolder(TextView v) {
super(v);
mTextView = v;
}
}
@Override
public MyAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.some_layout, parent, false);
//findViewById...
ViewHolder vh = new ViewHolder(v);
return vh;
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
holder.mTextView.setText(mDataset[position]);
}
@Override
public int getItemCount() {
return mDataset.length;
}
}
그렇다면 RecyclerView
여러 뷰 유형 으로 만들 수 있습니까?
예, 가능합니다. 그냥 구현 getItemViewType을 () 하고 돌봐 viewType
에서 매개 변수 onCreateViewHolder()
.
따라서 다음과 같이합니다.
public class MyAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
class ViewHolder0 extends RecyclerView.ViewHolder {
...
public ViewHolder0(View itemView){
...
}
}
class ViewHolder2 extends RecyclerView.ViewHolder {
...
public ViewHolder2(View itemView){
...
}
@Override
public int getItemViewType(int position) {
// Just as an example, return 0 or 2 depending on position
// Note that unlike in ListView adapters, types don't have to be contiguous
return position % 2 * 2;
}
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
switch (viewType) {
case 0: return new ViewHolder0(...);
case 2: return new ViewHolder2(...);
...
}
}
@Override
public void onBindViewHolder(final RecyclerView.ViewHolder holder, final int position) {
switch (holder.getItemViewType()) {
case 0:
ViewHolder0 viewHolder0 = (ViewHolder0)holder;
...
break;
case 2:
ViewHolder2 viewHolder2 = (ViewHolder2)holder;
...
break;
}
}
}
보기 유형의 레이아웃이 몇 개에 불과하고 바인딩 논리가 간단하다면 Anton의 솔루션을 따르십시오.
그러나 복잡한 레이아웃과 바인딩 논리를 관리해야하는 경우 코드가 지저분해질 것입니다.
복잡한 뷰 유형을 처리해야하는 사람에게 다음 솔루션이 유용 할 것이라고 생각합니다.
기본 DataBinder 클래스
abstract public class DataBinder<T extends RecyclerView.ViewHolder> {
private DataBindAdapter mDataBindAdapter;
public DataBinder(DataBindAdapter dataBindAdapter) {
mDataBindAdapter = dataBindAdapter;
}
abstract public T newViewHolder(ViewGroup parent);
abstract public void bindViewHolder(T holder, int position);
abstract public int getItemCount();
......
}
이 클래스에서 정의하는 데 필요한 함수는 단일 뷰 유형을 만들 때 어댑터 클래스와 거의 동일합니다.
각보기 유형에 대해이 DataBinder를 확장하여 클래스를 작성하십시오.
샘플 DataBinder 클래스
public class Sample1Binder extends DataBinder<Sample1Binder.ViewHolder> {
private List<String> mDataSet = new ArrayList();
public Sample1Binder(DataBindAdapter dataBindAdapter) {
super(dataBindAdapter);
}
@Override
public ViewHolder newViewHolder(ViewGroup parent) {
View view = LayoutInflater.from(parent.getContext()).inflate(
R.layout.layout_sample1, parent, false);
return new ViewHolder(view);
}
@Override
public void bindViewHolder(ViewHolder holder, int position) {
String title = mDataSet.get(position);
holder.mTitleText.setText(title);
}
@Override
public int getItemCount() {
return mDataSet.size();
}
public void setDataSet(List<String> dataSet) {
mDataSet.addAll(dataSet);
}
static class ViewHolder extends RecyclerView.ViewHolder {
TextView mTitleText;
public ViewHolder(View view) {
super(view);
mTitleText = (TextView) view.findViewById(R.id.title_type1);
}
}
}
DataBinder 클래스를 관리하려면 어댑터 클래스를 작성하십시오.
기본 DataBindAdapter 클래스
abstract public class DataBindAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
return getDataBinder(viewType).newViewHolder(parent);
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int position) {
int binderPosition = getBinderPosition(position);
getDataBinder(viewHolder.getItemViewType()).bindViewHolder(viewHolder, binderPosition);
}
@Override
public abstract int getItemCount();
@Override
public abstract int getItemViewType(int position);
public abstract <T extends DataBinder> T getDataBinder(int viewType);
public abstract int getPosition(DataBinder binder, int binderPosition);
public abstract int getBinderPosition(int position);
......
}
이 기본 클래스를 확장하여 클래스를 만든 다음 DataBinder 클래스를 인스턴스화하고 추상 메서드를 재정의합니다.
getItemCount
DataBinder의 총 항목 수를 반환합니다.getItemViewType
어댑터 위치와보기 유형 간의 매핑 논리를 정의합니다.getDataBinder
보기 유형에 따라 DataBinder 인스턴스를 반환합니다.getPosition
지정된 DataBinder의 위치에서 어댑터 위치로의 변환 논리를 정의합니다.getBinderPosition
어댑터 위치에서 DataBinder의 위치로 논리 변환을 정의합니다.
이 솔루션이 도움이되기를 바랍니다.
GitHub에 더 자세한 솔루션과 샘플을 남겨 두 었으니 필요하다면 다음 링크를 참조하십시오.
https://github.com/yqritc/RecyclerView-MultipleViewTypesAdapter
아래는 의사 코드가 아니며 테스트했으며 나를 위해 일했습니다.
내 recyclerview에서 headerview를 만든 다음 사용자가 클릭 할 수있는 헤더 아래에 그림 목록을 표시하고 싶었습니다.
내 코드에서 몇 가지 스위치를 사용했지만 이것이 가장 효율적인 방법인지 모르겠으므로 언제든지 의견을 보내주십시오.
public class ViewHolder extends RecyclerView.ViewHolder{
//These are the general elements in the RecyclerView
public TextView place;
public ImageView pics;
//This is the Header on the Recycler (viewType = 0)
public TextView name, description;
//This constructor would switch what to findViewBy according to the type of viewType
public ViewHolder(View v, int viewType) {
super(v);
if (viewType == 0) {
name = (TextView) v.findViewById(R.id.name);
decsription = (TextView) v.findViewById(R.id.description);
} else if (viewType == 1) {
place = (TextView) v.findViewById(R.id.place);
pics = (ImageView) v.findViewById(R.id.pics);
}
}
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent,
int viewType)
{
View v;
ViewHolder vh;
// create a new view
switch (viewType) {
case 0: //This would be the header view in my Recycler
v = LayoutInflater.from(parent.getContext())
.inflate(R.layout.recyclerview_welcome, parent, false);
vh = new ViewHolder(v,viewType);
return vh;
default: //This would be the normal list with the pictures of the places in the world
v = LayoutInflater.from(parent.getContext())
.inflate(R.layout.recyclerview_picture, parent, false);
vh = new ViewHolder(v, viewType);
v.setOnClickListener(new View.OnClickListener(){
@Override
public void onClick(View v) {
Intent intent = new Intent(mContext, nextActivity.class);
intent.putExtra("ListNo",mRecyclerView.getChildPosition(v));
mContext.startActivity(intent);
}
});
return vh;
}
}
//Overriden so that I can display custom rows in the recyclerview
@Override
public int getItemViewType(int position) {
int viewType = 1; //Default is 1
if (position == 0) viewType = 0; //if zero, it will be a header view
return viewType;
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
//position == 0 means its the info header view on the Recycler
if (position == 0) {
holder.name.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(mContext,"name clicked", Toast.LENGTH_SHORT).show();
}
});
holder.description.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(mContext,"description clicked", Toast.LENGTH_SHORT).show();
}
});
//this means it is beyond the headerview now as it is no longer 0. For testing purposes, I'm alternating between two pics for now
} else if (position > 0) {
holder.place.setText(mDataset[position]);
if (position % 2 == 0) {
holder.pics.setImageDrawable(mContext.getResources().getDrawable(R.drawable.pic1));
}
if (position % 2 == 1) {
holder.pics.setImageDrawable(mContext.getResources().getDrawable(R.drawable.pic2));
}
}
}
예, 가능합니다.
일반 뷰 홀더 작성 :
public abstract class GenericViewHolder extends RecyclerView.ViewHolder
{
public GenericViewHolder(View itemView) {
super(itemView);
}
public abstract void setDataOnView(int position);
}
그런 다음 뷰 홀더를 만들고 GenericViewHolder를 확장합니다. 예를 들면 다음과 같습니다.
public class SectionViewHolder extends GenericViewHolder{
public final View mView;
public final TextView dividerTxtV;
public SectionViewHolder(View itemView) {
super(itemView);
mView = itemView;
dividerTxtV = (TextView) mView.findViewById(R.id.dividerTxtV);
}
@Override
public void setDataOnView(int position) {
try {
String title= sections.get(position);
if(title!= null)
this.dividerTxtV.setText(title);
}catch (Exception e){
new CustomError("Error!"+e.getMessage(), null, false, null, e);
}
}
}
RecyclerView.Adapter 클래스는 다음과 같습니다.
public class MyClassRecyclerViewAdapter extends RecyclerView.Adapter<MyClassRecyclerViewAdapter.GenericViewHolder> {
@Override
public int getItemViewType(int position) {
// depends on your problem
switch (position) {
case : return VIEW_TYPE1;
case : return VIEW_TYPE2;
...
}
}
@Override
public GenericViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view;
if(viewType == VIEW_TYPE1){
view = LayoutInflater.from(parent.getContext()).inflate(R.layout.layout1, parent, false);
return new SectionViewHolder(view);
}else if( viewType == VIEW_TYPE2){
view = LayoutInflater.from(parent.getContext()).inflate(R.layout.layout2, parent, false);
return new OtherViewHolder(view);
}
// Cont. other view holders ...
return null;
}
@Override
public void onBindViewHolder(GenericViewHolder holder, int position) {
holder.setDataOnView(position);
}
다른 레이아웃에 대해 다른 ViewHolder 만들기
RecyclerView는 원하는 수의 뷰 홀더를 가질 수 있지만 더 나은 가독성을 위해 두 개의 뷰 홀더로 하나를 만드는 방법을 볼 수 있습니다.
간단한 세 단계로 수행 할 수 있습니다.
- 우세하다
public int getItemViewType(int position)
onCreateViewHolder()
메서드 의 ViewType에 따라 다른 ViewHolders 반환onBindViewHolder()
메서드 의 itemViewType을 기반으로 뷰 채우기
다음은 작은 코드 스 니펫입니다.
public class YourListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private static final int LAYOUT_ONE= 0;
private static final int LAYOUT_TWO= 1;
@Override
public int getItemViewType(int position)
{
if(position==0)
return LAYOUT_ONE;
else
return LAYOUT_TWO;
}
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view =null;
RecyclerView.ViewHolder viewHolder = null;
if(viewType==LAYOUT_ONE)
{
view = LayoutInflater.from(parent.getContext()).inflate(R.layout.one,parent,false);
viewHolder = new ViewHolderOne(view);
}
else
{
view = LayoutInflater.from(parent.getContext()).inflate(R.layout.two,parent,false);
viewHolder= new ViewHolderTwo(view);
}
return viewHolder;
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, final int position) {
if(holder.getItemViewType()== LAYOUT_ONE)
{
// Typecast Viewholder
// Set Viewholder properties
// Add any click listener if any
}
else {
ViewHolderOne vaultItemHolder = (ViewHolderOne) holder;
vaultItemHolder.name.setText(displayText);
vaultItemHolder.name.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
.......
}
});
}
}
//**************** VIEW HOLDER 1 ******************//
public class ViewHolderOne extends RecyclerView.ViewHolder {
public TextView name;
public ViewHolderOne(View itemView) {
super(itemView);
name = (TextView)itemView.findViewById(R.id.displayName);
}
}
//**************** VIEW HOLDER 2 ******************//
public class ViewHolderTwo extends RecyclerView.ViewHolder{
public ViewHolderTwo(View itemView) {
super(itemView);
..... Do something
}
}
}
getItemViewType (int position)은 키입니다.
제 생각에 이런 종류의 recyclerView를 만드는 시작점은이 방법에 대한 지식입니다. 이 메서드는 재정의하는 선택 사항이므로 기본적으로 RecylerView 클래스에 표시되지 않으므로 저를 포함한 많은 개발자가 어디서부터 시작해야할지 궁금해합니다. 이 방법이 존재한다는 것을 알고 나면 이러한 RecyclerView를 만드는 것은 쉬운 일이 아닙니다.
내 요점을 증명하는 한 가지 예를 살펴 보겠습니다. 두 개의 레이아웃을 다른 위치에 표시하려면 다음을 수행하십시오.
@Override
public int getItemViewType(int position)
{
if(position%2==0) // Even position
return LAYOUT_ONE;
else // Odd position
return LAYOUT_TWO;
}
관련 링크 :
내가 이것을 구현 한 프로젝트를 확인하십시오.
예, 가능합니다. 어댑터에서 다음과 같이 getItemViewType 레이아웃 ....
public class MultiViewTypeAdapter extends RecyclerView.Adapter {
private ArrayList<Model>dataSet;
Context mContext;
int total_types;
MediaPlayer mPlayer;
private boolean fabStateVolume = false;
public static class TextTypeViewHolder extends RecyclerView.ViewHolder {
TextView txtType;
CardView cardView;
public TextTypeViewHolder(View itemView) {
super(itemView);
this.txtType = (TextView) itemView.findViewById(R.id.type);
this.cardView = (CardView) itemView.findViewById(R.id.card_view);
}
}
public static class ImageTypeViewHolder extends RecyclerView.ViewHolder {
TextView txtType;
ImageView image;
public ImageTypeViewHolder(View itemView) {
super(itemView);
this.txtType = (TextView) itemView.findViewById(R.id.type);
this.image = (ImageView) itemView.findViewById(R.id.background);
}
}
public static class AudioTypeViewHolder extends RecyclerView.ViewHolder {
TextView txtType;
FloatingActionButton fab;
public AudioTypeViewHolder(View itemView) {
super(itemView);
this.txtType = (TextView) itemView.findViewById(R.id.type);
this.fab = (FloatingActionButton) itemView.findViewById(R.id.fab);
}
}
public MultiViewTypeAdapter(ArrayList<Model>data, Context context) {
this.dataSet = data;
this.mContext = context;
total_types = dataSet.size();
}
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view;
switch (viewType) {
case Model.TEXT_TYPE:
view = LayoutInflater.from(parent.getContext()).inflate(R.layout.text_type, parent, false);
return new TextTypeViewHolder(view);
case Model.IMAGE_TYPE:
view = LayoutInflater.from(parent.getContext()).inflate(R.layout.image_type, parent, false);
return new ImageTypeViewHolder(view);
case Model.AUDIO_TYPE:
view = LayoutInflater.from(parent.getContext()).inflate(R.layout.audio_type, parent, false);
return new AudioTypeViewHolder(view);
}
return null;
}
@Override
public int getItemViewType(int position) {
switch (dataSet.get(position).type) {
case 0:
return Model.TEXT_TYPE;
case 1:
return Model.IMAGE_TYPE;
case 2:
return Model.AUDIO_TYPE;
default:
return -1;
}
}
@Override
public void onBindViewHolder(final RecyclerView.ViewHolder holder, final int listPosition) {
Model object = dataSet.get(listPosition);
if (object != null) {
switch (object.type) {
case Model.TEXT_TYPE:
((TextTypeViewHolder) holder).txtType.setText(object.text);
break;
case Model.IMAGE_TYPE:
((ImageTypeViewHolder) holder).txtType.setText(object.text);
((ImageTypeViewHolder) holder).image.setImageResource(object.data);
break;
case Model.AUDIO_TYPE:
((AudioTypeViewHolder) holder).txtType.setText(object.text);
}
}
}
@Override
public int getItemCount() {
return dataSet.size();
}
}
참조 링크 : https://www.journaldev.com/12372/android-recyclerview-example
Anton의 솔루션에 ViewHolder
따라 다양한 유형의 레이아웃을 보유 / 처리 / 위임하는 이 를 생각해 냅니다. 그러나 재활용 뷰 ViewHolder
가 데이터 롤인 유형이 아닌 경우 새 레이아웃 교체가 작동하는지 확실 하지 않습니다.
따라서 기본적으로 onCreateViewHolder(ViewGroup parent, int viewType)
새 뷰 레이아웃이 필요할 때만 호출됩니다.
getItemViewType(int position)
에 대해 호출됩니다 viewType
;
onBindViewHolder(ViewHolder holder, int position)
뷰를 재활용 할 때 항상 호출됩니다 (새 데이터를 가져 와서 표시하려고합니다 ViewHolder
).
따라서를 onBindViewHolder
호출하면 올바른 뷰 레이아웃을 배치하고 ViewHolder
.
ViewHolder
가져올 뷰 레이아웃을 바꾸는 방법이 맞 습니까? 아니면 문제가 있습니까? 어떤 코멘트라도 감사합니다!
public int getItemViewType(int position) {
TypedData data = mDataSource.get(position);
return data.type;
}
public ViewHolder onCreateViewHolder(ViewGroup parent,
int viewType) {
return ViewHolder.makeViewHolder(parent, viewType);
}
public void onBindViewHolder(ViewHolder holder,
int position) {
TypedData data = mDataSource.get(position);
holder.updateData(data);
}
///
public static class ViewHolder extends
RecyclerView.ViewHolder {
ViewGroup mParentViewGroup;
View mCurrentViewThisViewHolderIsFor;
int mDataType;
public TypeOneViewHolder mTypeOneViewHolder;
public TypeTwoViewHolder mTypeTwoViewHolder;
static ViewHolder makeViewHolder(ViewGroup vwGrp,
int dataType) {
View v = getLayoutView(vwGrp, dataType);
return new ViewHolder(vwGrp, v, viewType);
}
static View getLayoutView(ViewGroup vwGrp,
int dataType) {
int layoutId = getLayoutId(dataType);
return LayoutInflater.from(vwGrp.getContext())
.inflate(layoutId, null);
}
static int getLayoutId(int dataType) {
if (dataType == TYPE_ONE) {
return R.layout.type_one_layout;
} else if (dataType == TYPE_TWO) {
return R.layout.type_two_layout;
}
}
public ViewHolder(ViewGroup vwGrp, View v,
int dataType) {
super(v);
mDataType = dataType;
mParentViewGroup = vwGrp;
mCurrentViewThisViewHolderIsFor = v;
if (data.type == TYPE_ONE) {
mTypeOneViewHolder = new TypeOneViewHolder(v);
} else if (data.type == TYPE_TWO) {
mTypeTwoViewHolder = new TypeTwoViewHolder(v);
}
}
public void updateData(TypeData data) {
mDataType = data.type;
if (data.type == TYPE_ONE) {
mTypeTwoViewHolder = null;
if (mTypeOneViewHolder == null) {
View newView = getLayoutView(mParentViewGroup,
data.type);
/**
* how to replace new view with
the view in the parent
view container ???
*/
replaceView(mCurrentViewThisViewHolderIsFor,
newView);
mCurrentViewThisViewHolderIsFor = newView;
mTypeOneViewHolder =
new TypeOneViewHolder(newView);
}
mTypeOneViewHolder.updateDataTypeOne(data);
} else if (data.type == TYPE_TWO){
mTypeOneViewHolder = null;
if (mTypeTwoViewHolder == null) {
View newView = getLayoutView(mParentViewGroup,
data.type);
/**
* how to replace new view with
the view in the parent view
container ???
*/
replaceView(mCurrentViewThisViewHolderIsFor,
newView);
mCurrentViewThisViewHolderIsFor = newView;
mTypeTwoViewHolder =
new TypeTwoViewHolder(newView);
}
mTypeTwoViewHolder.updateDataTypeOne(data);
}
}
}
public static void replaceView(View currentView,
View newView) {
ViewGroup parent = (ViewGroup)currentView.getParent();
if(parent == null) {
return;
}
final int index = parent.indexOfChild(currentView);
parent.removeView(currentView);
parent.addView(newView, index);
}
편집 : ViewHolder에는 뷰를 보유 할 멤버 mItemViewType이 있습니다.
편집 : onBindViewHolder (ViewHolder holder, int position)에서 전달 된 ViewHolder가 getItemViewType (int position)에서 일치하는지 확인하여 선택 (또는 생성) 된 것처럼 보이므로 ViewHolder가 일치하는지 걱정할 필요가 없습니다. 유형이 데이터 [위치]의 유형과 일치하지 않습니다. 누구든지 onBindViewHolder ()의 ViewHolder가 어떻게 선택되는지 더 알고 있습니까?
편집 : 재활용 ViewHolder
이 유형별로 선택되므로 전사가 없습니다.
편집 : http://wiresareobsolete.com/2014/09/building-a-recyclerview-layoutmanager-part-1/ 이 질문에 답합니다.
다음 ViewHolder
과 같이 재활용됩니다 .
holder = getRecycledViewPool().getRecycledView(mAdapter.getItemViewType(offsetPosition));
또는 ViewHolder
올바른 유형의 재활용 을 찾지 못하면 새로 만듭니다 .
public ViewHolder getRecycledView(int viewType) {
final ArrayList<ViewHolder> scrapHeap = mScrap.get(viewType);
if (scrapHeap != null && !scrapHeap.isEmpty()) {
final int index = scrapHeap.size() - 1;
final ViewHolder scrap = scrapHeap.get(index);
scrapHeap.remove(index);
return scrap;
}
return null;
}
View getViewForPosition(int position, boolean dryRun) {
......
if (holder == null) {
final int offsetPosition = mAdapterHelper.findPositionOffset(position);
if (offsetPosition < 0 || offsetPosition >= mAdapter.getItemCount()) {
throw new IndexOutOfBoundsException("Inconsistency detected. Invalid item "
+ "position " + position + "(offset:" + offsetPosition + ")."
+ "state:" + mState.getItemCount());
}
final int type = mAdapter.getItemViewType(offsetPosition);
// 2) Find from scrap via stable ids, if exists
if (mAdapter.hasStableIds()) {
holder = getScrapViewForId(mAdapter.getItemId(offsetPosition), type, dryRun);
if (holder != null) {
// update position
holder.mPosition = offsetPosition;
fromScrap = true;
}
}
if (holder == null && mViewCacheExtension != null) {
// We are NOT sending the offsetPosition because LayoutManager does not
// know it.
final View view = mViewCacheExtension
.getViewForPositionAndType(this, position, type);
if (view != null) {
holder = getChildViewHolder(view);
if (holder == null) {
throw new IllegalArgumentException("getViewForPositionAndType returned"
+ " a view which does not have a ViewHolder");
} else if (holder.shouldIgnore()) {
throw new IllegalArgumentException("getViewForPositionAndType returned"
+ " a view that is ignored. You must call stopIgnoring before"
+ " returning this view.");
}
}
}
if (holder == null) { // fallback to recycler
// try recycler.
// Head to the shared pool.
if (DEBUG) {
Log.d(TAG, "getViewForPosition(" + position + ") fetching from shared "
+ "pool");
}
holder = getRecycledViewPool()
.getRecycledView(mAdapter.getItemViewType(offsetPosition));
if (holder != null) {
holder.resetInternal();
if (FORCE_INVALIDATE_DISPLAY_LIST) {
invalidateDisplayListInt(holder);
}
}
}
if (holder == null) {
holder = mAdapter.createViewHolder(RecyclerView.this,
mAdapter.getItemViewType(offsetPosition));
if (DEBUG) {
Log.d(TAG, "getViewForPosition created new ViewHolder");
}
}
}
boolean bound = false;
if (mState.isPreLayout() && holder.isBound()) {
// do not update unless we absolutely have to.
holder.mPreLayoutPosition = position;
} else if (!holder.isBound() || holder.needsUpdate() || holder.isInvalid()) {
if (DEBUG && holder.isRemoved()) {
throw new IllegalStateException("Removed holder should be bound and it should"
+ " come here only in pre-layout. Holder: " + holder);
}
final int offsetPosition = mAdapterHelper.findPositionOffset(position);
mAdapter.bindViewHolder(holder, offsetPosition);
attachAccessibilityDelegate(holder.itemView);
bound = true;
if (mState.isPreLayout()) {
holder.mPreLayoutPosition = position;
}
}
final ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams();
final LayoutParams rvLayoutParams;
if (lp == null) {
rvLayoutParams = (LayoutParams) generateDefaultLayoutParams();
holder.itemView.setLayoutParams(rvLayoutParams);
} else if (!checkLayoutParams(lp)) {
rvLayoutParams = (LayoutParams) generateLayoutParams(lp);
holder.itemView.setLayoutParams(rvLayoutParams);
} else {
rvLayoutParams = (LayoutParams) lp;
}
rvLayoutParams.mViewHolder = holder;
rvLayoutParams.mPendingInvalidate = fromScrap && bound;
return holder.itemView;
}
선언적이고 안전한 방식으로 여러 뷰 유형을 만들 수있는 더 나은 솔루션이 있습니다. 그것은 btw가 정말 좋은 Kotlin으로 작성되었습니다.
모든 필수보기 유형에 대한 간단한보기 홀더
class ViewHolderMedium(itemView: View) : RecyclerView.ViewHolder(itemView) {
val icon: ImageView = itemView.findViewById(R.id.icon) as ImageView
val label: TextView = itemView.findViewById(R.id.label) as TextView
}
어댑터 데이터 항목의 추상화가 있습니다. 뷰 유형은 특정 뷰 홀더 클래스 (Kotlin의 KClass)의 hashCode로 표시됩니다.
trait AdapterItem {
val viewType: Int
fun bindViewHolder(viewHolder: RecyclerView.ViewHolder)
}
abstract class AdapterItemBase<T>(val viewHolderClass: KClass<T>) : AdapterItem {
override val viewType: Int = viewHolderClass.hashCode()
abstract fun bindViewHolder(viewHolder: T)
override fun bindViewHolder(viewHolder: RecyclerView.ViewHolder) {
bindViewHolder(viewHolder as T)
}
}
bindViewHolder
구체적인 어댑터 항목 클래스 에서만 재정의하면됩니다 (유형 안전 방식).
class AdapterItemMedium(val icon: Drawable, val label: String, val onClick: () -> Unit) : AdapterItemBase<ViewHolderMedium>(ViewHolderMedium::class) {
override fun bindViewHolder(viewHolder: ViewHolderMedium) {
viewHolder.icon.setImageDrawable(icon)
viewHolder.label.setText(label)
viewHolder.itemView.setOnClickListener { onClick() }
}
}
이러한 AdapterItemMedium
개체의 목록은 실제로 List<AdapterItem>
아래 참조를 수락하는 어댑터의 데이터 소스입니다 .
이 솔루션의 중요한 부분은 특정 ViewHolder의 새로운 인스턴스를 제공하는 뷰 홀더 팩토리입니다.
class ViewHolderProvider {
private val viewHolderFactories = hashMapOf<Int, Pair<Int, Any>>()
fun provideViewHolder(viewGroup: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
val (layoutId: Int, f: Any) = viewHolderFactories.get(viewType)
val viewHolderFactory = f as (View) -> RecyclerView.ViewHolder
val view = LayoutInflater.from(viewGroup.getContext()).inflate(layoutId, viewGroup, false)
return viewHolderFactory(view)
}
fun registerViewHolderFactory<T>(key: KClass<T>, layoutId: Int, viewHolderFactory: (View) -> T) {
viewHolderFactories.put(key.hashCode(), Pair(layoutId, viewHolderFactory))
}
}
간단한 어댑터 클래스는 다음과 같습니다.
public class MultitypeAdapter(val items: List<AdapterItem>) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
val viewHolderProvider = ViewHolderProvider() // inject ex Dagger2
init {
viewHolderProvider!!.registerViewHolderFactory(ViewHolderMedium::class, R.layout.item_medium, { itemView ->
ViewHolderMedium(itemView)
})
}
override fun getItemViewType(position: Int): Int {
return items[position].viewType
}
override fun getItemCount(): Int {
return items.size()
}
override fun onCreateViewHolder(viewGroup: ViewGroup, viewType: Int): RecyclerView.ViewHolder? {
return viewHolderProvider!!.provideViewHolder(viewGroup, viewType)
}
override fun onBindViewHolder(viewHolder: RecyclerView.ViewHolder, position: Int) {
items[position].bindViewHolder(viewHolder)
}
}
새보기 유형을 만드는 3 단계 :
- 뷰 홀더 클래스 생성
- 어댑터 항목 클래스 생성 (AdapterItemBase에서 확장)
- 뷰 홀더 클래스 등록
ViewHolderProvider
이 개념의 예는 다음과 같습니다. android-drawer-template 더 나아가-스피너 구성 요소로 작동하는 뷰 유형, 선택 가능한 어댑터 항목.
매우 간단하고 간단합니다.
어댑터에서 getItemViewType () 메서드를 재정의 하십시오. 데이터를 기반으로 다른 itemViewType 값을 반환합니다. 예를 들어, 구성원이 isMale 인 Person 유형의 객체를 생각해보십시오. isMale이 true이면 1을 반환하고 isMale이 false이면 getItemViewType () 메서드 에서 2를 반환 합니다.
이제 createViewHolder (ViewGroup parent, int viewType)가 나오며 , 다른 viewType을 기반으로 다른 레이아웃 파일을 팽창시킬 수 있습니다. 다음과 같이
if (viewType ==1){
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.male,parent,false);
return new AdapterMaleViewHolder(view);
}
else{
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.female,parent,false);
return new AdapterFemaleViewHolder(view);
}
에 onBindViewHolder (VH 홀더 INT 위치) 홀더의 인스턴스이고 확인 AdapterFemaleViewHolder
하거나 AdapterMaleViewHolder
하여이 instanceof
따라 값을 할당한다.
ViewHolder 다음과 같을 수 있습니다.
class AdapterMaleViewHolder extends RecyclerView.ViewHolder {
...
public AdapterMaleViewHolder(View itemView){
...
}
}
class AdapterFemaleViewHolder extends RecyclerView.ViewHolder {
...
public AdapterFemaleViewHolder(View itemView){
...
}
}
Hannes Dorfmann의이 도서관을 추천합니다. "AdapterDelegate"라는 별도의 개체에 특정보기 유형과 관련된 모든 논리를 캡슐화합니다. https://github.com/sockeqwe/AdapterDelegates
public class CatAdapterDelegate extends AdapterDelegate<List<Animal>> {
private LayoutInflater inflater;
public CatAdapterDelegate(Activity activity) {
inflater = activity.getLayoutInflater();
}
@Override public boolean isForViewType(@NonNull List<Animal> items, int position) {
return items.get(position) instanceof Cat;
}
@NonNull @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent) {
return new CatViewHolder(inflater.inflate(R.layout.item_cat, parent, false));
}
@Override public void onBindViewHolder(@NonNull List<Animal> items, int position,
@NonNull RecyclerView.ViewHolder holder, @Nullable List<Object> payloads) {
CatViewHolder vh = (CatViewHolder) holder;
Cat cat = (Cat) items.get(position);
vh.name.setText(cat.getName());
}
static class CatViewHolder extends RecyclerView.ViewHolder {
public TextView name;
public CatViewHolder(View itemView) {
super(itemView);
name = (TextView) itemView.findViewById(R.id.name);
}
}
}
public class AnimalAdapter extends ListDelegationAdapter<List<Animal>> {
public AnimalAdapter(Activity activity, List<Animal> items) {
// DelegatesManager is a protected Field in ListDelegationAdapter
delegatesManager.addDelegate(new CatAdapterDelegate(activity))
.addDelegate(new DogAdapterDelegate(activity))
.addDelegate(new GeckoAdapterDelegate(activity))
.addDelegate(23, new SnakeAdapterDelegate(activity));
// Set the items from super class.
setItems(items);
}
}
실제로 Anton의 답변 을 개선하고 싶습니다 .
getItemViewType(int position)
정수 값을 반환 하므로 확장 해야하는 레이아웃 리소스 ID를 반환 할 수 있습니다. 그렇게하면 onCreateViewHolder(ViewGroup parent, int viewType)
메소드에 로직을 저장할 수 있습니다 .
또한 getItemCount()
목록을 렌더링하는 동안 특정 함수가 최소 5 번 호출되고 표시되는 항목을 넘어서 각 항목을 렌더링하는 동안 집중적 인 계산을 수행하지 않는 것이 좋습니다 . 안타깝게도 notifyDatasetChanged()
메소드가 최종 이기 때문에 실제로 재정의 할 수는 없지만 어댑터 내의 다른 함수에서 호출 할 수 있습니다.
https://github.com/vivchar/RendererRecyclerViewAdapter 라이브러리를 사용할 수 있습니다.
mRecyclerViewAdapter = new RendererRecyclerViewAdapter(); /* included from library */
mRecyclerViewAdapter.registerRenderer(new SomeViewRenderer(SomeModel.TYPE, this));
mRecyclerViewAdapter.registerRenderer(...); /* you can use several types of cells */
`
각 항목에 대해 ViewRenderer, ViewHolder, SomeModel을 구현해야합니다.
ViewHolder-리사이클 러 뷰의 간단한 뷰 홀더입니다.
SomeModel- ItemModel
인터페이스 가있는 모델입니다.
public class SomeViewRenderer extends ViewRenderer<SomeModel, SomeViewHolder> {
public SomeViewRenderer(final int type, final Context context) {
super(type, context);
}
@Override
public void bindView(@NonNull final SomeModel model, @NonNull final SomeViewHolder holder) {
holder.mTitle.setText(model.getTitle());
}
@NonNull
@Override
public SomeViewHolder createViewHolder(@Nullable final ViewGroup parent) {
return new SomeViewHolder(LayoutInflater.from(getContext()).inflate(R.layout.some_item, parent, false));
}
}
자세한 내용은 문서를 참조하십시오.
kotlin을 사용하면보기 유형 구현이 더 쉬워집니다. 여기 라이트 라이브러리 https://github.com/Link184/KidAdapter 가있는 샘플이 있습니다.
recyclerView.setUp {
withViewType {
withLayoutResId(R.layout.item_int)
withItems(mutableListOf(1, 2, 3, 4, 5, 6))
bind<Int> { // this - is adapter view hoder itemView, it - current item
intName.text = it.toString()
}
}
withViewType("SECOND_STRING_TAG") {
withLayoutResId(R.layout.item_text)
withItems(mutableListOf("eight", "nine", "ten", "eleven", "twelve"))
bind<String> {
stringName.text = it
}
}
}
해당 위치에 대한 예상 값을 반환하여 multipleViewType RecyclerAdapter
을 처리 할 수 있습니다.getItemViewType()
viewType
나는 MultipleViewTypeAdapter
2 개 이상의 유효한 답 (체크 박스 옵션)과 단일 답안 (라디오 버튼 옵션)이있을 수있는 질문을 던질 수있는 시험을위한 MCQ 목록을 구성 하기 위해 준비했습니다 .
이를 위해 API 응답에서 질문 유형을 얻고 해당 질문에 대해 표시해야하는보기를 결정하는 데 사용했습니다.
public class MultiViewTypeAdapter extends RecyclerView.Adapter {
Context mContext;
ArrayList<Question> dataSet;
ArrayList<String> questions;
private Object radiobuttontype1;
//Viewholder to display Questions with checkboxes
public static class Checkboxtype2 extends RecyclerView.ViewHolder {
ImageView imgclockcheck;
CheckBox checkbox;
public Checkboxtype2(@NonNull View itemView) {
super(itemView);
imgclockcheck = (ImageView) itemView.findViewById(R.id.clockout_cbox_image);
checkbox = (CheckBox) itemView.findViewById(R.id.clockout_cbox);
}
}
//Viewholder to display Questions with radiobuttons
public static class Radiobuttontype1 extends RecyclerView.ViewHolder {
ImageView clockout_imageradiobutton;
RadioButton clockout_radiobutton;
TextView sample;
public radiobuttontype1(View itemView) {
super(itemView);
clockout_imageradiobutton = (ImageView) itemView.findViewById(R.id.clockout_imageradiobutton);
clockout_radiobutton = (RadioButton) itemView.findViewById(R.id.clockout_radiobutton);
sample = (TextView) itemView.findViewById(R.id.sample);
}
}
public MultiViewTypeAdapter(ArrayList<QueDatum> data, Context context) {
this.dataSet = data;
this.mContext = context;
}
@NonNull
@Override
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int viewType) {
if (viewType.equalsIgnoreCase("1")) {
View view = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.clockout_radio_list_row, viewGroup, false);
return new radiobuttontype1(view);
} else if (viewType.equalsIgnoreCase("2")) {
View view = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.clockout_cbox_list_row, viewGroup, false);
view.setHorizontalFadingEdgeEnabled(true);
return new Checkboxtype2(view);
} else if (viewType.equalsIgnoreCase("3")) {
View view = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.clockout_radio_list_row, viewGroup, false);
return new Radiobuttontype1(view);
} else if (viewType.equalsIgnoreCase("4")) {
View view = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.clockout_radio_list_row, viewGroup, false);
return new Radiobuttontype1(view);
} else if (viewType.equalsIgnoreCase("5")) {
View view = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.clockout_radio_list_row, viewGroup, false);
return new Radiobuttontype1(view);
}
return null;
}
@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder viewHolder, int viewType) {
if (viewType.equalsIgnoreCase("1")) {
options = dataSet.get(i).getOptions();
question = dataSet.get(i).getQuestion();
image = options.get(i).getValue();
((radiobuttontype1) viewHolder).clockout_radiobutton.setChecked(false);
((radiobuttontype1) viewHolder).sample.setText(question);
//loading image bitmap in the ViewHolder's View
Picasso.with(mContext)
.load(image)
.into(((radiobuttontype1) viewHolder).clockout_imageradiobutton);
} else if (viewType.equalsIgnoreCase("2")) {
options = (ArrayList<Clockout_questions_Option>) dataSet.get(i).getOptions();
question = dataSet.get(i).getQuestion();
image = options.get(i).getValue();
//loading image bitmap in the ViewHolder's View
Picasso.with(mContext)
.load(image)
.into(((Checkboxtype2) viewHolder).imgclockcheck);
} else if (viewType.equalsIgnoreCase("3")) {
//fit data to viewHolder for ViewType 3
} else if (viewType.equalsIgnoreCase("4")) {
//fit data to viewHolder for ViewType 4
} else if (viewType.equalsIgnoreCase("5")) {
//fit data to viewHolder for ViewType 5
}
}
@Override
public int getItemCount() {
return dataSet.size();
}
/**
* returns viewType for that position by picking the viewType value from the
* dataset
*/
@Override
public int getItemViewType(int position) {
return dataSet.get(position).getViewType();
}
}
onBindViewHolder()
위치가 다른 viewHolders에서 유사한 뷰에 대해 동일한 ID를 할당하여 여러 조건부 기반 viewHolder 데이터가 채워지는 것을 방지 할 수 있습니다 .
Android Data Binding과 함께 사용하려면 https://github.com/evant/binding-collection-adapter를 살펴보십시오. 지금까지 본 여러보기 유형에 대한 최상의 솔루션입니다 RecyclerView
.
당신은 그것을 다음과 같이 사용할 수 있습니다
var items: AsyncDiffPagedObservableList<BaseListItem> =
AsyncDiffPagedObservableList(GenericDiff)
val onItemBind: OnItemBind<BaseListItem> =
OnItemBind { itemBinding, _, item -> itemBinding.set(BR.item, item.layoutRes) }
그런 다음 목록이있는 레이아웃에서
<androidx.recyclerview.widget.RecyclerView
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
app:enableAnimations="@{false}"
app:scrollToPosition="@{viewModel.scrollPosition}"
app:itemBinding="@{viewModel.onItemBind}"
app:items="@{viewModel.items}"
app:reverseLayoutManager="@{true}"/>
목록 항목은 BaseListItem
다음과 같은 인터페이스를 구현해야합니다.
interface BaseListItem {
val layoutRes: Int
}
항목보기는 다음과 같아야합니다.
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="item"
type="...presentation.somescreen.list.YourListItem"/>
</data>
...
</layout>
YourListItem
구현하는 곳BaseListItem
누군가를 도울 수 있기를 바랍니다.
먼저 2 개의 레이아웃 xml을 만들어야합니다. 그 후 recyclerview 어댑터 TYPE_CALL 및 TYPE_EMAIL은 어댑터 클래스에서 각각 1과 2가있는 두 개의 정적 값입니다.
이제 Recycler 뷰 어댑터 클래스 수준에서 두 개의 정적 값을 정의합니다. 예를 들면 다음과 같습니다. private static int TYPE_CALL = 1; 개인 정적 int TYPE_EMAIL = 2;
이제 다음과 같이 여러보기가있는보기 홀더를 만듭니다.
class CallViewHolder extends RecyclerView.ViewHolder {
private TextView txtName;
private TextView txtAddress;
CallViewHolder(@NonNull View itemView) {
super(itemView);
txtName = itemView.findViewById(R.id.txtName);
txtAddress = itemView.findViewById(R.id.txtAddress);
}
}
class EmailViewHolder extends RecyclerView.ViewHolder {
private TextView txtName;
private TextView txtAddress;
EmailViewHolder(@NonNull View itemView) {
super(itemView);
txtName = itemView.findViewById(R.id.txtName);
txtAddress = itemView.findViewById(R.id.txtAddress);
}
}
이제 recyclerview 어댑터의 onCreateViewHolder 및 onBindViewHolder 메서드에서 아래와 같이 코드를 작성합니다.
@NonNull
@Override
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int viewType) {
View view;
if (viewType == TYPE_CALL) { // for call layout
view = LayoutInflater.from(context).inflate(R.layout.item_call, viewGroup, false);
return new CallViewHolder(view);
} else { // for email layout
view = LayoutInflater.from(context).inflate(R.layout.item_email, viewGroup, false);
return new EmailViewHolder(view);
}
}
@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder viewHolder, int position) {
if (getItemViewType(position) == TYPE_CALL) {
((CallViewHolder) viewHolder).setCallDetails(employees.get(position));
} else {
((EmailViewHolder) viewHolder).setEmailDetails(employees.get(position));
}
}
참고 URL : https://stackoverflow.com/questions/26245139/how-to-create-recyclerview-with-multiple-view-type
'program story' 카테고리의 다른 글
"인터페이스 프로그래밍"이란 무엇을 의미합니까? (0) | 2020.09.29 |
---|---|
이러한 구조가 사전 및 사후 증가 정의되지 않은 동작을 사용하는 이유는 무엇입니까? (0) | 2020.09.29 |
pip install mysql-python이 EnvironmentError와 함께 실패 : mysql_config not found (0) | 2020.09.29 |
git pull 실행 취소, 저장소를 이전 상태로 가져 오는 방법 (0) | 2020.09.28 |
작은 따옴표로 묶인 문자열에서 작은 따옴표를 이스케이프하는 방법 (0) | 2020.09.28 |