安卓RecyclerView实现3D滑动轮播效果全流程实战

发布于:2025-07-01 ⋅ 阅读:(23) ⋅ 点赞:(0)

安卓RecyclerView实现3D滑动轮播效果全流程实战

1. 前言

作为一名学习安卓的人,在接触之前和之后两种完全不同的想法:

好看和怎么实现
在这里插入图片描述

当初接触到RecyclerView就觉得这个控件就可以把关于列表的所有UI实现,即便不能,也是功能十分强大

放在现在依然是应用最广的滑动列表控件,被应用于聊天、朋友圈、商品列表、图片墙、轮播图、新闻流、视频流……

而我要说的就是基于RecyclerView控件实现带有一定视觉效果的轮播图(效果附上图)

2. 项目初始化

  • 新建项目流程

我这里先创建一个新项目用于做展示,项目名就叫RecyclerView3D

在这里插入图片描述

  • 环境与依赖配置

最低建议API:API 14(Android 4.0,Ice Cream Sandwich)及以上.大部分现代项目最低API都在16或21

3. RecyclerView基础实现

  • 添加RecyclerView控件

首先在你的 res/layout/activity_main.xml 中加入一个 RecyclerView:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recyclerView"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:layout_marginTop="30dp"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
  • 创建基础item布局

res/layout/ 下新建 item_simple.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:gravity="center"
    android:layout_width="120dp"
    android:layout_height="180dp"
    android:background="@android:color/holo_blue_light"
    android:layout_margin="8dp">

    <TextView
        android:id="@+id/tvTitle"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello"
        android:textSize="24sp"
        android:textColor="#FFFFFF"/>
</LinearLayout>
  • 编写Adapter与数据绑定

新建一个适配器类 SimpleAdapter

package com.app.recyclerview3d;

import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;

import java.util.List;

/**
 * 一个简单的RecyclerView.Adapter实现,用于展示字符串列表
 */
public class SimpleAdapter extends RecyclerView.Adapter<SimpleAdapter.ViewHolder> {
    // 数据源:字符串列表
    private List<String> dataList;

    // 构造函数,接收数据源
    public SimpleAdapter(List<String> dataList) {
        this.dataList = dataList;
    }

    /**
     * 当RecyclerView需要新建一个ViewHolder时调用
     * @param parent 父视图
     * @param viewType item类型(本例中只有一种类型)
     * @return 新的ViewHolder实例
     */
    @NonNull
    @Override
    public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        // 加载item布局
        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_simple, parent, false);
        // 创建并返回ViewHolder
        return new ViewHolder(view);
    }

    /**
     * 数据和View的绑定
     * @param holder 当前item的ViewHolder
     * @param position 当前item的位置
     */
    @Override
    public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
        // 设置TextView的内容为对应位置的数据
        holder.tvTitle.setText(dataList.get(position));
    }

    /**
     * 返回数据源的总数,决定RecyclerView有多少item
     */
    @Override
    public int getItemCount() {
        return dataList.size();
    }

    /**
     * ViewHolder:持有item视图的引用,提升性能
     */
    static class ViewHolder extends RecyclerView.ViewHolder {
        TextView tvTitle; // item中的TextView

        public ViewHolder(@NonNull View itemView) {
            super(itemView);
            // 绑定item中的TextView
            tvTitle = itemView.findViewById(R.id.tvTitle);
        }
    }
}

在你的 MainActivityonCreate 方法中添加如下代码,完成RecyclerView的调用和绑定:

package com.app.recyclerview3d;

import android.os.Bundle;

import androidx.activity.EdgeToEdge;
import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;

import java.util.Arrays;
import java.util.List;

public class MainActivity extends AppCompatActivity {

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

        RecyclerView recyclerView = findViewById(R.id.recyclerView);
        // 横向滑动
        recyclerView.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false));
        // 示例数据
        List<String> dataList = Arrays.asList("A", "B", "C", "D", "E", "F", "G");
        recyclerView.setAdapter(new SimpleAdapter(dataList));
    }
}
  • 简单实现效果

在这里插入图片描述

简单的滑动列表效果已经有了,但…

这样太单调不太美观,下面我们用自定义卡片来代替它

4. 美化和自定义item

  • 设计轮播卡片样式

在资源文件下创建一个新的布局文件item_carousel.xml

(注意:在ImageView里可以添加你自己的资源图片,仅充当默认图片,在主活动中会重新填充图片把此部分图片覆盖)

<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:card_view="http://schemas.android.com/apk/res-auto"
    android:layout_width="180dp"
    android:layout_height="260dp"
    android:layout_margin="12dp"
    card_view:cardCornerRadius="18dp"
    card_view:cardElevation="8dp"
    card_view:cardBackgroundColor="@android:color/white">

    <LinearLayout
        android:orientation="vertical"
        android:gravity="center"
        android:padding="18dp"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <ImageView
            android:id="@+id/imgCover"
            android:layout_width="120dp"
            android:layout_height="120dp"
            android:layout_gravity="center"
            android:scaleType="centerCrop"
            android:src="@drawable/ic_launcher_background"
            android:background="@drawable/ic_launcher_foreground"
            android:contentDescription="@string/app_name" />

        <TextView
            android:id="@+id/tvTitle"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="16dp"
            android:text="卡片标题"
            android:textSize="20sp"
            android:textColor="#222222"
            android:textStyle="bold"
            android:ellipsize="end"
            android:maxLines="1"/>

        <TextView
            android:id="@+id/tvDesc"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="8dp"
            android:text="这里是轮播卡片的简单描述信息"
            android:textSize="14sp"
            android:textColor="#666666"
            android:maxLines="2"
            android:ellipsize="end"/>

    </LinearLayout>

</androidx.cardview.widget.CardView>

卡片样式展示:

在这里插入图片描述

  • 丰富item内容与交互

创建 CarouselItem.java 文件,内容如下:

package com.app.recyclerview3d;

// 数据类:丰富的卡片内容
public class CarouselItem {
    public int imageResId;
    public String title;
    public String description;

    public CarouselItem(int imageResId, String title, String description) {
        this.imageResId = imageResId;
        this.title = title;
        this.description = description;
    }
}

创建RichCarouselAdapter.java,内容如下:

package com.app.recyclerview3d;

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;

import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;

import java.util.List;

// Adapter丰富实现
public class RichCarouselAdapter extends RecyclerView.Adapter<RichCarouselAdapter.ViewHolder> {
    private List<CarouselItem> itemList;
    private Context context;

    public RichCarouselAdapter(Context context, List<CarouselItem> itemList) {
        this.context = context;
        this.itemList = itemList;
    }

    @NonNull
    @Override
    public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_carousel, parent, false);
        return new ViewHolder(view);
    }

    @Override
    public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
        CarouselItem item = itemList.get(position);
        holder.tvTitle.setText(item.title);
        holder.tvDesc.setText(item.description);
        holder.imgCover.setImageResource(item.imageResId);

        // 简单的点击交互示例
        holder.itemView.setOnClickListener(v ->
                Toast.makeText(context, "点击了:" + item.title, Toast.LENGTH_SHORT).show()
        );
        holder.imgCover.setOnClickListener(v ->
                Toast.makeText(context, "点击了图片:" + item.title, Toast.LENGTH_SHORT).show()
        );
    }

    @Override
    public int getItemCount() {
        return itemList.size();
    }

    static class ViewHolder extends RecyclerView.ViewHolder {
        ImageView imgCover;
        TextView tvTitle;
        TextView tvDesc;

        public ViewHolder(@NonNull View itemView) {
            super(itemView);
            imgCover = itemView.findViewById(R.id.imgCover);
            tvTitle = itemView.findViewById(R.id.tvTitle);
            tvDesc = itemView.findViewById(R.id.tvDesc);
        }
    }
}
  • 在主活动MainActivity中应用:
package com.app.recyclerview3d;

import android.os.Bundle;

import androidx.activity.EdgeToEdge;
import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;

import java.util.Arrays;
import java.util.List;

public class MainActivity extends AppCompatActivity {

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

        RecyclerView recyclerView = findViewById(R.id.recyclerView);
        // 横向滑动布局
        recyclerView.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false));

        // 构造美化卡片的数据源
        List<CarouselItem> carouselItems = Arrays.asList(
                new CarouselItem(R.drawable.shilitupian, "1", "这是卡片1"),
                new CarouselItem(R.drawable.shilitupian, "2", "这是卡片2"),
                new CarouselItem(R.drawable.shilitupian, "3", "这是卡片3"),
                new CarouselItem(R.drawable.shilitupian, "4", "这是卡片4"),
                new CarouselItem(R.drawable.shilitupian, "5", "这是卡片5")
        );

        // 设置适配器,展示美化轮播卡片
        recyclerView.setAdapter(new RichCarouselAdapter(this, carouselItems));
    }
}

效果如下:

在这里插入图片描述

点击事件效果:

在这里插入图片描述

截至到这,其实一般情况下都够正常使用了,接下来继续实现3D轮播效果

5. 自定义LayoutManager实现3D效果

  • 缩放(scale)、旋转(rotation)等视觉特效实现

创建CarouselLayoutManager.java类,内容如下:

package com.app.recyclerview3d;

import android.content.Context;
import android.view.View;

import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;

/**
 * 基于LinearLayoutManager的轮播卡片(画廊)特效LayoutManager
 * 实现横向滑动时,卡片居中时最大,边缘逐渐缩小/透明/旋转,有3D视觉效果
 */
public class CarouselLayoutManager extends LinearLayoutManager {

    // 最大缩放比例(中间item)
    private static final float MAX_SCALE = 1.0f;
    // 最小缩放比例(边缘item,建议不要太小)
    private static final float MIN_SCALE = 0.8f;
    // 最大旋转角度(Y轴),单位:度
    private static final float MAX_ANGLE = 25.0f;

    // 构造方法,横向布局
    public CarouselLayoutManager(Context context) {
        super(context, HORIZONTAL, false);
    }

    // 布局完成后,给所有子item应用缩放和旋转效果
    @Override
    public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
        super.onLayoutChildren(recycler, state);
        scaleAndRotateItems();
    }

    // 横向滚动时,实时给所有子item应用缩放和旋转效果
    @Override
    public int scrollHorizontallyBy(int dx, RecyclerView.Recycler recycler, RecyclerView.State state) {
        int scrolled = super.scrollHorizontallyBy(dx, recycler, state);
        scaleAndRotateItems();
        return scrolled;
    }

    // 当滑动状态改变时(如滑动停止),保证特效刷新
    @Override
    public void onScrollStateChanged(int state) {
        super.onScrollStateChanged(state);
        if (state == RecyclerView.SCROLL_STATE_IDLE) {
            scaleAndRotateItems();
        }
    }

    /**
     * 对每个可见item进行缩放、透明和Y轴旋转处理,实现画廊轮播视觉效果
     */
    private void scaleAndRotateItems() {
        // RecyclerView水平方向中点
        int midPoint = getWidth() / 2;
        float d0 = 0.0f;
        // 有效距离(超过此距离的item都视为最小缩放)
        float d1 = 0.9f * midPoint;

        for (int i = 0; i < getChildCount(); i++) {
            View child = getChildAt(i);
            if (child != null) {
                // 计算当前item的中点
                float childMidPoint = (getDecoratedLeft(child) + getDecoratedRight(child)) / 2f;
                // 距离RecyclerView中点的距离(d)
                float d = Math.min(d1, Math.abs(midPoint - childMidPoint));
                // 线性插值计算缩放比例,居中最大,边缘最小
                float scaleFactor = MAX_SCALE - (MAX_SCALE - MIN_SCALE) * (d - d0) / (d1 - d0);

                // 设置缩放和透明度
                child.setScaleX(scaleFactor);
                child.setScaleY(scaleFactor);
                child.setAlpha(scaleFactor);

                // 计算Y轴旋转角度(居中为0,越远旋转越大)
                float rotationAngle = -(MAX_ANGLE * (midPoint - childMidPoint) / midPoint);
                child.setRotationY(rotationAngle);
            }
        }
    }
}
  • 修改MainActivity活动代码:

调用组定义效果

package com.app.recyclerview3d;

import android.os.Bundle;

import androidx.activity.EdgeToEdge;
import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.PagerSnapHelper;
import androidx.recyclerview.widget.RecyclerView;

import java.util.Arrays;
import java.util.List;

public class MainActivity extends AppCompatActivity {

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

        RecyclerView recyclerView = findViewById(R.id.recyclerView);
        // 横向滑动布局
        recyclerView.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false));

        // 构造美化卡片的数据源
        List<CarouselItem> carouselItems = Arrays.asList(
                new CarouselItem(R.drawable.shilitupian, "1", "这是卡片1"),
                new CarouselItem(R.drawable.shilitupian, "2", "这是卡片2"),
                new CarouselItem(R.drawable.shilitupian, "3", "这是卡片3"),
                new CarouselItem(R.drawable.shilitupian, "4", "这是卡片4"),
                new CarouselItem(R.drawable.shilitupian, "5", "这是卡片5")
        );

        // 设置自定义LinearLayoutManager(CarouselLayoutManager)
        recyclerView.setLayoutManager(new CarouselLayoutManager(this));
        // 设置美化卡片适配器
        recyclerView.setAdapter(new RichCarouselAdapter(this, carouselItems));
        // 推荐:吸附中间卡片
        new PagerSnapHelper().attachToRecyclerView(recyclerView);
    }
}
  • 简单实现效果:

在这里插入图片描述

6. 高级扩展–3D无限画廊轮播

自动轮播:

  • Handler定时调用smoothScrollToPosition(下一个位置),实现自动滚动
  • 自动滚动到最后一位时,自动回到第一个,实现无限循环播放
  • 可通过startAutoScroll()stopAutoScroll()控制自动轮播

更新CarouselLayoutManager.java代码:

package com.app.recyclerview3d;

import android.content.Context;
import android.view.View;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;

public class CarouselLayoutManager extends LinearLayoutManager {
    // 最大/最小缩放比例和旋转角度常量,决定画廊效果的强度
    private static final float MAX_SCALE = 1.0f;
    private static final float MIN_SCALE = 0.8f;
    private static final float MAX_ANGLE = 25.0f;

    // 构造函数,设置为水平滑动
    public CarouselLayoutManager(Context context) {
        super(context, HORIZONTAL, false);
    }

    // 水平滚动时,动态调整每个item的缩放和旋转,实现3D画廊动画
    @Override
    public int scrollHorizontallyBy(int dx, RecyclerView.Recycler recycler, RecyclerView.State state) {
        int scrolled = super.scrollHorizontallyBy(dx, recycler, state);
        scaleAndRotateItems();
        return scrolled;
    }

    // 布局完成后,刷新3D动画
    @Override
    public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
        super.onLayoutChildren(recycler, state);
        scaleAndRotateItems();
    }

    // 滚动状态变化时,滑动停下再刷新3D动画,保证吸附后效果正确
    @Override
    public void onScrollStateChanged(int state) {
        super.onScrollStateChanged(state);
        if (state == RecyclerView.SCROLL_STATE_IDLE) {
            scaleAndRotateItems();
        }
    }

    // 动态调整所有可见item的缩放和旋转
    private void scaleAndRotateItems() {
        int midPoint = getWidth() / 2; // 画廊中心
        float d0 = 0.0f;
        float d1 = 0.9f * midPoint; // 超出这个距离后缩放/旋转不会再变
        for (int i = 0; i < getChildCount(); i++) {
            View child = getChildAt(i);
            if (child != null) {
                // 计算item中点到画廊中心的距离
                float childMidPoint = (getDecoratedLeft(child) + getDecoratedRight(child)) / 2f;
                float d = Math.min(d1, Math.abs(midPoint - childMidPoint));
                // 距中心越近,scale越大,越远越小
                float scaleFactor = MAX_SCALE - (MAX_SCALE - MIN_SCALE) * (d - d0) / (d1 - d0);
                child.setScaleX(scaleFactor);
                child.setScaleY(scaleFactor);
                child.setAlpha(scaleFactor);
                // 距中心越远,旋转角度越大,形成Y轴倾斜
                float rotationAngle = -(MAX_ANGLE * (midPoint - childMidPoint) / midPoint);
                child.setRotationY(rotationAngle);
            }
        }
    }
}

更新MainActivity活动代码:

package com.app.recyclerview3d;

import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.view.View;

import androidx.activity.EdgeToEdge;
import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.PagerSnapHelper;
import androidx.recyclerview.widget.RecyclerView;

import java.util.Arrays;
import java.util.List;


public class MainActivity extends AppCompatActivity {

    private static final long AUTO_SCROLL_INTERVAL = 1000; // 自动轮播间隔(毫秒)

    private CarouselLayoutManager carouselLayoutManager;
    private RecyclerView recyclerView;
    private RichCarouselAdapter adapter;
    private PagerSnapHelper snapHelper;
    private List<CarouselItem> carouselItems;

    // Handler用于管理自动轮播的延时任务
    private final Handler handler = new Handler(Looper.getMainLooper());
    private boolean isAutoScroll = true; // 控制是否启动自动轮播

    // 自动轮播任务Runnable
    private final Runnable autoScrollRunnable = new Runnable() {
        @Override
        public void run() {
            if (!isAutoScroll) return; // 若未启用自动轮播,直接退出

            // 只在RecyclerView完全静止时才滑动,避免与吸附抢占滑动导致幅度过大
            if (recyclerView.getScrollState() != RecyclerView.SCROLL_STATE_IDLE) {
                handler.removeCallbacks(this); // 移除当前所有相同任务,防止任务堆积
                handler.postDelayed(this, 200); // 200ms后再次检测
                return;
            }

            // 找到当前被吸附在中间的item
            View snapView = snapHelper.findSnapView(carouselLayoutManager);
            if (snapView == null) {
                handler.removeCallbacks(this);
                handler.postDelayed(this, AUTO_SCROLL_INTERVAL);
                return;
            }

            // 计算当前item的实际宽度(含scaleX缩放和margin),这样3D动画时滑动距离也精准
            RecyclerView.LayoutParams lp = (RecyclerView.LayoutParams) snapView.getLayoutParams();
            float scale = snapView.getScaleX(); // 3D动画缩放因子
            int widthWithMargin = Math.round(snapView.getWidth() * scale) + lp.leftMargin + lp.rightMargin;

            // 像素级滑动到下一个item
            recyclerView.smoothScrollBy(widthWithMargin, 0);

            // 不要在这里post下一次轮播(否则可能“连开两枪”),等SCROLL_STATE_IDLE时再安排
        }
    };

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

        recyclerView = findViewById(R.id.recyclerView);

        // 初始化画廊数据
        carouselItems = Arrays.asList(
                new CarouselItem(R.drawable.shilitupian, "1", "这是卡片1"),
                new CarouselItem(R.drawable.shilitupian, "2", "这是卡片2"),
                new CarouselItem(R.drawable.shilitupian, "3", "这是卡片3"),
                new CarouselItem(R.drawable.shilitupian, "4", "这是卡片4"),
                new CarouselItem(R.drawable.shilitupian, "5", "这是卡片5")
        );

        // 吸附器,保证滑动后总有item居中
        snapHelper = new PagerSnapHelper();
        snapHelper.attachToRecyclerView(recyclerView);

        // 自定义LayoutManager,负责3D画廊视觉
        carouselLayoutManager = new CarouselLayoutManager(this);
        recyclerView.setLayoutManager(carouselLayoutManager);

        // 设置Adapter
        adapter = new RichCarouselAdapter(this, carouselItems);
        recyclerView.setAdapter(adapter);

        // 无限轮播体验,初始定位到中间
        int initialPos = carouselItems.size() * 500;
        recyclerView.scrollToPosition(initialPos);

        // 滚动状态监听器,只在滑动停稳后才安排下一次自动轮播
        recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
            @Override
            public void onScrollStateChanged(RecyclerView rv, int newState) {
                if (newState == RecyclerView.SCROLL_STATE_IDLE && isAutoScroll) {
                    // 移除所有等待的自动轮播任务,确保只存在一个
                    handler.removeCallbacks(autoScrollRunnable);
                    handler.postDelayed(autoScrollRunnable, AUTO_SCROLL_INTERVAL);
                }
            }
        });
    }

    @Override
    protected void onResume() {
        super.onResume();
        isAutoScroll = true;
        // 确保只启动一个自动轮播任务
        handler.removeCallbacks(autoScrollRunnable);
        handler.postDelayed(autoScrollRunnable, AUTO_SCROLL_INTERVAL);
    }

    @Override
    protected void onPause() {
        super.onPause();
        isAutoScroll = false;
        // 页面不可见时移除所有轮播任务,防止重复和内存泄漏
        handler.removeCallbacksAndMessages(null);
    }
}
  • 详细注释与设计说明:

  • 核心目标:

实现RecyclerView横向3D画廊,每隔固定时间自动滑动一项,并与吸附效果、动画效果完美兼容

  • 核心设计:

自动轮播严格只在RecyclerView静止时触发,避免与吸附冲突,不会出现多次滑动或者滑动幅度出错(解决了手动滑动和自动轮播之间的冲突)

轮播滑动采用smoothScrollBy,并且滑动距离根据当前item的实际宽度和缩放动画动态计算,保证即使有3D缩放动画也不会“跳两格”或“对不准”

任何时刻只存在一个轮播任务,防止任务堆积导致一次滑动多个item

吸附和动画全部解耦,Activity只负责滑动,LayoutManager负责3D动画

  • 为什么要这样做?

如果轮播和吸附抢占滑动,会导致动画抽搐或跳跃

如果滑动距离是固定的,item有缩放动画时实际距离会偏差,导致每次滑动不是正好一格

如果轮播任务堆积(不清理handler),会导致多次滑动合并在一次SCROLL_STATE_IDLE后执行(一次滑动不止一项,导致滑动混乱)

最终效果:手动和自动切换自如,不触发触摸事件就自动轮播,触摸则自动停止轮播,且一次只进行一次滑动

实现效果(没有任何手势动作):

在这里插入图片描述

实现效果(含有手势动作):
在这里插入图片描述

7. 总结

试错经历:

  1. 一开始可能把自动轮播、吸附、3D动画全都混在自定义LayoutManager里实现,导致滑动逻辑与动画耦合,容易冲突和失效
  2. smoothScrollToPositionsmoothScrollBy等方法时,没考虑吸附和item实际宽度、动画缩放的影响,导致自动轮播会抽搐、回弹或滑动幅度不对
  3. 动画和吸附抢占了RecyclerView的滚动指令,可能出现“吸附后动画丢失”,或者“动画只在滑动中有效,停下后消失”
  4. 现在把动画和滑动逻辑彻底分离,自动轮播和吸附只管滑动,3D动画只管视觉,滑动停稳后再刷新动画,所有问题都迎刃而解

rollBy,并且滑动距离根据当前item的实际宽度和缩放动画动态计算,保证即使有3D缩放动画也不会“跳两格”或“对不准”

任何时刻只存在一个轮播任务,防止任务堆积导致一次滑动多个item

吸附和动画全部解耦,Activity只负责滑动,LayoutManager负责3D动画

  • 为什么要这样做?

如果轮播和吸附抢占滑动,会导致动画抽搐或跳跃

如果滑动距离是固定的,item有缩放动画时实际距离会偏差,导致每次滑动不是正好一格

如果轮播任务堆积(不清理handler),会导致多次滑动合并在一次SCROLL_STATE_IDLE后执行(一次滑动不止一项,导致滑动混乱)

最终效果:手动和自动切换自如,不触发触摸事件就自动轮播,触摸则自动停止轮播,且一次只进行一次滑动

实现效果(没有任何手势动作):

[外链图片转存中…(img-RzANC2Lk-1751193138071)]

实现效果(含有手势动作):

[外链图片转存中…(img-jZOIFNRR-1751193138071)]

7. 总结

试错经历:

  1. 一开始可能把自动轮播、吸附、3D动画全都混在自定义LayoutManager里实现,导致滑动逻辑与动画耦合,容易冲突和失效
  2. smoothScrollToPositionsmoothScrollBy等方法时,没考虑吸附和item实际宽度、动画缩放的影响,导致自动轮播会抽搐、回弹或滑动幅度不对
  3. 动画和吸附抢占了RecyclerView的滚动指令,可能出现“吸附后动画丢失”,或者“动画只在滑动中有效,停下后消失”
  4. 现在把动画和滑动逻辑彻底分离,自动轮播和吸附只管滑动,3D动画只管视觉,滑动停稳后再刷新动画,所有问题都迎刃而解

网站公告

今日签到

点亮在社区的每一天
去签到