【问题标题】:Parent view doesn't get longpress event父视图没有得到长按事件
【发布时间】:2016-02-08 21:27:08
【问题描述】:

我正在尝试编写一个类似启动器的应用程序,可以将小部件添加到其屏幕。

我正在使用 Leonardo Fischer 的教程 (http://leonardofischer.com/hosting-android-widgets-my-appwidgethost-tutorial/),这很棒。

为了删除小部件,用户应该长按小部件,这就是我遇到麻烦的地方;一些小部件(例如 WhatsApp 消息列表、印象笔记列表)允许您滚动它们。出于某种原因,如果您滚动,Android 会触发一个 LongClick 事件,该事件会错误地删除小部件...

我的代码: (创建小部件并设置 LongClickListener)

public void createWidget(Intent data) {
    Bundle extras = data.getExtras();
    int appWidgetId = extras.getInt(AppWidgetManager.EXTRA_APPWIDGET_ID, -1);
    AppWidgetProviderInfo appWidgetInfo = mAppWidgetManager.getAppWidgetInfo(appWidgetId);

    final LauncherAppWidgetHostView hostView = (LauncherAppWidgetHostView) mAppWidgetHost.createView(this, appWidgetId, appWidgetInfo);
    hostView.setAppWidget(appWidgetId, appWidgetInfo);

    // relative layout
    //RelativeLayout.LayoutParams lp = new RelativeLayout()
    //mainlayout.addView(hostView, lp);
    mainlayout.addView(hostView);

    // [COMMENTED OUT] hostView.setOnLongClickListener(new AppWidgetLongClickListener(hostView));

}

更新

无数小时后,我想我部分了解发生了什么,但我仍然无法获得正确的行为。

根据http://balpha.de/2013/07/android-development-what-i-wish-i-had-known-earlier/,您需要在父容器中实现onInterceptTouchEvent(在我的情况下为mainlayout)以在事件到达子容器(我的小部件案例)。

所以我搜索了以下代码并尝试适应我的需求:

@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
    // Consume any touch events for ourselves after longpress is triggered
    //Log.i(TAG,"OnIntercept: "+ev.toString());
    if (mHasPerformedLongPress) {
        Log.i(TAG,"Longpress OK!: "+ev.toString());
        mHasPerformedLongPress = false;
        return true;
    }

    // Watch for longpress events at this level to make sure
    // users can always pick up this widget
    switch (ev.getAction()) {
        case MotionEvent.ACTION_DOWN: {
            postCheckForLongClick();
            break;
        }

        case MotionEvent.ACTION_UP:
        case MotionEvent.ACTION_CANCEL:
            mHasPerformedLongPress = false;
            if (mPendingCheckForLongPress != null) {
                removeCallbacks(mPendingCheckForLongPress);
            }
            break;
    }

    // Otherwise continue letting touch events fall through to children
    return false;
}

class CheckForLongPress implements Runnable {
    private int mOriginalWindowAttachCount;

    public void run() {
        Log.i(TAG,"Inside RUN");
        if (getParent()!= null) {
            Log.i(TAG,"getParent:"+getParent().toString());
        }
        if ((getParent() != null) && hasWindowFocus()
                && (mOriginalWindowAttachCount == getWindowAttachCount())
                && !mHasPerformedLongPress) {
            if (performLongClick()) { // <-- DOESN'T WORK :(
                mHasPerformedLongPress = true;
            }
        }
    }

    public void rememberWindowAttachCount() {
        mOriginalWindowAttachCount = getWindowAttachCount();
    }
}

private void postCheckForLongClick() {
    mHasPerformedLongPress = false;

    if (mPendingCheckForLongPress == null) {
        mPendingCheckForLongPress = new CheckForLongPress();
    }
    mPendingCheckForLongPress.rememberWindowAttachCount();
    postDelayed(mPendingCheckForLongPress, ViewConfiguration.getLongPressTimeout());
}

@Override
public void cancelLongPress() {
    super.cancelLongPress();

    mHasPerformedLongPress = false;
    if (mPendingCheckForLongPress != null) {
        removeCallbacks(mPendingCheckForLongPress);
    }
}

当我单击小部件时,上面的代码确实拦截了触摸事件,但它的逻辑似乎旨在拦截(并直接用于进一步处理)对小部件的长按。我真正需要的是拦截父视图中的长按。

诀窍似乎在于 if (performLongClick()),据我所知,它会触发一个 LongClick 事件到小部件...

...所以我想我现在的问题是如何在父视图中跟踪长按。

对于关于处理 Android UI 事件的冗长(而且看似基本的)问题很抱歉,但从我在 Google 上搜索的内容来看,这似乎是一个非常复杂的话题..

【问题讨论】:

    标签: android android-widget android-appwidget


    【解决方案1】:

    这样就完成了……!我不确定这是否是一个优雅的解决方案,但它确实有效。

    onInterceptTouchEvent 允许父视图在事件发送给最终发送者之前对其进行操作。请注意,如果您触摸实际视图,它不会触发。因此,如果您有一个带有一些“空白空间”和一些元素的布局,onInterceptTouchEvent 不会触发如果您触摸布局的“空白空间”(您将需要布局的 onTouchEvent 在此案例)。

    因为我们基本上只能跟踪 ACTION_UPACTION_MOVEACTION_DOWN 事件,所以我们需要计时 ACTION_DOWN / ACTION_UPpair 事件的持续时间来确定这是否是长按与否,所以我所做的如下:

    public class time_counter {
        private long begin_time;
        private long end_time;
    
    
        public time_counter(long i, long f) {
            this.begin_time = i;
            this.end_time = f;
        }
    
        public long getDuration() {
            return (this.end_time - this.begin_time);
        }
    
    }
    
    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
    
        // Consume any touch events for ourselves after longpress is triggered
    
        // Watch for longpress events at this level to make sure
        // users can always pick up this widget
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN: {
                cnt = new time_counter(ev.getEventTime(), (long)0);
                break;
            }
    
            case MotionEvent.ACTION_MOVE: {
                if (cnt != null) {
                    cnt.end_time = ev.getEventTime();
                }
                break;
            }
            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_CANCEL:
                if (cnt != null) {
                    cnt.end_time = ev.getEventTime();
                }
                Log.i(TAG, "DURATION: " + cnt.getDuration());
                if (cnt.getDuration() > ViewConfiguration.getLongPressTimeout()) {
                    Log.i(TAG, "it's a longpress: " + this.toString());
                    if (processClick) {
                        processClick = false;
                        this.doRemoveWidget();
                    }
                    cancelLongPress();
                    return true;
                }
                break;
        }
    
        // Otherwise continue letting touch events fall through to children
        return false;
    }
    

    每当 Android 发送 ACTION_DOWN 事件时,代码就会开始使用简单的“时间计数器”对象跟踪其持续时间。计数器的结束时间戳在ACTION_MOVE 事件中不断更新,当Android 发送ACTION_UPACTION_CANCEL 时,代码会检查最终持续时间。如果超过ViewConfiguration.getLongPressTimeout()(默认 = 500 毫秒),则会触发操作。

    请注意,在我的例子中,我需要一个布尔变量来防止多个事件触发,因为我想使用 LongClick 来删除一个小部件。几乎总是会发生的第二次意外触发将触发空指针异常,因为小部件已被删除。

    我用几个小部件(大的、小的、可配置的、有和没有滚动视图等)对其进行了测试,但没有发现故障。

    同样,不确定这是优雅的还是“android-wise”的解决方案,但它解决了我的问题。

    希望这会有所帮助!

    参考: 如果您需要关于触摸事件的优秀文章,请查看http://balpha.de/2013/07/android-development-what-i-wish-i-had-known-earlier/。它给了我正确的“思维框架”来解决我的问题。

    【讨论】:

      【解决方案2】:

      如果您确实看到启动滚动的事件之后是长按事件,您可以通过在事件处理类中设置一个标志来跟踪滚动开始和结束的时间,并选择忽略如果正在进行滚动,则长按。

      【讨论】:

      • 感谢您的想法,但正如您在我的代码 sn-p 中看到的那样,我正在实现 setOnLongClickListener,据我所知,AppWidgetHostView 没有可跟踪的接口滚动事件。你能指出我正确的方向吗?
      • 从你的问题描述来看,听起来你有一个滚动小部件。如果您没有滚动小部件,则主屏幕可能会将慢速拖动解释为长按,您无法更改它。
      • 你是对的; WhatsApp 未读列表和 Evernote 列表等小部件是滚动小部件,每当我滚动它们时,似乎会触发 LongClick。我检查了 Google Launcher 源代码,它使用与我的几乎相同的逻辑(除了它们处理多个屏幕,而我可怜的人的 Launcher 是“仅一个屏幕”......)并且这种类型的小部件不会生成这种奇怪的行为......我确定我错过了一些东西,但我无法弄清楚。
      • 您可能需要进入组成小部件的 RemoteViews 并查看其中一个是否希望能够滚动,然后假设整个东西可以滚动?听起来很难。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-12-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多