【安卓开发】自定义view


一、定位

SpecMode

模式规则
EXACTLY父容器为 View 制定了精确的大小, View 的最终大小就是 SpecSize 所指定的值,它对应于 match_parent 和具体的数值这两种模式
AT_MOST父容器指定了一个可用大小即 SpecSize,子View只要不超过这个尺寸即可 ,对应的场景就是wrap_content
UNSPECIFIED不限制模式,父视图对子视图没有任何约束,要多大给多大。

onMeasure()决定View的大小

    // 所希望的尺寸
    val desiredWidth = 100
    val desiredHeight = 100
    
    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        // 获取模式
        val heightMode = MeasureSpec.getMode(heightMeasureSpec)
        val widthMode = MeasureSpec.getMode(widthMeasureSpec)

        var height: Int;
        var width: Int;
        if (widthMode == MeasureSpec.EXACTLY) {//固定
            width = widthMeasureSpec
        } else if (widthMode == MeasureSpec.AT_MOST) {//最大
            width = Math.min(widthMeasureSpec, desiredWidth)
        } else {//不限制
            width = desiredWidth
        }
        if (heightMode == MeasureSpec.EXACTLY) {
            height = heightMeasureSpec
        } else if (heightMode == MeasureSpec.AT_MOST) {
            height = Math.min(heightMeasureSpec, desiredHeight)
        } else {
            height = desiredHeight
        }
        setMeasuredDimension(height, width)
    }

onLayout

onLayout()决定View在ViewGroup中的位置
onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int)四个参数分别为该控件相对于父控件的左(mLeft)上(mTop)右(mRight)下(mBottom)四个位置的坐标(见下图),也可以通过View的getLeft()、getTop()、getRight()、getBottom()获取上述距离。通常当ViewGroup/View有多个子view的时候才会需要重写该方法。
在这里插入图片描述


二、绘制流程

AttributeSet

View的构造函数constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {},可以获取到view在xml文件中被设定的属性以及属性值。

第一步:自定义属性:
需要创建attrs.xml文件,为特定的view增加属性:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <attr name="textColor" format="color" />

    <declare-styleable name="CustomView">
        <attr name="textColor" />
    </declare-styleable>

</resources>

第二步:在xml文件中为该自定义的view增加自定义的属性值。
记得要增加xmlns:app这一行。当然你也可以给它改名为xmlns:custom或其他名字。

</…………
    xmlns:app="http://schemas.android.com/apk/res-auto"/>
    <!--    xmlns:custom="http://schemas.android.com/apk/res-auto"-->

        <com.accmobile.kotlintestpro.custom.CustomView
            android:id="@+id/c_view"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            app:textColor="@color/white" />
<!--        custom:textColor="@color/white"-->

第三步: AttributeSet / TypedArray获取方式:

        // 直接从AttributeSet获取
        val count = attrs.attributeCount
        var i = 0
        while (i < count) {
            Log.e(TAG, "attrName = " + attrs.getAttributeName(i) + " , attrVal = " + attrs.getAttributeValue(i));
            i++
        }
        // 通过TypedArray获取
        val ta: TypedArray = context.obtainStyledAttributes(attrs, R.styleable.CustomView)
        pointColor = ta.getColor(R.styleable.CustomView_pointColor, Color.Black.toArgb())
        ta.recycle()

最后,通过获取到的属性值在代码中进行操作。

onDraw

参考文章

在这里插入图片描述
主要是重写onDraw(canvas: Canvas?)函数。
Canvas类提供了很多的绘制方法,详见参考文章
比较常用的函数是drawBitmap(Bitmap bitmap,float left, float top, Paint paint):在指定点(x,y)使用指定的画笔paint绘制位图。

    override fun onDraw(canvas: Canvas?) {
        super.onDraw(canvas)
        paint.color = pointColor
        if (pointX != 0f) {
            canvas!!.drawCircle(pointX, pointY, 50f, paint)
        }
    }

三、点击事件

通常会定义一个最短移动距离大小,当手指移动距离小于这个数,则认为是点击事件,否则认为是滑动事件:

    fun getTouchSlop(context: Context?): Int {
        return ViewConfiguration.get(context!!).scaledTouchSlop
    }

通常会重写onTouchEvent方法。
我们首先需要了解MotionEvent的常用方法:
getAction():获取事件类型。常用的几种事件类型在下方代码中。
getX():获得触摸点在当前 View 的 X 轴坐标。
getY():获得触摸点在当前 View 的 Y 轴坐标。
getRawX():获得触摸点在整个屏幕的 X 轴坐标。
getRawY():获得触摸点在整个屏幕的 Y 轴坐标。

单点点击:

    override fun onTouchEvent(event: MotionEvent?): Boolean {
        this.pointX = event!!.x
        this.pointY = event.y
        Log.d(TAG, "$pointX,$pointY")

        when (event.action) {

            /******************单点事件******************/
            MotionEvent.ACTION_DOWN -> {
                Log.d(TAG, "手指按下")
                downX = event.x
                downY = event.y
            }

            MotionEvent.ACTION_UP -> {
                //手指抬起
                val distanceX = abs(event.x - downX)
                val distanceY = abs(event.y - downY)
                if (distanceX < touchSlop && distanceY < touchSlop) {
                    Toast.makeText(context, "点击", Toast.LENGTH_SHORT).show()
                } else {
                    Toast.makeText(context, "滑动", Toast.LENGTH_SHORT).show()
                }
                pointX = 0f
            }

            MotionEvent.ACTION_MOVE -> {
                Log.d(TAG, "手指滑动")
            }

            MotionEvent.ACTION_CANCEL -> {
                Log.d(TAG, "事件被拦截")
            }

            MotionEvent.ACTION_OUTSIDE -> {
                Log.d(TAG, "超出区域")
            }
        }
        this.invalidate()// 更新视图
        return true
    }

多点点击:

详见下文

参考文章

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值