安卓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);}}
}
在你的 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 {@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. 總結
試錯經歷:
- 一開始可能把自動輪播、吸附、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動畫只管視覺,滑動停穩后再刷新動畫,所有問題都迎刃而解