android子view点击事件(click)和父view长点击事件(longclick)冲突

在Android开发中,当子View有点击事件而父View有长按事件时,长按子View不会触发父View的长按事件。通过为每个子View设置长按事件或者重写父View的dispatchTouchEvent方法来解决这个问题。本文介绍了一种重写父View事件派发的方法,根据用户点击时间判断是否将事件派发给子View。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

工作中想要实现这么一个效果:
这里写图片描述
如图中,当child有一个click事件,parent有一个longclick事件,当长按child的时候能够触发parent的longclick。

遇到的问题:
当child设置click事件时,长按child不会触发parent的longclick事件。

解决方案
1.当时我的解决方案是为child设置一个和parent一样的longclick事件,这样能够解决该问题,但是实际应用中,parent不止一个child,会有3个以上的child,那么将要为所有的child设置longclick事件,并且写出的代码不美观。

2.第二种解决方式能否让代码自动识别你的意图,是想要触发parent的longclick还是child的click事件。

接下来说明一下第二种解决方式。

对于android事件的派发机制这里不多说,网上能够找到很多的相关博客。在view的onTouchEvent方法中会看到这么一段代码

 public boolean onTouchEvent(MotionEvent event) {
    ……
    if (((viewFlags & CLICKABLE) == CLICKABLE ||
         (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
        ……
        return true;
    }
    return false;
}

其中的判断语句能够很明显的看出来,不论view设置了click还是longclick事件,该view都会消费该点击事件,否则才会把事件再交由父布局的onTouchEvent。需求中,child消费了click事件,所以parent的longclick自然不会被触发。

这里我给出的解决方案是重写parent的dispatchevent方法,根据判断用户点击时间的长短来判断是否将事件派发给child。

public class MyRelativeLayout extends RelativeLayout {
    public final static String TAG = "ClickTestActivity";
    Activity mActivity;
    int mTouchSlop; //最短滑动距离
    public MyRelativeLayout(Context context) {
        super(context);
        init(context);
    }

    public MyRelativeLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context);
    }

    public MyRelativeLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context);
    }

    private void init(Context context) {
        mActivity = (Activity) context;
        mTouchSlop = ViewConfiguration.get(mActivity).getScaledTouchSlop();
    }

    private boolean isLongClick = false; //是否是长点击事件
    private boolean isRelease = false; //是否已经释放
    private static int LONG_CLICK_TIME = 600;

    private LongClickListener longClickListener;
    //自定义长点击事件接口
    public interface LongClickListener {
        void OnLongClick();
    }

    public void setLongClickListener(LongClickListener l) {
        this.longClickListener = l;
    }

    private Runnable countDownRunnable = new Runnable() {
        @Override
        public void run() {
            isLongClick = true;
            //当用户在LONG_CLICK_TIME时间内没有做抬起滑动等取消动作,则触发longclick事件
            if(!isRelease) {
                return;
            }
            isRelease = true;
            mActivity.runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    if(longClickListener != null) {
                        longClickListener.OnLongClick();
                    }
                }
            });
        }
    };

    //记录按下时的坐标
    int downX = 0;
    int downY = 0;
    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                downX = (int) event.getX();
                downY = (int) event.getY();
                isRelease = false;
                isLongClick = false;
                //延迟LONG_CLICK_TIME毫秒的时间,触发长点击事件
                postDelayed(countDownRunnable, LONG_CLICK_TIME);
                break;
            case MotionEvent.ACTION_MOVE:
           //当横移或纵移的长度大于系统规定的滑动最短距离时,则视为用户取消了longclick事件
                if(Math.abs(event.getX() - downX) < mTouchSlop || Math.abs(event.getY() - downY) < mTouchSlop || isRelease) {
                    break;
                }
            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_CANCEL:
            case MotionEvent.ACTION_OUTSIDE:
                isRelease = true;
                if(isLongClick) {
                //当已经是longclick事件时,parent则拦截该事件,child不会再收到该事件
                    return true;
                }
                break;
        }

        boolean isDispatch = super.dispatchTouchEvent(event);
        if(event.getAction() == MotionEvent.ACTION_DOWN && !isDispatch) {
   //当down事件返回false时 不触发up事件 所以返回true强制触发UP事件,否则会出现click父布局出现longclick的效果
            return true;
        }
        return isDispatch;
    }
}

具体的实现方案就是这样,接下来把所有代码贴上:

click_layout.xml

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

    <com.example.lqy.testagain.view.MyRelativeLayout
        android:id="@+id/main_layout"
        android:layout_width="400dp"
        android:layout_height="400dp"
        android:layout_centerInParent="true"
        android:background="@color/abc_search_url_text_normal">
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="parent"
            />

        <TextView
            android:id="@+id/tx"
            android:layout_width="200dp"
            android:layout_height="200dp"
            android:layout_centerInParent="true"
            android:background="@android:color/black"
            android:gravity="center"
            android:text="child"
            android:textColor="@android:color/white"
            android:textSize="16sp" />
    </com.example.lqy.testagain.view.MyRelativeLayout>

</RelativeLayout>

ClickTestActivity .java

public class ClickTestActivity extends Activity{
    public static final String TAG = "ClickTestActivity";
    MyRelativeLayout mainlayout;
    TextView textView;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.click_layout);
        initView();
    }

    private void initView() {

        mainlayout = (MyRelativeLayout) findViewById(R.id.main_layout);
        textView = (TextView) findViewById(R.id.tx);
        mainlayout.setLongClickListener(new MyRelativeLayout.LongClickListener() {
            @Override
            public void OnLongClick() {
                Log.d(TAG, "parent long click");
            }
        });

        textView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Log.d(TAG, "child click");
            }
        });

    }
}
### Android 容器 Click 事件处理 在Android开发中,容器的点击事件处理涉及到事件分发机制的理解。当触摸事件发生时,`dispatchTouchEvent()` 方法会在视图层次结构中传播这些事件。对于容器来说,主要通过 `onInterceptTouchEvent(MotionEvent ev)` 来决定是否拦截并自己处理事件。 如果希望容器响应点击事件而不影响组件,则需注意以下几点: #### 设置项不消耗特定手势 可以通过设置项的 `setClickable(false)` 或者覆盖其 `onTouchEvent()` 方法来确保某些类型的触碰操作不会被项所消费。这样可以使得未被项处理的手势有机会传递给级容器[^1]。 #### 使用自定义逻辑控制事件流向 为了更灵活地管理不同级别的交互行为,在必要情况下可创建自定义ViewGroup,并覆写 `onInterceptTouchEvent()` 。在此方法内编写条件判断语句以识别何时应该允许元素接收输入以及何时应截获它们并将之转交给控件处理。 ```java @Override public boolean onInterceptTouchEvent(MotionEvent event){ switch(event.getAction()){ case MotionEvent.ACTION_DOWN: // 记录按下位置或其他初始化工作 break; case MotionEvent.ACTION_MOVE: // 如果移动距离超过阈值则认为是滑动而非点击 if (isScrollingGesture()) { return true; // 截取滚动动作 } break; default: break; } return super.onInterceptTouchEvent(event); } ``` #### 解决ClickLongClick冲突 针对同时存在短按(click按时的情况,通常建议采用延迟策略解决两者之间的竞争关系。即先注册一个定时器等待一段时间后再执行单击回调;一旦检测到时间按压就取消计时器而改为触发按处理器[^2]。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值