安卓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.RecyclerViewandroid: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"><TextViewandroid: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@Overridepublic ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {// 加载item布局View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_simple, parent, false);// 创建并返回ViewHolderreturn new ViewHolder(view);}/*** 数据和View的绑定* @param holder 当前item的ViewHolder* @param position 当前item的位置*/@Overridepublic void onBindViewHolder(@NonNull ViewHolder holder, int position) {// 设置TextView的内容为对应位置的数据holder.tvTitle.setText(dataList.get(position));}/*** 返回数据源的总数,决定RecyclerView有多少item*/@Overridepublic int getItemCount() {return dataList.size();}/*** ViewHolder:持有item视图的引用,提升性能*/static class ViewHolder extends RecyclerView.ViewHolder {TextView tvTitle; // item中的TextViewpublic ViewHolder(@NonNull View itemView) {super(itemView);// 绑定item中的TextViewtvTitle = 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 {@Overrideprotected 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"><LinearLayoutandroid:orientation="vertical"android:gravity="center"android:padding="18dp"android:layout_width="match_parent"android:layout_height="match_parent"><ImageViewandroid: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" /><TextViewandroid: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"/><TextViewandroid: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@Overridepublic ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_carousel, parent, false);return new ViewHolder(view);}@Overridepublic 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());}@Overridepublic 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 {@Overrideprotected 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应用缩放和旋转效果@Overridepublic void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {super.onLayoutChildren(recycler, state);scaleAndRotateItems();}// 横向滚动时,实时给所有子item应用缩放和旋转效果@Overridepublic int scrollHorizontallyBy(int dx, RecyclerView.Recycler recycler, RecyclerView.State state) {int scrolled = super.scrollHorizontallyBy(dx, recycler, state);scaleAndRotateItems();return scrolled;}// 当滑动状态改变时(如滑动停止),保证特效刷新@Overridepublic 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 {@Overrideprotected 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画廊动画@Overridepublic int scrollHorizontallyBy(int dx, RecyclerView.Recycler recycler, RecyclerView.State state) {int scrolled = super.scrollHorizontallyBy(dx, recycler, state);scaleAndRotateItems();return scrolled;}// 布局完成后,刷新3D动画@Overridepublic void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {super.onLayoutChildren(recycler, state);scaleAndRotateItems();}// 滚动状态变化时,滑动停下再刷新3D动画,保证吸附后效果正确@Overridepublic 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; // 控制是否启动自动轮播// 自动轮播任务Runnableprivate final Runnable autoScrollRunnable = new Runnable() {@Overridepublic void run() {if (!isAutoScroll) return; // 若未启用自动轮播,直接退出// 只在RecyclerView完全静止时才滑动,避免与吸附抢占滑动导致幅度过大if (recyclerView.getScrollState() != RecyclerView.SCROLL_STATE_IDLE) {handler.removeCallbacks(this); // 移除当前所有相同任务,防止任务堆积handler.postDelayed(this, 200); // 200ms后再次检测return;}// 找到当前被吸附在中间的itemView 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;// 像素级滑动到下一个itemrecyclerView.smoothScrollBy(widthWithMargin, 0);// 不要在这里post下一次轮播(否则可能“连开两枪”),等SCROLL_STATE_IDLE时再安排}};@Overrideprotected 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);// 设置Adapteradapter = new RichCarouselAdapter(this, carouselItems);recyclerView.setAdapter(adapter);// 无限轮播体验,初始定位到中间int initialPos = carouselItems.size() * 500;recyclerView.scrollToPosition(initialPos);// 滚动状态监听器,只在滑动停稳后才安排下一次自动轮播recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {@Overridepublic void onScrollStateChanged(RecyclerView rv, int newState) {if (newState == RecyclerView.SCROLL_STATE_IDLE && isAutoScroll) {// 移除所有等待的自动轮播任务,确保只存在一个handler.removeCallbacks(autoScrollRunnable);handler.postDelayed(autoScrollRunnable, AUTO_SCROLL_INTERVAL);}}});}@Overrideprotected void onResume() {super.onResume();isAutoScroll = true;// 确保只启动一个自动轮播任务handler.removeCallbacks(autoScrollRunnable);handler.postDelayed(autoScrollRunnable, AUTO_SCROLL_INTERVAL);}@Overrideprotected 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动画只管视觉,滑动停稳后再刷新动画,所有问题都迎刃而解

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/web/87055.shtml
繁体地址,请注明出处:http://hk.pswp.cn/web/87055.shtml
英文地址,请注明出处:http://en.pswp.cn/web/87055.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

电机控制——电机位置传感器零位标定

在有感FOC算法中电机位置是一个重要的输入&#xff0c;电机位置传感器的作用就是测量电机的旋转角度&#xff0c;通常是输出sin(Theta)和cos(Theta)两路模拟信号&#xff0c;根据这两路模拟信号测得电机旋转绝对角度。注意传感器测量的是机械角度&#xff0c;不是电角度。 关于…

生物化学(实验流程) PCR聚合酶链式反应: DNA 凝胶电泳实验原理 实验流程方法 实操建议笔记

凝胶电泳是分子生物学中最常用的技术之一&#xff0c;广泛用于 DNA 片段的可视化、分离与识别。在获取DNA 凝胶电泳相关设备&#xff08;电泳设备 & DNA样品染料 & 凝胶 & 染料&#xff09;之后&#xff0c;可以考虑进行电泳操作。 整体电泳操作流程&#xff08;从…

Python应用指南:利用高德地图API获取公交+地铁可达圈(三)

副标题&#xff1a;基于模型构建器的批处理多份CSV转换为点、线、面图层 在地理信息系统&#xff08;GIS&#xff09;的实际应用中&#xff0c;我们经常需要处理大量以表格形式存储的数据&#xff0c;例如人口统计数据、兴趣点&#xff08;POI&#xff09;信息和监测站点记录等…

每日算法刷题Day38 6.25:leetcode前缀和3道题,用时1h40min

5. 1749.任意子数组和的绝对值的最大值(中等,学习) 1749. 任意子数组和的绝对值的最大值 - 力扣&#xff08;LeetCode&#xff09; 思想 1.给你一个整数数组 nums 。一个子数组 [numsl, numsl1, ..., numsr-1, numsr] 的 和的绝对值 为 abs(numsl numsl1 ... numsr-1 nu…

创客匠人视角下创始人 IP 打造的底层逻辑与实践路径

在知识付费行业蓬勃发展的当下&#xff0c;创始人 IP 已成为连接用户与商业价值的核心纽带。创客匠人创始人老蒋在与行业头部 IP 洪鑫的对话中揭示了一个关键命题&#xff1a;IP 打造的成败&#xff0c;始于发心与理念的根基。从洪鑫教育中心营收超 6000 万的案例来看&#xff…

2022/7 N2 jlpt词汇

気力&#xff08;きりょく&#xff09; 清く&#xff08;きよく&#xff09; 記録&#xff08;きろく&#xff09; 記憶&#xff08;きおく&#xff09; 賢い&#xff08;かしこい&#xff09; 偉い&#xff08;えらい&#xff09; 凄い&#xff08;すごい&#xff09; 鋭い&am…

系统性能优化-8 TCP缓冲区与拥塞控制

每个 TCP 连接都有发送缓冲区和接收缓冲区&#xff0c;发送缓冲区存已发送未确认数据和待发送数据&#xff0c;接收缓冲区存接收但是没有被上层服务读取的数据。 # cat /proc/net/sockstat sockets: used 1885 TCP: inuse 537 orphan 0 tw 3 alloc 959 mem 10其中 mem 代表当前…

【前端】vue工程环境配置

环境准备(Windows版本) nodejs安装 (base) PS C:\Users\Administrator> nvm install 18.8.0 (base) PS C:\Users\Administrator> nvm use 18.8.0 Now using node v18.8.0 (64-bit) (base) PS C:\Users\Administrator> npm -v 8.18.0 (base) PS C:\Users\Administrat…

什么是data version control?为什么需要它?它能解决什么问题?

Data Version Control (DVC) 是一个开源工具&#xff0c;专为数据科学和机器学习项目设计。它的核心目标是像 Git 管理代码一样来管理机器学习项目中的数据和模型文件。 简单来说&#xff0c;DVC 是什么&#xff1f; Git for Data & Models&#xff1a; 它扩展了 Git 的功…

简约计生用品商城简介

计生用品商城简介&#xff1a;uniapp结合thinkphp实现的全开源代码&#xff0c; 内置基本功能&#xff1a;1.后台商品excel一键导入 2.分销利润&#xff0c;按照利润加个分红

go中自动补全插件安装-gopls

vscode中安装gopls失败&#xff0c;导致go中代码无提示&#xff0c;无法自动补全引用 环境变量中设置go的代理&#xff1a;setx GOPROXY “https://goproxy.cn,direct”go install golang.org/x/tools/goplslatest

力扣寻找数组中心索引-性能优化思考

如下代码 var pivotIndex function(nums) {// 空数组返回-1if (nums.length 0) return -1// 计算数组总和const totalSum nums.reduce((sum, num) > sum num, 0);let leftSum 0;// 遍历数组查找中心索引for (let i 0; i < nums.length; i) {// 右侧和 总和 - 左侧…

SVN 分支管理(本文以Unity项目为例)

文章目录 1.准备工作2.新建SVN仓库2.拉取远端空 trunk 到Unity项目目录下3.设置忽略&#xff0c;提交unity项目至仓库3.创建分支4.切换分支5.合并分支回主干&#xff08;例如将 trunk_01 合并回 trunk&#xff09;5.删除分支&#xff08;可选&#xff09; 1.准备工作 下载Tort…

数据结构学习day6---流+读写函数+缓冲+定义函数

目录 1.标准io&#xff1b; stdio.h 1.1标准io的概念 1.2Linux操作系统当中IO都是对文件的操作 1.3标准IO&#xff1a;ANSI C 设计的一组用文件IO 封装的操作库函数 2.文件 2.1作用 2.2linux中文件的类型 3.man 5.流: FILE* 5.1流的定义 5.2流的分类 6.c语言文…

互联网医院,正在发生的医疗新变革

随着信息技术的飞速发展&#xff0c;互联网医院作为医疗服务的新形态&#xff0c;正在全球范围内迅速崛起。在中国&#xff0c;这一变革尤为显著&#xff0c;互联网医院不仅改善了医疗服务的可及性和便捷性&#xff0c;还极大地提升了医疗服务的质量和效率。 一、互联网医院的发…

rabbitmq动态创建交换机、队列、动态绑定,销毁

// 缓存已创建的绑定&#xff0c;避免重复声明private final Map<String, Date> createdBindings new ConcurrentHashMap<>(); public void createAndBindQueueToExchange(String type,String clinetId, String routingKey) {String queueName routingKey;lo…

云效代码仓库导入自建gitlab中

登录自建GitLab 在浏览器中输入GitLab访问地址http://192.168.1.111:81/users/sign_in&#xff0c;输入账号和密码登录GitLab服务&#xff0c;如下图&#xff1a; 新建一个空的代码库 按照以下截图顺序&#xff0c;创建一个新的空项目&#xff0c;如下&#xff1a; 克隆镜像 …

业界优秀的零信任安全管理系统产品介绍

腾讯 iOA 零信任安全管理系统 简介&#xff1a;腾讯 iOA 零信任安全管理系统是腾讯终端安全团队针对企业安全上云和数字化转型&#xff0c;提供的企业网络边界处的应用访问管控系统&#xff0c;为企业应用提供统一、安全、高效的访问入口&#xff0c;同时提供终端安全加固、软…

从设计到开发一个小程序页面

巧妇难为无米之炊&#xff0c;想写功能但是没有好看的设计&#xff0c;边写边设计效率又不够高。mastergoAi生成的页面又不够好看&#xff0c;而且每月给的免费积分用得又超快&#xff0c;so决定自给自足。能有多难&#xff0c;先做&#xff0c;做了再改。 于是决定踏足设计&a…

Linux系统 / Ubuntu虚拟机 安装DHCP服务

一、安装DHCP服务 xxx:~$ sudo apt install isc-dhcp-server 正在读取软件包列表... 完成 正在分析软件包的依赖关系树 正在读取状态信息... 完成 将会同时安装下列软件&#xff1a; libirs-export161 libisccfg-export163 建议安装&#xff1a; isc-dhcp-s…