Wanting to switch to version 8.0 of AGP for my Android project I had to refactor a lot of java classes where the developer before me used ButterKnife to bind variables to layout elements. The last class I encounter is a RecyclerView that uses multiple ViewHolder in in a hierarchical way like this:
public class ViewHolder extends RecyclerView.ViewHolder {
ViewHolder(View v) {
super(v);
}
}
public class MessageViewHolder extends ViewHolder {
@BindView(R.id.message_text_list_item)
LinearLayout mMessageLayout;
@BindView(R.id.message_custom_widget)
LinearLayout mCustomWidget;
@Nullable
@BindView(R.id.message_textview_response_layout)
LinearLayout mMessageAcknoledgmentLayout;
@BindView(R.id.message_textview_sender_name)
TextView mSender;
@BindView(R.id.message_textview_text)
TextView mText;
@BindView(R.id.message_textview_time)
TextView mTime;
@Nullable
@BindView(R.id.message_textview_status)
TextView mStatus;
@Nullable
@BindView(R.id.message_imageview_ack_status)
ImageView mAckStatus;
private long id;
private MessageStatus mMessageStatus;
MessageViewHolder(View v) {
super(v);
ButterKnife.bind(this, v);
mCustomWidget.setVisibility(GONE);
mSender.setVisibility(GONE);
if(mMessageAcknoledgmentLayout != null) {
mMessageAcknoledgmentLayout.setVisibility(GONE);
}
}
@Optional
@OnClick(R.id.message_textview_ack)
void onMessageAckClick() {
easyLog.info("[onMessageAckClick] Ho cliccato sull'ack del messaggio con id -> "+ id);
showMessageAckConfirmDialog(id, true);
}
@Optional
@OnClick(R.id.message_textview_nack)
void onMessageNackClick() {
easyLog.info("[onMessageAckClick] Ho cliccato sul nack del messaggio con id -> "+ id);
showMessageAckConfirmDialog(id, false);
}
}
public class SeparatorViewHolder extends ViewHolder {
@BindView(R.id.separator_date)
TextView mDate;
SeparatorViewHolder(View v) {
super(v);
ButterKnife.bind(this, v);
}
}
public class AudioViewHolder extends MessageViewHolder {
AudioControlView mAudioControlView;
AudioViewHolder(View v, int audioLayout) {
super(v);
View view = inflater.inflate(audioLayout, null);
mAudioControlView = view.findViewById(R.id.player_view);
mCustomWidget.addView(view);
mCustomWidget.setVisibility(View.VISIBLE);
}
}
public class ImageViewHolder extends MessageViewHolder {
MessageImageView mMessageImageView;
ImageView mImageView;
ImageViewHolder(View v, int imageLayout) {
super(v);
View view = inflater.inflate(imageLayout, null);
mMessageImageView = view.findViewById(R.id.message_image);
mImageView = mMessageImageView.getImageView();
mCustomWidget.addView(mMessageImageView);
mCustomWidget.setVisibility(View.VISIBLE);
}
}
I know how to use ViewBinding for RecyclerView with a single ViewHolder, but I can't figure out how to handle this particular situation, anyone has some hints or solutions?
Thanks in advance!
EDIT: I forgot important infos: I had some problem understanding which bindings I have to pass and use because in the onCreateViewHolder function a layout is passed (list_item_text_****) but in the ViewHolders the layout on which @BindView is used is another one, not the same, for example
list_item_text_left.xml
<it.ingeniars.etmlib.ui.view.MessageTextViewLeft
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content">
</it.ingeniars.etmlib.ui.view.MessageTextViewLeft>
widget_text_msg_left.xml
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
style="@style/WidgetTextMessage.Left"
android:id="@+id/message_text_left_layout">
<LinearLayout
android:id="@+id/message_text_left_background"
style="@style/WidgetTextMessageBackground.Left">
<LinearLayout
android:id="@+id/message_text_list_item"
style="@style/LinearWidgetTextMsg.Left">
<TextView
android:id="@+id/message_textview_sender_name"
style="@style/TextViewSenderMsg.Left"/>
<LinearLayout
android:id="@+id/message_custom_widget"
style="@style/CustomWidgetFrame.Left"/>
<TextView
android:id="@+id/message_textview_text"
style="@style/TextViewTextMsg.Left"/>
<LinearLayout
android:id="@+id/message_textview_response_layout"
style="@style/TextViewAckLayout">
<Button
android:id="@+id/message_textview_nack"
style="@style/TextViewNack"/>
<Button
android:id="@+id/message_textview_ack"
style="@style/TextViewAck" />
</LinearLayout>
<LinearLayout
style="@style/TextViewStatusLayout">
<ImageView
android:id="@+id/message_imageview_ack_status"
style="@style/ImageViewAckStatusMsg.Left"/>
<TextView
android:id="@+id/message_textview_time"
style="@style/TextViewTimeMsg.Right"/>
</LinearLayout>
</LinearLayout>
</LinearLayout>
</LinearLayout>
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
switch(viewType) {
case TYPE_SEPARATOR:
return new SeparatorViewHolder(inflater.inflate(R.layout.widget_message_separator, parent, false));
case TYPE_MESSAGE_TEXT_LEFT:
return new MessageViewHolder(inflater.inflate(R.layout.list_item_text_left, parent, false));
case TYPE_MESSAGE_TEXT_WITH_IMAGE_LEFT:
return new ImageViewHolder(inflater.inflate(R.layout.list_item_text_left, parent, false), R.layout.list_item_image_left);
case TYPE_MESSAGE_TEXT_WITH_AUDIO_LEFT:
return new AudioViewHolder(inflater.inflate(R.layout.list_item_text_left, parent, false), R.layout.list_item_audio_left);
case TYPE_MESSAGE_TEXT_WITH_AUDIO_RIGHT:
return new AudioViewHolder(inflater.inflate(R.layout.list_item_text_right, parent, false), R.layout.list_item_audio_right);
case TYPE_MESSAGE_TEXT_RIGHT:
return new MessageViewHolder(inflater.inflate(R.layout.list_item_text_right, parent, false));
case TYPE_MESSAGE_TEXT_WITH_IMAGE_RIGHT:
return new ImageViewHolder(inflater.inflate(R.layout.list_item_text_right, parent, false), R.layout.list_item_image_right);
case TYPE_MESSAGE_TEXT_WITH_PDF_PREVIEW_IMAGE_LEFT:
return new ImageViewHolder(inflater.inflate(R.layout.list_item_text_left, parent, false), R.layout.list_item_image_left);
case TYPE_MESSAGE_TEXT_WITH_PDF_PREVIEW_IMAGE_RIGHT:
return new ImageViewHolder(inflater.inflate(R.layout.list_item_text_right, parent, false), R.layout.list_item_image_right);
default:
return null;
}
}
@Override
public void onBindViewHolder(@NonNull ViewHolder viewHolder, int position) {
InboxItem item = getItem(position);
int itemType = getItemViewType(position);
switch(itemType) {
case TYPE_MESSAGE_TEXT_LEFT:
onBindTextLeftViewHolder((MessageViewHolder) viewHolder, (InboxMessage) item);
break;
case TYPE_MESSAGE_TEXT_RIGHT:
onBindTextRightViewHolder((MessageViewHolder) viewHolder, (InboxMessage) item);
break;
case TYPE_MESSAGE_TEXT_WITH_AUDIO_RIGHT:
onBindTextAudioRightViewHolder((AudioViewHolder) viewHolder, (InboxMessage) item, position);
break;
case TYPE_MESSAGE_TEXT_WITH_AUDIO_LEFT:
onBindTextAudioLeftViewHolder((AudioViewHolder) viewHolder, (InboxMessage) item, position);
break;
case TYPE_MESSAGE_TEXT_WITH_IMAGE_RIGHT:
onBindTextImageRightViewHolder((ImageViewHolder) viewHolder, (InboxMessage) item);
break;
case TYPE_MESSAGE_TEXT_WITH_IMAGE_LEFT:
onBindTextImageLeftViewHolder((ImageViewHolder) viewHolder, (InboxMessage) item);
break;
case TYPE_SEPARATOR:
onBindSeparator((SeparatorViewHolder) viewHolder, (MessageSeparator) item);
break;
case TYPE_MESSAGE_TEXT_WITH_PDF_PREVIEW_IMAGE_LEFT:
onBindTextPDFImageLeftViewHolder((ImageViewHolder) viewHolder, (InboxMessage) item);
break;
case TYPE_MESSAGE_TEXT_WITH_PDF_PREVIEW_IMAGE_RIGHT:
onBindTextPDFImageRightViewHolder((ImageViewHolder) viewHolder, (InboxMessage) item);
break;
}
}
There's nothing super special about ViewBinding and ViewHolders:
(all pseudo-code)
One way could be having a small internal base class:
And then individual ViewHolders:
Then you have an Adapter:
As usual.
Then all you need is to override the correct methods:
override fun getItemViewType(position: Int): Intoverride fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BaseViewHolderoverride fun onBindViewHolder(holder: BaseViewHolder, position: Int)For 1, all you really do is
Then for 2:
And finally...
And that's really all there is.
Keep in mind, this is one way to do it. I've seen it, I've done it, it works. There may be others or you may want more or less abstractions but at the end of the day, you pass the binding object to your ViewHolder and carry on as usual ;)
Update 1
It's a bit difficult to follow your exact issue, but keep in mind: For views to be available in ViewBinding, they must have an
id.There's no "different magic" with ViewBinding vs. ButterKnife, other than the code generation behind the scenes to generate the "Binding" objects you use, versus ButterKnife's annotation system that did the
findViewByIdfor you.If you have a layout called
my_layout_one.xmlYou should also have aMyLayoutOneBinding. Each binding has aninflatemethod, so instead of passing the .xml, then calling inflate, then doingfindViewByIdon each view you wanted to reference/use, you can obtain a reference to your*Bindingclass once, and it will contain all the layout's views references already there (kind of populated at compile time).So let's take an example:
The
MessageViewHolderis expecting a View, as evidenced by the constructor:After the viewHolder receives the View, ButterKnife would be able to access all Views that have an
idvia the annotation. If you look atlist_item_text_left(the one you supplied), I see how this can be confusing, since the Layout references what appears to be a custom View calledit.ingeniars.etmlib.ui.view.MessageTextViewLeftYou didn't post the source code for this View, but based on the ViewHolder's current code you've shown, I'd assume the layout that the view is internally inflating is:
widget_text_msg_left.xmlThere's got to be a
WidgetTextMsgLeftBindingsomewhere in your auto-generated code that you could inflate by callinginflateon it.Because your current view takes a View (not a layout), it then does
findViewByIdon that view (well it currently uses ButterKnife, which kind of abstracts this findView for you, but if you look at the code you supplied, The Message view holder references (using just the top two fields as to make this shorter):Both
idare LinearLayouts defined inlist_item_text_left.xmlSo as you can see, there's nothing mysterious about how it's all orchestrated together.
Experiment passing and inspecting (in the IDE) what each binding has, and remember that for a view to be able to be used from a binding, it must contain an
id. You've seen the viewBinding docs, so you understand now how it works.Now think about what exact view each viewHolder is referencing, and that will tell you which
Bindingclass you need to inflate or pass.