安卓自定义view中 绘画基本图形点线面,矩形,方形,圆,扇形,文字及沿着特定方向布局,自定义圆角ImageView图片等等相关api使用方法及举例
安卓自定义view中 绘画基本图形点线面,矩形,方形,圆,扇形,文字及沿着特定方向布局,自定义圆角ImageView图片等等相关api使用方法及举例,图片压缩处理逻辑
本文旨在介绍自定义View的实现及流程
着重介绍安卓中的Canvas的绘制方法,让你从容面对各种view
并附带自定义圆角ImageView,和图片压缩处理等功能
详细内容可转连接:https://mp.weixin.qq.com/s/DL9XFo8Zd4osHvAy7efgog
简单那说一下 View 绘制的三个流程
在自定义View的时候一般需要重写父类的onMeasure()、onLayout()、onDraw()三个方法,来完成视图的展示过程。当然,这三个暴露给开发者重写的方法只不过是整个绘制流程的冰山一角,更多复杂的幕后工作,都让系统给代劳了。一个完整的绘制流程包括measure、layout、draw三个步骤,其中:measure:测量。系统会先根据xml布局文件和代码中对控件属性的设置,来获取或者计算出每个View和ViewGrop的尺寸,并将这些尺寸保存下来。layout:布局。根据测量出的结果以及对应的参数,来确定每一个控件应该显示的位置。draw:绘制。确定好位置后,就将这些控件绘制到屏幕上。重中之重:draw
Canvas 简单理解:画布
Canvas绘图有三个基本要素:Canvas、绘图坐标系以及Paint。
Canvas是画布,我们通过Canvas的各种drawXXX方法将图形绘制到Canvas上面,在drawXXX方法中我们需要传入要绘制的图形的坐标形状,还要传入一个画笔Paint。
基础知识:
Canvas的常用操作速查表
操作类型 | 相关API | 备注 |
---|---|---|
绘制颜色 | drawColor, drawRGB, drawARGB | 使用单一颜色填充整个画布 |
绘制基本形状 | drawPoint, drawPoints, drawLine, drawLines, drawRect, drawRoundRect, drawOval, drawCircle, drawArc | 依次为 点、线、矩形、圆角矩形、椭圆、圆、圆弧 |
绘制图片 | drawBitmap, drawPicture | 绘制位图和图片 |
绘制文本 | drawText, drawPosText, drawTextOnPath | 依次为 绘制文字、绘制文字时指定每个文字位置、根据路径绘制文字 |
绘制路径 | drawPath | 绘制路径,绘制贝塞尔曲线时也需要用到该函数 |
顶点操作 | drawVertices, drawBitmapMesh | 通过对顶点操作可以使图像形变,drawVertices直接对画布作用、 drawBitmapMesh只对绘制的Bitmap作用 |
画布剪裁 | clipPath, clipRect | 设置画布的显示区域 |
画布快照 | save, restore, saveLayerXxx, restoreToCount, getSaveCount | 依次为 保存当前状态、 回滚到上一次保存的状态、 保存图层状态、 回滚到指定状态、 获取保存次数 |
画布变换 | translate, scale, rotate, skew | 依次为 位移、缩放、 旋转、错切 |
Matrix(矩阵) | getMatrix, setMatrix, concat | 实际画布的位移,缩放等操作的都是图像矩阵Matrix,只不过Matrix比较难以理解和使用,故封装了一些常用的方法。 |
下方会对对应的API进行详解和举例使用
- 1,绘制单点 多点
基本思路
继承VIew,重写onDraw方法,进行绘制
//获取画笔 设置画笔属性
//调起画布 canvas 的drawPoint方法 输入相应的参数即可 绘画出 对应的点
Paint 对于 Text 的相关设置
1,普通设置
paint.setStrokeWidth(5):设置画笔宽度
paint.setAntiAlias(true):设置是否使用抗锯齿功能,如果使用,会导致绘图速度变慢
paint.setStyle(Paint.Style.FILL):设置绘图样式,对于设置文字和几何图形都有效,可取值有三种 :
1、Paint.Style.FILL:填充内部
2、Paint.Style.FILL_AND_STROKE:填充内部和描边
3、Paint.Style.STROKE:仅描边
paint.setTextAlign(Align.CENTER):设置文字对齐方式
paint.setTextSize(12):设置文字大小
2,样式设置
paint.setFakeBoldText(true):设置是否为粗体文字
paint.setUnderlineText(true):设置下划线
paint.setTextSkewX((float) -0.25):设置字体水平倾斜度,普通斜体字是 -0.25
paint.setStrikeThruText(true):设置带有删除线效果
3,其他设置
paint.setTextScaleX(2):设置水平拉伸,高度不会变
下面是绘制的相应内容图例展示:
详细代码实现如下:
首先准备画笔工作:
关于画笔 Paint 需强调说明一下 onDraw方法中不要创建Paint等对象,因为这个方法是多次调用的;
所以我这边做了一个处理
在项目初起就已经把Paint进行全局初始化,然后在其他地方调用的时候,直接设其属性即可,代码如下:
`lateinit var projectResources: ProjectResources
class ProjectResources(private val resources: Resources) {
val paintLight by lazy {getBasePaint().apply {color = Color.LTGRAYalpha = 128strokeWidth = resources.dpToPx(2)textSize = resources.dpToPx(30)}
}val paintDark by lazy {getBasePaint().apply {color = Color.BLACKalpha = 128strokeWidth = resources.dpToPx(4)textSize = resources.dpToPx(30)}
}private fun getBasePaint(): Paint {return Paint().apply {style = Paint.Style.STROKEstrokeCap = Paint.Cap.ROUNDstrokeJoin = Paint.Join.ROUNDisAntiAlias = truetextAlign = Paint.Align.CENTERtextSize = resources.dpToPx(30)}
}
}`
绘制单点 drawPoint
/*** 绘制单点 drawPoint*/
class DrawPointView @JvmOverloads constructor(context: Context,attrs: AttributeSet? = null,defStyleAttr: Int = 0,defStyleRes: Int = 0
) : View(context, attrs, defStyleAttr, defStyleRes) {override fun onDraw(canvas: Canvas) {super.onDraw(canvas)if (width == 0 || height == 0) returncanvas.drawPoint(0f + paddingStart,//x坐标0f + paddingTop,//Y坐标getPaint()//画笔paint)}
//获取画笔 设置画笔属性private fun getPaint(): Paint {return Paint().apply {style = Paint.Style.STROKEstrokeCap = Paint.Cap.ROUNDstrokeJoin = Paint.Join.ROUNDisAntiAlias = truetextAlign = Paint.Align.CENTERtextSize = resources.dpToPx(30)color = Color.GREEN//画笔颜色strokeWidth = resources.dpToPx(16)//画笔宽度}}
}
说明一下:我这边贴出来的代码 绘制过程中在ondraw调用getPaint方法;
是为了更直观的展示出来,看到必须设这个属性
其实demo中都已经做了全局处理,不会说每次都会创建paint这个对象。
绘制多点 drawPoints
/*** 绘制多点 drawPoints*/
class DrawPointsView @JvmOverloads constructor(context: Context,attrs: AttributeSet? = null,defStyleAttr: Int = 0,defStyleRes: Int = 0
) : View(context, attrs, defStyleAttr, defStyleRes) {override fun onDraw(canvas: Canvas) {super.onDraw(canvas)if (width == 0 || height == 0) returncanvas.drawPoints(listOf(0f + paddingStart, 0f + paddingTop,//第1个点的 x,y((width - paddingEnd)).toFloat(), 0f + paddingTop, //第2个点的 x,y((width - paddingEnd)/2).toFloat(),((height - paddingBottom)/2).toFloat(), //第3个点的 x,y0f + paddingStart, ((height - paddingBottom)).toFloat(), //第4个点的 x,y(width - paddingEnd).toFloat(), (height - paddingBottom).toFloat() //第5个点的 x,y).toFloatArray(),projectResources.paintPoint)}
}
绘制直线 drawLine
//绘制线条 drawLine
class DrawLineView @JvmOverloads constructor(context: Context,attrs: AttributeSet? = null,defStyleAttr: Int = 0,defStyleRes: Int = 0
) : View(context, attrs, defStyleAttr, defStyleRes) {override fun onDraw(canvas: Canvas) {super.onDraw(canvas)if (width == 0 || height == 0) returncanvas.drawLine(0f + paddingStart,//起始位置 x坐标0f + paddingTop,//起始位置 y坐标(width - paddingEnd).toFloat(),//终点位置 X坐标(height - paddingBottom).toFloat(),//终点位置 Y坐标projectResources.paint// 获取画笔)}
}
同时绘制多条线(1) drawLines
/*** 同时绘制多条线(1) drawLines*/class DrawLinesView @JvmOverloads constructor(context: Context,attrs: AttributeSet? = null,defStyleAttr: Int = 0,defStyleRes: Int = 0
) : View(context, attrs, defStyleAttr, defStyleRes) {override fun onDraw(canvas: Canvas) {super.onDraw(canvas)if (width == 0 || height == 0) returncanvas.drawLines(listOf(//第一条线的起始x坐标和Y坐标 末尾位置的X坐标和Y坐标0f + paddingStart, 0f + paddingTop, (width / 2f), (height - paddingBottom).toFloat(),//第二条线的起始x坐标和Y坐标 末尾位置的X坐标和Y坐标width / 2f, (height - paddingBottom).toFloat(), (width - paddingEnd).toFloat(), (0f + paddingStart),//第三条线的起始x坐标和Y坐标 末尾位置的X坐标和Y坐标(width - paddingEnd).toFloat(), (0f + paddingStart), width / 2f, (height/2f).toFloat()).toFloatArray(),projectResources.paint//画笔)}
}
同时绘制多条线(2)
//使用指定的画笔绘制指定的路径(线路)。路径(线路)将根据绘画的风格填充或装裱。
两条线交叉
同时绘制多条线(2)
//使用指定的画笔绘制指定的路径(线路)。路径(线路)将根据绘画的风格填充或装裱。
两条线交叉
class DrawPathView @JvmOverloads constructor(context: Context,attrs: AttributeSet? = null,defStyleAttr: Int = 0,defStyleRes: Int = 0
) : View(context, attrs, defStyleAttr, defStyleRes) {private val path by lazy {val path = Path()path.moveTo(0f + paddingStart, 0f + paddingTop)path.lineTo((width - paddingEnd).toFloat(), (height - paddingBottom).toFloat())path.moveTo(0f + paddingStart, (height - paddingBottom).toFloat())path.lineTo((width - paddingEnd).toFloat(), 0f + paddingTop)path}override fun onDraw(canvas: Canvas) {super.onDraw(canvas)if (width == 0 || height == 0) return// * Draw the specified path using the specified paint. The path will be filled or framed based on// * the Style in the paint.canvas.drawPath(path, projectResources.paint)}
}
绘画圆形图 drawCircle
/*** 绘画圆形图 drawCircle*/
class DrawCircleView @JvmOverloads constructor(context: Context,attrs: AttributeSet? = null,defStyleAttr: Int = 0,defStyleRes: Int = 0
) : View(context, attrs, defStyleAttr, defStyleRes) {override fun onDraw(canvas: Canvas) {super.onDraw(canvas)if (width == 0 || height == 0) returncanvas.drawCircle(width/2f,//圆点中心 X坐标height/2f,//圆点中心 Y坐标height/2f - paddingTop,//圆半径projectResources.paint//画笔)}
}
绘制矩形框 drawRect
确定确定一个矩形最少需要四个数据,就是对角线的两个点的坐标值,这里一般采用左上角和右下角的两个点的坐标。
/*** 绘制矩形框 drawRect
确定确定一个矩形最少需要四个数据,就是对角线的两个点的坐标值,这里一般采用左上角和右下角的两个点的坐标。*/
class DrawRectView @JvmOverloads constructor(context: Context,attrs: AttributeSet? = null,defStyleAttr: Int = 0,defStyleRes: Int = 0
) : View(context, attrs, defStyleAttr, defStyleRes) {private val rect by lazy {Rect(0 + paddingStart,//左0 + paddingTop,//顶width - paddingEnd,//右height - paddingBottom//底)}override fun onDraw(canvas: Canvas) {super.onDraw(canvas)if (width == 0 || height == 0) returncanvas.drawRect(rect, projectResources.paint)}}
绘制圆角矩形框 drawRoundRect
class DrawRoundRectView @JvmOverloads constructor(context: Context,attrs: AttributeSet? = null,defStyleAttr: Int = 0,defStyleRes: Int = 0
) : View(context, attrs, defStyleAttr, defStyleRes) {private val rect by lazy {RectF(0f + paddingStart,0f + paddingTop,(width - paddingEnd).toFloat(),(height - paddingBottom).toFloat())}override fun onDraw(canvas: Canvas) {super.onDraw(canvas)if (width == 0 || height == 0) returncanvas.drawRoundRect(rect, resources.dpToPx(10), resources.dpToPx(10), projectResources.paint)}
}
绘制椭圆框 drawOval
//绘制椭圆框 drawOval
class DrawOvalView @JvmOverloads constructor(context: Context,attrs: AttributeSet? = null,defStyleAttr: Int = 0,defStyleRes: Int = 0
) : View(context, attrs, defStyleAttr, defStyleRes) {private val rect by lazy {RectF(0f + paddingStart,//包围椭圆的矩形左上角端点x0f + paddingTop,//包围椭圆的矩形左上角端点Ywidth.toFloat() - paddingEnd,//椭圆的高度height.toFloat() - paddingBottom//椭圆的宽度 当高度和宽度相等时 就是圆)}override fun onDraw(canvas: Canvas) {super.onDraw(canvas)if (width == 0 || height == 0) returncanvas.drawOval(rect, projectResources.paint)}
}
扇形绘制 (椭圆和圆的)
Canvas中提供了drawArc方法用于绘制弧,这里的弧指两种:弧面和弧线,弧面即用弧围成的填充面,弧线即为弧面的轮廓线。
api参数解析
oval是RecF类型的对象,其定义了椭圆的形状。
startAngle指的是绘制的起始角度,如果传入的startAngle小于0或者大于等于360,那么用startAngle对360进行取模后作为起始绘制角度。
sweepAngle指的是从startAngle开始沿着钟表的顺时针方向旋转扫过的角度。如果sweepAngle大于等于360,那么会绘制完整的椭圆弧。如果sweepAngle小于0,那么会用sweepAngle对360进行取模后作为扫过的角度。
useCenter是个boolean值,如果为true,表示在绘制完弧之后,用椭圆的中心点连接弧上的起点和终点以闭合弧;如果值为false,表示在绘制完弧之后,弧的起点和终点直接连接,不经过椭圆的中心点。
class DrawArcView @JvmOverloads constructor(context: Context,attrs: AttributeSet? = null,defStyleAttr: Int = 0,defStyleRes: Int = 0
) : View(context, attrs, defStyleAttr, defStyleRes) {private val rect by lazy {RectF(0f + paddingStart,0f + paddingTop,height.toFloat() - paddingBottom,height.toFloat() - paddingBottom) 设置方形区域}private val rect2 by lazy {RectF(0f + paddingStart,0f + paddingTop,width.toFloat() - paddingEnd,height.toFloat() - paddingBottom) 设置矩形区域}override fun onDraw(canvas: Canvas) {super.onDraw(canvas)/* 设置渐变色 这个正方形的颜色是改变的 */val mShader = LinearGradient(0f,0f,100f,100f,intArrayOf(Color.RED, Color.GREEN, Color.BLUE, Color.YELLOW, Color.LTGRAY),null,Shader.TileMode.REPEAT) // 一个材质,打造出一个线性梯度沿著一条线。projectResources.paint.shader = mShaderif (width == 0 || height == 0) returncanvas.drawArc(rect,0f,//从哪个弧度开始330f,//扇形弧度true, //是否经过圆心projectResources.paint)canvas.drawArc(rect2,0f,//从哪个弧度开始30f,//扇形弧度true, //是否经过圆心projectResources.paint)}
}
文字绘制 在Android开发中,Canvas.drawText不会换行,即使一个很长的字符串也只会显示一行,超出部分会隐藏在屏幕之外。
class DrawTextView @JvmOverloads constructor(context: Context,attrs: AttributeSet? = null,defStyleAttr: Int = 0,defStyleRes: Int = 0
) : View(context, attrs, defStyleAttr, defStyleRes) {companion object {private const val TEXT = "三国演义-西游记-"}private val textBound by lazy {val textBound = Rect()projectResources.paint.getTextBounds(TEXT, 0, TEXT.length, textBound)textBound}override fun onDraw(canvas: Canvas) {super.onDraw(canvas)if (width == 0 || height == 0) returncanvas.drawText(TEXT,//:要绘制的文字width/2f,//绘制原点x坐标height/2f - textBound.exactCenterY(),//绘制原点y坐标 基线的位置。projectResources.paint)}
}
可换行的文字 StaticLayout是android中处理文字的一个工具类,StaticLayout 处理了文字换行的问题,
class DrawStaticLayoutTextView @JvmOverloads constructor(context: Context,attrs: AttributeSet? = null,defStyleAttr: Int = 0,defStyleRes: Int = 0
) : View(context, attrs, defStyleAttr, defStyleRes) {companion object {private const val TEXT = "Too long a line to display in one line, and let it auto wrap go to next line.\nCan handle \"\\n\" as well."}override fun onDraw(canvas: Canvas) {super.onDraw(canvas)if (width == 0 || height == 0) returnval staticLayout =StaticLayout.Builder.obtain(TEXT, 0, TEXT.length, projectResources.textPaint, width).build()canvas.save()canvas.translate(width / 2f, (height - staticLayout.height)/2f)staticLayout.draw(canvas)canvas.restore()}
}
可以自定义每个文字的位置
class DrawPosTextView @JvmOverloads constructor(context: Context,attrs: AttributeSet? = null,defStyleAttr: Int = 0,defStyleRes: Int = 0
) : View(context, attrs, defStyleAttr, defStyleRes) {companion object {private const val TEXT = "Testing 123"}override fun onDraw(canvas: Canvas) {super.onDraw(canvas)if (width == 0 || height == 0) returncanvas.drawPosText(TEXT.toCharArray(),//:要绘制的文字数组1, //第一个要绘制的文字的索引3,//要绘制的文字的个数,用来算最后一个文字的位置,从第一个绘制的文字开始算起listOf(width/1.5f, height/1.5f,width/2f, height/2f,width/3f, height/3f).toFloatArray(),//每个字体的位置,同样两两一组,projectResources.paint)}
}
绘制文字 沿着路线走
class DrawTextOnPathView @JvmOverloads constructor(context: Context,attrs: AttributeSet? = null,defStyleAttr: Int = 0,defStyleRes: Int = 0
) : View(context, attrs, defStyleAttr, defStyleRes) {companion object {private const val TEXT = "Test 123"}private val path by lazy {val path = Path()path.moveTo(0f + paddingStart, 0f + paddingTop)path.lineTo((width/2f), (height - paddingBottom).toFloat())path.lineTo((width - paddingEnd).toFloat(), 0f + paddingTop)path}private val textBound by lazy {val textBound = Rect()projectResources.paint.getTextBounds(TEXT, 0, TEXT.length, textBound)textBound}override fun onDraw(canvas: Canvas) {super.onDraw(canvas)if (width == 0 || height == 0) returncanvas.drawPath(path, projectResources.paintLight)canvas.drawTextOnPath(TEXT, //文字path,//路径 轨迹0f,//与路径起始点的水平偏移距离-textBound.exactCenterY(),//与路径中心的垂直偏移量projectResources.paint)}
}
将图片直接绘制到canvas
class DrawBitmapView @JvmOverloads constructor(context: Context,attrs: AttributeSet? = null,defStyleAttr: Int = 0,defStyleRes: Int = 0
) : View(context, attrs, defStyleAttr, defStyleRes) {private val bitmap by lazy {BitmapFactory.decodeResource(resources, R.drawable.image)}private val rect by lazy {Rect(0 + paddingLeft, 0 + paddingTop, width - paddingRight, height - paddingBottom)}override fun onDraw(canvas: Canvas) {super.onDraw(canvas)if (width == 0 || height == 0) returncanvas.drawBitmap(bitmap, null, rect, null)}
}
用颜色充填canvas
class DrawColorView @JvmOverloads constructor(context: Context,attrs: AttributeSet? = null,defStyleAttr: Int = 0,defStyleRes: Int = 0
) : View(context, attrs, defStyleAttr, defStyleRes) {override fun onDraw(canvas: Canvas) {super.onDraw(canvas)if (width == 0 || height == 0) returncanvas.drawColor(context.getColor(R.color.colorPrimary))}
}
用RGB颜色充填
class DrawRGBView @JvmOverloads constructor(context: Context,attrs: AttributeSet? = null,defStyleAttr: Int = 0,defStyleRes: Int = 0
) : View(context, attrs, defStyleAttr, defStyleRes) {override fun onDraw(canvas: Canvas) {super.onDraw(canvas)if (width == 0 || height == 0) returncanvas.drawRGB(255, 0 , 0)}
}
图片和颜色重合
class DrawARGBView @JvmOverloads constructor(context: Context,attrs: AttributeSet? = null,defStyleAttr: Int = 0,defStyleRes: Int = 0
) : View(context, attrs, defStyleAttr, defStyleRes) {private val bitmap by lazy {BitmapFactory.decodeResource(resources, R.drawable.image)}private val rect by lazy {Rect(0 + paddingLeft, 0 + paddingTop, width - paddingRight, height - paddingBottom)}override fun onDraw(canvas: Canvas) {super.onDraw(canvas)if (width == 0 || height == 0) returncanvas.drawBitmap(bitmap, null, rect, null)canvas.drawARGB(128, 255, 0 , 0)}
}
用pain笔的颜色去填充整个画布
class DrawPaintView @JvmOverloads constructor(context: Context,attrs: AttributeSet? = null,defStyleAttr: Int = 0,defStyleRes: Int = 0
) : View(context, attrs, defStyleAttr, defStyleRes) {companion object {private const val TEXT = "Testing 123"}private val gradientPaint by lazy {Paint().apply {shader = RadialGradient(width/2f,height/2f,height/2f,Color.GREEN,Color.RED,Shader.TileMode.MIRROR)}}override fun onDraw(canvas: Canvas) {super.onDraw(canvas)if (width == 0 || height == 0) returncanvas.drawPaint(gradientPaint)}
}
通过对顶点操作可以使图像形变, drawBitmapMesh只对绘制的Bitmap作用
//通过对顶点操作可以使图像形变, drawBitmapMesh只对绘制的Bitmap作用
class DrawBitmapMeshView @JvmOverloads constructor(context: Context,attrs: AttributeSet? = null,defStyleAttr: Int = 0,defStyleRes: Int = 0
) : View(context, attrs, defStyleAttr, defStyleRes) {private val bitmap by lazy {BitmapFactory.decodeResource(resources, R.drawable.santou)}private val firstX by lazy { 0f + paddingLeft }private val firstY by lazy { 0f + paddingTop }private val secondX by lazy { width/5f }private val secondY by lazy { height/3f }private val thirdX by lazy { width.toFloat() - paddingRight }private val thirdY by lazy { height.toFloat() - paddingBottom }override fun onDraw(canvas: Canvas) {super.onDraw(canvas)if (width == 0 || height == 0) returncanvas.drawBitmapMesh(bitmap, 2, 2,floatArrayOf(firstX, firstY, secondX, firstY, thirdX, firstY,firstX, secondY, secondX, secondY, thirdX, secondY,firstX, thirdY, secondX, thirdY, thirdX, thirdY),0, null, 0, null)canvas.drawLine(firstX, firstY, firstX, thirdY, projectResources.paintLight)canvas.drawLine(secondX, firstY, secondX, thirdY, projectResources.paintLight)canvas.drawLine(thirdX, firstY, thirdX, thirdY, projectResources.paintLight)canvas.drawLine(firstX, firstY, thirdX, firstY, projectResources.paintLight)canvas.drawLine(firstX, secondY, thirdX, secondY, projectResources.paintLight)canvas.drawLine(firstX, thirdY, thirdX, thirdY, projectResources.paintLight)}
}
对图片进行放大切割,及对应点可以给各个顶点颜色纹理渲染
// 通过对顶点操作可以使图像形变,drawVertices直接对画布作用、
class DrawVerticesView @JvmOverloads constructor(context: Context,attrs: AttributeSet? = null,defStyleAttr: Int = 0,defStyleRes: Int = 0
) : View(context, attrs, defStyleAttr, defStyleRes) {companion object {const val DUMMYCOLOR = -0x100000}val paint = Paint().apply {val bitmap = BitmapFactory.decodeResource(resources, R.drawable.santou)val shader = BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP)this.shader = shader}override fun onDraw(canvas: Canvas) {super.onDraw(canvas)if (width == 0 || height == 0) returnsetLayerType(View.LAYER_TYPE_SOFTWARE, null)val indices = shortArrayOf(0, 1, 2, 0, 2, 3)val verticesColors = intArrayOf(Color.RED, Color.GREEN, Color.BLUE, Color.YELLOW,DUMMYCOLOR, DUMMYCOLOR, DUMMYCOLOR, DUMMYCOLOR, DUMMYCOLOR, DUMMYCOLOR)val verts = floatArrayOf(width/2f, 0f, 0f, height/2f, width/2f, height.toFloat(), width.toFloat(), height/2f)canvas.drawVertices(Canvas.VertexMode.TRIANGLES,//顶点类型 比如他是三角形(连续3个顶点)或者 四边形 (连续4个顶点)等等verts.size,//顶点数 总共有多少个顶点绘制。verts, //顶点数组 [0,0,0,1,1,0,...] 前面有xy 3组 如果是类型是三角形 他就构成一个三角形的绘制基元,往后类推。0,//顶点数据 起始位置 可能全部绘制,也可能只绘制部分顶点。与 vertexCount 配置使用 一般为0verts,// 纹理数组 就是对图片等进行采样,然后去渲染顶点。0,//同上offset 就是偏移量verticesColors, // 颜色数组 直接用颜色渲染顶点0,//同上offset 就是偏移量indices,// 顶点索引 可能只绘制部分顶点 这个就是存放那些顶点的index , 即verts[index]0,// 同上offset 就是偏移量indices.size,//绘制多少个索引点。paint)}
}
对图片进行缩小和放大,位移等等操作
//如果想对绘制位置和比例进行控制用Canvas提供的drawPicture方法绘制
//如果想对绘制位置和比例进行控制用Canvas提供的drawPicture方法绘制
class DrawPictureView @JvmOverloads constructor(context: Context,attrs: AttributeSet? = null,defStyleAttr: Int = 0,defStyleRes: Int = 0
) : View(context, attrs, defStyleAttr, defStyleRes) {private val bitmap by lazy {BitmapFactory.decodeResource(resources, R.drawable.santou)}private val rect by lazy {//将高减为一般半,绘制出的Picture在y轴方向压缩了一半,也就是rect并不是对图形进行裁剪,而是缩小或放大,Rect(0 + paddingLeft, 0 + paddingTop, width - paddingRight, (height - paddingBottom)/2)}private val rect1 by lazy {//看到绘制的形状未变,只不过在y轴上上移了200px,Rect(0 + paddingLeft, 200, width - paddingRight, 200+height - paddingBottom)}private val picture by lazy {val picture = Picture()val pCanvas = picture.beginRecording(width, height)//开始录制,在返回的Canvas上进行绘制pCanvas.drawBitmap(bitmap, null, rect, null)picture.endRecording()//结束录制picture}private val picture1 by lazy {val picture = Picture()val pCanvas = picture.beginRecording(width, height)//开始录制,在返回的Canvas上进行绘制pCanvas.drawBitmap(bitmap, null, rect1, null)picture.endRecording()//结束录制picture}override fun onDraw(canvas: Canvas) {super.onDraw(canvas)if (width == 0 || height == 0) returncanvas.drawPicture(picture)canvas.drawPicture(picture1)}
}
以上就是调用onDraw方法绘制View的具体内容。。。
下面在对bitmap着重说明一下,大家应该对Bitmap这个词有很深的印象,做不好处理,就会经常在你的BUGLIST中出现OOM字段,内存溢出,app莫名其妙的就死了,奔溃掉了,最后看到日志才发现问题所在。
下面介绍一下我们项目中是如何处理这一块内容的:
先说一下情景:我们这边有一个图片搜索功能,简单俩说就是根据图片搜索出对应的商品,用户可以拍照,从相册选取。
这其中包括图片存储,压缩,上传,删除等一系列操作:
我这边会根据图片路径然后先去判断该图片的大小像素等等然后去压缩图片:
(1),图片比例压缩
`
//图片按比例大小压缩方法(根据路径获取图片并压缩)
private static Bitmap getImage(String srcPath) {BitmapFactory.Options newOpts = new BitmapFactory.Options();newOpts.inJustDecodeBounds = true;Bitmap bitmap = BitmapFactory.decodeFile(srcPath, newOpts);// 此时返回bm为空newOpts.inJustDecodeBounds = false;int w = newOpts.outWidth;int h = newOpts.outHeight;// 现在主流手机比较多是800*480分辨率,所以高和宽我们设置为float hh = 800f;// 这里设置高度为800ffloat ww = 480f;// 这里设置宽度为480f// 缩放比。由于是固定比例缩放,只用高或者宽其中一个数据进行计算即可int be = 1;// be=1表示不缩放if (w > h && w > ww) {// 如果宽度大的话根据宽度固定大小缩放be = (int) (newOpts.outWidth / ww);} else if (w < h && h > hh) {// 如果高度高的话根据宽度固定大小缩放be = (int) (newOpts.outHeight / hh);}if (be <= 0)be = 1;newOpts.inSampleSize = be;// 设置缩放比例// 重新读入图片,注意此时已经把options.inJustDecodeBounds 设回false了bitmap = BitmapFactory.decodeFile(srcPath, newOpts);return compressImage(bitmap);// 压缩好比例大小后再进行质量压缩
}`
(2)图片质量压缩
/*** 质量压缩方法** @param image* @return*/private static Bitmap compressImage(Bitmap image) {ByteArrayOutputStream baos = new ByteArrayOutputStream();image.compress(Bitmap.CompressFormat.JPEG, 100, baos);int options = 100;while (baos.toByteArray().length / 1024 > 100) {baos.reset();image.compress(Bitmap.CompressFormat.JPEG, options, baos);options -= 10;}ByteArrayInputStream isBm = new ByteArrayInputStream(baos.toByteArray());Bitmap bitmap = BitmapFactory.decodeStream(isBm, null, null);return bitmap;}
(3)将压缩的图片保存至本地
/*** 将压缩的bitmap保存到SDCard卡临时文件夹,用于上传** @param filename* @param bit* @return*/private static String saveMyBitmap(String filename, Bitmap bit) {String baseDir = Environment.getExternalStorageDirectory().getAbsolutePath() + "/chdd/";String filePath = baseDir + filename;File dir = new File(baseDir);if (!dir.exists()) {dir.mkdir();}File f = new File(filePath);try {f.createNewFile();FileOutputStream fOut = null;fOut = new FileOutputStream(f);bit.compress(Bitmap.CompressFormat.PNG, 100, fOut);fOut.flush();fOut.close();} catch (IOException e1) {e1.printStackTrace();}return filePath;}
(4)最后在适当的时候清楚缓存文件和删除图片
/*** 清除缓存文件*/public static void deleteCacheFile() {File file = new File(Environment.getExternalStorageDirectory().getAbsolutePath() + "/chdd/");RecursionDeleteFile(file);}/*** 递归删除*/private static void RecursionDeleteFile(File file) {if (file.isFile()) {file.delete();return;}if (file.isDirectory()) {File[] childFile = file.listFiles();if (childFile == null || childFile.length == 0) {file.delete();return;}for (File f : childFile) {RecursionDeleteFile(f);}file.delete();}}
**下面还做了一个自定义圆角图形的类 **,继承自ImageView,可以在布局中直接使用,如下
<com.hexun.logic.widget.RoundImageViewandroid:id="@+id/ivAvatar"android:layout_width="60dp"android:layout_height="60dp"android:layout_centerVertical="true"android:layout_marginLeft="@dimen/space_20"android:src="@drawable/personal_t" />
下面是代码实现:
下面是圆形图像的代码,如何你想实现自定义弧度圆角,调整方法setUp()中的
mDrawableRadius = Math.min(mDrawableRect.height() / 2.0f, mDrawableRect.width() / 2.0f);
这行代码即可,我这边写的 横竖一样,所以是绘制出圆形图像。
‘public class RoundImageView extends ImageView {
private static final ScaleType SCALE_TYPE = ScaleType.CENTER_CROP;private static final Bitmap.Config BITMAP_CONFIG = Bitmap.Config.ARGB_8888;
private static final int COLORDRAWABLE_DIMENSION = 2;private static final int DEFAULT_BORDER_WIDTH = 0;
private static final int DEFAULT_BORDER_COLOR = Color.BLACK;
private static final int DEFAULT_FILL_COLOR = Color.TRANSPARENT;
private static final boolean DEFAULT_BORDER_OVERLAY = false;private final RectF mDrawableRect = new RectF();
private final RectF mBorderRect = new RectF();private final Matrix mShaderMatrix = new Matrix();
private final Paint mBitmapPaint = new Paint();
private final Paint mBorderPaint = new Paint();
private final Paint mFillPaint = new Paint();private int mBorderColor = DEFAULT_BORDER_COLOR;
private int mBorderWidth = DEFAULT_BORDER_WIDTH;
private int mFillColor = DEFAULT_FILL_COLOR;private Bitmap mBitmap;
private BitmapShader mBitmapShader;
private int mBitmapWidth;
private int mBitmapHeight;private float mDrawableRadius;
private float mBorderRadius;private ColorFilter mColorFilter;private boolean mReady;
private boolean mSetupPending;
private boolean mBorderOverlay;public RoundImageView(Context context) {super(context);init();
}public RoundImageView(Context context, AttributeSet attrs) {this(context, attrs, 0);
}public RoundImageView(Context context, AttributeSet attrs, int defStyle) {super(context, attrs, defStyle);TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CircleImageView, defStyle, 0);mBorderWidth = a.getDimensionPixelSize(R.styleable.CircleImageView_civ_border_width, DEFAULT_BORDER_WIDTH);mBorderColor = a.getColor(R.styleable.CircleImageView_civ_border_color, DEFAULT_BORDER_COLOR);mBorderOverlay = a.getBoolean(R.styleable.CircleImageView_civ_border_overlay, DEFAULT_BORDER_OVERLAY);mFillColor = a.getColor(R.styleable.CircleImageView_civ_fill_color, DEFAULT_FILL_COLOR);a.recycle();init();
}private void init() {super.setScaleType(SCALE_TYPE);mReady = true;if (mSetupPending) {setup();mSetupPending = false;}
}@Override
public ScaleType getScaleType() {return SCALE_TYPE;
}@Override
public void setScaleType(ScaleType scaleType) {if (scaleType != SCALE_TYPE) {throw new IllegalArgumentException(String.format("ScaleType %s not supported.", scaleType));}
}@Override
public void setAdjustViewBounds(boolean adjustViewBounds) {if (adjustViewBounds) {throw new IllegalArgumentException("adjustViewBounds not supported.");}
}@Override
protected void onDraw(Canvas canvas) {if (mBitmap == null) {return;}if (mFillColor != Color.TRANSPARENT) {canvas.drawCircle(getWidth() / 2.0f, getHeight() / 2.0f, mDrawableRadius, mFillPaint);}canvas.drawCircle(getWidth() / 2.0f, getHeight() / 2.0f, mDrawableRadius, mBitmapPaint);if (mBorderWidth != 0) {canvas.drawCircle(getWidth() / 2.0f, getHeight() / 2.0f, mBorderRadius, mBorderPaint);}
}@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {super.onSizeChanged(w, h, oldw, oldh);setup();
}public int getBorderColor() {return mBorderColor;
}public void setBorderColor(int borderColor) {if (borderColor == mBorderColor) {return;}mBorderColor = borderColor;mBorderPaint.setColor(mBorderColor);invalidate();
}public void setBorderColorResource(@ColorRes int borderColorRes) {setBorderColor(ContextCompat.getColor(getContext(), borderColorRes));
}public int getFillColor() {return mFillColor;
}public void setFillColor(int fillColor) {if (fillColor == mFillColor) {return;}mFillColor = fillColor;mFillPaint.setColor(fillColor);invalidate();
}public void setFillColorResource(@ColorRes int fillColorRes) {setFillColor(ContextCompat.getColor(getContext(), fillColorRes));
}public int getBorderWidth() {return mBorderWidth;
}public void setBorderWidth(int borderWidth) {if (borderWidth == mBorderWidth) {return;}mBorderWidth = borderWidth;setup();
}public boolean isBorderOverlay() {return mBorderOverlay;
}public void setBorderOverlay(boolean borderOverlay) {if (borderOverlay == mBorderOverlay) {return;}mBorderOverlay = borderOverlay;setup();
}@Override
public void setImageBitmap(Bitmap bm) {super.setImageBitmap(bm);mBitmap = bm;setup();
}@Override
public void setImageDrawable(Drawable drawable) {super.setImageDrawable(drawable);mBitmap = getBitmapFromDrawable(drawable);setup();
}@Override
public void setImageResource(@DrawableRes int resId) {super.setImageResource(resId);mBitmap = getBitmapFromDrawable(getDrawable());setup();
}@Override
public void setImageURI(Uri uri) {super.setImageURI(uri);mBitmap = uri != null ? getBitmapFromDrawable(getDrawable()) : null;setup();
}@Override
public void setColorFilter(ColorFilter cf) {if (cf == mColorFilter) {return;}mColorFilter = cf;mBitmapPaint.setColorFilter(mColorFilter);invalidate();
}private Bitmap getBitmapFromDrawable(Drawable drawable) {if (drawable == null) {return null;}if (drawable instanceof BitmapDrawable) {return ((BitmapDrawable) drawable).getBitmap();}try {Bitmap bitmap;if (drawable instanceof ColorDrawable) {bitmap = Bitmap.createBitmap(COLORDRAWABLE_DIMENSION, COLORDRAWABLE_DIMENSION, BITMAP_CONFIG);} else {bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), BITMAP_CONFIG);}Canvas canvas = new Canvas(bitmap);drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());drawable.draw(canvas);return bitmap;} catch (Exception e) {e.printStackTrace();return null;}
}private void setup() {if (!mReady) {mSetupPending = true;return;}if (getWidth() == 0 && getHeight() == 0) {return;}if (mBitmap == null) {invalidate();return;}mBitmapShader = new BitmapShader(mBitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);mBitmapPaint.setAntiAlias(true);mBitmapPaint.setShader(mBitmapShader);mBorderPaint.setStyle(Paint.Style.STROKE);mBorderPaint.setAntiAlias(true);mBorderPaint.setColor(mBorderColor);mBorderPaint.setStrokeWidth(mBorderWidth);mFillPaint.setStyle(Paint.Style.FILL);mFillPaint.setAntiAlias(true);mFillPaint.setColor(mFillColor);mBitmapHeight = mBitmap.getHeight();mBitmapWidth = mBitmap.getWidth();mBorderRect.set(0, 0, getWidth(), getHeight());mBorderRadius = Math.min((mBorderRect.height() - mBorderWidth) / 2.0f, (mBorderRect.width() - mBorderWidth) / 2.0f);mDrawableRect.set(mBorderRect);if (!mBorderOverlay) {mDrawableRect.inset(mBorderWidth, mBorderWidth);}mDrawableRadius = Math.min(mDrawableRect.height() / 2.0f, mDrawableRect.width() / 2.0f);updateShaderMatrix();invalidate();
}private void updateShaderMatrix() {float scale;float dx = 0;float dy = 0;mShaderMatrix.set(null);if (mBitmapWidth * mDrawableRect.height() > mDrawableRect.width() * mBitmapHeight) {scale = mDrawableRect.height() / (float) mBitmapHeight;dx = (mDrawableRect.width() - mBitmapWidth * scale) * 0.5f;} else {scale = mDrawableRect.width() / (float) mBitmapWidth;dy = (mDrawableRect.height() - mBitmapHeight * scale) * 0.5f;}mShaderMatrix.setScale(scale, scale);mShaderMatrix.postTranslate((int) (dx + 0.5f) + mDrawableRect.left, (int) (dy + 0.5f) + mDrawableRect.top);mBitmapShader.setLocalMatrix(mShaderMatrix);
}
}`
View的子类需要重写onDraw方法,并根据自身的特点来编写绘制View内容的逻辑。
详细内容可转连接:https://mp.weixin.qq.com/s/DL9XFo8Zd4osHvAy7efgog
安卓自定义view中 绘画基本图形点线面,矩形,方形,圆,扇形,文字及沿着特定方向布局,自定义圆角ImageView图片等等相关api使用方法及举例相关推荐
- android 自定义多边形,Android:自定义view之Canvas绘制图形
前面讲解了onMeasure,接下来讲解onDraw,onDraw主要就是绘制,也就是我们真正关心的部分,使用的是Canvas绘图. @Override protected void onDraw(C ...
- Android 自定义View中坐标点的理解学习(一)
本文主要是记录学习自定义view中看到的资料,为了方便记忆做了保存整理便于自己学习也方便其他Android开发爱好者学习,参考资料看底部链接. 一.getLocationInWindow和getLoc ...
- Android中实现Bitmap在自定义View中的放大与拖动
一基本实现思路: 基于View类实现自定义View –MyImageView类.在使用View的Activity类中完成OnTouchListener接口,实现对MotionEvent事件的监听与处理 ...
- 《Android开发艺术探索》自定义View中关于“HorizontalScrollViewEx”的改进
在<Android开发艺术探索>一书中自定义View一节中提到了关于一个类似横向滑动List的自定义ViewGroup:HorizontalScrollViewEx.如果你使用过的话就会发 ...
- android 自定义paint,Android自定义View中Paint、Rect、Canvas介绍(一)
自定义View对于新手而言貌似是一个很复杂的东西.格式,各函数的意义.对于大神经常忘记各函数及一些参数的具体写法及意义,刚好在做一个风车效果,把过程及遇到的问题都写下来 1.如何自定义一个View p ...
- Android弹性滑动在自定义View中的高级应用
本文出自门心叼龙的博客,属于原创类容,转载请注明出处. 好久没有更新博客了,特意的看了博客最后的更新时间为2019年7月21日,今天是10月24日掐指一算已经有三个月时间了,自从上篇<开发杂谈: ...
- 自定义View中,四个参数的构造函数,其最后两个参数的含义
先看两个参数的构造函数: public View(Context context, @Nullable AttributeSet attrs) {this(context, attrs, 0);} p ...
- 关于自定义View中wrap_content属性失效的问题
我们在使用自定义控件的时候,有时候会发现当我们设置子View的属性为wrap_content时,发现它最终展现的效果跟我们说预想的不一样,它展现的是match_parent的效果,这是为什么呢?先把问 ...
- Android 自定义View中invalidate()的自动清屏含义以及屏幕刷新
invalidate()含义 invalidate()是用来刷新View的,必须是在UI线程中进行工作.比如在修改某个view的显示时,调用invalidate()才能看到重新绘制的界面.invali ...
最新文章
- Android Webview H5 秒开方案实现
- oracle导出BOM文件,ORACLE ERP导数据(BOM清单)-备份恢复-Oracle频道-中国IT实验室
- 深度学习《Transfer Learning》
- 详细整理Spring事务失效的具体场景及解决方案
- 【C语言】编译预处理和宏(附带##介绍)
- 第六章 自动测试实施(上)
- JS为键盘中的Enter键添加onkeyDown()和onkeyUp()事件
- IE实现PDF在线预览功能
- 安卓装Linux ,坑真的多,Linux deployTermux踩坑记||在旧手机上建立自己的服务器(1)||2020年新货
- Android实现滑块拼图验证码功能
- html框架自动居中,Pandas DataFrame.to_html方法,让自动生成的html中的表格整体居中...
- word中插入罗马数字并且设置为Times New Roman字体
- 自媒体平台搜狗号登陆 搜狗挑战百度、头条有胜算吗?
- Java通过HAPI解析HL7消息
- mongodb可视化工具 mac版 Studio 3T破解
- pip安装python库总提示下载超时read timed out的解决办法
- mybatis plus环境搭建及代码生成器
- java计算机毕业设计车辆保险平台系统研究与设计MyBatis+系统+LW文档+源码+调试部署
- 商务礼仪培训PPT模板
- java基于安卓的人脸识别_基于android studio开发的 opencv关于android人脸识别的DEMO