自定义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的介绍和用处(遮罩图层,圆形图片)

原创粉丝点击