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

指示器(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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 195,585评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,283评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 142,760评论 0 324
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,461评论 1 266
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,280评论 4 357
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,268评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,656评论 3 385
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,322评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,629评论 1 293
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,691评论 2 312
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,445评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,299评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,694评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,982评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,244评论 1 251
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,642评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,829评论 2 335

推荐阅读更多精彩内容