自定义View 总结

画笔

一、实现步骤

1. 继承View类或其子类

 

2. 复写view中的一些函数

 

3. 为自定义View类增加属性(两种方式)

 

4. 绘制控件(导入布局)

 

5. 响应用户事件

 

6. 定义回调函数(根据自己需求来选择)

 

二、哪些方法需要被重写

  • onDraw()

view中onDraw()是个空函数,也就是说具体的视图都要覆写该函数来实现自己的绘制。对于ViewGroup则不需要实现该函数,因为作为容器是“没有内容“的(但必须实现dispatchDraw()函数,告诉子view绘制自己)。

  • onLayout()

主要是为viewGroup类型布局子视图用的,在View中这个函数为空函数。

  • onMeasure()

用于计算视图大小(即长和宽)的方式,并通过setMeasuredDimension (width, height) 保存计算结果。

  • onTouchEvent()

定义触屏事件来响应用户操作。

还有一些不常用的方法:

onKeyDown()当按下某个键盘时

onKeyUp()当松开某个键盘时

onTrackballEvent()当发生轨迹球事件时

onSizeChange()当该组件的大小被改变时

onFinishInflate()回调方法,当应用从XML加载该组件并用它构建界面之后调用的方法

onWindowFocusChanged(boolean)当该组件得到、失去焦点时

onAttachedToWindow()当把该组件放入到某个窗口时

onDetachedFromWindow()当把该组件从某个窗口上分离时触发的方法

onWindowVisibilityChanged(int)当包含该组件的窗口的可见性发生改变时触发的方法

View的绘制流程

二、哪些方法需要被重写

  • onDraw()

view中onDraw()是个空函数,也就是说具体的视图都要覆写该函数来实现自己的绘制。对于ViewGroup则不需要实现该函数,因为作为容器是“没有内容“的(但必须实现dispatchDraw()函数,告诉子view绘制自己)。

  • onLayout()

主要是为viewGroup类型布局子视图用的,在View中这个函数为空函数。

  • onMeasure()

用于计算视图大小(即长和宽)的方式,并通过setMeasuredDimension (width, height) 保存计算结果。

  • onTouchEvent()

定义触屏事件来响应用户操作。

还有一些不常用的方法:

onKeyDown()当按下某个键盘时

onKeyUp()当松开某个键盘时

onTrackballEvent()当发生轨迹球事件时

onSizeChange()当该组件的大小被改变时

onFinishInflate()回调方法,当应用从XML加载该组件并用它构建界面之后调用的方法

onWindowFocusChanged(boolean)当该组件得到、失去焦点时

onAttachedToWindow()当把该组件放入到某个窗口时

onDetachedFromWindow()当把该组件从某个窗口上分离时触发的方法

onWindowVisibilityChanged(int)当包含该组件的窗口的可见性发生改变时触发的方法

View的绘制流程

我们调用requestLayout()的时候,会触发measure 和 layout 过程,调用invalidate,会执行 draw 过程。

三.自定义控件的三种方式

1. 继承已有的控件

当要实现的控件和已有的控件在很多方面比较类似, 通过对已有控件的扩展来满足要求。

2. 继承一个布局文件

一般用于自定义组合控件,在构造函数中通过inflater和addView()方法加载自定义控件的布局文件形成图形界面(不需要onDraw方法)。

3.继承view

通过onDraw方法来绘制出组件界面。

四.自定义属性

attr_zxy.xml

<?xml version="1.0" encoding="utf-8"?>
<!-- 功能:自定义attr -->
<resources>
    <declare-styleable name="zxy">
        <attr name="zxyTextColor" format="color" />
    </declare-styleable>
</resources>

ZxyTextView.kt

 
/**
 * Created by zxy on 2020/4/18 0018 16:02
 * ******************************************
 * * 简单的自定义View
 * ******************************************
 */
class ZxyTextView : androidx.appcompat.widget.AppCompatTextView {
    var zxyColor: Int = Color.BLACK
    constructor(mContext: Context?, attrs: AttributeSet?) : super(mContext,attrs) {
        //取出自定义Attr的属性分组
        val array =
                context!!.theme.obtainStyledAttributes(attrs, R.styleable.zxy, R.attr.zxyTextColor, 0)
        //遍历,这边只有一个自定义attr属性,循环可有可无
        for (index in 0..array.indexCount step 1) {
            when (array.getIndex(index)) {
                R.styleable.zxy_zxyTextColor ->
                    //得到xml中传入的颜色值
                    zxyColor = array.getColor(R.styleable.zxy_zxyTextColor, Color.BLACK)
            }
        }
        array.recycle()//回收
        init()
    }
    /**
     * 初始化内容
     */
    private fun init() {
        this.setTextColor(zxyColor)//设置TextView的文字颜色为xml传入的颜色
    }
}

布局文件中的app:zxyTextColor="#1425DF" 属性为暴露出去的属性

<com.zxy.zxypermissionsdispatcher.ZxyTextView
        android:id="@+id/text9"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="20dp"
        android:text="TextView9TextView9"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintTop_toBottomOf="@id/text8"
        app:zxyTextColor="#1425DF"
        />

其他属性示例:

attr定义的属性

 <attr name="attr_zxy" format="reference" />
    <declare-styleable name="ZXY">
        <!-- 颜色       -->
        <attr name="zxyTitleTextColor" format="color" />
        <attr name="zxyRightTextColor" format="color" />
        <attr name="zxyBackgroundColor" format="color" />
        <!-- 图片       -->
        <attr name="zxyLeftImg" format="reference" />
        <attr name="zxyRightImg1" format="reference" />
        <attr name="zxyRightImg2" format="reference" />
        <!-- 文案       -->
        <attr name="zxyTitleText" format="string" />
        <attr name="zxyRightText" format="string" />
        <!-- 尺寸       -->
        <attr name = "zxyTitleSize" format = "dimension" />
        <attr name = "zxyRightTitleSize" format = "dimension" />
    </declare-styleable>

获取布局的属性

 when (array.getIndex(index)) {
                R.styleable.ZXY_zxyBackgroundColor ->
                    backgroundColor = array.getColor(R.styleable.ZXY_zxyBackgroundColor, Color.BLACK)
                R.styleable.ZXY_zxyRightTextColor ->
                    rightTextColor = array.getColor(R.styleable.ZXY_zxyRightTextColor, Color.BLACK)
                R.styleable.ZXY_zxyTitleTextColor ->
                    titleTextColor = array.getColor(R.styleable.ZXY_zxyTitleTextColor, Color.BLACK)
                R.styleable.ZXY_zxyRightText ->
                    rightText = array.getString(R.styleable.ZXY_zxyRightText)
                R.styleable.ZXY_zxyTitleText ->
                    titleText = array.getString(R.styleable.ZXY_zxyTitleText)
                R.styleable.ZXY_zxyRightImg1 ->
                    rightImg1 = array.getResourceId(R.styleable.ZXY_zxyRightImg1,-1)
                R.styleable.ZXY_zxyLeftImg ->
                    leftImg = array.getResourceId(R.styleable.ZXY_zxyLeftImg,-1)
                R.styleable.ZXY_zxyRightImg2 ->
                    rightImg2 = array.getResourceId(R.styleable.ZXY_zxyRightImg2,-1)
                R.styleable.ZXY_zxyTitleSize ->
                    titleSize = array.getDimensionPixelSize(R.styleable.ZXY_zxyTitleSize, 18)
                R.styleable.ZXY_zxyRightTitleSize ->
                    rightTextSize = array.getDimensionPixelOffset(R.styleable.ZXY_zxyRightTitleSize, 18)
            }

format 数据类型参考

1. reference:参考某一资源ID。
 
    (1)属性定义:
 
            <declare-styleable name = "名称">
 
                   <attr name = "background" format = "reference" />
 
            </declare-styleable>
 
    (2)属性使用:
 
            <ImageView
 
                     android:layout_width = "42dip"
 
                     android:layout_height = "42dip"
 
                     android:background = "@drawable/图片ID"
 
                     />
 
2. color:颜色值。
 
    (1)属性定义:
 
            <declare-styleable name = "名称">
 
                   <attr name = "textColor" format = "color" />
 
            </declare-styleable>
 
    (2)属性使用:
 
            <TextView
 
                     android:layout_width = "42dip"
 
                     android:layout_height = "42dip"
 
                     android:textColor = "#00FF00"
 
                     />
 
3. boolean:布尔值。
 
    (1)属性定义:
 
            <declare-styleable name = "名称">
 
                <attr name = "focusable" format = "boolean" />
 
            </declare-styleable>
 
    (2)属性使用:
 
            <Button
 
                   android:layout_width = "42dip"
 
                   android:layout_height = "42dip"
 
                   android:focusable = "true"
 
                    />
 
4. dimension:尺寸值。
 
     (1)属性定义:
 
             <declare-styleable name = "名称">
 
                   <attr name = "layout_width" format = "dimension" />
 
            </declare-styleable>
 
    (2)属性使用:
 
            <Button
 
                   android:layout_width = "42dip"
 
                   android:layout_height = "42dip"
 
                  />
 
5. float:浮点值。
 
    (1)属性定义:
 
            <declare-styleable name = "AlphaAnimation">
 
                   <attr name = "fromAlpha" format = "float" />
 
                   <attr name = "toAlpha" format = "float" />
 
            </declare-styleable>
 
    (2)属性使用:
 
            <alpha
 
                   android:fromAlpha = "1.0"
 
                   android:toAlpha = "0.7"
 
                   />
 
6. integer:整型值。
 
    (1)属性定义:
 
            <declare-styleable name = "AnimatedRotateDrawable">
 
                   <attr name = "visible" />
 
                   <attr name = "frameDuration" format="integer" />
 
                   <attr name = "framesCount" format="integer" />
 
                   <attr name = "pivotX" />
 
                   <attr name = "pivotY" />
 
                   <attr name = "drawable" />
 
            </declare-styleable>
 
    (2)属性使用:
 
            <animated-rotate
 
                   xmlns:android = "http://schemas.android.com/apk/res/android"  
 
                   android:drawable = "@drawable/图片ID"  
 
                   android:pivotX = "50%"  
 
                   android:pivotY = "50%"  
 
                   android:framesCount = "12"  
 
                   android:frameDuration = "100"
 
                   />
 
7. string:字符串。
 
    (1)属性定义:
 
            <declare-styleable name = "MapView">
 
                   <attr name = "apiKey" format = "string" />
 
            </declare-styleable>
 
    (2)属性使用:
 
            <com.google.android.maps.MapView
 
                    android:layout_width = "fill_parent"
 
                    android:layout_height = "fill_parent"
 
                    android:apiKey = "0jOkQ80oD1JL9C6HAja99uGXCRiS2CGjKO_bc_g"
 
                    />
 
8. fraction:百分数。
 
     (1)属性定义:
 
            <declare-styleable name="RotateDrawable">
 
                   <attr name = "visible" />
 
                   <attr name = "fromDegrees" format = "float" />
 
                   <attr name = "toDegrees" format = "float" />
 
                   <attr name = "pivotX" format = "fraction" />
 
                   <attr name = "pivotY" format = "fraction" />
 
                   <attr name = "drawable" />
 
            </declare-styleable>
 
    (2)属性使用:
 
            <rotate
 
                 xmlns:android = "http://schemas.android.com/apk/res/android" 
 
             android:interpolator = "@anim/动画ID"
 
                 android:fromDegrees = "0" 
 
             android:toDegrees = "360"
 
                 android:pivotX = "200%"
 
                 android:pivotY = "300%" 
 
             android:duration = "5000"
 
                 android:repeatMode = "restart"
 
                 android:repeatCount = "infinite"
 
                />
 
9. enum:枚举值。
 
    (1)属性定义:
 
            <declare-styleable name="名称">
 
                   <attr name="orientation">
 
                          <enum name="horizontal" value="0" />
 
                          <enum name="vertical" value="1" />
 
                   </attr>            
 
            </declare-styleable>
 
    (2)属性使用:
 
            <LinearLayout
 
                    xmlns:android = "http://schemas.android.com/apk/res/android"
 
                    android:orientation = "vertical"
 
                    android:layout_width = "fill_parent"
 
                    android:layout_height = "fill_parent"
 
                    >
 
            </LinearLayout>
 
10. flag:位或运算。
 
     (1)属性定义:
 
             <declare-styleable name="名称">
 
                    <attr name="windowSoftInputMode">
 
                            <flag name = "stateUnspecified" value = "0" />
 
                            <flag name = "stateUnchanged" value = "1" />
 
                            <flag name = "stateHidden" value = "2" />
 
                            <flag name = "stateAlwaysHidden" value = "3" />
 
                            <flag name = "stateVisible" value = "4" />
 
                            <flag name = "stateAlwaysVisible" value = "5" />
 
                            <flag name = "adjustUnspecified" value = "0x00" />
 
                            <flag name = "adjustResize" value = "0x10" />
 
                            <flag name = "adjustPan" value = "0x20" />
 
                            <flag name = "adjustNothing" value = "0x30" />
 
                     </attr>         
 
             </declare-styleable>

/**
 * Created by zxy on 2020/9/16 14:12
 * ******************************************
 * * 自定义View
 * ******************************************
 */
class MyView : View {
    private lateinit var linePaint: Paint //默认线条画笔
    private lateinit var linePaintSelect: Paint//选中线条画笔
    private var startX = 200f//起始位置X
    private var startY = 200f//起始位置Y
    private var lineStep = 300f//线的步长
    private var radius = 40f//圆圈的半径

    private var defaultWidth = 0//默认View的宽度
    private var defaultHeight = 0//默认View的高度

    private val textSize = 40f//字体的大小
    private var strokeWidthLine = 4f//画笔的宽度
    var textList = mutableListOf<String>()//数据源
    var number = 0//进度
    var bitmap = BitmapFactory.decodeResource(resources, R.drawable.jingbi)//金币图片


    constructor(mContext: Context) : super(mContext) {
        init(mContext)
    }

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


    constructor(mContext: Context, attrs: AttributeSet, defStyleAttr: Int) : super(mContext, attrs, defStyleAttr) {
        init(mContext)
    }

    private fun init(mContext: Context) {
        initLinePaint(mContext)
    }

    private fun initLinePaint(mContext: Context) {
        linePaint = Paint()
        //设置是否抗锯齿;设置抗锯齿会使图像边缘更清晰一些,锯齿痕迹不会那么明显。
        linePaint.isAntiAlias = true
        linePaint.style = Paint.Style.FILL
        //设置画笔颜色
        linePaint.color = 0xffCECECE.toInt()
        //设置画笔宽度
        linePaint.strokeWidth = ScreenUtil.dp2px(mContext, strokeWidthLine).toFloat()
        linePaint.textSize = textSize

        linePaintSelect = Paint()
        //设置是否抗锯齿;设置抗锯齿会使图像边缘更清晰一些,锯齿痕迹不会那么明显。
        linePaintSelect.isAntiAlias = true
        linePaintSelect.style = Paint.Style.FILL
        //设置画笔颜色
        linePaintSelect.color = 0xff3C5EF9.toInt()
        //设置画笔宽度
        linePaintSelect.strokeWidth = ScreenUtil.dp2px(mContext, strokeWidthLine).toFloat()
        linePaintSelect.textSize = textSize
    }


    @SuppressLint("DrawAllocation")
    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)
        canvas.drawColor(0xffffffff.toInt())
        drawOld(canvas)
        drawNew(canvas)
    }

    private fun drawOld(canvas: Canvas) {
        val width = bitmap.width
        val height = bitmap.height
        for (index in textList.indices) {
            if (index == 0) {
                canvas.drawCircle(startX + (lineStep * index), startY, radius, linePaint)
                canvas.drawText(textList[index], startX + (lineStep * index) - (textSize / 2), startY + radius * 2, linePaint)
                canvas.drawBitmap(bitmap, startX - (width / 2) + (lineStep * index), (startY - radius * 2) - height, linePaint)
                if (index != textList.size - 1)
                    canvas.drawLine(startX + (lineStep * index), startY, startX + (lineStep * (index + 1)), startY, linePaint)
            } else {
                canvas.drawCircle(startX + (lineStep * index) + radius, startY, radius, linePaint)
                canvas.drawText(textList[index], startX + (lineStep * index) - (textSize / 2) + radius, startY + radius * 2, linePaint)
                canvas.drawBitmap(bitmap, startX + (lineStep * index) + radius - width / 2, (startY - radius * 2) - height, linePaint)
                if (index != textList.size - 1)
                    canvas.drawLine(startX + (lineStep * index) + (radius * 2), startY, startX + (lineStep * (index + 1)), startY, linePaint)
            }

        }
    }

    private fun drawNew(canvas: Canvas) {
        if (number != 0)
            for (index in textList.indices) {
                val decade = number / 10 % 10 //取到十位
                if (index == 0) {
                    canvas.drawCircle(startX + (lineStep * index), startY, radius, linePaintSelect)
                    canvas.drawText(textList[index], startX + (lineStep * index) - (textSize / 2), startY + radius * 2, linePaintSelect)
                } else {
                    canvas.drawCircle(startX + (lineStep * index) + radius, startY, radius, linePaintSelect)
                    canvas.drawText(textList[index], startX + (lineStep * index) - (textSize / 2) + radius, startY + radius * 2, linePaintSelect)
                }
                if (decade > index) {
                    if (index != textList.size - 1)
                        canvas.drawLine(startX + (lineStep * index), startY, startX + (lineStep * (index + 1)), startY, linePaintSelect)
                } else {
                    if (decade==0 || index != textList.size - 1)
                        canvas.drawLine(startX + (lineStep * index), startY, startX + (lineStep * index) + (number % 10 * 10 * (lineStep / 100)), startY, linePaintSelect)
                    break
                }
            }
    }

    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec)
        val widthMode = MeasureSpec.getMode(widthMeasureSpec)
        val heightMode = MeasureSpec.getMode(heightMeasureSpec)
        val widthSize = MeasureSpec.getSize(widthMeasureSpec)
        val heightSize = MeasureSpec.getSize(heightMeasureSpec)
        if (widthMode == MeasureSpec.AT_MOST && heightMode == MeasureSpec.AT_MOST) {
            setMeasuredDimension(defaultWidth, defaultHeight)
        } else if (widthMode == MeasureSpec.AT_MOST) {
            setMeasuredDimension(defaultWidth, heightSize)
        } else if (heightMode == MeasureSpec.AT_MOST) {
            defaultWidth = (lineStep * (textList.size - 1) + (startX * 2)).toInt()//线的宽度
            defaultHeight = (bitmap.height + startY + radius * 2).toInt()
            setMeasuredDimension(defaultWidth, defaultHeight)
        }
    }

    /**
     * 刷新测量View
     */
    fun refreshView() {
        postInvalidate()
        requestLayout()
    }

}

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值