安卓RecyclerView实现3D滑动轮播效果全流程实战_android recyclerview实现轮播
安卓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:
<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
:
<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); } }}
在你的 MainActivity
的 onCreate
方法中添加如下代码,完成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里可以添加你自己的资源图片,仅充当默认图片,在主活动中会重新填充图片把此部分图片覆盖)
<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. 总结
试错经历:
- 一开始可能把自动轮播、吸附、3D动画全都混在自定义LayoutManager里实现,导致滑动逻辑与动画耦合,容易冲突和失效
- 用
smoothScrollToPosition
、smoothScrollBy
等方法时,没考虑吸附和item实际宽度、动画缩放的影响,导致自动轮播会抽搐、回弹或滑动幅度不对 - 动画和吸附抢占了RecyclerView的滚动指令,可能出现“吸附后动画丢失”,或者“动画只在滑动中有效,停下后消失”
- 现在把动画和滑动逻辑彻底分离,自动轮播和吸附只管滑动,3D动画只管视觉,滑动停稳后再刷新动画,所有问题都迎刃而解
rollBy,并且滑动距离根据当前item的实际宽度和缩放动画动态计算,保证即使有3D缩放动画也不会“跳两格”或“对不准”
任何时刻只存在一个轮播任务,防止任务堆积导致一次滑动多个item
吸附和动画全部解耦,Activity只负责滑动,LayoutManager负责3D动画
- 为什么要这样做?
如果轮播和吸附抢占滑动,会导致动画抽搐或跳跃
如果滑动距离是固定的,item有缩放动画时实际距离会偏差,导致每次滑动不是正好一格
如果轮播任务堆积(不清理handler),会导致多次滑动合并在一次SCROLL_STATE_IDLE后执行(一次滑动不止一项,导致滑动混乱)
最终效果:手动和自动切换自如,不触发触摸事件就自动轮播,触摸则自动停止轮播,且一次只进行一次滑动
实现效果(没有任何手势动作):
[外链图片转存中…(img-RzANC2Lk-1751193138071)]
实现效果(含有手势动作):
[外链图片转存中…(img-jZOIFNRR-1751193138071)]
7. 总结
试错经历:
- 一开始可能把自动轮播、吸附、3D动画全都混在自定义LayoutManager里实现,导致滑动逻辑与动画耦合,容易冲突和失效
- 用
smoothScrollToPosition
、smoothScrollBy
等方法时,没考虑吸附和item实际宽度、动画缩放的影响,导致自动轮播会抽搐、回弹或滑动幅度不对 - 动画和吸附抢占了RecyclerView的滚动指令,可能出现“吸附后动画丢失”,或者“动画只在滑动中有效,停下后消失”
- 现在把动画和滑动逻辑彻底分离,自动轮播和吸附只管滑动,3D动画只管视觉,滑动停稳后再刷新动画,所有问题都迎刃而解