Android游戏Credits界面可点击文本链接触摸无响应问题排查
问题排查:Credits界面可点击链接无响应
我在Roboyard应用的Credits界面添加了可在浏览器打开的链接,链接显示为蓝色,但触摸时无法触发点击事件。以下是相关代码,帮忙排查遗漏的部分:
RenderManager.java
package roboyard.eclabs; import android.content.Context; import android.content.Intent; import android.content.res.AssetManager; import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Rect; import android.graphics.Typeface; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.net.Uri; import android.util.SparseArray; import android.view.MotionEvent; import java.io.IOException; import java.io.InputStream; import java.util.Random; public class RenderManager { private Canvas target, mainTarget; private final Paint brush; private final Resources resources; private final SparseArray<Drawable> resourceMap; private final Random random; public RenderManager(Resources resources){ this.target = null; this.brush = new Paint(); this.brush.setColor(Color.WHITE); this.resources = resources; this.resourceMap = new SparseArray<>(); this.random = new Random(); } ... public void drawText(int x, int y, String str){ this.target.drawText(str, x, y, this.brush); } public void drawClickableText(int x, int y, String text, int color, int textSize, ClickListener clickListener) { Rect bounds = new Rect(); brush.setColor(color); brush.setTextSize(textSize); brush.getTextBounds(text, 0, text.length(), bounds); target.drawText(text, x, y + bounds.height() - textSize, brush); clickListener.setClickableBounds(x, y, x + bounds.width(), y + bounds.height()); } public void handleTouchEvent(MotionEvent event, ClickListener clickListener) { int action = event.getAction(); float x = event.getX(); float y = event.getY(); switch (action) { case MotionEvent.ACTION_UP: if (clickListener != null && clickListener.isClickable() && clickListener.isInsideClickableBounds(x, y)) { clickListener.onClick(); } break; } } public interface ClickListener { void onClick(); void setClickableBounds(float left, float top, float right, float bottom); boolean isInsideClickableBounds(float x, float y); boolean isClickable(); } }
CreditsGameScreen.java
package roboyard.eclabs; import android.content.Intent; import android.net.Uri; import android.graphics.Color; /** * Represents the Credits game screen. * Created by Pierre on 04/02/2015. */ public class CreditsGameScreen extends GameScreen { public CreditsGameScreen(GameManager gameManager) { super(gameManager); } int hs2; // Half the screen height /** * Creates game objects for the Credits screen. */ @Override public void create() { int ws2 = this.gameManager.getScreenWidth() / 2; hs2 = this.gameManager.getScreenHeight() / 2; // Add back button this.instances.add(new GameButtonGoto(7 * ws2 / 4 - 128, 9 * hs2 / 5 - 128, 128, 128, R.drawable.bt_back_up, R.drawable.bt_back_down, 0)); } /** * Loads assets for rendering. * * @param renderManager The render manager */ @Override public void load(RenderManager renderManager) { super.load(renderManager); } /** * Renders the Credits screen. * * @param renderManager The render manager */ @Override public void draw(RenderManager renderManager) { // Get app version information int versionCode = BuildConfig.VERSION_CODE; String versionName = BuildConfig.VERSION_NAME; // Set background color //renderManager.setColor(Color.BLUE); renderManager.setColor(Color.parseColor("#B0CC99")); // Light green background renderManager.paintScreen(); // Set text color and size renderManager.setColor(Color.BLACK); int ts = hs2 / 10; // Text size // Draw credits text renderManager.setTextSize((int) (1.2 * ts)); renderManager.drawText(10, 2 * ts, "Created by"); renderManager.setTextSize((int) (0.7 * ts)); renderManager.drawText(10, 3 * ts, "Alain Caillaud"); renderManager.drawText(10, 4 * ts, "Pierre Michel"); renderManager.drawText(10, 5 * ts, "Ruben Barkow-Kuder"); renderManager.setTextSize((int) (1.2 * ts)); renderManager.drawText(10, 7 * ts, "Based on"); renderManager.setTextSize((int) (0.7 * ts)); renderManager.drawText(10, 8 * ts, "Ricochet Robots(r)"); renderManager.setTextSize((int) (1.2 * ts)); renderManager.drawText(10, 10 * ts, "Imprint/privacy policy"); renderManager.setTextSize((int) (0.7 * ts)); drawClickableLink(renderManager, 10, 11 * ts, "https://eclabs.de/datenschutz.html"); renderManager.setColor(Color.BLACK); renderManager.setTextSize((int) (1.2 * ts)); renderManager.drawText(10, 13 * ts, "Open Source"); drawClickableLink(renderManager, 10, 14 * ts, "https://git.io/fjs5H"); renderManager.setColor(Color.BLACK); renderManager.drawText(10, 17 * ts, "Version: " + versionName + " (Build " + versionCode + ")"); super.draw(renderManager); } /** * Draws a clickable link. * * @param renderManager The render manager * @param x The x-coordinate of the link * @param y The y-coordinate of the link * @param url The URL of the link */ private void drawClickableLink(RenderManager renderManager, int x, int y, String url) { renderManager.drawClickableText(x, y, url, Color.BLUE, (int) (0.7 * (hs2 / 10)), new RenderManager.ClickListener() { @Override public void onClick() { openLink(url); } @Override public void setClickableBounds(float left, float top, float right, float bottom) { // No need to assign values here // These bounds are set internally by the RenderManager } @Override public boolean isInsideClickableBounds(float x, float y) { // Check if the touch coordinates (x, y) are inside the clickable bounds // Not implemented in this snippet, but you would check the bounds here return true; // Placeholder return value } @Override public boolean isClickable() { // Determine if the text is clickable // Not implemented in this snippet, but you would handle the clickable state here return true; // Placeholder return value } }); } /** * Opens a link in a web browser. * * @param url The URL to open */ private void openLink(String url) { // Create an intent to open a web browser with the specified URL Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); // Start the activity (open the web browser) gameManager.getActivity().startActivity(browserIntent); } /** * Updates the Credits screen. * * @param gameManager The game manager */ @Override public void update(GameManager gameManager) { super.update(gameManager); // Handle back button press to return to the main menu if (gameManager.getInputManager().backOccurred()) { gameManager.setGameScreen(0); // Set the main menu screen } } /** * Cleans up resources used by the Credits screen. */ @Override public void destroy() { super.destroy(); } }
核心问题与修复方案
1. 点击边界未被保存与校验
CreditsGameScreen中ClickListener的setClickableBounds是空实现,没有保存RenderManager传递的边界值,导致isInsideClickableBounds无法正确判断触摸位置是否在链接范围内。
修复代码:
修改drawClickableLink中的ClickListener实现,添加变量保存边界并做正确校验:
private void drawClickableLink(RenderManager renderManager, int x, int y, String url) { renderManager.drawClickableText(x, y, url, Color.BLUE, (int) (0.7 * (hs2 / 10)), new RenderManager.ClickListener() { private float left, top, right, bottom; @Override public void onClick() { openLink(url); } @Override public void setClickableBounds(float left, float top, float right, float bottom) { this.left = left; this.top = top; this.right = right; this.bottom = bottom; } @Override public boolean isInsideClickableBounds(float x, float y) { return x >= left && x <= right && y >= top && y <= bottom; } @Override public boolean isClickable() { return true; } }); }
2. 触摸事件未传递给链接监听器
当前代码没有将屏幕触摸事件传递给每个链接的ClickListener,RenderManager的handleTouchEvent方法从未被调用。
修复步骤:
- 在
CreditsGameScreen中添加列表保存所有链接监听器:
private List<RenderManager.ClickListener> linkClickListeners = new ArrayList<>();
- 修改
drawClickableLink,将创建的监听器加入列表:
private void drawClickableLink(RenderManager renderManager, int x, int y, String url) { RenderManager.ClickListener clickListener = new RenderManager.ClickListener() { // ... 上述修复后的实现 ... }; linkClickListeners.add(clickListener); renderManager.drawClickableText(x, y, url, Color.BLUE, (int) (0.7 * (hs2 / 10)), clickListener); }
- 在屏幕触摸事件处理方法中遍历监听器,传递触摸事件:
@Override public boolean onTouchEvent(MotionEvent event) { for (RenderManager.ClickListener listener : linkClickListeners) { gameManager.getRenderManager().handleTouchEvent(event, listener); } return super.onTouchEvent(event); }
3. 文本绘制Y轴偏移导致边界错位
RenderManager的drawClickableText中,文本绘制的Y坐标计算为y + bounds.height() - textSize,但传递给监听器的边界是原始的y值,导致边界与实际文本位置不匹配。
优化代码:
调整drawClickableText的边界计算,确保覆盖实际文本区域:
public void drawClickableText(int x, int y, String text, int color, int textSize, ClickListener clickListener) { Rect bounds = new Rect(); brush.setColor(color); brush.setTextSize(textSize); brush.getTextBounds(text, 0, text.length(), bounds); // 使用y作为文本基线,与drawText默认行为一致 target.drawText(text, x, y, brush); // 基于基线计算正确的上下边界 clickListener.setClickableBounds(x, y - bounds.height(), x + bounds.width(), y); }
内容的提问来源于stack exchange,提问作者rubo77
相关产品推荐
相关产品推荐

