美文网首页
kotlin-自定View

kotlin-自定View

作者: jeffrey12138 | 来源:发表于2020-11-09 15:48 被阅读0次

    作为小白的我,一直都觉得自定义View是个很难的课题,现在也是这么觉得,虽然使用起来还是挺方便,不过也是需要加入自己的想象力,无奈我想象力不太丰富,好了开始说下这个应该如何使用的吧
    最近也开始使用Xmind去做一个思维导图,我觉得这样也有利于去理解,所以我也附上了这个图
    顺便说,如果想更深一层去了解自定View的话,看下这位大佬的文章,分析的很透彻
    https://juejin.im/post/6844903607855218702#heading-16

    image.png
    按照这个思维导图的顺序,开始撸代码:
    1 自定义属性:
     <declare-styleable name="TestView">
            <attr name="test_boolean" format="boolean"></attr>
            <attr name="test_string" format="string"></attr>
            <attr name="test_integer" format="integer"></attr>
            <attr name="test_enum" format="enum">
                <enum name="top" value="1"></enum>
                <enum name="bottom" value="2"></enum>
            </attr>
            <attr name="test_dimension" format="dimension"></attr>
        </declare-styleable>
    
     <declare-styleable name="RoundCircleProgressBar">
            <attr name="color" format="color"/>
            <attr name="line_width" format="dimension"/>
            <attr name="radius" format="dimension"/>
            <attr name="android:progress"/>
            <attr name="android:textSize"/>
        </declare-styleable>
    

    然后是控件的编写,里面写得都比较详细了啦,请慢慢看,我把两个案例都放进去吧
    1 TextView

    class TextVIew(context: Context, attrs: AttributeSet?) :
        View(context, attrs) {
        val typedArray=context.obtainStyledAttributes(attrs,R.styleable.TextVIew)
        private var mTextColor=0
        private var mTextSize=0
        private var mTextContent=""
        private lateinit var mPaint: Paint
        init {
            //通过for循环确保用户未输入属性值的时候,能使用默认属性值
            for (i in 0..typedArray.indexCount)
            {
                when(typedArray.getIndex(i)){
                    R.styleable.TextVIew_text_color->mTextColor=typedArray.getColor(R.styleable.TextVIew_text_color,0xFFFF00)
                    R.styleable.TextVIew_text_size->mTextSize=typedArray.getInt(R.styleable.TextVIew_text_size,dpTosp(30).toInt())
                    R.styleable.TextVIew_text_content->mTextContent=
                        typedArray.getString(R.styleable.TextVIew_text_content).toString()
                }
            }
            typedArray.recycle()
            initPaint()
        }
    
        private fun initPaint() {
            mPaint= Paint()
            //STROKE为空心,FILL为实心
            mPaint.style=Paint.Style.STROKE
            mPaint.color=-0x10000
            mPaint.strokeWidth=6f
            mPaint.isAntiAlias=true
        }
    
        private fun dpTosp(dpValue:Int): Float {
            return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP,dpValue.toFloat(),resources.displayMetrics)
        }
    
        override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
            //MeasureSpec是View的内部类,它封装了一个View的尺寸,在onMeasure()当中会根据这个MeasureSpec的值来确定View的宽高。
            //MeasureSpec的值保存在一个int值当中。一个int值有32位,前两位表示模式mode后30位表示大小size。即MeasureSpec = mode + size。
            val widthMode=MeasureSpec.getMode(widthMeasureSpec)
            val widthSize=MeasureSpec.getSize(widthMeasureSpec)
            var width=0
            //EXACTLY:精准模式。父容器已经解决了子元素所需要的精准大小,这时候子 View 的最终大小就是 SpecSize 所指定的值。它对应于 LayoutParams 中的 match_parent 和具体的数值
            //AT_MOST:最大模式。父最大模式容器指定了一个可用大小即 SpecSize,子元素最大不可以超过指定的这个值。它对应于LayoutParams 中的 wrap_content
            //UNSPECIFIED:父容器不对子元素作任何约束,子 View 想要多大就给多大。这种情况一般用在系统内部,表示一种测量的状态用于类型可滑动的布局中,例如ListView之类的
            width = if (widthMode==MeasureSpec.EXACTLY){
                widthSize
            }else {
                val needWidth = measuredWidth() + paddingLeft + paddingRight
                if (widthMode == MeasureSpec.AT_MOST) {
                    needWidth.coerceAtMost(widthSize)
                } else {
                    needWidth
                }
            }
            val heightMode=MeasureSpec.getMode(heightMeasureSpec)
            val heightSize=MeasureSpec.getSize(heightMeasureSpec)
            var height=0
            height = if (heightMode==MeasureSpec.EXACTLY){
                heightSize
            }else{
                val needHeight=measuredHeight()+paddingBottom+paddingTop
                if (heightMode==MeasureSpec.AT_MOST){
                    needHeight.coerceAtMost(width)
                }else{
                    needHeight
                }
            }
            //为view传入绘制后的宽高,这个很重要哦,要不然前面的工作就白做了
            setMeasuredDimension(width,height)
        }
        private fun measuredWidth():Int{
            return 0
        }
        private fun measuredHeight():Int{
            return 0
        }
    
        override fun onDraw(canvas: Canvas?) {
    
    //        canvas.drawCircle(getWidth() / 2, getHeight() / 2, getWidth() / 2 - mPaint.getStrokeWidth() / 2, mPaint);
    //        mPaint.setStrokeWidth(1);
    //        canvas.drawLine(0, getHeight() / 2, getWidth(), getHeight() / 2, mPaint);
    //        canvas.drawLine(getWidth() / 2, 0, getWidth() / 2, getHeight(), mPaint);
            mPaint.textSize = 72f
            mPaint.style = Paint.Style.FILL
            mPaint.strokeWidth = 0f
            canvas?.drawText(mTextContent, 0, mTextContent.length, 0f, height.toFloat(), mPaint)
        }
        //使用状态的存储和复位之后,记得在xml中对应的控件需要添加id,否则控件无法复位
        override fun onSaveInstanceState(): Parcelable? {
            val bundle=Bundle()
            //这一步是为了把当前控件的状态呢进行存储,因为在继承别的View的时候,可能他们自身已经有一套存储的方法,
            // 所以为了避免使自身的存储失效,所以要在这里进行状态的存储
            bundle.putParcelable(INSTANCE,super.onSaveInstanceState())
            bundle.putString(KEY_TEXT,mTextContent)
            return bundle
        }
    
        override fun onRestoreInstanceState(state: Parcelable?) {
            if (state is Bundle){
                val bundle= state as Bundle
                val parcelable=bundle.getParcelable<Parcelable>(INSTANCE)
                super.onRestoreInstanceState(parcelable)
                mTextContent = bundle.getString(KEY_TEXT).toString()
                return
            }
            super.onRestoreInstanceState(state)
        }
    
        override fun onTouchEvent(event: MotionEvent?): Boolean {
            mTextContent="哦豁"
            //刷新控件
            invalidate()
            return true
        }
    
        companion object{
            val INSTANCE="instance"
            val KEY_TEXT="key_text"
        }
    }
    

    2 圆形进度条

    class CircleProgress(context: Context, attrs: AttributeSet?) : View(context, attrs) {
        private val mRadius: Int
        private val mColor: Int
        private val mLineWidth: Int
        private val mTextSize: Int
        private var mProgress: Int
        private var mPaint: Paint? = null
        private fun dp2px(dpVal: Int): Float {
            return TypedValue.applyDimension(
                TypedValue.COMPLEX_UNIT_DIP, dpVal.toFloat(), resources.displayMetrics
            )
        }
    
        private fun initPaint() {
            mPaint = Paint()
            mPaint!!.isAntiAlias = true
            mPaint!!.color = mColor
        }
    
        var progress: Int
            get() = mProgress
            set(progress) {
                mProgress = progress
                invalidate()
            }
    
        override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
            val widthMode = MeasureSpec.getMode(widthMeasureSpec)
            val widthSize = MeasureSpec.getSize(widthMeasureSpec)
            var width = 0
            width = if (widthMode == MeasureSpec.EXACTLY) {
                widthSize
            } else {
                val needWidth = measureWidth() + paddingLeft + paddingRight
                if (widthMode == MeasureSpec.AT_MOST) {
                    needWidth.coerceAtMost(widthSize)
                } else {
                    needWidth
                }
            }
            val heightMode = MeasureSpec.getMode(heightMeasureSpec)
            val heightSize = MeasureSpec.getSize(heightMeasureSpec)
            var height = 0
            height = if (heightMode == MeasureSpec.EXACTLY) {
                heightSize
            } else {
                val needHeight = measureHeight() + paddingTop + paddingBottom
                if (heightMode == MeasureSpec.AT_MOST) {
                    needHeight.coerceAtMost(heightSize)
                } else  //MeasureSpec.UNSPECIFIED
                {
                    needHeight
                }
            }
            setMeasuredDimension(width, height)
        }
    
        private fun measureHeight(): Int {
            return mRadius * 2
        }
    
        private fun measureWidth(): Int {
            return mRadius * 2
        }
    
        @SuppressLint("DrawAllocation")
        override fun onDraw(canvas: Canvas) {
            mPaint!!.style = Paint.Style.STROKE
            mPaint!!.strokeWidth = mLineWidth * 1.0f / 4
            val width = width
            val height = height
            canvas.drawCircle(
                width / 2.toFloat(), height / 2.toFloat(),
                width / 2 - paddingLeft - mPaint!!.strokeWidth / 2, mPaint!!
            )
    
            mPaint!!.strokeWidth = mLineWidth.toFloat()
            canvas.save()
            canvas.translate(paddingLeft.toFloat(), paddingTop.toFloat())
            val angle = mProgress * 1.0f / 100 * 360
            canvas.drawArc(
                RectF(
                    0f,
                    0f,
                    (width - paddingLeft * 2).toFloat(),
                    (height - paddingLeft * 2).toFloat()
                ),
                0f,
                angle,
                false,
                mPaint!!
            )
            canvas.restore()
            val text = "$mProgress%"
            //        text = "张鸿洋";
            mPaint!!.strokeWidth = 0f
            mPaint!!.textAlign = Paint.Align.CENTER
            mPaint!!.textSize = mTextSize.toFloat()
            val y = getHeight() / 2
            val bound = Rect()
            mPaint!!.getTextBounds(text, 0, text.length, bound)
            val textHeight = bound.height()
            canvas.drawText(
                text,
                0,
                text.length,
                getWidth() / 2.toFloat(),
                y + textHeight / 2.toFloat(),
                mPaint!!
            )
            mPaint!!.strokeWidth = 0f
            //        canvas.drawLine(0, height / 2, width, height / 2, mPaint);
        }
    
        override fun onSaveInstanceState(): Parcelable? {
            val bundle = Bundle()
            bundle.putInt(KEY_PROGRESS, mProgress)
            bundle.putParcelable(INSTANCE, super.onSaveInstanceState())
            return bundle
        }
    
        override fun onRestoreInstanceState(state: Parcelable) {
            if (state is Bundle) {
                val parcelable =
                    state.getParcelable<Parcelable>(INSTANCE)
                super.onRestoreInstanceState(parcelable)
                mProgress = state.getInt(KEY_PROGRESS)
                return
            }
            super.onRestoreInstanceState(state)
        }
    
        companion object {
            private const val INSTANCE = "instance"
            private const val KEY_PROGRESS = "key_progress"
        }
    
        init {
            val ta = context.obtainStyledAttributes(attrs, R.styleable.RoundCircleProgressBar)
            mRadius = ta.getDimension(R.styleable.RoundCircleProgressBar_radius, dp2px(30)).toInt()
            mColor = ta.getColor(R.styleable.RoundCircleProgressBar_color, -0x10000)
            mLineWidth =
                ta.getDimension(R.styleable.RoundCircleProgressBar_line_width, dp2px(3)).toInt()
            mTextSize =
                ta.getDimension(R.styleable.RoundCircleProgressBar_android_textSize, dp2px(36)).toInt()
            mProgress = ta.getInt(R.styleable.RoundCircleProgressBar_android_progress, 30)
            ta.recycle()
            initPaint()
        }
    }
    

    emmm剩下就是在xml里面直接使用了啦!!!好了,到此结束,反正你们都不点赞,我给自己看就足够了!!

    相关文章

      网友评论

          本文标题:kotlin-自定View

          本文链接:https://www.haomeiwen.com/subject/xqnlbktx.html