Android实现SoulApp星球效果

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了)哈哈哈~

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