基于Android實現(xiàn)三維效果的動態(tài)旋轉圖
一、項目背景詳細介紹
在電商、相冊、視頻封面、海報展示、啟動頁 Logo 等場景里,帶真實透視感的 3D 旋轉能明顯提升界面質感。常見需求:
- 圖片繞 X/Y 軸持續(xù)旋轉(封面展示、加載動效)。
- 卡片翻轉(繞 X 或 Y 軸 180° 翻面)。
- 帶景深的 3D 旋轉(近大遠小,具備透視壓縮)。
Android 自 3.x 起就支持基于屬性的 3D 旋轉(rotationX/rotationY),配合 setCameraDistance() 能得到還不錯的透視感;而傳統(tǒng) Camera + Matrix 則能實現(xiàn)更精細的像素級控制。
二、項目需求詳細介紹
- 圖片能連續(xù)、平滑地 3D 旋轉(可配方向/速度)。
- 可選繞 X 或繞 Y 軸旋轉。
- 可設置景深強度(近大遠小的透視感)。
- 可暫停/恢復、重復/往返等播放控制。
- 兼容 Android 5.0+,盡量避免兼容雷區(qū)。
三、相關技術詳細介紹
- 屬性動畫:
ObjectAnimator/ValueAnimator控制rotationX/rotationY。 - 透視距離:
View.setCameraDistance(float),距離越大透視越弱,單位是像素乘以屏幕密度系數(shù)。 - 插值器:
LinearInterpolator(勻速)、AccelerateDecelerateInterpolator(緩入緩出)。 - Camera/Matrix:
android.graphics.Camera做 3D 變換、Matrix應用到Canvas。 - 硬件加速:屬性動畫天然兼容;
Camera+Matrix某些機型需要切換圖層類型。
四、實現(xiàn)思路詳細介紹
方案A(首選):
1)XML/代碼里設定較大的 cameraDistance;
2)用 ObjectAnimator 驅動 rotationY(或 rotationX)從 0 → 360 循環(huán);
3)可選 repeatCount/Mode、Interpolator、時長。
優(yōu)點:簡單、兼容性好、硬件加速性能佳。
方案B(可精細控制):
1)自定義 Rotate3DImageView,在 onDraw() 里用 Camera.rotateX/rotateY + Matrix;
2)可在繪制前/后裁剪半區(qū),做上半/下半獨立翻轉;
3)ValueAnimator 驅動角度更新;
4)必要時設置 setLayerType(LAYER_TYPE_SOFTWARE/HARDWARE) 規(guī)避機型差異。
五、完整實現(xiàn)代碼
// ======================= A. 推薦方案:屬性動畫 + cameraDistance =======================
// 文件:res/layout/activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/root"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:foregroundGravity="center">
<ImageView
android:id="@+id/ivSpin"
android:layout_width="220dp"
android:layout_height="220dp"
android:scaleType="centerCrop"
android:src="@drawable/sample" />
<!-- 可加控制按鈕/文本,這里省略 -->
</FrameLayout>
// 文件:java/com/example/rotate3d/MainActivity.java
package com.example.rotate3d;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.os.Bundle;
import android.util.TypedValue;
import android.view.animation.LinearInterpolator;
import android.widget.ImageView;
import androidx.appcompat.app.AppCompatActivity;
public class MainActivity extends AppCompatActivity {
private ImageView ivSpin;
private ObjectAnimator spinAnimatorY;
private ObjectAnimator spinAnimatorX;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ivSpin = findViewById(R.id.ivSpin);
// 1) 設置相機距離(越大透視越弱;數(shù)值過小會導致透視夸張/變形)
// 建議:以屏幕密度為基準放大;經驗值:8000f ~ 20000f(按像素 * density)
float density = getResources().getDisplayMetrics().density;
ivSpin.setCameraDistance(12000 * density); // 試著改大/改小感受透視差別
// 2) 繞Y軸無限旋轉(可換成 rotationX)
spinAnimatorY = ObjectAnimator.ofFloat(ivSpin, "rotationY", 0f, 360f);
spinAnimatorY.setDuration(3000); // 一圈3秒
spinAnimatorY.setRepeatCount(ValueAnimator.INFINITE);
spinAnimatorY.setInterpolator(new LinearInterpolator());
spinAnimatorY.start();
// 如需切換成繞X軸旋轉,改用下面這段(示例先不啟動)
spinAnimatorX = ObjectAnimator.ofFloat(ivSpin, "rotationX", 0f, 360f);
spinAnimatorX.setDuration(3000);
spinAnimatorX.setRepeatCount(ValueAnimator.INFINITE);
spinAnimatorX.setInterpolator(new LinearInterpolator());
// 可依據(jù)交互,在按鈕點擊時:spinAnimatorY.pause()/resume()/cancel()
}
@Override
protected void onPause() {
super.onPause();
if (spinAnimatorY != null && spinAnimatorY.isRunning()) {
spinAnimatorY.pause();
}
}
@Override
protected void onResume() {
super.onResume();
if (spinAnimatorY != null && spinAnimatorY.isPaused()) {
spinAnimatorY.resume();
}
}
}
// ======================= B. 進階方案:自定義 View + Camera/Matrix =======================
// 亮點:可精細控制透視與局部翻轉;適合卡片翻頁、上半/下半獨立翻轉等
// 文件:java/com/example/rotate3d/Rotate3DImageView.java
package com.example.rotate3d;
import android.animation.ValueAnimator;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Camera;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.RectF;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.View;
import android.view.animation.LinearInterpolator;
import androidx.annotation.Nullable;
public class Rotate3DImageView extends View {
public static final int AXIS_Y = 0;
public static final int AXIS_X = 1;
private Drawable drawable;
private Bitmap bitmap;
private final Camera camera = new Camera();
private final Matrix matrix = new Matrix();
private float degree = 0f; // 當前角度
private int axis = AXIS_Y; // 旋轉軸,默認Y
private float cameraZ = -12_000f; // 相機Z,負值表示遠離屏幕(像素維度)
private boolean autoStart = true;
private long duration = 3000L;
private ValueAnimator animator;
public Rotate3DImageView(Context context) { this(context, null); }
public Rotate3DImageView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public Rotate3DImageView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
if (attrs != null) {
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.Rotate3DImageView);
axis = a.getInt(R.styleable.Rotate3DImageView_axis, AXIS_Y);
cameraZ = a.getFloat(R.styleable.Rotate3DImageView_cameraZ, -12000f);
autoStart = a.getBoolean(R.styleable.Rotate3DImageView_autoStart, true);
duration = a.getInt(R.styleable.Rotate3DImageView_durationMs, 3000);
a.recycle();
}
setWillNotDraw(false);
setLayerType(LAYER_TYPE_HARDWARE, null); // 也可嘗試 SOFTWARE 處理某些機型的Camera兼容
}
public void setImageDrawable(Drawable d) {
this.drawable = d;
if (d instanceof BitmapDrawable) {
bitmap = ((BitmapDrawable) d).getBitmap();
} else {
bitmap = null;
}
requestLayout();
invalidate();
}
public void setImageResource(int resId) {
setImageDrawable(getResources().getDrawable(resId));
}
public void setAxis(int axis) {
this.axis = axis;
invalidate();
}
public void setDegree(float degree) {
this.degree = degree;
invalidate();
}
public void setCameraZ(float z) {
this.cameraZ = z;
invalidate();
}
public void setDuration(long durationMs) {
this.duration = durationMs;
if (animator != null) animator.setDuration(durationMs);
}
private void ensureAnimator() {
if (animator != null) return;
animator = ValueAnimator.ofFloat(0f, 360f);
animator.setInterpolator(new LinearInterpolator());
animator.setDuration(duration);
animator.setRepeatCount(ValueAnimator.INFINITE);
animator.addUpdateListener(a -> {
degree = (float) a.getAnimatedValue();
invalidate();
});
}
@Override protected void onAttachedToWindow() {
super.onAttachedToWindow();
if (autoStart) start();
}
@Override protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
stop();
}
public void start() {
ensureAnimator();
if (!animator.isStarted()) animator.start();
}
public void pause() {
if (animator != null && animator.isRunning()) animator.pause();
}
public void resumeAnim() {
if (animator != null && animator.isPaused()) animator.resume();
}
public void stop() {
if (animator != null) {
animator.cancel();
}
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int w = resolveSize(
drawable == null ? 200 : Math.max(drawable.getIntrinsicWidth(), 1),
widthMeasureSpec);
int h = resolveSize(
drawable == null ? 200 : Math.max(drawable.getIntrinsicHeight(), 1),
heightMeasureSpec);
setMeasuredDimension(w, h);
if (drawable != null) {
drawable.setBounds(0, 0, w, h);
}
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (drawable == null) return;
final int cx = getWidth() / 2;
final int cy = getHeight() / 2;
// 保存畫布狀態(tài)
int saveCount = canvas.save();
matrix.reset();
camera.save();
// 設置相機位置(Z 軸),單位是像素。負值遠離屏幕,絕對值越大透視越弱
// Camera#translate(0, 0, z) 不同廠商實現(xiàn)略有差異,必要時可按密度縮放
camera.translate(0, 0, cameraZ);
if (axis == AXIS_Y) {
camera.rotateY(degree);
} else {
camera.rotateX(degree);
}
camera.getMatrix(matrix);
camera.restore();
// 將旋轉中心平移到控件中心(Camera/Matrix 默認以(0,0)為中心)
matrix.preTranslate(-cx, -cy);
matrix.postTranslate(cx, cy);
// 應用矩陣到畫布
canvas.concat(matrix);
// 繪制圖片
drawable.draw(canvas);
// 恢復畫布
canvas.restoreToCount(saveCount);
}
}
// ======================= 自定義屬性聲明 =======================
// 文件:res/values/attrs.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="Rotate3DImageView">
<!-- 0: Y軸;1: X軸 -->
<attr name="axis" format="enum">
<enum name="y" value="0"/>
<enum name="x" value="1"/>
</attr>
<!-- Camera Z 位置(像素),負值表示遠離屏幕,絕對值越大透視越弱 -->
<attr name="cameraZ" format="float"/>
<!-- 自動開始動畫 -->
<attr name="autoStart" format="boolean"/>
<!-- 周期(毫秒) -->
<attr name="durationMs" format="integer"/>
</declare-styleable>
</resources>
// ======================= 使用自定義 View 的布局示例 =======================
// 文件:res/layout/activity_custom.xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout 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"
android:foregroundGravity="center">
<com.example.rotate3d.Rotate3DImageView
android:id="@+id/iv3d"
android:layout_width="240dp"
android:layout_height="240dp"
app:axis="y"
app:cameraZ="-12000"
app:autoStart="true"
app:durationMs="2800" />
</FrameLayout>
// 文件:java/com/example/rotate3d/CustomActivity.java
package com.example.rotate3d;
import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;
public class CustomActivity extends AppCompatActivity {
@Override protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_custom);
Rotate3DImageView v = findViewById(R.id.iv3d);
v.setImageResource(R.drawable.sample);
// 也可在代碼里切換軸/時長/相機Z
// v.setAxis(Rotate3DImageView.AXIS_X);
// v.setCameraZ(-16000f);
// v.setDuration(3500);
}
}六、代碼與關鍵點解讀
相機距離(方案A)
setCameraDistance(12000 * density)是非常關鍵的一步。- 距離越小透視越強(近大遠小更明顯),太小會變形嚴重;距離越大透視越弱,趨近于平面旋轉。
- 常見經驗范圍:
8000f ~ 20000f(乘以density)。
屬性動畫控制
- 使用
ObjectAnimator.ofFloat(view, "rotationY", 0f, 360f)連續(xù)旋轉; LinearInterpolator讓轉動勻速;可換AccelerateDecelerate做“呼吸感”。pause()/resume()方便在onPause/onResume做生命周期管理,避免后臺耗電。
Camera/Matrix(方案B)
- 在
onDraw()中使用Camera.rotateY/rotateX后要平移到控件中心旋轉:preTranslate(-cx,-cy) / postTranslate(cx,cy); camera.translate(0,0,cameraZ)控制景深;也可不平移僅靠旋轉,效果會更“緊”。- 如果遇到某些機型顯示異常,可試試
setLayerType(LAYER_TYPE_SOFTWARE, null)或保持HARDWARE,二者擇一以實際效果為準。
性能 & 資源
- 方案A 基本不需要擔心性能(GPU 動畫,輕量);
- 方案B 每幀重繪,盡量避免做額外開銷(如 Bitmap 頻繁創(chuàng)建)。
- 圖片過大時請用
centerCrop與合適分辨率,避免內存抖動。
七、項目詳細總結
- 首選:屬性動畫 +
cameraDistance,實現(xiàn)簡單、性能穩(wěn)、展示效果已足夠“3D”。 - 進階:
Camera+Matrix給你更高自由度(半區(qū)翻轉、復雜翻書效果),代價是自己管理繪制與兼容。 - 通用建議:合理設置透視距離與動畫時長,注意生命周期暫停恢復,避免后臺白跑。
八、常見問題與解答(FAQ)
為什么我設置了 rotationY 但看不出 3D 透視?
→ 大概率是 cameraDistance 太大(透視過弱)或太?。ɑ儯=ㄗh在 8000~20000 * density 內調參。
Camera 效果在某些手機發(fā)虛/鋸齒?
→ 嘗試 setLayerType(LAYER_TYPE_SOFTWARE, null) 或 HARDWARE 切換;另外避免在動畫中同時做大幅 scale。
如何只做 180° 卡片翻轉?
→ 把動畫區(qū)間調到 0~180,結束時替換圖片即可;或在 90° 時切換前后圖層。
如何讓旋轉更絲滑?
→ 使用 LinearInterpolator 勻速,時長 2.5~3.5s;圖片盡量使用與控件尺寸匹配的資源,減少 GPU 采樣壓力。
如何點擊暫停/繼續(xù)?
→ 方案A 直接 pause()/resume();方案B 對 ValueAnimator 調用相同方法或 cancel()/start()。
九、擴展方向與優(yōu)化建議
- 組合動效:在旋轉同時,疊加輕微
scale/alpha做呼吸感。 - 多圖輪播:配合
ViewPager2或RecyclerView,在切換頁時加 3D 翻頁過渡。 - 曲線速度:自定義
TimeInterpolator(如先快后慢)營造動勢。 - 數(shù)據(jù)驅動:把
degree暴露為可綁定屬性(DataBinding/Compose),做可控進度展示。 - Compose 版本:用
Modifier.graphicsLayer { rotationY = ...; cameraDistance = ... }+rememberInfiniteTransition實現(xiàn)同等效果。
以上就是基于Android實現(xiàn)三維效果的動態(tài)旋轉圖的詳細內容,更多關于Android動態(tài)旋轉圖的資料請關注腳本之家其它相關文章!
相關文章
Android提高之AudioRecord實現(xiàn)助聽器的方法
這篇文章主要介紹了Android中AudioRecord實現(xiàn)助聽器的方法,對進行Android項目開發(fā)有一定的借鑒價值,需要的朋友可以參考下2014-08-08
使用Android開發(fā)接入第三方原生SDK實現(xiàn)微信登錄
這篇文章主要介紹了使用Android開發(fā)接入第三方原生SDK實現(xiàn)微信登錄,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-03-03
Android 藍牙連接 ESC/POS 熱敏打印機打印實例(ESC/POS指令篇)
這篇文章主要介紹了Android 藍牙連接 ESC/POS 熱敏打印機打印實例(ESC/POS指令篇),具有一定的參考價值,感興趣的小伙伴們可以參考一下。2017-04-04
Android開發(fā)之基于DialogFragment創(chuàng)建對話框的方法示例
這篇文章主要介紹了Android開發(fā)之基于DialogFragment創(chuàng)建對話框的方法,結合實例形式分析了DialogFragment創(chuàng)建對話框的具體功能與布局相關實現(xiàn)技巧,需要的朋友可以參考下2017-08-08
Android使用java實現(xiàn)網絡連通性檢查詳解
這篇文章主要為大家詳細介紹了Android使用java實現(xiàn)網絡連通性檢查的相關知識,文中的示例代碼講解詳細,感興趣的小伙伴可以跟隨小編一起學習一下2025-12-12
Android中Gallery和ImageSwitcher的使用實例
今天小編就為大家分享一篇關于Android中Gallery和ImageSwitcher的使用實例,小編覺得內容挺不錯的,現(xiàn)在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧2019-03-03
Android table布局開發(fā)實現(xiàn)簡單計算器
這篇文章主要為大家詳細介紹了Android table布局開發(fā)實現(xiàn)簡單計算器,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2020-05-05

