记录自定义view的基本使用

  1. 使用一个自定义view记录思想

  2. 效果如下,实现方式有很多种,以此来回顾一下自定义view
    在这里插入图片描述

  3. 使用Android Studio快速创建
    在这里插入图片描述

  4. 删除一些无用的

(1)首先思考一下这个图片,共有三个一样,这时候我们只要实现抽出公共的,我们看到总共分三个模块,1个是矩形背景,2是倒计时文字,3是右边的文字显示,相应的,我们对每个模块进行颜色,文字大小等划分
在这里插入图片描述
(2)对应的attrs.xml

<!--倒计时-->
    <declare-styleable name="TimeView">
        <!--矩形背景-->
        <attr name="re_color" format="color" />
        <attr name="re_width" format="dimension" />
        <attr name="re_height" format="dimension" />
        <!--倒计时数字-->
        <attr name="time_string" format="string" />
        <attr name="time_color" format="color" />
        <attr name="time_size" format="dimension" />
        <!--日期文字-->
        <attr name="date_text_string" format="string" />
        <attr name="date_text_color" format="color" />
        <attr name="date_text_size" format="dimension" />
    </declare-styleable>

(3)我们再去java文件中将这些属性获取出来,都是一些固定操作

 // 矩形背景
    private var _rectangleColor: Int = Color.BLACK // 矩形背景颜色
    private var _rectangleWidth: Float = 0f // 矩形宽度
    private var _rectangleHeight: Float = 0f // 矩形高度

    // 倒计时数字
    private var _timeString: String = ""
    private var _timeTextColor: Int = Color.WHITE // 倒计时颜色
    private var _timeTextSize: Float = 13f // 倒计时数字大小

    // 日期数字
    private var _textString: String = ""
    private var _textColor: Int = Color.BLACK // 倒计时颜色
    private var _textSize: Float = 13f // 倒计时数字大小

	// init中,最后别忘了a.recycle()
	// Load attributes
        val a = context.obtainStyledAttributes(
            attrs, R.styleable.TimeView, defStyle, 0
        )

        // 矩形背景
        _rectangleColor = a.getColor(
            R.styleable.TimeView_re_color,
            _rectangleColor
        )
        _rectangleWidth = a.getDimension(
            R.styleable.TimeView_re_width,
            _rectangleWidth
        )
        _rectangleHeight = a.getDimension(
            R.styleable.TimeView_re_height,
            _rectangleHeight
        )

        // 倒计时
        _timeString = a.getString(
            R.styleable.TimeView_time_string
        ).toString()
        _timeTextSize = a.getDimension(
            R.styleable.TimeView_time_size,
            _timeTextSize
        )
        _timeTextColor = a.getColor(
            R.styleable.TimeView_time_color,
            _timeTextColor
        )

        // 文字
        _textString = a.getString(
            R.styleable.TimeView_date_text_string
        ).toString()
        _textSize = a.getDimension(
            R.styleable.TimeView_date_text_size,
            _textSize
        )
        _textColor = a.getColor(
            R.styleable.TimeView_date_text_color,
            _textColor
        )
        a.recycle()
   

(4)这时候我们再分析图片,三个模块都需要一个画笔吧,然后我们再各自定义画笔和初始化画笔

    private var rectanglePaint: Paint? = null // 矩形画笔
    private var timePaint: TextPaint? = null // 时间画笔
    private var textPaint: TextPaint? = null // 文字画笔

	// 初始化它们
	// Set up a default TextPaint object
        rectanglePaint = Paint().apply {
            flags = Paint.ANTI_ALIAS_FLAG
            color = _rectangleColor
            style = Paint.Style.FILL
        }

        // time
        timePaint = TextPaint().apply {
            flags = Paint.ANTI_ALIAS_FLAG
            textAlign = Paint.Align.CENTER
            textSize = _timeTextSize
            color = _timeTextColor
        }

        // text
        textPaint = TextPaint().apply {
            flags = Paint.ANTI_ALIAS_FLAG
            textAlign = Paint.Align.CENTER
            textSize = _textSize
            color = _textColor
        }

(5)然后我们来进行绘制,可以自己动手画个草图,边调试边改

@SuppressLint("DrawAllocation")
    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)

        // allocations per draw cycle.
        val paddingLeft = paddingLeft
        val paddingTop = paddingTop
        val paddingRight = paddingRight
        val paddingBottom = paddingBottom

        val contentWidth = width - paddingLeft - paddingRight
        val contentHeight = height - paddingTop - paddingBottom

        var timeWidth = getTextWidth(_timeString, timePaint!!)
        val timeHeight = getTextHeight(_timeString, timePaint!!)

        val textWidth = getTextWidth(_textString, textPaint!!)
        val textHeight = getTextHeight(_textString, textPaint!!)

        val leftWidth = (contentWidth - (_rectangleWidth + 10 + textWidth)) / 2

        with(canvas) {
            drawRoundRect(RectF(leftWidth , (contentHeight - _rectangleHeight) / 2, leftWidth +  _rectangleWidth, _rectangleHeight + (contentHeight - _rectangleHeight) / 2), 10f, 10f, rectanglePaint!!)
            drawText(_timeString, leftWidth + _rectangleWidth / 2, (contentHeight / 2 + timeHeight / 2).toFloat(), timePaint!!)
            drawText(_textString, leftWidth + _rectangleWidth + textWidth / 2 + 10, (contentHeight / 2 + textHeight / 2).toFloat(), textPaint!!)
        }
    }

    private fun getTextWidth(text: String, paint: Paint): Float {
        val rect = Rect() // 文字所在区域的矩形
        paint.getTextBounds(text, 0, text.length, rect)
        return rect.width().toFloat()
    }

    private fun getTextHeight(text: String, paint: Paint): Float {
        val rect = Rect() // 文字所在区域的矩形
        paint.getTextBounds(text, 0, text.length, rect)
        return rect.height().toFloat()
    }

(6)然后我们看下效果,接下来就是再LinearLayout布局中,水平放置三个就可以了
在这里插入图片描述
(7)代码改变各个属性的状态也别落下

/**
     * 矩形背景
     */
    var rectangleColor: Int
        get() = _rectangleColor
        set(value) {
            _rectangleColor = value
            invalidate()
        }

    var rectangleWidth: Float
        get() = _rectangleWidth
        set(value) {
            _rectangleWidth = value
            invalidate()
        }

    var rectangleHeight: Float
        get() = _rectangleHeight
        set(value) {
            _rectangleHeight = value
            invalidate()
        }

    /**
     * 倒计时
     */
    var timeString: String
        get() = _timeString
        set(value) {
            _timeString = value
            invalidate()
        }

    var timeTextColor: Int
        get() = _timeTextColor
        set(value) {
            _timeTextColor = value
            invalidate()
        }

    var timeTextSize: Float
        get() = _timeTextSize
        set(value) {
            _timeTextSize = value
            invalidate()
        }

    /**
     * 日期
     */
    var textString: String
        get() = _textString
        set(value) {
            _textString = value
            invalidate()
        }

    var textColor: Int
        get() = _textColor
        set(value) {
            _textColor = value
            invalidate()
        }

    var textSize: Float
        get() = _textSize
        set(value) {
            _textSize = value
            invalidate()
        }
  1. 在我们感觉快大功告成时候发现,宽度怎么只能设置具体值,比如100,200,设置android:layout_width=“wrap_content” android:layout_width="match_parent"效果是一样的,我们百度一下,发现父类View中onMeasure()中默认的使用方式
 /**
     * Utility to return a default size. Uses the supplied size if the
     * MeasureSpec imposed no constraints. Will get larger if allowed
     * by the MeasureSpec.
     *
     * @param size Default size for this view
     * @param measureSpec Constraints imposed by the parent
     * @return The size this view should be.
     */
    public static int getDefaultSize(int size, int measureSpec) {
        int result = size;
        int specMode = MeasureSpec.getMode(measureSpec);
        int specSize = MeasureSpec.getSize(measureSpec);

        switch (specMode) {
        case MeasureSpec.UNSPECIFIED:
            result = size;
            break;
        case MeasureSpec.AT_MOST:
        case MeasureSpec.EXACTLY:
            result = specSize;
            break;
        }
        return result;
    }

case MeasureSpec.AT_MOST:
case MeasureSpec.EXACTLY:使用的是一样的,这时候我们需要自己来重写,进行区分

// 给个默认值
    private val DEFAULT_WITH = 140
    private val DEFAULT_Height = 140

 override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
       super.onMeasure(widthMeasureSpec, heightMeasureSpec)
       val widthSize = MeasureSpec.getSize(widthMeasureSpec)
       val widthMode = MeasureSpec.getMode(widthMeasureSpec)
       val heightSize = MeasureSpec.getSize(heightMeasureSpec)
       val heightMode = MeasureSpec.getMode(heightMeasureSpec)
       //处理wrap_contentde情况
       if (widthMode == MeasureSpec.AT_MOST && heightMode == MeasureSpec.AT_MOST) {
           setMeasuredDimension(DEFAULT_WITH, DEFAULT_Height)
       } else if (widthMode == MeasureSpec.AT_MOST) {
           setMeasuredDimension(DEFAULT_WITH, heightSize)
       } else if (heightMode == MeasureSpec.AT_MOST) {
           setMeasuredDimension(widthSize, DEFAULT_Height)
       }
   }
  1. 我们再去试一下使用wrap_content,这次正常了,然后再在项目中使用
    LinearLayout水平放置
    在这里插入图片描述

  2. 源码
    attrs.xml

<!--倒计时-->
    <declare-styleable name="TimeView">
        <!--矩形背景-->
        <attr name="re_color" format="color" />
        <attr name="re_width" format="dimension" />
        <attr name="re_height" format="dimension" />
        <!--倒计时数字-->
        <attr name="time_string" format="string" />
        <attr name="time_color" format="color" />
        <attr name="time_size" format="dimension" />
        <!--日期文字-->
        <attr name="date_text_string" format="string" />
        <attr name="date_text_color" format="color" />
        <attr name="date_text_size" format="dimension" />
    </declare-styleable>

TimeView

class TimeView : View {

    // 矩形背景
    private var _rectangleColor: Int = Color.BLACK // 矩形背景颜色
    private var _rectangleWidth: Float = 0f // 矩形宽度
    private var _rectangleHeight: Float = 0f // 矩形高度
    private var rectanglePaint: Paint? = null // 矩形画笔

    // 倒计时数字
    private var _timeString: String = ""
    private var _timeTextColor: Int = Color.WHITE // 倒计时颜色
    private var _timeTextSize: Float = 13f // 倒计时数字大小
    private var timePaint: TextPaint? = null

    // 日期数字
    private var _textString: String = ""
    private var _textColor: Int = Color.BLACK // 倒计时颜色
    private var _textSize: Float = 13f // 倒计时数字大小
    private var textPaint: TextPaint? = null

    private val DEFAULT_WITH = 140
    private val DEFAULT_Height = 140
    /**
     * 矩形背景
     */
    var rectangleColor: Int
        get() = _rectangleColor
        set(value) {
            _rectangleColor = value
            invalidate()
        }

    var rectangleWidth: Float
        get() = _rectangleWidth
        set(value) {
            _rectangleWidth = value
            invalidate()
        }

    var rectangleHeight: Float
        get() = _rectangleHeight
        set(value) {
            _rectangleHeight = value
            invalidate()
        }

    /**
     * 倒计时
     */
    var timeString: String
        get() = _timeString
        set(value) {
            _timeString = value
            invalidate()
        }

    var timeTextColor: Int
        get() = _timeTextColor
        set(value) {
            _timeTextColor = value
            invalidate()
        }

    var timeTextSize: Float
        get() = _timeTextSize
        set(value) {
            _timeTextSize = value
            invalidate()
        }

    /**
     * 日期
     */
    var textString: String
        get() = _textString
        set(value) {
            _textString = value
            invalidate()
        }

    var textColor: Int
        get() = _textColor
        set(value) {
            _textColor = value
            invalidate()
        }

    var textSize: Float
        get() = _textSize
        set(value) {
            _textSize = value
            invalidate()
        }

    constructor(context: Context) : super(context) {
        init(null, 0)
    }

    constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {
        init(attrs, 0)
    }

    constructor(context: Context, attrs: AttributeSet, defStyle: Int) : super(
        context,
        attrs,
        defStyle
    ) {
        init(attrs, defStyle)
    }

    private fun init(attrs: AttributeSet?, defStyle: Int) {
        // Load attributes
        val a = context.obtainStyledAttributes(
            attrs, R.styleable.TimeView, defStyle, 0
        )

        // 矩形背景
        _rectangleColor = a.getColor(
            R.styleable.TimeView_re_color,
            _rectangleColor
        )
        _rectangleWidth = a.getDimension(
            R.styleable.TimeView_re_width,
            _rectangleWidth
        )
        _rectangleHeight = a.getDimension(
            R.styleable.TimeView_re_height,
            _rectangleHeight
        )

        // 倒计时
        _timeString = a.getString(
            R.styleable.TimeView_time_string
        ).toString()
        _timeTextSize = a.getDimension(
            R.styleable.TimeView_time_size,
            _timeTextSize
        )
        _timeTextColor = a.getColor(
            R.styleable.TimeView_time_color,
            _timeTextColor
        )

        // 文字
        _textString = a.getString(
            R.styleable.TimeView_date_text_string
        ).toString()
        _textSize = a.getDimension(
            R.styleable.TimeView_date_text_size,
            _textSize
        )
        _textColor = a.getColor(
            R.styleable.TimeView_date_text_color,
            _textColor
        )
        a.recycle()

        // Set up a default TextPaint object
        rectanglePaint = Paint().apply {
            flags = Paint.ANTI_ALIAS_FLAG
            color = _rectangleColor
            style = Paint.Style.FILL
        }

        // time
        timePaint = TextPaint().apply {
            flags = Paint.ANTI_ALIAS_FLAG
            textAlign = Paint.Align.CENTER
            textSize = _timeTextSize
            color = _timeTextColor
        }

        // text
        textPaint = TextPaint().apply {
            flags = Paint.ANTI_ALIAS_FLAG
            textAlign = Paint.Align.CENTER
            textSize = _textSize
            color = _textColor
        }

    }

    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec)
        val widthSize = MeasureSpec.getSize(widthMeasureSpec)
        val widthMode = MeasureSpec.getMode(widthMeasureSpec)
        val heightSize = MeasureSpec.getSize(heightMeasureSpec)
        val heightMode = MeasureSpec.getMode(heightMeasureSpec)
        //处理wrap_contentde情况
        if (widthMode == MeasureSpec.AT_MOST && heightMode == MeasureSpec.AT_MOST) {
            setMeasuredDimension(DEFAULT_WITH, DEFAULT_Height)
        } else if (widthMode == MeasureSpec.AT_MOST) {
            setMeasuredDimension(DEFAULT_WITH, heightSize)
        } else if (heightMode == MeasureSpec.AT_MOST) {
            setMeasuredDimension(widthSize, DEFAULT_Height)
        }
    }

    @SuppressLint("DrawAllocation")
    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)

        // allocations per draw cycle.
        val paddingLeft = paddingLeft
        val paddingTop = paddingTop
        val paddingRight = paddingRight
        val paddingBottom = paddingBottom

        val contentWidth = width - paddingLeft - paddingRight
        val contentHeight = height - paddingTop - paddingBottom

        var timeWidth = getTextWidth(_timeString, timePaint!!)
        val timeHeight = getTextHeight(_timeString, timePaint!!)

        val textWidth = getTextWidth(_textString, textPaint!!)
        val textHeight = getTextHeight(_textString, textPaint!!)

        val leftWidth = (contentWidth - (_rectangleWidth + 10 + textWidth)) / 2

        with(canvas) {
            drawRoundRect(RectF(leftWidth , (contentHeight - _rectangleHeight) / 2, leftWidth +  _rectangleWidth, _rectangleHeight + (contentHeight - _rectangleHeight) / 2), 10f, 10f, rectanglePaint!!)
            drawText(_timeString, leftWidth + _rectangleWidth / 2, (contentHeight / 2 + timeHeight / 2).toFloat(), timePaint!!)
            drawText(_textString, leftWidth + _rectangleWidth + textWidth / 2 + 10, (contentHeight / 2 + textHeight / 2).toFloat(), textPaint!!)
        }
    }

    private fun getTextWidth(text: String, paint: Paint): Float {
        val rect = Rect() // 文字所在区域的矩形
        paint.getTextBounds(text, 0, text.length, rect)
        return rect.width().toFloat()
    }

    private fun getTextHeight(text: String, paint: Paint): Float {
        val rect = Rect() // 文字所在区域的矩形
        paint.getTextBounds(text, 0, text.length, rect)
        return rect.height().toFloat()
    }

}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值