天天看點

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的定義聲明
<?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>
           
  • 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實戰篇之自定義View圖檔圓角簡單應用(一)
Kotlin實戰篇之自定義View圖檔圓角簡單應用(一)

歡迎關注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圖檔圓角簡單應用