How create Bottom sheets in Xamarin.Forms - c#

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.

Related

Scrollview control not working properly in .Net MAUI (Android)

I have created custom tab control using ScrollView control and Bindable StackLayout control.
I have first created this solution in Xamarin.Forms (VS for Mac 2019) and it works fine in both platforms, but the same solution when developed in .Net MAUI (VS for Mac 2022 Prev) it's not working properly in Android.
Update 30 Jun 2022
There is an issue with BindableLayout (StackLayout) properties in MAUI currently so when we are changing values it does not get reflected, and because of this, I think I'm facing this issue. Here is the reference
Here is what I have done so far:
MainPage.xaml
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:vm="clr-namespace:poc_maui.ViewModels"
x:Class="poc_maui.Views.HomePage"
xmlns:tabs="clr-namespace:poc_maui.Views.SubViews"
Title="HomePage">
<ContentPage.BindingContext>
<vm:MainPageViewModel />
</ContentPage.BindingContext>
<Grid RowDefinitions="50, *" RowSpacing="0">
<ScrollView Grid.Row="0" Orientation="Horizontal" VerticalOptions="Start" HorizontalScrollBarVisibility="Never"
Scrolled="ScrollView_Scrolled">
<StackLayout x:Name="TabsView"
Orientation="Horizontal"
BindableLayout.ItemsSource="{Binding Tabs}" Spacing="0">
<BindableLayout.ItemTemplate>
<DataTemplate>
<Grid RowDefinitions="*, 4" RowSpacing="0">
<Label Grid.Row="0"
Text="{Binding TabTitle}"
TextColor="White"
BackgroundColor="navy"
Padding="20,0"
VerticalTextAlignment="Center"
HorizontalTextAlignment="Center"
FontSize="12"
HeightRequest="40"/>
<BoxView Grid.Row="1"
Color="Yellow"
IsVisible="{Binding IsSelected}"/>
<Grid.GestureRecognizers>
<TapGestureRecognizer Command="{Binding Path=BindingContext.TabChangedCommand,
Source={x:Reference TabsView}}"
CommandParameter="{Binding .}"/>
</Grid.GestureRecognizers>
</Grid>
</DataTemplate>
</BindableLayout.ItemTemplate>
</StackLayout>
</ScrollView>
<tabs:ParentRecordTabView Grid.Row="1" IsVisible="{Binding IsParentRecordTabVisible}"
VerticalOptions="FillAndExpand"/>
<tabs:AdditionalInfoTabView Grid.Row="1" IsVisible="{Binding IsAdditionalInfoTabVisible}"
VerticalOptions="FillAndExpand" />
</Grid>
</ContentPage>
MainPageViewModel
using System;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Windows.Input;
using poc_maui.Models;
namespace poc_maui.ViewModels
{
public class MainPageViewModel : BaseViewModel
{
#region Constructor
public MainPageViewModel()
{
GetTabs();
}
#endregion
#region Private Properties
private bool _isParentRecordTabVisible = true;
private bool _isAdditionalInfoTabVisible;
private ObservableCollection<TabViewModel> _tabs { get; set; }
#endregion
#region Public Properties
public bool IsParentRecordTabVisible
{
get => _isParentRecordTabVisible;
set { _isParentRecordTabVisible = value; OnPropertyChanged(nameof(IsParentRecordTabVisible)); }
}
public bool IsAdditionalInfoTabVisible
{
get => _isAdditionalInfoTabVisible;
set { _isAdditionalInfoTabVisible = value; OnPropertyChanged(nameof(IsAdditionalInfoTabVisible)); }
}
public ObservableCollection<TabViewModel> Tabs
{
get => _tabs;
set { _tabs = value; OnPropertyChanged(nameof(Tabs)); }
}
#endregion
#region Commands
public ICommand TabChangedCommand { get { return new Command<TabViewModel>(ChangeTabClick); } }
#endregion
#region Private Methods
private void GetTabs()
{
Tabs = new ObservableCollection<TabViewModel>();
Tabs.Add(new TabViewModel { TabId = 1, IsSelected = true, TabTitle = "Parent record" });
Tabs.Add(new TabViewModel { TabId = 2, TabTitle = "Additional Info" });
Tabs.Add(new TabViewModel { TabId = 3, TabTitle = "Contacts" });
Tabs.Add(new TabViewModel { TabId = 4, TabTitle = "Previous inspections" });
Tabs.Add(new TabViewModel { TabId = 5, TabTitle = "Attachments" });
SelectedTab = Tabs.FirstOrDefault();
}
private void ChangeTabClick(TabViewModel tab)
{
try
{
var tabs = new ObservableCollection<TabViewModel>(Tabs);
foreach (var item in tabs)
{
if (item.TabId == tab.TabId)
{
item.IsSelected = true;
}
else
{
item.IsSelected = false;
}
}
Tabs.Clear();
Tabs = new ObservableCollection<TabViewModel>(tabs);
switch (tab.TabId)
{
case 1:
IsParentRecordTabVisible = true;
IsAdditionalInfoTabVisible = false;
break;
case 2:
IsParentRecordTabVisible = false;
IsAdditionalInfoTabVisible = true;
break;
}
}
catch (Exception ex)
{
Debug.WriteLine(ex.Message);
}
}
#endregion
}
}
#ParentTabView.xaml
<ContentView xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="poc_maui.Views.SubViews.ParentTabView">
<StackLayout HorizontalOptions="FillAndExpand" VerticalOptions="CenterAndExpand" >
<Label
Text="Welcome to Parent tab!"
VerticalOptions="Center"
HorizontalOptions="Center" />
</StackLayout>
</ContentView>
#AdditionalInfoTabView.xaml
<ContentView xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="poc_maui.Views.SubViews.AdditionalInfoTabView">
<StackLayout HorizontalOptions="FillAndExpand" VerticalOptions="CenterAndExpand" >
<Label
Text="Welcome to Additiona info tab!"
VerticalOptions="Center"
HorizontalOptions="Center" />
</StackLayout>
</ContentView>
So what happens here in Android is when I'm clicking AdditionalInfo Tab then it will show a blank white screen and if you press the hardware back button and open the app again it will show AdditionalTab as selected and its views content as well.
If I remove switch() code part from the ViewModel then it will work fine but tabs will not change. Does anyone have idea about this kind of behavior of scroll view in MAUI?
The full source code is here: maui_sample
Does this work-around fix it?
MainPage.xaml:
<ScrollView x:Name "theScrollView" ... >
MainPage.xaml.cs:
public MainPage()
{
InitializeComponent();
MessagingCenter.Subscribe<MainPageViewModel>(this, "update", (sender) =>
{
// Tell theScrollView to re-layout its contents.
(theScrollView as IView).InvalidateArrange();
});
}
MainPageViewModel:
private void ChangeTabClick(TabViewModel tab)
{
... make changes ...
MessagingCenter.Send<MainPageViewModel>(this, "update");
}
MAYBE:
I'm not sure if MessagingCenter Subscribe is on Dispatcher (Main) thread. To be reliable, do:
MessagingCenter.Subscribe<MainPageViewModel>(this, "update", (sender) =>
{
Dispatcher.Dispatch( () =>
{
(theScrollView as IView).InvalidateArrange();
});
}
UPDATE
There are other Maui bugs, that have a common "theme": Maui on Android does "something" related to layout only once - at the time the page is first drawn. UNFORTUNATELY, anything that is "not visible" at that time, is skipped. And won't work when later made visible.
Until such bugs are fixed, you'll have to do some work-around.
WORK-AROUND #1:
Start with ALL tabs IsVisible="True".
As soon as the page has been drawn the first time, in code-behind, create the desired Bindings on those IsVisible properties. Page drawn first time can be intercepted in a custom handler. But this is a temp work-around, so its easier to just run a method after a 250 ms delay. Use a boolean "flag" to make the method only run the first time.
Might have to do InvalidateArrange as shown above, to force the Bindings to function the first time.
OR WORK-AROUND #2:
Each time tab changes, use shell route to go to MainPage again. Keep same view model, so knows which tab to show first (and remembers any other state you care about).
Both of these are ugly.
I recommend creating an issue at .Net Maui github, and providing link to your github sample.
This is still not works for me properly but after looking at below two links I found that it it not what we are looking for. The Isvisible : false first and then on switch or check box change you are trying to make it visible then it will not visible but the actual control visible. So on look after I have see this link but again the answer is not what I was looking for.
Step to resolve.
On View use the Parent as ScrollView or control belongs to IView,IElement.
<ScrollView x:Name "myScrollView">
.....
...
Add Action on ViewModel
public delegate void Action(T obj);
Invoke the Action
Note: Make sure you call this on require not all the time.
e.g. On Visibility set in ViewModel call after visibility update.
MeasureAction?.Invoke("reSetVisibility");
Now on View's Code File, use Viewmodel and accept the invoke
Here Call the below line will works perfectly.
(myScrollView as IView).InvalidateMeasure();
That's IT... Enjoy IsVisible now and make your layout as require.

How to print Generated Barcode ZXING in xamarin forms

I managed to generate a Barcode using ZXING library, now I want to print the generated barcode as it is from the ZXingBarcodeImageView with the same size ,how can I do that
private async Task<Item> getItem()
{
HttpResponseMessage _response = await _client.GetAsync(url + txt_Barcode.Text);
if (_response.IsSuccessStatusCode)
{
string itemDetailes = await _response.Content.ReadAsStringAsync();
return JsonConvert.DeserializeObject<Item>(itemDetailes);
}
return new Item();
}
private async void Btn_GetInfo(object sender, EventArgs e)
{
var selected_item = await getItem();
// Item_barcode.Text = selected_item.Value;
Item_Description.Text = selected_item.Description;
Item_name.Text = selected_item.Name;
Item_price.Text = selected_item.Price.ToString();
item_barcode.BarcodeValue = selected_item.Value;
}
xaml code
<!--<Image HeightRequest="300" WidthRequest="300" Margin="1" x:Name="img_barcode"></Image>-->
<zxing:ZXingBarcodeImageView x:Name="item_barcode" HeightRequest="20" WidthRequest="100" BarcodeFormat="CODE_39" BarcodeValue="Place Barcode" >
<zxing:ZXingBarcodeImageView.BarcodeOptions>
<zxingcomon:EncodingOptions Height="80" Width="1000"></zxingcomon:EncodingOptions>
</zxing:ZXingBarcodeImageView.BarcodeOptions>
</zxing:ZXingBarcodeImageView>
<StackLayout Orientation="Horizontal" Spacing="20" >
<Label TextColor="Black" x:Name="Item_Description"></Label>
<Label TextColor="Black" x:Name="Item_price"></Label>
</StackLayout>
<StackLayout x:Name="SLPrint" BackgroundColor="GhostWhite" VerticalOptions="FillAndExpand">
<Button Text="print" BackgroundColor="Black" TextColor="White"></Button>
</StackLayout>
</StackLayout>
</StackLayout>
The best and easiest option to do this is show the barcode inside webview.
here is a library which u can use Link
you can copy the script in asset folder. create a html file import the script
Now from xamarin side Evalute javascript in webview where you would send the barcode data to webview
here is how you can do it.
Link
note : you dont need to do any custom rendering
with this you can remove the zxing and can make your app size small

Xamarin Navigation with Modal page

I need to open modal window in my app, to do it we use method like this:
public async Task OpenModal<T>(object parameter = null)
where T : BaseViewModel
{
var modal = _pageService.CreatePageFor<T>(parameter);
await _navigation.PushModalAsync(modal, false);
}
The call is done in another viewmodel, which shows modal page. After everything is done, I have to go to the root page. I call
await _navigation.PopToRootAsync(false);
and after that
await _navigation.PopModalAsync();
The thing is that PopToRootAsync never completes and the call await for ever for iOS (not for Android). If I change order of PopToRootAsync and PopModalAsync, then I have flashing, which is not acceptable in our case.
I read this:
https://forums.xamarin.com/discussion/22156/poptorootasync-with-modal
but still cannot find the solution, any suggestions?
If you want to ensure a smooth transition animation my suggestion would be to manually remove all pages from the navigation stack but the root and then proceed to pop your modal. Here's some example code to give you an idea of what you might have to add to your navigation service implementation.
var navigationStack = MainPage.Navigation.NavigationStack.ToList();
var rootPage = navigationStack.FirstOrDefault();
for (int i = navigationStack.Count - 1; i >= 0; i--)
{
var page = navigationStack[i];
if (page.GetType() == rootPage.GetType())
{
return;
}
else
{
Navigation.RemovePage(page);
}
}
Navigation.PopModalAsync(true)
Use absolutelayout!
<AbsoluteLayout xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="className">
<!-- Transparent Background -->
<StackLayout AbsoluteLayout.LayoutBounds="0, 0, 1, 1"
AbsoluteLayout.LayoutFlags="All"
BackgroundColor="Gray"
Opacity="0.5"
>
</StackLayout>
<!-- Content -->
<ContentView x:Name="Overlay"
AbsoluteLayout.LayoutBounds="0.5, 0.5, 0.5, 0.5"
AbsoluteLayout.LayoutFlags="All"
BackgroundColor="Transparent"
Padding="10, 0">
<Label Text="myLabel">
<!-- Overlay -->
</ContentView>
</AbsoluteLayout>

Xamarin Forms PanGesture within a View

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?

How to make the loading screen to disappear when my data is loaded in Xamarin Forms?

I want to build a loading screen when no data is displayed. But it's not working, it keeps loading forever. How to make the loading screen to disappear when my data is loaded?
This is my C# code
if (Clublistview.ItemsSource == null)
{
try
{
base.OnAppearing();
await setClubs(Clublistview);
overlay.IsVisible = false;
Clublistview.IsVisible = true;
}
catch (Exception ex)
{
//MessagingCenter
await DisplayAlert("Error",
"There seems to be an error, please check your internet connection.",
"OK");
}
}
else
{
overlay.IsVisible = true;
Clublistview.IsVisible = false;
}
This is the XAML code
<ListView x:Name="Clublistview" HasUnevenRows="true" ItemSelected="OnItemSelected" ItemsSource="{Binding Id}" IsVisible="true">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell Height="55">
<StackLayout BackgroundColor="White"
Orientation="Vertical">
<StackLayout Orientation="Horizontal" Padding="2,2,2,2">
<Image Source="{Binding Logo}" IsVisible="true" WidthRequest="50" HeightRequest="50"/>
<StackLayout Orientation="Vertical">
<Label Text="{Binding Name}" FontSize="20" x:Name="BtnClub"
TextColor="Black" />
<Label HorizontalOptions="Start" Text="Select for more info" FontSize="10"/>
<!--<Button BackgroundColor="White" TextColor="Black" HorizontalOptions="Start" x:Name="btnInfo"
Text="Select for more info" FontSize="10" Clicked="OnInfoClicked" CommandParameter="{Binding Id}"/>-->
</StackLayout>
</StackLayout>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<ContentView x:Name="overlay" HorizontalOptions="CenterAndExpand" VerticalOptions="CenterAndExpand" AbsoluteLayout.LayoutFlags="All" IsVisible="false">
<ActivityIndicator IsRunning="True" IsVisible="True" Color="Black" HorizontalOptions="CenterAndExpand" VerticalOptions="CenterAndExpand"/>
</ContentView>
It looks like this code is placed on the OnAppearing method of your ContentPage. If that's the case, it's only going to be called 1 time as the page is shown. Assuming that Clublistview.ItemsSource is not null, then this code gets executed:
overlay.IsVisible = true;
Clublistview.IsVisible = false;
This means that your overlay is visible and the ActivityIndicator will be spinning. If this is not in OnAppearing then I am not sure when you are calling the method it is in.
You might want to do something like this instead:
public override async void OnAppearing()
{
base.OnAppearing();
// Show your overlay
overlay.IsVisible = true;
Clublistview.IsVisible = false;
// Load the items into the ItemsSource
await setClubs(Clublistview);
// Hide the overlay
overlay.IsVisible = false;
Clublistview.IsVisible = true;
}
You can achieve this type of behavior in a cleaner way with the MVVM pattern. With MVVM you can use a property binding to control when the overlay is shown. We have some guides on MVVM and Xamarin.Forms that can help get you started here. Here is a blog post that shows an example too.
This is what worked for me:
protected override void OnAppearing()
{
base.OnAppearing();
this.layoutLoadingSpinner.IsVisible = true;
this.layoutContent.IsVisible = false;
// Load slow-loading model on a separate thread
MySlowLoadingModel model = null;
await Task.Run(() =>
{
model = new MySlowLoadingModel();
});
this.BindingContext = model;
this.layoutLoadingSpinner.IsVisible = false;
this.layoutContent.IsVisible = true;
}
(Another option that avoids async/await is to call MainThread.BeginInvokeOnMainThread() inside the Task.Run)
An unfortunate side-effect is that any code run on the second thread becomes very difficult to debug. It seems the Xamarin debugger doesn't work right with multiple threads.

Categories

Resources