自定义View学习一(圆形头像)
来源:互联网 发布:如何分析问卷调查数据 编辑:程序博客网 时间:2024/05/22 01:56
前言
系统为我们提供的控件是有限的,当我们想要在有限的屏幕上显示更丰富多彩的内容,我们往往需要自定义控件。作为一个android初学者,我对android的自定义View也不是很熟悉。这段时间刚好无事,就先从我们平常使用的圆形头像开始练起吧。
我们要知道一个View绘制需要三大流程onMeasure,onLayout,onDraw
使用BitmapShader实现
控件实现主代码
CircleImageView.kt
class CircleImageView : ImageView { private var radius: Float? = null private val defaultRadius = 40.toFloat() private var mPaint: Paint? = null private var mWidth: Int? = null constructor(context: Context) : super(context) { CircleImageView(context, null) } constructor(context: Context, attributeSet: AttributeSet?) : super(context, attributeSet) { init(attributeSet) } constructor(context: Context, attributeSet: AttributeSet?, defStyleAttributeSet: Int) : super(context, attributeSet, defStyleAttributeSet) { init(attributeSet) } init { mPaint = Paint() mPaint!!.isAntiAlias = true } fun init(attributeSet: AttributeSet?){ val typeArray = context.obtainStyledAttributes(attributeSet, R.styleable.CircleImageView); radius = typeArray.getDimension(R.styleable.CircleImageView_radius, defaultRadius); typeArray.recycle() } //支持padding override fun onDraw(canvas: Canvas?) { if (drawable == null) return setUpShader() val mWidth = width - paddingLeft - paddingRight val mHeight = height - paddingBottom - paddingTop val temp = Math.min(mWidth,mHeight) if (radius!!*2 > temp){ radius = temp/2f } //这个控件的radius原本是不需要的,添加radius主要是为了演示自定义View的自定义属性。 /** * onDraw方法里面我们支持了padding * 由于我们绘制图像的时候需要将图像绘制到中心区域,所以绘制的时候我们也需要考虑padding */ canvas?.drawCircle(paddingLeft+radius!!, paddingTop+radius!!, radius!!, mPaint) } //重写onMeasure,这个自定义view是继承自ImageView,可以不用重写onMeasure方法,但是如果是直接继承自View或者是ViewGroup就需要 //重写onMeasure,否则的话,自定义控件的wrap_content的效果和match_parent一样 override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { super.onMeasure(widthMeasureSpec, heightMeasureSpec) mWidth = Math.min(measuredWidth, measuredHeight) setMeasuredDimension(mWidth!!, mWidth!!) } /** * TileMode的取值有三种: * CLAMP 拉伸 * REPEAT 重复 * MIRROR 镜像 */ private fun setUpShader() { if (drawable == null) return val bitmap = BitmapUtils.drawableToBitmap(drawable) val temp = Math.min(bitmap.width,bitmap.height) val squareBitmap = BitmapUtils.cropBitmap(bitmap,temp,temp) val shader = BitmapShader(squareBitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP) var scale = 1.0f scale = mWidth!! * scale / squareBitmap.width val matrix = Matrix() matrix.setScale(scale, scale) // 为了缩放使用 shader.setLocalMatrix(matrix) mPaint!!.shader = shader }}
使用到的BitmapUtils工具类
BitmapUtils.kt
object BitmapUtils { //将drawable转换成bitmap fun drawableToBitmap(drawable: Drawable): Bitmap { val w = drawable.intrinsicWidth val h = drawable.intrinsicHeight val config = if (drawable.opacity != PixelFormat.OPAQUE) Bitmap.Config.ARGB_8888 else Bitmap.Config.RGB_565 val bitmap = Bitmap.createBitmap(w, h, config) //注意,下面三行代码要用到,否则在View或者SurfaceView里的canvas.drawBitmap会看不到图 val canvas = Canvas(bitmap) drawable.setBounds(0, 0, w, h) drawable.draw(canvas) return bitmap } /** * 裁剪 * @param bitmap 原图 * * * @return 裁剪后的图像 */ fun cropBitmap(bitmap: Bitmap, aimWidth:Int, aimHeight:Int): Bitmap { var toWidth = aimWidth; var toHeight = aimHeight if (aimWidth > bitmap.width) toWidth = bitmap.width if (aimHeight > bitmap.height) toHeight = bitmap.height val cropWidthSide = (bitmap.width - aimWidth)/2 val cropHeightSide = (bitmap.height - aimHeight)/2 return Bitmap.createBitmap(bitmap,cropWidthSide,cropHeightSide,toWidth,toHeight) }}
CircleImageView的属性定义,目前只定义了一个半径
<declare-styleable name="CircleImageView"> <!--圆形头像的半径--> <attr name="radius" format="dimension"/> </declare-styleable>
具体使用
<?xml version="1.0" encoding="utf-8"?><android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context="com.example.xiaojun.blog.MainActivity"> <com.example.xiaojun.kotlin_try.ui.widget.blog.CircleImageView android:layout_width="100dp" android:layout_height="100dp" app:radius="50dp" android:src="@drawable/xue3" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent" /></android.support.constraint.ConstraintLayout>
效果图
参考: Android BitmapShader 实战 实现圆形、圆角图片
使用Xfermode实现圆形头像
代码里面有很多注释,在这里就不解释了
CircleImageViewX.kt
/** * CircleImageViewX是用xFerMode来实现的圆形头像 * CircleImageViewX在头像图片的外层有一圈自定义的圆环,美化头像,默认存在圆环 *///直接继承自View而不再是ImageView,这个时候如果我们想实现wrap_content效果必须自己重写onMeasureclass CircleImageViewX :View{ private val defaultWidth = 200 private val defaultHeight = 200 private val defaultRingWidth = 10f private val defaultRingColor = Color.WHITE private val defaultHasRing = true private var ringColor = defaultRingColor private var ringWidth = defaultRingWidth private var hasRing = defaultHasRing private var drawable:Drawable? = null private var mPaint: Paint? = null constructor(context: Context) : super(context) { } constructor(context: Context, attributeSet: AttributeSet?) : super(context, attributeSet) { init(attributeSet) } constructor(context: Context, attributeSet: AttributeSet?, defStyleAttributeSet: Int) : super(context, attributeSet, defStyleAttributeSet) { init(attributeSet) } init { mPaint = Paint() mPaint!!.isAntiAlias = true } fun init(attributeSet: AttributeSet?){ val typeArray = context.obtainStyledAttributes(attributeSet, R.styleable.CircleImageViewX) drawable = typeArray.getDrawable(R.styleable.CircleImageViewX_src) hasRing = typeArray.getBoolean(R.styleable.CircleImageViewX_hasRing,defaultHasRing) if (hasRing){ ringColor = typeArray.getColor(R.styleable.CircleImageViewX_ringColor,defaultRingColor) ringWidth = typeArray.getDimension(R.styleable.CircleImageViewX_ringWidth,defaultRingWidth) } typeArray.recycle() } //当布局中的宽或者高属性设置的是wrap_content的时候,我们返回默认宽或者高 override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { val widthMode = MeasureSpec.getMode(widthMeasureSpec) val widthSize = MeasureSpec.getSize(widthMeasureSpec) val heightMode = MeasureSpec.getMode(heightMeasureSpec) val heightSize = MeasureSpec.getSize(heightMeasureSpec) if (widthMode == MeasureSpec.AT_MOST && heightMode == MeasureSpec.AT_MOST){ setMeasuredDimension(defaultWidth,defaultHeight) }else if (widthMode == MeasureSpec.AT_MOST){ setMeasuredDimension(defaultWidth,heightSize) }else if (heightMode == MeasureSpec.AT_MOST){ setMeasuredDimension(widthSize,defaultHeight) }else{ setMeasuredDimension(widthSize,heightSize) } super.onMeasure(widthMeasureSpec, heightMeasureSpec) } override fun onDraw(canvas: Canvas?) { if (drawable == null){ Log.e("drawable","null") return } Log.e("drawable","draw") val bitmap = BitmapUtils.drawableToBitmap(drawable!!) val squareBitmap = BitmapUtils.cropSquareBitmap(bitmap) //为了支持padding val mWidth = width - paddingLeft - paddingRight val mHeight = height - paddingBottom - paddingTop var interval = 0 val radius = Math.min(mWidth,mHeight)/2f if (hasRing){ //画圆环 interval = ringWidth.toInt() mPaint?.color = ringColor mPaint?.strokeWidth = ringWidth mPaint?.style = Paint.Style.STROKE //当线条有宽度的时候,paint是默认在线条宽度的中央绘制,这就要求我们在绘制外部大圆的时候有所调整 canvas?.drawCircle(paddingLeft+radius,paddingTop+radius,radius - ringWidth/2,mPaint) } val circleBitmapRadius = radius - interval val circleBitmap = cropCircleBitmap(squareBitmap,circleBitmapRadius) //画圆形图片 val srcRect = Rect(0,0,circleBitmap.width,circleBitmap.height) val desRect = Rect(paddingLeft+interval,paddingTop+interval,circleBitmap.width+paddingLeft+interval,circleBitmap.height+paddingTop+interval) canvas?.drawBitmap(circleBitmap,srcRect,desRect,mPaint) } //传进来的bitmap是已经裁剪好的bitmap fun cropCircleBitmap(bitmap: Bitmap,radius:Float):Bitmap{ val ret = Bitmap.createBitmap(2*radius.toInt(),2*radius.toInt(), Bitmap.Config.ARGB_8888) val paint = Paint(Paint.ANTI_ALIAS_FLAG) //创建一个和目标图片大小相同的canvas val canvas = Canvas(ret) //绘制下层图,是一个圆 canvas.drawCircle(radius,radius,radius,paint) // 设置混合模式,取绘制的图的交集部分,显示上层 paint.xfermode = PorterDuffXfermode(PorterDuff.Mode.SRC_IN) // 第一个Rect 代表要绘制的bitmap 区域,第二个 Rect 代表的是要将bitmap 绘制在屏幕的什么地方 val srcRect = Rect(0,0,bitmap.height,bitmap.width) val desRect = Rect(0,0,radius.toInt()*2,radius.toInt()*2) //上面两个rect的含义相当于用matrix设置scale。含义是把源图片的srcRect区域内容绘制在desRect区域内 canvas.drawBitmap(bitmap,srcRect,desRect,paint) return ret } //设置圆环颜色 fun setRingColor(color:Int){ this.ringColor = color invalidate() } //设置圆环宽度 fun setRingWidth(width:Float){ this.ringWidth = width invalidate() } //设置是否有圆环 fun setHasRing(has:Boolean){ hasRing = has invalidate() }}
自定义属性
<declare-styleable name="CircleImageViewX"> <!--图片资源--> <attr name="src" format="reference"/> <!--是否添加外围圆环,如果没有的话,那么后面的两个参数都没有意义--> <attr name="hasRing" format="boolean"/> <!--圆环宽度--> <attr name="ringWidth" format="dimension"/> <!--圆环颜色--> <attr name="ringColor" format="color"/> </declare-styleable>
使用
<com.example.xiaojun.blog.widget.CircleImageViewX android:id="@+id/circleX" android:layout_width="100dp" android:layout_height="100dp" app:ringColor="@color/colorAccent" app:hasRing="true" app:ringWidth="10px" app:src="@drawable/xue1" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintBottom_toBottomOf="parent" android:layout_marginBottom="88dp" app:layout_constraintStart_toStartOf="parent" app:layout_constraintHorizontal_bias="0.539" />
效果图
参考: 关于Xfermode的介绍和用处(遮罩图层,圆形图片)
阅读全文
0 0
- 自定义View学习一(圆形头像)
- 自定义view圆形头像
- android圆形头像,自定义view
- Android 自定义View -->圆形头像
- 自定义view之圆形头像(2种实现方式)
- 自定义View--圆形头像(用PorterDuff.Mode)
- Android 中CircleImageView自定义View(圆形头像)
- Android自定义View之圆形头像
- android自定义圆形头像view,继承imageview
- Android圆形头像显示自定义View
- Android 自定义View 之 圆形头像
- Android自定义View之圆形头像
- Android 自定义圆形头像(简单的画出来,不需要自定义view)
- CircleImagerView,圆形头像view
- 自定义View 之 ImageView(一) 自定义圆形ImageView
- 安卓开发之自定义View------> 圆形头像ImageView
- android自定义view之圆形头像的完美例子
- Android-解析自定义view之圆形头像的各类方案
- Spring-AOP 复合切点切面
- JavaWeb学习笔记—Tomcat服务器安装与配置
- JS继承
- Spring概述
- 移植mplayer
- 自定义View学习一(圆形头像)
- HDU 6141 最小树形图
- 二分图模板
- java-String
- 面试总结
- shutdown函数和FIN_WAIT2状态
- NYOJ27
- 2017"百度之星"程序设计大赛
- Linux 下 MySQL安装教程