循环范围的反向布局启动滚动位置问题



我正在创建一个聊天应用程序,并且用于显示我使用的回收器视图的消息。最新消息显示在底部。用户滚动查看更多消息。

加载聊天屏幕时,视图不会从最底部开始,最新消息不可见。用户必须向下滚动几行才能查看最新消息。这是不良的UI,最新消息应在屏幕的末端/底部看到。

我正在使用setReverseLayout(true)setStackFromEnd(false),并且我在线搜索了类似的问题,没有运气。目前,我将滚动位置设置为0后,将回收器视图延迟设置后,但这并不总是可行,而且很跳跃。

如果我正常设置RecyClerview(不使用setReverseLayoutsetStackFromEnd(,则最新消息每次都可以完美地加载到顶部。

这是启动回收器视图的代码:

RecyclerView recyclerView = findViewById(R.id.recyclerViewMessageRoom);
    adapter = new RA_MessageRoom(this, userFirebaseUid, messagesGroupedByDate, messageUsers);
    recyclerView.setAdapter(adapter);
    LinearLayoutManager layoutManager = new LinearLayoutManager(this);
    layoutManager.setReverseLayout(true);
    layoutManager.setStackFromEnd(false);
    recyclerView.setHasFixedSize(true);
    recyclerView.setLayoutManager(layoutManager);
    // -- Workaround with delay - still doesn't completely work
    new Handler().postDelayed(() -> {
        recyclerView.scrollToPosition(0);
    }, 200);

任何经历了这个问题并知道如何解决的人,请分享!谢谢。

编辑(2019年7月6日(:

这是回收器适配器的代码。作为提醒,如果我删除了反向布局设置,则可以很好地工作。

ra_messageroom:

public class RA_MessageRoom extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private static final String TAG = "RA_MessageRoom";
private static final int TYPE_USER = 1;
private static final int TYPE_PARTICIPANT = 2;
private Context context;
private String userFirebaseUid;
private List<MessagesDateGrouper> messagesGroupedByDate;
private HashSet<MessagesUserModel> messageUsers;
public RA_MessageRoom(Context context, String userFirebaseUid, List<MessagesDateGrouper> messagesGroupedByDate, HashSet<MessagesUserModel> messageUsers) {
    this.context = context;
    this.userFirebaseUid = userFirebaseUid;
    this.messagesGroupedByDate = messagesGroupedByDate;
    this.messageUsers = messageUsers;
}
@Override
public int getItemViewType(int position) {
    if (messagesGroupedByDate.get(position).getViewType() == MessagesDateGrouper.TYPE_CHAT) {
        MessageChatItem message = (MessageChatItem) messagesGroupedByDate.get(position);
        if (message.getMessages().getSenderFirebaseUid().equals(userFirebaseUid)) {
            return TYPE_USER;
        } else {
            return TYPE_PARTICIPANT;
        }
    } else {
        return messagesGroupedByDate.get(position).getViewType();
    }
}

@Override
public int getItemCount() {
    return messagesGroupedByDate != null ? messagesGroupedByDate.size() : 0;
}
@NonNull
@Override
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
    final RecyclerView.ViewHolder holder;
    View view;
    switch (viewType) {
        case MessagesDateGrouper.TYPE_DATE:
            view = LayoutInflater.from(parent.getContext()).inflate(R.layout.recycler_message_room_separator, parent, false);
            holder = new MessageRoomDateVH(view);
            break;
        case TYPE_USER:
            view = LayoutInflater.from(parent.getContext()).inflate(R.layout.recycler_message_room_user, parent, false);
            holder = new MessageRoomUserVH(view);
            break;
        case TYPE_PARTICIPANT:
            view = LayoutInflater.from(parent.getContext()).inflate(R.layout.recycler_message_room_participant, parent, false);;
            holder = new MessageRoomParticipantVH(view);
            break;
        default:
            view = LayoutInflater.from(parent.getContext()).inflate(R.layout.recycler_message_room_user, parent, false);;
            holder = new MessageRoomUserVH(view);
            break;
    }
    return holder;
}
@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
    if (holder instanceof MessageRoomDateVH) {
        MessageDateItem date = (MessageDateItem) messagesGroupedByDate.get(position);
        ((MessageRoomDateVH)holder).date.setText(date.getDate());
    } else if (holder instanceof MessageRoomParticipantVH) {
        MessageRoomParticipantVH view = (MessageRoomParticipantVH) holder;
        MessageChatItem messageItem = (MessageChatItem) messagesGroupedByDate.get(position);
        MessagesModel message = messageItem.getMessages();
        
        view.name.setText(context.getString(R.string.unknown));
        view.profileImage.setImageResource(R.drawable.default_profile_image_grey);
        for (MessagesUserModel user: messageUsers) {
            if (user.getFirebaseId().equals(message.getSenderFirebaseUid())) {
                int fallbackImage;
                if (user.getMerchant() == null || !user.getMerchant()) {
                    fallbackImage = R.drawable.default_profile_image_grey;
                } else {
                    fallbackImage = R.drawable.store_profile;
                }
                GlideApp.with(context)
                        .load(user.getPhotoThumbUrl())
                        .placeholder(R.drawable.placeholder)
                        .fallback(fallbackImage)
                        .into(view.profileImage);
                view.name.setText(user.getName());
                break;
            }
        }

        if (message.getImageUrl() != null && !message.getImageUrl().equals("") ) {
            view.image.setVisibility(View.VISIBLE);
            view.imageSpinner.setVisibility(View.VISIBLE);
            view.chat.setVisibility(View.GONE);

            view.image.setClipToOutline(true);
            GlideApp.with(context)
                    .load(message.getImageUrl())
                    .placeholder(R.drawable.placeholder_message)
                    .fallback(R.drawable.placeholder_message)
                    .listener(new RequestListener<Drawable>() {
                        @Override
                        public boolean onLoadFailed(@Nullable GlideException e, Object model, Target<Drawable> target, boolean isFirstResource) {
                            return false;
                        }
                        @Override
                        public boolean onResourceReady(Drawable resource, Object model, Target<Drawable> target, DataSource dataSource, boolean isFirstResource) {
                            view.imageSpinner.setVisibility(View.GONE);
                            return false;
                        }
                    })
                    .into(view.image);

        } else {
            view.chat.setVisibility(View.VISIBLE);
            view.image.setVisibility(View.GONE);
            view.imageSpinner.setVisibility(View.GONE);
            view.chat.setText(message.getMessageText());
        }
        String time = DateFormatService.messageRoomParseDateToTimeString(message.getDate());
        view.date.setText(time);
    } else if (holder instanceof MessageRoomUserVH){
        MessageRoomUserVH view = (MessageRoomUserVH) holder;
        MessageChatItem messageItem = (MessageChatItem) messagesGroupedByDate.get(position);
        MessagesModel message = messageItem.getMessages();

        if (message.getImageUrl() != null && !message.getImageUrl().equals("") ) {
            view.image.setVisibility(View.VISIBLE);
            view.imageSpinner.setVisibility(View.VISIBLE);
            view.chat.setVisibility(View.GONE);
            view.image.setClipToOutline(true);
            GlideApp.with(context)
                    .load(message.getImageUrl())
                    .placeholder(R.drawable.placeholder_message)
                    .fallback(R.drawable.placeholder_message)
                    .listener(new RequestListener<Drawable>() {
                        @Override
                        public boolean onLoadFailed(@Nullable GlideException e, Object model, Target<Drawable> target, boolean isFirstResource) {
                            return false;
                        }
                        @Override
                        public boolean onResourceReady(Drawable resource, Object model, Target<Drawable> target, DataSource dataSource, boolean isFirstResource) {
                            view.imageSpinner.setVisibility(View.GONE);
                            return false;
                        }
                    })
                    .into(view.image);

        } else {
            view.chat.setVisibility(View.VISIBLE);
            view.image.setVisibility(View.GONE);
            view.imageSpinner.setVisibility(View.GONE);
            view.chat.setText(message.getMessageText());
        }
        String time = DateFormatService.messageRoomParseDateToTimeString(message.getDate());
        view.date.setText(time);
    }

}
public class MessageRoomDateVH extends RecyclerView.ViewHolder {
    TextView date;
    public MessageRoomDateVH(@NonNull View itemView) {
        super(itemView);
        date = itemView.findViewById(R.id.textMessageRoomDateSection);
    }
}

public class MessageRoomParticipantVH extends RecyclerView.ViewHolder {
    ImageView profileImage;
    TextView name;
    TextView chat;
    ImageView image;
    TextView date;
    ProgressBar imageSpinner;
    public MessageRoomParticipantVH(@NonNull View itemView) {
        super(itemView);
        profileImage = itemView.findViewById(R.id.imageMessageRoomParticipantProfile);
        name = itemView.findViewById(R.id.textMessageRoomParticipantName);
        chat = itemView.findViewById(R.id.textMessageRoomParticipantChat);
        image = itemView.findViewById(R.id.imageMessageRoomParticipantImage);
        date = itemView.findViewById(R.id.textMessageRoomParticipantDate);
        imageSpinner = itemView.findViewById(R.id.progressBarMessageRoomParticipantImage);

    }
}
public class MessageRoomUserVH extends RecyclerView.ViewHolder {
    TextView chat;
    ImageView image;
    TextView date;
    ProgressBar imageSpinner;
    public MessageRoomUserVH(@NonNull View itemView) {
        super(itemView);
        chat = itemView.findViewById(R.id.textMessageRoomUserChat);
        image = itemView.findViewById(R.id.imageMessageRoomUserImage);
        date = itemView.findViewById(R.id.textMessageRoomUserDate);
        imageSpinner = itemView.findViewById(R.id.progressBarMessageRoomUserImage);
    }
}

}

编辑:

尽管偏好是将stackfromend设置为false,但我最终将stackfromend从false更改为true,删除了延迟,并按照可接受的答案的建议来解决此问题。

更新的工作代码:

RecyclerView recyclerView = findViewById(R.id.recyclerViewMessageRoom);
    adapter = new RA_MessageRoom(this, userFirebaseUid, messagesGroupedByDate, messageUsers);
    recyclerView.setAdapter(adapter);
    LinearLayoutManager layoutManager = new LinearLayoutManager(this);
    layoutManager.setReverseLayout(true);
    layoutManager.setStackFromEnd(true);
    recyclerView.setHasFixedSize(true);
    recyclerView.setLayoutManager(layoutManager);
    recyclerView.scrollToPosition(0);

感谢您的帮助!

您还需要在首页加载上处理消息加载,收到的新消息位于RecyClerview 的底部:

if (pageLoad) { 
 
 list.add(Model)
} else {
 
 // Add to the top of the list (since list is reverse message will come at the bottom)
  list.add(0, Model)
}

这很容易您只需添加单行: -

LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this);
linearLayoutManager.setStackFromEnd(true);
recyclerView.setLayoutManager(linearLayoutManager);

注意:被接受的答案误导了您。

LinearLayoutManager的列表激情有4种可能性。

    1. 
     startStackFromEnd=true
     reverseLayout=true
    2.
     startStackFromEnd=false
     reverseLayout=false
    3. 
     startStackFromEnd=true
     reverseLayout=false
    4. 
     startStackFromEnd=false //best for chatting
     reverseLayout=true      //applications

每种组合的行为都不同,我不知道您的要求到底是什么,所以请围绕这些价值观,我相信您会得到想要的。

请参阅此答案:

recyclerview-反向顺序

并为您的RA_MessageRoom创建一个设置器以更新您的messagesGroupedByDate。这样的东西:

Collections.reverse(messagesGroupedByDate); // Reverse your dataset like in answer above
adapter.setMessagesGroupedByDate(messagesGroupedByDate); // Update your dataset in adapter
adapter.notifyDataSetChanged(); // Notify your adapter

每次新消息到达时,您的列表将被更新。您需要将此摘要放在数据获取中,然后可以删除延迟的处理程序。

您可以在XML中设置布局管理器并进行反向布局。当我们设置反向布局时,我们会发现此问题。我们可以通过添加setStackFromend = true

来解决它
    app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
    android:orientation="vertical"
    app:reverseLayout="true"
    app:stackFromEnd="true"

看起来像是回收器查看实现中的一个错误,或者是在线性布局管理器中。

要加入,您需要确保stackFromEnd是正确的,当您的回收器可以滚动时。如果您的回收器没有足够的物品可以滚动,并且您仍然希望它们位于屏幕的底部,请将stackFromEnd放回false,如果回收器无法滚动。

要确定您的视图是否可以滚动,您可以使用Kotlin扩展名:

/**
 * Tells if this view can scroll vertically.
 * This view may still contain children who can scroll.
 */
fun View.canScrollVertically() = this.let {
    it.canScrollVertically(-1) || it.canScrollVertically(1)
}

在fulguris中,我使用以下功能来修复该错误,诀窍是找到在您的代码中调用它的正确位置:

/**
 * Workaround reversed layout bug: https://github.com/Slion/Fulguris/issues/212    
 */
fun fixScrollBug(aList : RecyclerView): Boolean {
    val lm = (aList.layoutManager as LinearLayoutManager)
    // Can't change stackFromEnd when computing layout or scrolling otherwise it throws an exception
    if (!aList.isComputingLayout) {
        if (aList.context.configPrefs.toolbarsBottom) {
            // Workaround reversed layout bug: https://github.com/Slion/Fulguris/issues/212
            if (lm.stackFromEnd != aList.canScrollVertically()) {
                lm.stackFromEnd = !lm.stackFromEnd
                return true
            }
        } else {
            // Make sure this is set properly when not using bottom toolbars
            // No need to check if the value is already set properly as this is already done internally
            lm.stackFromEnd = false
        }
    }
    return false
}

我可以用lm.reverseLayout对CC_11进行特定的检查。

有同样的问题 - 提交列表总是会导致显示的项目位于列表中间的某个地方,而不是底部显示的第一个项目。当应用程序:reverseLayout =&quort true;但是定期用于非转型。意识到我的回收库是在约束layout内的0DP高度,对父母的约束和底部的约束。更改为高度的match_parent,它正常工作 - 现在,底部的第一个项目是提交列表的起点。

如果您想要相同的可靠性(行为(与默认使用回收器视图一样,即使用startStackFromEnd=falsereverseLayout=false我发现的最佳解决方案是将180 rotation应用于回收器视图和与适配器项目一样。

因此,在您的片段课中,您会拥有

之类的东西
override fun onCreateView(inflater: LayoutInflater,
                              container: ViewGroup?,
                              savedInstanceState: Bundle?): View {
     //code...
     yourRecyclerView.rotation = 180F
     //code...
}

和您的适配器(带有视图或数据框(之类的东西

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
    //code...
    val binding = //inflation...
    binding.root.rotation = 180F
    //code
}

最新更新