Установите время с помощью аналоговых часов в Xamarin.Forms

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

Главный вопрос - как двигать рукой на ощупь и как извлечь из этого пользу.

Предполагаю, что мне нужно как-то подключить событие ondrag и отследить, в каких координатах находится палец. и после того, как палец отпущен, мне нужно определить, рядом с каким часом или какой минутой были последние координаты.

Может быть, есть лучший способ нарисовать аналоговые часы, чтобы я мог все это реализовать?

В настоящее время я использую SkiaSharp для рисования часов

<skia:SKCanvasView 
      x:Name="canvasView"  
      BackgroundColor="Transparent"
      PaintSurface="canvasView_PaintSurface"
      AbsoluteLayout.LayoutBounds="0, 0, 250, 250" />
SKPaint transparentFillPaint = new SKPaint
{
    Style = SKPaintStyle.Fill,
    Color = SKColors.Transparent
};

SKPaint whiteStrokePaint = new SKPaint
{
    Style = SKPaintStyle.Stroke,
    Color = SKColors.White,
    StrokeWidth = 2,
    StrokeCap = SKStrokeCap.Round,
    IsAntialias = true
};

SKPaint blueFillPaint = new SKPaint
{
    Style = SKPaintStyle.Fill,
    Color = new SKColor(187, 245, 247),
};

SKPaint backgroundFillPaint = new SKPaint
{
    Style = SKPaintStyle.Fill,
    Color = SKColors.Transparent
};

SKPath hourHandPath = SKPath.ParseSvgPathData(
    "M 0 -60 C 0 -55 0 -50 2.5 0 C 2.5 5 -2.5 5 -2.5 0 C 0 -50 0 -55 0 -60");
SKPath minuteHandPath = SKPath.ParseSvgPathData(
    "M 0 -80 C 0 -75 0 -70 2.5 0 C 2.5 5 -2.5 5 -2.5 0 C 0 -70 0 -75 0 -80");

public AnalogClockView()
{
    InitializeComponent();

    SetupCanvas();
}

protected void SetupCanvas()
{
    Device.StartTimer(TimeSpan.FromSeconds(1f / 60), () =>
    {
        canvasView.InvalidateSurface();
        return true;
    });
}

private void canvasView_PaintSurface(object sender, SKPaintSurfaceEventArgs e)
{
    SKSurface surface = e.Surface;
    SKCanvas canvas = surface.Canvas;

    canvas.Clear();

    canvas.DrawPaint(backgroundFillPaint);

    int width = e.Info.Width;
    int height = e.Info.Height;

    // Set transforms
    canvas.Translate(width / 2, height / 2);
    canvas.Scale(Math.Min(width / 210f, height / 210f));

    // Get DateTime
    DateTime dateTime = DateTime.Now;

    // Clock background
    canvas.DrawCircle(0, 0, 100, transparentFillPaint);

    // Hour and minute marks
    for (int angle = 0; angle < 360; angle += 6)
    {
        if (angle % 30 == 0)
        {
            var rect = new SKRect(0, -90, 6, -75);
            var r = new SKSize(6, 4);
            canvas.DrawRoundRect(rect, r, blueFillPaint);
        }

        canvas.RotateDegrees(6);
    }

    // Hour hand
    canvas.Save();
    canvas.RotateDegrees(30 * dateTime.Hour + dateTime.Minute / 2f);
    canvas.DrawPath(hourHandPath, blueFillPaint);
    canvas.Restore();

    // Minute hand
    canvas.Save();
    canvas.RotateDegrees(6 * dateTime.Minute + dateTime.Second / 10f);
    canvas.DrawPath(minuteHandPath, blueFillPaint);
    canvas.Restore();

    // Second hand
    canvas.Save();
    float seconds = dateTime.Second + dateTime.Millisecond / 1000f;
    canvas.RotateDegrees(6 * seconds);
    whiteStrokePaint.StrokeWidth = 2;
    canvas.DrawLine(0, 10, 0, -80, blueFillPaint);
    canvas.Restore();
}

person ivsalti    schedule 27.08.2019    source источник


Ответы (1)


Я использую BoxView для создания аналоговых часов, и вы можете использовать BoxView.GestureRecognizers, чтобы установить время с помощью рук.

 <AbsoluteLayout x:Name="absoluteLayout"
                SizeChanged="OnAbsoluteLayoutSizeChanged" >

    <BoxView x:Name="hourHand"
             Color="Black">
        <BoxView.GestureRecognizers>
            <PanGestureRecognizer PanUpdated="OnPanUpdated" />
        </BoxView.GestureRecognizers>
    </BoxView>

    <BoxView x:Name="minuteHand"
             Color="Black" />

    <BoxView x:Name="secondHand"
             Color="Black" />
</AbsoluteLayout>


 public partial class MainPage : ContentPage
{
    // Structure for storing information about the three hands.
    struct HandParams
    {
        public HandParams(double width, double height, double offset) : this()
        {
            Width = width;
            Height = height;
            Offset = offset;
        }

        public double Width { private set; get; }   // fraction of radius
        public double Height { private set; get; }  // ditto
        public double Offset { private set; get; }  // relative to center pivot
    }

    static readonly HandParams secondParams = new HandParams(0.02, 1.1, 0.85);
    static readonly HandParams minuteParams = new HandParams(0.05, 0.8, 0.9);
    static readonly HandParams hourParams = new HandParams(0.125, 0.65, 0.9);

    BoxView[] tickMarks = new BoxView[60];

    public MainPage()
    {
        InitializeComponent();

        // Create the tick marks (to be sized and positioned later).
        for (int i = 0; i < tickMarks.Length; i++)
        {
            tickMarks[i] = new BoxView { Color = Color.Black };
            absoluteLayout.Children.Add(tickMarks[i]);
        }

        //Device.StartTimer(TimeSpan.FromSeconds(1.0 / 60), OnTimerTick);
        OnTimerTick();
    }

    void OnAbsoluteLayoutSizeChanged(object sender, EventArgs args)
    {
        // Get the center and radius of the AbsoluteLayout.
        Point center = new Point(absoluteLayout.Width / 2, absoluteLayout.Height / 2);
        double radius = 0.45 * Math.Min(absoluteLayout.Width, absoluteLayout.Height);

        // Position, size, and rotate the 60 tick marks.
        for (int index = 0; index < tickMarks.Length; index++)
        {
            double size = radius / (index % 5 == 0 ? 15 : 30);
            double radians = index * 2 * Math.PI / tickMarks.Length;
            double x = center.X + radius * Math.Sin(radians) - size / 2;
            double y = center.Y - radius * Math.Cos(radians) - size / 2;
            AbsoluteLayout.SetLayoutBounds(tickMarks[index], new Rectangle(x, y, size, size));
            tickMarks[index].Rotation = 180 * radians / Math.PI;
        }

        // Position and size the three hands.
        LayoutHand(secondHand, secondParams, center, radius);
        LayoutHand(minuteHand, minuteParams, center, radius);
        LayoutHand(hourHand, hourParams, center, radius);
    }

    void LayoutHand(BoxView boxView, HandParams handParams, Point center, double radius)
    {
        double width = handParams.Width * radius;
        double height = handParams.Height * radius;
        double offset = handParams.Offset;

        AbsoluteLayout.SetLayoutBounds(boxView,
            new Rectangle(center.X - 0.5 * width,
                          center.Y - offset * height,
                          width, height));

        // Set the AnchorY property for rotations.
        boxView.AnchorY = handParams.Offset;
    }

    bool OnTimerTick()
    {
        // Set rotation angles for hour and minute hands.
        DateTime dateTime = DateTime.Now;
        hourHand.Rotation = 30 * (dateTime.Hour % 12) + 0.5 * dateTime.Minute;
        minuteHand.Rotation = 6 * dateTime.Minute + 0.1 * dateTime.Second;

        // Do an animation for the second hand.
        double t = dateTime.Millisecond / 1000.0;

        if (t < 0.5)
        {
            t = 0.5 * Easing.SpringIn.Ease(t / 0.5);
        }
        else
        {
            t = 0.5 * (1 + Easing.SpringOut.Ease((t - 0.5) / 0.5));
        }

        secondHand.Rotation = 6 * (dateTime.Second + t);
        return true;
    }


    private void OnPanUpdated(object sender, PanUpdatedEventArgs e)
    {          
        DateTime dateTime = DateTime.Now;
        double r = hourHand.Rotation;
        switch (e.StatusType)
        {
            case GestureStatus.Running:


                break;

            case GestureStatus.Completed:
                //set rotation according to TranslationX and TranslationY
                hourHand.Rotation = 180;

                break;
        }


    }
}

Вот статья о добавлении распознавателя жестов панорамирования.

https://docs.microsoft.com/en-us/xamarin/xamarin-forms/app-fundamentals/gestures/pan

person Cherry Bu - MSFT    schedule 28.08.2019
comment
Большое спасибо! Это именно то, что мне нужно. У меня все еще проблема с получением угла поворота. Я использую Atan2, чтобы получить угол, но это не работает. angle = Math.Atan2 (e.TotalY - centerY, e.TotalX - centerX); Может я ошибся с центром. Я пробовал absoluteLayout.Width / 2, absoluteLayout.Height / 2, но угол всегда между 0 и 2. Я также пробовал ваши функции center.X - 0.5 * width; и center.Y - offset * height; И результат тот же - person ivsalti; 28.08.2019
comment
Я не думаю, что можно рассчитать угол с помощью TotalX и TotalY из PanGesture. - person ivsalti; 29.08.2019
comment
@ ivsalti, может быть, но вы можете указать, на сколько градусов поворота. - person Cherry Bu - MSFT; 03.09.2019