Brief introduction: Those of you who follow my Kotlin Shallow Talk series will know that there have been some posts about Kotlin grammar. After all, all the learning is for solving practical problems, so I prepare a wave of Kotlin practice, mainly with Kotlin to achieve some common functions and requirements. You may prefer using Kotlin to implementing the same functionality in Java.

  • 1. Why use Kotlin to implement custom Views in Android?
  • 2, why to customize the View to achieve picture fillet customization?
  • 3. Knowledge required to achieve this function
  • 4, picture fillet customization needs to meet what needs
  • 5. Analysis of implementation principles and ideas
  • 6, custom View Java and Kotlin implementation comparison
  • 7. Specific code implementation

Why use Kotlin to implement a custom View in Android?

The answer to this question is generally for beginners who are learning Kotlin or Kotlin learning. If you are just learning Kotlin, let you use Kotlin to implement a custom View, you may have some unaccustomed, such as Kotlin definition View constructor reload how to implement? Do you want to expose a lot of set method properties like Java to external calls and then redraw the page? Since this is the first Kotlin practice post, it is relatively simple and aimed at beginners.

Second, why to customize the View to achieve picture fillet customization?

There are many ways to achieve the picture circle and rounded corner this demand, after the development of the test is more stable or custom View to achieve. Image circles or rounded corners are integrated in some image loading frameworks, such as Glide, which has BitmapTransformation. Developers can inherit BitmapTransformation and then implement the Bitmap drawing logic on the image level to achieve rounded corners or rounded corners. I gave it up for two reasons:

First, from an object-oriented perspective, the image loading library is responsible for loading images from web sources, and the shape of the ImageView is represented by the ImageView.

Second, there is a bug in using custom BitmapTransformation to define shapes. It is not possible to get the size of an image from the network when it is not loaded. In BitmapTransformation, the width and height of the image are required. So using post and Runnable mechanisms to define shapes after loading is acceptable in most scenarios. However, in a list that needs to be refreshed, it will be obvious that every time the image is refreshed to load, the image will be blank, which will affect the experience.

Third, the realization of this function requires knowledge

  • 1. Basic grammar knowledge in Kotlin
  • 2. Custom property accessors in Kotlin
  • The default parameter in Kotlin implements constructor function overloading and the @jvMoverloads annotation
  • 4, Kotlin standard library common use of apply,run,with functions
  • 5. Use of default parameter functions in Kotlin
  • 6. Basic knowledge of custom View
  • 7. Use of Path
  • 8. Use of Matrix
  • 9. Use of BitmapShader

Four, picture fillet customization needs to meet what needs

  • 1. Support the customization of picture circle
  • 2, support picture rounded corner and each corner of the X,Y direction value customization
  • 3, support shape border width color customization
  • 4, support picture rounded corner or round top right corner message dot customization (generally used for round or rounded head)

Five, the principle and thinking analysis of the implementation of custom View

This custom View implementation principle is very simple, there are three important points, the first is to build the definition of the rounded rectangle Path; The second is to use the Matrix Matrix transformation to scale down or enlarge the image size and ImageView size to keep consistent; The third is to use BitmapShader to render the defined path using a brush with the shader.

6, custom View Java and Kotlin implementation comparison

  • 1, custom View constructor load comparison

Java implementation, such writing is Java implementation custom View common routine

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); }}Copy the code

The kotlin implementation uses the default parameter described in the previous blog to implement function overloading and the @jvMoverloads annotation so that in Java it can call the overloaded constructor method defined in Kotlin. (These two points of knowledge are the previous blog has a special analysis including their principles)

class PrettyImageView @JvmOverloads constructor(context: Context, attributeSet: AttributeSet? = null, defAttrStyle: Int = 0)
	: ImageView(context, attributeSet, defAttrStyle) {
	    
	}
Copy the code
  • 2, custom View property change setter exposed

Java implementation, need to implement the corresponding property setter method, and then internal call invalidate redraw

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(); }}Copy the code

The Kotlin implementation does not need to define so many setter methods, because the var variable in Kotlin comes with setter and getter methods, so we want to call invalidate when the value is changed again. To do this, you need to use the custom variable accessor we talked about earlier.

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()
		}
}
Copy the code

Seven, specific code implementation

  • 1, open custom View properties
Open attribute name Open attribute meaning
shape_type There are only two types of shape: round corner and round
left_top_radiusX Top left x-radius
left_top_radiusY The y-radius in the upper left corner
right_top_radiusX The X-axis radius in the upper right corner
right_top_radiusY The Y radius in the upper right corner
right_bottom_radiusX X-axis radius in lower right corner
right_bottom_radiusY The Y radius in the lower right corner
left_bottom_radiusX X radius in the lower left corner
left_bottom_radiusY The Y radius in the lower left corner
show_border Whether to show borders
border_width Border width
border_color Border color
show_circle_dot Whether to display the upper-right dot
circle_dot_color Dot color in the upper right corner
circle_dot_radius The radius of the upper right dot
  • Attrs.xml definition declaration
<?xml version="1.0" encoding="utf-8"? >
<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>
Copy the code
  • 3. Specific implementation code
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/ / the width of the View
	private var mHeight: Int = 200/ / the height of the View
	private var mRadius: Float = 100f// Radius of the circle

	init {
		initAttrs(context, attributeSet, defAttrStyle)// Get the value of the custom attribute
		initDrawTools()// Initialize the drawing tool
	}

	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(a) {
		mBitmapPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {// Set the BitmapShader shader to draw the image on different shapes
			style = Paint.Style.FILL
		}
		mBorderPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {// Draw the border brushstyle = Paint.Style.STROKE color = mBorderColor strokeCap = Paint.Cap.ROUND strokeWidth = mBorderWidth } mCircleDotPaint  = Paint(Paint.ANTI_ALIAS_FLAG).apply {// Draw the upper right dot brush
			style = Paint.Style.FILL
			color = mCircleDotColor
		}
		mShapePath = Path()// Describe the path of the shape contour
		mBorderPath = Path()// Describe the path of the image border outline
		mMatrix = Matrix()// The matrix used to scale the image
		scaleType = ScaleType.CENTER_CROP
	}

	override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {/ / the View of measurement
		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) {// Determine the final View size
		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(a) {// Build a circular Path
		if(! mShowBorder) {// Drawing circles without borders is actually just a matter of dropping a circle into the path
			mShapePath.addCircle(mRadius, mRadius, mRadius, Path.Direction.CW)
		} else {// To draw a circle with a border, throw both the inner circle and the outer circle into the path
			mShapePath.addCircle(mRadius, mRadius, mRadius - mBorderWidth, Path.Direction.CW)
			mBorderPath.addCircle(mRadius, mRadius, mRadius - mBorderWidth / 2.0f, Path.Direction.CW)
		}
	}

	private fun buildRoundPath(a) {// Build a rounded Path
		if(! mShowBorder) {// Drawing rounded corners without borders is actually just a matter of dropping a rounded rectangle into the 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 {// Drawing rounded corners with borders is actually just a matter of throwing both a rounded rectangle and a rounded rectangle's variables into the 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?). {// Since the boderPath and shapePath are constructed according to different logic above, the corresponding shapes are stored in path, now just need to draw the corresponding shapePath shape with BitmapShader brush,boderPath with normal brushdrawable ? :return
		mBitmapPaint.shader = getBitmapShader()// Get the corresponding BitmapShader object
		when (mShapeType) {
			ShapeType.SHAPE_CIRCLE -> {
				if(mShowBorder) { canvas? .drawPath(mBorderPath, mBorderPaint)// Draw the circular image border path} canvas? .drawPath(mShapePath, mBitmapPaint)// Draw the circular image shape path
				if (mShowCircleDot) {
					drawCircleDot(canvas)// Draw the dot in the upper right corner of the circle image
				}
			}
			ShapeType.SHAPE_ROUND -> {
				if(mShowBorder) { canvas? .drawPath(mBorderPath, mBorderPaint)// Draw the rounded image border path} canvas? .drawPath(mShapePath, mBitmapPaint)// Draw the rounded image shape 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(a): 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) {
				// If the width or height of the image does not match the width and height of the view, calculate the scale to be scaled; The width and height of the zoomed image must be greater than the width and height of our view. So we're going to take a large value here;
				if(! (width == bitmap.width && width == bitmap.height)) { scale = Math.max(width *1.0f / bitmap.width, height * 1.0f / bitmap.height)
				}
			}
			// Shader's transformation matrix is used to zoom in and out
			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(a): 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)
		}
	}
}
Copy the code

GitHub address of the project

Operation effect screenshot:

Welcome to the Kotlin Developer Association, where the latest Kotlin technical articles are published, and a weekly Kotlin foreign technical article is translated from time to time. If you like Kotlin, welcome to join us ~~~