如何应对Android面试官->文字中心绘制和颜色渐变,实战头条炫酷ViewPager指示器

前言

本章要实现的效果

自定义TextView文字绘制

我们在进行文字的自定义绘制的时候,通过不会通过 setText 的方式,而是重写 onDraw 方法,通过 Canvas 的 drawText 方法来实现,整体实现方式如下:

less 复制代码
public class CustomTextView extends AppCompatTextView {    
    public CustomTextView(@NonNull Context context) {        
        super(context);    
    }    
    public CustomTextView(@NonNull Context context, @Nullable AttributeSet attrs) {        
        super(context, attrs);    
    }    
    public CustomTextView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {        
        super(context, attrs, defStyleAttr);    
    }    
    @Override    
    protected void onDraw(Canvas canvas) {        
        super.onDraw(canvas);        
        Paint paint = new Paint();        
        canvas.drawText("我是老A", 0, 0, paint);    
    }
}

我们运行看下效果:

运行,并没有在屏幕上看到『我是老A』这几个字,这是为什么呢?我们来一探究竟

我们来看一下 drawText 的参数注释:

float y,是 text 的 baseline,这个 baseline 是什么鬼呢?

baseline

上图中的的那条红线,就是 baseline;大部分文字都以这条线为基准线,保证文字的对齐;而文字绘制的起始点,就是第一个文字的左下角,也就是下图剪头指的位置

y值 如果设置成0,文字其实就跑到屏幕外面了,所以我们的 y 值不能为0,我们修改下 y 值,然后看下效果:

可以看到 『我是老A』文字绘制了出来;因为没有设置文字的大小,所以看着会比较小;

也就是说:无论什么文字,Android 系统都会让其在某一条线上对齐,而这条对齐的线,就是 baseline,也叫做基准线;

有人可能会说了,为什么不把『我是老A』绘制到屏幕X轴中间位置,这样方便看,好的,那我们把这几个字绘制到屏幕的中间位置,为了便于看到中间位置,我们现在屏幕中心画一条竖线

scss 复制代码
private void drawCenterLineX(Canvas canvas) {    
    paint.setStyle(Paint.Style.FILL);    
    paint.setColor(Color.RED);    
    paint.setStrokeWidth(3);    
    canvas.drawLine(getWidth() / 2,0, getWidth()/2, getHeight(), paint);
}

我们在屏幕 X 轴上画了一条竖线;然后我们把 文字绘制到 X 轴中心位置

less 复制代码
canvas.drawText("我是老A", getWidth() / 2, 200, paint);

运行看下效果:

咦咦咦,好像和我们期望的不一样,整体文字偏右了。。。那么怎么解决呢?我们需要设置文字的对齐方式

less 复制代码
paint.setTextAlign(Paint.Align.CENTER);
canvas.drawText("我是老A", getWidth() / 2, 200, paint);

我们在编译运行看下效果:

这次文字不是从中心线的左侧开始绘制了;也就是说,如果我们不设置 TextAlign,那么默认的 TextAlign 是从左边开始;

我们现在是 X 轴的中心位置,那么如何绘制到 Y 轴的中心位置呢?我们先来画一条 Y 轴中心线

scss 复制代码
private void drawCenterLineY(Canvas canvas) {    
    paint.setStyle(Paint.Style.FILL);    
    paint.setColor(Color.RED);    
    paint.setStrokeWidth(3);    
    canvas.drawLine(0,getHeight() / 2, getWidth(), getHeight() / 2, paint);
}

那么,我们如何把文字绘制到中心位置呢?可能大家会说 Y 值直接除以 2,好,我们试一下

less 复制代码
paint.setTextAlign(Paint.Align.CENTER);
canvas.drawText("我是老A", getWidth() / 2, getHeight() / 2, paint);

运行看下效果:

看起来,还是没有达到我们期望效果,希望文字在Y轴方向上平分,那么我们需要知道文字的高度才能进行向下绘制;那么文字的高度如何计算呢?

文字高度计算

我们需要使用 FontMetrics 来进行文字的高度测量,我们点进去看下这个类的实现:

这几个值,正好对应的就是下图的这几个值

那么文字的高度具体怎么计算呢?本质就是 ascent + descent 的值,但是根据 Android 坐标系来看 X 轴是右正左负,Y 轴是下正上负,也就是说根据基准线,我们的 ascent 值是 负值,descent 是正值,所以文字的高度最终的计算方式应是 descent - ascent 的值

所以 Y 值应该是 getHeight()/2 + ((fontMetircs.descent - fontMetircs.descent) / 2)

less 复制代码
canvas.drawText("我是老A", getWidth() / 2, 
    getHeight() / 2 + ((fontMetrics.descent - fontMetrics.ascent)/2), 
    paint);

我们运行看下效果:

咦,还是不对呀,文字又偏下了,这是为什么呢?

文字绘制的(0, 0)点,是基于基准线来绘制的,也就是文字的左下角,如果我们用文字高度的一半的话,Y 轴的值就是偏大的,因为基准线的位置没有在文字高度的一半,所以需要减去一部分高度,这个减去的高度就是 descent;

less 复制代码
canvas.drawText("我是老A", getWidth() / 2, 
    getHeight() / 2 + ((fontMetrics.descent - fontMetrics.ascent)/2) - fontMetrics.descent, 
    paint);

运行看下效果:

可以看到,这次我们的文字 绘制到了中心位置!

接下来,可能有人会问了,不用 TextAlign 可以将文字绘制到 X 轴中心位置吗?答案是可以的,我们只需要计算出文字的宽度,然后用屏幕宽度的一半减去文字宽度的一半,就可以算出起始位置;那么文字的宽度应该如何计算呢?

文字宽度的计算

使用 Paint 的 measureText 方法来获取文字的宽度,我们将 TextAlign 注释掉

scss 复制代码
Paint.FontMetrics fontMetrics = paint.getFontMetrics();
// paint.setTextAlign(Paint.Align.CENTER);
float width = paint.measureText("我是老A");
canvas.drawText("我是老A", getWidth() / 2 - width / 2 , 
    getHeight() / 2 + ((fontMetrics.descent - fontMetrics.ascent)/2) - fontMetrics.descent, 
    paint);

运行看下效果:

依然是中心位置;

自定义TextView颜色渐变

我们的文字绘制的时候,是不可能只绘制一半的,那么我们就需要对画布进行裁剪,而 canvas 也给我们提供了一系列的 API 来进行画布的裁切;

也就是说,我们对画布进行裁剪,裁剪之后,能绘制多少就会绘制多少,看个例子:

scss 复制代码
private void drawText(Canvas canvas) {    
    canvas.save();    
    paint.setTextSize(40);    
    Paint.FontMetrics fontMetrics = paint.getFontMetrics();    
    float width = paint.measureText("我是老A");    
    float baseLine = getHeight() / 2 + ((fontMetrics.descent - fontMetrics.ascent) / 2) - fontMetrics.descent;    
    float x = getWidth() / 2 - width / 2;    
    Rect rect = new Rect((int) x, 0, 330, getHeight());    
    canvas.clipRect(rect);    
    canvas.drawText("我是老A", x,            
            baseLine,            
            paint);    
    canvas.restore();
}

运行看下效果:

裁剪的画布只能够把『我』字绘制出来;

如果我们想实现文字的渐变,那么我们就可以用两层画布,一层是黑色,一层是红色,然后我们计算每一个字在整个文字占的百分比,从而设置 Rect 的右边界的值;

scss 复制代码
private void drawTextBlack(Canvas canvas) {    
    paint.setTextSize(40);    
    paint.setColor(Color.BLACK);    
    Paint.FontMetrics fontMetrics = paint.getFontMetrics();    
    float width = paint.measureText("我是老A");    
    float baseLine = getHeight() / 2 + ((fontMetrics.descent - fontMetrics.ascent) / 2) - fontMetrics.descent;    
    float x = getWidth() / 2 - width / 2;    
    canvas.drawText("我是老A", x,            
        baseLine,            
        paint);
}

private void drawTextRed(Canvas canvas) {    
    canvas.save();    
    paint.setTextSize(40);    
    Paint.FontMetrics fontMetrics = paint.getFontMetrics();    
    float width = paint.measureText("我是老A");    
    float baseLine = getHeight() / 2 + ((fontMetrics.descent - fontMetrics.ascent) / 2) - fontMetrics.descent;    
    float x = getWidth() / 2 - width / 2;    
    float right = x + width*mPercent;    
    Rect rect = new Rect((int) x, 0, (int) right, getHeight());    
    canvas.clipRect(rect);    
    canvas.drawText("我是老A", x,            
        baseLine,            
        paint);    
    canvas.restore();
}

我们需要动态的改变 mPercent 的值,可以通过属性动画,也可以通过 ViewPager 的手势滑动,我们先用属性动画来看下效果;

scss 复制代码
ObjectAnimator.ofFloat(view, "percent", 0f, 1f).setDuration(5000).start()

我们运行看下效果:

我们实现了文字的渐变效果;

ViewPager 的滑动监听,可以通过 onPageScrolled 回调设置

java 复制代码
mViewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {    
    @Override    
    public void onPageSelected(int position) {    
    }    
    @Override    
    public void onPageScrolled(int position, float positionOffset,                               
            int positionOffsetPixels) {        
        if (positionOffset > 0) {            
            CustomTextView left = mTabs.get(position);            
            CustomTextView right = mTabs.get(position + 1);                        left.setDirection(ColorChangeTextView1.DIRECTION_RIGHT);            
            right.setDirection(ColorChangeTextView1.DIRECTION_LEFT);            
            Log.v(TAG, positionOffset + "");            
            left.setProgress(1 - positionOffset);            
            right.setProgress(positionOffset);        
        }    
    }    
    @Override    
    public void onPageScrollStateChanged(int state) {    
    }
});

运行,实现我们开头的效果;

简历润色

简历上可写:深度理解文字绘制原理,可实现炫酷的文字绘制效果;

下一章预告

玩转RV,使用RecyclerView实现吸顶效果;

欢迎三连

来都来了,点个关注,点个赞吧,你的支持是我最大的动力~~~

相关推荐
qq_353737541 分钟前
网站 Favicon 获取 API 技术实现指南
android
小碗羊肉3 分钟前
【从零开始学Java | 第三十篇】不可变集合
java·开发语言
鱼鳞_4 分钟前
Java学习笔记_Day21(Set)
java·笔记·学习
五阿哥永琪6 分钟前
record只读类
java·开发语言
sjmaysee7 分钟前
mysql之联合索引
java
stevenzqzq8 分钟前
Android Navigation 组件页面跳转方法说明
android·compose
HoneyMoose13 分钟前
Markdown 为什么会被更广泛的使用
java
Kapaseker16 分钟前
Compose 响应式布局要变天—入门 FlexBox
android·kotlin
极创信息21 分钟前
不同开发语言程序如何做信创适配认证?完整流程与评价指标有哪些
java·c语言·开发语言·python·php·ruby·hibernate
Seven9723 分钟前
用300行代码手写SpringBoot核心原理
java