父视图无法获取长按事件

时间:2016-02-08 21:27:08

标签: android android-widget android-appwidget

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

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

为了删除小部件,用户应该长按Widget以及我遇到麻烦的地方;一些小部件(例如WhatsApp消息列表,Evernote列表)允许您滚动它们。出于某种原因,如果您滚动,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);
    }
}

当我点击一个小部件时,上面的代码会拦截触摸事件,但它的逻辑似乎旨在拦截(并指示进一步处理)longclicks到小部件。我真正需要的是截取父视图中的longclick。

诀窍似乎在于if (performLongClick()),就我所知,它会向窗口小部件发送一个LongClick事件 ......

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

对于处理Android UI事件的长期(并且看似基本)问题感到抱歉,但从我用Google搜索的内容看来,这似乎是一个非常复杂的主题..

2 个答案:

答案 0 :(得分:1)

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

onInterceptTouchEvent允许父视图针对之前的事件发送给最终发件人。请注意,如果您触摸实际视图,它将不会触发。因此,如果您的布局中包含一些“空格”和一些元素,那么如果触摸布局的“空白区域”,则onInterceptTouchEvent 将无法触发(您将需要布局的{{1在这种情况下)。

因为我们基本上只能跟踪onTouchEventACTION_UPACTION_MOVE事件,所以我们需要时间 ACTION_DOWN对的持续时间决定这是否是长篇大论的事件,所以我做了如下:

ACTION_DOWN / ACTION_UP

每当Android发送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; } 事件时,代码就会开始使用简单的“时间计数器”对象来跟踪其持续时间。计数器的结束时间戳会在ACTION_DOWN个事件中不断更新,当Android发送ACTION_MOVEACTION_UP时,代码会检查最终的持续时间。如果它超过ACTION_CANCEL(默认值= 500毫秒),则会触发操作。

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

我用几个小部件(大,小,可配置,有和没有滚动视图等等)测试它,我没有发现故障。

再次,不确定这是一个优雅的或“安全的”解决方案,但它解决了我的问题。

希望这有帮助!

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

答案 1 :(得分:0)

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