当前位置: 首页 > 知识库问答 >
问题:

使用ListAdapter的android recyclerview中缺少默认动画

周育
2023-03-14

我将recyclerview与扩展ListAdapter的适配器一起使用。我正在通过调整提交列表来更新内容。但每次内容更新(例如删除一项)时,recyclerview都会消失一秒钟,并与更新的内容一起重新出现。有两件事我不喜欢:第一是闪烁,第二是缺少默认动画。

如何修复它并显示流畅的默认动画?

适配器:

public class AdapterFlightRecords extends ListAdapter<FlightRecordListItem, RecyclerView.ViewHolder> {

    private FlightRecordListItem mRecentlyDeletedItem;
    private int mRecentlyDeletedItemPosition;

    private class ViewHolderFlightRecord extends RecyclerView.ViewHolder {
        private RvItemFlightRecordsBinding binding;

        private ViewHolderFlightRecord(final RvItemFlightRecordsBinding binding) {
            super(binding.getRoot());
            this.binding = binding;
        }

        private void bind(final EntityFlightRecord flightRecord){
            binding.setFlightRecord(flightRecord);
            binding.executePendingBindings();
        }
    }

    public class ViewHolderDate extends RecyclerView.ViewHolder {
        private RvItemFlightRecordsDateBinding binding;

        private ViewHolderDate(final RvItemFlightRecordsDateBinding binding) {
            super(binding.getRoot());
            this.binding = binding;
        }

        private void bind(final DateFlightRecordsListItem item){
            OffsetDateTime date = item.getDate();
            binding.setYearMonth(YearMonth.from(date));
            binding.executePendingBindings();
        }
    }

    public AdapterFlightRecords(){
        super(new FlightRecordsDiffCallback());
    }

    @NonNull
    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        switch(viewType){
            case 1:
                return new ViewHolderDate(RvItemFlightRecordsDateBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false));
            case 2:
                return new ViewHolderFlightRecord(RvItemFlightRecordsBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false));
            default:
                throw new RuntimeException("No such viewType");
        }

        //return new ViewHolderFlightRecord(RvItemFlightRecordsBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false));
    }

    @Override
    public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
        switch(holder.getItemViewType()){
            case 1:
                DateFlightRecordsListItem date = (DateFlightRecordsListItem) getItem(position);
                ((ViewHolderDate) holder).bind(date);
                break;
            case 2:
                EntityFlightRecord flightRecord = (EntityFlightRecord) getItem(position);
                ((ViewHolderFlightRecord) holder).bind(flightRecord);
                holder.itemView.setTag(flightRecord);
                break;
        }
    }

    @Override
    public int getItemViewType(int position) {
        // Just as an example, return 0 or 2 depending on position
        // Note that unlike in ListView adapters, types don't have to be contiguous
        return getItem(position).getType().getValue();
    }

    public EntityFlightRecord getItemFromList(int position){
            return (EntityFlightRecord) getItem(position);
    }

    /*private View.OnClickListener createOnClickListener(int id) {
        return view -> {
            QuestionnaireFragmentDirections.ActionQuestionnaireFragmentToSubjectFragment action = QuestionnaireFragmentDirections.actionQuestionnaireFragmentToSubjectFragment(id);
            Navigation.findNavController(view).navigate(action);
        };
    }*/
}

class FlightRecordsDiffCallback extends DiffUtil.ItemCallback<FlightRecordListItem> {

    @Override
    public boolean areItemsTheSame(@NonNull FlightRecordListItem oldItem, @NonNull FlightRecordListItem newItem) {
        //TODO implement equals and hashcode in Questionnaire
        return false;
        //return oldItem.id == newItem.id;
    }

    @Override
    public boolean areContentsTheSame(@NonNull FlightRecordListItem oldItem, @NonNull FlightRecordListItem newItem) {
        //TODO implement equals and hashcode in Questionnaire
        return false;
        //return oldItem.equals(newItem);
    }
}

布局:

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:bind="http://schemas.android.com/apk/res-auto">

    <data>
        <variable
            name="adapter"
            type="de.flightlogger.view.adapter.AdapterFlightRecords"/>
        <variable
            name="viewmodel"
            type="de.flightlogger.viewmodel.VmFlightRecords"/>
        <variable
            name="handler"
            type="de.flightlogger.view.adapter.SwipeToDeleteCallback.OnItemSwipeListener" />
    </data>

    <FrameLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".view.ui.FragmentFlightRecords">

        <androidx.constraintlayout.widget.ConstraintLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent" >

            <TextView
                android:id="@+id/tv_empty_rv"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="@string/no_entry"
                android:visibility="@{viewmodel.flightRecords.size() == 0}"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintTop_toTopOf="parent"
                app:layout_constraintBottom_toBottomOf="parent" />

            <com.google.android.material.floatingactionbutton.FloatingActionButton
                android:id="@+id/floatingActionButton"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_margin="@dimen/fab_margin"
                android:clickable="true"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintBottom_toTopOf="@id/cl_total_flight_time"
                app:srcCompat="@drawable/ic_add_black_24dp" />

            <androidx.recyclerview.widget.RecyclerView
                android:id="@+id/rv_flight_records"
                android:layout_width="match_parent"
                android:layout_height="0dp"
                android:adapter="@{adapter}"
                android:visibility="@{viewmodel.flightRecords.size() != 0}"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintTop_toTopOf="parent"
                app:layout_constraintBottom_toTopOf="@id/cl_total_flight_time"
                app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
                bind:bgColorSwipe="@{@color/primaryDarkColor}"
                bind:drawableSwipe="@{@drawable/ic_add_black_24dp}"
                bind:onItemSwipe="@{(position) -> handler.onItemSwiped(position)}"
                bind:swipeEnabled="@{true}"
                tools:listitem="@layout/rv_item_flight_records"/>

            <androidx.constraintlayout.widget.ConstraintLayout
                android:id="@+id/cl_total_flight_time"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:background="@color/primaryDarkColor"
                android:padding="@dimen/rv_items_padding"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintBottom_toBottomOf="parent">

                <TextView
                    android:id="@+id/textView2"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:textColor="@color/primaryTextColor"
                    android:text="@string/total_flight_time"
                    app:layout_constraintStart_toStartOf="parent"
                    app:layout_constraintTop_toTopOf="parent"
                    app:layout_constraintBottom_toBottomOf="parent" />

                <TextView
                    android:id="@+id/textView3"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:textColor="@color/primaryTextColor"
                    android:text="Test"
                    app:layout_constraintEnd_toEndOf="parent"
                    app:layout_constraintTop_toTopOf="parent"
                    app:layout_constraintBottom_toBottomOf="parent" />
            </androidx.constraintlayout.widget.ConstraintLayout>

        </androidx.constraintlayout.widget.ConstraintLayout>
    </FrameLayout>
</layout>

绑定适配器:

@BindingAdapter(value = {"swipeEnabled", "drawableSwipe", "bgColorSwipe", "onItemSwipe"}, requireAll = false)
    public static void setItemSwipeToRecyclerView(RecyclerView recyclerView, boolean swipeEnabled, Drawable drawableSwipe, ColorDrawable bgColorSwipe,
                                                  SwipeToDeleteCallback.OnItemSwipeListener onItemSwipe) {

        ItemTouchHelper.Callback swipeCallback = new SwipeToDeleteCallback
                .Builder(0, ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT)
                .bgColorSwipe(bgColorSwipe)
                .drawableSwipe(drawableSwipe)
                .setSwipeEnabled(swipeEnabled)
                .onItemSwipeListener(onItemSwipe)
                .build();

        ItemTouchHelper itemTouchHelper = new ItemTouchHelper(swipeCallback);
        itemTouchHelper.attachToRecyclerView(recyclerView);
    }

SwipeToDeleteCallback:

public class SwipeToDeleteCallback extends ItemTouchHelper.SimpleCallback {

private Drawable icon;
private ColorDrawable background;
private OnItemSwipeListener onItemSwipeListener;
private boolean swipeEnabled;

private SwipeToDeleteCallback(int dragDirs, int swipeDirs) {
    super(dragDirs, swipeDirs);
}

private SwipeToDeleteCallback(Builder builder) {
    this(builder.dragDirs, builder.swipeDirs);
    background = builder.bgColorSwipe;
    icon = builder.drawableSwipe;
    swipeEnabled = builder.swipeEnabled;
    onItemSwipeListener = builder.onItemSwipeListener;
}

@Override public boolean isItemViewSwipeEnabled() {
    return swipeEnabled;
}

@Override
public int getSwipeDirs(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
    if (viewHolder instanceof AdapterFlightRecords.ViewHolderDate) return 0;
    return super.getSwipeDirs(recyclerView, viewHolder);
}

@Override
public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
    return false;
}

@Override public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
    int position = viewHolder.getAdapterPosition();
    onItemSwipeListener.onItemSwiped(position);
}

@Override
public void onChildDraw(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) {
    super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);

    View itemView = viewHolder.itemView;
    int backgroundCornerOffset = 20; //so background is behind the rounded corners of itemView

    int iconMargin = (itemView.getHeight() - icon.getIntrinsicHeight()) / 2;
    int iconTop = itemView.getTop() + (itemView.getHeight() - icon.getIntrinsicHeight()) / 2;
    int iconBottom = iconTop + icon.getIntrinsicHeight();

    if (dX > 0) { // Swiping to the right
        int iconLeft = itemView.getLeft() + iconMargin;
        int iconRight = itemView.getLeft() + icon.getIntrinsicWidth() + iconMargin;
        icon.setBounds(iconLeft, iconTop, iconRight, iconBottom);

        background.setBounds(itemView.getLeft(), itemView.getTop(),
                itemView.getLeft() + ((int) dX) + backgroundCornerOffset, itemView.getBottom());
    } else if (dX < 0) { // Swiping to the left
        int iconLeft = itemView.getRight() - iconMargin - icon.getIntrinsicWidth();
        int iconRight = itemView.getRight() - iconMargin;
        icon.setBounds(iconLeft, iconTop, iconRight, iconBottom);

        background.setBounds(itemView.getRight() + ((int) dX) - backgroundCornerOffset,
                itemView.getTop(), itemView.getRight(), itemView.getBottom());
    } else { // view is unSwiped
        background.setBounds(0, 0, 0, 0);
    }

    background.draw(c);
    icon.draw(c);
}

public interface OnItemSwipeListener {
    void onItemSwiped(int position);
}

public static final class Builder {
    private int dragDirs, swipeDirs;
    private Drawable drawableSwipe;
    private ColorDrawable bgColorSwipe;
    private OnItemSwipeListener onItemSwipeListener;
    private boolean swipeEnabled;

    public Builder(int dragDirs, int swipeDirs) {
        this.dragDirs = dragDirs;
        this.swipeDirs = swipeDirs;
    }

    public Builder drawableSwipe(Drawable val) {
        drawableSwipe = val;
        return this;
    }

    public Builder bgColorSwipe(ColorDrawable val) {
        bgColorSwipe = val;
        return this;
    }

    public Builder onItemSwipeListener(OnItemSwipeListener val) {
        onItemSwipeListener = val;
        return this;
    }

    public Builder setSwipeEnabled(boolean val) {
        swipeEnabled = val;
        return this;
    }

    public SwipeToDeleteCallback build() {
        return new SwipeToDeleteCallback(this);
    }
}
}

我猜肯定有任何一行代码取消了默认动画,但是在哪里呢?

我找到了这个线程,但没有解决我的问题:RecyclerView上没有关于项目移除的动画

共有1个答案

齐阎宝
2023-03-14

在对照我的代码检查您的代码后,发现FlightRecordsDiffCallback类中存在问题,请尝试取消代码中的return语句的注释,并删除默认值- return false 。在我的代码中,动画没有正常工作,因为我不断地在两个返回语句之间混淆:

@重写<br>公共布尔项是相同的(…)

@ Override < br > public boolean areContentsTheSame(...)

 类似资料:
  • 我使用的是Json。net将对象序列化到数据库。 我向类中添加了一个新属性(该属性在数据库的json中缺失),我希望新属性在json中缺失时具有默认值。 我尝试了DefaultValue属性,但它不起作用。我正在使用私有setter和构造函数来反序列化json,因此在构造函数中设置属性的值将不起作用,因为有一个带有该值的参数。 以下是一个例子: 我预计年龄是5岁,但现在是零岁。 有什么建议吗?

  • 问题内容: 我在play scala中具有以下模型的等效项: 对于以下Foo实例 我将获得以下JSON文档: 该JSON将持久保存并从数据存储中读取。现在,我的要求已更改,我需要向Foo添加一个属性。该属性具有默认值: 写入JSON没问题: 但是,从中读取会由于缺少“ / status”路径而产生JsError。 如何提供具有最小噪声的默认设置? (ps:我有一个答案,我将在下面发布,但我对此并不

  • 可能有一个非常明显的答案,但如何做到以下几点: 基本上是从一个类型中检索所有键,以循环通过?

  • 问题内容: 当未设置相同名称的环境变量时,是否可以确保将GOMAXPROCS设置为1? 此代码显示值: 并像这样运行它: 显示在这种情况下为1,但我在这里寻找一些确认。 问题答案: UPDATE 2018: 默认情况下,Go程序在将GOMAXPROCS设置为可用内核数的情况下运行;在以前的版本中,它默认为1。 从Go 1.5开始,默认值为核心数。如果您在较新的Go版本中对此不满意,则只需要显式设置

  • 我正在尝试使用纱线与Kafka一起运行spark streaming应用程序。我得到以下堆栈跟踪错误- 造成原因:org.apache.kafka.common.config.配置异常:缺少所需的配置partition.assignment.strategy没有默认值。在org.apache.kafka.common.config.配置ef.parse(配置ef.java:124)在org.apa

  • 问题内容: http://selenium.googlecode.com/svn/trunk/docs/api/dotnet/html/M_OpenQA_Selenium_Chrome_ChromeDriver__ctor_4.htm 有人知道chromedriver commandtimeout的默认值是什么吗?做了谷歌搜索,但找不到任何东西。 问题答案: 记住Selenium是开源的。 您可以