RecycleView系列五:底部渐隐和动画

528 阅读4分钟

一、RecyclerView底部渐隐

1. 自定义RecyclerView.ItemDecoration

  • 目的:通过继承RecyclerView.ItemDecoration类,来自定义分割线、阴影等装饰效果。
  • 自定义内容:包括开始颜色(渐变的起始颜色)、结束颜色(渐变的结束颜色)和阴影高度(渐变覆盖的高度或范围)。
import android.graphics.Canvas;
import android.graphics.LinearGradient;
import android.graphics.Paint;
import android.graphics.Shader;
import android.view.View;
import androidx.recyclerview.widget.RecyclerView;

public class FadingEdgeItemDecoration extends RecyclerView.ItemDecoration {
    private final Paint paint = new Paint();
    private final int startColor; // 渐变起始颜色
    private final int endColor;   // 渐变结束颜色
    private final int fadeHeight; // 渐变高度

    public FadingEdgeItemDecoration(int startColor, int endColor, int fadeHeight) {
        this.startColor = startColor;
        this.endColor = endColor;
        this.fadeHeight = fadeHeight;
    }

    @Override
    public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
        if (parent.getLayoutManager() == null) return;

        int left = parent.getLeft();
        int right = parent.getRight();
        int bottom = parent.getBottom();

        Shader shader = new LinearGradient(left, bottom - fadeHeight, left, bottom,
                startColor, endColor, Shader.TileMode.CLAMP);
        paint.setShader(shader);

        c.drawRect(left, bottom - fadeHeight, right, bottom, paint);
    }
}

2. 绘制渐隐效果

  • 方法:在ItemDecorationonDrawOver()onDraw()方法中, 使用CanvasdrawRect()方法来绘制矩形。 这个矩形用于实现渐隐效果,其颜色通过LinearGradient(线性渐变),从自定义的开始颜色渐变到结束颜色。
  • 渐变区域:渐变通常应用于RecyclerView的底部区域,以创建视觉上的渐隐效果。

3. 外部业务调用

  • 初始化RecyclerView:首先,完成RecyclerView的初始化工作,包括设置适配器(Adapter)、布局管理器(LayoutManager)等。
  • 启动渐隐:在RecyclerView初始化完成后,业务方通过调用特定接口来启动渐隐效果的绘制。

4. 内部实现:判定是否铺满并添加ItemDecoration

  • 全局布局监听:通过给RecyclerViewViewTreeObserver添加OnGlobalLayoutListener,监听RecyclerView的全局布局变化。

  • 判定逻辑:在监听器的回调中,通过比较RecyclerView的第一个可见项(firstVisibleItem) 和最后一个可见项(lastVisibleItem)与总项数(itemCount)的关系,来判定是否铺满屏幕。

              如果`firstVisibleItem`大于0(向上滑动过),
              或`lastVisibleItem`小于`itemCount - 1`(底下还有内容),则认为屏幕之外有数据,列表铺满了。
              原因:RecyclerView是从第一个项目开始显示的,那么firstVisibleItem将会是0;大于0说明向上滑动了一点
              
    
  • 添加ItemDecoration:一旦确定需要绘制渐隐效果,就通过RecyclerViewaddItemDecoration()方法添加之前自定义的ItemDecoration,从而完成渐隐效果的绘制。

import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.recyclerview.widget.RecyclerView.OnScrollListener;

public class MainActivity extends AppCompatActivity {

    private RecyclerView recyclerView;
    private boolean isFadingEnabled = false; // 是否启用渐隐效果

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        recyclerView = findViewById(R.id.recyclerView);
        recyclerView.setLayoutManager(new LinearLayoutManager(this));
        recyclerView.setAdapter(new YourAdapter()); // 替换为你的适配器

        // 添加滚动监听器
        recyclerView.addOnScrollListener(new OnScrollListener() {
            @Override
            public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
                super.onScrolled(recyclerView, dx, dy);
                checkAndAddFadingEdge();
            }
        });

        // 如果需要立即启用渐隐效果
        enableFadingEdge(true);
    }

    private void checkAndAddFadingEdge() {
        if (isFadingEnabled) {
            int firstVisibleItem = ((LinearLayoutManager) recyclerView.getLayoutManager()).findFirstVisibleItemPosition();
            int lastVisibleItem = ((LinearLayoutManager) recyclerView.getLayoutManager()).findLastVisibleItemPosition();
            int itemCount = recyclerView.getAdapter().getItemCount();

            // 判断是否列表没有完全铺满屏幕
            if (firstVisibleItem > 0 || lastVisibleItem < itemCount - 1) {
                // 添加渐隐效果
                if (recyclerView.getItemDecorationCount() == 0) { // 确保只添加一次
                    recyclerView.addItemDecoration(new FadingEdgeItemDecoration(
                            getResources().getColor(R.color.start_color), // 设置起始颜色
                            getResources().getColor(R.color.end_color),   // 设置结束颜色
                            200));                                        // 设置渐变高度
                }
            } else {
                // 移除渐隐效果
                if (recyclerView.getItemDecorationCount() > 0) {
                    recyclerView.removeItemDecorationAt(0);
                }
            }
        }
    }

    public void enableFadingEdge(boolean enable) {
        isFadingEnabled = enable;
        checkAndAddFadingEdge(); // 根据当前状态更新渐隐效果
    }
}

总结

通过自定义RecyclerView.ItemDecoration来实现底部渐隐效果,包括自定义渐变的颜色、高度,以及在合适的时机(如RecyclerView未铺满屏幕时)添加这个装饰, 以覆盖RecyclerView的底部区域,实现视觉上的渐隐效果。

二、RecyclerView动画

继承RecyclerView.ItemAnimator类并对其进行定制添加、删除、移动或更改时的动画效果, 包括插值器(Interpolator)、缩放比例(Scale)、持续时间(Duration)等:

1. 继承SimpleItemAnimator

首先,你需要创建一个类继承自SimpleItemAnimator。在这个类中,你将重写与动画相关的几个关键方法。

public class CustomItemAnimator extends SimpleItemAnimator {
    // 构造函数、变量定义等
}

2. 重写动画方法

你需要重写的关键方法有: animateAdd(RecyclerView.ViewHolder)animateRemove(RecyclerView.ViewHolder)animateMove(RecyclerView.ViewHolder, int fromX, int fromY, int toX, int toY) animateChange(RecyclerView.ViewHolder oldHolder, RecyclerView.ViewHolder newHolder, Payloads payloads)

示例:自定义添加动画
@Override
public boolean animateAdd(RecyclerView.ViewHolder holder) {
    resetAnimation(holder);
    // 定义动画属性,如持续时间、插值器等
    long duration = getAddDuration();
    int interpolatorType = getInterpolator(ANIMATE_ADD);

    // 假设你使用的是ObjectAnimator来缩放和移动
    ValueAnimator animatorX = ObjectAnimator.ofFloat(holder.itemView, "translationX", 
                                                     holder.itemView.getTranslationX(), 0);
    ValueAnimator animatorY = ObjectAnimator.ofFloat(holder.itemView, "translationY", 
                                                     holder.itemView.getTranslationY(), holder.itemView.getHeight());
    animatorX.setInterpolator(Interpolators.get(interpolatorType));
    animatorY.setInterpolator(Interpolators.get(interpolatorType));
    animatorX.setDuration(duration);
    animatorY.setDuration(duration);

    // 启动动画
    startAnimation(animatorX, animatorY, holder);
    return true;
}

// 其他动画方法也需要类似地实现

3. 设置插值器、缩放和持续时间

在上面的代码中,你可以通过定义自己的方法来设置动画的插值器、缩放比例和持续时间。 例如,你可以创建一个枚举来表示不同类型的动画,并为每种类型指定默认的插值器和持续时间。

4. 调用runPendingAnimations()

当你对列表进行更新(如添加、删除、移动或更改项)时,RecyclerView不会自动触发动画。 你需要在适当的时候调用RecyclerViewgetItemAnimator().runPendingAnimations()方法来开始执行待处理的动画。

5. 设置自定义动画器

最后,将你的自定义动画器设置为RecyclerView的项动画器:

recyclerView.setItemAnimator(new CustomItemAnimator());

注意事项

  • 确保在动画过程中正确处理视图的可见性和位置变化。
  • 考虑到性能优化,避免在动画过程中执行复杂的计算或UI更新。
  • 测试不同的动画效果和持续时间,以找到最适合你应用体验的参数。

通过遵循上述步骤,你可以为RecyclerView创建高度定制化的动画效果,从而提升用户界面的交互性和吸引力。