I'm having a problem with the PanGestuer behaviour in Xamarin Form. This is my page:
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:Xamarin_Test"
x:Class="Xamarin_Test.MainPage">
<StackLayout x:Name="parentFrame" Margin="100" BackgroundColor="Gray">
<AbsoluteLayout x:Name="parentLayout" BackgroundColor="Red">
<local:PanContainer x:Name="parentContainer" BackgroundColor="Yellow">
<Image x:Name="panImage" Source="MonoMonkey.jpg"
WidthRequest="{Binding Path=Width, Source={x:Reference Name=parentFrame}}"
HeightRequest="{Binding Path=Height, Source={x:Reference Name=parentFrame}}"/>
</local:PanContainer>
</AbsoluteLayout>
</StackLayout>
</ContentPage>
PanContainer.cs
public class PanContainer : ContentView
{
double x, y;
public PanContainer()
{
var panGesture = new PanGestureRecognizer();
panGesture.PanUpdated += OnPanUpdated;
GestureRecognizers.Add(panGesture);
}
void OnPanUpdated(object sender, PanUpdatedEventArgs e)
{
switch (e.StatusType)
{
case GestureStatus.Running:
// Translate and ensure we don't pan beyond the wrapped user interface element bounds.
Content.TranslationX = Math.Max(Math.Min(0, x + e.TotalX), -Math.Abs(Content.Width - App.ScreenWidth));
Content.TranslationY = Math.Max(Math.Min(0, y + e.TotalY), -Math.Abs(Content.Height - App.ScreenHeight));
break;
case GestureStatus.Completed:
// Store the translation applied during the pan
x = Content.TranslationX;
y = Content.TranslationY;
break;
}
}
}
The image loads with correct size (within the StackLayout borders, for better testing I colorized the elements). The panning works to the left and to the upper way of the image, but overlays the white area. How can I handle panning, so that the moved image is only visible within my panContainer?
Related
Following the answer in the following link
"
Xamarin WebView scale to fit "
I've managed to fit the html live stream to the phone screen while it is in portrait mode. The result in portrait is acceptable. But when the screen rotates to landscape mode it exceeds the limits of the screen and in order to view the whole stream it requires for the user to scroll. I've found the following topic but it does not seem to work
Xamarin webview does not fill screen
What needs to be done in order for the stream to fit without scrolling. I feel that its simply. Do I follow the correct path?
I am using a custom renderer for the WebView
public class CustomWebViewRenderer : WebViewRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<WebView> e)
{
base.OnElementChanged(e);
if (Control != null)
{
Control.Settings.BuiltInZoomControls = true;
Control.Settings.DisplayZoomControls = false;
Control.Settings.SetSupportZoom(true);
Control.Settings.LoadWithOverviewMode = true;
Control.Settings.UseWideViewPort = true;
}
/*
* The code for the second link
*
* Control.Settings.JavaScriptEnabled = true;
Control.EvaluateJavascript("document.documentElement.style.height = screen.height + 'px';", null);
Control.EvaluateJavascript("document.documentElement.style.width = screen.width + 'px';", null);
*/
}
}
The xaml code of the page is
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage
NavigationPage.HasNavigationBar="False"
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:androidclient="clr-namespace:AndroidClient"
x:Class="AndroidClient.LiveViewPage">
<StackLayout HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand">
<Button x:Name="ExitFullScreenBtn" Clicked="ExitFullScreenBtn_Clicked"
HorizontalOptions="End" VerticalOptions="Start" WidthRequest="50"
Text="X"></Button>
<androidclient:CustomWebView x:Name="Browser" VerticalOptions="FillAndExpand" HorizontalOptions="FillAndExpand"
></androidclient:CustomWebView>
</StackLayout>
</ContentPage>
The c# for the same page
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class LiveViewPage : ContentPage
{
public LiveViewPage()
{
InitializeComponent();
var url = "http://192.168.1.7:8080/";
Browser.Source = url; }
private void ExitFullScreenBtn_Clicked(object sender, EventArgs e)
{
Console.WriteLine("Closed Live");
Navigation.PopAsync();
}
}
This is the xaml for the app I'm working on. It simply displays a map on the screen with a ListView:
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:maps="clr-namespace:Xamarin.Forms.Maps;assembly=Xamarin.Forms.Maps"
xmlns:local="clr-namespace:GasStations"
x:Class="GasStations.MainPage">
<StackLayout>
<StackLayout VerticalOptions="FillAndExpand">
<maps:Map WidthRequest="960" HeightRequest="200"
x:Name="MyMap"
IsShowingUser="true"/>
<ListView x:Name="ListView_Pets">
<ListView.ItemsSource>
<x:Array Type="{x:Type x:String}">
<x:String>dog</x:String>
<x:String>cat</x:String>
<x:String>bird</x:String>
</x:Array>
</ListView.ItemsSource>
</ListView>
</StackLayout>
</StackLayout>
</ContentPage>
I set the property Map.IsShowingUser="true" thinking that it would display the current location, but it's not showing the point.
This is what the form looks like. It doesn't display the current location pin.
My question is: how can I add current location to the map?
Use the Geolocation plugin to get your current location. If you don't have it already and then use the MoveCamera method to move to that position:
private async Task TrackCurrentLocation()
{
var current = await CrossGeolocator.Current.GetPositionAsync();
current.Accuracy = 30;
float bearing =//bearing in case any
var pins = new Pin { Label = "ME", Icon =''yourIcon"), Flat = true
};
var latitude = current.Latitude;
var longitude = current.Longitude;
Device.BeginInvokeOnMainThread(() =>
{
pins.Position = new Position(latitude, longitude);
pins.Rotation = bearing;
if (MapTrack.Pins.Count == 0)
{
MapTrack.Pins.Add(pins);
}
else
{
MapTrack.Pins[0] = pins;
}
MapTrack.MoveCamera(CameraUpdateFactory.NewPosition(new Position(latitude, longitude)));
});
}
I would like to have ability to slide up and down the panel to see more data as seen in the image. Prefer to have cross platform solution. I have tried to use the SlideOverKit but it does not seem to have this sort of ability - or could I be wrong? Would there be a better solution out there for such a scenario?
There are many way to achieve a sliding panel, here is one using a PanGestureRecognizer for a swipe up / down on an absolute layout:
<AbsoluteLayout BackgroundColor="Silver" AbsoluteLayout.LayoutBounds="0,1,1,1">
<!--
Your page's content here
-->
<StackLayout x:Name="bottomDrawer" BackgroundColor="Olive" AbsoluteLayout.LayoutBounds="0.5,1.00,0.9,0.04" AbsoluteLayout.LayoutFlags="All">
<StackLayout.GestureRecognizers>
<PanGestureRecognizer PanUpdated="PanGestureHandler" />
</StackLayout.GestureRecognizers>
<!--
Your sliding panel's content here
-->
<Label x:Name="swipeLabel" Text="Swipe me up" TextColor="Black" HorizontalOptions="Center" />
<Button Text="Click Me" BackgroundColor="Silver" TextColor="Black" BorderColor="Gray" BorderWidth="5" BorderRadius="20" />
<Image Source="whome" />
</StackLayout>
</AbsoluteLayout>
The PanGestureRecognizer handler:
double? layoutHeight;
double layoutBoundsHeight;
int direction;
const double layoutPropHeightMax = 0.75;
const double layoutPropHeightMin = 0.04;
void PanGestureHandler(object sender, PanUpdatedEventArgs e)
{
layoutHeight = layoutHeight ?? ((sender as StackLayout).Parent as AbsoluteLayout).Height;
switch (e.StatusType)
{
case GestureStatus.Started:
layoutBoundsHeight = AbsoluteLayout.GetLayoutBounds(sender as StackLayout).Height;
break;
case GestureStatus.Running:
direction = e.TotalY < 0 ? 1 : -1;
break;
case GestureStatus.Completed:
if (direction > 0) // snap to max/min, you could use an animation....
{
AbsoluteLayout.SetLayoutBounds(bottomDrawer, new Rectangle(0.5, 1.00, 0.9, layoutPropHeightMax));
swipeLabel.Text = "Swipe me down";
}
else
{
AbsoluteLayout.SetLayoutBounds(bottomDrawer, new Rectangle(0.5, 1.00, 0.9, layoutPropHeightMin));
swipeLabel.Text = "Swipe me up";
}
break;
}
}
You can add dragging to the pan gesture by setting the "panel" size in the GestureStatus.Running case statement. Something like will get you started:
case GestureStatus.Running:
direction = e.TotalY < 0 ? 1 : -1;
var yProp = layoutBoundsHeight + (-e.TotalY / (double)layoutHeight);
if ((yProp > layoutPropHeightMin) & (yProp < layoutPropHeightMax))
AbsoluteLayout.SetLayoutBounds(bottomDrawer, new Rectangle(0.5, 1.00, 0.9, yProp));
break;
Note: Personally I would use this style of dragging as a prototype only as it is glitchy. I would write a custom renderer per platform to avoid the Forms' layout manager.
How create Bottom sheets in Xamarin.Forms?
https://material.io/guidelines/components/bottom-sheets.html
Use SlideOverKit for this purpose.
It's available on nuget
Here are some examples
The control you are looking for is called Slide up Menu
Xamarin Forms Bottom sheet without a nuget package library.
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="YourAppName.Views.YourPageName">
<ContentPage.Content>
<RelativeLayout BackgroundColor="White">
<StackLayout HorizontalOptions="Fill" VerticalOptions="Fill">
<!-- place your page content here -->
</StackLayout>
<Frame x:Name="BottomSheet" CornerRadius="20" HasShadow="True" BackgroundColor="White" Padding="10,5" BorderColor="LightGray"
RelativeLayout.YConstraint="{ConstraintExpression Type=RelativeToParent,Property=Height,Factor=.93,Constant=0}"
RelativeLayout.WidthConstraint="{ConstraintExpression Type=RelativeToParent,Property=Width,Factor=1,Constant=0}"
RelativeLayout.HeightConstraint="{ConstraintExpression Type=RelativeToParent,Property=Height,Factor=1,Constant=0}">
<Frame.GestureRecognizers>
<PanGestureRecognizer PanUpdated="OnPanelUpdated" />
</Frame.GestureRecognizers>
<StackLayout VerticalOptions="StartAndExpand" HorizontalOptions="Fill">
<BoxView HeightRequest="5" CornerRadius="2" BackgroundColor="Grey" HorizontalOptions="CenterAndExpand" WidthRequest="50" Margin="25,10,0,10"></BoxView>
<!-- Place your bottom sheet layout here -->
</StackLayout>
</Frame>
</RelativeLayout>
</ContentPage.Content>
</ContentPage>
YourPageName.xaml.cs Code behind
protected override void OnAppearing()
{
MoveBottomSheet(true);
}
/// The -negative value determines how many vertical units should the panel occuply on the screen.
private async void MoveBottomSheet(bool close)
{
double finalTranslation = close ? (Device.Idiom == TargetIdiom.Phone ? -134.0 : -144.0) : (Device.Idiom == TargetIdiom.Phone ? -389.0 : -434.0);
await BottomSheet.TranslateTo(BottomSheet.X, finalTranslation, 450, Easing.SinIn);
}
/// This is fired multiple times while the user pans the bottom sheet. This variable captures the first intention of determining whether to open (pan up) or close (pan down)
bool _panelActivated = false;
private void OnPanelUpdated(object sender, PanUpdatedEventArgs e)
{
switch (e.StatusType)
{
case GestureStatus.Started:
break;
case GestureStatus.Running:
if (_panelActivated)
{
return;
}
MoveBottomSheet(e.TotalY > 0);
_panelActivated = true;
break;
case GestureStatus.Completed:
_panelActivated = false;
break;
case GestureStatus.Canceled:
break;
}
}
you can use a nuget made by me this is the github project
https://github.com/josco007/CHDBottomSheet
It also supports scrollviews inside.
You need a relative layout as root view and set your relative layout in this way on the onAppearing method
protected override void OnAppearing()
{
base.OnAppearing();
CHDBottomSheetBs.SetRelativeLayout(_rootRlt);
}
See the github project for more information.
I would like to figure out, how to keep my label text appear after label animation completion. My idea is that I have Label on top and Entry field behind it. Until there isn't any text in entry, my label is empty, but when I starting to type my label appear with animation over and over after each newly typed character. I already did that, but I wan't that it will appear with animation one time, and then keep showing my regular label without repeating animation. Because now my label shows with animation and do no keep, it disappears immediately.
Here is my xaml code:
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Project.MyPage">
<ContentPage.Content>
<StackLayout Padding="7,7,7,7" VerticalOptions="FillAndExpand" HorizontalOptions="FillAndExpand" Spacing="0">
<StackLayout BackgroundColor="White">
<Label x:Name="NameText" />
<Entry x:Name="Name" />
</StackLayout>
</StackLayout>
</ContentPage.Content>
</ContentPage>
and here is my C# code with EventHandler: TextChange
public MyPage()
{
InitializeComponent();
Name.TextChanged += Name_TextChanged;
}
private async void Name_TextChanged(object sender, TextChangedEventArgs e)
{
NameText.Animate("nameAnimation", new Animation(v => NameText.Scale = v, 1, 2, Easing.SpringIn));
NameText.Text = "MyLabel";
}
How to my label appear and complete animation action only one time?
Thank you for answers or suggestions.
Detach your handler after the animation:
async void Name_TextChanged(object sender, TextChangedEventArgs e)
{
NameText.Animate("nameAnimation", new Animation(v => NameText.Scale = v, 1, 2, Easing.SpringIn));
NameText.Text = "MyLabel";
Name.TextChanged -= Name_TextChanged;
}