前言
通常来说,子view大小超出父布局大小的时候,超出的部分是不会显示的,这个时候如果想要实现超出部分能够显示,那么clipChildren就是它了。
使用
代码和xml使用方法
#ViewGroup.java
public void setClipChildren(boolean clipChildren)
复制代码
android:clipChildren="false"
android:clipChildren="true"
布局编写(clipChildren=false)
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也就超出部分显示不了
如果写在爷爷布局,那么父布局本身不会被裁剪了,大小和爷爷布局一样了,这个时候子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点击了。