最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

基于Android實(shí)現(xiàn)三維效果的動態(tài)旋轉(zhuǎn)圖

 更新時間:2025年09月09日 10:12:15   作者:Katie。  
在電商、相冊、視頻封面、海報展示、啟動頁 Logo 等場景里,帶真實(shí)透視感的 3D 旋轉(zhuǎn)能明顯提升界面質(zhì)感,Android 自 3.x 起就支持基于屬性的 3D 旋轉(zhuǎn),所以本文給大家介紹了如何基于Android實(shí)現(xiàn)三維效果的動態(tài)旋轉(zhuǎn)圖,需要的朋友可以參考下

一、項(xiàng)目背景詳細(xì)介紹

在電商、相冊、視頻封面、海報展示、啟動頁 Logo 等場景里,帶真實(shí)透視感的 3D 旋轉(zhuǎn)能明顯提升界面質(zhì)感。常見需求:

  • 圖片繞 X/Y 軸持續(xù)旋轉(zhuǎn)(封面展示、加載動效)。
  • 卡片翻轉(zhuǎn)(繞 X 或 Y 軸 180° 翻面)。
  • 帶景深的 3D 旋轉(zhuǎn)(近大遠(yuǎn)小,具備透視壓縮)。

Android 自 3.x 起就支持基于屬性的 3D 旋轉(zhuǎn)(rotationX/rotationY),配合 setCameraDistance() 能得到還不錯的透視感;而傳統(tǒng) Camera + Matrix 則能實(shí)現(xiàn)更精細(xì)的像素級控制。

二、項(xiàng)目需求詳細(xì)介紹

  1. 圖片能連續(xù)、平滑地 3D 旋轉(zhuǎn)(可配方向/速度)。
  2. 可選繞 X 或繞 Y 軸旋轉(zhuǎn)。
  3. 可設(shè)置景深強(qiáng)度(近大遠(yuǎn)小的透視感)。
  4. 可暫停/恢復(fù)、重復(fù)/往返等播放控制。
  5. 兼容 Android 5.0+,盡量避免兼容雷區(qū)。

三、相關(guān)技術(shù)詳細(xì)介紹

  • 屬性動畫ObjectAnimator / ValueAnimator 控制 rotationX/rotationY。
  • 透視距離View.setCameraDistance(float),距離越大透視越弱,單位是像素乘以屏幕密度系數(shù)。
  • 插值器LinearInterpolator(勻速)、AccelerateDecelerateInterpolator(緩入緩出)。
  • Camera/Matrixandroid.graphics.Camera 做 3D 變換、Matrix 應(yīng)用到 Canvas。
  • 硬件加速:屬性動畫天然兼容;Camera+Matrix 某些機(jī)型需要切換圖層類型。

四、實(shí)現(xiàn)思路詳細(xì)介紹

方案A(首選):
1)XML/代碼里設(shè)定較大的 cameraDistance;
2)用 ObjectAnimator 驅(qū)動 rotationY(或 rotationX)從 0 → 360 循環(huán);
3)可選 repeatCount/Mode、Interpolator、時長。
優(yōu)點(diǎn):簡單、兼容性好、硬件加速性能佳。

方案B(可精細(xì)控制):
1)自定義 Rotate3DImageView,在 onDraw() 里用 Camera.rotateX/rotateY + Matrix;
2)可在繪制前/后裁剪半?yún)^(qū),做上半/下半獨(dú)立翻轉(zhuǎn);
3)ValueAnimator 驅(qū)動角度更新;
4)必要時設(shè)置 setLayerType(LAYER_TYPE_SOFTWARE/HARDWARE) 規(guī)避機(jī)型差異。

五、完整實(shí)現(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è)置相機(jī)距離(越大透視越弱;數(shù)值過小會導(dǎo)致透視夸張/變形)
        // 建議:以屏幕密度為基準(zhǔn)放大;經(jīng)驗(yàn)值:8000f ~ 20000f(按像素 * density)
        float density = getResources().getDisplayMetrics().density;
        ivSpin.setCameraDistance(12000 * density); // 試著改大/改小感受透視差別
 
        // 2) 繞Y軸無限旋轉(zhuǎn)(可換成 rotationX)
        spinAnimatorY = ObjectAnimator.ofFloat(ivSpin, "rotationY", 0f, 360f);
        spinAnimatorY.setDuration(3000); // 一圈3秒
        spinAnimatorY.setRepeatCount(ValueAnimator.INFINITE);
        spinAnimatorY.setInterpolator(new LinearInterpolator());
        spinAnimatorY.start();
 
        // 如需切換成繞X軸旋轉(zhuǎn),改用下面這段(示例先不啟動)
        spinAnimatorX = ObjectAnimator.ofFloat(ivSpin, "rotationX", 0f, 360f);
        spinAnimatorX.setDuration(3000);
        spinAnimatorX.setRepeatCount(ValueAnimator.INFINITE);
        spinAnimatorX.setInterpolator(new LinearInterpolator());
 
        // 可依據(jù)交互,在按鈕點(diǎn)擊時: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. 進(jìn)階方案:自定義 View + Camera/Matrix =======================
// 亮點(diǎn):可精細(xì)控制透視與局部翻轉(zhuǎn);適合卡片翻頁、上半/下半獨(dú)立翻轉(zhuǎn)等
// 文件: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;  // 當(dāng)前角度
    private int axis = AXIS_Y;  // 旋轉(zhuǎn)軸,默認(rèn)Y
    private float cameraZ = -12_000f; // 相機(jī)Z,負(fù)值表示遠(yuǎn)離屏幕(像素維度)
    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 處理某些機(jī)型的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();
 
        // 設(shè)置相機(jī)位置(Z 軸),單位是像素。負(fù)值遠(yuǎn)離屏幕,絕對值越大透視越弱
        // Camera#translate(0, 0, z) 不同廠商實(shí)現(xiàn)略有差異,必要時可按密度縮放
        camera.translate(0, 0, cameraZ);
 
        if (axis == AXIS_Y) {
            camera.rotateY(degree);
        } else {
            camera.rotateX(degree);
        }
        camera.getMatrix(matrix);
        camera.restore();
 
        // 將旋轉(zhuǎn)中心平移到控件中心(Camera/Matrix 默認(rèn)以(0,0)為中心)
        matrix.preTranslate(-cx, -cy);
        matrix.postTranslate(cx, cy);
 
        // 應(yīng)用矩陣到畫布
        canvas.concat(matrix);
 
        // 繪制圖片
        drawable.draw(canvas);
 
        // 恢復(fù)畫布
        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 位置(像素),負(fù)值表示遠(yuǎn)離屏幕,絕對值越大透視越弱 -->
        <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);
        // 也可在代碼里切換軸/時長/相機(jī)Z
        // v.setAxis(Rotate3DImageView.AXIS_X);
        // v.setCameraZ(-16000f);
        // v.setDuration(3500);
    }
}

六、代碼與關(guān)鍵點(diǎn)解讀

相機(jī)距離(方案A)

  • setCameraDistance(12000 * density) 是非常關(guān)鍵的一步。
  • 距離越小透視越強(qiáng)(近大遠(yuǎn)小更明顯),太小會變形嚴(yán)重;距離越大透視越弱,趨近于平面旋轉(zhuǎn)。
  • 常見經(jīng)驗(yàn)范圍:8000f ~ 20000f(乘以 density)。

屬性動畫控制

  • 使用 ObjectAnimator.ofFloat(view, "rotationY", 0f, 360f) 連續(xù)旋轉(zhuǎn);
  • LinearInterpolator 讓轉(zhuǎn)動勻速;可換 AccelerateDecelerate 做“呼吸感”。
  • pause()/resume() 方便在 onPause/onResume 做生命周期管理,避免后臺耗電。

Camera/Matrix(方案B)

  • onDraw() 中使用 Camera.rotateY/rotateX 后要平移到控件中心旋轉(zhuǎn):preTranslate(-cx,-cy) / postTranslate(cx,cy)
  • camera.translate(0,0,cameraZ) 控制景深;也可不平移僅靠旋轉(zhuǎn),效果會更“緊”。
  • 如果遇到某些機(jī)型顯示異常,可試試 setLayerType(LAYER_TYPE_SOFTWARE, null) 或保持 HARDWARE,二者擇一以實(shí)際效果為準(zhǔn)。

性能 & 資源

  • 方案A 基本不需要擔(dān)心性能(GPU 動畫,輕量);
  • 方案B 每幀重繪,盡量避免做額外開銷(如 Bitmap 頻繁創(chuàng)建)。
  • 圖片過大時請用 centerCrop 與合適分辨率,避免內(nèi)存抖動。

七、項(xiàng)目詳細(xì)總結(jié)

  • 首選:屬性動畫 + cameraDistance,實(shí)現(xiàn)簡單、性能穩(wěn)、展示效果已足夠“3D”。
  • 進(jìn)階Camera+Matrix 給你更高自由度(半?yún)^(qū)翻轉(zhuǎn)、復(fù)雜翻書效果),代價是自己管理繪制與兼容。
  • 通用建議:合理設(shè)置透視距離與動畫時長,注意生命周期暫?;謴?fù),避免后臺白跑。

八、常見問題與解答(FAQ)

為什么我設(shè)置了 rotationY 但看不出 3D 透視?
→ 大概率是 cameraDistance 太大(透視過弱)或太?。ɑ儯?。建議在 8000~20000 * density 內(nèi)調(diào)參。

Camera 效果在某些手機(jī)發(fā)虛/鋸齒?
→ 嘗試 setLayerType(LAYER_TYPE_SOFTWARE, null)HARDWARE 切換;另外避免在動畫中同時做大幅 scale。

如何只做 180° 卡片翻轉(zhuǎn)?
→ 把動畫區(qū)間調(diào)到 0~180,結(jié)束時替換圖片即可;或在 90° 時切換前后圖層。

如何讓旋轉(zhuǎn)更絲滑?
→ 使用 LinearInterpolator 勻速,時長 2.5~3.5s;圖片盡量使用與控件尺寸匹配的資源,減少 GPU 采樣壓力。

如何點(diǎn)擊暫停/繼續(xù)?
→ 方案A 直接 pause()/resume();方案B 對 ValueAnimator 調(diào)用相同方法或 cancel()/start()。

九、擴(kuò)展方向與優(yōu)化建議

  • 組合動效:在旋轉(zhuǎn)同時,疊加輕微 scale/alpha 做呼吸感。
  • 多圖輪播:配合 ViewPager2RecyclerView,在切換頁時加 3D 翻頁過渡。
  • 曲線速度:自定義 TimeInterpolator(如先快后慢)營造動勢。
  • 數(shù)據(jù)驅(qū)動:把 degree 暴露為可綁定屬性(DataBinding/Compose),做可控進(jìn)度展示。
  • Compose 版本:用 Modifier.graphicsLayer { rotationY = ...; cameraDistance = ... } + rememberInfiniteTransition 實(shí)現(xiàn)同等效果。

以上就是基于Android實(shí)現(xiàn)三維效果的動態(tài)旋轉(zhuǎn)圖的詳細(xì)內(nèi)容,更多關(guān)于Android動態(tài)旋轉(zhuǎn)圖的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

最新評論

仪陇县| 林口县| 开远市| 阳山县| 阿荣旗| 建宁县| 宁德市| 伊春市| 福鼎市| 凭祥市| 钟山县| 三都| 东辽县| 丰城市| 宜兰市| 阿拉善右旗| 阳信县| 海南省| 南宁市| 龙南县| 漳浦县| 桐柏县| 娄底市| 舒兰市| 塔城市| 萨迦县| 贡觉县| 西乌| 称多县| 汉源县| 姜堰市| 竹北市| 德庆县| 鸡泽县| 白玉县| 南昌县| 莎车县| 封开县| 张家川| 梁平县| 洱源县|