将 RecyclerView 可见性从 Gone 更改为 Visible,在显示新添加的项目之前暂时错误地显示先前删除的项目

Posted

技术标签:

【中文标题】将 RecyclerView 可见性从 Gone 更改为 Visible,在显示新添加的项目之前暂时错误地显示先前删除的项目【英文标题】:Change in RecyclerView visibility from Gone to Visible, incorrectly shows previously removed item momentarily, before displaying newly added item 【发布时间】:2017-08-09 00:18:33 【问题描述】:

背景/上下文:

在我的主要活动中,我有一个标签布局,有两个标签。每个选项卡包含两个片段,SearchVehicleFragment 和 VehicleListFragment。

以下是 VehicleListFragment 的布局文件。

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_
    android:layout_
    xmlns:tools="http://schemas.android.com/tools"
    tools:context=".ui.fragments.VehicleListFragment"
    android:orientation="vertical">


    <com.boulevardclan.vvp.ui.recyclerviews.VehicleRecyclerView
        android:id="@+id/rvVehicleList"
        android:layout_
        android:layout_ />

    <TextView
        android:id="@+id/tvNoSearchedVehicles"
        style="?android:attr/textAppearanceMedium"
        android:layout_
        android:layout_
        android:layout_gravity="center"
        android:gravity="center"
        android:text="You haven't searched for any vehicles yet." />
</LinearLayout>

我的 VehicleRecyclerView 的创建方式与 AttractionsRecyclerView 完全一样 here。这是因为我想为我的 VehicleRecyclerView 实现空状态机制。另外,在初始化期间,我的 VehicleRecyclerView 有 setHasFixedSize(true)

public class VehicleRecyclerView extends RecyclerView 
    private View mEmptyView;

    public VehicleRecyclerView(Context context) 
        super(context);
    

    private AdapterDataObserver mDataObserver = new AdapterDataObserver() 
        @Override
        public void onChanged() 
            super.onChanged();
            updateEmptyView();
        

        @Override
        public void onItemRangeRemoved(int positionStart, int itemCount) 
            super.onItemRangeRemoved(positionStart, itemCount);
            try
                updateEmptyView();
            catch(Exception e)
            //                TODO
            
        

        @Override
        public void onItemRangeInserted(int positionStart, int itemCount) 
            super.onItemRangeInserted(positionStart, itemCount);
            updateEmptyView();
        

        @Override
        public void onItemRangeChanged(int positionStart, int itemCount) 
            super.onItemRangeChanged(positionStart, itemCount);
            updateEmptyView();
        

        @Override
        public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) 
            super.onItemRangeMoved(fromPosition, toPosition, itemCount);
            updateEmptyView();
        
    ;

    public VehicleRecyclerView(Context context, AttributeSet attrs) 
        super(context, attrs);
    

    public VehicleRecyclerView(Context context, AttributeSet attrs, int defStyle) 
        super(context, attrs, defStyle);
    

    /**
     * Designate a view as the empty view. When the backing adapter has no
     * data this view will be made visible and the recycler view hidden.
     *
     */
    public void setEmptyView(View emptyView) 
        mEmptyView = emptyView;
    

    @Override
    public void setAdapter(RecyclerView.Adapter adapter) 
        if (getAdapter() != null) 
            getAdapter().unregisterAdapterDataObserver(mDataObserver);
        
        if (adapter != null) 
            adapter.registerAdapterDataObserver(mDataObserver);
        
        super.setAdapter(adapter);
        updateEmptyView();
    

    private void updateEmptyView() 
        if (mEmptyView != null && getAdapter() != null) 
            boolean showEmptyView = getAdapter().getItemCount() == 0;
            if(showEmptyView)
                mEmptyView.setVisibility(VISIBLE);
                setVisibility(GONE);
            else 
                mEmptyView.setVisibility(GONE);
                setVisibility(VISIBLE);
            
        
    

我的 VehicleAdapter 具有 setHasStableIds(true) 以及覆盖的 getItemId(position)

public class VehicleAdapter extends RecyclerView.Adapter<VehicleAdapter.VehicleViewHolder> 

    private List<Vehicle> vehicleList = new ArrayList<>();
    private Context mContext;

    public VehicleAdapter(Context context, List<Vehicle> vehicles) 
        vehicleList.addAll(vehicles);
        setHasStableIds(true);
        mContext = context;
    

    @Override
    public VehicleViewHolder onCreateViewHolder(ViewGroup parent, int viewType) 
        return new VehicleViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.vehicle_list_item_card_view, parent, false));
    

    @Override
    public void onBindViewHolder(VehicleViewHolder holder, int position) 
        holder.bind(vehicleList.get(position));
    

    @Override
    public int getItemCount() 
        return vehicleList.size();
    

    @Override
    public long getItemId(int position) 
        return vehicleList.get(position).getId();
    

    public void addVehicle(Vehicle vehicle)
        if(vehicleList != null)
            vehicleList.add(0, vehicle);
            notifyItemInserted(0);
        
    

    public void removeVehicle(int position)
        if(vehicleList.get(position).delete())
            vehicleList.remove(position);
            notifyItemRemoved(position);
        else 
//            TODO
        
    

    public void updateVehicle(Vehicle vehicle, int position)
        vehicleList.set(position, vehicle);
        notifyItemChanged(position);
    

    class VehicleViewHolder extends RecyclerView.ViewHolder

        TextView tvRegistrationNumber;
        TextView tvOwnerName;
        TextView tvColor;
        TextView tvOwnerCity;
        TextView tvLookupDate;
        TextView tvManufacturer;
        TextView tvModel;
        TextView tvMakeYear;

        ImageView ivDetail;
        ImageView ivBookmark;
        ImageView ivDelete;

        public VehicleViewHolder(View itemView) 
            super(itemView);

            tvRegistrationNumber = (TextView) itemView.findViewById(R.id.tvRegistrationNumber);
            tvOwnerName = (TextView) itemView.findViewById(R.id.tvOwnerName);
            tvColor = (TextView) itemView.findViewById(R.id.tvColor);
            tvOwnerCity = (TextView) itemView.findViewById(R.id.tvOwnerCity);
            tvLookupDate = (TextView) itemView.findViewById(R.id.tvLookupDate);
            tvManufacturer = (TextView) itemView.findViewById(R.id.tvManufacturer);
            tvModel = (TextView) itemView.findViewById(R.id.tvModel);
            tvMakeYear = (TextView) itemView.findViewById(R.id.tvMakeYear);

            ivDetail = (ImageView) itemView.findViewById(R.id.ivDetail);
            ivBookmark = (ImageView) itemView.findViewById(R.id.ivBookmark);
            ivDelete = (ImageView) itemView.findViewById(R.id.ivDelete);
        

        void setOwnerName(String ownerName)
            tvOwnerName.setText(ownerName);
        
        void setColor(String color)
            tvColor.setText(color);
        
        void setOwnerCity(String ownerCity)
            tvOwnerCity.setText(ownerCity);
        
        void setLookupDate(String lookupDate)
            tvLookupDate.setText(lookupDate);
        
        void setManufacturer(String manufacturer)
            tvManufacturer.setText(manufacturer);
        
        void setMakeYear(int makeYear)
            tvMakeYear.setText(String.format(Locale.getDefault(),"%s",makeYear));
        
        void setModel(String model)
            tvModel.setText(model);
        
        void setBookmarked(boolean isBookmarked)
            ivBookmark.setImageDrawable(ViewUtils.getDrawable(mContext, (isBookmarked ? R.drawable.ic_favorite_black_24dp: R.drawable.ic_favorite_border_black_24dp)));
        

        void bind(final Vehicle vehicle)
            tvRegistrationNumber.setText(vehicle.getRegistrationNumber());
            setOwnerName(vehicle.getOwnerName());
            setColor(vehicle.getColor());
            setOwnerCity(vehicle.getOwnerCity());
            setLookupDate(DateTimeUtils.getPKTDateTime(vehicle.getModifiedAt()));
            setManufacturer(vehicle.getManufacturer());
            setModel(vehicle.getMakeType());
            setMakeYear(vehicle.getMakeYear());
            setBookmarked(vehicle.isBookmarked());
            setupClickListeners(vehicle);
        

        private void setupClickListeners(final Vehicle vehicle) 
            ivDelete.setOnClickListener(new View.OnClickListener() 
                @Override
                public void onClick(View v) 
                    new MaterialDialog.Builder(mContext)
                            .title(R.string.confirm_delete_vehicle_heading)
                            .content(R.string.confirm_delete_vehicle_label)
                            .positiveText(R.string.confirm_response_yes)
                            .negativeText(R.string.confirm_response_cancel)
                            .stackingBehavior(StackingBehavior.ALWAYS)
                            .onPositive(new MaterialDialog.SingleButtonCallback() 
                                @Override
                                public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) 
                                    if(getAdapterPosition() != RecyclerView.NO_POSITION)
                                        removeVehicle(getAdapterPosition());
                                    
                                
                            )
                            .show();
                
            );
        
    


问题:

当 recyclerview 中没有项目并且我通过 notifyItemInserted(0) 向 recyclerview 添加一个或多个新项目时,一切正常。 但是当recyclerview中只有一个项目时,我通过notifyItemRemoved(position)删除了该项目,现在通过notifyItemInserted(0)将一个新项目添加到recyclerview,有一个过渡/动画效果,隐藏空视图后,显示recyclerview之前移除的项目会在几分之一秒内消失,然后新添加的项目会淡入视口。

因此,这是应该遵循的“理想”事件顺序:

    [工作正常]我删除了 recyclerview 中的最后一项。 recyclerview 的可见性更改为 GONE,emptyView (TextView) 的可见性更改为 VISIBLE。 [工作正常] 然后,我将一个新项目添加到 recyclerview。 emptyView 的可见性更改为 GONE。 [未按预期工作] recyclerview 的可见性应更改为 VISIBLE,并且应仅包含一项,即新添加的项目 [未按预期工作] 相反,有一个 flickr/blink,其中包含先前存在的唯一项目的 recyclerview 显示了几分之一秒,然后该项目通过淡入(?)转换被新添加的项目替换。

更新:您可以在这里查看问题:https://media.giphy.com/media/l0IydcuJDAqPNlmla/giphy.gif

我期待找到一种方法来摆脱这种 flickr/blink 效果,该效果会在很短的时间内显示旧的 item/recyclerview 状态。


我尝试过的:

RecyclerView.ItemAnimator animator = mVehicleListRV.getItemAnimator();
if (animator instanceof SimpleItemAnimator) 
    ((SimpleItemAnimator) animator).setSupportsChangeAnimations(false);

免责声明:

我是 Android 开发的新手,在这个问题上停留了好几个小时。在SO 寻求大神们的帮助/指点以畅通无阻。请原谅我的知识有限。

【问题讨论】:

请发布您的适配器代码...更多与recyclerview相关的代码?? 我为 VehicleAdapter 和 VehicleRecyclerView 添加了代码。 @PN10 你解决了这个问题吗?我遇到了类似的问题,回收站视图在再次可见之前不会处理其更新 【参考方案1】:

我遇到了这个问题 - 实际上,当 Android 系统认为 RecyclerView 不可见时,它不会重新布局其子项。这会在重新显示 RecyclerView 时产生闪烁,因为旧的子视图仍然可见一小段时间,然后才能为新数据更新视图。

我尝试了几种不同的方法来欺骗 RecyclerView 使其对用户不可见,同时仍更新其子视图。

一些无效的方法:

将 RecyclerView 可见性设置为 GONE 将 RecyclerView 可见性设置为 INVISIBLE 将 RecyclerView alpha 设置为 0

所有这些方法都会导致 RecyclerView 消失,但也不会重新布局其子项,从而导致闪烁。

确实奏效的方法:

将 RecyclerView 高度设置为 1

我在ConstraintLayout 中有我的 RecyclerView,所以我写了一个小实用函数。它看起来像这样:

/**
 * Toggle whether [recyclerView] is visible.
 * It does not use [View.setVisibility] or [View.setAlpha],
 * because doing so will prevent recyclerView children from being updated,
 * and this creates a flicker.
 *
 * Instead, this workaround makes the recyclerView too small to be seen,
 * while 'tricking' the Android system into thinking the view is still visible,
 * so allows View updates to continue as necessary.
 */
fun toggleRecyclerView(shouldShow: Boolean) 
    recyclerView.updateLayoutParams 
        // Use 1 as the 'invisible' height - it's the smallest height available to us
        height = if(shouldShow) MATCH_CONSTRAINT else 1
    

我将它与ListAdapter 提供的submitList 回调结合起来——这只会在新旧数据之间计算出异步差异后重新显示 RecyclerView,然后是View.post——等到RecyclerView 子节点已更新:

if (items.isEmpty()) 
    toggleRecyclerView(false)
    emptyItemsText.visibility = View.VISIBLE
    adapter.submitList(emptyList())
    return


// Only show recyclerview after items are updated, to prevent flicker
adapter.submitList(items) 
    recyclerView.post 
        toggleRecyclerView(true)
        emptyItemsText.visibility = View.GONE
    

【讨论】:

【参考方案2】:

您可以使用可见性 INVISIBLE 而不是 GONE,在这种情况下,即使适配器不可见,也会调用 onBindViewHolder。

【讨论】:

以上是关于将 RecyclerView 可见性从 Gone 更改为 Visible,在显示新添加的项目之前暂时错误地显示先前删除的项目的主要内容,如果未能解决你的问题,请参考以下文章

RecyclerView 展开/折叠项目

Recyclerview 中的 view.GONE 仍然保留空间

如何使reyclerview项目可见并且不留空间--Kotlin?

当没有要显示的文本时,将 textview 可见性设置为 GONE

即使将其可见性设置为 GONE,Android View 仍会获得 OnClick()

android - 使视图“GONE”导致其下方的视图也不可见