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

欢迎三连

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

相关推荐
快乐就好ya1 小时前
Java多线程
java·开发语言
IT学长编程1 小时前
计算机毕业设计 二手图书交易系统的设计与实现 Java实战项目 附源码+文档+视频讲解
java·spring boot·毕业设计·课程设计·毕业论文·计算机毕业设计选题·二手图书交易系统
CS_GaoMing2 小时前
Centos7 JDK 多版本管理与 Maven 构建问题和注意!
java·开发语言·maven·centos7·java多版本
艾伦~耶格尔2 小时前
Spring Boot 三层架构开发模式入门
java·spring boot·后端·架构·三层架构
man20172 小时前
基于spring boot的篮球论坛系统
java·spring boot·后端
2401_858120533 小时前
Spring Boot框架下的大学生就业招聘平台
java·开发语言
S hh3 小时前
【Linux】进程地址空间
java·linux·运维·服务器·学习
小雨cc5566ru3 小时前
uniapp+Android面向网络学习的时间管理工具软件 微信小程序
android·微信小程序·uni-app
Java探秘者3 小时前
Maven下载、安装与环境配置详解:从零开始搭建高效Java开发环境
java·开发语言·数据库·spring boot·spring cloud·maven·idea
攸攸太上3 小时前
Spring Gateway学习
java·后端·学习·spring·微服务·gateway