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 ~~~