Android实现SoulApp星球效果

作者: 我控几不住丶 | 来源:发表于2019-05-20 23:31 被阅读24次

    SoulApp的星球看起来太炫酷了!!!
    啥也不说先上图:

    soul planet.gif

    Github传送门 biu biu biu ~~~

    基础知识

    首先得拿出我们的数学知识:

    1. 球坐标系(r,θ,φ)与直角坐标系(x,y,z)的转换关系:
    x = rsinθcosφ.
    y = rsinθsinφ.
    z = rcosθ.
    

    Q:What!为什么要整个球坐标系啊?
    A:因为星球嘛,位置信息当然球坐标系更加简单额。

    实现思路

    核心算法来自:3dTagCloudAndroid
    在此基础上做了些修改后的效果

    1. 获取均匀分布点坐标:
    for (int i = 1; i < count + 1; i++) {
        // 平均(三维直角得Z轴等分[-1,1]) θ范围[-π/2,π/2])
        phi = Math.acos(-1.0 + (2.0 * i - 1.0) / count);
        theta = Math.sqrt(count * Math.PI) * phi;
    }
    
    2. 三维坐标
    planetModelCloud.get(i - 1).setLocX((float) (radius * Math.cos(theta) * Math.sin(phi)));
    planetModelCloud.get(i - 1).setLocY((float) (radius * Math.sin(theta) * Math.sin(phi)));
    planetModelCloud.get(i - 1).setLocZ((float) (radius * Math.cos(phi)));
    
    3. 坐标旋转

    三维空间中的旋转变换比二维空间中的旋转变换复杂。除了须要指定旋转角外,还需指定旋转轴。
    若以坐标系的三个坐标轴x,y,z分别作为旋转轴,则点实际上仅仅在垂直坐标轴的平面上作二维旋转。此时用二维旋转公式就能够直接推出三维旋转变换矩阵。
    规定在右手坐标系中,物体旋转的正方向是右手螺旋方向,即从该轴正半轴向原点看是逆时针方向。

    绕X轴

    angle-X.jpg

    绕Y轴

    angle-Y.jpg

    绕Z轴

    angle-Z.jpg
    /**
     * 返回角度转换成弧度之后各方向的值
     * <p>
     * 1度=π/180
     *
     * @param mAngleX x方向旋转距离
     * @param mAngleY y方向旋转距离
     * @param mAngleZ z方向旋转距离
     */
    private void sineCosine(float mAngleX, float mAngleY, float mAngleZ) {
        double degToRad = (Math.PI / 180);
        sinAngleX = (float) Math.sin(mAngleX * degToRad);
        cosAngleX = (float) Math.cos(mAngleX * degToRad);
        sinAngleY = (float) Math.sin(mAngleY * degToRad);
        cosAngleY = (float) Math.cos(mAngleY * degToRad);
        sinAngleZ = (float) Math.sin(mAngleZ * degToRad);
        cosAngleZ = (float) Math.cos(mAngleZ * degToRad);
    }
    

    利用上面的矩阵计算旋转后的三维直角坐标:

    // 绕x轴旋转
    float rx1 = (planetModel.getLocX());
    float ry1 = (planetModel.getLocY()) * cosAngleX + planetModel.getLocZ() * -sinAngleX;
    float rz1 = (planetModel.getLocY()) * sinAngleX + planetModel.getLocZ() * cosAngleX;
    // 绕y轴旋转
    float rx2 = rx1 * cosAngleY + rz1 * sinAngleY;
    float ry2 = ry1;
    float rz2 = rx1 * -sinAngleY + rz1 * cosAngleY;
    // 绕z轴旋转
    float rx3 = rx2 * cosAngleZ + ry2 * -sinAngleZ;
    float ry3 = rx2 * sinAngleZ + ry2 * cosAngleZ;
    float rz3 = rz2;
    // 将数组设置为新位置
    planetModel.setLocX(rx3);
    planetModel.setLocY(ry3);
    planetModel.setLocZ(rz3);
    
    4. 计算二维坐标(为了让二维面看的更均匀,per可以始终设置为1)
    // 添加透视图
    int diameter = 2 * radius;
    float per = diameter / (diameter + rz3);
    // 让我们为标签设置位置、比例和透明度
    planetModel.setLoc2DX(rx3 * per);
    planetModel.setLoc2DY(ry3 * per);
    planetModel.setScale(per);
    
    5. 触摸事件处理
    • 单指移动旋转(根据手指移动位移计算出旋转距离)
    // 单点触摸,旋转星球
    float dx = event.getX() - downX;
    float dy = event.getY() - downY;
    if (isValidMove(dx, dy)) {
        mAngleX = (dy / radius) * speed * TOUCH_SCALE_FACTOR;
        mAngleY = (-dx / radius) * speed * TOUCH_SCALE_FACTOR;
        processTouch();
        downX = event.getX();
        downY = event.getY();
     }
    

    延时执行

    // 延时
    handler.postDelayed(this, 30);
    

    手指滑动旋转后,对旋转距离做递减操作

    // 减速模式(均速衰减)
    if (mode == MODE_DECELERATE) {
        if (Math.abs(mAngleX) > 0.2f) {
            mAngleX -= mAngleX * 0.1f;
        }
        if (Math.abs(mAngleY) > 0.2f) {
            mAngleY -= mAngleY * 0.1f;
        }
    }
    processTouch();
    
    • 双指缩放
      记录起始的缩放比和起始双指距离
    if (event.getActionIndex() == 1) {
        // 第二个触摸点
        scaleX = getScaleX();
        startDistance = distance(event.getX(0) - event.getX(1),
                event.getY(0) - event.getY(1));
        return true;
    }
    

    双指移动的时候计算缩放比,并对缩放比做限制

    // 双点触摸,缩放
    float endDistance = distance(event.getX(0) - event.getX(1),
            event.getY(0) - event.getY(1));
    // 缩放比例
    float scale = ((endDistance - startDistance) / (endDistance * 2) + 1) * scaleX;
    if (scale > 1.4f) {
        scale = 1.2f;
    }
    if (scale < 1) {
        scale = 1f;
    }
    setScaleX(scale);
    setScaleY(scale);
    
    6. 每个点的View和效果
    • 点的大小/点的明暗度
      点的大小缩放,根据透视距离计算即可;明暗度根据缩放计算
    • 点的文字过长跑马灯
    // 位移
    if (isOverstep) {
        signDistanceX = signDistanceX + 2.5f;
        if (signDistanceX > maxSignRange) {
            signDistanceX = signWidth;
        }
    }
    // 昵称文字过长(跑马灯)
    if (isOverstep) {
        canvas.drawText(sign, totalSignWidth - signDistanceX, signY, signPaint);
     } else {
        canvas.drawText(sign, signX, signY, signPaint);
    }
    
    • 点的闪动效果
    // 设置阴影
    if (hasShadow) {
        starPaint.setShadowLayer(shadowRadius, 1.0f, 1.0f, alpha);
        canvas.drawCircle(starCenterX, starCenterY, radius, starPaint);
    }
    ......
    // 忽大忽小
    if (hasShadow) {
        if (isEnlarge) {
            shadowRadius += radiusIncrement;
        } else {
            shadowRadius -= radiusIncrement;
        }
        if (shadowRadius < 1) {
            shadowRadius = 1.0f;
            isEnlarge = true;
        } else if (shadowRadius > radius) {
            shadowRadius = radius;
            isEnlarge = false;
        }
    }
    

    差不多就只有这些了,具体的就看代码吧:

    Github传送门 biu biu biu ~~~

    最后

    如果你有什么意见和反馈,欢迎到Github提issue(最喜欢别人提issue了)哈哈哈~

    相关文章

      网友评论

        本文标题:Android实现SoulApp星球效果

        本文链接:https://www.haomeiwen.com/subject/rnclzqtx.html