Surface Dial 與 Windows Wheel UWP應用開發


隨着微軟發布 Surface Studio 在演示視頻中非常搶眼的一個配件就是 Surface Dial,Dial 是Windows輸入設備大家庭中的新成員我們把它歸類為Windows Wheel 類型設備。今天為大家介紹一下如何配合這個神奇設備開發自己的應用。

Dial 是一個類似於滾輪的設備,可以協助用戶更快的想計算機輸入信息,但是Dial並不是一個專為精准輸入所設計的設備,換句話說如果用戶想點擊屏幕上某一個點我們還是推薦用戶使用手指觸摸或者Surface Pan 或者鼠標完成這項工作,Dial更適合類似於畫布的旋轉,筆觸的選擇等快速的操作。

Surface Dial操作非常簡單,只有三個操作事件 長按單擊,以及 轉動並且如果Surface Dial 與 Surface Studio 一起使用的話 Surface Dial 的菜單可以使用 Surface Dial的屏幕菜單,就是將 Dial 放在 Surface Studio 屏幕上隨后通過對Surface Dial的操作,圍繞Dial的屏幕周圍會出現一個菜單。

Dial 自身與windows銜接緊密並且內置系統工具例如調整系統音量,大小縮小,以及撤銷從做等功能。此外Dial 與 Windows Ink集成也是非常緊密。如果您的WUP應用已經使用了 InkCanvas 和 InkToolbar 那么 Dial就會自動和 InkToolbar中的內容相結合例如 調整標尺的角度和調整筆觸的大小等功能。

然而對於我們開發者而言 Surface 也是為我們提供了API(這里分別有UWP 和 Win32版本,本文着重介紹UWP版本),其實開發起來也是非常簡單。

首先關鍵點 

RadialController 類,通過CreateForCurrentView()靜態方法獲取實例。

RadialControllerMenuItem 類,用來自定義菜單內容。當Dial長按時會彈出自定義菜單。

ButtonClicked 事件,用來捕捉Dial的點擊事件。

RotationChanged 事件,用來捕捉Dial旋轉事件。

ScreenContactStarted 事件,捕捉 Dial 放在了 Surface Studio 上事件,並且可以從回調參數中獲取Dial在屏幕中擺放的位置。

ScreenContactContinued 事件,捕捉 Dial 放在了 Surface Studio 上並且移動了位置,並且可以從回調參數中獲取Dial在屏幕中擺放的位置。

ScreenContactEnded 事件,捕捉 Dial 從 Surface Studio 上移開事件。

ControlLost 事件,捕捉操作的焦點離開。

 

照搬一個MSDN上的demo code比較直觀的展示這幾個事件的用法以及如何判斷Dial在Surface Studio上的位置

Xaml代碼

<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid.RowDefinitions>
 <RowDefinition Height="Auto"/>
 <RowDefinition Height="*"/>
</Grid.RowDefinitions>
<StackPanel x:Name="HeaderPanel" 
     Orientation="Horizontal" 
     Grid.Row="0">
 <TextBlock x:Name="Header"
   Text="RadialController customization sample"
   VerticalAlignment="Center"
   Style="{ThemeResource HeaderTextBlockStyle}"
   Margin="10,0,0,0" />
</StackPanel>
<Grid Grid.Row="1" x:Name="RootGrid">
 <Grid.RowDefinitions>
   <RowDefinition Height="*"/>
   <RowDefinition Height="*"/>
 </Grid.RowDefinitions>
 <Grid.ColumnDefinitions>
   <ColumnDefinition Width="*"/>
   <ColumnDefinition Width="*"/>
 </Grid.ColumnDefinitions>
 <Grid x:Name="Grid0"
   Grid.Row="0"
   Grid.Column="0">
   <StackPanel Orientation="Vertical" 
     VerticalAlignment="Center" 
     HorizontalAlignment="Center">
     <!-- Slider for rotational input -->
     <Slider x:Name="RotationSlider0"
       Width="300"
       HorizontalAlignment="Left"/>
     <!-- Switch for button input -->
     <ToggleSwitch x:Name="ButtonToggle0"
         HorizontalAlignment="Left"/>
   </StackPanel>
 </Grid>
 <Grid x:Name="Grid1"
   Grid.Row="0"
   Grid.Column="1">
   <StackPanel Orientation="Vertical" 
     VerticalAlignment="Center" 
     HorizontalAlignment="Center">
     <!-- Slider for rotational input -->
     <Slider x:Name="RotationSlider1"
       Width="300"
       HorizontalAlignment="Left"/>
     <!-- Switch for button input -->
     <ToggleSwitch x:Name="ButtonToggle1"
         HorizontalAlignment="Left"/>
   </StackPanel>
 </Grid>
 <Grid x:Name="Grid2"
   Grid.Row="1"
   Grid.Column="0">
   <StackPanel Orientation="Vertical" 
     VerticalAlignment="Center" 
     HorizontalAlignment="Center">
     <!-- Slider for rotational input -->
     <Slider x:Name="RotationSlider2"
       Width="300"
       HorizontalAlignment="Left"/>
     <!-- Switch for button input -->
     <ToggleSwitch x:Name="ButtonToggle2"
         HorizontalAlignment="Left"/>
   </StackPanel>
 </Grid>
 <Grid x:Name="Grid3"
   Grid.Row="1"
   Grid.Column="1">
   <StackPanel Orientation="Vertical" 
     VerticalAlignment="Center" 
     HorizontalAlignment="Center">
     <!-- Slider for rotational input -->
     <Slider x:Name="RotationSlider3"
       Width="300"
       HorizontalAlignment="Left"/>
     <!-- Switch for button input -->
     <ToggleSwitch x:Name="ButtonToggle3"
         HorizontalAlignment="Left"/>
   </StackPanel>
 </Grid>
</Grid>
</Grid>

code-behind 代碼

Slider ActiveSlider;
ToggleSwitch ActiveSwitch;
Grid ActiveGrid;

public MainPage()
{
  ...

  myController.ScreenContactStarted += 
    MyController_ScreenContactStarted;
  myController.ScreenContactContinued += 
    MyController_ScreenContactContinued;
  myController.ScreenContactEnded += 
    MyController_ScreenContactEnded;
  myController.ControlLost += MyController_ControlLost;

  //Set initial grid for Surface Dial input.
  ActiveGrid = Grid0;
  ActiveSlider = RotationSlider0;
  ActiveSwitch = ButtonToggle0;
}

private void MyController_ScreenContactStarted(RadialController sender, 
  RadialControllerScreenContactStartedEventArgs args)
{
  //find grid at contact location, update visuals, selection
  ActivateGridAtLocation(args.Contact.Position);
}

private void MyController_ScreenContactContinued(RadialController sender, 
  RadialControllerScreenContactContinuedEventArgs args)
{
  //if a new grid is under contact location, update visuals, selection
  if (!VisualTreeHelper.FindElementsInHostCoordinates(
    args.Contact.Position, RootGrid).Contains(ActiveGrid))
  {
    ActiveGrid.Background = new 
      SolidColorBrush(Windows.UI.Colors.White);
    ActivateGridAtLocation(args.Contact.Position);
  }
}

private void MyController_ScreenContactEnded(RadialController sender, object args)
{
  //return grid color to normal when contact leaves screen
  ActiveGrid.Background = new 
  SolidColorBrush(Windows.UI.Colors.White);
}

private void MyController_ControlLost(RadialController sender, object args)
{
  //return grid color to normal when focus lost
  ActiveGrid.Background = new 
    SolidColorBrush(Windows.UI.Colors.White);
}

private void ActivateGridAtLocation(Point Location)
{
  var elementsAtContactLocation = 
    VisualTreeHelper.FindElementsInHostCoordinates(Location, 
      RootGrid);

  foreach (UIElement element in elementsAtContactLocation)
  {
    if (element as Grid == Grid0)
    {
      ActiveSlider = RotationSlider0;
      ActiveSwitch = ButtonToggle0;
      ActiveGrid = Grid0;
      ActiveGrid.Background = new SolidColorBrush( 
        Windows.UI.Colors.LightGoldenrodYellow);
      return;
    }
    else if (element as Grid == Grid1)
    {
      ActiveSlider = RotationSlider1;
      ActiveSwitch = ButtonToggle1;
      ActiveGrid = Grid1;
      ActiveGrid.Background = new SolidColorBrush( 
        Windows.UI.Colors.LightGoldenrodYellow);
      return;
    }
    else if (element as Grid == Grid2)
    {
      ActiveSlider = RotationSlider2;
      ActiveSwitch = ButtonToggle2;
      ActiveGrid = Grid2;
      ActiveGrid.Background = new SolidColorBrush( 
        Windows.UI.Colors.LightGoldenrodYellow);
      return;
    }
    else if (element as Grid == Grid3)
    {
      ActiveSlider = RotationSlider3;
      ActiveSwitch = ButtonToggle3;
      ActiveGrid = Grid3;
      ActiveGrid.Background = new SolidColorBrush( 
        Windows.UI.Colors.LightGoldenrodYellow);
      return;
    }
  }
}

當然還有一個關鍵點如何創建自定義的菜單 使用Controller.Menu.Items.Add()方法添加和使用 Remove()方法刪除自定義菜單。

注意:這里Surface Dial 菜單可以容納7個選項,如果超過7個那么Dial 需要有浮動控件配合選擇,這樣做會影響用戶體驗是不推薦的。

        private void CreateMenuItems()
        {
            menuItems = new List<RadialControllerMenuItem>
            {
                RadialControllerMenuItem.CreateFromKnownIcon("Item0", RadialControllerMenuKnownIcon.InkColor),
                RadialControllerMenuItem.CreateFromKnownIcon("Item1", RadialControllerMenuKnownIcon.NextPreviousTrack),
                RadialControllerMenuItem.CreateFromKnownIcon("Item2", RadialControllerMenuKnownIcon.Volume),
                RadialControllerMenuItem.CreateFromIcon("Item3", RandomAccessStreamReference.CreateFromUri(new Uri("ms-appx:///Assets/Item3.png"))),
                RadialControllerMenuItem.CreateFromIcon("Item4", RandomAccessStreamReference.CreateFromUri(new Uri("ms-appx:///Assets/Item4.png"))),
                RadialControllerMenuItem.CreateFromIcon("Item5", RandomAccessStreamReference.CreateFromUri(new Uri("ms-appx:///Assets/Item5.png")))
            };
            sliders = new List<Slider> { Slider0, Slider1, Slider2, Slider3, Slider4, Slider5 };
            toggles = new List<ToggleSwitch> { Toggle0, Toggle1, Toggle2, Toggle3, Toggle4, Toggle5 };

            for (int i = 0; i < menuItems.Count; ++i)
            {
                RadialControllerMenuItem radialControllerItem = menuItems[i];
                int index = i;

                radialControllerItem.Invoked += (sender, args) => { OnItemInvoked(index); };
            }
        }

        private void OnItemInvoked(int selectedItemIndex)
        {
            activeItemIndex = selectedItemIndex;
        }

        private void AddItem(object sender, RoutedEventArgs e)
        {
            RadialControllerMenuItem radialControllerMenuItem = GetRadialControllerMenuItemFromSender(sender);

            if (!Controller.Menu.Items.Contains(radialControllerMenuItem))
            {
                Controller.Menu.Items.Add(radialControllerMenuItem);
            }
        }

        private void RemoveItem(object sender, RoutedEventArgs e)
        {
            RadialControllerMenuItem radialControllerMenuItem = GetRadialControllerMenuItemFromSender(sender);

            if (Controller.Menu.Items.Contains(radialControllerMenuItem))
            {
                Controller.Menu.Items.Remove(radialControllerMenuItem);
            }
        }

        private void SelectItem(object sender, RoutedEventArgs e)
        {
            RadialControllerMenuItem radialControllerMenuItem = GetRadialControllerMenuItemFromSender(sender);

            if (Controller.Menu.Items.Contains(radialControllerMenuItem))
            {
                Controller.Menu.SelectMenuItem(radialControllerMenuItem);
                PrintSelectedItem();
            }
        }

 菜單選項推薦使用64x64像素的PNG透明圖像即可,但是同時支持到44x44的最小像素值。

注意:黑色邊框是為了在高對比度模式下也可以讓我們的圖標也可以清晰可見。

 

最后附上微軟的示例代碼:RadialControlle

希望上的總結可以幫助到大家, 同時歡迎大家在這里和我溝通交流或者在新浪微博上 @王博_Nick

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM