【问题标题】:Trigger onTouch in a Dialog from onLongClick in a fragment从片段中的 onLongClick 在对话框中触发 onTouch
【发布时间】:2019-01-25 18:02:53
【问题描述】:

我有一个带有图片提要(Instagram 风格)的应用程序。 我正在尝试通过长按图像来显示快速图像预览。

主要思想是当用户长按时在对话框中显示图像,然后在用户向下/向上移动手指时修改缩放并在松开手指时关闭预览。

为了存档,我在片段的适配器中有一个 onLongClick,如下所示:

holder.itemView.setOnLongClickListener(new View.OnLongClickListener() {

        @Override
        public boolean onLongClick(View view) {

            listener.onLongClick(item.getId());

            return false;
        }
    });

然后,片段实现监听器并像这样调用对话框:

@Override
public void onLongClick(long itemId) {

    FullscreenPhotoPreviewDialog dialog = FullscreenPhotoPreviewDialog.newInstance(itemId);

    dialog.show(getActivity().getSupportFragmentManager(), "FullscreenPhotoPreviewDialog");
}

最后,Dialog 实现了所有 OnTouch 逻辑,让用户在不松开手指的情况下进行缩放。

@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {

    super.onViewCreated(view, savedInstanceState);

    view.setOnTouchListener(new View.OnTouchListener() {

        @Override
        public boolean onTouch(View view, MotionEvent motionEvent) {

            switch(motionEvent.getAction()) {

                case MotionEvent.ACTION_DOWN:

                    break;

                case MotionEvent.ACTION_MOVE:

                    float scale = 0;

                    if (motionEvent.getHistorySize() > 0)
                        scale = ((motionEvent.getY() > motionEvent.getHistoricalY(motionEvent.getHistorySize() - 1)) ? 0.1f : -0.1f);

                    FullscreenPhotoPreviewDialog.this.applyScale(scale);

                    break;

                case MotionEvent.ACTION_UP:

                    FullscreenPhotoPreviewDialog.this.dismiss();

                    break;
            }

            return true;
        }
    });
}

长按打开对话框的流程正常。 onTouch 的问题。长按不会将 ACTION_DOWN 事件发送到 onTouch。所以我需要往上拉,然后再往下拉来启动onTouch。

有没有办法做到这一点?长按自动调用ACTION_DOWN?

感谢和抱歉我的英语!

【问题讨论】:

  • 使用 Handler 触发显示对话框。
  • @Toris 什么?,为什么?谢谢
  • 我重新阅读了这个问题并更新了我的答案。请忘记处理程序。 ACTION_DOWN 或 ACTION_MOVE 不会被发送到对话框,因为用户还没有触摸对话框。在长按后从屏幕上松开手指之前,第一个视图(itemView)将继续跟踪触摸事件。因此,itemView 应该在用户触摸对话框之前调整缩放。

标签: android ontouchlistener onlongclicklistener


【解决方案1】:

使用 itemView onTouch 事件在第一次触摸后第一次释放前检测手指运动。释放后,下一个 onTouch 事件被发送到对话框,如果用户触摸对话框,则会调用对话框的 ACTION_DOWN。

当用户持续触摸时,同一视图将跟踪触摸动作。进入或穿过另一个视图区域并不重要。

class SomeClass {
    private int touchCount;
    private ItemFunctions itemFunctions;
    private FullscreenPhotoPreviewDialog dialog;

    void someFunction(Holder holder) {
        holder.itemView.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                // This block is called after first touch and before releasing his finger
                int action = event.getAction();
                switch (action) {
                case MotionEvent.ACTION_DOWN: {
                    // First touch down event here.
                    // Second one depends on where user will touch again.
                    if (0 >= touchCount) {
                        itemFunctions = new ItemFunctions(item.getId());
                    }
                    touchCount++;
                    break;
                }
                case MotionEvent.ACTION_MOVE: {
                    // First touch move events here
                    float scale = 0;
                    ...
                    if (itemFunctions.displayed) {
                        itemFunctions.applyScale(scale);
                    }
                    break;
                }
                case MotionEvent.ACTION_UP: {
                    // First up event here.
                    // Second one depends on where user will touch again.
                    break;
                }
                }

                // This should be false to get long touch event
                return false;
            }
        });

        holder.itemView.setOnLongClickListener(new View.OnLongClickListener() {
            @Override
            public boolean onLongClick(View view) {
                // dialog may need itemFunctions to manipulate zoom, dismiss state callback, etc.
                dialog = FullscreenPhotoPreviewDialog.newInstance(itemFunctions.itemId);
                dialog.show(getActivity().getSupportFragmentManager(), "FullscreenPhotoPreviewDialog");
                itemFunctions.displayed = true;
                return false;
            }
        });
    }
}

class ItemFunctions {
    long itemId;
    boolean displayed;

    ItemFunctions(long itemId) {
        this.itemId = itemId;
    }

    void applyScale(float scale) {
    }
}

补充说明

如果一个 UI 或触摸相关的功能需要很长时间,它可能会阻塞另一个事件。在这种情况下,Handler 很有用。

没有处理程序

void funcA() {
    // funcB wiil be executed inside funcA.
    funcB();
}

带有处理程序

void funcA() {
    // Send a request to call funcB after this point.
    // Then looper will fetch the request from queue.
    // And funcB will be called then.
    // handler.post() -> sendMessageDelayed(getPostMessage(), ...)
    // getPostMessage() -> Message m = Message.obtain(); ... return m;
    handler.post(new Runnable() {
        @Override
        public void run() {
            funcB();
        }
    });
}

void funcB() {
}

要了解事件会被阻止的原因,我们应该知道输入事件是如何传递和处理的。

如果您熟悉 Windows 消息泵,请将ViewRootHandler.handleMessage()Window ProcedureLooper.loop()Message Loop 进行比较。

Android 上的 Looper 和 Windows 上的消息泵具有相似的概念。他们传递排队的消息。触摸、点击等输入事件就是这样的消息之一。因此,如果某些功能阻塞或需要很长时间来处理一条消息,则其他消息将等待下一个队列拾取。

Android 上的处理程序和 Windows 程序上的窗口过程用于处理此类消息。解码发送的内容并执行相应的任务。

Android 上的 ViewRootImpl.ViewRootHandler.handleMessage

public void handleMessage(Message msg) {
    switch (msg.what) {
        ...
        case MSG_PROCESS_INPUT_EVENTS:
            mProcessInputEventsScheduled = false;
            doProcessInputEvents();
            break;
        ...
    }
}     

Windows 程序中的窗口过程

LRESULT CALLBACK MainWndProc(
    HWND hwnd,        // handle to window
    UINT uMsg,        // message identifier
    WPARAM wParam,    // first message parameter
    LPARAM lParam)    // second message parameter
{ 

    switch (uMsg) 
    {
    ...
        case WM_MOUSEMOVE: 
            return 0;
    ...
    }
}

Android 上的 Looper.loop

public static void loop() {
    final Looper me = myLooper();
    ...
    final MessageQueue queue = me.mQueue;
    ...
    for (;;) {
        Message msg = queue.next(); // might block
        ...
        try {
            msg.target.dispatchMessage(msg);
            ...
        } finally {
            ...
        }
    }
}

Windows 程序中的消息循环

while( (bRet = GetMessage( &msg, NULL, 0, 0 )) != 0)
{ 
    if (bRet == -1)
    {
        // handle the error and possibly exit
    }
    else
    {
        TranslateMessage(&msg); 
        DispatchMessage(&msg); 
    }
}

参考。

What is a message pump? (Windows)

What is the purpose of Looper and how to use it?(安卓)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-05-05
    • 2012-06-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-04-28
    • 2022-01-21
    相关资源
    最近更新 更多