如何应对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实现吸顶效果;

欢迎三连

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

相关推荐
李少兄1 小时前
Unirest:优雅的Java HTTP客户端库
java·开发语言·http
此木|西贝1 小时前
【设计模式】原型模式
java·设计模式·原型模式
可乐加.糖2 小时前
一篇关于Netty相关的梳理总结
java·后端·网络协议·netty·信息与通信
s9123601012 小时前
rust 同时处理多个异步任务
java·数据库·rust
9号达人2 小时前
java9新特性详解与实践
java·后端·面试
cg50172 小时前
Spring Boot 的配置文件
java·linux·spring boot
啊喜拔牙2 小时前
1. hadoop 集群的常用命令
java·大数据·开发语言·python·scala
anlogic3 小时前
Java基础 4.3
java·开发语言
omegayy3 小时前
Unity 2022.3.x部分Android设备播放视频黑屏问题
android·unity·视频播放·黑屏
非ban必选3 小时前
spring-ai-alibaba第七章阿里dashscope集成RedisChatMemory实现对话记忆
java·后端·spring