3

I have an application with an image's feed (Instagram style). I'm trying to show a quick image preview using long click over the image.

The main idea is show the image in a dialog when the user made a longclik, then modify zoom when the user move down/up his finger and close the preview when the finger is released.

In orden to archive that I have a onLongClick in the fragment's adapter like this:

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

        @Override
        public boolean onLongClick(View view) {

            listener.onLongClick(item.getId());

            return false;
        }
    });

Then, the fragment implements the listener and call the dialog like this:

@Override
public void onLongClick(long itemId) {

    FullscreenPhotoPreviewDialog dialog = FullscreenPhotoPreviewDialog.newInstance(itemId);

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

Finally, the Dialog implements all the OnTouch logic to let the user make the zoom, without releasing the finger.

@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;
        }
    });
}

The flow of open the dialog with the long click it's working ok. The problem it's with the onTouch. The long click isn't sending the ACTION_DOWN event to the onTouch. So I need to pull up, and the pull down again to start the onTouch.

There is any way to do this?. To automatically call the ACTION_DOWN from the long press?

Thanks and sorry for my english!

Mark Comix
  • 1,878
  • 7
  • 28
  • 44
  • Use Handler to trigger showing dialogs. – Toris Jan 25 '19 at 18:10
  • @Toris what?, why?. Thanks – Mark Comix Jan 25 '19 at 18:18
  • I've re-read the question and updated my answer. Please forget about handler. ACTION_DOWN or ACTION_MOVE will not be sent to the dialog as user doesn't touch the dialog yet. Before releasing his finger from screen after a long press, the first view (itemView) will keep tracking touch events. So, itemView should tweak zoom before user will touch the dialog. – Toris Jan 27 '19 at 04:29

1 Answers1

0

Use itemView onTouch event to detect finger motions before first release after first touch. Once released, next onTouch event is sent to dialog, if user touches the dialog, and ACTION_DOWN for dialog will be called then.

Touch motion will be tracked by the same view while user keeps touching. Entering or crossing another view area doesn't matter.

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) {
    }
}

Additional notes

If a UI or touch related function takes long time, it may block another events. In such cases Handler is useful.

Without handlers

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

With handlers

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() {
}

To understand why events will be blocked, we should know how input events are delivered and processed.

If you are familiar with Windows message pump, please compare ViewRootHandler.handleMessage() with Window Procedure and Looper.loop() with Message Loop.

Looper on Android and message pump on Windows have similar concepts. They deliver queued messages. Input events like touch, click etc. are one of such messages. So, if some function blocks or takes long time to process a message, other messages are left waiting next queue pick up.

Handlers on Android and window procedure on Windows programs are for handling such messages. Decode what is sent and do tasks corresponding to it.

ViewRootImpl.ViewRootHandler.handleMessage on Android

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

Window Procedure in programs for 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;
    ...
    }
}

Looper.loop on Android

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 {
            ...
        }
    }
}

Message Loop in programs for Windows

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

Refs.

What is a message pump? (Windows)

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

Toris
  • 2,348
  • 13
  • 16