Android-类似soul星球的立体球效果自定义View

已经好久没用过简书了,一是之前工作太忙,没什么时间来写新文章,二是感觉也没什么太多可写的内容。

这次就来更新一章,以前就想自己打造的轮子,一个类似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);
    }
}

完整代码链接Globe3D

效果图

screen.gif
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,919评论 6 502
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,567评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,316评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,294评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,318评论 6 390
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,245评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,120评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,964评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,376评论 1 313
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,592评论 2 333
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,764评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,460评论 5 344
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,070评论 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,697评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,846评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,819评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,665评论 2 354

推荐阅读更多精彩内容