说明:之前在网上到处搜寻类似的旋转效果 但搜到的结果都不是十分满意 原因不多追述
( 由于之前只是交流学习使用 现在已经改进为能用状态 )
(最新去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
如有疑问可私信交流^_^