Android自定義View實(shí)現(xiàn)手指滑動(dòng)3D變換效果

目錄

效果展示

相關(guān)文章

核心的邏輯可以參考我的這篇文章:Android自定義View實(shí)現(xiàn)3D翻轉(zhuǎn)效果

實(shí)現(xiàn)步驟

1.創(chuàng)建TouchRotateLayout
TouchRotateLayout繼承自FrameLayout,觸摸變換的邏輯都在這個(gè)類中實(shí)現(xiàn)

public class TouchRotateLayout extends FrameLayout {
    public TouchRotateLayout(@NonNull Context context) {
        this(context,null);
    }
    public TouchRotateLayout(@NonNull Context context, @Nullable AttributeSet attrs) {
        this(context, attrs,0);
    }
    public TouchRotateLayout(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }
}

2.實(shí)現(xiàn)變換邏輯
由于TouchRotateLayout是一個(gè)ViewGroup因此我們需要在dispatchDraw方法中進(jìn)行處理

 @Override
    protected void dispatchDraw(Canvas canvas) {
        //重置Matrix
        mMatrix.reset();
        //保存canvas和camera的狀態(tài)
        canvas.save();
        mCamera.save();

        //圍繞XY軸旋轉(zhuǎn)相應(yīng)角度
        mCamera.rotateX(mRotateX);
        mCamera.rotateY(mRotateY);

        mCamera.getMatrix(mMatrix);
        //將原點(diǎn)移動(dòng)到中心位置
        mMatrix.preTranslate(-getWidth() / 2f , -getHeight() / 2f);
        mMatrix.postTranslate(getWidth() / 2f , getHeight() / 2f);

        //操作完并且獲取了Matrix后要恢復(fù)camera的狀態(tài)
        mCamera.restore();

        //將操作camera產(chǎn)生的Matrix賦給canvas
        canvas.setMatrix(mMatrix);
        super.dispatchDraw(canvas);
        
        //繪制完之后要恢復(fù)canvas的狀態(tài)
        canvas.restore();
    }

3.X、Y軸旋轉(zhuǎn)角度的計(jì)算
接下來(lái)我們就需要根據(jù)手指觸摸的點(diǎn)的X、Y值來(lái)計(jì)算CameraX、Y軸方向的旋轉(zhuǎn)角度即mRotateX和mRotateY的值

private static final float MAX_DEGREES = 30;//最大旋轉(zhuǎn)角度為30度
 @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()){
            case MotionEvent.ACTION_DOWN:
                calcRotateXY(event.getX(),event.getY());
                break;
            case MotionEvent.ACTION_MOVE:
                calcRotateXY(event.getX(),event.getY());
                break;
            case MotionEvent.ACTION_UP:
                calcRotateXY(event.getX(),event.getY());
                break;
        }
        return true;
    }
    /**
     * 根據(jù)傳來(lái)的觸摸點(diǎn)來(lái)計(jì)算旋轉(zhuǎn)角度
     * @param x
     * @param y
     */
    private void calcRotateXY(float x, float y) {
        //算出整個(gè)控件寬高的一半
        int halfWidth = getWidth() / 2;
        int halfHeight = getHeight() / 2;

        //計(jì)算X軸的旋轉(zhuǎn)角度
        float percentX = (x - halfWidth) / halfWidth;
        mRotateX = (percentX > 1f ? 1f : Math.max(percentX, -1f)) * MAX_DEGREES;

        //計(jì)算Y軸的旋轉(zhuǎn)角度
        float percentY = (y - halfHeight) / halfHeight;
        mRotateY = (percentY > 1f ? 1f : Math.max(percentY, -1f)) * MAX_DEGREES;

        //重繪
        postInvalidate();
    }

其中X軸與Y軸的旋轉(zhuǎn)角度的計(jì)算方法是這樣的,以X軸為例,我們?nèi)〉接|摸點(diǎn)的X軸方向的值減去整個(gè)控件寬度的一半,也就是觸摸點(diǎn)與中心點(diǎn)之間的距離,然后用此距離除以整個(gè)控件的寬度,得出的結(jié)果也就是觸摸點(diǎn)到中心點(diǎn)X軸方向的距離所占整個(gè)控件寬度一半的百分比,然后我們?cè)侔逊?hào)加上,比如在中心點(diǎn)左邊的是負(fù)值右邊是正值,最后用這個(gè)帶符號(hào)的百分比去與設(shè)定好的最大的旋轉(zhuǎn)角度相乘(這里我們?cè)O(shè)置的是30度),最后得出的就是X軸要旋轉(zhuǎn)的角度。Y軸與X軸的計(jì)算方式一樣。


這一步完成后得到的效果如下:

我們看到當(dāng)手指離開(kāi)屏幕后,整個(gè)旋轉(zhuǎn)角度并沒(méi)有復(fù)原,因此接下來(lái)我們要做手指抬起復(fù)原角度的處理。
4.手指抬起時(shí)的復(fù)原處理
這里的復(fù)原邏輯我們使用一個(gè)動(dòng)畫(huà)來(lái)實(shí)現(xiàn),其中mUpRotateX與mUpRotateY分別時(shí)手指抬起時(shí)所計(jì)算出來(lái)的X,Y軸的旋轉(zhuǎn)角度,這里我們做了一個(gè)值從1到0變化的ValueAnimator目的是讓手指抬起時(shí)的X,Y軸的旋轉(zhuǎn)角度由100%逐漸降到0%,通過(guò)這樣來(lái)讓復(fù)原看起來(lái)更平滑

        recoverAnimator = ValueAnimator.ofFloat(1f, 0f);
        recoverAnimator.setDuration(500);
        recoverAnimator.setInterpolator(new BounceInterpolator());
        recoverAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator valueAnimator) {
                float percent = (float) valueAnimator.getAnimatedValue();
                mRotateX = mUpRotateX * percent;
                mRotateY = mUpRotateY * percent;
                postInvalidate();
            }
        });

除此之外我們還要對(duì)onTouchEvent中的代碼做相應(yīng)的調(diào)整,即在ACTION_DOWN的時(shí)候停止上一次的復(fù)原動(dòng)畫(huà),在ACTION_UP的時(shí)候執(zhí)行復(fù)原動(dòng)畫(huà)

 @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()){
            case MotionEvent.ACTION_DOWN:
                //如果在觸摸的時(shí)候上一個(gè)復(fù)原動(dòng)畫(huà)還沒(méi)執(zhí)行完畢則先將動(dòng)畫(huà)取消
                if(recoverAnimator != null){
                    recoverAnimator.cancel();
                }
                calcRotateXY(event.getX(),event.getY(),false);
                break;
            case MotionEvent.ACTION_MOVE:
                calcRotateXY(event.getX(),event.getY(),false);
                break;
            case MotionEvent.ACTION_UP:
                calcRotateXY(event.getX(),event.getY(),true);
                //手指抬起的時(shí)候進(jìn)行復(fù)原
                recover();
                break;
        }
        return true;
    }

    /**
     * 復(fù)原角度
     */
    private void recover() {
        recoverAnimator.start();
    }

這樣就實(shí)現(xiàn)了我們一開(kāi)始展示的效果了


5.控件的使用方法
使用方法很簡(jiǎn)單,我們只需要用這個(gè)自定義View包裹要實(shí)現(xiàn)觸摸變換的布局即可,如下所示

<com.itfitness.touchrotatelayout.widget.TouchRotateLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        <!-- 要實(shí)現(xiàn)觸摸變換的布局 -->
        <LinearLayout
            android:orientation="vertical"
            android:layout_gravity="center"
            android:gravity="center_horizontal"
            android:layout_width="match_parent"
            android:layout_height="wrap_content">
            <Button
                android:text="我是太極八卦圖"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"/>
            <ImageView
                android:src="@drawable/bg"
                android:layout_width="300dp"
                android:layout_height="300dp"/>
        </LinearLayout>
    </com.itfitness.touchrotatelayout.widget.TouchRotateLayout>

案例源碼

https://gitee.com/itfitness/touch-rotate-layout

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容