带有附件的邮件的 RecyclerView 无法正确显示附件
RecyclerView for Messages with Attachments not properly showing attachments
我有一个 MessageAdapter
扩展 RecyclerView.Adapter
的消息。这是正常工作时看起来的样子。您单击卡片,它会展开以显示图像。这只会发生在有图片的邮件上:
但是有时我向下滚动并向上滚动,图像就这样消失了:
有时我在 RecyclerView
上上下滚动,不应该有附件的邮件有一个:
在我的 MessageAdapter
中有两个 ViewType
,一个用于 Header 消息,另一个用于评论消息。
这是我的 MessageAdapter
的样子:
public class MessageAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private static final int TYPE_MESSAGE_HEADER = 0;
private static final int TYPE_MESSAGE_COMMENT = 1;
private Context mContext;
private Message mOriginalMessage;
private List<Message> mMessages;
public MessageAdapter(Context context) {
this.mContext = context;
}
public void setOriginalMessage(Message originalMessage) {
this.mOriginalMessage = originalMessage;
}
public void setMessages(List<Message> messages) {
this.mMessages = new ArrayList<>(messages);
}
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if (viewType == TYPE_MESSAGE_HEADER) {
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.message_header,
parent, false);
return new MessageViewHolder(v, viewType);
} else {
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.message_comment,
parent, false);
return new MessageViewHolder(v, viewType);
}
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
final MessageViewHolder messageViewHolder = (MessageViewHolder) holder;
int viewType = holder.getItemViewType();
switch (viewType) {
case TYPE_MESSAGE_HEADER:
if (messageViewHolder.mIsViewExpanded && mOriginalMessage.getAttachment() != null)
animateHeader(messageViewHolder);
// Other initialization stuff
// Set the image
if (mOriginalMessage.getAttachment() != null) {
messageViewHolder.mHeaderImage.setVisibility(View.INVISIBLE);
messageViewHolder.mHeaderShowTextView.setVisibility(View.VISIBLE);
messageViewHolder.mHeaderShowTextView.setText("Show Attachment");
String attachmentUrl = mOriginalMessage.getAttachment().getImageUrl();
if (messageViewHolder.mIsViewExpanded) {
Picasso.with(mContext)
.load(attachmentUrl)
.into(messageViewHolder.mHeaderImage);
}
messageViewHolder.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
animateHeader(messageViewHolder);
}
});
}
break;
case TYPE_MESSAGE_COMMENT:
Message message = mMessage.get(position - 1);
if (messageViewHolder.mIsViewExpanded && message.getAttachment() != null)
animateComment(messageViewHolder);
// Other initialization stuff
// Show attachment if there is an attachment
if (message.getAttachment() != null) {
messageViewHolder.mMessageImage.setVisibility(View.INVISIBLE);
messageViewHolder.mMessageShowTextView.setVisibility(View.VISIBLE);
messageViewHolder.mMessageShowTextView.setText("Show Attachment");
String attachmentUrl = message.getAttachment().getImageUrl();
if (messageViewHolder.mIsViewExpanded) {
Picasso.with(mContext)
.load(attachmentUrl)
.into(messageViewHolder.mMessageImage);
}
messageViewHolder.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
animateComment(messageViewHolder);
}
});
}
break;
default:
break;
}
}
@Override
public int getItemViewType(int position) {
if (isPositionHeader(position)) {
return TYPE_MESSAGE_HEADER;
}
return TYPE_MESSAGE_COMMENT;
}
private boolean isPositionHeader(int position) {
return position == 0;
}
// GUESSING SOMETHING WRONG HERE?
@Override
public int getItemCount() {
if (mOriginalMessage != null && mMessages != null) {
if (!mMessages.isEmpty())
return mMessages.size() + 1;
else
return 1;
} else if (mMessages != null) {
if (!mMessages.isEmpty())
return mMessages.size();
}
return 0;
}
private void animateHeader(final MessageViewHolder messageViewHolder) {
if (messageViewHolder.mOriginalHeight == 0)
messageViewHolder.mOriginalHeight = messageViewHolder.itemView.getHeight();
ValueAnimator valueAnimator;
if (!messageViewHolder.mIsViewExpanded) {
messageViewHolder.mHeaderImage.setVisibility(View.VISIBLE);
messageViewHolder.mHeaderImage.setEnabled(true);
messageViewHolder.mIsViewExpanded = true;
valueAnimator = ValueAnimator
.ofInt(messageViewHolder.mOriginalHeight, commentViewHolder.mOriginalHeight
+ (int) (messageViewHolder.mOriginalHeight * 0.8) + 10);
messageViewHolder.mHeaderShowTextView.setText("Hide Attachment");
} else {
messageViewHolder.mIsViewExpanded = false;
valueAnimator = ValueAnimator.ofInt(messageViewHolder.mOriginalHeight + (int) (messageViewHolder.mOriginalHeight * 0.8)
+ 10, messageViewHolder.mOriginalHeight);
Animation a = new AlphaAnimation(1.00f, 0.00f);
a.setDuration(200);
a.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
messageViewHolder.mHeaderShowTextView.setText("Show Attachment");
}
@Override
public void onAnimationEnd(Animation animation) {
messageViewHolder.mAttachmentImage.setVisibility(View.INVISIBLE);
messageViewHolder.mHeaderImage.setEnabled(false);
}
@Override
public void onAnimationRepeat(Animation animation) {
}
});
messageViewHolder.mHeaderImage.startAnimation(a);
}
valueAnimator.setDuration(400);
valueAnimator.setInterpolator(new BakedBezierInterpolator());
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
messageViewHolder.itemView.getLayoutParams().height = (int) animation.getAnimatedValue();
messageViewHolder.itemView.requestLayout();
}
});
valueAnimator.start();
}
private void animateComment(final MessageViewHolder messageViewHolder) {
if (messageViewHolder.mOriginalHeight == 0)
messageViewHolder.mOriginalHeight = messageViewHolder.itemView.getHeight();
ValueAnimator valueAnimator;
if (!messageViewHolder.mIsViewExpanded) {
messageViewHolder.mMessageImage.setVisibility(View.VISIBLE);
messageViewHolder.mMessageImage.setEnabled(true);
messageViewHolder.mIsViewExpanded = true;
valueAnimator = ValueAnimator
.ofInt(messageViewHolder.mOriginalHeight, messageViewHolder.mOriginalHeight
+ (int) (messageViewHolder.mOriginalHeight * 0.8) + 10);
messageViewHolder.mMessageShowTextView.setText("Hide Attachment");
} else {
messageViewHolder.mIsViewExpanded = false;
valueAnimator = ValueAnimator
.ofInt(messageViewHolder.mOriginalHeight + (int) (messageViewHolder.mOriginalHeight * 0.8)
+ 10, messageViewHolder.mOriginalHeight);
Animation a = new AlphaAnimation(1.00f, 0.00f);
a.setDuration(200);
a.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
messageViewHolder.mMessageShowTextView.setText("Show Attachment");
}
@Override
public void onAnimationEnd(Animation animation) {
messageViewHolder.mMessageImage.setVisibility(View.INVISIBLE);
messageViewHolder.mMessageImage.setEnabled(false);
}
@Override
public void onAnimationRepeat(Animation animation) {
}
});
messageViewHolder.mMessageImage.startAnimation(a);
}
valueAnimator.setDuration(300);
valueAnimator.setInterpolator(new BakedBezierInterpolator());
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
messageViewHolder.itemView.getLayoutParams().height = (int) animation.getAnimatedValue();
messageViewHolder.itemView.requestLayout();
}
});
valueAnimator.start();
}
public class MessageViewHolder extends RecyclerView.ViewHolder {
// Header
private ImageView mHeaderImage;
private TextView mHeaderShowTextView;
// Comment
private ImageView mMessageImage;
private TextView mMessageShowTextView;
// Variables for View
private int mOriginalHeight = 0;
private boolean mIsViewExpanded = false;
private int mHolderId;
public MessageViewHolder(View itemView, int viewType) {
super(itemView);
if (viewType == TYPE_MESSAGE_HEADER)
initHeaderViews(itemView);
else if (viewType == TYPE_MESSAGE_COMMENT)
initCommentViews(itemView);
}
private void initHeaderViews(View view) {
mHeaderImage = (ImageView) view.findViewById(R.id.header_image);
mHeaderShowTextView = (TextView) view.findViewById(R.id.header_show_textview);
mHeaderShowTextView.setVisibility(View.INVISIBLE);
if (!mIsViewExpanded) {
mHeaderImage.setEnabled(false);
mHeaderImage.setVisibility(View.GONE);
}
mHolderId = TYPE_MESSAGE_HEADER;
}
private void initCommentViews(View view) {
mMessageImage = (ImageView) view.findViewById(R.id.itemAttachmentImage);
mMessageShowTextView = (TextView) view.findViewById(R.id.showItemAttachment);
mMessageShowTextView.setVisibility(View.INVISIBLE);
if (!mIsViewExpanded) {
mMessageShowTextView.setText("Show Attachment");
mMessageImage.setEnabled(false);
mMessageImage.setVisibility(View.GONE);
}
mHolderId = TYPE_MESSAGE_COMMENT;
}
}
}
有没有更好更准确的方法呢?具体来说,最大的问题是消除任何不一致之处,以及这段代码是否可以解耦。
我怎样才能获得正确的邮件以仅正确显示其附件?如何在向上或向下滚动时将图像保留在卡片中?当我添加新评论时,这也开始变得混乱,因为现在有一个 N + 1
问题。
具体来说,我想知道是否有更好的方法来处理多个 ViewHolders
,而不是尝试处理 RecyclerView
.
的 offset
值
更新:
我可以通过在 Fragment
中使用以下内容来降低我的适配器的一些复杂性我正在初始化我的 RecyclerView.Adapter
中:
public void setParentMessage(Message parentMessage) {
this.mParentMessage = parentMessage;
mAllMessages = new ArrayList<>();
mAllMessages.add(mParentMessage);
}
public void setMessages(List<Messages> messages) {
this.mMessages = messages;
mAllMessages.addAll(mMessages);
}
然后我在启动时初始化我的适配器:
mMessageAdapter.setMessages(mAllMessages);
然后,如果我必须向我的列表中添加一个新的 Message
object,我可以简单地执行以下操作:
public void addComment(Message message) {
mMessageAdapter.addItem(mMessageAdapter.getItemCount(), message);
mRecyclerView.scrollToPosition(mMessageAdapter.size() - 1);
}
在我的 MessageAdapter
中,我有以下内容来添加新的消息评论:
public void addItem(int position, Message message) {
mMessages.add(position, message);
notifyItemInserted(position);
}
这意味着我可以更改它:
@Override
public int getItemCount() {
if (mOriginalMessage != null && mMessages != null) {
if (!mMessages.isEmpty())
return mMessages.size() + 1;
else
return 1;
} else if (mMessages != null) {
if (!mMessages.isEmpty())
return mMessages.size();
}
return 0;
}
为此:
@Override
public int getItemCount() {
if (mMessages != null) {
if (!mMessages.isEmpty())
return mMessages.size();
}
return 0;
}
在我的 onBindViewHolder
方法中,我不再需要跟踪偏移量,所以这发生了变化:
Message message = mMessage.get(position - 1);
收件人:
Message message = mMessage.get(position);
此外,我将 MessageViewHolder
解耦为两个单独的 ViewHolder
类:
public class MessageHeaderViewHolder extends RecyclerView.ViewHolder {
// Header
private ImageView mHeaderImage;
private TextView mHeaderShowTextView;
// Variables for View
private int mOriginalHeight = 0;
private boolean mIsViewExpanded = false;
private int mHolderId;
public MessageHeaderViewHolder(View itemView, int viewType) {
super(itemView);
initHeaderViews(itemView);
}
private void initHeaderViews(View view) {
mHeaderImage = (ImageView) view.findViewById(R.id.header_image);
mHeaderShowTextView = (TextView) view.findViewById(R.id.header_show_textview);
mHeaderShowTextView.setVisibility(View.INVISIBLE);
if (!mIsViewExpanded) {
mHeaderImage.setEnabled(false);
mHeaderImage.setVisibility(View.GONE);
}
mHolderId = TYPE_MESSAGE_HEADER;
}
private void initOnClickListener() {
itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
animate(v);
}
});
}
private void removeClickListener() {
if (itemView.hasOnClickListeners())
itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// THIS CODE DOESN'T WORK AS THOUGHT.
// Empty click listener to keep itemSelectableBackground.
}
});
}
private void animate(View v) {
// All animation code for header moved here
}
}
另一个也一样ViewHolder
:
public class MessageCommentViewHolder extends RecyclerView.ViewHolder {
// Comment
private ImageView mMessageImage;
private TextView mMessageShowTextView;
// Variables for View
private int mOriginalHeight = 0;
private boolean mIsViewExpanded = false;
private int mHolderId;
public MessageCommentViewHolder(View itemView, int viewType) {
super(itemView);
initCommentViews(itemView);
}
private void initCommentViews(View view) {
mMessageImage = (ImageView) view.findViewById(R.id.itemAttachmentImage);
mMessageShowTextView = (TextView) view.findViewById(R.id.showItemAttachment);
mMessageShowTextView.setVisibility(View.INVISIBLE);
if (!mIsViewExpanded) {
mMessageShowTextView.setText("Show Attachment");
mMessageImage.setEnabled(false);
mMessageImage.setVisibility(View.GONE);
}
mHolderId = TYPE_MESSAGE_COMMENT;
}
private void initOnClickListener() {
itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
animate(v);
}
});
}
private void removeClickListener() {
if (itemView.hasOnClickListeners())
itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// THIS CODE DOESN'T WORK AS THOUGHT.
// Empty click listener to keep itemSelectableBackground.
}
});
}
private void animate(View v) {
// All animation code for header moved here
}
}
这意味着在我的 onBindViewHolder
方法中,我可以为每种类型的项目执行以下操作(请记住,现在将有两种类型的 ViewHolder
,因此 messageViewHolder
会更改为 headerViewHolder
或 commentViewHolder
或类似内容):
if (message.getAttachment() != null) {
messageViewHolder.mMessageImage.setVisibility(View.INVISIBLE);
messageViewHolder.mMessageShowTextView.setVisibility(View.VISIBLE);
messageViewHolder.mMessageShowTextView.setText("Show Attachment");
String attachmentUrl = message.getAttachment().getImageUrl();
Picasso.with(mContext)
.load(attachmentUrl)
.into(messageViewHolder.mMessageImage);
messageViewHolder.initOnClickListener();
} else {
messageViewHolder.removeClickListener();
messageViewHolder.mMessageImage.setVisibility(View.GONE);
messageViewholder.mMessageShowTextView.setVisibility(View.GONE);
}
现在工作正常,虽然这是一个非常 hacky 的解决方案,但我计划使用 doubleA 的答案,并在本周末使这段代码更加优化。一个仍然存在的问题是一些项目将有 itemSelectableBackground
并且是 clickable
而其他的则没有,据我了解 removeClickListener()
应该初始化一个空的 View.OnClickListener
从而使项目可点击因此显示 itemSelectableBackground
但事实并非如此?? Log
输出表明我正在正确初始化侦听器和图像。
这是recycler views的一个普遍问题,实际上是我最近的一个面试问题。当您使用回收器视图和视图持有者时,它会按照它说的做......回收视图。因此,如果您开始向下滚动并且一个视图附有图像并且您显示该图像,然后当该视图被回收并且您放入其中的新数据没有与之关联的图像时,您的代码不会明确告诉父视图持有者隐藏其中的图像视图。因此,您的图片会出现在回收视图中,因为它已经存在并且只是被回收了。
这是我的建议
if (message.getAttachment() != null) {
//all your fun view binding stuff.
} else {
messageViewHolder.mMessageImage.setVisibility(View.GONE);
}
此外,我建议您将视图绑定代码放入您的视图持有者中,并在必要时使用 2 个具有相同布局的不同视图持有者。它将缩短您的 onBindViewHolder 调用并将不同的视图绑定代码与与之关联的视图持有者链接起来。这是我的一个回收器视图适配器的示例。
public class ProgramRecyclerAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
/**
This is an abstract class that all of my viewholders inherit from.
This is a contract telling me that any subclasses that inherit from this
base class are required to write their own `public void bind(int position,
Program program);` method.
*/
abstract class ProgramBaseViewHolder extends RecyclerView.ViewHolder {
public ProgramBaseViewHolder(View itemView) {
super(itemView);
}
public abstract void bindDataToView(int position, Program program);
}
我意识到关键字 bind 在这个视图持有者中被多次使用,并且他们在做不同的事情。 @Bind
和 Butterknife.bind 是名为 Butterknife 的视图绑定库的一部分,该库由为您提供 Picasso 的同一批好人制作。 "bind" 的这种用法等同于您的 findViewById()
调用。抽象 class' 绑定只是将数据绑定到适配器中的视图的方法的通用名称。我已将 bind 重命名为 bindDataToView 以更明确一些。
/**
This is the Airtime view that holds airtimes. It is a view holder that
inherits from my base view holder and implements its own version if bind.
*/
class AirtimeViewHolder extends ProgramBaseViewHolder {
@Bind(R.id.program_airtimes)
TextView mProgramAirtimes;
static final int viewType = 0;
public AirtimeViewHolder(View itemView) {
super(itemView);
/**This call to butterknife can be replaced with an
itemView.findViewById(R.id.yourview) */
ButterKnife.bind(this, itemView);
}
//This is where you set your text and hide or show your views.
@Override
public void bindDataToView(int position, Program program) {
List<Airtime> airtimes = program.getAirtimes();
if (!airtimes.isEmpty()) {
mProgramAirtimes.setText(Utils.getFriendlyAirtimes(airtimes));
} else {
mProgramAirtimes.setText(
Utils.getFriendlyAirTime(program.getAirtime()));
}
}
}
/**
This is the Description view that holds descriptions. It is a view holder
that inherits from my base view holder and implements its own version if
bind.
*/
class DescriptionViewHolder extends ProgramBaseViewHolder {
@Bind(R.id.description_card_layout)
TextView mProgramDescription;
static final int viewType = 1;
public DescriptionViewHolder(View itemView) {
super(itemView);
ButterKnife.bind(this, itemView);
}
@Override
public void bindDataToView(int position, Program program) {
mProgramDescription.setText(Html.fromHtml(program.getFullDescription()));
}
}
//This is another type of view with another different type of layout.
class HostsViewHolder extends ProgramBaseViewHolder {
@Bind(R.id.card_view_host_name)
TextView mProgramHostName;
static final int viewType = 2;
public HostsViewHolder(View itemView) {
super(itemView);
ButterKnife.bind(this, itemView);
}
@Override
public void bindDataToView(int position, Program program) {
mProgramHostName.setText(program.getHosts().get(position - 2).getDisplayName());
}
}
//Again another type of view extending my base view.
class CategoriesViewHolder extends ProgramBaseViewHolder {
@Bind(R.id.program_categories)
TextView mProgramCategories;
static final int viewType = 42;
public CategoriesViewHolder(View itemView) {
super(itemView);
ButterKnife.bind(this, itemView);
}
@Override
public void bindDataToView(int position, Program program) {
List<Category> categoryList = program.getCategories();
StringBuilder stringBuilder = new StringBuilder();
for (Category category : categoryList) {
stringBuilder.append(category.getTitle())
.append(" ");
}
mProgramCategories.setText(stringBuilder.toString());
}
}
//This is where the normal looking recycler view code comes in.
private Context mContext;
private LayoutInflater mInflater;
private Program mProgramData;
private int mNextProgramId;
public ProgramRecyclerAdapter(Context context) {
mContext = context;
mInflater = LayoutInflater.from(mContext);
}
/**This method is where I am determining what view type each item in my list
will be. I wanted a single airtimes view followed by a single description
view and then X amount of hosts views and a single category view. I return
position in the third else if because the position helps me determine which
host name to display in the bindDataToViews call of the HostViewHolder.*/
@Override
public int getItemViewType(int position) {
if (position == AirtimeViewHolder.viewType) {
return AirtimeViewHolder.viewType;
} else if (position == DescriptionViewHolder.viewType) {
return DescriptionViewHolder.viewType;
} else if (position > DescriptionViewHolder.viewType
&& position <= DescriptionViewHolder.viewType + getHostsNum()) {
return position;
} else {
return CategoriesViewHolder.viewType;
}
}
//This method figures out how many hosts will be displayed
private int getHostsNum() {
if (mProgramData != null) {
return mProgramData.getHosts().size();
}
return 0;
}
// This method determines if I will show a category view or not.
private int getCategoriesNum() {
if (mProgramData != null && mProgramData.getCategories().size() > 0) {
return 1;
}
return 0;
}
/**This returns haw many items will be in the list. 1 Airtime view, 1
Description view, x amount of Host views and 0 or 1 category views */
@Override
public int getItemCount() {
return 2 + getHostsNum() + getCategoriesNum();
}
/** This returns the appropriate View holder for the requested view type that
was set by getItemViewType(). I pass the inflated parent view and the data.
*/
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if (viewType == AirtimeViewHolder.viewType) {
return new AirtimeViewHolder(mInflater.inflate(R.layout.airtime_card_layout, parent, false));
} else if (viewType == DescriptionViewHolder.viewType) {
return new DescriptionViewHolder(mInflater.inflate(R.layout.description_card_layout, parent, false));
} else if (viewType > DescriptionViewHolder.viewType
&& viewType <= DescriptionViewHolder.viewType + getHostsNum()) {
return new HostsViewHolder(mInflater.inflate(R.layout.hosts_card_layout, parent, false));
} else
return new CategoriesViewHolder(mInflater.inflate(R.layout.categories_card_layout, parent, false));
}
/*This method is what ties everything together. After I ensure that the data
is not null I call bindDataToView on a ProgramBaseViewHolder. Depending on
which type of subclass it is will determine which overridden bindData code to
use. */
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
ProgramBaseViewHolder baseViewHolder = (ProgramBaseViewHolder) holder;
if (mProgramData != null) {
baseViewHolder.bindDataToView(position, mProgramData);
}
}
//This is used to set the data for this program
public void setProgramData(Program program) {
mProgramData = program;
}
public Program getProgramData() {
return mProgramData;
}
public boolean isEmpty() {
return mProgramData == null;
}
}
这是通话时间布局
<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.CardView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="@dimen/card_margin">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="16dp"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/airtimes_label"
android:textSize="18dp"
android:textStyle="bold"
android:textAppearance="@style/TextAppearance.AppCompat.Body2"
android:layout_marginBottom="4dp"/>
<TextView
android:id="@+id/program_airtimes"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="18sp"
android:textAppearance="@style/TextAppearance.AppCompat.Title" />
</LinearLayout>
这是我的主机布局。您会注意到我没有使用此处的大部分视图,因为这是一个正在开发中的应用程序。
<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:card_view="http://schemas.android.com/apk/res-auto"
android:id="@+id/host_card_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="@dimen/card_margin"
card_view:cardBackgroundColor="@color/white"
card_view:cardCornerRadius="2dp">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="8dp" />
<ImageView
android:id="@+id/host_image"
android:layout_width="112dp"
android:layout_height="112dp"
android:layout_alignParentLeft="true"
android:visibility="gone"
android:layout_centerVertical="true"
android:layout_marginRight="8dp" />
<LinearLayout
android:id="@+id/details"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_toRightOf="@id/host_image"
android:orientation="vertical">
<TextView
android:id="@+id/card_view_host_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="18sp"
android:layout_margin="16dp"
android:textAppearance="@style/TextAppearance.AppCompat.Body2"
android:layout_gravity="left" />
<TextView
android:id="@+id/card_view_hosts_programs"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"
android:textStyle="bold"
android:textSize="12sp"
android:layout_marginBottom="16dp"
android:layout_gravity="left"/>
</LinearLayout>
</RelativeLayout>
</android.support.v7.widget.CardView>
我有一个 MessageAdapter
扩展 RecyclerView.Adapter
的消息。这是正常工作时看起来的样子。您单击卡片,它会展开以显示图像。这只会发生在有图片的邮件上:
但是有时我向下滚动并向上滚动,图像就这样消失了:
有时我在 RecyclerView
上上下滚动,不应该有附件的邮件有一个:
在我的 MessageAdapter
中有两个 ViewType
,一个用于 Header 消息,另一个用于评论消息。
这是我的 MessageAdapter
的样子:
public class MessageAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private static final int TYPE_MESSAGE_HEADER = 0;
private static final int TYPE_MESSAGE_COMMENT = 1;
private Context mContext;
private Message mOriginalMessage;
private List<Message> mMessages;
public MessageAdapter(Context context) {
this.mContext = context;
}
public void setOriginalMessage(Message originalMessage) {
this.mOriginalMessage = originalMessage;
}
public void setMessages(List<Message> messages) {
this.mMessages = new ArrayList<>(messages);
}
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if (viewType == TYPE_MESSAGE_HEADER) {
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.message_header,
parent, false);
return new MessageViewHolder(v, viewType);
} else {
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.message_comment,
parent, false);
return new MessageViewHolder(v, viewType);
}
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
final MessageViewHolder messageViewHolder = (MessageViewHolder) holder;
int viewType = holder.getItemViewType();
switch (viewType) {
case TYPE_MESSAGE_HEADER:
if (messageViewHolder.mIsViewExpanded && mOriginalMessage.getAttachment() != null)
animateHeader(messageViewHolder);
// Other initialization stuff
// Set the image
if (mOriginalMessage.getAttachment() != null) {
messageViewHolder.mHeaderImage.setVisibility(View.INVISIBLE);
messageViewHolder.mHeaderShowTextView.setVisibility(View.VISIBLE);
messageViewHolder.mHeaderShowTextView.setText("Show Attachment");
String attachmentUrl = mOriginalMessage.getAttachment().getImageUrl();
if (messageViewHolder.mIsViewExpanded) {
Picasso.with(mContext)
.load(attachmentUrl)
.into(messageViewHolder.mHeaderImage);
}
messageViewHolder.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
animateHeader(messageViewHolder);
}
});
}
break;
case TYPE_MESSAGE_COMMENT:
Message message = mMessage.get(position - 1);
if (messageViewHolder.mIsViewExpanded && message.getAttachment() != null)
animateComment(messageViewHolder);
// Other initialization stuff
// Show attachment if there is an attachment
if (message.getAttachment() != null) {
messageViewHolder.mMessageImage.setVisibility(View.INVISIBLE);
messageViewHolder.mMessageShowTextView.setVisibility(View.VISIBLE);
messageViewHolder.mMessageShowTextView.setText("Show Attachment");
String attachmentUrl = message.getAttachment().getImageUrl();
if (messageViewHolder.mIsViewExpanded) {
Picasso.with(mContext)
.load(attachmentUrl)
.into(messageViewHolder.mMessageImage);
}
messageViewHolder.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
animateComment(messageViewHolder);
}
});
}
break;
default:
break;
}
}
@Override
public int getItemViewType(int position) {
if (isPositionHeader(position)) {
return TYPE_MESSAGE_HEADER;
}
return TYPE_MESSAGE_COMMENT;
}
private boolean isPositionHeader(int position) {
return position == 0;
}
// GUESSING SOMETHING WRONG HERE?
@Override
public int getItemCount() {
if (mOriginalMessage != null && mMessages != null) {
if (!mMessages.isEmpty())
return mMessages.size() + 1;
else
return 1;
} else if (mMessages != null) {
if (!mMessages.isEmpty())
return mMessages.size();
}
return 0;
}
private void animateHeader(final MessageViewHolder messageViewHolder) {
if (messageViewHolder.mOriginalHeight == 0)
messageViewHolder.mOriginalHeight = messageViewHolder.itemView.getHeight();
ValueAnimator valueAnimator;
if (!messageViewHolder.mIsViewExpanded) {
messageViewHolder.mHeaderImage.setVisibility(View.VISIBLE);
messageViewHolder.mHeaderImage.setEnabled(true);
messageViewHolder.mIsViewExpanded = true;
valueAnimator = ValueAnimator
.ofInt(messageViewHolder.mOriginalHeight, commentViewHolder.mOriginalHeight
+ (int) (messageViewHolder.mOriginalHeight * 0.8) + 10);
messageViewHolder.mHeaderShowTextView.setText("Hide Attachment");
} else {
messageViewHolder.mIsViewExpanded = false;
valueAnimator = ValueAnimator.ofInt(messageViewHolder.mOriginalHeight + (int) (messageViewHolder.mOriginalHeight * 0.8)
+ 10, messageViewHolder.mOriginalHeight);
Animation a = new AlphaAnimation(1.00f, 0.00f);
a.setDuration(200);
a.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
messageViewHolder.mHeaderShowTextView.setText("Show Attachment");
}
@Override
public void onAnimationEnd(Animation animation) {
messageViewHolder.mAttachmentImage.setVisibility(View.INVISIBLE);
messageViewHolder.mHeaderImage.setEnabled(false);
}
@Override
public void onAnimationRepeat(Animation animation) {
}
});
messageViewHolder.mHeaderImage.startAnimation(a);
}
valueAnimator.setDuration(400);
valueAnimator.setInterpolator(new BakedBezierInterpolator());
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
messageViewHolder.itemView.getLayoutParams().height = (int) animation.getAnimatedValue();
messageViewHolder.itemView.requestLayout();
}
});
valueAnimator.start();
}
private void animateComment(final MessageViewHolder messageViewHolder) {
if (messageViewHolder.mOriginalHeight == 0)
messageViewHolder.mOriginalHeight = messageViewHolder.itemView.getHeight();
ValueAnimator valueAnimator;
if (!messageViewHolder.mIsViewExpanded) {
messageViewHolder.mMessageImage.setVisibility(View.VISIBLE);
messageViewHolder.mMessageImage.setEnabled(true);
messageViewHolder.mIsViewExpanded = true;
valueAnimator = ValueAnimator
.ofInt(messageViewHolder.mOriginalHeight, messageViewHolder.mOriginalHeight
+ (int) (messageViewHolder.mOriginalHeight * 0.8) + 10);
messageViewHolder.mMessageShowTextView.setText("Hide Attachment");
} else {
messageViewHolder.mIsViewExpanded = false;
valueAnimator = ValueAnimator
.ofInt(messageViewHolder.mOriginalHeight + (int) (messageViewHolder.mOriginalHeight * 0.8)
+ 10, messageViewHolder.mOriginalHeight);
Animation a = new AlphaAnimation(1.00f, 0.00f);
a.setDuration(200);
a.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
messageViewHolder.mMessageShowTextView.setText("Show Attachment");
}
@Override
public void onAnimationEnd(Animation animation) {
messageViewHolder.mMessageImage.setVisibility(View.INVISIBLE);
messageViewHolder.mMessageImage.setEnabled(false);
}
@Override
public void onAnimationRepeat(Animation animation) {
}
});
messageViewHolder.mMessageImage.startAnimation(a);
}
valueAnimator.setDuration(300);
valueAnimator.setInterpolator(new BakedBezierInterpolator());
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
messageViewHolder.itemView.getLayoutParams().height = (int) animation.getAnimatedValue();
messageViewHolder.itemView.requestLayout();
}
});
valueAnimator.start();
}
public class MessageViewHolder extends RecyclerView.ViewHolder {
// Header
private ImageView mHeaderImage;
private TextView mHeaderShowTextView;
// Comment
private ImageView mMessageImage;
private TextView mMessageShowTextView;
// Variables for View
private int mOriginalHeight = 0;
private boolean mIsViewExpanded = false;
private int mHolderId;
public MessageViewHolder(View itemView, int viewType) {
super(itemView);
if (viewType == TYPE_MESSAGE_HEADER)
initHeaderViews(itemView);
else if (viewType == TYPE_MESSAGE_COMMENT)
initCommentViews(itemView);
}
private void initHeaderViews(View view) {
mHeaderImage = (ImageView) view.findViewById(R.id.header_image);
mHeaderShowTextView = (TextView) view.findViewById(R.id.header_show_textview);
mHeaderShowTextView.setVisibility(View.INVISIBLE);
if (!mIsViewExpanded) {
mHeaderImage.setEnabled(false);
mHeaderImage.setVisibility(View.GONE);
}
mHolderId = TYPE_MESSAGE_HEADER;
}
private void initCommentViews(View view) {
mMessageImage = (ImageView) view.findViewById(R.id.itemAttachmentImage);
mMessageShowTextView = (TextView) view.findViewById(R.id.showItemAttachment);
mMessageShowTextView.setVisibility(View.INVISIBLE);
if (!mIsViewExpanded) {
mMessageShowTextView.setText("Show Attachment");
mMessageImage.setEnabled(false);
mMessageImage.setVisibility(View.GONE);
}
mHolderId = TYPE_MESSAGE_COMMENT;
}
}
}
有没有更好更准确的方法呢?具体来说,最大的问题是消除任何不一致之处,以及这段代码是否可以解耦。
我怎样才能获得正确的邮件以仅正确显示其附件?如何在向上或向下滚动时将图像保留在卡片中?当我添加新评论时,这也开始变得混乱,因为现在有一个 N + 1
问题。
具体来说,我想知道是否有更好的方法来处理多个 ViewHolders
,而不是尝试处理 RecyclerView
.
offset
值
更新:
我可以通过在 Fragment
中使用以下内容来降低我的适配器的一些复杂性我正在初始化我的 RecyclerView.Adapter
中:
public void setParentMessage(Message parentMessage) {
this.mParentMessage = parentMessage;
mAllMessages = new ArrayList<>();
mAllMessages.add(mParentMessage);
}
public void setMessages(List<Messages> messages) {
this.mMessages = messages;
mAllMessages.addAll(mMessages);
}
然后我在启动时初始化我的适配器:
mMessageAdapter.setMessages(mAllMessages);
然后,如果我必须向我的列表中添加一个新的 Message
object,我可以简单地执行以下操作:
public void addComment(Message message) {
mMessageAdapter.addItem(mMessageAdapter.getItemCount(), message);
mRecyclerView.scrollToPosition(mMessageAdapter.size() - 1);
}
在我的 MessageAdapter
中,我有以下内容来添加新的消息评论:
public void addItem(int position, Message message) {
mMessages.add(position, message);
notifyItemInserted(position);
}
这意味着我可以更改它:
@Override
public int getItemCount() {
if (mOriginalMessage != null && mMessages != null) {
if (!mMessages.isEmpty())
return mMessages.size() + 1;
else
return 1;
} else if (mMessages != null) {
if (!mMessages.isEmpty())
return mMessages.size();
}
return 0;
}
为此:
@Override
public int getItemCount() {
if (mMessages != null) {
if (!mMessages.isEmpty())
return mMessages.size();
}
return 0;
}
在我的 onBindViewHolder
方法中,我不再需要跟踪偏移量,所以这发生了变化:
Message message = mMessage.get(position - 1);
收件人:
Message message = mMessage.get(position);
此外,我将 MessageViewHolder
解耦为两个单独的 ViewHolder
类:
public class MessageHeaderViewHolder extends RecyclerView.ViewHolder {
// Header
private ImageView mHeaderImage;
private TextView mHeaderShowTextView;
// Variables for View
private int mOriginalHeight = 0;
private boolean mIsViewExpanded = false;
private int mHolderId;
public MessageHeaderViewHolder(View itemView, int viewType) {
super(itemView);
initHeaderViews(itemView);
}
private void initHeaderViews(View view) {
mHeaderImage = (ImageView) view.findViewById(R.id.header_image);
mHeaderShowTextView = (TextView) view.findViewById(R.id.header_show_textview);
mHeaderShowTextView.setVisibility(View.INVISIBLE);
if (!mIsViewExpanded) {
mHeaderImage.setEnabled(false);
mHeaderImage.setVisibility(View.GONE);
}
mHolderId = TYPE_MESSAGE_HEADER;
}
private void initOnClickListener() {
itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
animate(v);
}
});
}
private void removeClickListener() {
if (itemView.hasOnClickListeners())
itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// THIS CODE DOESN'T WORK AS THOUGHT.
// Empty click listener to keep itemSelectableBackground.
}
});
}
private void animate(View v) {
// All animation code for header moved here
}
}
另一个也一样ViewHolder
:
public class MessageCommentViewHolder extends RecyclerView.ViewHolder {
// Comment
private ImageView mMessageImage;
private TextView mMessageShowTextView;
// Variables for View
private int mOriginalHeight = 0;
private boolean mIsViewExpanded = false;
private int mHolderId;
public MessageCommentViewHolder(View itemView, int viewType) {
super(itemView);
initCommentViews(itemView);
}
private void initCommentViews(View view) {
mMessageImage = (ImageView) view.findViewById(R.id.itemAttachmentImage);
mMessageShowTextView = (TextView) view.findViewById(R.id.showItemAttachment);
mMessageShowTextView.setVisibility(View.INVISIBLE);
if (!mIsViewExpanded) {
mMessageShowTextView.setText("Show Attachment");
mMessageImage.setEnabled(false);
mMessageImage.setVisibility(View.GONE);
}
mHolderId = TYPE_MESSAGE_COMMENT;
}
private void initOnClickListener() {
itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
animate(v);
}
});
}
private void removeClickListener() {
if (itemView.hasOnClickListeners())
itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// THIS CODE DOESN'T WORK AS THOUGHT.
// Empty click listener to keep itemSelectableBackground.
}
});
}
private void animate(View v) {
// All animation code for header moved here
}
}
这意味着在我的 onBindViewHolder
方法中,我可以为每种类型的项目执行以下操作(请记住,现在将有两种类型的 ViewHolder
,因此 messageViewHolder
会更改为 headerViewHolder
或 commentViewHolder
或类似内容):
if (message.getAttachment() != null) {
messageViewHolder.mMessageImage.setVisibility(View.INVISIBLE);
messageViewHolder.mMessageShowTextView.setVisibility(View.VISIBLE);
messageViewHolder.mMessageShowTextView.setText("Show Attachment");
String attachmentUrl = message.getAttachment().getImageUrl();
Picasso.with(mContext)
.load(attachmentUrl)
.into(messageViewHolder.mMessageImage);
messageViewHolder.initOnClickListener();
} else {
messageViewHolder.removeClickListener();
messageViewHolder.mMessageImage.setVisibility(View.GONE);
messageViewholder.mMessageShowTextView.setVisibility(View.GONE);
}
现在工作正常,虽然这是一个非常 hacky 的解决方案,但我计划使用 doubleA 的答案,并在本周末使这段代码更加优化。一个仍然存在的问题是一些项目将有 itemSelectableBackground
并且是 clickable
而其他的则没有,据我了解 removeClickListener()
应该初始化一个空的 View.OnClickListener
从而使项目可点击因此显示 itemSelectableBackground
但事实并非如此?? Log
输出表明我正在正确初始化侦听器和图像。
这是recycler views的一个普遍问题,实际上是我最近的一个面试问题。当您使用回收器视图和视图持有者时,它会按照它说的做......回收视图。因此,如果您开始向下滚动并且一个视图附有图像并且您显示该图像,然后当该视图被回收并且您放入其中的新数据没有与之关联的图像时,您的代码不会明确告诉父视图持有者隐藏其中的图像视图。因此,您的图片会出现在回收视图中,因为它已经存在并且只是被回收了。
这是我的建议
if (message.getAttachment() != null) {
//all your fun view binding stuff.
} else {
messageViewHolder.mMessageImage.setVisibility(View.GONE);
}
此外,我建议您将视图绑定代码放入您的视图持有者中,并在必要时使用 2 个具有相同布局的不同视图持有者。它将缩短您的 onBindViewHolder 调用并将不同的视图绑定代码与与之关联的视图持有者链接起来。这是我的一个回收器视图适配器的示例。
public class ProgramRecyclerAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
/**
This is an abstract class that all of my viewholders inherit from.
This is a contract telling me that any subclasses that inherit from this
base class are required to write their own `public void bind(int position,
Program program);` method.
*/
abstract class ProgramBaseViewHolder extends RecyclerView.ViewHolder {
public ProgramBaseViewHolder(View itemView) {
super(itemView);
}
public abstract void bindDataToView(int position, Program program);
}
我意识到关键字 bind 在这个视图持有者中被多次使用,并且他们在做不同的事情。 @Bind
和 Butterknife.bind 是名为 Butterknife 的视图绑定库的一部分,该库由为您提供 Picasso 的同一批好人制作。 "bind" 的这种用法等同于您的 findViewById()
调用。抽象 class' 绑定只是将数据绑定到适配器中的视图的方法的通用名称。我已将 bind 重命名为 bindDataToView 以更明确一些。
/**
This is the Airtime view that holds airtimes. It is a view holder that
inherits from my base view holder and implements its own version if bind.
*/
class AirtimeViewHolder extends ProgramBaseViewHolder {
@Bind(R.id.program_airtimes)
TextView mProgramAirtimes;
static final int viewType = 0;
public AirtimeViewHolder(View itemView) {
super(itemView);
/**This call to butterknife can be replaced with an
itemView.findViewById(R.id.yourview) */
ButterKnife.bind(this, itemView);
}
//This is where you set your text and hide or show your views.
@Override
public void bindDataToView(int position, Program program) {
List<Airtime> airtimes = program.getAirtimes();
if (!airtimes.isEmpty()) {
mProgramAirtimes.setText(Utils.getFriendlyAirtimes(airtimes));
} else {
mProgramAirtimes.setText(
Utils.getFriendlyAirTime(program.getAirtime()));
}
}
}
/**
This is the Description view that holds descriptions. It is a view holder
that inherits from my base view holder and implements its own version if
bind.
*/
class DescriptionViewHolder extends ProgramBaseViewHolder {
@Bind(R.id.description_card_layout)
TextView mProgramDescription;
static final int viewType = 1;
public DescriptionViewHolder(View itemView) {
super(itemView);
ButterKnife.bind(this, itemView);
}
@Override
public void bindDataToView(int position, Program program) {
mProgramDescription.setText(Html.fromHtml(program.getFullDescription()));
}
}
//This is another type of view with another different type of layout.
class HostsViewHolder extends ProgramBaseViewHolder {
@Bind(R.id.card_view_host_name)
TextView mProgramHostName;
static final int viewType = 2;
public HostsViewHolder(View itemView) {
super(itemView);
ButterKnife.bind(this, itemView);
}
@Override
public void bindDataToView(int position, Program program) {
mProgramHostName.setText(program.getHosts().get(position - 2).getDisplayName());
}
}
//Again another type of view extending my base view.
class CategoriesViewHolder extends ProgramBaseViewHolder {
@Bind(R.id.program_categories)
TextView mProgramCategories;
static final int viewType = 42;
public CategoriesViewHolder(View itemView) {
super(itemView);
ButterKnife.bind(this, itemView);
}
@Override
public void bindDataToView(int position, Program program) {
List<Category> categoryList = program.getCategories();
StringBuilder stringBuilder = new StringBuilder();
for (Category category : categoryList) {
stringBuilder.append(category.getTitle())
.append(" ");
}
mProgramCategories.setText(stringBuilder.toString());
}
}
//This is where the normal looking recycler view code comes in.
private Context mContext;
private LayoutInflater mInflater;
private Program mProgramData;
private int mNextProgramId;
public ProgramRecyclerAdapter(Context context) {
mContext = context;
mInflater = LayoutInflater.from(mContext);
}
/**This method is where I am determining what view type each item in my list
will be. I wanted a single airtimes view followed by a single description
view and then X amount of hosts views and a single category view. I return
position in the third else if because the position helps me determine which
host name to display in the bindDataToViews call of the HostViewHolder.*/
@Override
public int getItemViewType(int position) {
if (position == AirtimeViewHolder.viewType) {
return AirtimeViewHolder.viewType;
} else if (position == DescriptionViewHolder.viewType) {
return DescriptionViewHolder.viewType;
} else if (position > DescriptionViewHolder.viewType
&& position <= DescriptionViewHolder.viewType + getHostsNum()) {
return position;
} else {
return CategoriesViewHolder.viewType;
}
}
//This method figures out how many hosts will be displayed
private int getHostsNum() {
if (mProgramData != null) {
return mProgramData.getHosts().size();
}
return 0;
}
// This method determines if I will show a category view or not.
private int getCategoriesNum() {
if (mProgramData != null && mProgramData.getCategories().size() > 0) {
return 1;
}
return 0;
}
/**This returns haw many items will be in the list. 1 Airtime view, 1
Description view, x amount of Host views and 0 or 1 category views */
@Override
public int getItemCount() {
return 2 + getHostsNum() + getCategoriesNum();
}
/** This returns the appropriate View holder for the requested view type that
was set by getItemViewType(). I pass the inflated parent view and the data.
*/
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if (viewType == AirtimeViewHolder.viewType) {
return new AirtimeViewHolder(mInflater.inflate(R.layout.airtime_card_layout, parent, false));
} else if (viewType == DescriptionViewHolder.viewType) {
return new DescriptionViewHolder(mInflater.inflate(R.layout.description_card_layout, parent, false));
} else if (viewType > DescriptionViewHolder.viewType
&& viewType <= DescriptionViewHolder.viewType + getHostsNum()) {
return new HostsViewHolder(mInflater.inflate(R.layout.hosts_card_layout, parent, false));
} else
return new CategoriesViewHolder(mInflater.inflate(R.layout.categories_card_layout, parent, false));
}
/*This method is what ties everything together. After I ensure that the data
is not null I call bindDataToView on a ProgramBaseViewHolder. Depending on
which type of subclass it is will determine which overridden bindData code to
use. */
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
ProgramBaseViewHolder baseViewHolder = (ProgramBaseViewHolder) holder;
if (mProgramData != null) {
baseViewHolder.bindDataToView(position, mProgramData);
}
}
//This is used to set the data for this program
public void setProgramData(Program program) {
mProgramData = program;
}
public Program getProgramData() {
return mProgramData;
}
public boolean isEmpty() {
return mProgramData == null;
}
}
这是通话时间布局
<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.CardView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="@dimen/card_margin">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="16dp"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/airtimes_label"
android:textSize="18dp"
android:textStyle="bold"
android:textAppearance="@style/TextAppearance.AppCompat.Body2"
android:layout_marginBottom="4dp"/>
<TextView
android:id="@+id/program_airtimes"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="18sp"
android:textAppearance="@style/TextAppearance.AppCompat.Title" />
</LinearLayout>
这是我的主机布局。您会注意到我没有使用此处的大部分视图,因为这是一个正在开发中的应用程序。
<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:card_view="http://schemas.android.com/apk/res-auto"
android:id="@+id/host_card_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="@dimen/card_margin"
card_view:cardBackgroundColor="@color/white"
card_view:cardCornerRadius="2dp">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="8dp" />
<ImageView
android:id="@+id/host_image"
android:layout_width="112dp"
android:layout_height="112dp"
android:layout_alignParentLeft="true"
android:visibility="gone"
android:layout_centerVertical="true"
android:layout_marginRight="8dp" />
<LinearLayout
android:id="@+id/details"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_toRightOf="@id/host_image"
android:orientation="vertical">
<TextView
android:id="@+id/card_view_host_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="18sp"
android:layout_margin="16dp"
android:textAppearance="@style/TextAppearance.AppCompat.Body2"
android:layout_gravity="left" />
<TextView
android:id="@+id/card_view_hosts_programs"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"
android:textStyle="bold"
android:textSize="12sp"
android:layout_marginBottom="16dp"
android:layout_gravity="left"/>
</LinearLayout>
</RelativeLayout>
</android.support.v7.widget.CardView>