中秋节听夜曲,Android OpenGL 呈现周董专属的玉兔主题音乐播放器
概述
前几天发现QQ音乐有个好玩的功能,为用户提供了多种 播放器主题,其中 原神 的主题让我眼前一亮:

当然,诸如 换肤、主题 类的功能已经屡见不鲜,但这类沉浸式播放器的听歌体验确实不错。
见猎心喜,正好中秋马上就到,我也尝试整个 中秋主题音乐播放器 试试水。
整体思路有2点:
首先是技术方面,纯粹的 ImageView
图层堆砌来实现,渲染效率太低,OpenGL
是一个不错的技术方案(QQ
应该也是这么实现的),顺便复习下图形学的知识。
其次是玩法上,干脆在基础的功能上加一些 更好玩的,比如为播放页设计多个图层,通过陀螺仪+图层联动实现的 裸眼3D 的视觉效果,边听歌边玩。后续还可以考虑通过制定 设计规范,让不同图层的UI
元素,达成更多新奇好玩的 联动效果。
说了这么多,最后效果如下所示,左侧展示录屏效果,右侧是裸眼3D效果:


1. 裸眼3D原理
2年前自如的 《自如客APP裸眼3D效果的实现》 一文引发了社区的热烈讨论和实践,本着不重复造轮子的原则,这里简单对原理介绍,感兴趣的读者可参考上述链接。
裸眼 3D
效果的本质是——将整个图片结构分为 3
层:上层、中层、以及底层。在手机左右上下旋转时,上层和底层的图片呈相反的方向进行移动,中层则不动,在视觉上给人一种 3D
的感觉:
本文的效果是由以下四张图,由底至顶,依次绘制而成的:
接下来,如何感应手机的旋转状态,并将4层图片进行对应的移动呢?当然是使用设备自身提供的 传感器 了,通过传感器不断回调获取设备的旋转状态,对 UI
进行对应地渲染即可。
2. 为何选择 OpenGL
GPU
更适合图形、图像的处理,裸眼3D效果中有大量的 旋转、缩放 和 位移 操作,都可在 java
层通过一个 矩阵 对几何变换进行描述,通过 shader
小程序中交给 GPU
处理 ——理论上 OpenGL
的渲染性能比原生的 ImageView
更好。
借助OpenGL
的API
,渲染性能也符合预期,打开 布局边界 和 GPU过渡绘制 选项后,播放页渲染性能也依然稳定,更不会增加布局层级的复杂度,直接证明了该方案 具备应用到实际生产项目的可行性:

3 代码实现
本文重点是描述 OpenGL 绘制时的思路描述,因此下文仅展示部分核心代码,对具体实现感兴趣的读者可参考文末的链接。
3.1 绘制静态图片
首先需要将4张图片(图片素材来源)依次进行静态绘制,这里涉及大量 OpenGL API
的使用,不熟悉的读可略读本小节,以捋清思路为主。
首先看一下顶点和片元着色器的 shader
代码,其定义了图像纹理是如何在GPU
中处理渲染的:
// 顶点着色器代码
// 顶点坐标
attribute vec4 av_Position;
// 纹理坐标
attribute vec2 af_Position;
uniform mat4 u_Matrix;
varying vec2 v_texPo;void main() {v_texPo = af_Position;gl_Position = u_Matrix * av_Position;
}
// 片元着色器代码
precision mediump float;
// 纹理坐标
varying vec2 v_texPo;
uniform sampler2D sTexture;
void main() {gl_FragColor=texture2D(sTexture, v_texPo);
}
定义好了 Shader
,接下来在 GLSurfaceView
(可以理解为 OpenGL
中的画布) 创建时,初始化Shader
小程序,并将图像纹理依次加载到GPU
中:
public class ZQRenderer implements GLSurfaceView.Renderer {@Overridepublic void onSurfaceCreated(GL10 gl, EGLConfig config) {// 1.加载shader小程序mProgram = loadShaderWithResource(mContext, R.raw.projection_vertex_shader, R.raw.projection_fragment_shader);//...// 2. 依次将切图纹理传入GPUthis.texImageInner(R.drawable.icon_player_bg, mBackTextureId);this.texImageInner(R.drawable.icon_player_moon, mMidTextureId);this.texImageInner(R.drawable.icon_album_cover_nocturne, mCoverTextureId);this.texImageInner(R.drawable.icon_player_text, mFrontTextureId);}
}
接下来是定义视口以及投影矩阵,因为切图的比例各不相同,为了保证视觉效果,需要针对不同层级的图片,设置不同的正交投影策略。
public class ZQRenderer implements GLSurfaceView.Renderer {@Overridepublic void onSurfaceChanged(GL10 gl, int width, int height) {//设置大小位置GLES20.glViewport(0, 0, width, height);Matrix.setIdentityM(mBgProjectionMatrix, 0);Matrix.setIdentityM(mMoonProjectionMatrix, 0);Matrix.setIdentityM(mCoverProjectionMatrix, 0);// 计算宽高比boolean isVertical = width < height;float screenRatio = (float) width / (float) height;// 设置投影矩阵// 1.深色背景图的投影矩阵,只需要铺全屏// 2.月亮和装饰图的投影矩阵float ratio = (float) 1080 / (float) 1528;if (isVertical) {Matrix.orthoM(mMoonProjectionMatrix, 0, -1f, 1f, -1f / ratio, 1f / ratio, -1f, 1f);} else {Matrix.orthoM(mMoonProjectionMatrix, 0, -ratio, ratio, -1f, 1f, -1f, 1f);}// 3.歌曲封面图投影矩阵if (isVertical) {Matrix.orthoM(mCoverProjectionMatrix, 0, -1f, 1f, -1f / screenRatio, 1f / screenRatio, -1f, 1f);} else {Matrix.orthoM(mCoverProjectionMatrix, 0, -screenRatio, screenRatio, -1f, 1f, -1f, 1f);}}
}
最后就是 绘制,读者需要理解,对于4层图像的渲染,其逻辑是基本一致的,差异仅仅有2点:图像本身不同 以及 图像的几何变换不同。
public class ZQRenderer implements GLSurfaceView.Renderer {@Overridepublic void onDrawFrame(GL10 gl) {GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);GLES20.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);GLES20.glUseProgram(mProgram);this.updateMatrix();this.drawLayerInner(mBackTextureId, mTextureBuffer, mBackMatrix); // 画背景this.drawLayerInner(mMidTextureId, mTextureBuffer, mMoonMatrix); // 画月亮this.drawLayerInner(mCoverTextureId, mTextureBuffer, mCoverMatrix); // 画封面this.drawLayerInner(mFrontTextureId, mTextureBuffer, mFrontMatrix); // 画前景装饰}private void texImageInner(@DrawableRes int drawableRes, int textureId) {//绑定纹理GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureId);//环绕方式GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_REPEAT);GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_REPEAT);//过滤方式GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR);GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);GLES20.glEnable(GLES20.GL_BLEND);GLES20.glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);Bitmap bitmap = BitmapFactory.decodeResource(mContext.getResources(), drawableRes);GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, bitmap, 0);bitmap.recycle();}
}
现在我们完成了图像的 静态绘制,接下来我们需要接入 传感器,并引入不同层级图片各自的几何变换, 让图片动起来。
3.2 让图片动起来
首先我们需要对 Android
平台上的传感器进行注册,监听手机的旋转状态,并拿到手机 xy
轴的旋转角度。
// 2.1 注册传感器
mSensorManager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE);
mAcceleSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
mMagneticSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD);
mSensorManager.registerListener(mSensorEventListener, mAcceleSensor, SensorManager.SENSOR_DELAY_GAME);
mSensorManager.registerListener(mSensorEventListener, mMagneticSensor, SensorManager.SENSOR_DELAY_GAME);// 2.2 不断接受旋转状态
private final SensorEventListener mSensorEventListener = new SensorEventListener() {@Overridepublic void onSensorChanged(SensorEvent event) {// ... 省略具体代码float[] values = new float[3];float[] R = new float[9];SensorManager.getRotationMatrix(R, null, mAcceleValues, mMageneticValues);SensorManager.getOrientation(R, values);// x轴的偏转角度float degreeX = (float) Math.toDegrees(values[1]);// y轴的偏转角度float degreeY = (float) Math.toDegrees(values[2]);// z轴的偏转角度float degreeZ = (float) Math.toDegrees(values[0]);// 拿到 xy 轴的旋转角度,进行矩阵变换updateMatrix(degreeX, degreeY);}
};
注意,因为我们只需控制图像的左右和上下移动,因此,我们只需关注设备本身 x
轴和 y
轴的偏转角度。但如果将图片直接进行位移操作,将会因为位移后图像的另一侧没有纹理数据,导致渲染结果有黑边现象,为了避免这个问题,我们需要将图像默认从中心点进行放大,保证图像移动的过程中,不会超出自身的边界。
也就是说,我们一开始进入时,看到的肯定只是图片的部分区域。给每一个图层设置 scale
,将图片进行放大。显示窗口是固定的,那么一开始只能看到图片的正中位置。(中层可以不用,因为中层本身是不移动的,所以也不必放大)
这里的处理参考自 Nayuta 的 这篇文章,内部已经将思路阐述的非常清晰,强烈建议读者进行阅读。
明白了这一点,我们就能理解,裸眼 3D
的效果实际上就是对 不同层级的图像 进行 缩放 和 位移 的变换,下面是分别获取几何变换的代码:
public class ZQRenderer implements GLSurfaceView.Renderer {private float[] mBgProjectionMatrix = new float[16];private float[] mMoonProjectionMatrix = new float[16];private float[] mCoverProjectionMatrix = new float[16];private float[] mBackMatrix = new float[16];private float[] mMoonMatrix = new float[16];private float[] mCoverMatrix = new float[16];private float[] mFrontMatrix = new float[16];// 封面图旋转一圈的时间,单位秒.private static final long ROTATE_TIME = 20L;public static final long DELAY_INTERVAL = 1000 / (360 / ROTATE_TIME);/*** 陀螺仪数据回调,更新各个层级的变换矩阵.** @param degreeX x轴旋转角度,图片应该上下移动* @param degreeY y轴旋转角度,图片应该左右移动*/private void updateMatrix() {// ---------- 背景-蓝色底图 ----------Matrix.setIdentityM(mBackMatrix, 0);// 1.最大位移量float maxTransXY = MAX_VISIBLE_SIDE_BACKGROUND - 1f;// 2.本次的位移量float transX = ((maxTransXY) / MAX_TRANS_DEGREE_Y) * -mCurDegreeY;float transY = ((maxTransXY) / MAX_TRANS_DEGREE_X) * -mCurDegreeX;float[] backMatrix = new float[16];// 蓝色底图的投影矩阵,需要铺展全屏.Matrix.setIdentityM(mBgProjectionMatrix, 0);Matrix.setIdentityM(backMatrix, 0);Matrix.translateM(backMatrix, 0, transX, transY, 0f); // 2.平移Matrix.scaleM(backMatrix, 0, SCALE_BACK_GROUND, SCALE_BACK_GROUND, 1f); // 1.缩放Matrix.multiplyMM(mBackMatrix, 0, mBgProjectionMatrix, 0, backMatrix, 0); // 3.正交投影// ---------- 背景 -月亮 ----------Matrix.setIdentityM(mMoonMatrix, 0);float[] midMatrix = new float[16];Matrix.setIdentityM(midMatrix, 0);
// Matrix.translateM(midMatrix, 0, transX, transY, 0f); // 2.平移,这行注释解开后,手机摇一摇,封面图和月亮也会有位移偏差.Matrix.scaleM(midMatrix, 0, SCALE_MOON_GROUND, SCALE_MOON_GROUND, 1.0f); // 1.缩放Matrix.multiplyMM(mMoonMatrix, 0, mMoonProjectionMatrix, 0, midMatrix, 0); // 3.正交投影// --------- 中景-歌曲封面 ----------Matrix.setIdentityM(mCoverMatrix, 0);float[] rotateMatrix = new float[16];float[] tranAndScale = new float[16];float[] coverMatrix = new float[16];Matrix.setIdentityM(rotateMatrix, 0);Matrix.setIdentityM(tranAndScale, 0);Matrix.setIdentityM(coverMatrix, 0);Matrix.scaleM(tranAndScale, 0, 0.565f, 0.58f, 1.0f); // 3.缩放,这里的缩放参数是开发时,即时调整的,保证歌曲封面和月亮的大小一致Matrix.translateM(tranAndScale, 0, 0.05f, 1.41f, 0f); // 2.平移,这里的位移参数是开发时,即时调整的,保证歌曲封面和月亮的center位置在一起Matrix.setRotateM(rotateMatrix, 0, 360 - mCoverDegree, 0.0f, 0.0f, 1.0f); // 1.旋转,顺时针Matrix.multiplyMM(coverMatrix, 0, tranAndScale, 0, rotateMatrix, 0);Matrix.multiplyMM(mCoverMatrix, 0, mCoverProjectionMatrix, 0, coverMatrix, 0); // 4.正交投影// ---------- 前景-装饰 ----------Matrix.setIdentityM(mFrontMatrix, 0);// 1.最大位移量maxTransXY = MAX_VISIBLE_SIDE_FOREGROUND - 1f;// 2.本次的位移量transX = ((maxTransXY) / MAX_TRANS_DEGREE_Y) * -mCurDegreeY;transY = ((maxTransXY) / MAX_TRANS_DEGREE_X) * -mCurDegreeX;float[] frontMatrix = new float[16];Matrix.setIdentityM(frontMatrix, 0);Matrix.translateM(frontMatrix, 0, -transX, -transY, 0f); // 2.平移Matrix.scaleM(frontMatrix, 0, SCALE_FORE_GROUND, SCALE_FORE_GROUND, 1f); // 1.缩放Matrix.multiplyMM(mFrontMatrix, 0, mMoonProjectionMatrix, 0, frontMatrix, 0); // 3.正交投影}
}
背景、月亮、前景都很简单,只有 中景的歌曲封面 麻烦一些,首先歌曲封面要伴着歌曲进度做 旋转动画,其次,由于图片素材尺寸的原因,中心点要 位移 到和月亮相同的位置,最后 缩放 到和月亮一样的大小完成重合。
小结
现在,我们完成了图示效果的开发。
限于篇幅,文中代码以捋清思路为主,部分细节(如 Handler
不断发消息实现旋转动画、添加 低通滤波器 防止抖动等)没展示出来,感兴趣的小伙伴可以点击 这里 查看源码。
关于我
Hello,我是 却把清梅嗅 ,如果您觉得文章对您有价值,欢迎 ❤️,也欢迎关注我的 博客 或者 GitHub。
- 我的Android学习体系
- 关于文章纠错
- 关于知识付费
- 关于《反思》系列
相关文章:

中秋节听夜曲,Android OpenGL 呈现周董专属的玉兔主题音乐播放器
概述 前几天发现QQ音乐有个好玩的功能,为用户提供了多种 播放器主题,其中 原神 的主题让我眼前一亮: 当然,诸如 换肤、主题 类的功能已经屡见不鲜,但这类沉浸式播放器的听歌体验确实不错。 见猎心喜,正好…...

008_第一代软件系统架构
第一代软件系统架构 文章目录 第一代软件系统架构项目介绍软件架构和软件构架系统框架硬件组成运行系统基础库软件层 系统架构 关键字: Qt、 Qml、 关键字3、 关键字4、 关键字5 项目介绍 欢迎来到我们的 QML & C 项目!这个项目结合了 QML&…...

oracle客户端的安装(SQL Developer)
参考资料 软件首页:https://www.oracle.com/database/sqldeveloper/ 官方文档:https://docs.oracle.com/en/database/oracle/sql-developer/ 下载地址:https://www.oracle.com/database/sqldeveloper/technologies/download/ 安装指南&#…...
Mysql索引优化1
关闭查询缓存 set global query_cache_size 0; set global query_cache_type 0; force index(索引)where 条件 强制走索引 一般不推荐,因为mysql结构中会通过cost计算出最优sql路线 索引下推 5.6之前 会先从辅助索引表也就是二级索引…...
Spring常考知识点(IOC、事务、容器等)
作者:逍遥Sean 简介:一个主修Java的Web网站\游戏服务器后端开发者 主页:https://blog.csdn.net/Ureliable 觉得博主文章不错的话,可以三连支持一下~ 如有需要我的支持,请私信或评论留言! Spring需要理解的问…...
Leetcode 2867. Count Valid Paths in a Tree
Leetcode 2867. Count Valid Paths in a Tree 1. 解题思路2. 代码实现 题目链接:2867. Count Valid Paths in a Tree 1. 解题思路 这一题思路上的话由于要求路径上有且仅有一个质数点,因此,一个直接的思路就是考察所有质数的点作为中心点时…...
Jtti:Ubuntu下如何创建XFS文件系统的LVM
在 Ubuntu 下创建一个 XFS 文件系统的 LVM(Logical Volume Manager)分区需要一系列步骤。以下是详细的步骤: 1. 创建物理卷 (PV) 首先,将要用于 LVM 的硬盘分区(物理卷)初始化为物理卷。假设你有一个硬盘…...

做销售管理分析需要看哪些关键指标?
做销售管理分析需要看哪些关键指标? 销售管理分析时抓取关键指标,有着能够【分析和判断销售趋势、为销售决策提供数据支持、优化销售流程和客户管理】等的好处 在了解了分析关键指标的目的之后,我们就可以根据企业的需求来确定关键指标&…...
【Python】自动完成手写字体图片贴入以及盖章工具
简介 该工具完成了如下功能: 1.将文字转换为手写体填入到模板文件中 2.自动将文字转换为盖章格式填入到模板文件中 3.字体格式可以替换 4.有配置文件进行扩展功能 功能模块 1.界面模块 import sys from PyQt5.QtWidgets import QApplication, QMessageBox, QWid…...

基于Xml方式Bean的配置-初始化方法和销毁方法
SpringBean的配置详解 Bean的初始化和销毁方法配置 Bean在被实例化后,可以执行指定的初始化方法完成一些初始化的操作,Bean在销毁之前也可以执行指定的销毁方法完成一些操作,初始化方法名称和销毁方法名称通过 <bean id"userService…...

实时更新进度条:JavaScript中的定时器和异步编程技巧
前言 在Web开发中,有许多场景需要实时地更新页面上的进度,例如上传文件、数据处理等。本文将介绍如何利用JavaScript中的定时器和异步编程技巧来实现实时更新进度,并探讨一些其他解决方案。 处理进度实时更新: 利用异步编程实现实…...

【简单图论】CF898 div4 H
Problem - H - Codeforces 题意: 思路: 手玩一下样例就能发现简单结论: v 离它所在的树枝的根的距离 < m 离这个根的距离时是 YES 否则就是NO 实现就很简单,先去树上找环,然后找出这个根,分别给a 和…...

【大虾送书第十一期】适合新手自学的网络安全基础技能“蓝宝书”:《CTF那些事儿》
目录 🥮写在前面 🥮内容简介 🥮读者对象 🥮专家推荐 🥮目录 🥮文末福利 🦐博客主页:大虾好吃吗的博客 🦐专栏地址:免费送书活动专栏地址 写在前面 CTF比赛是快…...

IDEA安装离线插件后重启无法打开
解决方法 1.找到插件安装目录删除插件 插件的位置一般在C:\Users\19058\AppData\Roaming\JetBrains\IntelliJIdea2021.1\plugins 高亮部分是自己电脑的用户位置,把报错前的刚才最新安装的插件删除,再尝试打开idea即可解决该问题 2.补充说明 AppData是个隐…...
论软件的可靠性设计
摘要 2021年6月,我所在的公司中标某集团保险大数据平台一体化研发项目,该项目总投资2000万人民币,项目周期为2年,通过该项目,搭建该集团保险大数据平台,一方面将全国所有保险业务全部入库并保存࿰…...

AG35学习笔记(一):debug串口抓取模组log、debug串口测试AT指令、echo命令通过串口发送16进制数据
目录 一、概述二、抓取模组log2.1 硬件接口2.2 用户登录2.3 相关指令 三、测试AT指令3.1 查看端口3.2 进入模式 四、串口发16进制echo使用 一、概述 二、抓取模组log 在之前记录了通过USB,使用移远工具Qwinlog来抓取log(3.3 抓取模组log)。…...

Python进阶学习----一闭三器
目录 编辑 前言 一.三器 1. 迭代器(Iterator) 1.1 什么是可迭代对象 1.2什么是迭代器 1.3案例演示: 以下是一个简单的迭代器示例,遍历一个列表并打印每个元素: 1.4迭代器总结 2. 生成器(Generat…...
C/S架构学习之TCP客户端
TCP客户端的实现流程:一、创建套接字(socket函数):通信域选择IPV4网络协议、流式套接字; int sockfd socket(AF_INET,SOCK_STREAM,0); 二、填充服务器的网络信息结构体(struct sockaddr_in serveraddr&…...

系统集成|第十二章(笔记)
目录 第十二章 沟通管理12.1 沟通的基本概念12.2 主要过程12.2.1 规划沟通管理12.2.2 管理沟通12.2.3 控制沟通 12.3 常见问题 上篇:第十一章、项目人力资源管理 第十二章 沟通管理 沟通管理在项目计划、执行、监控过程中具有重要的作用,项目经理应该拿…...

图神经网络(GNN)最新顶会论文汇总【附源码】
得益于强大的建模和分析能力,图神经网络(GNN)在社交网络分析、推荐系统、知识图谱、文本分析、等诸多领域得到了广泛的应用,目前已成为了人工智能领域的热门研究方向。 在今年的各大顶会获奖论文中,图神经网络相关的论…...

Unity3D中Gfx.WaitForPresent优化方案
前言 在Unity中,Gfx.WaitForPresent占用CPU过高通常表示主线程在等待GPU完成渲染(即CPU被阻塞),这表明存在GPU瓶颈或垂直同步/帧率设置问题。以下是系统的优化方案: 对惹,这里有一个游戏开发交流小组&…...
【位运算】消失的两个数字(hard)
消失的两个数字(hard) 题⽬描述:解法(位运算):Java 算法代码:更简便代码 题⽬链接:⾯试题 17.19. 消失的两个数字 题⽬描述: 给定⼀个数组,包含从 1 到 N 所有…...
鸿蒙中用HarmonyOS SDK应用服务 HarmonyOS5开发一个医院挂号小程序
一、开发准备 环境搭建: 安装DevEco Studio 3.0或更高版本配置HarmonyOS SDK申请开发者账号 项目创建: File > New > Create Project > Application (选择"Empty Ability") 二、核心功能实现 1. 医院科室展示 /…...

3-11单元格区域边界定位(End属性)学习笔记
返回一个Range 对象,只读。该对象代表包含源区域的区域上端下端左端右端的最后一个单元格。等同于按键 End 向上键(End(xlUp))、End向下键(End(xlDown))、End向左键(End(xlToLeft)End向右键(End(xlToRight)) 注意:它移动的位置必须是相连的有内容的单元格…...

使用 Streamlit 构建支持主流大模型与 Ollama 的轻量级统一平台
🎯 使用 Streamlit 构建支持主流大模型与 Ollama 的轻量级统一平台 📌 项目背景 随着大语言模型(LLM)的广泛应用,开发者常面临多个挑战: 各大模型(OpenAI、Claude、Gemini、Ollama)接口风格不统一;缺乏一个统一平台进行模型调用与测试;本地模型 Ollama 的集成与前…...
Pinocchio 库详解及其在足式机器人上的应用
Pinocchio 库详解及其在足式机器人上的应用 Pinocchio (Pinocchio is not only a nose) 是一个开源的 C 库,专门用于快速计算机器人模型的正向运动学、逆向运动学、雅可比矩阵、动力学和动力学导数。它主要关注效率和准确性,并提供了一个通用的框架&…...

招商蛇口 | 执笔CID,启幕低密生活新境
作为中国城市生长的力量,招商蛇口以“美好生活承载者”为使命,深耕全球111座城市,以央企担当匠造时代理想人居。从深圳湾的开拓基因到西安高新CID的战略落子,招商蛇口始终与城市发展同频共振,以建筑诠释对土地与生活的…...
JS手写代码篇----使用Promise封装AJAX请求
15、使用Promise封装AJAX请求 promise就有reject和resolve了,就不必写成功和失败的回调函数了 const BASEURL ./手写ajax/test.jsonfunction promiseAjax() {return new Promise((resolve, reject) > {const xhr new XMLHttpRequest();xhr.open("get&quo…...
LOOI机器人的技术实现解析:从手势识别到边缘检测
LOOI机器人作为一款创新的AI硬件产品,通过将智能手机转变为具有情感交互能力的桌面机器人,展示了前沿AI技术与传统硬件设计的完美结合。作为AI与玩具领域的专家,我将全面解析LOOI的技术实现架构,特别是其手势识别、物体识别和环境…...

【LeetCode】算法详解#6 ---除自身以外数组的乘积
1.题目介绍 给定一个整数数组 nums,返回 数组 answer ,其中 answer[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积 。 题目数据 保证 数组 nums之中任意元素的全部前缀元素和后缀的乘积都在 32 位 整数范围内。 请 不要使用除法,且在 O…...