Статьи

Android Drag and Drop Tutorial

Этот пост будет посвящен реализации Drag and Drop в приложении для Android.

(Я в настоящее время использую версию 4.0 SDK, но я изначально написал код в этой серии с версией 3.1.)

Зачем использовать Drag and Drop

Когда я начал работать над своим Android-приложением, которое включает в себя перемещение шахматной фигуры по шахматной доске, я решил использовать функцию перетаскивания по двум причинам:

  1. Я чувствовал, что перетаскивание максимально приблизится к опыту перемещения фигуры по доске.
  2. Используя перетаскивание, будет намного легче определить действительные ходы, опуская шахматную фигуру на отдельный объект (в данном случае квадрат на шахматной доске), а не отслеживая координаты x, y шахматной фигуры. Я объясню больше о том, как я реализовал это в последней части этой серии

Мои цели перетаскивания

У меня было 3 конкретные цели, когда речь шла о реализации перетаскивания в отношении перемещения шахматной фигуры на доске.

  1. Когда пользователь нажимает и начинает перетаскивать, шахматная фигура будет заменена гораздо более легкой версией оригинала, делая очевидным, что она перемещается.
  2. Я хотел ограничить область, куда можно перетащить шахматную фигуру, а точнее, я не хочу, чтобы игроки перетаскивали фигуру с доски
  3. Если игрок попытается сбросить игровую фигуру, что приведет к незаконному ходу, я бы хотел, чтобы игровая фигура «вернулась» в исходное положение.

Здесь я расскажу о своей первой цели — перетаскивать в моем приложении.

Изменение ImageView на Drag Start

Когда я начал работать, я понял, что функциональность перетаскивания не поддерживает то, что я хотел прямо из коробки, но, конечно, после небольшой работы в API было достаточно, чтобы дать мне то, что я искал. Когда пользователь нажимает на шахматную фигуру (которая является ImageView), чтобы начать перетаскивание, вот шаги, которые я делаю:

  1. Создайте объект DragShadowBuilder.
  2. Вызовите метод startDrag.
  3. Скройте исходный ImageView (шахматная фигура), вызвав setVisibility и передав в View.INVISIBLE, оставив видимым только объект DragShadowBuilder, ясно указывающий, что перетаскивание началось.

Все вышеперечисленные шаги выполняются в объекте OnTouchListner объекта ImageView в реализованном методе onTouch. Вот код:

01
02
03
04
05
06
07
08
09
10
11
12
@Override
    public boolean onTouch(View view, MotionEvent motionEvent) {
        if (motionEvent.getAction() == MotionEvent.ACTION_DOWN) {
            ClipData clipData = ClipData.newPlainText("", "");
            View.DragShadowBuilder dsb = new View.DragShadowBuilder(view);
            view.startDrag(clipData, dsb, view, 0);
            view.setVisibility(View.INVISIBLE);
            return true;
        } else {
            return false;
        }
    }

Изменено изображение шахматной фигуры при начале перетаскивания!

До сих пор я рассмотрел, как скрыть ImageView и только что показал объект DragShadowBuilder в начале перетаскивания. Теперь я собираюсь сосредоточиться на ограничении области перетаскивания.

Попытка ограничить область перетаскивания

Поскольку мое приложение предполагает перемещение шахматной фигуры по доске, я хотел ограничить область перетаскивания самой игровой доской. Моя мотивация заключается в том, что ImageView скрывается в начале перетаскивания. Если ImageView упал с доски, он никогда не появится снова. Это оставило бы мое заявление в несогласованном состоянии. Поэтому я потратил некоторое время на просмотр API Android и на поиски, как я могу ограничить область перетаскивания. Я ничего не придумал. Затем я сделал шаг назад и подумал, какую проблему я пытаюсь решить? Это пользователь перетаскивает ImageView с доски? Нет, настоящая проблема в том, что действие перетаскивания никогда не обрабатывается, поэтому у меня никогда не будет возможности снова сделать ImageView видимым. Поэтому я пересмотрел свою проблему, чтобы определить, является ли падение допустимым или нет.

Определение допустимых капель

Чтобы выяснить, как определить допустимые отбрасывания, я вернулся к документации разработчика Android. Я заточил в обработке событий торможения . Вот ключевые моменты, которые я нашел:

  • Когда перетаскивание заканчивается, событие ACTION_DRAG_ENDED отправляется всем DragListeners.
  • DragListener может определить дополнительную информацию об операциях перетаскивания, вызвав метод getResult объекта DragEvent.
  • Если DragListener вернул true из события ACTION_DROP, вызов getResult вернет true, иначе false.

Теперь мне совершенно ясно, что я могу сделать, когда / если пользователь перетаскивает шахматную фигуру в непредусмотренной области. Мой класс DragListener уже возвращает true для любого события, которое обрабатывается в методе onDrag. Таким образом, возвращаемое значение false из getResult указывает, что падение произошло где-то за пределами игрового поля. Что мне нужно сделать сейчас:

  1. Когда происходит событие ACTION_DRAG_ENDED, вызовите метод getResult
  2. Если getResult возвращает false, немедленно установите видимость ImageView (используется в начале перетаскивания) обратно на видимость.

Вот код, соответствующие строки выделены:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
@Override
    public boolean onDrag(View view, DragEvent dragEvent) {
        int dragAction = dragEvent.getAction();
        View dragView = (View) dragEvent.getLocalState();
        if (dragAction == DragEvent.ACTION_DRAG_EXITED) {
            containsDragable = false;
        } else if (dragAction == DragEvent.ACTION_DRAG_ENTERED) {
            containsDragable = true;
        } else if (dragAction == DragEvent.ACTION_DRAG_ENDED) {
            if (dropEventNotHandled(dragEvent)) {
                dragView.setVisibility(View.VISIBLE);
            }
        } else if (dragAction == DragEvent.ACTION_DROP && containsDragable) {
            checkForValidMove((ChessBoardSquareLayoutView) view, dragView);
            dragView.setVisibility(View.VISIBLE);
        }
        return true;
    }
 
    private boolean dropEventNotHandled(DragEvent dragEvent) {
        return !dragEvent.getResult();
    }

Теперь пользователи могут перетаскивать ImageView куда угодно, и игра не окажется в несовместимом состоянии. Моя вторая цель достигнута.

В последней части рассказывается о перемещении игровой фигуры в результате действительного хода или о том, что эта игровая фигура «вернулась» в исходное положение, когда был сделан неверный ход.

Информация о приложении

Для этого поста может быть полезна некоторая справочная информация о моем заявлении. Игра включает в себя перемещение одной шахматной фигуры (рыцаря) вокруг шахматной доски. Шахматная доска является TableLayout, и каждый квадрат является подклассом LinearLayout и имеет набор OnDragListener. Кроме того, каждый OnDragListener имеет ссылку на объект-посредник, который:

  1. Управляет связью между объектами.
  2. Отслеживает текущую клетку (клетку посадки последнего действующего хода).

Определение допустимых ходов

Когда пользователь начинает перетаскивать ImageView и проходит через данный квадрат, возможны следующие действия:

  • Используйте событие ACTION_DRAG_ENTERED, чтобы установить для переменной экземпляра ‘containsDraggable’ значение true.
  • Используйте событие ACTION_DRAG_EXITED, чтобы установить для «containsDraggable» значение false.
  • Когда событие ACTION_DROP происходит в квадрате, посреднику задается вопрос, является ли попытка перемещения допустимой, путем проверки всех возможных допустимых перемещений (предварительно рассчитанных) из «текущего» квадрата.

Вот код с выделенными соответствующими разделами:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
@Override
    public boolean onDrag(View view, DragEvent dragEvent) {
        int dragAction = dragEvent.getAction();
        View dragView = (View) dragEvent.getLocalState();
        if (dragAction == DragEvent.ACTION_DRAG_EXITED) {
            containsDragable = false;
        } else if (dragAction == DragEvent.ACTION_DRAG_ENTERED) {
            containsDragable = true;
        } else if (dragAction == DragEvent.ACTION_DRAG_ENDED) {
            if (dropEventNotHandled(dragEvent)) {
                dragView.setVisibility(View.VISIBLE);
            }
        } else if (dragAction == DragEvent.ACTION_DROP && containsDragable) {
            checkForValidMove((ChessBoardSquareLayoutView) view, dragView);
            dragView.setVisibility(View.VISIBLE);
        }
        return true;
    }

Как видно из вышеизложенного, независимо от того, является ли перемещение действительным или нет, ImageView становится видимым. Сделав ImageView видимым сразу после удаления, мои цели плавного перемещения или «возврата» к исходному положению достигнуты.

Перемещение ImageView

Ранее я упоминал, что каждый квадрат является подклассом LayoutView. Это было сделано для облегчения перемещения ImageView от квадрата к квадрату. Вот подробности из метода checkForValidMove (строка 14 в примере кода выше), которые показывают, как выполняется перемещение ImageView.

01
02
03
04
05
06
07
08
09
10
private void checkForValidMove(ChessBoardSquareLayoutView view, View dragView) {
        if (mediator.isValidMove(view)) {
            ViewGroup owner = (ViewGroup) dragView.getParent();
            owner.removeView(dragView);
            view.addView(dragView);
            view.setGravity(Gravity.CENTER);
            view.showAsLanded();
            mediator.handleMove(view);
        }
    }

Я надеюсь, что читатель найдет эту статью полезной в своих собственных усилиях по разработке Android.

Ссылка: Android Drag and Drop (Часть 1) , Android Drag and Drop (Часть 2) , Android Drag and Drop (Часть 3) от нашего партнера по JCG Билла Бецеката из блога « Случайные мысли о кодировании» .

Статьи по Теме :