如何应对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 小时前
MyBatis的面试题以及详细解答二
java·servlet·tomcat
秋恬意1 小时前
Mybatis能执行一对一、一对多的关联查询吗?都有哪些实现方式,以及它们之间的区别
java·数据库·mybatis
FF在路上2 小时前
Knife4j调试实体类传参扁平化模式修改:default-flat-param-object: true
java·开发语言
真的很上进2 小时前
如何借助 Babel+TS+ESLint 构建现代 JS 工程环境?
java·前端·javascript·css·react.js·vue·html
众拾达人3 小时前
Android自动化测试实战 Java篇 主流工具 框架 脚本
android·java·开发语言
皓木.3 小时前
Mybatis-Plus
java·开发语言
不良人天码星3 小时前
lombok插件不生效
java·开发语言·intellij-idea
守护者1703 小时前
JAVA学习-练习试用Java实现“使用Arrays.toString方法将数组转换为字符串并打印出来”
java·学习
源码哥_博纳软云3 小时前
JAVA同城服务场馆门店预约系统支持H5小程序APP源码
java·开发语言·微信小程序·小程序·微信公众平台
禾高网络3 小时前
租赁小程序成品|租赁系统搭建核心功能
java·人工智能·小程序