NO.2 Android文字&显示变换
零蚀
文字的描绘
-
文字基础
代码如下:
{ paint = new Paint(Paint.ANTI_ALIAS_FLAG); paint.setColor(Color.RED); paint.setStrokeWidth(Utils.dp2px(2)); paint.setTextSize(Utils.dp2px(60)); paint.setTypeface(Typeface.createFromAsset(getContext().getAssets(),"happy.ttf")); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); paint.setStyle(Paint.Style.STROKE); canvas.drawText("新年快乐!!",getWidth()/6,getHeight()/2,paint); }
这里的镂空是 Paint.Style.STROKE 所设置,它还有以下的参数:
- Style 参数:
- Paint.Style.STROKE 描边
- Paint.Style.FILL 填充
- Paint.Style.FILL_AND_STROKE
同时这里还在资源文件 assets 中调用字体样式文件:
paint.setTypeface(Typeface.createFromAsset(getContext().getAssets(),"happy.ttf"));
最后这里需要指定文字绘制的大小,默认绘制区域大小不会随文字的多少和边框而改变,所以要设置 setTextSize 。
- Style 参数:
-
文字的参考系
- 居中设置
paint.getTextBounds("新年快乐!!",0,"新年快乐!!".length(),rect); int offsetX=(rect.left+rect.right)/2; int offsetY=(rect.top+rect.bottom)/2; canvas.drawText("新年快乐!!",getWidth()/2-offsetX,getHeight()/2-offsetY,paint);
其中 paint.getTextBounds 是以基线为准获取字体的 rect 大小,需要的参数有文本,文本长度,和传入结果的 rect。这里的 rect.top…的坐标如图所示:
所以(rect.left+rect.right)/2得到的是X方向文本內的中心点便宜距离,而getWidth()/2 偏移了textWith距离,所以就有了后面的减去offsetX的操作。
除此之外,还有以下方法,可以将x的坐标点移到文本中心:
paint.setTextAlign(Paint.Align.CENTER);
⚠️上述设置会有一定的问题,像aaa这个字符串是非常居中的,但是如果有“b”,“h”等在,由于字符上半部分较长,所以会向下微微平移,而如果有“g”,"j"等在,由于下半部分较长,会使得文本下移,所以在文本变化的动画时候会出现各种文字跳动的情况
文本的顶线和底线不会随着字符的变化而产生变化,所以可以用此方式来设置文本中心的高度来解决文字在动画中的跳动代码如下:
Paint.FontMetrics metrics=new Paint.FontMetrics(); @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); paint.setStyle(Paint.Style.FILL_AND_STROKE); paint.setTextAlign(Paint.Align.CENTER); // 将参数传递给fontMetrics paint.getFontMetrics(metrics); float offsetY=(metrics.ascent+metrics.descent)/2; canvas.drawText("66666",getWidth()/2,(float)getHeight()/2-offsetY,paint); }
- 字体居向
字体的边缘并不是紧紧挨着文字的边缘进行范围划分,而是每个字符的上下左右都会留有一定的空隙,且这些空隙的大小会随着文字的大小发生变化。所以当我们设置**paint.setTextAlign(Paint.Align.LEFT);**文字的X轴方向居左时候,会由于不同大小的字体而拥有不同大小的间距。此时可以用 -rect.left 来设置左边距
paint.getTextBounds("66666",0,"66666".length(),rect); canvas.drawText("66666",-rect.left,(float)getHeight()/2,paint);
-
多行文字
- 基本绘制
{ paint = new TextPaint(); paint.setTextSize(Utils.dp2px(20)); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); /** * spacingmult 字体间距的倍数,默认1 * spacingadd 字体间距的添加默认宽度 * includepad 纵向额外高度 */ staticLayout = new StaticLayout(str,paint,getWidth(), Layout.Alignment.ALIGN_LEFT, 1,0,false); staticLayout.draw(canvas); }
-
文本中插入图片
加载图片的方法:
public Bitmap getBitmap(int width) { BitmapFactory.Options options = new BitmapFactory.Options(); // 只能取到宽高,知道宽高比,进行设置,这样可以节约性能 options.inJustDecodeBounds = true; BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher, options); options.inJustDecodeBounds = false; options.inDensity = options.outWidth; options.inTargetDensity = width; return BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher, options); }
利用截断文字的方法,计算文字绘制的长度:
float cut[] = new float[1]; @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); canvas.drawBitmap(getBitmap((int) Utils.dp2px(100)), getWidth() - Utils.dp2px(100), 100, paint); /** * 截断文字,返回截断点的index * measureForwards 正向left->right * measureWidth[] 截断的数据 */ index = paint.breakText(str, 0, str.length(), true, getWidth(), cut); newIndex = +index; paint.getTextBounds(str, 0, 1, rect); int top; top=Math.abs(rect.top+rect.bottom); Log.e("tag", "onDraw: "+index ); for(int i=0;i<str.length();i=i+ index){ if(top>100 && top< 100+Utils.dp2px(100)+Math.abs(rect.top+rect.bottom)){ index =paint.breakText(str, oldIndex, str.length(), true, getWidth()-Utils.dp2px(100), cut); newIndex+= index; if (index==0)break; }else{ index =paint.breakText(str, newIndex, str.length(), true, getWidth(), cut); newIndex+= index; if (index==0)break; } canvas.drawText(str,oldIndex,newIndex,0,top,paint); top+=Math.abs(rect.top+rect.bottom)*2; oldIndex=newIndex; } }
这里 float cut[] = new float[1]; 计算的是一次绘制的行数。top这里的是坐标,默认是在 (0,0)点,所以top是 文字高度的相反数。文字截断的方式主要是 paint.breakText(),且当前只是计算了靠右情况下的文字安排,其他的情况以后有待开发,这里另一个问题是在str文本在最后一次绘制时index可能为空导致绘制失败,canvas一片空白。所以有了if (index==0)break;
显示变换
-
Clip
canvas.clipPath()有一点需要注意,这个方法开启ANTI_ALIAS_FLAG,是无效的,所以这个api使用时候是会有锯齿的。因为抗锯齿是边缘用颜色渐变来达到模糊的效果,所以切边是不可能达到抗锯齿的效果的,或者自己进行曲线救国方式。
-
常见的效果
- translate(x,y)平移
- roate(degree)旋转
- scale(x,y)缩放
- skew(x,y)错切
-
Camera变换
- Camera实现绕x轴的翻转
- 代码如下
Camera camera=new Camera(); //-----------------------------分割线----------------------------- { camera.rotateX(45); } //-----------------------------分割线----------------------------- @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); canvas.translate(Utils.dp2px(100)/2+400,Utils.dp2px(100)/2+400); camera.applyToCanvas(canvas); canvas.translate(-Utils.dp2px(100)/2-400,-Utils.dp2px(100)/2-400); canvas.drawBitmap(getBitmap(Utils.dp2px(100)),400,400,paint); }
在这里的Camera类似于Unit 3D里面的视角摄像机,且这个Camera是不可以移动的,所以我们只能移动画布。Camera所处的位置是(0,0)的位置,图中是吧canvas向左上移到图片中心点再进行翻转,使图片在canvas上的投影为正常的正面投影(camera的视角点位于图片中心)。
⚠️在这里,canvas移动和Camera的显示是反向的,相当于onDraw的内容反向执行。
- 投影
当摄像机的视角过近导致视图拉扯过大,此时需要拉远视距,方法如下:
camera.setLocation(0,0,-10);
但是这里的参数是英寸,规定了一英寸等于72像素,所以要进行适配,保持翻转度一致:
camera.setLocation(0,0,-10*getResources().getDisplayMetrics().density);
- 案例效果实现
先构建出一次的效果,在进行不断的改变。
图片的绘制方式还是和之前的一致,接下来是对onDraw()的构造:
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//静止边绘制
canvas.save();
canvas.translate(Utils.dp2px(100)/2+400,Utils.dp2px(100)/2+400);
canvas.rotate(-degree);
canvas.clipRect(-Utils.dp2px(100),-Utils.dp2px(100),Utils.dp2px(100),0);
canvas.rotate(degree);
canvas.translate(-Utils.dp2px(100)/2-400,-Utils.dp2px(100)/2-400);
canvas.drawBitmap(getBitmap(Utils.dp2px(100)),400,400,paint);
canvas.restore();
//翻转边绘制
canvas.save();
canvas.translate(Utils.dp2px(100)/2+400,Utils.dp2px(100)/2+400);
canvas.rotate(-degree);
camera.applyToCanvas(canvas);
canvas.clipRect(-Utils.dp2px(100),0,Utils.dp2px(100),Utils.dp2px(100));
canvas.rotate(degree);
canvas.translate(-Utils.dp2px(100)/2-400,-Utils.dp2px(100)/2-400);
canvas.drawBitmap(getBitmap(Utils.dp2px(100)),400,400,paint);
canvas.restore();
}
Camera 的代码需要从下往上看,首先我们将翻起边进行平移然后旋转一定的角度,剪切后再将它移回来,为了保证后续的能继续在完整的图片上绘制clip,而不是一会来的剪切后的图片,我们需要**canvas.save()**和 canvas.restore(),然后再进行静止面的绘制。
设置单次图片翻转的角度
{
camera.rotateX(60);
}
然后使用ValueAnimator不断改变degree就好。
🔗 前言
🔗 Android 自定义控件列表
🔗 NO.1 Android基础绘制
🔗 NO.3 Android属性动画和硬件加速
🔗 NO.4 Android BitMap&MaterialEditText
🔗 NO.5 Android 自定义布局
🔗 NO.6 Android 触摸反馈
🔗 NO.7 Android 拖拽&嵌套滑动