从片段中的onLongClick触发对话框中的onTouch

时间:2019-01-25 18:02:54

标签: android ontouchlistener onlongclicklistener

我有一个带有图片供稿的应用程序(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");
}

最后,对话框实现了所有的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?

谢谢你,对不起我的英语!

1 个答案:

答案 0 :(得分:0)

使用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?(Android)