Разработка поверхности: перемещение/поворот/масштабирование элементов без ScatterView

Можно ли транслировать/вращать/масштабировать элементы без ScatterView? Я хочу манипулировать элементами, которые могут быть поверх других элементов, таких как кнопка, список или настраиваемый элемент управления, который должен быть статическим. Когда я добавляю их в ScatterView, все они становятся ScatterViewItems, что не является желаемым эффектом.


person Maarten    schedule 09.12.2009    source источник


Ответы (2)


Немного расширив ответ Марка...

Да, для этого можно использовать API манипуляции и инерции, см. эту обзорную страницу. .

Некоторое время назад я создал свой собственный очень простой элемент управления Scatterview, который, по сути, делал то же, что и Scatterview, но со следующими ограничениями:

  • Только один ребенок, поэтому он работает больше как граница
  • Нет внешнего вида по умолчанию или особого поведения дочернего элемента, как это делает SV.

Одна проблема, с которой я столкнулся при разработке, заключается в том, что вы должны сделать так, чтобы ваш элемент управления контейнером занимал большую площадь, чем фактический дочерний элемент. В противном случае вы не сможете надежно зафиксировать события контакта, когда пальцы находятся вне объекта, которым вы манипулируете. Что я сделал, так это сделал свой контейнер полноэкранным (1024x768) и прозрачным, и это нормально работает для моего случая.

Для самой манипуляции вы используете экземпляр Класс Affine2DManipulationProcessor. Этот класс требует, чтобы вы начали манипуляцию, а затем он будет постоянно кормить вас дельта-событием (Affine2DManipulationDelta).

Если вы хотите, чтобы ваша манипуляция имела более реальное поведение после того, как пользователь отпускает пальцы, вы будете использовать класс Affine2DInertiaProcessor, который работает аналогично обработчику манипуляций. Базовая настройка заключается в том, что, как только процессор манипулирования завершен (пользователь отпускает пальцы), вы указываете процессору инерции запуститься.

Давайте посмотрим на код :) Вот мой метод настройки в моем классе-контейнере:

private void InitializeManipulationInertiaProcessors()
{
    manipulationProcessor = new Affine2DManipulationProcessor(
      Affine2DManipulations.TranslateY | Affine2DManipulations.TranslateX |
      Affine2DManipulations.Rotate | Affine2DManipulations.Scale, 
      this);
    manipulationProcessor.Affine2DManipulationCompleted += new EventHandler<Affine2DOperationCompletedEventArgs>(processor_Affine2DManipulationCompleted);
    manipulationProcessor.Affine2DManipulationDelta += new EventHandler<Affine2DOperationDeltaEventArgs>(processor_Affine2DManipulationDelta);
    inertiaProcessor = new Affine2DInertiaProcessor();
    inertiaProcessor.Affine2DInertiaDelta += new EventHandler<Affine2DOperationDeltaEventArgs>(processor_Affine2DManipulationDelta);
}

Чтобы начать все это, я перехватываю ContactDown в своем классе-контейнере:

protected override void OnContactDown(ContactEventArgs e)
{
    base.OnContactDown(e);
    e.Contact.Capture(this);
    // Tell the manipulation processor what contact to track and it will 
    // start sending manipulation delta events based on user motion.
    manipulationProcessor.BeginTrack(e.Contact);
    e.Handled = true;
}

Вот и все, теперь откиньтесь на спинку кресла и дайте манипуляционному процессору сделать свою работу. Всякий раз, когда у него есть новые данные, он вызывает дельта-событие (происходит несколько раз в секунду, пока пользователь двигает пальцами). Обратите внимание, что вы, как потребитель процессора, должны что-то делать со значениями. Он будет сообщать вам только такие вещи, как «пользователь применил поворот на X градусов» или «пользователь переместил палец на X, Y пикселей». Что вы обычно делаете, так это пересылаете эти значения в rendertransforms, чтобы фактически показать пользователю, что произошло.

В моем случае мой дочерний объект имеет три жестко закодированных преобразования рендеринга: «перевод», «поворот» и «масштабирование», которые я обновляю значениями из процессора. Я также выполняю некоторую проверку границ, чтобы убедиться, что объект не перемещается за пределы поверхности и не масштабируется слишком сильно или слишком мало:

/// <summary>
/// This is called whenever the manipulator or the inertia processor has calculated a new position
/// </summary>
/// <param name="sender">The processor who caused the change</param>
/// <param name="e">Event arguments containing the calculations</param>
void processor_Affine2DManipulationDelta(object sender, Affine2DOperationDeltaEventArgs e)
{            
    var x = translate.X + e.Delta.X;
    var y = translate.Y + e.Delta.Y;
    if (sender is Affine2DManipulationProcessor)
    {
        // Make sure we don't move outside the screen
        // Inertia processor does this automatically so only adjust if sender is manipulation processor
        y = Math.Max(0, Math.Min(y, this.ActualHeight - box.RenderSize.Height));
        x = Math.Max(0, Math.Min(x, this.ActualWidth - box.RenderSize.Width));
    }
    translate.X = x;
    translate.Y = y;
    rotate.Angle += e.RotationDelta;
    var newScale = scale.ScaleX * e.ScaleDelta;
    Console.WriteLine("Scale delta: " + e.ScaleDelta + " Rotate delta: " + e.RotationDelta);
    newScale = Math.Max(0.3, Math.Min(newScale, 3));
    scale.ScaleY = scale.ScaleX = newScale;
}

Здесь следует отметить, что и манипуляция, и инерционный процессор используют один и тот же обратный вызов для дельта-событий.

Последняя часть головоломки — это когда пользователь отпускает палец, и я хочу запустить инерционный процессор:

/// <summary>
/// This is called when the manipulator has completed (i.e. user released contacts) and we let inertia take over movement to make a natural slow down
/// </summary>
void processor_Affine2DManipulationCompleted(object sender, Affine2DOperationCompletedEventArgs e)
{
    inertiaProcessor.InitialOrigin = e.ManipulationOrigin;

    // Set the deceleration rates. Smaller number means less friction (i.e. longer time before it stops)
    inertiaProcessor.DesiredAngularDeceleration = .0010;
    inertiaProcessor.DesiredDeceleration = .0010;
    inertiaProcessor.DesiredExpansionDeceleration = .0010;
    inertiaProcessor.Bounds = new Thickness(0, 0, this.ActualWidth, this.ActualHeight);
    inertiaProcessor.ElasticMargin = new Thickness(20);

    // Set the initial values.
    inertiaProcessor.InitialVelocity = e.Velocity;
    inertiaProcessor.InitialExpansionVelocity = e.ExpansionVelocity;
    inertiaProcessor.InitialAngularVelocity = e.AngularVelocity;

    // Start the inertia.
    inertiaProcessor.Begin();
}
person Isak Savo    schedule 25.03.2010

да, вы можете использовать ManipulationProcessor, который поставляется с API

person Mark    schedule 08.01.2010