文章目录
- 来源
- 字体medium
- FontMetrics
- textSize这是为70F的时候
- 自己绘制的线条 top、ascent、descent、bottom
- baseLine 通过ascent和descent计算, top和bottom计算出来的还不一样
- TextPaint 和 StaticLayout
- breakText (CharSequence text, int start, int end, boolean measureForwards, float maxWidth, float[] measuredWidth)
- setUnderlineText(boolean underlineText)
- setTypeface(Typeface typeface)
- Typyface.defaultFromStyle(int style)
- Typyface.create(String familyName, int style)和create(Typeface family, int style)
- Typeface.createFromAsset(AssetManager mgr, String path)、createFromFile(String path)和createFromFile(File path)
- setTextSkewX(float skewX)
- setTextSize (float textSize)
- setTextScaleX (float scaleX)
- setTextAlign (Paint.Align align)
- setSubpixelText (boolean subpixelText)
- setStrikeThruText (boolean strikeThruText)
- setLinearText (boolean linearText)
- setFakeBoldText (boolean fakeBoldText)
- measureText (String text),measureText (CharSequence text, int start, int end),measureText (String text, int start, int end),measureText (char[] text, int index, int count)
- setDither(boolean dither)
- setMaskFilter(MaskFilter maskfilter)
- BlurMaskFilter
- EmbossMaskFilter
- setRasterizer (Rasterizer rasterizer)
- setPathEffect(PathEffect effect)
- 心电图
- setStrokeCap(Paint.Cap cap)
- setStrokeJoin(Paint.Join join)
- setShadowLayer(float radius, float dx, float dy, int shadowColor)
来源
字体medium
<item name="android:fontFamily">sans-serif</item>
<item name="android:textStyle">normal</item>
<item name="android:fontFamily">sans-serif</item>
<item name="android:textStyle">italic</item>
<item name="android:fontFamily">sans-serif</item>
<item name="android:textStyle">bold</item>
<item name="android:fontFamily">sans-serif</item>
<item name="android:textStyle">bold|italic</item>
<item name="android:fontFamily">sans-serif-light</item>
<item name="android:textStyle">normal</item>
<item name="android:fontFamily">sans-serif-light</item>
<item name="android:textStyle">italic</item>
<item name="android:fontFamily">sans-serif-thin</item>
<item name="android:textStyle">normal</item>
<item name="android:fontFamily">sans-serif-thin</item>
<item name="android:textStyle">italic</item>
<item name="android:fontFamily">sans-serif-condensed</item>
<item name="android:textStyle">normal</item>
<item name="android:fontFamily">sans-serif-condensed</item>
<item name="android:textStyle">italic</item>
<item name="android:fontFamily">sans-serif-condensed</item>
<item name="android:textStyle">bold</item>
<item name="android:fontFamily">sans-serif-condensed</item>
<item name="android:textStyle">bold|italic</item>
5.0 以上
<item name="android:fontFamily">sans-serif-medium</item>
<item name="android:textStyle">normal</item>
<item name="android:fontFamily">sans-serif-medium</item>
<item name="android:textStyle">italic</item>
<item name="android:fontFamily">sans-serif-black</item>
<item name="android:textStyle">italic</item>
FontMetrics
名为字体测量,是Paint的一个内部类
/**
* Class that describes the various metrics for a font at a given text size.
* Remember, Y values increase going down, so those values will be positive,
* and values that measure distances going up will be negative. This class
* is returned by getFontMetrics().
*/
public static class FontMetrics {
/**
* The maximum distance above the baseline for the tallest glyph in
* the font at a given text size.
*/
public float top;
/**
* The recommended distance above the baseline for singled spaced text.
*/
public float ascent;
/**
* The recommended distance below the baseline for singled spaced text.
*/
public float descent;
/**
* The maximum distance below the baseline for the lowest glyph in
* the font at a given text size.
*/
public float bottom;
/**
* The recommended additional space to add between lines of text.
*/
public float leading;
}
这张图很简单但是也很扼要的说明了top,ascent,descent,bottom,leading这五个参数。
在Android中,t文字的绘制都是从Baseline处开始的。
top; top的意思其实就是除了Baseline到字符顶端的距离外还应该包含这些符号的高度
ascent; baseline往上至字符最高处的距离,上坡度
descent; baseline往下至字符最底处的距离,下坡度
bottom; bottom的意思也是一样,一般情况下我们极少使用到类似的符号所以往往会忽略掉这些符号的存在
leading; 表示上一行字符的sescent到该行字符的ascent之间的距离,行间距
class FontView(context: Context?, attrs: AttributeSet?) : View(context, attrs) {
val TEXT = "ap爱哥ξτβбпшㄎㄊěǔぬも┰┠№@↓";
val mPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
this.textSize = 50F
this.color = Color.BLACK
}
init {
val fontMetrics = mPaint.fontMetrics
Log.d("flannery", "ascent = ${fontMetrics.ascent}")
Log.d("flannery", "top = ${fontMetrics.top}")
Log.d("flannery", "leading = ${fontMetrics.leading}")
Log.d("flannery", "descent = ${fontMetrics.descent}")
Log.d("flannery", "bottom = ${fontMetrics.bottom}")
}
override fun onDraw(canvas: Canvas?) {
super.onDraw(canvas)
canvas?.drawText(TEXT, 0F, Math.abs(mPaint.fontMetrics.top), mPaint)
}
}
D/flannery: ascent = -46.38672
D/flannery: top = -52.807617
D/flannery: leading = 0.0
D/flannery: descent = 12.207031
D/flannery: bottom = 13.549805
我们将文本绘制的起点Y坐标向下移动Math.abs(fontMetrics.top)个单位(注:top是负数),相当于把文本的Baseline向下移动Math.abs(fontmetrics.top)个单位,此时文本的顶部刚好会和屏幕底部重合
从代码中我们可以看到一个很特别的现象,在我们绘制文本之前我们便可以获取文本的FontMetrics属性
textSize这是为70F的时候
D/flannery: ascent = -64.94141
D/flannery: top = -73.930664
D/flannery: leading = 0.0
D/flannery: descent = 17.089844
D/flannery: bottom = 18.969727
- setTypeface这是为Typeface.SERIF的时候
D/flannery: ascent = -74.819336
D/flannery: top = -73.34961
D/flannery: leading = 0.0
D/flannery: descent = 20.507813
D/flannery: bottom = 17.5
所有的值也改变了,那么我们知道这样的一个东西有什么用呢?如上所说文本的绘制从Baseline开始,并且Baseline并非文本的分割线,当我们想让文本绘制的时候居中屏幕或其他的东西就需要计算Baseline的Y轴坐标,
比如我们让我们的文本居中画布:
class FontView2(context: Context?, attrs: AttributeSet?) : View(context, attrs) {
val TEXT = "ap爱哥ξτβбпшㄎㄊěǔぬも┰┠№@↓";
var baseX = 0F
var baseY = 0F
val mPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
this.color = Color.BLACK
}
val textPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
this.style = Paint.Style.STROKE
this.textSize = 70F
this.strokeWidth = 1F
this.color = Color.RED
}
init {
val fontMetrics = mPaint.fontMetrics
Log.d("flannery", "ascent = ${fontMetrics.ascent}")
Log.d("flannery", "top = ${fontMetrics.top}")
Log.d("flannery", "leading = ${fontMetrics.leading}")
Log.d("flannery", "descent = ${fontMetrics.descent}")
Log.d("flannery", "bottom = ${fontMetrics.bottom}")
}
override fun onDraw(canvas: Canvas?) {
super.onDraw(canvas)
canvas?.let { c->
//计算Baseline绘制的起点X轴坐标
baseX = c.width.div(2).toFloat() - textPaint.measureText(TEXT) / 2
//计算Baseline绘制的Y坐标
baseY = c.height.div(2).toFloat() - (textPaint.descent() + textPaint.ascent()).div(2)
c.drawText(TEXT, baseX, baseY, textPaint)
// 为了便于理解我们在画布中新绘制一条中线
c.drawLine(0F, c.height.div(2).toFloat(), c.width.toFloat(),c.height.div(2).toFloat(), mPaint)
}
}
}
自己绘制的线条 top、ascent、descent、bottom
val centerTop = baseLine + top
val centerAscent = baseLine + ascent
val centerDescent = baseLine + descent
val centerBottom = baseLine + bottom
mPaint.color = Color.RED
canvas.drawLine(0f, centerTop, cwidth, centerTop, mPaint)
mPaint.color = Color.GREEN
canvas.drawLine(0f, centerAscent, cwidth, centerAscent, mPaint)
mPaint.color = Color.BLUE
canvas.drawLine(0f, centerDescent, cwidth, centerDescent, mPaint)
mPaint.color = Color.CYAN
canvas.drawLine(0f, centerBottom, cwidth, centerBottom, mPaint)
baseLine 通过ascent和descent计算, top和bottom计算出来的还不一样
class FontView2(context: Context?, attrs: AttributeSet?) : View(context, attrs) {
val TEXT = "X赵ξτㄎ";
val mPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
this.color = Color.BLACK
this.style = Paint.Style.FILL_AND_STROKE
this.strokeWidth = 2F
}
override fun onDraw(canvas: Canvas?) {
super.onDraw(canvas)
canvas?.let { c ->
drawBaseline(c)
//drawCenter(c)
}
}
fun drawBaseline(canvas: Canvas) {
mPaint.textSize = 250F //必须在测量宽度之前
val halfTextWidth = mPaint.measureText(TEXT).div(2)
val fontMetrics = mPaint.fontMetrics
Log.d("flannery", "ascent = ${fontMetrics.ascent}")
Log.d("flannery", "top = ${fontMetrics.top}")
Log.d("flannery", "leading = ${fontMetrics.leading}")
Log.d("flannery", "descent = ${fontMetrics.descent}")
Log.d("flannery", "bottom = ${fontMetrics.bottom}")
//计算Baseline绘制的起点X轴坐标
val cwidth = canvas.width.toFloat()
val cheight = canvas.height.toFloat()
val halfWidth = cwidth.div(2)
val halfHeight = cheight.div(2)
// 十字中线
mPaint.color = Color.BLACK
mPaint.strokeWidth = 5F
canvas.drawLine(0F, halfHeight, cwidth, halfHeight, mPaint)
canvas.drawLine(halfWidth, 0F, halfWidth, cheight, mPaint)
mPaint.strokeWidth = 1F
val ascent = mPaint.fontMetrics.ascent
val top = mPaint.fontMetrics.top
val leading = mPaint.fontMetrics.leading
val descent = mPaint.fontMetrics.descent
val bottom = mPaint.fontMetrics.bottom
val halfTextHeightTopBottom = (bottom + top).div(2)
val halfTextHeightCescent = (descent + ascent).div(2)
val baseLineHalfHeight = halfHeight //baseline 跟中心重合
val baseLineTextCenterY = halfHeight - (ascent+descent).div(2)
val baseLine = baseLineTextCenterY
val centerYTopBottom = baseLine + halfTextHeightTopBottom
val centerYSCent = baseLine + halfTextHeightCescent
//画文字
mPaint.color = Color.BLACK
canvas.drawText(TEXT, halfWidth - halfTextWidth, baseLine, mPaint)
// 画top。。。。
if(true) {
val centerTop = baseLine + top
val centerAscent = baseLine + ascent
val centerDescent = baseLine + descent
val centerBottom = baseLine + bottom
mPaint.color = Color.RED
canvas.drawLine(0f, centerTop, cwidth, centerTop, mPaint)
mPaint.color = Color.GREEN
canvas.drawLine(0f, centerAscent, cwidth, centerAscent, mPaint)
mPaint.color = Color.BLUE
canvas.drawLine(0f, centerDescent, cwidth, centerDescent, mPaint)
mPaint.color = Color.CYAN
canvas.drawLine(0f, centerBottom, cwidth, centerBottom, mPaint)
}
// 中线
if(true) {
mPaint.color = Color.RED
canvas.drawLine(0f, centerYTopBottom, cwidth, centerYTopBottom, mPaint)
mPaint.color = Color.GREEN
canvas.drawLine(0f, centerYSCent, cwidth, centerYSCent, mPaint)
}
}
// fun drawCenter(c: Canvas) {
// //计算Baseline绘制的起点X轴坐标
// val cwidth = c.width.toFloat()
// val cheight = c.height.toFloat()
//
// val baseX = cwidth.div(2) - textPaint.measureText(TEXT) / 2
// //计算Baseline绘制的Y坐标
// val baseY = cheight.div(2) - (textPaint.descent() + textPaint.ascent()).div(2)
//
// c.drawText(TEXT, baseX, baseY, textPaint)
// // 为了便于理解我们在画布中新绘制一条中线
// c.drawLine(
// 0F
// , cheight.div(2)
// , cwidth
// , cheight.div(2)
// , mPaint
// )
// c.drawLine(
// textPaint.measureText(TEXT).div(2)
// , 0F
// , textPaint.measureText(TEXT).div(2)
// , cheight
// , mPaint
// )
// }
override fun onTouchEvent(event: MotionEvent?): Boolean {
Log.d("flannery", "x = ${event?.getX()} , y = ${event?.getY()}")
return super.onTouchEvent(event)
}
}
TextPaint 和 StaticLayout
class StaticLayoutView(context: Context?, attrs: AttributeSet?) : View(context, attrs) {
val TEXT =
"This is used by widgets to control text layout. You should not need to use this class directly unless you are implementing your own widget or custom display object, or would be tempted to call Canvas.drawText() directly.";
val mPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
this.color = Color.BLACK
this.style = Paint.Style.FILL_AND_STROKE
this.strokeWidth = 2F
}
@SuppressLint("DrawAllocation")
override fun onDraw(canvas: Canvas?) {
super.onDraw(canvas)
canvas?.let { canvas ->
val paint = TextPaint(Paint.ANTI_ALIAS_FLAG)
.apply {
this.textSize = 50F
this.color = Color.BLACK
}
Log.d("flannery", "ascent = ${paint.fontMetrics.ascent}")
Log.d("flannery", "top = ${paint.fontMetrics.top}")
Log.d("flannery", "leading = ${paint.fontMetrics.leading}")
Log.d("flannery", "descent = ${paint.fontMetrics.descent}")
Log.d("flannery", "bottom = ${paint.fontMetrics.bottom}")
Log.d("flannery", "ascent() = ${paint.ascent()}")
// Log.d("flannery", "top() = ${paint.top()}")
// Log.d("flannery", "leading() = ${paint.leading()}")
Log.d("flannery", "descent() = ${paint.descent()}")
// Log.d("flannery", "bottom() = ${paint.bottom()}")
/*
D/flannery: ascent = -46.38672
D/flannery: top = -52.807617
D/flannery: leading = 0.0
D/flannery: descent = 12.207031
D/flannery: bottom = 13.549805
D/flannery: ascent() = -46.38672
D/flannery: descent() = 12.207031
*/
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
StaticLayout.Builder
.obtain(TEXT, 0, TEXT.length, paint, getWidth())
.setAlignment(Layout.Alignment.ALIGN_NORMAL)
.setLineSpacing(0.0F, 1.0F)
.setIncludePad(false)
.build()
.draw(canvas)
} else {
StaticLayout(
TEXT,
paint,
getWidth(),
Layout.Alignment.ALIGN_NORMAL,
1.0F,
0.0F,
false
).draw(canvas)
}
canvas.restore()
}
}
}
breakText (CharSequence text, int start, int end, boolean measureForwards, float maxWidth, float[] measuredWidth)
val paint = TextPaint(Paint.ANTI_ALIAS_FLAG).apply {
this.color = Color.BLACK
this.textSize = 50F
}
var measuredHeight:FloatArray = floatArrayOf()
// TEXT 原有字符串
// start 从0开始
// 10 到第10个结束
// measureForwards表示向前还是向后测量
// maxWidth表示一个给定的最大宽度在这个宽度内能测量出几个字符
// 可以为空
val breakText = paint.breakText(TEXT, 0, 10, false, paint.measureText(TEXT), measuredHeight)
StaticLayout(
TEXT.subSequence(0,breakText),
paint,
canvas.getWidth(),
Layout.Alignment.ALIGN_NORMAL,
1.0F,
0.0F,
false
).draw(canvas)
setUnderlineText(boolean underlineText)
设置下划线
setTypeface(Typeface typeface)
设置字体类型,上面我们也使用过,Android中字体有四种样式:
- BOLD(加粗),
- BOLD_ITALIC(加粗并倾斜)
- ITALIC(倾斜),
- NORMAL(正常);
而其为我们提供的字体有五种:DEFAULT,DEFAULT_BOLD,MONOSPACE,SANS_SERIF和SERIF,这些什么类型啊、字体啊之类的都很简单大家自己去试试就知道就不多说了。但是系统给我们的字体有限我们可不可以使用自己的字体呢?答案是肯定的!Typeface这个类中给我们提供了多个方法去个性化我们的字体
Typyface.defaultFromStyle(int style)
sDefaults = new Typeface[] {
DEFAULT,
DEFAULT_BOLD,
create((String) null, Typeface.ITALIC),
create((String) null, Typeface.BOLD_ITALIC),
};
封装typeface
Typyface.create(String familyName, int style)和create(Typeface family, int style)
创建typeface
textPaint.setTypeface(Typeface.create("SERIF", Typeface.NORMAL));
textPaint.setTypeface(Typeface.create(Typeface.SERIF, Typeface.NORMAL));
Typeface.createFromAsset(AssetManager mgr, String path)、createFromFile(String path)和createFromFile(File path)
允许我们使用自己的字体比如我们从asset目录读取一个字体文件
// 获取字体并设置画笔字体
Typeface typeface = Typeface.createFromAsset(context.getAssets(), "kt.ttf");
textPaint.setTypeface(typeface);
setTextSkewX(float skewX)
值为负右倾值为正左倾,默认值为0
paint.textSkewX = -0.45F
setTextSize (float textSize)
setTextScaleX (float scaleX)
setTextScaleX (float scaleX)
将文本沿X轴水平缩放,默认值为1,当值大于1会沿X轴水平放大文本,当值小于1会沿X轴水平缩放文本
缩放前
缩放后
setTextAlign (Paint.Align align)
下面都是从中心点开始绘制的
fun drawBaselineAlign(canvas: Canvas) {
mPaint.textSize = 100F //必须在测量宽度之前
//计算Baseline绘制的起点X轴坐标
val cwidth = canvas.width.toFloat()
val cheight = canvas.height.toFloat()
val halfWidth = cwidth.div(2)
val halfHeight = cheight.div(2)
// 十字中线
mPaint.color = Color.BLACK
mPaint.strokeWidth = 5F
canvas.drawLine(0F, halfHeight, cwidth, halfHeight, mPaint)
canvas.drawLine(halfWidth, 0F, halfWidth, cheight, mPaint)
mPaint.strokeWidth = 1F
val ascent = mPaint.fontMetrics.ascent
val top = mPaint.fontMetrics.top
val leading = mPaint.fontMetrics.leading
val descent = mPaint.fontMetrics.descent
val bottom = mPaint.fontMetrics.bottom
val halfTextHeightTopBottom = (bottom + top).div(2)
val halfTextHeightCescent = (descent + ascent).div(2)
val baseLineHalfHeight = halfHeight //baseline 跟中心重合
val baseLineTextCenterY = halfHeight - (ascent+descent).div(2)
val baseLine = baseLineHalfHeight
val centerYTopBottom = baseLine + halfTextHeightTopBottom
val centerYSCent = baseLine + halfTextHeightCescent
//画文字
mPaint.color = Color.BLACK
mPaint.textAlign = Paint.Align.LEFT
canvas.drawText(TEXT, halfWidth, baseLine, mPaint)
// 中线
if(true) {
mPaint.color = Color.RED
canvas.drawLine(0f, centerYTopBottom, cwidth, centerYTopBottom, mPaint)
mPaint.color = Color.GREEN
canvas.drawLine(0f, centerYSCent, cwidth, centerYSCent, mPaint)
}
}
- mPaint.textAlign = Paint.Align.LEFT
- mPaint.textAlign = Paint.Align.RIGHT
- mPaint.textAlign = Paint.Align.CENTER
setSubpixelText (boolean subpixelText)
设置自像素。如果该项为true,将有助于文本在LCD屏幕上的显示效果。
setStrikeThruText (boolean strikeThruText)
文本删除线
setLinearText (boolean linearText)
设置是否打开线性文本标识,这玩意对大多数人来说都很奇怪不知道这玩意什么意思。想要明白这东西你要先知道文本在Android中是如何进行存储和计算的。在Android中文本的绘制需要使用一个bitmap作为单个字符的缓存,既然是缓存必定要使用一定的空间,我们可以通过setLinearText (true)告诉Android我们不需要这样的文本缓存。
setFakeBoldText (boolean fakeBoldText)
设置文本仿粗体
measureText (String text),measureText (CharSequence text, int start, int end),measureText (String text, int start, int end),measureText (char[] text, int index, int count)
测量文本宽度
setDither(boolean dither)
这玩意用来设置我们在绘制图像时的抗抖动,也称为递色,那什么叫抗抖动呢?在Android中我确实不好拿出一个明显的例子,我就在PS里模拟说明一下
大家看到的这张七彩渐变图是一张RGB565模式下图片,即便图片不是很大我们依然可以很清晰地看到在两种颜色交接的地方有一些色块之类的东西感觉很不柔和,因为在RGB模式下只能显示2^16=65535种色彩,因此很多丰富的色彩变化无法呈现,而Android呢为我们提供了抗抖动这么一个方法,它会将相邻像素之间颜色值进行一种“中和”以呈现一个更细腻的过渡色:
放大来看,其在很多相邻像素之间插入了一个“中间值”:
抗抖动不是Android的专利,是图形图像领域的一种解决位图精度的技术。上面说了太多理论性的东西,估计大家都疲惫了,接下来我们来瞅瞅一个比较酷的东西MaskFilter遮罩过滤器!在Paint我们有个方法来设置这东西
setMaskFilter(MaskFilter maskfilter)
javascript:void(0)
BlurMaskFilter
Android中的很多自带控件都有类似软阴影的效果,比如说Button
它周围就有一圈很淡的阴影效果,这种效果看起来让控件更真实,那么是怎么做的呢?其实很简单,使用BlurMaskFilter就可以得到类似的效果
val paint = Paint(Paint.ANTI_ALIAS_FLAG or Paint.DITHER_FLAG).apply {
this.style = Paint.Style.FILL
this.color = 0xFF603811.toInt()
//this.maskFilter = BlurMaskFilter(20f, BlurMaskFilter.Blur.OUTER)
}
canvas.drawColor(Color.WHITE)
var left = 100F; var top = 100F; var right = 300F; var bottom=300F
//画一个矩形
paint.maskFilter = BlurMaskFilter(20f, BlurMaskFilter.Blur.SOLID)
canvas.drawRect(left, top, right, bottom, paint)
top = top.plus(250F); bottom = bottom.plus(250F)
paint.maskFilter = BlurMaskFilter(20f, BlurMaskFilter.Blur.NORMAL)
canvas.drawRect(left, top, right, bottom, paint)
top = top.plus(250F); bottom = bottom.plus(250F)
paint.maskFilter = BlurMaskFilter(20f, BlurMaskFilter.Blur.OUTER)
canvas.drawRect(left, top, right, bottom, paint)
top = top.plus(250F); bottom = bottom.plus(250F)
paint.maskFilter = BlurMaskFilter(20f, BlurMaskFilter.Blur.INNER)
canvas.drawRect(left, top, right, bottom, paint)
private fun drawBlurFilter(canvas: Canvas) {
val paint = Paint(Paint.ANTI_ALIAS_FLAG or Paint.DITHER_FLAG).apply {
this.style = Paint.Style.FILL
this.color = 0xFF603811.toInt()
this.maskFilter = BlurMaskFilter(20f, BlurMaskFilter.Blur.OUTER)
}
//获取位图
val srcBitmap =
BitmapFactory.decodeResource(context.resources, R.mipmap.ic_launcher)
// 获取位图的Alpha通道图
val shadowBitmap = srcBitmap.extractAlpha()
//绘制阴影
canvas.drawBitmap(shadowBitmap,100F, 100F, paint)
//再绘制位图
canvas.drawBitmap(srcBitmap, 100F, 100F, null)
}
EmbossMaskFilter
private fun drawEmbossMaskFilter(canvas: Canvas) {
val paint = Paint(Paint.ANTI_ALIAS_FLAG or Paint.DITHER_FLAG).apply {
this.style = Paint.Style.FILL
this.color = 0xFF603811.toInt()
this.maskFilter = EmbossMaskFilter(floatArrayOf(1f, 1f, 1f), 0.1f, 10f, 20f)
}
canvas.drawColor(Color.WHITE)
//画矩形
//画8个矩形
for (index in 1..4){
canvas.drawRect(100F, 100F*index, 200F, 100F*index+100F, paint)
}
for (index in 1..4){
canvas.drawRect(200F, 100F*index, 300F, 100F*index+100F, paint)
}
}
setRasterizer (Rasterizer rasterizer)
设置光栅
setPathEffect(PathEffect effect)
var mPhase = 0F
val paint = Paint(Paint.ANTI_ALIAS_FLAG or Paint.DITHER_FLAG).apply {
this.style = Paint.Style.STROKE
this.color = Color.RED
this.strokeWidth = 5F
}
val path = Path().apply {
moveTo(0F, 0F)
//定义路径的各个点
Array(30) {
lineTo(it * 100F, (Math.random() * 300).toFloat())
}
}
private fun drawPathEffectView(canvas: Canvas) {
val cornerPathEffect = CornerPathEffect(30F) //圆角效果
val discretePathEffect = DiscretePathEffect(3.0F, 5.0F) //离散路径效果
val dashPathEffect = DashPathEffect(floatArrayOf(50F, 10F, 5F, 10F), mPhase) //虚线效果
val pathDashPathEffect = PathDashPathEffect(
Path().apply {
moveTo(0F, 20F)
lineTo(10F, 0F)
lineTo(20F, 20F)
close()
addCircle(0F, 0F, 3F, Path.Direction.CCW) //ccw 逆时针 CW 顺时针
},
52F, //spacing between each stamp of shape
mPhase,
PathDashPathEffect.Style.ROTATE //MORPH:变形过度角 ROTATE:旋转过度角 TRANSLATE:改变位置过度
) //印章效果
val composePathEffect = ComposePathEffect(discretePathEffect, pathDashPathEffect) //合并两个特效的,有先后顺序的
val sumPathEffect = SumPathEffect(pathDashPathEffect, dashPathEffect) 合并两个特效的,没有顺序
val pathEffects = arrayListOf<PathEffect?>(
null
, cornerPathEffect
, discretePathEffect
, dashPathEffect
, pathDashPathEffect
, composePathEffect
, sumPathEffect
)
//创建路径效果数组
for (pathEffect in pathEffects) {
paint.pathEffect = pathEffect
canvas.drawPath(path, paint)
canvas.translate(0F, 180F) //每绘制一条,将画布向下平移250个像素
}
mPhase += 1
Thread.sleep(50)
invalidate()
}
心电图
public class ECGView extends View {
private Paint mPaint;// 画笔
private Path mPath;// 路径对象
private int screenW, screenH;// 屏幕宽高
private float x, y;// 路径初始坐标
private float initScreenW;// 屏幕初始宽度
private float initX;// 初始X轴坐标
private float transX, moveX;// 画布移动的距离
private boolean isCanvasMove;// 画布是否需要平移
public ECGView(Context context, AttributeSet set) {
super(context, set);
/*
* 实例化画笔并设置属性
*/
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);
mPaint.setColor(Color.GREEN);
mPaint.setStrokeWidth(5);
mPaint.setStrokeCap(Paint.Cap.ROUND);
mPaint.setStrokeJoin(Paint.Join.ROUND);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setShadowLayer(7, 0, 0, Color.GREEN);
mPath = new Path();
transX = 0;
isCanvasMove = false;
}
@Override
public void onSizeChanged(int w, int h, int oldw, int oldh) {
/*
* 获取屏幕宽高
*/
screenW = w;
screenH = h;
/*
* 设置起点坐标
*/
x = 0;
y = (screenH / 2) + (screenH / 4) + (screenH / 10);
// 屏幕初始宽度
initScreenW = screenW;
// 初始X轴坐标
initX = ((screenW / 2) + (screenW / 4));
moveX = (screenW / 24);
mPath.moveTo(x, y);
}
@Override
public void onDraw(Canvas canvas) {
canvas.drawColor(Color.BLACK);
mPath.lineTo(x, y);
// 向左平移画布
canvas.translate(-transX, 0);
// 计算坐标
calCoors();
// 绘制路径
canvas.drawPath(mPath, mPaint);
invalidate();
}
/**
* 计算坐标
*/
private void calCoors() {
if (isCanvasMove == true) {
transX += 4;
}
if (x < initX) {
x += 8;
} else {
if (x < initX + moveX) {
x += 2;
y -= 8;
} else {
if (x < initX + (moveX * 2)) {
x += 2;
y += 14;
} else {
if (x < initX + (moveX * 3)) {
x += 2;
y -= 12;
} else {
if (x < initX + (moveX * 4)) {
x += 2;
y += 6;
} else {
if (x < initScreenW) {
x += 8;
} else {
isCanvasMove = true;
initX = initX + initScreenW;
}
}
}
}
}
}
}
}
setStrokeCap(Paint.Cap cap)
private fun drawPaintCap(canvas: Canvas) {
val p1 = Paint(Paint.ANTI_ALIAS_FLAG or Paint.DITHER_FLAG)
.apply {
this.strokeCap = Paint.Cap.BUTT //无线冒
this.style = Paint.Style.STROKE
this.strokeWidth = 20F
}
val p2 = Paint(Paint.ANTI_ALIAS_FLAG or Paint.DITHER_FLAG)
.apply {
this.strokeCap = Paint.Cap.SQUARE //方形线冒
this.style = Paint.Style.STROKE
this.strokeWidth = 20F
}
val p3 = Paint(Paint.ANTI_ALIAS_FLAG or Paint.DITHER_FLAG)
.apply {
this.strokeCap = Paint.Cap.ROUND //圆形线冒
this.style = Paint.Style.STROKE
this.strokeWidth = 20F
}
// 三个有宽度的线条
canvas.drawLine(100F, 200F, 400F, 200F, p1)
canvas.drawLine(100F, 250F, 400F, 250F, p2)
canvas.drawLine(100F, 300F, 400F, 300F, p3)
//画条参考线
p1.also { paint -> paint.strokeWidth=2F; paint.color = Color.GREEN}
canvas.drawLine(100F, 150F, 100F, 350F, p1)
canvas.drawLine(400F, 150F, 400F, 350F, p1)
}
}
setStrokeJoin(Paint.Join join)
private fun drawJoin(canvas: Canvas) {
val mPaintForJoin: Paint = Paint()
mPaintForJoin.strokeWidth = 40F
mPaintForJoin.color = Color.GREEN
mPaintForJoin.style = Paint.Style.STROKE //有边框
mPaintForJoin.isAntiAlias = true //抗锯齿
// mPaintForJoin.strokeJoin = Paint.Join.MITER//结合处为锐角
// mPaintForJoin.strokeJoin = Paint.Join.ROUND//结合处为圆弧
// mPaintForJoin.strokeJoin = Paint.Join.BEVEL//结合处为直线
val path:Path = Path()
path.moveTo(500F, 100F)
path.lineTo(600F, 100F)
path.lineTo(500F, 200F)
mPaintForJoin.strokeJoin = Paint.Join.MITER//结合处为锐角
canvas.drawPath(path, mPaintForJoin)
path.moveTo(500F, 200F)
path.lineTo(600F, 200F)
path.lineTo(500F, 300F)
mPaintForJoin.strokeJoin = Paint.Join.ROUND//结合处为圆弧
canvas.drawPath(path, mPaintForJoin)
path.moveTo(500F, 300F)
path.lineTo(600F, 300F)
path.lineTo(500F, 400F)
mPaintForJoin.strokeJoin = Paint.Join.BEVEL//结合处为直线
canvas.drawPath(path, mPaintForJoin)
val paint:Paint = Paint()
paint.color = Color.RED
paint.strokeWidth = 2F
paint.style = Paint.Style.STROKE
//左边线
canvas.drawLine(500F,0F, 500F, 420F, paint)
//右边线
canvas.drawLine(600F,0F, 600F, 420F, paint)
}
setShadowLayer(float radius, float dx, float dy, int shadowColor)
设置阴影效果
private fun drawShadowView(canvas: Canvas) {
val paint = Paint(Paint.ANTI_ALIAS_FLAG or Paint.DITHER_FLAG)
.apply {
this.color = Color.RED
this.style = Paint.Style.FILL
this.setShadowLayer(40F, 40F, 40F, Color.DKGRAY)
}
canvas.drawRect(100F, 100F, 400F, 400F, paint)
}