簡體   English   中英

Recyclerview反向布局啟動滾動position問題

[英]Recyclerview reverse layout starting scroll position issue

我正在創建一個聊天應用程序並顯示我正在使用回收站視圖的消息。 最新消息顯示在底部。 用戶向上滾動以查看更多消息。

加載聊天屏幕時,視圖不會從最底部開始,並且最新消息不可見。 用戶必須向下滾動幾行才能看到最新消息。 這是一個糟糕的 UI,最新的消息應該在屏幕的末尾/底部可見。

我正在使用setReverseLayout(true)setStackFromEnd(false) ,並且我在網上搜索了類似的問題,但沒有運氣。 現在,我在延遲設置回收站視圖后立即將滾動 position 設置為 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,消除了延遲,並按照接受的答案的建議繼續設置滾動 position 以解決此問題。

更新的工作代碼:

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);

看到這個答案:

RecyclerView - 逆序

並為您的RA_MessageRoom創建一個 setter 以更新您的messagesGroupedByDate 類似的東西:

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

每次收到新消息時,您的列表都會更新。 您需要將此代碼段放入您的數據提取中,然后您可以刪除您的 postDelayed 處理程序。

您可以在 xml 和反向布局中設置布局管理器。 當我們設置反向布局時,我們發現了這個問題。 我們可以通過添加 setStackFromEnd =true 來解決它

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

注意:接受的答案會誤導您。

關於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

每種組合的作用不同我不知道你的要求到底是什么,所以圍繞這些值進行調整,我相信你會得到你想要的。

看起來像是回收器視圖實現中的錯誤,或者更確切地說是該線性布局管理器中的錯誤。

要解決此問題,您需要確保當您的回收站可以滾動時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檢查替換toolbarsBottom上的特定於應用程序的檢查。

有同樣的問題 - 提交列表總是會導致顯示的項目位於列表中間的某個位置,而不是第一個項目顯示在底部。 這發生在 app:reverseLayout="true" 但經常用於 non-reversed 時。 意識到我的 recyclerview 在約束布局內的高度為 0dp,約束從頂部到父級,從底部到父級。 將高度更改為 match_parent 並且它可以正常工作 - 現在底部的第一項是提交列表時的起點。

如果您想要與默認情況下使用 Recycler 視圖時相同的可靠性(行為),即startStackFromEnd=falsereverseLayout=false ,我發現的最佳解決方案是將180 rotation應用於回收器視圖,並將其應用於適配器項。

所以在你的片段 class 你會有類似的東西

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

}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM