步骤 2 - 实现拖放功能

这一步中,您将实现拖放功能。

创建拖动功能

在本节中,您首先使用 Kanzi Engine API 来实例化您在教程的上一步中创建的 Drag Item 预设模板。然后,您定义当用户开始拖放手势并拖动按钮时的行为。最后,您为导航栏中的每个按钮创建并配置一个拖放操作器。

要创建拖动功能:

  1. 在 Visual Studio 中,在 drag_and_drop.cpp 文件的 Application::onProjectLoaded 函数中实例化用于可视化用户正在拖动的按钮的 Drag Item 预设:

        void onProjectLoaded() override
        {
            ...
    
            // Get the reference to the Drag Item prefab.
            ResourceManager* resourceManager = getDomain()->getResourceManager();
            PrefabTemplateSharedPtr dragItemPrefab = resourceManager->acquireResource<PrefabTemplate>("kzb://drag_and_drop/Prefabs/Drag Item");
    
            // Instantiate the Drag Item prefab.
            m_dragItem = dragItemPrefab->instantiate<Node2D>("Drag Item");
    
            // Get the RootPage node using its alias.
            Node2DSharedPtr rootPage = screen->lookupNode<Node2D>("#RootPage");
    
            // Add to the RootPage node the instance of the Drag Item prefab that you created.
            rootPage->addChild(m_dragItem);
    
            // Disable the Visible property of the Drag Item prefab instance.
            // You hide the Drag Item when the user is not dragging it.
            m_dragItem->setVisible(false);
        }
    
    private:
        ...
    
        // Define a member variable for the instantiated Drag Item.
        Node2DSharedPtr m_dragItem;
    
  2. 在 DragAndDrop 类的私有部分中创建一个函数,该函数移动 Drag Item 预设实例:

    private:
    
        ...
    
        // Update the position of the Drag Item.
        void updateDragAndDrop(Vector2 dragPosition, Matrix3x3 dragWorldTransform)
        {
            // Calculate the local drag anchor that is the top-left corner of the button that the user is dragging.
            Vector2 localDragAnchor = dragPosition - m_dragGrabOffset;
    
            // Restrict the movement of the Drag Item to the x axis.
            localDragAnchor.setY(0.0f);
    
            // Calculate the global drag anchor.
            Vector2 globalDragAnchor = dragWorldTransform * localDragAnchor;
    
            // The structure that you use to describe the Render Transformation property of the Drag Item.
            SRTValue2D transform;
            // Set the Render Transformation property Translation property field to the global drag anchor.
            transform.setTranslation(globalDragAnchor);
    
            // Move the Drag Item for the amount of the dragged distance.
            m_dragItem->setRenderTransformation(transform);
    
            // Set the icons of the buttons.
            updateItems();
        }
    
        ...
    
        // Define a member variable for the offset from the top-left corner of the button where the user pressed down or clicked it.
        Vector2 m_dragGrabOffset;
    };
    
  3. 为拖动消息添加处理程序:

    1. 要定义用户开始拖动按钮时的行为,在 DragAndDrop 类的私有部分中定义 DragAndDropManipulator::StartedMessage 消息的处理程序:

      private:
      
          ...
      
          // Define the handler for the DragAndDropManipulator::StartedMessage message from 2D nodes
          // that have an input manipulator which generates drag-and-drop messages.
          // This prepares the 2D node for dragging.
          void onDragStarted(DragAndDropManipulator::StartedMessageArguments& messageArguments)
          {
              // Get from the message arguments the button that the user starts to drag.
              Node2DSharedPtr dragSourceItem = dynamic_pointer_cast<Node2D>(messageArguments.getSource());
      
              // Get the size of the button that the user starts to drag.
              Vector2 dragSourceItemSize = dragSourceItem->getActualSize();
      
              // Set the size of the Drag Item to be the same as the size of that button.
              m_dragItem->setSize(dragSourceItemSize.getX(), dragSourceItemSize.getY());
      
              // Move the Drag Item to the correct position.
              updateDragAndDrop(messageArguments.getPoint(), dragSourceItem->getWorldTransform());
      
              // Make the Drag Item visible.
              m_dragItem->setVisible(true);
          }
      
          ...
      
    2. 要定义用户拖动按钮时的行为,在 onDragStarted 函数之后定义 DragAndDropManipulator::MovedMessage 消息的处理程序:

      // Define the handler for the DragAndDropManipulator::MovedMessage message from 2D nodes
      // that have an input manipulator which generates drag-and-drop messages.
      void onDragMoved(DragAndDropManipulator::MovedMessageArguments& messageArguments)
      {
          // Get from the message arguments the button that the user is dragging.
          Node2DSharedPtr dragSourceItem = dynamic_pointer_cast<Node2D>(messageArguments.getSource());
      
          // Move the Drag Item and update the icons of the buttons.
          updateDragAndDrop(messageArguments.getPoint(), dragSourceItem->getWorldTransform());
      }
      
  4. 创建并配置各按钮的拖放操纵器:

    1. 在 DragAndDrop 类的私有部分中,添加一个为节点创建和配置拖放操作器的函数:

      // Create and configure a drag-and-drop manipulator for a node.
      void createDragAndDropManipulator(NodeSharedPtr dragSourceItem)
      {
          Domain* domain = getDomain();
      
          // Create an input manipulator that generates drag-and-drop messages.
          DragAndDropManipulatorSharedPtr dragAndDropManipulator = DragAndDropManipulator::create(domain);
      
          // Add the input manipulator to the node.
          dragSourceItem->addInputManipulator(dragAndDropManipulator);
      
          // Set the duration of the long press before the drag-and-drop starts to 200 ms. The default is 500 ms.
          // This is the amount of time the user must press the node before they can start dragging it.
          dragAndDropManipulator->setPressDuration(chrono::milliseconds(200));
      
          // Subscribe to the DragAndDropManipulator::StartedMessage message at the node.
          // The DragAndDropManipulator generates this message when the user presses the node
          // for the duration set by DragAndDropManipulator::setPressDuration.
          dragSourceItem->addMessageHandler(DragAndDropManipulator::StartedMessage, bind(&DragAndDrop::onDragStarted, this, placeholders::_1));
      
          // Subscribe to the DragAndDropManipulator::MovedMessage message at the node.
          // The DragAndDropManipulator generates this message when the pointer moves.
          dragSourceItem->addMessageHandler(DragAndDropManipulator::MovedMessage, bind(&DragAndDrop::onDragMoved, this, placeholders::_1));
      }
      
    2. 在 Application::onProjectLoaded 函数的末尾,为每个按钮调用 createDragAndDropManipulator 函数:

      void onProjectLoaded() override
      {
          ...
      
          // Create a drag-and-drop manipulator for each button.
          // You get the button nodes using their aliases.
          createDragAndDropManipulator(screen->lookupNode<Node>("#Navigation"));
          createDragAndDropManipulator(screen->lookupNode<Node>("#Phone"));
          createDragAndDropManipulator(screen->lookupNode<Node>("#Applications"));
          createDragAndDropManipulator(screen->lookupNode<Node>("#Music"));
          createDragAndDropManipulator(screen->lookupNode<Node>("#Car"));
      }
      
  5. 构建和运行应用程序。

    在应用程序中长按按钮,并在水平方向拖动。

    您拖动 Drag Item 预设的一个实例。Drag Item 的左侧定位在输入指针处,并且 Drag Item 尚未显示图标。在下一节中,您将完成拖动功能。

完成拖动功能

这一节中,您首先设置用户拖动的按钮图标和位置。然后,在用户拖动其中一个按钮时,您重新定位按钮图标。

要完成拖动功能:

  1. 在调用 updateDragAndDrop 函数之前的 onDragStarted 函数中,为 Drag Item 设置图标并正确定位节点:

    void onDragStarted(DragAndDropManipulator::StartedMessageArguments& messageArguments)
        {
            ...
    
            // Get the data context object of the button that the user starts to drag.
            m_draggedDataContext = dynamic_pointer_cast<DataObject>(dragSourceItem->getProperty(DataContext::DataContextProperty));
    
            // Set the Data Context property of the Drag Item to the data context of the button.
            // This way you set the Drag Item to have the same icon as the button that the user starts to drag.
            m_dragItem->setProperty(DataContext::DataContextProperty, m_draggedDataContext);
    
            // Save the point from which the user started dragging the node,
            // relative to the origin (by default the top-left corner) of the node.
            m_dragGrabOffset = messageArguments.getPoint();
    
            ...
        }
    
        ...
    
        // Define a member variable for the data context object of the button that the user drags.
        DataObjectSharedPtr m_draggedDataContext;
    };
    
  2. 在 updateDragAndDrop 函数中,在调用 updateItems() 函数之前,添加代码以在用户拖动其中一个按钮时重新定位所有按钮的图标:

    void updateDragAndDrop(Vector2 dragPosition, Matrix3x3 dragWorldTransform)
    {
        ...
    
        // Get the global pointer position.
        Vector2 globalPointerPosition = dragWorldTransform * dragPosition;
    
        // Convert global coordinates to local coordinates of the Grid Layout 2D node.
        Vector2 hitTestPoint = *m_grid->globalToLocal(globalPointerPosition);
    
        // Get the width of the button.
        float cellWidth = m_grid->getActualColumnSize(0);
    
        // Calculate the index of the button in the Grid Layout 2D node.
        unsigned int cellIndex = 0;
    
        if (hitTestPoint.getX() > 0.0f)
        {
            cellIndex = floatToUint(hitTestPoint.getX() / cellWidth);
            cellIndex = min(cellIndex, static_cast<unsigned int>(m_grid->getChildCount() - 1u));
        }
    
        // Remove the data object from the old position.
        m_rootData->removeChild(*m_draggedDataContext);
    
        // Insert the data object to the new position.
        m_rootData->insertChild(cellIndex, m_draggedDataContext);
    
        ...
    }
    
  3. 在 for 循环中的 updateItems() 函数中添加 if-else 子句,该子句在用户拖动按钮时隐藏按钮的图标:

    void updateItems()
    {
        ...
    
        for (; dataIt != endDataIt; dataIt++, nodeIt++)
        {
            ...
    
            // If the button node is the one that the user is dragging, hide it.
            // You hide the node because you use the Drag Item to visualize the dragging of the node.
            if (m_draggedDataContext && itemData == m_draggedDataContext)
            {
                itemNode->setVisible(false);
            }
            else
            {
                itemNode->setVisible(true);
            }
        }
    }
    

创建放置功能

在前面的部分中,您实现了按钮的拖动。当用户结束拖放手势时,Drag Item 保持可见,并位于用户释放指针的确切位置。在本节中,您将添加代码,使按钮在用户放下时看起来像是落入其位置。

要创建放置功能:

  1. 要定义用户停止拖动节点并释放指针时的行为,在 onDragMoved 函数之后定义 DragAndDropManipulator::FinishedMessage 消息的处理程序:

    // Define the handler for the DragAndDropManipulator::FinishedMessage message from 2D nodes
    // that have an input manipulator which generates drag-and-drop messages.
    void onDragFinished(DragAndDropManipulator::FinishedMessageArguments&)
    {
        // Hide the Drag Item prefab instance.
        m_dragItem->setVisible(false);
    
        // Clear the pointer to the data context object.
        m_draggedDataContext.reset();
    
        // Assign the correct icons to the buttons.
        updateItems();
    }
    
  2. 在 createDragAndDropManipulator 函数的末尾,在节点处订阅 DragAndDropManipulator::FinishedMessage 消息:

    // Create and configure a drag-and-drop manipulator for a node.
    void createDragAndDropManipulator(NodeSharedPtr dragAndDropNode)
    {
        ...
    
        // Subscribe to the DragAndDropManipulator::FinishedMessage message at the node.
        // The DragAndDropManipulator generates this message when the user ends the drag-and-drop gesture
        // by releasing the pointer.
        dragSourceItem->addMessageHandler(DragAndDropManipulator::FinishedMessage, bind(&DragAndDrop::onDragFinished, this, placeholders::_1));
    }
    
  3. 构建和运行应用程序。

    当结束拖放手势时,Drag Item 变为不可见。

另请参阅

要详细了解拖放输入操纵器,请参阅 使用 Drag-And-Drop Manipulator。

要了解有关在 Kanzi 中处理用户输入的更多信息,请参阅 处理用户输入。