前言
- 需求:2個(gè)View并不在同一層父布局布局中,點(diǎn)擊上層的檢測(cè)View,將點(diǎn)擊事件轉(zhuǎn)發(fā)給背后的兄弟View
- 原理:基于自定義事件處理點(diǎn)擊的x、y坐標(biāo),判斷是否在背后的兄弟View的范圍內(nèi)
Java代碼
import android.content.Context;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewTreeObserver;
import androidx.annotation.Nullable;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/**
* 觸摸檢測(cè)View,可以將自身的點(diǎn)擊事件,轉(zhuǎn)發(fā)給指定的目標(biāo)View
*/
public class TouchDetectorView extends View {
/**
* 觸摸滑動(dòng)的最小距離,超過(guò)這個(gè)距離則認(rèn)為是拖動(dòng),而不是點(diǎn)擊
*/
private int touchSlop;
/**
* 目標(biāo)View列表
*/
private final List<TargetWrapper> targets = new ArrayList<>();
/**
* 點(diǎn)擊監(jiān)聽器
*/
private OnTargetTouchListener touchListener;
/**
* Down事件的X坐標(biāo)
*/
private float downX;
/**
* Up事件的Y坐標(biāo)
*/
private float downY;
public TouchDetectorView(Context context) {
this(context, null);
}
public TouchDetectorView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public TouchDetectorView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context);
}
private void init(Context context) {
touchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
}
public interface OnTargetTouchListener {
void onTargetTouched(View targetView);
}
/**
* 設(shè)置觸摸監(jiān)聽器
*/
public void setOnTargetTouchListener(OnTargetTouchListener listener) {
this.touchListener = listener;
}
/**
* 動(dòng)態(tài)添加監(jiān)控目標(biāo)View
*/
public void addTargetView(View targetView) {
if (targetView == null) return;
// 避免重復(fù)添加
for (TargetWrapper wrapper : targets) {
if (wrapper.getView() == targetView) {
return;
}
}
TargetWrapper wrapper = new TargetWrapper(targetView);
targets.add(wrapper);
registerTargetViewLayoutListener(targetView);
}
/**
* 移除對(duì)目標(biāo)View的監(jiān)控
*/
public void removeTargetView(View targetView) {
Iterator<TargetWrapper> iterator = targets.iterator();
while (iterator.hasNext()) {
TargetWrapper wrapper = iterator.next();
if (wrapper.getView() == targetView) {
unregisterTargetViewLayoutListener(targetView);
iterator.remove();
break;
}
}
}
@Override
public boolean onTouchEvent(MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
downX = event.getRawX();
downY = event.getRawY();
} else if (event.getAction() == MotionEvent.ACTION_UP) {
int deltaX = (int) (event.getRawX() - downX);
int deltaY = (int) (event.getRawY() - downY);
// 如果移動(dòng)距離超過(guò)touchSlop,則認(rèn)為是滑動(dòng),不是點(diǎn)擊
boolean isClick = (Math.abs(deltaX) <= touchSlop) && (Math.abs(deltaY) <= touchSlop);
// 如果是點(diǎn)擊,則開始判斷點(diǎn)擊范圍,是否在目標(biāo)View的矩形區(qū)域內(nèi)
if (isClick) {
// 倒序遍歷(后添加的View優(yōu)先檢測(cè))
for (int i = targets.size() - 1; i >= 0; i--) {
TargetWrapper wrapper = targets.get(i);
if (wrapper.rect == null) {
continue;
}
if (wrapper.rect.contains((int) downX, (int) downY)) {
View target = wrapper.getView();
if (target != null && touchListener != null) {
touchListener.onTargetTouched(target);
return true;
}
}
}
}
}
return super.onTouchEvent(event);
}
/**
* 注冊(cè),目標(biāo)View的布局監(jiān)聽器,以便在布局變化時(shí)更新矩形區(qū)域
*/
private void registerTargetViewLayoutListener(View targetView) {
LayoutTracker layoutTracker = new LayoutTracker(targetView);
ViewTreeObserver observer = targetView.getViewTreeObserver();
observer.addOnGlobalLayoutListener(layoutTracker);
// 保存監(jiān)聽器引用
targetView.setTag(layoutTracker);
}
/**
* 取消注冊(cè),目標(biāo)View的布局監(jiān)聽器
*/
private void unregisterTargetViewLayoutListener(View targetView) {
Object tag = targetView.getTag();
if (tag instanceof LayoutTracker) {
LayoutTracker layoutTracker = (LayoutTracker) tag;
ViewTreeObserver observer = targetView.getViewTreeObserver();
observer.removeOnGlobalLayoutListener(layoutTracker);
}
}
/**
* 保存目標(biāo)View的引用和它的矩形區(qū)域
*/
private static class TargetWrapper {
/**
* View弱引用,避免內(nèi)存泄漏
*/
private final WeakReference<View> viewRef;
/**
* View的矩形區(qū)域
*/
private Rect rect;
TargetWrapper(View view) {
this.viewRef = new WeakReference<>(view);
updateRect();
}
View getView() {
return viewRef.get();
}
/**
* 更新View的矩形區(qū)域
*/
void updateRect() {
View view = viewRef.get();
if (view == null || view.getVisibility() != View.VISIBLE) {
rect = null;
return;
}
int[] location = new int[2];
view.getLocationOnScreen(location);
rect = new Rect(
location[0],
location[1],
location[0] + view.getWidth(),
location[1] + view.getHeight()
);
}
}
/**
* 監(jiān)聽目標(biāo)View的布局變化,更新View的矩形區(qū)域
*/
private class LayoutTracker implements ViewTreeObserver.OnGlobalLayoutListener {
private final WeakReference<View> trackedView;
LayoutTracker(View view) {
this.trackedView = new WeakReference<>(view);
}
@Override
public void onGlobalLayout() {
View view = trackedView.get();
if (view != null) {
updateViewRect(view);
}
}
private void updateViewRect(View targetView) {
for (TargetWrapper wrapper : targets) {
if (wrapper.getView() == targetView) {
wrapper.updateRect();
break;
}
}
}
}
}
XML布局
<com.xxx.xxx.widget.TouchDetectorView
android:id="@+id/touch_detector_view"
android:layout_width="match_parent"
android:layout_height="354"
android:clickable="true" />
簡(jiǎn)單使用
- 添加需要檢測(cè)的View,以及監(jiān)聽回調(diào),轉(zhuǎn)發(fā)點(diǎn)擊事件給檢測(cè)View
// 因?yàn)槭堑剐虮闅v,所以底層的View,先添加,上層的View后添加
binding.touchDetectorView.addTargetView(binding.magicMvVideo);
binding.touchDetectorView.addTargetView(binding.startCreateMvLayout);
binding.touchDetectorView.addTargetView(binding.oneKeyCreateMvLayout);
binding.touchDetectorView.addTargetView(binding.albumMvBtnLayout);
binding.touchDetectorView.addTargetView(binding.templateMvBtnLayout);
binding.touchDetectorView.setOnTargetTouchListener(new TouchDetectorView.OnTargetTouchListener() {
@Override
public void onTargetTouched(View targetView) {
// 轉(zhuǎn)發(fā)點(diǎn)擊事件
targetView.performClick();
}
});
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。