美文网首页
建立自己的王国:Android 自定义封装View(2)

建立自己的王国:Android 自定义封装View(2)

作者: 努尔江 | 来源:发表于2021-08-05 15:20 被阅读0次

    指示器(IndicatorView)
    场景:Fragment的指示器,虽然有很多IndicatorView可以使用,但是自给自足还是好的。
    国际惯例:效果


    1.png

    思路:
    View里通过canvas.drawCircle(x坐标,y坐标,半径,Paint)来达到画圆的目的。
    需要解决的问题是按照不同的数量,要计算出圆的具体坐标。
    探索:

    1. 利用canvas的宽度等分,高度在中间。
      一只圆的时候:


      circle1.png

      圆刚好在中间。
      结论: x=View的宽度/2,y=View的高度/2

    两只圆的时候:


    circle2.png

    可以得出:x1=View的宽度/3, y1=View的高度/2
    x2=2*View的宽度/3,y2=View的高度/2

    那么总体的坐标公式为:
    canvas?.drawCircle(i * mWidth / (count + 1).toFloat(), mHeight / 2F, radius, mPaint)

    实现:

    1. 定义需要的自定义Styleable属性:
      <!--    IndicatorView-->
          <!--    IndicatorView-->
        <declare-styleable name="IndicatorView">
    <!--        总数-->
            <attr name="indicatorCount" format="integer" />
    <!--        指示器位置-->
            <attr name="indicatePosition" format="integer" />
    <!--        指示器半径-->
            <attr name="indicatorRadius" format="float" />
    <!--        指示器主颜色-->
            <attr name="indicateMainColor" format="color" />
    <!--        指示器副颜色-->
            <attr name="indicateSecondColor" format="color" />
    <!--        view背景-->
            <attr name="android:background" format="color" />
        </declare-styleable>
    

    2.自定义View主结构:
    必须重写的方法类

    class IndicatorView : View {
       //实例化时
       constructor(mContext: Context) : super(mContext) {
            initView(mContext, null)
        }
    
    //sdk 24以上时调用
        constructor(mContext: Context, attributes: AttributeSet) : super(mContext, attributes) {
            initView(mContext, attributes)
        }
    //如果有主题时
        constructor(mContext: Context, attributes: AttributeSet, theme: Int) : super(
            mContext,
            attributes,
            theme
        ) {
            initView(mContext, attributes)
        }
    
    1. 在InitView方法里解析sylable:
        /**
           *   mainColor 主颜色
           */
        private var mainColor by Delegates.notNull<Int>()
        /**
         * Second color 副颜色
         */
        private var secondColor by Delegates.notNull<Int>()
        /**
         * Count 总数
         */
        var count by Delegates.notNull<Int>()
         /**
          *position 当前位置
          */
        var position: Int = 0
    
        private lateinit var mPaint: Paint
    
        /**
         * Bg color 画布背景颜色
         */
        private var bgColor by Delegates.notNull<Int>()
    
        /**
         * Radius 需要的圆的背景。
         */
        private var radius by Delegates.notNull<Float>()
    
        /**
         * M width 所要的宽度
         */
        private var mWidth by Delegates.notNull<Int>()
    
        /**
         * M height 所需高度
         */
        private var mHeight by Delegates.notNull<Int>()
    
     private fun initView(mContext: Context, attributes: AttributeSet?) {
           初始化
            mPaint = Paint()
            val ta = mContext.obtainStyledAttributes(attributes, R.styleable.IndicatorView)
            mainColor = ta.getColor(R.styleable.IndicatorView_indicateMainColor, Color.RED)
            secondColor = ta.getColor(R.styleable.IndicatorView_indicateSecondColor, Color.GRAY)
            position = ta.getInt(R.styleable.IndicatorView_indicatePosition, 1)
            bgColor = ta.getColor(R.styleable.IndicatorView_android_background, Color.WHITE)
            radius = ta.getFloat(R.styleable.IndicatorView_indicatorRadius, 10F)
            count = ta.getInt(R.styleable.IndicatorView_indicatorCount, 3)
            /*    //判断总数不要0
                if (count==0){
                    throw  IllegalArgumentException("Can not set 0 to the Total")
                }
                //判断position不能大于总数
                if (count < position){
                    throw IllegalArgumentException("Position must be greater than Count")
                }*/
            //回收
            ta.recycle()
        }
    

    注意:初始化的时候必须走查逻辑,保证不能出现总数为0,或者当前位置超过总数的情况,也可以Throw Exception来抛出异常。

    重写OnDraw方法,按照总数,指示器位置等信息画圆。

     override fun onDraw(canvas: Canvas?) {
            super.onDraw(canvas)
            mPaint.isAntiAlias = true
            //画背景先
            canvas?.drawColor(bgColor)
            //遍历时画图
            //保证不能0
            if (count != 0) {
                for (i: Int in 1..count) {
                    //如果是当前位置,颜色要MainColor。
                    if (position == i) {
                        mPaint.color = mainColor
                    } else {
                        //如果普通位置,用副颜色。
                        mPaint.color = secondColor
                    }
                    //画圆,参数中高度要刚好在View中间。
                    canvas?.drawCircle(i * mWidth / (count + 1).toFloat(), mHeight / 2F, radius, mPaint)
                }
            }
            canvas?.save()
        }
    

    需要计算mWidth,mHeight的值,在onMeasure的时候,重写。

      override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
            super.onMeasure(widthMeasureSpec, heightMeasureSpec)
          //宽度的设定为:全部圆的直径+圆之间的空间距离。可以灵活设定。
            mWidth = getMySize(30 * (count + 2), widthMeasureSpec)
           //高度就20dp(圆的直径),
            mHeight = getMySize(20, heightMeasureSpec)
          //使用上面的值作为view的宽高。
            setMeasuredDimension(mWidth, mHeight)
        }
    

    分别对应View的三个模式(UNSPASSIFIED, EXACTLY, ATMOST)进行不同的设计。
    我的是:

        private fun getMySize(size: Int, measureSpec: Int): Int {
            var result = size
            val specMode = MeasureSpec.getMode(measureSpec)
            val specSize = MeasureSpec.getSize(measureSpec)
            when (specMode) {
                    //未指定或 wrap_content的时候,使用计算出来的值。
                MeasureSpec.UNSPECIFIED,
                MeasureSpec.AT_MOST -> {
                    result = size
                    return result
                }
                    //Match_parent的时候.
                MeasureSpec.EXACTLY -> {
                    result = specSize
                    return result
                }
            }
            return result
        }
    

    最后写一个public方法,让外部更新View的当前指示器位置(状态)

      /**
         * Update position
         *
         * @param mPosition 需要更新的位置
         */
        fun updatePosition(mPosition: Int) {
          //判断一下,免得超过了总数.
            position = if (mPosition <= count) {
                mPosition
            } else {
                1
            }
            //重绘
            this.postInvalidate()
        }
    

    Github直通车 https://github.com/Neo-Turak/learning

    相关文章

      网友评论

          本文标题:建立自己的王国:Android 自定义封装View(2)

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