說明:之前在網上到處搜尋類似的旋轉效果 但搜到的結果都不是十分滿意 原因不多追述
( 由于之前隻是交流學習使用 現在已經改進為能用狀态 )
(最新去github下載下傳 修複滑動時偶有卡頓的問題 , 修正設定某些視圖旋轉到超過90° 殘影問題 頁面切換 修正為滑動到 角度設定的一半時 ,添加了自動歸位為正面顯示效果)
(如果有人找到過相關 比較好的效果 可以發一下連接配接 一起共同進步)
一 效果展示 :如非您所需要的效果 也希望能給些微幫助 (已經添加了上下滾動内容 )
具體操作以及實作 效果 請看項目例子
二 使用方式
此控件繼承FrameLayout
//aar引用 現更新為如下 建議不要使用(Bintray 要停了 有空換個倉庫)
implementation 'com.icefordog:3dviewGroup:2.0.0'
1.所有包含子控件 現已經自動 重新布局到相應位置以及調整了大小
2.添加了上下滾動 以及代碼主動翻頁 和翻頁回調
3.添加了自定義屬性調整部分功能機體如下
# 使用方式如下
<com.burning.foethedog.Rota3DSwithView
android:id="@+id/_test_Rota3DVSwithView"
android:layout_width="400dp"
android:layout_height="300dp"
app:autoscroll="true"
app:heightRatio="0.8"
app:rotateV="true"
app:rotation="40"
app:widthRatio="0.6">
#是否自動滾動 app:autoscroll="true"
#子控件對于改viegroup 中的寬高比例 預設無為0.7
app:heightRatio app:widthRatio
#垂直或者水準滑動 app:rotateV="true"
#偏轉角度 app:rotation="40"
setR3DPagechange 監聽滑動頁面正面頁的變化
#此處list偷懶借用了RecyclerView.Adapter 暫時僅可設定單一模闆
<com.burning.foethedog.Rota3DSwithViewList
android:id="@+id/rota3DSwithViewList_test"
android:layout_width="match_parent"
android:layout_height="300dp"
android:background="@color/colorAccent"
app:autoscroll="true" />
子空間直接添加如同framelayout 相同 如要如圖效果 唯一要求子空間必須位于父控件中心且寬高等大小 為了友善擴充而做 如有其他需求可自行更改 (注 所有子控件 最好添加上背景 由于繪制機制和動畫原因 沒有背景會有部分重貼) 内部子view 可為 任意ViewGroup 。 弱使用過程中遇見任何BUG 歡迎提出。
三 實作原理
實作原理由Camera 與Maxtrix 組合修改View的繪制而得 具體Camera 與Maxtrix 的變換 過程請自行搜尋。 在此不班門弄斧。具體修改有
@Override
protected void dispatchDraw(Canvas canvas) {
int indexleft = getWidth() / 2;//中間顯示視圖 ----左邊的位置
int postTranslateX = rotationX * childWith / 2 / rotation;//設-----定邊移動 距離
//定點 又稱頂點
// chilDrawforCamera(canvas, postTranslateX, indexleft, 3);//預繪制 的 縣繪制 防止遮擋
for (int i = 0; i < 4; i++)
chilDrawforCamera(canvas, postTranslateX, indexleft, i);
if (!isTouch)
handler.sendEmptyMessageDelayed(1, 100);
}
重新編寫 dispathDraw() 進而達到 不必要去修改子view的内容 而添加擴充性
具體變換包括
private void chilDrawforCamera(Canvas canvas, int postTranslateX, int indexleft, int i) {
canvas.save();
mCamera.save();
mMaxtrix.reset();
mCamera.translate(postTranslateX, 0, 0);
mCamera.rotateY(rotationX);
mCamera.translate(postTranslateX, 0, 0);
if (postTranslateX == 0) {
if (isright)
setCameraChange(childWith, rotation, i);
else
setCameraChange(-childWith, -rotation, i);
} else if (postTranslateX > 0) {
setCameraChange(childWith, rotation, i);
} else if (postTranslateX < 0) {
setCameraChange(-childWith, -rotation, i);
}
mCamera.getMatrix(mMaxtrix);
mCamera.restore();
mMaxtrix.preTranslate(-indexleft, -getHeight() / 2);//指定在 螢幕上 運作的棱 是哪一條
mMaxtrix.postTranslate(indexleft, getHeight() / 2);//運作路徑
canvas.concat(mMaxtrix);
//繪制
View childAt = getChildAt((swithView(i) + 2 * getChildCount()) % getChildCount());
drawChild(canvas, childAt, 0);
canvas.restore();
}
指定需要繪制的子view 先後順序以及哪些子view
private int swithView(int i) {
int k = 0;
switch (i) {
case 0:
if (isright)
k = index - 2;
else
k = index + 2;
break;
case 1:
if (isright)
k = index + 1;
else
k = index - 1;
break;
case 2:
if (isright)
k = index - 1;
else
k = index + 1;
break;
case 3:
k = index;
break;
}
return k;
具體的網上其他 類似效果到底有什麼不同于優勢在此不多做描述 實作方式上有哪些不同 嗯 下次有空再細說(由于需要大量圖文描述 3D的變換過程才講的清楚,有些間隔的時間也稍長 隻記得大概的思路過程)。
private void setCameraChange(int translate, int roat, int i) {
switch (i) {
case 0:
//預繪制 的VIEW
mCamera.translate(-translate / 2, 0, 0);
mCamera.rotateY(-roat);
mCamera.translate(-translate / 2, 0, 0);
mCamera.translate(-translate / 2, 0, 0);
mCamera.rotateY(-roat);
mCamera.translate(-translate / 2, 0, 0);
break;
//目前位置兩側的View
case 1:
mCamera.translate(translate / 2, 0, 0);
mCamera.rotateY(roat);
mCamera.translate(translate / 2, 0, 0);
break;
case 2:
mCamera.translate(-translate / 2, 0, 0);
mCamera.rotateY(-roat);
mCamera.translate(-translate / 2, 0, 0);
break;
//最後繪制 目前顯示位置 防止 被遮擋
case 3:
mCamera.rotateY(0);
break;
}
}
其他剩下的就是 index 選中切換 以及滑動内容 和分發修改等。
整個類複制也可以(以下為重新修改後的完整代碼)
package com.burning.foethedog;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Camera;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.MessageQueue;
import android.support.annotation.RequiresApi;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.Animation;
import android.view.animation.Interpolator;
import android.widget.FrameLayout;
/**
* Created by burning on 2017/5/2.
* When I wrote this, only God and I understood what I was doing
* Now, God only knows
* -------------------------//┏┓ ┏┓
* -------------------------//┏┛┻━━━┛┻┓
* -------------------------//┃ ┃
* -------------------------//┃ ━ ┃
* -------------------------//┃ ┳┛ ┗┳ ┃
* -------------------------//┃ ┃
* -------------------------//┃ ┻ ┃
* -------------------------//┃ ┃
* -------------------------//┗━┓ ┏━┛
* -------------------------//┃ ┃ 神獸保佑
* -------------------------//┃ ┃ 代碼無BUG!
* -------------------------//┃ ┗━━━┓
* -------------------------//┃ ┣┓
* -------------------------//┃ ┏┛
* -------------------------//┗┓┓┏━┳┓┏┛
* -------------------------// ┃┫┫ ┃┫┫
* -------------------------// ┗┻┛ ┗┻┛
*/
public class Rota3DSwithView extends FrameLayout {
Camera mCamera;
Matrix mMaxtrix;
public Rota3DSwithView(Context context) {
super(context);
initRoat3D();
}
private void initRoat3D() {
mCamera = new Camera();
mMaxtrix = new Matrix();
setWillNotDraw(false);
}
private void initRoat3DStyle(AttributeSet attrs) {
TypedArray typedArray = getContext().obtainStyledAttributes(attrs, R.styleable.rota3DVSwithView);
rotateV = typedArray.getBoolean(R.styleable.rota3DVSwithView_rotateV, false);
autoscroll = typedArray.getBoolean(R.styleable.rota3DVSwithView_autoscroll, true);
setAutoscroll(autoscroll);
rotation = typedArray.getInt(R.styleable.rota3DVSwithView_rotation, 40);
heightRatio = typedArray.getFloat(R.styleable.rota3DVSwithView_heightRatio, 0.7f);
widthRatio = typedArray.getFloat(R.styleable.rota3DVSwithView_widthRatio, 0.7f);
}
float widthRatio = 0.7f;
float heightRatio = 0.7f;
public Rota3DSwithView(Context context, AttributeSet attrs) {
super(context, attrs);
initRoat3DStyle(attrs);
initRoat3D();
}
public Rota3DSwithView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initRoat3DStyle(attrs);
initRoat3D();
}
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public Rota3DSwithView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
initRoat3DStyle(attrs);
initRoat3D();
}
//擷取子View的寬或者高--作為旋轉和移動依據
int childHeight;
int childWith;
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
int rx = (int) ((right - left) * (1 - widthRatio) / 2);
int ry = (int) ((bottom - top) * (1 - heightRatio) / 2);
childHeight = (int) ((bottom - top) * heightRatio);
childWith = (int) ((right - left) * widthRatio);
int count = getChildCount();
for (int i = 0; i < count; i++) {
View child = getChildAt(i);
child.layout(rx, ry, right - left - rx, bottom - top - ry);
child.setClickable(true);
ViewGroup.LayoutParams layoutParams = child.getLayoutParams();
if (layoutParams.width != childWith) {
layoutParams.width = childWith;
layoutParams.height = childHeight;
child.setLayoutParams(layoutParams);
}
}
}
//錄影機 為點光源 正真的直角 反而看起來 并不是直角
int rotation = 40;// 設定外角
int moveRotation = 00;
int index = 0;
private void disDrawrX(Canvas canvas) {
int indexleft = getWidth() / 2;//中間顯示視圖 ----左邊的位置
int postTranslateX = moveRotation * childWith / 2 / rotation;//設-----定邊移動 距離
for (int i = 0; i < 4; i++)
chilDrawforCameraX(canvas, postTranslateX, indexleft, i);
}
private void disDrawrY(Canvas canvas) {
int indexleft = getHeight() / 2;//中間顯示視圖 ----左邊的位置
int postTranslateX = moveRotation * childHeight / 2 / rotation;//設-----定邊移動 距離
//定點 又稱頂點
for (int i = 0; i < 4; i++) {
chilDrawforCameraY(canvas, postTranslateX, indexleft, i);
}
}
boolean rotateV = false;
public boolean isRotateV() {
return rotateV;
}
public void setRotateV(boolean rotateV) {
this.rotateV = rotateV;
this.invalidate();
}
public int getmoveRotation() {
return moveRotation;
}
public void setmoveRotation(int moveRotation) {
this.moveRotation = moveRotation;
}
@Override
protected void dispatchDraw(Canvas canvas) {
if (getChildCount() == 0) {
return;
}
if (rotateV) {
disDrawrY(canvas);
} else {
disDrawrX(canvas);
}
}
boolean autoscroll = true;
public boolean isAutoscroll() {
return autoscroll;
}
public void setAutoscroll(boolean autoscroll) {
if (autoscroll) {
senMessageStart();
}
this.autoscroll = autoscroll;
}
private void senMessageStart() {
handler.sendEmptyMessageDelayed(2, 10);
}
private void setCameraChangeY(int translate, int roat, int i) {
switch (i) {
case 0:
//預繪制 的VIEW
mCamera.translate(0, -translate / 2, 0);
mCamera.rotateX(-roat);
mCamera.translate(0, -translate / 2, 0);
mCamera.translate(0, -translate / 2, 0);
mCamera.rotateX(-roat);
mCamera.translate(0, -translate / 2, 0);
break;
//目前位置兩側的View
case 1:
mCamera.translate(0, translate / 2, 0);
mCamera.rotateX(roat);
mCamera.translate(0, translate / 2, 0);
break;
case 2:
mCamera.translate(0, -translate / 2, 0);
mCamera.rotateX(-roat);
mCamera.translate(0, -translate / 2, 0);
break;
//最後繪制 目前顯示位置 防止 被遮擋
case 3:
mCamera.rotateX(0);
break;
}
}
private boolean isleftortop = false;
public boolean isIsleftortop() {
return isleftortop;
}
public void setIsleftortop(boolean isleftortop) {
startAnimation(isleftortop);
}
Handler handler = new Handler(Looper.getMainLooper()) {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case 2: {
if (getChildCount() == 0) {
return;
}
if (isTouch) {
return;
}
if (isleftortop)
moveRotation++;
else
moveRotation--;
if (Math.abs(moveRotation) == rotation) {
moveRotation = 0;
int position = index % getChildCount();
reSetIndex(position);
if (isleftortop) {
position = index - 1;
} else {
position = index + 1;
}
reSetIndex(position);
}
showIndex = index;
rotaViewtangle(moveRotation);
if (isAutoscroll())
senMessageStart();
}
}
}
};
private void chilDrawforCameraY(Canvas canvas, int postTranslateX, int indexleft, int i) {
canvas.save();
mCamera.save();
mMaxtrix.reset();
mCamera.translate(0, postTranslateX, 0);
mCamera.rotateX(moveRotation);
mCamera.translate(0, postTranslateX, 0);
if (postTranslateX == 0) {
if (isleftortop)
setCameraChangeY(childHeight, rotation, i);
else
setCameraChangeY(-childHeight, -rotation, i);
} else if (postTranslateX > 0) {
setCameraChangeY(childHeight, rotation, i);
} else if (postTranslateX < 0) {
setCameraChangeY(-childHeight, -rotation, i);
}
mCamera.getMatrix(mMaxtrix);
mCamera.restore();
mMaxtrix.preTranslate(-getWidth() / 2, -indexleft);//指定在 螢幕上 運作的棱 是哪一條
mMaxtrix.postTranslate(getWidth() / 2, indexleft);//運作路徑
canvas.concat(mMaxtrix);
//繪制
View childAt = getChildAt(swithView(i)/*(swithView(i) + 2 * getChildCount()) % getChildCount()*/);
if (childAt != null)
drawChild(canvas, childAt, 0);
canvas.restore();
}
private int swithView(int i) {
int k = 0;
switch (i) {
case 0:
if (isleftortop)
k = index - 2;
else
k = index + 2;
break;
case 1:
if (isleftortop)
k = index + 1;
else
k = index - 1;
break;
case 2:
if (isleftortop)
k = index - 1;
else
k = index + 1;
break;
case 3:
k = index;
break;
}
int j = k % getChildCount();
if (j >= 0) {
return j;
} else {
return (j + getChildCount());
}
// return k;
}
boolean isTouch = false;
int downXorY = 0;
public boolean dispatchTouchEvent(MotionEvent event) {
if (getChildCount() == 0) {
return super.dispatchTouchEvent(event);
}
//這裡我們就 就隻分發給目前index子View
if (!onInterceptTouchEvent(event)) {
return getChildAt(swithView(3)).dispatchTouchEvent(event);
}
return super.dispatchTouchEvent(event);
}
int thisRx = 0;
int showIndex;
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
if (getChildCount() == 0) {
return super.onInterceptTouchEvent(event);
}
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
if (isRotateV()) {
downXorY = (int) event.getY();
} else {
downXorY = (int) event.getX();
}
showIndex = index;
thisRx = moveRotation;
break;
case MotionEvent.ACTION_MOVE:
if (isRotateV()) {
if (Math.abs(event.getY() - downXorY) > 50) {
return true /*onTouchEvent(event)*/;
}
} else {
if (Math.abs(event.getX() - downXorY) > 50) {
return true /*onTouchEvent(event)*/;
}
}
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
return isTouch;
}
return super.onInterceptTouchEvent(event);
}
boolean touching_auto = false;
@SuppressLint("ClickableViewAccessibility")
@Override
public boolean onTouchEvent(MotionEvent event) {
if (getChildCount() == 0) {
return super.onTouchEvent(event);
}
switch (event.getAction()) {
case MotionEvent.ACTION_MOVE:
isTouch = true;
ontouchView(event);
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
isTouch = false;
senMessageStart();
break;
}
return true;
}
private int ontouchView(MotionEvent event) {
int movedistance = 0;
int moveRxory = 0;
if (isRotateV()) {
movedistance = -((int) event.getY() - downXorY);
moveRxory = thisRx + movedistance * rotation * 2 / (getHeight() + 100);
} else {
movedistance = (int) event.getX() - downXorY;
moveRxory = thisRx + movedistance * rotation * 2 / (getWidth() + 100);
}
isleftortop = (moveRxory > 0) ? true : false;
rotaViewtangle(moveRxory);
return movedistance;
}
private int rotaViewtangle(int moveRxory) {
int removeItem = moveRxory / rotation;
int position = showIndex - removeItem;
reSetIndex(position);
moveRotation = moveRxory % rotation;
this.invalidate();
return moveRotation;
}
private void setCameraChangeX(int translate, int roat, int i) {
switch (i) {
case 0:
//預繪制 的VIEW
mCamera.translate(-translate / 2, 0, 0);
mCamera.rotateY(-roat);
mCamera.translate(-translate / 2, 0, 0);
mCamera.translate(-translate / 2, 0, 0);
mCamera.rotateY(-roat);
mCamera.translate(-translate / 2, 0, 0);
break;
//目前位置兩側的View
case 1:
mCamera.translate(translate / 2, 0, 0);
mCamera.rotateY(roat);
mCamera.translate(translate / 2, 0, 0);
break;
case 2:
mCamera.translate(-translate / 2, 0, 0);
mCamera.rotateY(-roat);
mCamera.translate(-translate / 2, 0, 0);
break;
//最後繪制 目前顯示位置 防止 被遮擋
case 3:
mCamera.rotateY(0);
break;
}
}
private void chilDrawforCameraX(Canvas canvas, int postTranslateX, int indexleft, int i) {
canvas.save();
mCamera.save();
mMaxtrix.reset();
mCamera.translate(postTranslateX, 0, 0);
mCamera.rotateY(moveRotation);
mCamera.translate(postTranslateX, 0, 0);
if (postTranslateX == 0) {
if (isleftortop)
setCameraChangeX(childWith, rotation, i);
else
setCameraChangeX(-childWith, -rotation, i);
} else if (postTranslateX > 0) {
setCameraChangeX(childWith, rotation, i);
} else if (postTranslateX < 0) {
setCameraChangeX(-childWith, -rotation, i);
}
mCamera.getMatrix(mMaxtrix);
mCamera.restore();
mMaxtrix.preTranslate(-indexleft, -getHeight() / 2);//指定在 螢幕上 運作的棱 是哪一條
mMaxtrix.postTranslate(indexleft, getHeight() / 2);//運作路徑
canvas.concat(mMaxtrix);
//繪制
View childAt = getChildAt(swithView(i) /*( + 2 * getChildCount()) % getChildCount()*/);
if (childAt != null)
drawChild(canvas, childAt, 0);
canvas.restore();
}
public void destory() {
handler.removeCallbacksAndMessages(null);
handler = null;
}
private void reSetIndex(int position) {
index = position;
showDataPage(position);
}
@SuppressLint("ObjectAnimatorBinding")
public void returnPage() {
startAnimation(true);
}
private boolean isAnimationStarting = false;
@SuppressLint("ObjectAnimatorBinding")
public void nextPage() {
startAnimation(false);
}
@SuppressLint("ObjectAnimatorBinding")
private void startAnimation(boolean rightortop) {
if (isAnimationStarting) {
return;
}
isAnimationStarting = true;
isleftortop = rightortop;
showIndex = index;
Interpolator interpolator = new AccelerateInterpolator();
ObjectAnimator mAnimator = ObjectAnimator.ofInt(this, "xxxxx", moveRotation, rightortop ? rotation : -rotation);
mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
int animatedValue = (int) animation.getAnimatedValue();
rotaViewtangle(animatedValue);
}
});
mAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
isAnimationStarting = false;
}
});
mAnimator.setInterpolator(interpolator);
mAnimator.setDuration(120);
mAnimator.start();
}
int showPage = 0;
private void showDataPage(int position) {
int i = position % getChildCount();
int mathpage = 0;
if (i >= 0) {
mathpage = i;
} else {
mathpage = i + getChildCount();
}
if (mathpage != showPage) {
showPage = mathpage;
if (r3DPagechange != null) {
r3DPagechange.onPageChanged(showPage);
}
}
}
R3DPagechange r3DPagechange;
public void setR3DPagechange(R3DPagechange r3DPagechange) {
this.r3DPagechange = r3DPagechange;
}
public interface R3DPagechange {
void onPageChanged(int position);
}
}
*為下載下傳了之前資源的說聲抱歉-之前那個并不适用直接使用僅作為一個半成品
github下載下傳位址:https://github.com/liuyangxiao/3DViewGroup
如有疑問可私信交流^_^