Запуск onTouch в диалоге из onLongClick во фрагменте

У меня есть приложение с фидом изображений (в стиле 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 при длительном нажатии?

Спасибо и извините за мой английский!


person Mark Comix    schedule 25.01.2019    source источник
comment
Используйте Handler для запуска показа диалогов.   -  person Toris    schedule 25.01.2019
comment
@Toris что ?, почему ?. Спасибо   -  person Mark Comix    schedule 25.01.2019
comment
Я перечитал вопрос и обновил свой ответ. Забудьте про обработчик. ACTION_DOWN или ACTION_MOVE не будут отправлены в диалоговое окно, поскольку пользователь еще не касается диалогового окна. Прежде чем убрать палец с экрана после длительного нажатия, первое представление (itemView) будет отслеживать события касания. Итак, itemView должен настроить масштаб, прежде чем пользователь коснется диалогового окна.   -  person Toris    schedule 27.01.2019


Ответы (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) {
    }
}

Дополнительные замечания

Если функция, связанная с пользовательским интерфейсом или сенсорным экраном, занимает много времени, она может блокировать другие события. В таких случаях полезен 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 Procedure и Looper.loop () с Цикл сообщений.

Looper на Android и помпа сообщений в Windows имеют схожие концепции. Они доставляют сообщения в очереди. События ввода, такие как прикосновение, щелчок и т. Д., Являются одним из таких сообщений. Таким образом, если какая-то функция блокирует или занимает много времени для обработки сообщения, другие сообщения остаются в ожидании следующего захвата очереди.

Обработчики на Android и оконные процедуры в программах Windows предназначены для обработки таких сообщений. Расшифруйте то, что отправлено, и выполните соответствующие задачи.

ViewRootImpl.ViewRootHandler.handleMessage на Android

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

Looper.loop на 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 {
            ...
        }
    }
}

Цикл сообщений в программах для Windows

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

Ссылка

Что такое насос сообщений? (Windows)

Какова цель Looper и как его использовать это? (Android)

person Toris    schedule 25.01.2019