Kotlin实战篇之自定义View图片圆角简单应用(一)

简述: 关注我的Kotlin浅谈系列文章的小伙伴就知道关于Kotlin语法篇的内容已经发布了一些。然后就会有小伙伴问了一直都在讲语法是否来一波实战了,毕竟一切一切的学习都是为了解决实际问题的,所以准备来一波Kotlin实战篇,主要是用Kotlin来实现一些常见的功能和需求。实现同一个功能相比Java实现你也许会更钟爱于使用kotlin。

  • 1、为什么要用Kotlin去实现Android中的自定义View?
  • 2、为什么要去自定义View实现图片圆角定制化?
  • 3、实现该功能需要具备的知识
  • 4、图片圆角定制化需要满足哪些需求
  • 5、实现原理和思路分析
  • 6、自定义View中Java和Kotlin实现的对比
  • 7、具体的代码实现

一、为什么要用Kotlin去实现Android中的自定义View?

针对这个问题的回答一般是给正在学习Kotlin或者Kotlin学习的新手而言,如果是刚刚学习Kotlin的时候,让你去用Kotlin实现一个自定义View,可能会有些不习惯,比如在Kotlin定义View的构造器重载怎么实现?是否要像Java暴露很多的set方法属性给外部调用,然后重绘页面呢?由于这是第一篇Kotlin实战篇,也就比较简单主要针对新手。

二、为什么要去自定义View实现图片圆角定制化?

实现图片圆形和圆角这个需求有很多种方式,经过开发试验最终比较稳的还是自定义View来实现。图片圆形或者圆角在一些图片加载框架中就集成好了,比如Glide中就有BitmapTransformation,开发者可以去继承BitmapTransformation,然后去实现Bitmap绘制逻辑在图片层面来达到图片圆角或者圆形的效果。有两点原因让我放弃使用它:

第一,单从面向对象的角度,库单一职责来说,图片加载库就是负责从网络源加载图片的,至于这个ImageView长得什么形状,则是通过ImageView来呈现的。

第二, 使用自定义BitmapTransformation来定义形状发现有bug,就是一张来自网络端的图片当它没有加载完成的时候是无法拿到图片尺寸的,而在BitmapTransformation中需要拿到图片宽和高。所以用post,Runnable机制等待加载完毕后就去定义形状,这样的实现在大部分场景是可以满足的。但是在一个需要刷新的列表中就会明显发现,每次刷新图片去加载,图片会有空白的过程很影响体验。

三、实现该功能需要具备知识

  • 1、Kotlin中基本语法知识
  • 2、Kotlin中自定义属性访问器
  • 3、Kotlin中默认值参数实现构造器函数重载以及@JvmOverloads注解的使用
  • 4、Kotlin标准库中常见的apply,run,with函数的使用
  • 5、Kotlin中默认值参数函数的使用
  • 6、自定义View的基本知识
  • 7、Path的使用
  • 8、Matrix的使用
  • 9、BitmapShader的使用

四、图片圆角定制化需要满足哪些需求

  • 1、支持图片圆形的定制化
  • 2、支持图片圆角以及每个角的X,Y方向值的定制化
  • 3、支持形状边框宽度颜色的定制化
  • 4、支持图片圆角或者圆形右上角消息圆点定制化(一般用于圆形或者圆角头像)

五、自定义View实现的原理和思路分析

这个自定义View实现原理很简单,主要有三个比较重要的点,第一就是构建定义圆角矩形的Path;第二是利用Matrix矩阵变换按比例缩小或放大使得图片大小和ImageView大小保持一致;第三就是使用BitmapShader对已经定义好的path,用带shader的画笔进行渲染。

六、自定义View中Java和Kotlin实现的对比

  • 1、自定义View构造器重载对比

java实现,这样的写法是java实现自定义View常用套路

public class PrettyImageView extends ImageView {
    public PrettyImageView(Context context) {
        this(context, null);
    }

    public PrettyImageView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public PrettyImageView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }
}

kotlin实现,使用到了之前博客讲过的默认值参数实现函数重载以及使用@JvmOverloads注解是为了在Java中可以调用Kotlin中定义重载构造器方法。(这两个知识点都是之前博客有专门分析包括其原理)

class PrettyImageView @JvmOverloads constructor(context: Context, attributeSet: AttributeSet? = null, defAttrStyle: Int = 0)
	: ImageView(context, attributeSet, defAttrStyle) {
	    
	}
  • 2、自定义View 属性改变的setter暴露

java实现,需要实现对应属性的setter方法,然后内部调用invalidate重绘

public class PrettyImageView extends ImageView {
    private boolean mIsShowBorder;
    private float mBorderWidth;
    private int mBorderColor;
    private boolean mIsShowDot;
    private float mDotRadius;
    private int mDotColor;

    public PrettyImageView(Context context) {
        this(context, null);
    }

    public PrettyImageView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public PrettyImageView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    public void setmIsShowBorder(boolean mIsShowBorder) {
        this.mIsShowBorder = mIsShowBorder;
        invalidate();
    }

    public void setmBorderWidth(float mBorderWidth) {
        this.mBorderWidth = mBorderWidth;
        invalidate();
    }

    public void setmBorderColor(int mBorderColor) {
        this.mBorderColor = mBorderColor;
        invalidate();
    }

    public void setmIsShowDot(boolean mIsShowDot) {
        this.mIsShowDot = mIsShowDot;
        invalidate();
    }

    public void setmDotRadius(float mDotRadius) {
        this.mDotRadius = mDotRadius;
        invalidate();
    }

    public void setmDotColor(int mDotColor) {
        this.mDotColor = mDotColor;
        invalidate();
    }
}

Kotlin实现则不需要定义那么多setter方法,因为Kotlin中var变量就自带setter和getter方法,可以我们又想达到当重新改变值后需要调用invalidate函数。这是就需要用之前讲过自定义变量访问器。

class PrettyImageView @JvmOverloads constructor(context: Context, attributeSet: AttributeSet? = null, defAttrStyle: Int = 0)
	: ImageView(context, attributeSet, defAttrStyle) {
	private var mBorderWidth: Float = 20f
		set(value) {
			field = value
			invalidate()
		}
	private var mBorderColor: Int = Color.parseColor("#ff9900")
		set(value) {
			field = value
			invalidate()
		} 
			private var mShowBorder: Boolean = true
		set(value) {
			field = value
			invalidate()
		}
	private var mShowCircleDot: Boolean = false
		set(value) {
			field = value
			invalidate()
		}
	private var mCircleDotColor: Int = Color.RED
		set(value) {
			field = value
			invalidate()
		}

	private var mCircleDotRadius: Float = 20f
		set(value) {
			field = value
			invalidate()
		}
}

七、具体代码实现

  • 1、开放的自定义View属性
开放属性name 开放属性含义
shape_type 形状类型,目前只有圆角和圆形两种类型
left_top_radiusX 左上角X轴方向半径
left_top_radiusY 左上角Y轴方向半径
right_top_radiusX 右上角X轴方向半径
right_top_radiusY 右上角Y轴方向半径
right_bottom_radiusX 右下角X轴方向半径
right_bottom_radiusY 右下角Y轴方向半径
left_bottom_radiusX 左下角X轴方向半径
left_bottom_radiusY 左下角Y轴方向半径
show_border 是否显示边框
border_width 边框宽度
border_color 边框颜色
show_circle_dot 是否显示右上角圆点
circle_dot_color 右上角圆点颜色
circle_dot_radius 右上角圆点半径
  • 2、attrs.xml的定义声明

<resources>
    <declare-styleable name="PrettyImageView">
        <attr name="shape_type">
            <enum name="SHAPE_CIRCLE" value="0">enum>
            <enum name="SHAPE_ROUND" value="1">enum>
        attr>
        <attr name="border_width" format="dimension"/>
        <attr name="border_color" format="color"/>
        <attr name="left_top_radiusX" format="dimension"/>
        <attr name="left_top_radiusY" format="dimension"/>
        <attr name="right_top_radiusX" format="dimension"/>
        <attr name="right_top_radiusY" format="dimension"/>
        <attr name="right_bottom_radiusX" format="dimension"/>
        <attr name="right_bottom_radiusY" format="dimension"/>
        <attr name="left_bottom_radiusX" format="dimension"/>
        <attr name="left_bottom_radiusY" format="dimension"/>
        <attr name="show_border" format="boolean"/>
        <attr name="show_circle_dot" format="boolean"/>
        <attr name="circle_dot_color" format="color"/>
        <attr name="circle_dot_radius" format="dimension"/>
    declare-styleable>
resources>
  • 3、具体实现代码
class PrettyImageView @JvmOverloads constructor(context: Context, attributeSet: AttributeSet? = null, defAttrStyle: Int = 0)
	: ImageView(context, attributeSet, defAttrStyle) {

	enum class ShapeType {
		SHAPE_CIRCLE,
		SHAPE_ROUND
	}

	//defAttr var
	private var mShapeType: ShapeType = ShapeType.SHAPE_CIRCLE
		set(value) {
			field = value
			invalidate()
		}
	private var mBorderWidth: Float = 20f
		set(value) {
			field = value
			invalidate()
		}
	private var mBorderColor: Int = Color.parseColor("#ff9900")
		set(value) {
			field = value
			invalidate()
		}

	private var mLeftTopRadiusX: Float = 0f
		set(value) {
			field = value
			invalidate()
		}
	private var mLeftTopRadiusY: Float = 0f
		set(value) {
			field = value
			invalidate()
		}
	private var mRightTopRadiusX: Float = 0f
		set(value) {
			field = value
			invalidate()
		}
	private var mRightTopRadiusY: Float = 0f
		set(value) {
			field = value
			invalidate()
		}
	private var mLeftBottomRadiusX: Float = 0f
		set(value) {
			field = value
			invalidate()
		}
	private var mLeftBottomRadiusY: Float = 0f
		set(value) {
			field = value
			invalidate()
		}
	private var mRightBottomRadiusX: Float = 0f
		set(value) {
			field = value
			invalidate()
		}
	private var mRightBottomRadiusY: Float = 0f
		set(value) {
			field = value
			invalidate()
		}
	private var mShowBorder: Boolean = true
		set(value) {
			field = value
			invalidate()
		}
	private var mShowCircleDot: Boolean = false
		set(value) {
			field = value
			invalidate()
		}
	private var mCircleDotColor: Int = Color.RED
		set(value) {
			field = value
			invalidate()
		}

	private var mCircleDotRadius: Float = 20f
		set(value) {
			field = value
			invalidate()
		}

	//drawTools var
	private lateinit var mShapePath: Path
	private lateinit var mBorderPath: Path
	private lateinit var mBitmapPaint: Paint
	private lateinit var mBorderPaint: Paint
	private lateinit var mCircleDotPaint: Paint
	private lateinit var mMatrix: Matrix

	//temp var
	private var mWidth: Int = 200//View的宽度
	private var mHeight: Int = 200//View的高度
	private var mRadius: Float = 100f//圆的半径

	init {
		initAttrs(context, attributeSet, defAttrStyle)//获取自定义属性的值
		initDrawTools()//初始化绘制工具
	}

	private fun initAttrs(context: Context, attributeSet: AttributeSet?, defAttrStyle: Int) {
		val array = context.obtainStyledAttributes(attributeSet, R.styleable.PrettyImageView, defAttrStyle, 0)
		(0..array.indexCount)
				.asSequence()
				.map { array.getIndex(it) }
				.forEach {
					when (it) {
						R.styleable.PrettyImageView_shape_type ->
							mShapeType = when {
								array.getInt(it, 0) == 0 -> ShapeType.SHAPE_CIRCLE
								array.getInt(it, 0) == 1 -> ShapeType.SHAPE_ROUND
								else -> ShapeType.SHAPE_CIRCLE
							}
						R.styleable.PrettyImageView_border_width ->
							mBorderWidth = array.getDimension(it, TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 4f, resources.displayMetrics))
						R.styleable.PrettyImageView_border_color ->
							mBorderColor = array.getColor(it, Color.parseColor("#ff0000"))
						R.styleable.PrettyImageView_left_top_radiusX ->
							mLeftTopRadiusX = array.getDimension(it, TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 0f, resources.displayMetrics))
						R.styleable.PrettyImageView_left_top_radiusY ->
							mLeftTopRadiusY = array.getDimension(it, TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 0f, resources.displayMetrics))
						R.styleable.PrettyImageView_left_bottom_radiusX ->
							mLeftBottomRadiusX = array.getDimension(it, TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 0f, resources.displayMetrics))
						R.styleable.PrettyImageView_left_bottom_radiusY ->
							mLeftBottomRadiusY = array.getDimension(it, TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 0f, resources.displayMetrics))
						R.styleable.PrettyImageView_right_bottom_radiusX ->
							mRightBottomRadiusX = array.getDimension(it, TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 0f, resources.displayMetrics))
						R.styleable.PrettyImageView_right_bottom_radiusY ->
							mRightBottomRadiusY = array.getDimension(it, TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 0f, resources.displayMetrics))
						R.styleable.PrettyImageView_right_top_radiusX ->
							mRightTopRadiusX = array.getDimension(it, TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 0f, resources.displayMetrics))
						R.styleable.PrettyImageView_right_top_radiusY ->
							mRightTopRadiusY = array.getDimension(it, TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 0f, resources.displayMetrics))
						R.styleable.PrettyImageView_show_border ->
							mShowBorder = array.getBoolean(it, false)
						R.styleable.PrettyImageView_show_circle_dot ->
							mShowCircleDot = array.getBoolean(it, false)
						R.styleable.PrettyImageView_circle_dot_color ->
							mCircleDotColor = array.getColor(it, Color.parseColor("#ff0000"))
						R.styleable.PrettyImageView_circle_dot_radius ->
							mCircleDotRadius = array.getDimension(it, TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 5f, resources.displayMetrics))
					}
				}
		array.recycle()
	}

	private fun initDrawTools() {
		mBitmapPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {//最终绘制图片的画笔,需要设置BitmapShader着色器,从而实现把图片绘制在不同形状图形上
			style = Paint.Style.FILL
		}
		mBorderPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {//绘制边框画笔
			style = Paint.Style.STROKE
			color = mBorderColor
			strokeCap = Paint.Cap.ROUND
			strokeWidth = mBorderWidth
		}
		mCircleDotPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {//绘制右上角圆点画笔
			style = Paint.Style.FILL
			color = mCircleDotColor
		}
		mShapePath = Path()//描述形状轮廓的path路径
		mBorderPath = Path()//描述图片边框轮廓的path路径
		mMatrix = Matrix()//用于缩放图片的矩阵
		scaleType = ScaleType.CENTER_CROP
	}

	override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {//View的测量
		super.onMeasure(widthMeasureSpec, heightMeasureSpec)
		if (mShapeType == ShapeType.SHAPE_CIRCLE) {
			mWidth = Math.min(measuredWidth, measuredHeight)
			mRadius = mWidth / 2.0f
			setMeasuredDimension(mWidth, mWidth)
		} else {
			mWidth = measuredWidth
			mHeight = measuredHeight
			setMeasuredDimension(mWidth, mHeight)
		}
	}

	override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {//确定了最终View的尺寸
		super.onSizeChanged(w, h, oldw, oldh)
		mBorderPath.reset()
		mShapePath.reset()
		when (mShapeType) {
			ShapeType.SHAPE_ROUND -> {
				mWidth = w
				mHeight = h
				buildRoundPath()
			}
			ShapeType.SHAPE_CIRCLE -> {
				buildCirclePath()
			}
		}
	}

	private fun buildCirclePath() {//构建圆形类型的Path路径
		if (!mShowBorder) {//绘制不带边框的圆形实际上只需要把一个圆形扔进path即可
			mShapePath.addCircle(mRadius, mRadius, mRadius, Path.Direction.CW)
		} else {//绘制带边框的圆形需要把内部圆形和外部圆形边框都要扔进path
			mShapePath.addCircle(mRadius, mRadius, mRadius - mBorderWidth, Path.Direction.CW)
			mBorderPath.addCircle(mRadius, mRadius, mRadius - mBorderWidth / 2.0f, Path.Direction.CW)
		}
	}

	private fun buildRoundPath() {//构建圆角类型的Path路径
		if (!mShowBorder) {//绘制不带边框的圆角实际上只需要把一个圆角矩形扔进path即可
			floatArrayOf(mLeftTopRadiusX, mLeftTopRadiusY,
					mRightTopRadiusX, mRightTopRadiusY,
					mRightBottomRadiusX, mRightBottomRadiusY,
					mLeftBottomRadiusX, mLeftBottomRadiusY).run {
				mShapePath.addRoundRect(RectF(0f, 0f, mWidth.toFloat(), mHeight.toFloat()), this, Path.Direction.CW)
			}

		} else {//绘制带边框的圆角实际上只需要把一个圆角矩形和一个圆角矩形的变量都扔进path即可
			floatArrayOf(mLeftTopRadiusX - mBorderWidth / 2.0f, mLeftTopRadiusY - mBorderWidth / 2.0f,
					mRightTopRadiusX - mBorderWidth / 2.0f, mRightTopRadiusY - mBorderWidth / 2.0f,
					mRightBottomRadiusX - mBorderWidth / 2.0f, mRightBottomRadiusY - mBorderWidth / 2.0f,
					mLeftBottomRadiusX - mBorderWidth / 2.0f, mLeftBottomRadiusY - mBorderWidth / 2.0f).run {
				mBorderPath.addRoundRect(RectF(mBorderWidth / 2.0f, mBorderWidth / 2.0f, mWidth.toFloat() - mBorderWidth / 2.0f, mHeight.toFloat() - mBorderWidth / 2.0f), this, Path.Direction.CW)
			}

			floatArrayOf(mLeftTopRadiusX - mBorderWidth, mLeftTopRadiusY - mBorderWidth,
					mRightTopRadiusX - mBorderWidth, mRightTopRadiusY - mBorderWidth,
					mRightBottomRadiusX - mBorderWidth, mRightBottomRadiusY - mBorderWidth,
					mLeftBottomRadiusX - mBorderWidth, mLeftBottomRadiusY - mBorderWidth).run {
				mShapePath.addRoundRect(RectF(mBorderWidth, mBorderWidth, mWidth.toFloat() - mBorderWidth, mHeight.toFloat() - mBorderWidth),
						this, Path.Direction.CW)
			}

		}
	}

	override fun onDraw(canvas: Canvas?) {//由于经过以上根据不同逻辑构建了boderPath和shapePath,path中已经储存相应的形状,现在只需要把相应shapePath中形状用带BitmapShader画笔绘制出来,boderPath用普通画笔绘制出来即可
		drawable ?: return
		mBitmapPaint.shader = getBitmapShader()//获得相应的BitmapShader着色器对象
		when (mShapeType) {
			ShapeType.SHAPE_CIRCLE -> {
				if (mShowBorder) {
					canvas?.drawPath(mBorderPath, mBorderPaint)//绘制圆形图片边框path
				}
				canvas?.drawPath(mShapePath, mBitmapPaint)//绘制圆形图片形状path
				if (mShowCircleDot) {
					drawCircleDot(canvas)//绘制圆形图片右上角圆点
				}
			}
			ShapeType.SHAPE_ROUND -> {
				if (mShowBorder) {
					canvas?.drawPath(mBorderPath, mBorderPaint)//绘制圆角图片边框path
				}
				canvas?.drawPath(mShapePath, mBitmapPaint)//绘制圆角图片形状path
			}
		}

	}

	private fun drawCircleDot(canvas: Canvas?) {
		canvas?.run {
			drawCircle((mRadius + mRadius * (Math.sqrt(2.0) / 2.0f)).toFloat(), (mRadius - mRadius * (Math.sqrt(2.0) / 2.0f)).toFloat(), mCircleDotRadius, mCircleDotPaint)
		}
	}

	private fun getBitmapShader(): BitmapShader {
		val bitmap = drawableToBitmap(drawable)
		return BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP).apply {
			var scale = 1.0f
			if (mShapeType == ShapeType.SHAPE_CIRCLE) {
				scale = (mWidth * 1.0f / Math.min(bitmap.width, bitmap.height))
			} else if (mShapeType == ShapeType.SHAPE_ROUND) {
				// 如果图片的宽或者高与view的宽高不匹配,计算出需要缩放的比例;缩放后的图片的宽高,一定要大于我们view的宽高;所以我们这里取大值;
				if (!(width == bitmap.width && width == bitmap.height)) {
					scale = Math.max(width * 1.0f / bitmap.width, height * 1.0f / bitmap.height)
				}
			}
			// shader的变换矩阵,我们这里主要用于放大或者缩小
			mMatrix.setScale(scale, scale)
			setLocalMatrix(mMatrix)
		}
	}

	private fun drawableToBitmap(drawable: Drawable): Bitmap {
		if (drawable is BitmapDrawable) {
			return drawable.bitmap
		}
		return Bitmap.createBitmap(drawable.intrinsicWidth, drawable.intrinsicHeight, Bitmap.Config.ARGB_8888).apply {
			drawable.setBounds(0, 0, drawable.intrinsicWidth, drawable.intrinsicHeight)
			drawable.draw(Canvas(this@apply))
		}
	}

	companion object {
		private const val STATE_INSTANCE = "state_instance"
		private const val STATE_INSTANCE_SHAPE_TYPE = "state_shape_type"
		private const val STATE_INSTANCE_BORDER_WIDTH = "state_border_width"
		private const val STATE_INSTANCE_BORDER_COLOR = "state_border_color"
		private const val STATE_INSTANCE_RADIUS_LEFT_TOP_X = "state_radius_left_top_x"
		private const val STATE_INSTANCE_RADIUS_LEFT_TOP_Y = "state_radius_left_top_y"
		private const val STATE_INSTANCE_RADIUS_LEFT_BOTTOM_X = "state_radius_left_bottom_x"
		private const val STATE_INSTANCE_RADIUS_LEFT_BOTTOM_Y = "state_radius_left_bottom_y"
		private const val STATE_INSTANCE_RADIUS_RIGHT_TOP_X = "state_radius_right_top_x"
		private const val STATE_INSTANCE_RADIUS_RIGHT_TOP_Y = "state_radius_right_top_y"
		private const val STATE_INSTANCE_RADIUS_RIGHT_BOTTOM_X = "state_radius_right_bottom_x"
		private const val STATE_INSTANCE_RADIUS_RIGHT_BOTTOM_Y = "state_radius_right_bottom_y"
		private const val STATE_INSTANCE_RADIUS = "state_radius"
		private const val STATE_INSTANCE_SHOW_BORDER = "state_radius_show_border"
	}

	//View State Save
	override fun onSaveInstanceState(): Parcelable = Bundle().apply {
		putParcelable(STATE_INSTANCE, super.onSaveInstanceState())
		putInt(STATE_INSTANCE_SHAPE_TYPE, when (mShapeType) {
			ShapeType.SHAPE_CIRCLE -> 0
			ShapeType.SHAPE_ROUND -> 1
		})
		putFloat(STATE_INSTANCE_BORDER_WIDTH, mBorderWidth)
		putInt(STATE_INSTANCE_BORDER_COLOR, mBorderColor)
		putFloat(STATE_INSTANCE_RADIUS_LEFT_TOP_X, mLeftTopRadiusX)
		putFloat(STATE_INSTANCE_RADIUS_LEFT_TOP_Y, mLeftTopRadiusY)
		putFloat(STATE_INSTANCE_RADIUS_LEFT_BOTTOM_X, mLeftBottomRadiusX)
		putFloat(STATE_INSTANCE_RADIUS_LEFT_BOTTOM_Y, mLeftBottomRadiusY)
		putFloat(STATE_INSTANCE_RADIUS_RIGHT_TOP_X, mRightTopRadiusX)
		putFloat(STATE_INSTANCE_RADIUS_RIGHT_TOP_Y, mRightTopRadiusY)
		putFloat(STATE_INSTANCE_RADIUS_RIGHT_BOTTOM_X, mRightBottomRadiusX)
		putFloat(STATE_INSTANCE_RADIUS_RIGHT_BOTTOM_Y, mRightBottomRadiusY)
		putFloat(STATE_INSTANCE_RADIUS, mRadius)
		putBoolean(STATE_INSTANCE_SHOW_BORDER, mShowBorder)
	}

	//View State Restore
	override fun onRestoreInstanceState(state: Parcelable?) {
		if (state !is Bundle) {
			super.onRestoreInstanceState(state)
			return
		}

		with(state) {
			super.onRestoreInstanceState(getParcelable(STATE_INSTANCE))
			mShapeType = when {
				getInt(STATE_INSTANCE_SHAPE_TYPE) == 0 -> ShapeType.SHAPE_CIRCLE
				getInt(STATE_INSTANCE_SHAPE_TYPE) == 1 -> ShapeType.SHAPE_ROUND
				else -> ShapeType.SHAPE_CIRCLE
			}
			mBorderWidth = getFloat(STATE_INSTANCE_BORDER_WIDTH)
			mBorderColor = getInt(STATE_INSTANCE_BORDER_COLOR)
			mLeftTopRadiusX = getFloat(STATE_INSTANCE_RADIUS_LEFT_TOP_X)
			mLeftTopRadiusY = getFloat(STATE_INSTANCE_RADIUS_LEFT_TOP_Y)
			mLeftBottomRadiusX = getFloat(STATE_INSTANCE_RADIUS_LEFT_BOTTOM_X)
			mLeftBottomRadiusY = getFloat(STATE_INSTANCE_RADIUS_LEFT_BOTTOM_Y)
			mRightTopRadiusX = getFloat(STATE_INSTANCE_RADIUS_RIGHT_TOP_X)
			mRightTopRadiusY = getFloat(STATE_INSTANCE_RADIUS_RIGHT_TOP_Y)
			mRightBottomRadiusX = getFloat(STATE_INSTANCE_RADIUS_RIGHT_BOTTOM_X)
			mRightBottomRadiusY = getFloat(STATE_INSTANCE_RADIUS_RIGHT_BOTTOM_Y)
			mRadius = getFloat(STATE_INSTANCE_RADIUS)
			mShowBorder = getBoolean(STATE_INSTANCE_SHOW_BORDER)
		}
	}
}

项目GitHub地址

运行效果截图:

欢迎关注Kotlin开发者联盟,这里有最新Kotlin技术文章,每周会不定期翻译一篇Kotlin国外技术文章。如果你也喜欢Kotlin,欢迎加入我们~~~

Kotlin系列文章,欢迎查看:

Kotlin邂逅设计模式系列:

  • 当Kotlin完美邂逅设计模式之单例模式(一)

数据结构与算法系列:

  • 每周一算法之二分查找(Kotlin描述)

翻译系列:

  • [译] Kotlin中关于Companion Object的那些事
  • [译]记一次Kotlin官方文档翻译的PR(内联类)
  • [译]Kotlin中内联类的自动装箱和高性能探索(二)
  • [译]Kotlin中内联类(inline class)完全解析(一)
  • [译]Kotlin的独门秘籍Reified实化类型参数(上篇)
  • [译]Kotlin泛型中何时该用类型形参约束?
  • [译] 一个简单方式教你记住Kotlin的形参和实参
  • [译]Kotlin中是应该定义函数还是定义属性?
  • [译]如何在你的Kotlin代码中移除所有的!!(非空断言)
  • [译]掌握Kotlin中的标准库函数: run、with、let、also和apply
  • [译]有关Kotlin类型别名(typealias)你需要知道的一切
  • [译]Kotlin中是应该使用序列(Sequences)还是集合(Lists)?
  • [译]Kotlin中的龟(List)兔(Sequence)赛跑

原创系列:

  • 教你如何完全解析Kotlin中的类型系统
  • 如何让你的回调更具Kotlin风味
  • Jetbrains开发者日见闻(三)之Kotlin1.3新特性(inline class篇)
  • JetBrains开发者日见闻(二)之Kotlin1.3的新特性(Contract契约与协程篇)
  • JetBrains开发者日见闻(一)之Kotlin/Native 尝鲜篇
  • 教你如何攻克Kotlin中泛型型变的难点(实践篇)
  • 教你如何攻克Kotlin中泛型型变的难点(下篇)
  • 教你如何攻克Kotlin中泛型型变的难点(上篇)
  • Kotlin的独门秘籍Reified实化类型参数(下篇)
  • 有关Kotlin属性代理你需要知道的一切
  • 浅谈Kotlin中的Sequences源码解析
  • 浅谈Kotlin中集合和函数式API完全解析-上篇
  • 浅谈Kotlin语法篇之lambda编译成字节码过程完全解析
  • 浅谈Kotlin语法篇之Lambda表达式完全解析
  • 浅谈Kotlin语法篇之扩展函数
  • 浅谈Kotlin语法篇之顶层函数、中缀调用、解构声明
  • 浅谈Kotlin语法篇之如何让函数更好地调用
  • 浅谈Kotlin语法篇之变量和常量
  • 浅谈Kotlin语法篇之基础语法

Effective Kotlin翻译系列

  • [译]Effective Kotlin系列之考虑使用原始类型的数组优化性能(五)
  • [译]Effective Kotlin系列之使用Sequence来优化集合的操作(四)
  • [译]Effective Kotlin系列之探索高阶函数中inline修饰符(三)
  • [译]Effective Kotlin系列之遇到多个构造器参数要考虑使用构建器(二)
  • [译]Effective Kotlin系列之考虑使用静态工厂方法替代构造器(一)

实战系列:

  • 用Kotlin撸一个图片压缩插件ImageSlimming-导学篇(一)
  • 用Kotlin撸一个图片压缩插件-插件基础篇(二)
  • 用Kotlin撸一个图片压缩插件-实战篇(三)
  • 浅谈Kotlin实战篇之自定义View图片圆角简单应用

你可能感兴趣的:(Kotlin,kotlin,自定义View,android,java)