自定义View学习总结

自定义View学习总结

自定义View一般有三种方式

  • 继承View自定实现所有的逻辑
  • 继承现有的View或者ViewGroup,在这些基础上加一些逻辑
  • 组合现有的控件,添加一些额外的逻辑

这里我们就只看第一种,也是比较基础的。自定义View一般重写三个方法onMeasureonLayoutonDraw

  • onMeasure:测量View来确定最终的宽高
  • onLayout:布局确定子View的位置,自定义ViewGroup才需要重写这个方法
  • onDraw:可以在画布上画任何你想画的东西

首先定义一个CustomizeView,看看怎么调用

class CustomizeView @JvmOverloads constructor(
        context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {
    
    init { }
    
    override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
        super.onLayout(changed, left, top, right, bottom)
        XLogUtils.d("onLayout")
    }

    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec)
        XLogUtils.d("onMeasure")
    }

    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)
        XLogUtils.d("onDraw")
    }
}

Activity和XML代码很简单

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">


    <FrameLayout
        android:layout_width="300dp"
        android:layout_height="300dp"
        android:background="@android:color/holo_blue_light">

        <com.example.cb.test.ui.custom_view.CustomizeView
            android:layout_width="100dp"
            android:background="@android:color/holo_red_light"
            android:layout_height="100dp" />

    </FrameLayout>

</LinearLayout>

class CustomViewActivity : BaseActivity() {
    override fun getLayoutId()= R.layout.activity_custom_view
    override fun initUI() {}
    override fun initEvent() {}
}

运行之后可以看到

在这里插入图片描述
onMeasure执行了两次,onLayoutonDraw各执行一次;onMeasure不一定全是执行两次,这里不讨论这个了,有兴趣的可以去搜下。

onMeasure探究

override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {}
widthMeasureSpec宽的测量模式,heightMeasureSpec高度测量模式,测量模式??什么东西?
我们看下google的解释
在这里插入图片描述
大概的意思是 父View对其的要求,也就是这里的参数是父View传来的MeasureSpecMeasureSpec是一个32位的int值,高2位表示SpecMode,低30位表示SpecSize也就是测量的大小,先记着后面会讲,MeasureSpec也提供的方法可以直接获取SpecModeSpecSize

  /**
         * Extracts the mode from the supplied measure specification.
         *
         * @param measureSpec the measure specification to extract the mode from
         * @return {@link android.view.View.MeasureSpec#UNSPECIFIED},
         *         {@link android.view.View.MeasureSpec#AT_MOST} or
         *         {@link android.view.View.MeasureSpec#EXACTLY}
         */
        public static int getMode(int measureSpec) {
            //noinspection ResourceType
            return (measureSpec & MODE_MASK);
        }
  /**
         * Extracts the size from the supplied measure specification.
         *
         * @param measureSpec the measure specification to extract the size from
         * @return the size in pixels defined in the supplied measure specification
         */
        public static int getSize(int measureSpec) {
            return (measureSpec & ~MODE_MASK);
        }

getMode可以看出来 测量模式一共有三种

  • UNSPECIFIED:父容器不对子View做任何限制,子View要多大给多大,例如ScrollView
  • AT_MOST:父容器限定子View不能超过它,例如wrap_content
  • EXACTLY:父容器固定值或者match_parent

在这里插入图片描述

下面代码演示两个,第一行第二个 和 第三行第一个

 override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec)
        XLogUtils.d("onMeasure")

        val widthMode = MeasureSpec.getMode(widthMeasureSpec)
        val heightMode = MeasureSpec.getMode(heightMeasureSpec)
        val widthSize = MeasureSpec.getSize(widthMeasureSpec)
        val heightSize = MeasureSpec.getSize(heightMeasureSpec)

        when (widthMode) {
            MeasureSpec.AT_MOST -> XLogUtils.i("MeasureSpec.AT_MOST")
            MeasureSpec.UNSPECIFIED -> XLogUtils.i("MeasureSpec.UNSPECIFIED")
            MeasureSpec.EXACTLY -> XLogUtils.i("MeasureSpec.EXACTLY")
            else -> XLogUtils.i("")
        }
    }

在这里插入图片描述
在这里插入图片描述

都和上面的图对上了,小伙伴们可以自己去试试,这么说还不明显 我用一张图来表示,
在这里插入图片描述
两张图一对照就明白了了。加入我们要自定义一个宽高相等的view利用上面所讲的内容代码如下

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)

        val screenWidth = 500//假设屏幕宽度

        var width = when (widthMode) {
            MeasureSpec.AT_MOST -> min(screenWidth, widthSize)
            MeasureSpec.UNSPECIFIED -> screenWidth
            MeasureSpec.EXACTLY -> widthSize
             else -> widthSize //不写要报错
        }
        //存储测量测的值
        setMeasuredDimension(width, width)
    }

基本上onMeasure差不多就上面的这些东西,具体还要看业务中的需求。

onLayout

只会执行一次,用来决定子View的位置

onTouchEvent

  @Override
    public boolean onTouchEvent(MotionEvent ev) {
        return super.onTouchEvent(ev);
    }

int action = ev.getAction(); 常用的有下面几种

  • MotionEvent.ACTION_DOWN 手指按下操作
  • MotionEvent.ACTION_MOVE 手指移动操作
  • MotionEvent.ACTION_UP 手指抬起操作
  • MotionEvent.ACTION_CANCEL 事件结束操作

还有需要注意的一点就是
ev.getX()和ev.getRawX()区别

前者是获取在相对父View的x轴位置,后者是获取相对屏幕的x轴位置

自定义view与viewgroup的区别

  1. onDraw(Canvas canvas):两者都有,可以绘制自己的各种样式。
  2. onLayout() :viewgroup中必须重写,是个抽象方法
  3. dispatchDraw():控制子View绘制分发,可以控制子View绘制顺序

invalidate()、postInvalidate()、requestLayout()

  1. invalidate:重绘,会执行onDraw方法
  2. postInvalidate:跟invalidate功能一样,可以在子线程操作
  3. requestLayout:当View的宽高,发生了变化时调用,会重新执行一遍 onMease onlayout
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值