目錄

效果展示

相關(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>