一简述
1 需求
最近在使用 Libgdx 进行游戏大厅开发, 遇到这种需求: 为个别文本控件 (Label) 设置纯色透明的圆角矩形背景
2 思路
Libgdx 中的 Label 是提供背景设置的: 对 Label 的 Style 的 background 属性进行设置即可, 这个 background 是个 Drawable, 可以使用图片作为 Label 的背景, 很好很强大, 但我这个项目中的 Label 背景只需要一种透明颜色而已, 用图片来实现的话我觉得并不是一种很好的方式 (有种杀鸡用牛刀的感觉) 想来想去, 认为 Libgdx 中的 Pixmap 可以帮助我实现这种需求, 因为 Pixmap 是可以被用来绘制一个简单图形的, 之后将 pixmap 转换成 drawable 赋值给 background 就好了:
- Drawable bg = new TextureRegionDrawable(new TextureRegion(new Texture(pixmap)));
- label.getStyle().background = bg;
3 难点
然而, pixmap 只提供了如下几种绘制图形的方法:
- pixmap.drawLine() // 画线
- pixmap.drawRectangle(); // 画矩形
- pixmap.drawCircle(); // 画环
- pixmap.fillTriangle(); // 填充三角形
- pixmap.fillRectangle(); // 填充矩形
- pixmap.fillCircle(); // 填充圆形
我要的圆角矩形正好没有(毕竟圆角矩形不是简单图形是吧), 于是, 经过 google 大法及本人的 "缜密" 思考之后, 纯色透明圆角矩形实现出来了, 本篇将记录两种实现圆角矩形的方案, 下面开始进入正题
二方案一
这个方案借鉴了一个歪果人的博文, 本文为我之后的方案二做了启发, 这里就先把地址贴出来, 方便今后再翻出来欣赏:
博文原地址: LIBGDX DRAWING A ROUNDED RECTANGLE PIXMAP
下面就开始强行翻译一下
1 原理
绘制出一个圆角矩形, 实际上, 可以通过使用填充了相同的颜色的 2 个矩形和 4 个圆圈来实现, 这几个图形的摆放如下图所示
2 实现
通过上图, 可以很清晰的明白原作者的实现思想, 下面就开始码代码(copy):
- public Pixmap getRoundedRectangle(int width, int height, int radius, int color) {
- Pixmap pixmap = new Pixmap(width, height, Pixmap.Format.RGBA8888);
- pixmap.setColor(color);
- // Pink rectangle
- pixmap.fillRectangle(0, radius, pixmap.getWidth(), pixmap.getHeight() - 2 * radius);
- // Green rectangle
- pixmap.fillRectangle(radius, 0, pixmap.getWidth() - 2 * radius, pixmap.getHeight());
- // Bottom-left circle
- pixmap.fillCircle(radius, radius, radius);
- // Top-left circle
- pixmap.fillCircle(radius, pixmap.getHeight() - radius, radius);
- // Bottom-right circle
- pixmap.fillCircle(pixmap.getWidth() - radius, radius, radius);
- // Top-right circle
- pixmap.fillCircle(pixmap.getWidth() - radius, pixmap.getHeight() - radius, radius);
- return pixmap;
- }
3 效果
为了直观的看出效果, 我把 Demo 的舞台背景渲染为黑色, 圆角矩形设置为白色, 下面列出 demo 中的部分代码:
- Texture roundedRectangle = new Texture(getRoundedRectangle(color, width, height, radius));
- Image image = new Image(roundedRectangle);
- image.setPosition(Gdx.graphics.getWidth() / 2, Gdx.graphics.getHeight() / 2, Align.center);
- addActor(image);
4 缺陷
效果很棒, 不得不说, 歪果人的想法还是挺好的, 但是, 当我把圆角矩形的颜色设置为白色透明时, 这效果就恶心了, 这里贴出白透明色的设置代码:
Color color = new Color(1, 1, 1, 0.5f);
为什么会这样, 仔细想想就能明白, 这是因为 pixmap 在绘制这几个图形时, 它们的重合部分透明度叠加了
5 完善
既然知道了原因, 那有什么解决办法呢? 这里列出我能想到的 2 个办法:
先使用不透明颜色进行绘制, 待所有图形绘制完成后, 再来设置整体的透明度
先用一个 pixmap 绘制出不透明圆角矩形, 然后遍历所有的像素点, 如果该像素不是透明的, 则在另一个 pixmap 的相同位置, 用 rgb 相同但 a 不同的颜色再绘制一次
第一个方法我觉得是比较好的, 感觉实现上比较简单可靠, 然而我始终没有找到可以对 pixmap 设置整体透明度的方法, 于是我这里采用了第二个方法来实现:
- public Pixmap getRoundedRectangle(Color color, int width, int height, int radius) {
- Pixmap pixmap = new Pixmap(width, height, Pixmap.Format.RGBA8888);
- // 1 保存原先的透明度
- float alpha = color.a;
- // 2 将透明度设置为 1 之后开始绘制圆角矩形
- color.set(color.r, color.g, color.b, 1);
- pixmap.setColor(color);
- // Pink rectangle
- pixmap.fillRectangle(0, radius, pixmap.getWidth(), pixmap.getHeight() - 2 * radius);
- // Green rectangle
- pixmap.fillRectangle(radius, 0, pixmap.getWidth() - 2 * radius, pixmap.getHeight());
- // Bottom-left circle
- pixmap.fillCircle(radius, radius, radius);
- // Top-left circle
- pixmap.fillCircle(radius, pixmap.getHeight() - radius, radius);
- // Bottom-right circle
- pixmap.fillCircle(pixmap.getWidth() - radius, radius, radius);
- // Top-right circle
- pixmap.fillCircle(pixmap.getWidth() - radius, pixmap.getHeight() - radius, radius);
- // 3 如果原来的背景色存在透明度, 则需要对图形整体重绘一次
- if (alpha != 1) {
- Pixmap newPixmap = new Pixmap(pixmap.getWidth(), pixmap.getHeight(), pixmap.getFormat());
- int r = ((int)(255 * color.r) << 16);
- int g = ((int)(255 * color.g) << 8);
- int b = ((int)(255 * color.b));
- int a = ((int)(255 * alpha) << 24);
- int argb8888 = new Color(r | g | b | a).toIntBits();
- for (int y = 0; y < pixmap.getHeight(); y++) {
- for (int x = 0; x < pixmap.getWidth(); x++) {
- int pixel = pixmap.getPixel(x, y);
- if ((pixel & color.toIntBits()) == color.toIntBits()) {
- newPixmap.drawPixel(x, y, argb8888);
- }
- }
- }
- pixmap.dispose();
- pixmap = newPixmap;
- }
- return pixmap;
- }
来看下效果, 嗯, 还可以吧
三方案二(个人认为比较完美的方案)
虽然用 2 个 pixmap 的方式可以 "完美" 地绘制出纯色透明圆角矩形, 但是, 每创建出 1 个透明圆角矩形都必须创建出 2 个 pixmap 来为之辅助, 尽管最后会对旧的 pixmap 进行 dispose, 但总感觉这种方案不是并最优方式
1 原理
通过一番思考之后, 我得出了这样一个结论:
既然最后在使用到第 2 个 pixmap 的时候需要遍历所有像素点来重新绘制一遍, 那我干脆直接进行第 2 步(第 1 步绘制不透明矩形的步骤不要了), 在遍历所有像素的时候把需要绘制到 pixmap 的像素点绘制出来不就好了吗? 这样做还可以省掉一个 pixmap 的开销
那么现在的问题就是, 我怎么知道哪些像素应该被绘制, 哪些像素不要被绘制呢? 其实可以把圆角矩形看成是一个不完整的有缺角的矩形, 而这些缺角正好就是不用被绘制的那些像素点
通过观察, 可以知道, 四个缺角中的像素都有如下相同点:
在绿线与蓝线组成的小矩形区域中;
都不在圆上, 换句话说就是点与圆心的距离超过半径
2 实现
根据结论, 代码实现如下:
- public Pixmap getRoundedRectangle(Color color, int width, int height, int radius) {
- Pixmap pixmap = new Pixmap(width, height, Pixmap.Format.RGBA8888);
- pixmap.setColor(color);
- for (int y = 0; y < pixmap.getHeight(); y++) {
- for (int x = 0; x < pixmap.getWidth(); x++) {
- if ((x >= 0 && x <= radius) && (y >= 0 && y <= radius)) { // bottom-left
- if (Math.sqrt((radius - x) * (radius - x) + (radius - y) * (radius - y)) > radius) {
- continue;
- }
- } else if ((x >= 0 && x <= radius) && (y >= (height - radius) && y <= height)) { // top-left
- if (Math.sqrt((radius - x) * (radius - x) + ((height - radius) - y) * ((height - radius) - y)) > radius) {
- continue;
- }
- } else if ((x >= (width - radius) && x <= width) && (y >= 0 && y <= radius)) { // bottom-right
- if (Math.sqrt(((width - radius) - x) * ((width - radius) - x) + (radius - y) * (radius - y)) > radius) {
- continue;
- }
- } else if ((x >= (width - radius) && x <= width) && (y >= (height - radius) && y <= height)) { // top-right
- if (Math.sqrt(((width - radius) - x) * ((width - radius) - x) + ((height - radius) - y) * ((height - radius) - y)) > radius) {
- continue;
- }
- }
- pixmap.drawPixel(x, y);
- }
- }
- return pixmap;
- }
为了方便理解, 下面列出各个缺角的圆心与小矩形 x 与 y 的取值范围:
- // bottom-left
- // ------------ 圆心:(radius, radius)
- // ------------ 矩形:([0,radius], [0,radius])
- // top-left
- // ------------ 圆心:(radius, height-radius)
- // ------------ 矩形:([0,radius], [height-radius,height])
- // bottom-right
- // ------------ 圆心:(width-radius,radius)
- // ------------ 矩形:([width-radius,width], [0,radius])
- // top-right
- // ------------ 圆心:(width-radius,height-radius)
- // ------------ 矩形:([width-radius,width], [height-radius,height])
结果是 OK 的, 与方案一绘制出来的透明圆角矩形一致, 并且少了一个 pixmap 的开销
四最后
最后, 想多说两句, Libgdx 作为一款优秀的 Android 端游戏开发引擎, 网上的资料却相当的少, 很多东西就算 Google 了也不一定能找到答案, 本人也是最近才对其进行了解并上手使用, 对于本文中所说的需求或许并不是最好的解决方式, 如果您有什么好的解决方案或建议, 请不吝赐教, thx
来源: https://juejin.im/post/5a7cf9605188257a63110287