已经好久没用过简书了,一是之前工作太忙,没什么时间来写新文章,二是感觉也没什么太多可写的内容。
这次就来更新一章,以前就想自己打造的轮子,一个类似soul app的球体自定义View。
先说下思路
1.球体上如果附着很多子view肯定会有近大远小的效果,那么我们第一件事就是要模拟出z轴效果。怎么模拟呢?就需要一个视角原点,类似摄像头或人眼的效果,设置个最远可视距离,那个地方view就是最小的,这样就可以模拟出z轴的效果了。
2.因为要做成球体的效果,所以要去看下高中学的球体坐标相关知识,知道是如何把三维坐标和球坐标相互转化的。
3.最后球体效果实现后,就需要实现触摸view让球体进行绕轴旋转和双指缩放功能。绕轴旋转这里我偷了下懒,直接借助了下AI给我提供的方法,让我可以得到绕轴旋转后的新坐标。
下面贴下代码和效果图
public class GlobeParentView extends ViewGroup {
private int measuredWidth = 0;
private int measuredHeight = 0;
private double maxEyeDistance;//最远可视距离
private double eyeX, eyeY, eyeZ;//视角原点坐标(默认控件中心位置)
private double globeCenterX, globeCenterY, globeCenterZ;//球心坐标
private double globeRadius;//球半径
private double globeToRootGap = 50;//球到边缘间隔(默认50)
@Override
public String toString() {
return "GlobeView{" +
"measuredWidth=" + measuredWidth +
", measuredHeight=" + measuredHeight +
", maxEyeDistance=" + maxEyeDistance +
", eyeX=" + eyeX +
", eyeY=" + eyeY +
", eyeZ=" + eyeZ +
", globeCenterX=" + globeCenterX +
", globeCenterY=" + globeCenterY +
", globeCenterZ=" + globeCenterZ +
", globeRadius=" + globeRadius +
", globeToRootGap=" + globeToRootGap +
'}';
}
public GlobeParentView(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
measuredWidth = getMeasuredWidth();
measuredHeight = getMeasuredHeight();
if (globeRadius == 0) {
globeRadius = Math.min(measuredWidth, measuredHeight) / 2 - globeToRootGap;
}
maxEyeDistance = globeRadius * 2;
eyeX = measuredWidth / 2;
eyeY = measuredHeight / 2;
eyeZ = 0;
globeCenterX = eyeX;
globeCenterY = eyeY;
globeCenterZ = globeRadius;
measureChildren(widthMeasureSpec, heightMeasureSpec);
Logger.e(toString());
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
for (int i = 0; i < getChildCount(); i++) {
View childView = getChildAt(i);
int childViewMeasuredWidth = childView.getMeasuredWidth();
int childViewMeasuredHeight = childView.getMeasuredHeight();
if (childView instanceof GlobeChildView) {
GlobeChildView globeChildView = (GlobeChildView) childView;
//计算视角中心点到子View中心点距离,来决定View大小
double d = GlobeUtil.calculateTwoPointDistance(eyeX, eyeY, eyeZ, globeChildView.x, globeChildView.y, globeChildView.z);
//由maxEyeDistance的值,决定近大远小效果。默认面最大,越往里越小,且越透明
float scaleVlue = Math.max((float) (1.0f - d / maxEyeDistance), 0) * 0.5f + 0.5f;//缩放范围在0.5-1之间
float alphaVlue = Math.max((float) (1.0f - d / maxEyeDistance), 0) * 0.2f + 0.8f;//透明度范围在0.8-1之间
Logger.e("scaleVlue=" + scaleVlue + " d=" + d);
globeChildView.setScaleX(scaleVlue);
globeChildView.setScaleY(scaleVlue);
globeChildView.setAlpha(alphaVlue);
int left = (int) (globeChildView.x - childViewMeasuredWidth / 2);
int top = (int) (globeChildView.y - childViewMeasuredHeight / 2);
int right = left + childViewMeasuredWidth;
int bottom = top + childViewMeasuredHeight;
globeChildView.layout(left, top, right, bottom);
} else {
Logger.e("检测的子View(" + childView + ")非Custome3dChildView类型,请使用Custome3dChildView作为子View");
}
}
}
private Handler mHandler = new Handler();
private int mStartX = 100, mStartY = 0, mEndX = 0, mEndY = 100;
public void recoveryAnimal() {
startAnimal(mStartX, mStartY, mEndX, mEndY);
}
@SuppressLint("NewApi")
public void startAnimal(final int startX, final int startY, final int endX, final int endY) {
mHandler.removeCallbacksAndMessages(null);
Logger.e(startX + " " + startY + "-" + endX + " " + endY);
mStartX = startX;
mStartY = startY;
mEndX = endX;
mEndY = endY;
if (startX == endX && startY == endY) {
mStartX = 100;
mStartY = 0;
mEndX = 0;
mEndY = 100;
}
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
rotateAxis(startX, startY, endX, endY, -1);
startAnimal(startX, startY, endX, endY);
}
}, 100);
}
@RequiresApi(api = Build.VERSION_CODES.N)
private void rotateAxis(int startX, int startY, int endX, int endY, int angle) {
if (startX == endX && startY == endY) {
return;
}
List<GlobeChildView> globeChildViewList = new ArrayList<>();
for (int i = 0; i < getChildCount(); i++) {
View childAt = getChildAt(i);
if (childAt instanceof GlobeChildView) {
globeChildViewList.add((GlobeChildView) childAt);
}
}
// removeAllViews();
globeChildViewList.sort(new Comparator<GlobeChildView>() {
@Override
public int compare(GlobeChildView o1, GlobeChildView o2) {
return (int) (o2.z - o1.z);
}
});
for (int i = 0; i < globeChildViewList.size(); i++) {
GlobeChildView globeChildView = globeChildViewList.get(i);
//更新绕轴坐标
Logger.e(angle + " " + startX + "-" + startY + ":" + endX + "-" + endY);
globeChildView.updateRotateAxisNewPoint(angle, startX, startY, endX, endY);
//绕轴旋转end
// addView(globeChildView);//频繁重新添加可能导致子view点击事件不触发
}
requestLayout();
}
public void stopAnimal() {
mHandler.removeCallbacksAndMessages(null);
}
@SuppressLint("NewApi")
private void scaleGlobe(double scaleVlue) {
globeRadius = Math.min(Math.min(measuredWidth, measuredHeight) / 2 - globeToRootGap, globeRadius + scaleVlue * 0.5);
globeRadius = Math.max(globeRadius, Math.min(measuredWidth, measuredHeight) / 4);
Logger.e("globeRadius=" + globeRadius + " " + scaleVlue);
requestLayout();
updateZAllView();
}
@RequiresApi(api = Build.VERSION_CODES.N)
public void updateZAllView() {//按z高度重新添加view
Logger.e("updateZAllView");
List<GlobeChildView> globeChildViewList = new ArrayList<>();
for (int i = 0; i < getChildCount(); i++) {
View childAt = getChildAt(i);
if (childAt instanceof GlobeChildView) {
globeChildViewList.add((GlobeChildView) childAt);
}
}
removeAllViews();
globeChildViewList.sort(new Comparator<GlobeChildView>() {
@Override
public int compare(GlobeChildView o1, GlobeChildView o2) {
return (int) (o2.z - o1.z);
}
});
for (int i = 0; i < globeChildViewList.size(); i++) {
GlobeChildView globeChildView = globeChildViewList.get(i);
addView(globeChildView);
}
}
List<Point> pointList = new ArrayList<>();
private float dispatchTouchEventDownX, dispatchTouchEventDownY;
private boolean isInterceptTouchEvent = false;
@RequiresApi(api = Build.VERSION_CODES.N)
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
Logger.e(event.toString());
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
dispatchTouchEventDownX = event.getX();
dispatchTouchEventDownY = event.getY();
isInterceptTouchEvent = false;
break;
case MotionEvent.ACTION_MOVE:
if (Math.abs(event.getX() - dispatchTouchEventDownX) > 2 || Math.abs(event.getY() - dispatchTouchEventDownY) > 2) {
isInterceptTouchEvent = true;//如果是滑动事件进行拦截,交给父view处理
}
break;
case MotionEvent.ACTION_UP:
break;
}
return super.dispatchTouchEvent(event);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
Logger.e(isInterceptTouchEvent + " " + ev.toString());
return super.onInterceptTouchEvent(ev) || isInterceptTouchEvent;
}
private float pointerX1, pointerY1, pointerX2, pointerY2;
private double pointDistance;
@RequiresApi(api = Build.VERSION_CODES.N)
@Override
public boolean onTouchEvent(MotionEvent event) {
int pointerCount = event.getPointerCount();//触控数量
Logger.e(pointerCount + " Action=" + event.getActionMasked() + " " + event.toString());
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_POINTER_DOWN:
if (pointerCount == 1) {
pointList.clear();
updateZAllView();
stopAnimal();
pointList.add(new Point((int) event.getRawX(), (int) event.getRawY()));
} else if (pointerCount == 2) {
pointerX1 = event.getX(0);
pointerY1 = event.getY(0);
pointerX2 = event.getX(1);
pointerY2 = event.getY(1);
pointDistance = Math.sqrt(Math.pow(pointerX1 - pointerX2, 2) + Math.pow(pointerY1 - pointerY2, 2));
Logger.e(pointerCount + " pointDistance=" + pointDistance);
}
return true;//如果未被子view消费或事件不经过子view,则自己处理
case MotionEvent.ACTION_MOVE:
if (pointerCount == 1) {
stopAnimal();
if (pointList.size() >= 2) {
pointList.remove(0);
}
pointList.add(new Point((int) event.getRawX(), (int) event.getRawY()));
if (pointList.size() >= 2) {//防止子View拦截了down事件,之后父View又把事件拦截交给父View处理,导致pointList只有1
rotateAxis(pointList.get(0).x, pointList.get(0).y, pointList.get(1).x, pointList.get(1).y, -3);
} else {
Logger.e("pointList.size 过短");
}
} else if (pointerCount == 2) {
pointerX1 = event.getX(0);
pointerY1 = event.getY(0);
pointerX2 = event.getX(1);
pointerY2 = event.getY(1);
Logger.e(pointerX1 + "-" + pointerY1 + " " + pointerX2 + "-" + pointerY2);
scaleGlobe(Math.sqrt(Math.pow(pointerX1 - pointerX2, 2) + Math.pow(pointerY1 - pointerY2, 2)) - pointDistance);
}
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_POINTER_UP:
if (pointerCount == 1) {
if (pointList.size() >= 2) {
startAnimal(pointList.get(0).x, pointList.get(0).y, pointList.get(1).x, pointList.get(1).y);
} else {
recoveryAnimal();
}
pointList.clear();
}
break;
}
return super.onTouchEvent(event);
}
public double getMaxEyeDistance() {
return maxEyeDistance;
}
public double getEyeX() {
return eyeX;
}
public double getEyeY() {
return eyeY;
}
public double getEyeZ() {
return eyeZ;
}
public double getGlobeCenterX() {
return globeCenterX;
}
public double getGlobeCenterY() {
return globeCenterY;
}
public double getGlobeCenterZ() {
return globeCenterZ;
}
public double getGlobeRadius() {
return globeRadius;
}
public double getGlobeToRootGap() {
return globeToRootGap;
}
}
public class GlobeChildView extends ConstraintLayout {
public double x, y, z;//空间坐标
public double angleA, angleB, globeRadius;//球坐标 方位角,仰角,球半径
public double globeCenterX, globeCenterY, globeCenterZ;//球心坐标
@Override
public String toString() {
return "ChildGlobeView{" +
"x=" + x +
", y=" + y +
", z=" + z +
", angleA=" + angleA +
", angleB=" + angleB +
", globeRadius=" + globeRadius +
", globeCenterX=" + globeCenterX +
", globeCenterY=" + globeCenterY +
", globeCenterZ=" + globeCenterZ +
'}';
}
public GlobeChildView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context, attrs);
}
public void init(Context context, @Nullable AttributeSet attrs) {
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.GlobeChildView);
angleA = typedArray.getFloat(R.styleable.GlobeChildView_globeChildView_angleA, 0);
angleB = typedArray.getFloat(R.styleable.GlobeChildView_globeChildView_angleB, 0);
typedArray.recycle();
Logger.e(toString());
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
ViewParent parent = getParent();
if (parent instanceof GlobeParentView) {
globeCenterX = ((GlobeParentView) parent).getGlobeCenterX();
globeCenterY = ((GlobeParentView) parent).getGlobeCenterY();
globeCenterZ = ((GlobeParentView) parent).getGlobeCenterZ();
globeRadius = ((GlobeParentView) parent).getGlobeRadius();
}
setAngleABR(angleA,angleB);//更新空间坐标
Logger.e(toString());
}
public void setXYZ(double x, double y, double z) {
this.x = x;
this.y = y;
this.z = z;
double[] abr = GlobeUtil.convertRoomToGlobePoint(globeCenterX, globeCenterY, globeCenterZ, x, y, z);
this.angleA = abr[0];
this.angleB = abr[1];
this.globeRadius = abr[2];
}
/**
*
* @param angleA 方位角
* @param angleB 仰角
*/
public void setAngleABR(double angleA, double angleB) {
this.angleA = angleA;
this.angleB = angleB;
double[] xyz = GlobeUtil.convertGlobeToRoomPoint(globeCenterX, globeCenterY, globeCenterZ, angleA, angleB, globeRadius);
this.x = xyz[0];
this.y = xyz[1];
this.z = xyz[2];
}
public void updateRotateAxisNewPoint(int angle, int startX, int startY, int endX, int endY) {
double[] calculateRotateAxisNewPoint2 = GlobeUtil.calculateRotateAxisNewPoint2(angle, x, y, z, globeCenterX, globeCenterY, globeCenterZ, startX, startY, 0, endX, endY, 0);
setXYZ(calculateRotateAxisNewPoint2[0], calculateRotateAxisNewPoint2[1], calculateRotateAxisNewPoint2[2]);
}
}
public class GlobeUtil {
/**
* 计算两个点之间的距离
*
* @param x1 第一个点的x坐标
* @param y1 第一个点的y坐标
* @param z1 第一个点的z坐标
* @param x2 第二个点的x坐标
* @param y2 第二个点的y坐标
* @param z2 第二个点的z坐标
* @return 两点之间的距离
*/
public static double calculateTwoPointDistance(double x1, double y1, double z1, double x2, double y2, double z2) {
double dx = x2 - x1;
double dy = y2 - y1;
double dz = z2 - z1;
return Math.sqrt(dx * dx + dy * dy + dz * dz);
}
/**
* 空间坐标转球坐标
*
* @param globeCenterX球心
* @param globeCenterY
* @param globeCenterZ
* @param x球面坐标
* @param y
* @param z
* @return 球坐标 方位角a 仰角b 半径R
*/
public static double[] convertRoomToGlobePoint(double globeCenterX, double globeCenterY, double globeCenterZ, double x, double y, double z) {
double dx = x - globeCenterX;
double dy = y - globeCenterY;
double dz = z - globeCenterZ;
// 计算半径
double R = Math.sqrt(dx * dx + dy * dy + dz * dz);
// 计算仰角
double b = Math.acos(dz / R);
// 计算方位角
double a = Math.atan2(dy, dx);
// 将仰角和方位角从弧度转换为角度
b = Math.toDegrees(b);
a = Math.toDegrees(a);
return new double[]{a, b, R};
}
/**
* 球坐标转空间坐标
*
* @param globeCenterX 球心X坐标
* @param globeCenterY 球心Y坐标
* @param globeCenterZ 球心Z坐标
* @param a 方位角a
* @param b 仰角b
* @param R 球半径
* @return 返回一个包含x, y, z坐标的float数组
*/
public static double[] convertGlobeToRoomPoint(double globeCenterX, double globeCenterY, double globeCenterZ, double a, double b, double R) {
// 将角度转换为弧度
double alpha = a * Math.PI / 180;
double beta = b * Math.PI / 180;
// 计算球面坐标
double x = globeCenterX + R * Math.sin(beta) * Math.cos(alpha);
double y = globeCenterY + R * Math.sin(beta) * Math.sin(alpha);
double z = globeCenterZ + R * Math.cos(beta);
// 返回结果
return new double[]{(float) x, (float) y, (float) z};
}
/**
* 计算绕轴新坐标
*
* @param angle
* @param x 球面坐标
* @param y
* @param z
* @param globeCenterX 球心坐标
* @param globeCenterY
* @param globeCenterZ
* @param x1 绕轴的两点坐标
* @param y1
* @param z1
* @param x2
* @param y2
* @param z2
* @return 返回绕轴新坐标 x y z
*/
public static double[] calculateRotateAxisNewPoint(double angle, double x, double y, double z, double globeCenterX, double globeCenterY, double globeCenterZ, double x1, double y1, double z1, double x2, double y2, double z2) {
// 计算旋转轴
double[] axis = {x2 - x1, y2 - y1, z2 - z1};
double axisLength = Math.sqrt(axis[0] * axis[0] + axis[1] * axis[1] + axis[2] * axis[2]);
axis[0] /= axisLength;
axis[1] /= axisLength;
axis[2] /= axisLength;
// 将角度转换为弧度
double angleRadian = Math.toRadians(angle);
// 计算旋转矩阵
double[][] rotationMatrix = calculateRotationMatrix(axis, angleRadian);
// 应用旋转矩阵
double[] originalPoint = {x - globeCenterX, y - globeCenterY, z - globeCenterZ};
double[] rotatedPoint = multiplyMatrixAndPoint(rotationMatrix, originalPoint);
return new double[]{rotatedPoint[0] + globeCenterX, rotatedPoint[1] + globeCenterY, rotatedPoint[2] + globeCenterZ};
}
/**
*
* @param angle 旋转角度
* @param x 老坐标
* @param y
* @param z
* @param globeCenterX 球心
* @param globeCenterY
* @param globeCenterZ
* @param startX 滑动起点
* @param startY
* @param startZ
* @param endX 滑动终点
* @param endY
* @param endZ
* @return 返回绕轴新坐标x y z
*/
public static double[] calculateRotateAxisNewPoint2(double angle, double x, double y, double z, double globeCenterX, double globeCenterY, double globeCenterZ, double startX, double startY, double startZ, double endX, double endY, double endZ) {
// 计算滑动方向向量
double[] direction = {endX - startX, endY - startY, endZ - startZ};
double directionLength = Math.sqrt(direction[0] * direction[0] + direction[1] * direction[1] + direction[2] * direction[2]);
direction[0] /= directionLength;
direction[1] /= directionLength;
direction[2] /= directionLength;
// 计算垂直于滑动方向的旋转轴
double[] axis = {-direction[1], direction[0], 0};
double axisLength = Math.sqrt(axis[0] * axis[0] + axis[1] * axis[1] + axis[2] * axis[2]);
axis[0] /= axisLength;
axis[1] /= axisLength;
axis[2] /= axisLength;
// 将角度转换为弧度
double angleRadian = Math.toRadians(angle);
// 计算旋转矩阵
double[][] rotationMatrix = calculateRotationMatrix(axis, angleRadian);
// 应用旋转矩阵
double[] originalPoint = {x - globeCenterX, y - globeCenterY, z - globeCenterZ};
double[] rotatedPoint = multiplyMatrixAndPoint(rotationMatrix, originalPoint);
return new double[]{rotatedPoint[0] + globeCenterX, rotatedPoint[1] + globeCenterY, rotatedPoint[2] + globeCenterZ};
}
private static double[][] calculateRotationMatrix(double[] axis, double angle) {
double c = Math.cos(angle);
double s = Math.sin(angle);
double t = 1 - c;
double x = axis[0];
double y = axis[1];
double z = axis[2];
double[][] rotationMatrix = new double[3][3];
rotationMatrix[0][0] = c + x * x * t;
rotationMatrix[0][1] = x * y * t - z * s;
rotationMatrix[0][2] = x * z * t + y * s;
rotationMatrix[1][0] = y * x * t + z * s;
rotationMatrix[1][1] = c + y * y * t;
rotationMatrix[1][2] = y * z * t - x * s;
rotationMatrix[2][0] = z * x * t - y * s;
rotationMatrix[2][1] = z * y * t + x * s;
rotationMatrix[2][2] = c + z * z * t;
return rotationMatrix;
}
private static double[] multiplyMatrixAndPoint(double[][] matrix, double[] point) {
double[] result = new double[3];
result[0] = matrix[0][0] * point[0] + matrix[0][1] * point[1] + matrix[0][2] * point[2];
result[1] = matrix[1][0] * point[0] + matrix[1][1] * point[1] + matrix[1][2] * point[2];
result[2] = matrix[2][0] * point[0] + matrix[2][1] * point[1] + matrix[2][2] * point[2];
return result;
}
}
public class MainActivity extends AppCompatActivity {
TextView textView;
GlobeChildView globeChildView;
private GlobeParentView globeParentView;
private int count = 0;
@RequiresApi(api = Build.VERSION_CODES.N)
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
for (int a = 0; a < 6; a++) {
for (int b = 0; b < 12; b++) {
count++;
GlobeChildView globeChildView = new GlobeChildView(this, null);
globeChildView.setBackgroundColor(Color.parseColor("#00ff00"));
globeChildView.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
final TextView textView = new TextView(this, null);
textView.setText(count + "");
textView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Logger.e("onClick " + textView.getText().toString());
Toast.makeText(MainActivity.this, textView.getText().toString(), Toast.LENGTH_SHORT).show();
}
});
globeChildView.addView(textView);
globeChildView.setAngleABR(a * 30 % 180, b * 30 % 360);//方位角,仰角
if (globeChildView.angleB == 0 || globeChildView.angleB == 180) {
continue;
}
globeParentView.addView(globeChildView);
}
}
count++;
globeChildView = new GlobeChildView(this, null);
globeChildView.setBackgroundColor(Color.parseColor("#00ff00"));
globeChildView.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
textView = new TextView(this, null);
textView.setText(count + "");
textView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Logger.e("onClick " + ((TextView) v).getText().toString());
Toast.makeText(MainActivity.this, ((TextView) v).getText().toString(), Toast.LENGTH_SHORT).show();
}
});
globeChildView.addView(textView);
globeChildView.angleA = 0;//方位角
globeChildView.angleB = 0;//仰角
globeParentView.addView(globeChildView);
count++;
globeChildView = new GlobeChildView(this, null);
globeChildView.setBackgroundColor(Color.parseColor("#00ff00"));
globeChildView.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
textView = new TextView(this, null);
textView.setText(count + "");
textView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Logger.e("onClick " + ((TextView) v).getText().toString());
Toast.makeText(MainActivity.this, ((TextView) v).getText().toString(), Toast.LENGTH_SHORT).show();
}
});
globeChildView.addView(textView);
globeChildView.angleA = 0;//方位角
globeChildView.angleB = 180;//仰角
globeParentView.addView(globeChildView);
globeParentView.post(new Runnable() {
@Override
public void run() {
globeParentView.updateZAllView();//这里延迟是因为Z轴高度还没设置
globeParentView.recoveryAnimal();
}
});
}
private void initView() {
globeParentView = (GlobeParentView) findViewById(R.id.globeParentView);
}
}
效果图