gl_FragColor = distance < 0.5 ? texture2D(u_Text, v_UV) : u_BgColor;
我根据点到中心的距离调整片段的颜色,没有采取抗锯齿手段。当然结果差强人意 —— 圆的边是凹凸不平的。
有锯齿的圆
解决方案是 smoothstep
。它根据到 texture
与背景的变换起始点的距离平滑的从0
到1
变化。因此距离 0
到 0.49
时 texture
的透明度为 1
,大于等于 0.5
时为 0
,0.49
和 0.5
之间时平滑变化,如此圆的边就平滑了。
无锯齿圆
OpenGL
中如何使用 texture
显示图像和文本?在动画中圆有两种状态 —— 普通和选中。在普通状态下圆的 texture
包含文字和颜色,在选中状态下同时包含图像。因此我需要为每个圆创建两个不同的 texture
。
我使用 Bitmap
实例来创建 texture
,绘制所有元素。
fun bindTextures(textureIds: IntArray, index: Int) {
texture = bindTexture(textureIds, index * 2, false)
imageTexture = bindTexture(textureIds, index * 2 + 1, true)
}
private fun bindTexture(textureIds: IntArray, index: Int, withImage: Boolean): Int {
glGenTextures(1, textureIds, index)
createBitmap(withImage).toTexture(textureIds[index])
return textureIds[index]
}
private fun createBitmap(withImage: Boolean): Bitmap {
var bitmap = Bitmap.createBitmap(bitmapSize.toInt(), bitmapSize.toInt(), Bitmap.Config.ARGB_4444)
val bitmapConfig: Bitmap.Config = bitmap.config ?: Bitmap.Config.ARGB_8888
bitmap = bitmap.copy(bitmapConfig, true)
val canvas = Canvas(bitmap)
if (withImage) drawImage(canvas)
drawBackground(canvas, withImage)
drawText(canvas)
return bitmap
}
private fun drawBackground(canvas: Canvas, withImage: Boolean) {
…
}
private fun drawText(canvas: Canvas) {
…
}
private fun drawImage(canvas: Canvas) {
…
}
之后我将 texture
单元赋值给 u_Text
变量。我使用 texture2()
方法获取片段的真实颜色,texture2()
接收 texture
单元和片段顶点的位置两个参数。
关于动画的物理特性十分的简单。主要的对象是 World
实例,所有的实体创建都需要它。
class CircleBody(world: World, var position: Vec2, var radius: Float, var increasedRadius: Float) {
val decreasedRadius: Float = radius
val increasedDensity = 0.035f
val decreasedDensity = 0.045f
var isIncreasing = false
var isDecreasing = false
var physicalBody: Body
var increased = false
private val shape: CircleShape
get() = CircleShape().apply {
m_radius = radius + 0.01f
m_p.set(Vec2(0f, 0f))
}
private val fixture: FixtureDef
get() = FixtureDef().apply {
this.shape = this@CircleBody.shape
density = if (radius > decreasedRadius) decreasedDensity else increasedDensity
}
private val bodyDef: BodyDef
get() = BodyDef().apply {
type = BodyType.DYNAMIC
this.position = this@CircleBody.position
}
init {
physicalBody = world.createBody(bodyDef)
physicalBody.createFixture(fixture)
}
}
如你所见创建实体很简单:需要指定实体的类型(例如:动态、静态、运动学)、位置、半径、形状、密度以及运动。
每次画面绘制,都需要调用 World
的 step()
方法移动所有的实体。之后你可以在图形的新位置进行绘制。
我遇到的问题是 World
的重力只能是一个方向,而不能是一个点。JBox2D
不支持轨道重力。因此将圆移动到屏幕中心是无法实现的,所以我只能自己来实现引力。
private val currentGravity: Float
get() = if (touch) increasedGravity else gravity
private fun move(body: CircleBody) {
body.physicalBody.apply {
val direction = gravityCenter.sub(position)
val distance = direction.length()
val gravity = if (body.increased) 1.3f * currentGravity else currentGravity
if (distance > step * 200) {
applyForce(direction.mul(gravity / distance.sqr()), position)
}
}
}
引力挑战
每次发生移动时,我计算出力的大小并作用于每个实体,看上去就像圆受引力作用在移动。
GLSurfaceView
和其它的 Android view
一样可以响应用户的点击事件。
override fun onTouchEvent(event: MotionEvent): Boolean {
when (event.action) {
MotionEvent.ACTION_DOWN -> {
startX = event.x
startY = event.y
previousX = event.x
previousY = event.y
}
MotionEvent.ACTION_UP -> {
if (isClick(event)) renderer.resize(event.x, event.y)
renderer.release()
}
MotionEvent.ACTION_MOVE -> {
if (isSwipe(event)) {
renderer.swipe(event.x, event.y)
previousX = event.x
previousY = event.y
} else {
release()
}
}
else -> release()
}
return true
}
private fun release() = postDelayed({ renderer.release() }, 1000)
private fun isClick(event: MotionEvent) = Math.abs(event.x - startX) < 20 && Math.abs(event.y - startY) < 20
private fun isSwipe(event: MotionEvent) = Math.abs(event.x - previousX) > 20 && Math.abs(event.y - previousY) > 20
GLSurfaceView 拦截所有的点击,并用渲染器进行处理。
渲染器:
fun swipe(x: Float, y: Float) = Engine.swipe(x.convert(glView.width, scaleX),
y.convert(glView.height, scaleY))
fun release() = Engine.release()
fun Float.convert(size: Int, scale: Float) = (2f * (this / size.toFloat()) - 1f) / scale
引擎:
fun swipe(x: Float, y: Float) {
gravityCenter.set(x * 2, -y * 2)
touch = true
}
fun release() {
gravityCenter.setZero()
touch = false
}
用户点击屏幕时,我将重力中心设为用户点击点,这样看起来就像用户在控制气泡的移动。用户停止移动后我会将气泡恢复到初始位置。
当用户点击圆时,我从 onTouchEvent()
方法获取屏幕点击点。但是我也需要找到 OpenGL
坐标系中点击的圆。GLSurfaceView
的默认中心位置坐标为[0, 0]
,x y
取值范围为 -1
到 1
。所以我需要考虑屏幕的比例。
private fun getItem(position: Vec2) = position.let {
val x = it.x.convert(glView.width, scaleX)
val y = it.y.convert(glView.height, scaleY)
circles.find { Math.sqrt(((x - it.x).sqr() + (y - it.y).sqr()).toDouble()) <= it.radius }
}
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)
![](https://img-blog.csdnimg.cn/img_convert/c283963739959593027df6b7ba9de4aa.jpeg)
文末
我总结了一些Android核心知识点,以及一些最新的大厂面试题、知识脑图和视频资料解析。
以后的路也希望我们能一起走下去。(谢谢大家一直以来的支持)
部分资料一览:
- 330页PDF Android学习核心笔记(内含8大板块)
-
Android学习的系统对应视频
-
Android进阶的系统对应学习资料
- Android BAT大厂面试题(有解析)
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
脑图和视频资料解析。
以后的路也希望我们能一起走下去。(谢谢大家一直以来的支持)
部分资料一览:
- 330页PDF Android学习核心笔记(内含8大板块)
[外链图片转存中…(img-NtekG4Ig-1712350914546)]
[外链图片转存中…(img-dTVxacbs-1712350914546)]
-
Android学习的系统对应视频
-
Android进阶的系统对应学习资料
[外链图片转存中…(img-IBpfTPi3-1712350914546)]
- Android BAT大厂面试题(有解析)
[外链图片转存中…(img-R5KzIetO-1712350914547)]
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!