Paint---FontMetrics

本文深入探讨了Paint.FontMetrics的五个核心属性:Ascent、Descent、Leading、Top和Bottom,并通过实例代码展示了这些属性如何受Paint对象中字体大小和类型的影响。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

这一篇接着讲Paint

Paint.FontMetrics,字体属性及测量。

API—Paint.FontMetrics | Android 开发者

这里写图片描述
5个属性,介绍如下:

  1. 基准点是baseline
  2. Ascent是baseline之上至字符最高处的距离
  3. Descent是baseline之下至字符最低处的距离
  4. Leading文档说的很含糊,其实就是行间距
  5. Top指的是指的是最高字符到baseline的值,即ascent的最大值
  6. bottom指的是最下字符到baseline的值,即descent的最大值

为了帮助理解,我特此搜索了不同的示意图。对照示意图,会很容易理解FontMetrics的参数。
pic-1
这里写图片描述
pic-2
这里写图片描述
pic-3
这里写图片描述
pic-4
这里写图片描述
pic-5
这里写图片描述
pic-6
这里写图片描述

测试

//字体属性及测量
public class TestPaintViewFontMetrics extends View {

    private Paint mPaint;

    public TestPaintViewFontMetrics(Context context, AttributeSet attrs) {
        super(context, attrs);
        // TODO Auto-generated constructor stub
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        // TODO Auto-generated method stub
        // super.onDraw(canvas);
        mPaint.setTextSize(55);
        mPaint.setColor(Color.BLACK);
        // FontMetrics对象
        FontMetrics fontMetrics = mPaint.getFontMetrics();
        String text = "abcdefghijklmnopqrstu";
        // 计算每一个坐标
        float baseX = 0;
        float baseY = 100;
        float topY = baseY + fontMetrics.top;
        float ascentY = baseY + fontMetrics.ascent;
        float descentY = baseY + fontMetrics.descent;
        float bottomY = baseY + fontMetrics.bottom;
        float leading = baseY + fontMetrics.leading;

        Log.d("pepe", "baseX    is:" + 0);
        Log.d("pepe", "baseY    is:" + 100);
        Log.d("pepe", "topY     is:" + topY);
        Log.d("pepe", "ascentY  is:" + ascentY);
        Log.d("pepe", "descentY is:" + descentY);
        Log.d("pepe", "bottomY  is:" + bottomY);
        Log.d("pepe", "leading  is:" + leading);

        // 绘制文本
        canvas.drawText(text, baseX, baseY, mPaint);

        // BaseLine描画
        Paint baseLinePaint = new Paint(Paint.ANTI_ALIAS_FLAG);

        baseLinePaint.setColor(Color.RED);
        canvas.drawLine(0, baseY, canvas.getWidth(), baseY, baseLinePaint);

        // Base描画
        canvas.drawCircle(baseX, baseY, 5, baseLinePaint);

        // TopLine描画
        Paint topLinePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        topLinePaint.setColor(Color.LTGRAY);
        canvas.drawLine(0, topY, canvas.getWidth(), topY, topLinePaint);

        // AscentLine描画
        Paint ascentLinePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        ascentLinePaint.setColor(Color.GREEN);
        canvas.drawLine(0, ascentY, canvas.getWidth(), ascentY, ascentLinePaint);

        // DescentLine描画
        Paint descentLinePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        descentLinePaint.setColor(Color.YELLOW);
        canvas.drawLine(0, descentY, canvas.getWidth(), descentY,
                descentLinePaint);

        // ButtomLine描画
        Paint bottomLinePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        bottomLinePaint.setColor(Color.MAGENTA);
        canvas.drawLine(0, bottomY, canvas.getWidth(), bottomY, bottomLinePaint);
    }
}

log如下:
这里写图片描述

从代码中我们可以看到一个很特别的现象,在我们绘制文本之前我们便可以获取文本的FontMetrics属性值,也就是说我们FontMetrics的这些值跟我们要绘制什么文本是无关的,而仅与绘制文本Paint的size和typeface有关。当你改变了paint绘制文字的size或typeface时,FontMetrics中的top、bottom等值就会发生改变。如果我们仅仅更改了文字,这些值是不会发生任何改变的。

我们注意到各个数值都是正数,这是建立在baseY=100的情况下,去掉baseY,重新运行代码,log如下:
这里写图片描述

参照线为baseline,即baseline=0的情况下,其他各线的数值。leading = 0,即行间距=0
以上是根据paint设置,获取相关的FontMetrics属性,并且只绘制了一行字符串,我们猜想,如果是多行,是否可以获得行间距leanding,代码如下:

        TextView textView = (TextView) findViewById(R.id.textView1);
        String text =  "abcdefghijklmnopqrstuabcdefghijklmnopqrstuabcdefghijklmnopqrstuabcdefghijklmnopqrstuabcdefghijklmnopqrstu"; 
        textView.setTextSize(55);
        textView.setText(text);

        FontMetrics fontMetrics = textView.getPaint().getFontMetrics();

        // 计算每一个坐标
        float topY = fontMetrics.top;
        float ascentY = fontMetrics.ascent;
        float descentY = fontMetrics.descent;
        float bottomY = fontMetrics.bottom;
        float leading = fontMetrics.leading;

        Log.d("pepe", "topY     is:" + topY);
        Log.d("pepe", "ascentY  is:" + ascentY);
        Log.d("pepe", "descentY is:" + descentY);
        Log.d("pepe", "bottomY  is:" + bottomY);
        Log.d("pepe", "leading  is:" + leading);

log如下:
这里写图片描述

显然,即使是多行的情况下,仍不能获得leading

如果text是单行,获得各个属性将会怎样,代码如下:

        TextView textView = (TextView) findViewById(R.id.textView1);
        String text = "abcdefghijklmj";
        textView.setTextSize(55);
        textView.setText(text);

        FontMetrics fontMetrics = textView.getPaint().getFontMetrics();

        // 计算每一个坐标
        float topY = fontMetrics.top;
        float ascentY = fontMetrics.ascent;
        float descentY = fontMetrics.descent;
        float bottomY = fontMetrics.bottom;
        float leading = fontMetrics.leading;

        Log.d("pepe", "topY     is:" + topY);
        Log.d("pepe", "ascentY  is:" + ascentY);
        Log.d("pepe", "descentY is:" + descentY);
        Log.d("pepe", "bottomY  is:" + bottomY);
        Log.d("pepe", "leading  is:" + leading);

log如下:
这里写图片描述

与多行获得的属性都相同。

结论:
A:虽然paint和textView所设置的textSize均为55,且为相同的字符串,但是两个获得的FontMetrics属性值并不相同。但是,我们发现,做除法之后,均为2倍关系。做出猜测,即Paint下,为mdpi对应的size,而TextView的size已经关联到了显示屏幕本身的320dip。所以获得属性值均为整2倍数
B:各种情况下,均未获得leading值。

测试代码:

    float scale = this.getResources().getDisplayMetrics().density;
    Log.d("pepe", "scale     is:" + scale);

log:
这里写图片描述

源码下载

引用:
Android字符串进阶之三:字体属性及测量(FontMetrics) - 小新专栏 - 51CTO技术博客

开发基于Java的贪吃蛇游戏是一个非常适合初学者的项目,它不仅能够帮助理解Java编程语言的基础知识,还能深入掌握面向对象编程、图形用户界面(GUI)设计、事件驱动编程、多线程处理等高级特性。以下是实现贪吃蛇游戏的基本步骤和技术要点: ### 游戏核心组件设计 1. **游戏窗口**:使用`JFrame`创建主窗口,设置窗口大小、关闭操作、不可调整大小等属性。 2. **游戏面板**:继承`JPanel`类来创建游戏面板,用于绘制游戏元素,如蛇、食物等。重写`paintComponent(Graphics g)`方法来进行自定义绘制。 3. **游戏逻辑**:在游戏面板中加入定时器(如`Timer`类),定期更新游戏状态,包括蛇的位置变化、食物的生成、碰撞检测等。 ### 蛇的设计与实现 - 蛇可以表示为一系列坐标点的集合,通常使用`ArrayList<Point>`来存储每个身体部分的位置。 - 蛇的移动通过不断更新头部位置并移除尾部位置实现,当吃到食物时增加新的头部位置而不移除尾部,从而实现增长效果。 - 控制蛇的方向需要监听键盘事件,允许玩家通过方向键控制蛇的移动方向。 ### 食物的生成 - 食物的位置应该随机生成,并确保不在蛇的身体上。 - 当蛇头与食物位置相同时,视为吃到了食物,此时需要生成新的食物位置,并增加蛇的长度。 ### 碰撞检测 - 检测蛇头是否与墙壁碰撞或与自身碰撞,一旦发生碰撞则游戏结束。 - 使用简单的条件判断即可实现,例如检查蛇头的坐标是否超出游戏区域或者是否存在于蛇身的坐标列表中。 ### 示例代码片段 下面是一个简化的贪吃蛇游戏的核心逻辑示例,包括游戏面板的创建、蛇的移动、食物的生成以及碰撞检测: ```java import javax.swing.*; import java.awt.*; import java.awt.event.*; import java.util.ArrayList; import java.util.List; import java.util.Random; public class SnakeGame extends JFrame { private static final int WIDTH = 800; private static final int HEIGHT = 600; private static final int DOT_SIZE = 10; private static final int ALL_DOTS = 3600; private static final int RAND_POS = 29; private static final int DELAY = 140; private final List<Point> dots = new ArrayList<>(); private Point apple = new Point(); private char direction = 'R'; private boolean leftDirection = false; private boolean rightDirection = true; private boolean upDirection = false; private boolean downDirection = false; private boolean inGame = true; public SnakeGame() { initUI(); } private void initUI() { add(new TAdapter()); setResizable(false); pack(); setTitle("Snake Game"); setLocationRelativeTo(null); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); setVisible(true); startGame(); } private void startGame() { dots.clear(); for (int z = 0; z < 3; z++) { dots.add(new Point(50 - z * 10, 50)); } locateApple(); Timer timer = new Timer(DELAY, e -> { if (inGame) { checkApple(); checkCollision(); move(); repaint(); } else { timer.stop(); } }); timer.start(); } private void move() { Point head = dots.get(0); int x = head.x; int y = head.y; switch (direction) { case 'L' -> x -= DOT_SIZE; case 'R' -> x += DOT_SIZE; case 'U' -> y -= DOT_SIZE; case 'D' -> y += DOT_SIZE; } dots.add(0, new Point(x, y)); if (!inGame) { return; } if (dots.size() > 4) { checkCollision(); } if (!inGame) { return; } if (x >= WIDTH || x < 0 || y >= HEIGHT || y < 0) { inGame = false; } } private void checkApple() { if (dots.get(0).equals(apple)) { dots.add(new Point(0, 0)); locateApple(); } } private void checkCollision() { Point head = dots.get(0); for (int z = 4; z < dots.size(); z++) { if (head.equals(dots.get(z))) { inGame = false; } } } private void locateApple() { int r = new Random().nextInt(RAND_POS); int apple_x = r * DOT_SIZE; r = new Random().nextInt(RAND_POS); int apple_y = r * DOT_SIZE; apple = new Point(apple_x, apple_y); } @Override public void paint(Graphics g) { super.paint(g); if (inGame) { g.setColor(Color.red); g.fillOval(apple.x, apple.y, DOT_SIZE, DOT_SIZE); for (Point dot : dots) { g.setColor(Color.green); g.fillRect(dot.x, dot.y, DOT_SIZE, DOT_SIZE); } } else { gameOver(g); } } private void gameOver(Graphics g) { String msg = "Game Over"; Font small = new Font("Helvetica", Font.BOLD, 14); FontMetrics fm = getFontMetrics(small); g.setColor(Color.black); g.setFont(small); g.drawString(msg, (WIDTH - fm.stringWidth(msg)) / 2, HEIGHT / 2); } private class TAdapter extends KeyAdapter { @Override public void keyPressed(KeyEvent e) { int key = e.getKeyCode(); if ((key == KeyEvent.VK_LEFT) && (!rightDirection)) { leftDirection = true; upDirection = false; downDirection = false; direction = 'L'; } if ((key == KeyEvent.VK_RIGHT) && (!leftDirection)) { rightDirection = true; upDirection = false; downDirection = false; direction = 'R'; } if ((key == KeyEvent.VK_UP) && (!downDirection)) { upDirection = true; rightDirection = false; leftDirection = false; direction = 'U'; } if ((key == KeyEvent.VK_DOWN) && (!upDirection)) { downDirection = true; rightDirection = false; leftDirection = false; direction = 'D'; } } } public static void main(String[] args) { EventQueue.invokeLater(() -> { var ex = new SnakeGame(); ex.setVisible(true); }); } } ``` 以上代码提供了一个基础的贪吃蛇游戏框架,可以根据个人需求进一步扩展功能,比如增加得分系统、难度等级、音效等。通过这个项目的学习,可以加深对Java编程的理解和应用能力。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值