这里我没有用3D动画库来做,是自己使用公式计算的坐标点。
首先我们看一下效果,拆分一下需求(没有PS,以后会更新,不好意思)功能点:
1.画圆
2.绕着球体旋转
3.斜向旋转
4.循环动画
5.如果有遮挡,应该是上面的圆遮挡住在下面的圆
既然要做3D效果,那么首先肯定得有一个上帝视角,这里定义EyeZHeight = 800 //上帝视角坐标点(0,0,800)
其实在画三维空间的物体时,都是将该物体投影到xOy平面上,这个其实就是最简单的等比例缩放。要是这个还不会,自行百度吧
难点在于如何求得三维平面上的点,这里我为了方面判断z轴的正负,使用了偏转角。我使用的所有点都是基于x轴进行偏移的。
举个例子:
以点D(10,0,0)为例,做一个经过点D且平行于yOz的切面,从X+ -> X-方向看,以z轴为其实线,偏转角为a,将会跟球体相交于F点,这个F点就是我们要求的三维坐标点。
分析可知:
假设球体半径为R
则F(x, Rsin(a), Rcos(a))
至于为什么要以z轴为起始点呢,观察坐标点的y轴变化轨迹和z轴的变化轨迹可知,y轴的变化轨迹正好与sin三角函数相同,z轴的变化轨迹正好与cos三角函数相同。
这里我给出我使用的Circle类字段:
上图中,我在构造函数中已经将y,z轴计算好,代码很简单,理解一下就可以。
然后是三个获取投影值方法
最后再加一个动态设置偏转角a的方法,记住在设置偏转角之后,还必须重新计算一次y和z的值。
这个自定义view的难点就在于计算三维的坐标点,现在都已经解决了。
哦对了,还有最后一个方法
直接在使用的地方写如下代码即可
不多说了,写太长也没人看,我只给了大致思路,希望大家自己动手实践一下,毕竟自己动手丰衣足食嘛,当然完整代码我还是会放一波的!——__——!
Circle.java
package com.ds.a3ddemo;
import android.graphics.Paint;
import android.support.annotation.NonNull;
public class Circle implements Comparable{
private static float PI = (float) 3.141592654;
private float x;//变换前的圆心x轴坐标
private float rad;//旋转角度,在y,z轴平面内 [0,2*PI)
private float radius;//圆半径
private float R; //圆球半径
private float y;
private float z;
private Paint paint;
public Circle(float x, float radius, float rad/*传进来的是角度,范围在[0,360)之间*/, float R, Paint paint) {
this.x = x;
this.rad = (rad*2*PI)/360;
this.radius = radius;
this.R = R;
this.paint = paint;
this.y = (float) (Math.sqrt(R*R - x*x)*Math.sin(this.rad));
this.z = (float) (Math.sqrt(R*R - x*x)*Math.cos(this.rad));
}
/**
* 获取到映射点转换后的x坐标(绘制)
* @return
*/
public float getTransX(float eyeZHeight) {
if (z > 0) {
return x/((1-(z/eyeZHeight)));
}else if (z < 0) {
return x*(eyeZHeight/(eyeZHeight - z));
} else return x;
}
/**
* 获取到映射点转换后的y坐标(绘制)
* @return
*/
public float getTransY(float eyeZHeight) {
if (z > 0) {
return y/((1-(z/eyeZHeight)));
}else if (z < 0) {
return y*(eyeZHeight/(eyeZHeight - z));
} else return y;
}
/**
* 获取到转换后的圆半径
* @return
*/
public float getTransRadius(float eyeZHeight) {
if (z > 0) {
return radius/((1-(z/eyeZHeight)));
}else if (z < 0) {
return radius/((1-(z/eyeZHeight)));
} else return radius;
}
public float getX() {
return x;
}
public void setX(float x) {
this.x = x;
}
public float getY() {
return y;
}
public void setY(float y) {
this.y = y;
}
public float getRadius() {
return radius;
}
public void setRadius(float radius) {
this.radius = radius;
}
public float getZ() {
return z;
}
public void setZ(float z) {
this.z = z;
}
public float getRad() {
return rad;
}
public Paint getPaint() {
return paint;
}
public void setPaint(Paint paint) {
this.paint = paint;
}
public void setRad(float rad) {
this.rad = (rad*2*PI)/360;
this.y = (float) (Math.sqrt(R*R - x*x)*Math.sin(this.rad));
this.z = (float) (Math.sqrt(R*R - x*x)*Math.cos(this.rad));
}
@Override
public int compareTo(@NonNull Object o) {
Circle that = (Circle)o;
return (int) (this.getZ() - that.getZ()); // this>that 则升序
}
}
custom3D.java
package com.ds.a3ddemo;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.view.animation.LinearInterpolator;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class custom3D extends View {
private String TAG = "custom3DInfo";
private static float PI = (float) 3.141592654;
private float EyeZHeight = 800;//视野高度,视野的x,y轴均为0
private Center center;
private ValueAnimator valueAnimator = null;
private float xx = -150;
private List<Circle> circleList = new ArrayList<>();
private List<Paint> paintList = new ArrayList<>();
private List<String> colorList = new ArrayList<>();
public custom3D(Context context) {
this(context, null);
}
public custom3D(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public custom3D(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
public void init() {
colorList.add("#EEEE00");
colorList.add("#EE7621");
colorList.add("#E066FF");
colorList.add("#CD5555");
colorList.add("#C6E2FF");
colorList.add("#9AFF9A");
for (int i=0;i<6;i++) {
Paint p = new Paint();
p.setAntiAlias(true);
p.setStyle(Paint.Style.FILL_AND_STROKE);
p.setDither(true);
p.setColor(Color.parseColor(colorList.get(i)));
paintList.add(p);
}
initData();
}
public void initData() {
Circle circle_1 = new Circle(-150, 30, 30, 300, paintList.get(0));
Circle circle_2 = new Circle(-230, 50, 90, 300, paintList.get(1));
Circle circle_3 = new Circle(0, 31, 30, 300, paintList.get(2));
Circle circle_4 = new Circle(100, 25, 240, 300, paintList.get(3));
Circle circle_5 = new Circle(50, 40, 150, 300, paintList.get(4));
Circle circle_6 = new Circle(150, 38, 330, 300, paintList.get(5));
circleList = new ArrayList<>();
circleList.add(circle_1);
circleList.add(circle_2);
circleList.add(circle_3);
circleList.add(circle_4);
circleList.add(circle_5);
circleList.add(circle_6);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Collections.sort(circleList);//排序,将z总小到大排列,即先画下面的,再画上面的,否会上面的圆有可能会被下面的遮挡
canvas.rotate(45, center.getX(), center.getY());
for (int i=0;i<circleList.size();i++) {
toDraw(canvas, circleList.get(i));
}
}
private void toDraw(Canvas canvas, Circle circle) {
Log.i(TAG, "x = " + circle.getTransX(EyeZHeight) + " y = " + circle.getTransY(EyeZHeight) + " radius = " + circle.getTransRadius(EyeZHeight));
canvas.drawCircle(center.getX() + circle.getTransX(EyeZHeight),
center.getY() + circle.getTransY(EyeZHeight),
circle.getTransRadius(EyeZHeight),
circle.getPaint());
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = MeasureSpec.getSize(heightMeasureSpec);
center = new Center();
center.setX((float) (width / 2.0));
center.setY((float) (height/2.0));
setMeasuredDimension(getMeasuredWidth(), getMeasuredHeight());
}
@SuppressLint("WrongConstant")
public void start() {
if(valueAnimator==null){
valueAnimator = ValueAnimator.ofFloat(0, 36000f);
valueAnimator.setDuration(4000);
valueAnimator.setRepeatCount(ValueAnimator.INFINITE);//无限循环
valueAnimator.setRepeatMode(ValueAnimator.INFINITE);//
valueAnimator.setInterpolator(new LinearInterpolator());
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
float value = (float) valueAnimator.getAnimatedValue();
//Log.i(TAG, "valueAnimator.value = " + value);
initData();
for (int i=0;i<circleList.size();i++) {
circleList.get(i).setRad(circleList.get(i).getRad()*360/(2*PI )+ value/100);
}
postInvalidate();
}
});
valueAnimator.start();
}
}
public class Center {
private float X;
private float Y;
public float getX() {
return X;
}
public void setX(float x) {
X = x;
}
public float getY() {
return Y;
}
public void setY(float y) {
Y = y;
}
}
}