安卓RecyclerView實現3D滑動輪播效果全流程實戰

安卓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…