ClipChildren学习记录

前言

通常来说,子view大小超出父布局大小的时候,超出的部分是不会显示的,这个时候如果想要实现超出部分能够显示,那么clipChildren就是它了。

使用

代码和xml使用方法

#ViewGroup.java
public void setClipChildren(boolean clipChildren)
复制代码

android:clipChildren="false"
android:clipChildren="true"

布局编写(clipChildren=false)

截屏2021-05-17 上午11.32.30.png

ClipParentView实际上是一个ConstraintLayout (其实就是作为3个按钮的父布局而已)

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/clHighest"
    android:layout_width="match_parent"
    android:background="@android:color/holo_blue_bright"
    android:layout_height="match_parent"
    android:clipChildren="false">


    <com.czy.view.ClipParentView
        android:id="@+id/clParent"
        android:layout_width="0dp"
        android:layout_height="50dp"
        android:background="@color/colorAccent"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent">

        <TextView
            android:id="@+id/tv1"
            android:layout_width="0dp"
            android:layout_height="0dp"
            android:background="@android:color/holo_green_dark"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toStartOf="@id/tv2"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

        <TextView
            android:id="@+id/tv2"
            android:layout_width="0dp"
            android:layout_height="200dp"
            android:background="@android:color/black"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toStartOf="@id/tv3"
            app:layout_constraintStart_toEndOf="@id/tv1"
            app:layout_constraintTop_toTopOf="parent" />

        <TextView
            android:id="@+id/tv3"
            android:layout_width="0dp"
            android:layout_height="0dp"
            android:background="@android:color/holo_red_dark"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toEndOf="@id/tv2"
            app:layout_constraintTop_toTopOf="parent" />

    </com.czy.view.ClipParentView>
</androidx.constraintlayout.widget.ConstraintLayout>
复制代码

为什么要吧ClipChildren写在父布局的外层(爷爷层)?
如果写在父布局,父布局并没有裁剪子view了,但是爷爷层还是进行的裁剪,所以父布局被爷爷限制了,子view也就超出部分显示不了

截屏2021-05-14 下午5.57.26.png
如果写在爷爷布局,那么父布局本身不会被裁剪了,大小和爷爷布局一样了,这个时候子view超出部分就算被父布局限制了,那么也不会影响超出部分的显示。

点击效果实现

tv2.setOnClickListener { Toast.makeText(this, "tv2", Toast.LENGTH_SHORT).show() }
光速打脸~点击黑色超出的部分竟然毫无反应,原因就是点击超出了viewgroup的范围,那这个时候就想到扩大viewgroup的点击范围,我们可以靠TouchDelegate来实现。(自己定义一个ClipTouchDelegate继承TouchDelegate)

TouchDelegate的onTouchEvent里面会让每次点击移动到viewgroup的中心点,但是这个点击范围不一定在子view上,需要重新写onTouchEvent,把hit里面的操作去掉

override fun onTouchEvent(event: MotionEvent): Boolean {
        val x = event.x.toInt()
        val y = event.y.toInt()
        var sendToDelegate = false
        var hit = true
        var handled = false

        when (event.actionMasked) {
          ...
        if (sendToDelegate) {
            if (hit) {
                //命中,则将MotionEvent 坐标移动到目标View的中心
                // Offset event coordinates to be inside the target view
//                event.setLocation(
//                    (mDelegateView.getWidth() / 2).toFloat(),
//                    (mDelegateView.getHeight() / 2).toFloat()
//                )
            } else {
              ...
        }
        return handled
复制代码
clParent.post {
            val rect = Rect()
            clParent.getHitRect(rect)
            rect.top -= 300
            clParent.isClickable = true
            val touchDelegate = ClipTouchDelegate(rect, clParent)
            clHighest.isClickable = true
            clHighest.touchDelegate = touchDelegate
        }
复制代码

这样就可以让viewgroup接收的点击范围扩大了。viewGroup是接收到了,但是我们实际上是里面的子view消费事件,这里需要对子view进行分发事件的处理。

自定义一个父布局,重新onTouchEvent

override fun onTouchEvent(event: MotionEvent): Boolean {
        //获取坐标相对屏幕的位置
        val rawX = event.rawX
        val rawY = event.rawY
      
        //检测坐标是否落在对应的子布局内

        checkChildTouch(rawX, rawY)?.apply {
            event.setLocation((this.width / 2).toFloat(), (this.height / 2).toFloat())
            //分发事件给子布局
            return this.dispatchTouchEvent(event)
        }

        return super.onTouchEvent(event)


    }

    private fun checkChildTouch(x: Float, y: Float): View? {
        val outLocation = IntArray(2)
        for (i in 0 until childCount) {
            val child: View = getChildAt(i)
            if (child.visibility === VISIBLE) {
                //获取View 在屏幕上的可见坐标
                child.getLocationOnScreen(outLocation)
                //点击坐标是否落在View 的可见区域,若是则将事件分发给它
                val hit =
                    x >= outLocation[0] && y > outLocation[1] && x <= outLocation[0] + child.width && y <= outLocation[1] + child.height
                if (hit) return child
            }
        }
        return null
    }
复制代码

这样就能够实现子view点击了。

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享