I'm playing with the COOL xamarin shell, but I didn't found a way to change icon of the selected tab.
<TabBar Route="sections">
<Tab Title="home">
<Tab.Icon>
<FontImageSource FontFamily="{StaticResource AppIcons}" Glyph="{x:Static framework:Icons.HomePage}" />
</Tab.Icon>
<ShellContent ContentTemplate="{DataTemplate home:HomePage}" Route="home" />
</Tab>
The goal is to use Icons.HomePageFilled instead of Icons.HomePage for this tab only when it's selected. Same logic should apply to other tabs.
I think I got lost in the solutions found on the web. They talk about Custom renderers(ShellTabLayoutAppearanceTracker), Visual states, effects etc ...
But I do not know if it is feasible and what is the ideal solution
You need to use custom renderer of shell to customize the tabbar selected icon on each platform.
In iOS, override the CreateTabBarAppearanceTracker method:
[assembly: ExportRenderer(typeof(AppShell), typeof(MyShellRenderer))]
namespace App30.iOS
{
public class MyShellRenderer : ShellRenderer
{
protected override IShellSectionRenderer CreateShellSectionRenderer(ShellSection shellSection)
{
var renderer = base.CreateShellSectionRenderer(shellSection);
if (renderer != null)
{
}
return renderer;
}
protected override IShellTabBarAppearanceTracker CreateTabBarAppearanceTracker()
{
return new CustomTabbarAppearance();
}
}
public class CustomTabbarAppearance : IShellTabBarAppearanceTracker
{
public void Dispose()
{
}
public void ResetAppearance(UITabBarController controller)
{
}
public void SetAppearance(UITabBarController controller, ShellAppearance appearance)
{
UITabBar myTabBar = controller.TabBar;
if (myTabBar.Items != null)
{
UITabBarItem itemOne = myTabBar.Items[0];
itemOne.Image = UIImage.FromBundle("tab_about.png");
itemOne.SelectedImage = UIImage.FromBundle("tab_feed.png");
UITabBarItem itemTwo = myTabBar.Items[1];
itemTwo.Image = UIImage.FromBundle("tab_feed.png");
itemTwo.SelectedImage = UIImage.FromBundle("tab_about.png");
//The same logic if you have itemThree, itemFour....
}
}
public void UpdateLayout(UITabBarController controller)
{
}
}
}
In Android, override the CreateBottomNavViewAppearanceTracker method:
[assembly: ExportRenderer(typeof(AppShell), typeof(MyShellRenderer))]
namespace App30.Droid
{
public class MyShellRenderer : ShellRenderer
{
public MyShellRenderer(Context context) : base(context)
{
}
protected override IShellBottomNavViewAppearanceTracker CreateBottomNavViewAppearanceTracker(ShellItem shellItem)
{
return new CustomBottomNavAppearance();
}
}
public class CustomBottomNavAppearance : IShellBottomNavViewAppearanceTracker
{
public void Dispose()
{
}
public void ResetAppearance(BottomNavigationView bottomView)
{
}
public void SetAppearance(BottomNavigationView bottomView, ShellAppearance appearance)
{
IMenu myMenu = bottomView.Menu;
IMenuItem myItemOne = myMenu.GetItem(0);
if (myItemOne.IsChecked)
{
myItemOne.SetIcon(Resource.Drawable.tab_about);
}
else
{
myItemOne.SetIcon(Resource.Drawable.tab_feed);
}
//The same logic if you have myItemTwo, myItemThree....
}
}
}
I uploaded a sample project here and you can check it.
Related
I'm trying to show custom buttons for text selection in React-Native WebView on Android. I've created a custom component as described here. It works fine.
Following this answer, I'm trying to override ActionMode with custom ActionMode.Callback to display my custom menu. However it doesn't seem to work at all.
My code:
CustomWebViewManager.java
protected static class CustomWebView extends RNCWebView {
public Context context;
public CustomWebView(ThemedReactContext reactContext) {
super(reactContext);
this.context=context;
}
// setting custom action bar
private ActionMode mActionMode;
private ActionMode.Callback mSelectActionModeCallback;
private GestureDetector mDetector;
// this will over ride the default action bar on long press
#Override
public ActionMode startActionMode(Callback callback) {
ViewParent parent = getParent();
if (parent == null) {
return null;
}
String name = callback.getClass().toString();
if (name.contains("SelectActionModeCallback")) {
mSelectActionModeCallback = callback;
mDetector = new GestureDetector(context,
new CustomGestureListener());
}
CustomActionModeCallback mActionModeCallback = new CustomActionModeCallback();
return parent.startActionModeForChild(this, mActionModeCallback);
}
private class CustomActionModeCallback implements ActionMode.Callback {
#Override
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
mActionMode = mode;
MenuInflater inflater = mode.getMenuInflater();
inflater.inflate(R.menu.menu, menu);
return true;
}
//overriding onPrepareActionMode, onActionItemClicked and onDestroyActionMode as usual
...
}
private class CustomGestureListener extends GestureDetector.SimpleOnGestureListener {
#Override
public boolean onSingleTapUp(MotionEvent e) {
if (mActionMode != null) {
mActionMode.finish();
return true;
}
return false;
}
}
#Override
public boolean onTouchEvent(MotionEvent event) {
if(mDetector !=null)
mDetector.onTouchEvent(event);
return super.onTouchEvent(event);
}
}
What I'm doing wrong here? Is that a correct approach?
Thanks.
im working with custon entry rendered, i need to hear from xaml in my custom render when i clicked my button
i have this code in my xaml
<local:MyEntry eventRefresh="true">
when i clicked my button this function is actived
private async void Execute(object sender)
{
var entry = ((MyEntry)view);
entry.eventRefresh = "false";
but my EntryRendered donot hear the change
protected override void OnElementChanged(ElementChangedEventArgs<Entry> e)
{
base.OnElementChanged(e);
if (Control != null)
{
var element = Element as MyEntry;
You should define the property eventRefresh as Bindable Property .
in your custom Entry
using System;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using Xamarin.Forms;
namespace xxx
{
public class MyEntry:Entry,INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public static readonly BindableProperty eventRefreshProperty = BindableProperty.Create("eventRefresh", typeof(bool), typeof(MyEntry), true,propertyChanged:(obj,oldValue,newValue)=> {
//var entry = obj as MyEntry;
// entry.Text = newValue.ToString();
});
bool refresh;
public bool eventRefresh
{
get { return refresh; }
set {
if(refresh !=value)
{
refresh = value;
NotifyPropertyChanged("eventRefresh");
}
}
}
public MyEntry()
{
}
}
}
in xaml
<StackLayout VerticalOptions="CenterAndExpand" HorizontalOptions="CenterAndExpand">
<local:MyEntry eventRefresh="{Binding Refresh}" BackgroundColor="{Binding BGcolor}" WidthRequest="200" HeightRequest="50" />
<Button Command="{Binding ClickCommand}" />
</StackLayout>
in View Model
public class MyViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
Color color;
public Color BGcolor
{
get { return color; }
set
{
if (color != value)
{
color = value;
NotifyPropertyChanged("BGcolor");
}
}
}
bool refresh;
public bool Refresh
{
get { return refresh; }
set
{
if (refresh != value)
{
refresh = value;
NotifyPropertyChanged("Refresh");
}
}
}
public ICommand ClickCommand { get; set; }
public MyViewModel()
{
BGcolor = Color.LightPink;
ClickCommand = new Command(()=> {
BGcolor = Color.Red;
});
}
}
in Custom Renderer
using System.ComponentModel;
using Android.Content;
using xxx;
using xxx.Droid;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
[assembly:ExportRenderer(typeof(MyEntry),typeof(NyEntryRenderer))]
namespace xxx.Droid
{
public class NyEntryRenderer : EntryRenderer
{
public NyEntryRenderer(Context context) : base(context)
{
}
protected override void OnElementChanged(ElementChangedEventArgs<Entry> e)
{
base.OnElementChanged(e);
if(Control!=null)
{
Element.TextChanged += Element_TextChanged;
}
}
private void Element_TextChanged(object sender, TextChangedEventArgs e)
{
// var content = Element.Text;
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
if (e.PropertyName == MyEntry.BackgroundColorProperty.PropertyName)
{
// will been invoked when click button
}
}
}
}
Make your view model like this.
public class YourViewModel
{
public Command command
{
get
{
return new Command(() => {
//Change here button background colors
BackgroundColor = Color.Green;
});
}
}
private _backgroundColor = Color.White;
public Color BackgroundColor
{
get { return _backgroundColor;}
set
{
if (value == _backgroundColor)
return;
_backgroundColor = value;
NotifyOnPropertyChanged(nameof(BackgroundColor));
}
}
}
Your XAML
<local:MyEntry Text="{Binding Password}" Placeholder="Enter" />
<Button Text="send" Command="{Binding command}" BackgroundColor="{Binding BackgroundColor}"></Button>
I am trying develop a xamarin app which has tabbed pages.
I have 3 main tabs.Each page viewmodel contructor has 3-5 Api calls.So its taking more time(20s) to load my app(for opening).
mainpage.xaml
<?xml version="1.0" encoding="utf-8" ?>
<TabbedPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Myapplication.Views.MenuPage"
xmlns:prism="clr-namespace:Prism.Mvvm;assembly=Prism.Forms"
xmlns:b="clr-namespace:Prism.Behaviors;assembly=Prism.Forms"
prism:ViewModelLocator.AutowireViewModel="True"
xmlns:android="clr-namespace:Xamarin.Forms.PlatformConfiguration.AndroidSpecific;assembly=Xamarin.Forms.Core"
xmlns:views="clr-namespace:Dyocense.Views"
android:TabbedPage.ToolbarPlacement="Bottom"
android:TabbedPage.IsSwipePagingEnabled="False"
>
<views:A Title="A" Icon="dsjsdsd_18dp.png" ></views:A>
<views:B Title="B" Icon="askjasa.png"></views:B>
<views:C Title="C" Icon="abc.png"></views:C>
<views:D Title="D" Icon="abc.png"></views:D>
</TabbedPage>
How to load only first tab(A) detail page on app loading and rest of the pages on tab changing.
A solution is to make the heavy pages load their content in a lazy manner, only when their tab becomes selected. This way, since these pages are now empty when TabbedPage is created, navigating to the TabbedPage suddenly becomes very fast!
1.create a behavior for the TabbedPage page, called ActivePageTabbedPageBehavior.
class ActivePageTabbedPageBehavior : Behavior<TabbedPage>
{
protected override void OnAttachedTo(TabbedPage tabbedPage)
{
base.OnAttachedTo(tabbedPage);
tabbedPage.CurrentPageChanged += OnTabbedPageCurrentPageChanged;
}
protected override void OnDetachingFrom(TabbedPage tabbedPage)
{
base.OnDetachingFrom(tabbedPage);
tabbedPage.CurrentPageChanged -= OnTabbedPageCurrentPageChanged;
}
private void OnTabbedPageCurrentPageChanged(object sender, EventArgs e)
{
var tabbedPage = (TabbedPage)sender;
// Deactivate previously selected page
IActiveAware prevActiveAwarePage = tabbedPage.Children.OfType<IActiveAware>()
.FirstOrDefault(c => c.IsActive && tabbedPage.CurrentPage != c);
if (prevActiveAwarePage != null)
{
prevActiveAwarePage.IsActive = false;
}
// Activate selected page
if (tabbedPage.CurrentPage is IActiveAware activeAwarePage)
{
activeAwarePage.IsActive = true;
}
}
}
2.define IActiveAware interface
interface IActiveAware
{
bool IsActive { get; set; }
event EventHandler IsActiveChanged;
}
3.create a base generic abstract class called LoadContentOnActivateBehavior
abstract class LoadContentOnActivateBehavior<TActivateAwareElement> : Behavior<TActivateAwareElement>
where TActivateAwareElement : VisualElement
{
public DataTemplate ContentTemplate { get; set; }
protected override void OnAttachedTo(TActivateAwareElement element)
{
base.OnAttachedTo(element);
(element as IActiveAware).IsActiveChanged += OnIsActiveChanged;
}
protected override void OnDetachingFrom(TActivateAwareElement element)
{
(element as IActiveAware).IsActiveChanged -= OnIsActiveChanged;
base.OnDetachingFrom(element);
}
void OnIsActiveChanged(object sender, EventArgs e)
{
var element = (TActivateAwareElement)sender;
element.Behaviors.Remove(this);
SetContent(element, (View)ContentTemplate.CreateContent());
}
protected abstract void SetContent(TActivateAwareElement element, View contentView);
}
4.the specialized LazyContentPageBehavior
class LazyContentPageBehavior : LoadContentOnActivateBehavior<ContentView>
{
protected override void SetContent(ContentView element, View contentView)
{
element.Content = contentView;
}
}
then we can use in xaml like this:
<TabbedPage>
<TabbedPage.Behaviors>
<local:ActivePageTabbedPageBehavior />
</TabbedPage.Behaviors>
<ContentPage Title="First tab">
<Label Text="First tab layout" />
</ContentPage>
<local:LazyLoadedContentPage Title="Second tab">
<ContentPage.Behaviors>
<local:LazyContentPageBehavior ContentTemplate="{StaticResource ContentTemplate}" />
</ContentPage.Behaviors>
<ContentPage.Resources>
<ResourceDictionary>
<DataTemplate x:Key="ContentTemplate">
<!-- Complex and slow to render layout -->
<local:SlowContentView />
</DataTemplate>
</ResourceDictionary>
</ContentPage.Resources>
</local:LazyLoadedContentPage>
</TabbedPage>
we moved the ContentPage complex layout to become a DataTemplate.
Here's the custom LazyLoadedContentPage page which is activation aware:
class LazyLoadedContentPage : ContentPage, IActiveAware
{
public event EventHandler IsActiveChanged;
bool _isActive;
public bool IsActive
{
get => _isActive;
set
{
if (_isActive != value)
{
_isActive = value;
IsActiveChanged?.Invoke(this, EventArgs.Empty);
}
}
}
}
SlowContentView do some complex things
public partial class SlowContentView : ContentView
{
public SlowContentView()
{
InitializeComponent();
// Simulating a complex view
...
}
}
you could refer to the link
As a workaround I created a new class extending the Xamarin.Forms.TabbedPage and I send a message every time one of the tabs is clicked (or in general is diplayed)
public enum TabbedPages
{
MyPage1 = 0,
MyPage2 = 1,
MyPage3 = 2,
}
public class BottomBarPage : Xamarin.Forms.TabbedPage
{
protected override void OnCurrentPageChanged()
{
base.OnCurrentPageChanged();
var newCurrentPage = (TabbedPages)Children.IndexOf(CurrentPage);
MessagingCenter.Send<Xamarin.Forms.TabbedPage>(this, newCurrentPage.ToString("g"));
}
}
and then on the view models used for each page loaded when clicking on the tab I subscribe to the message and call my APIs
public class MyPage2ViewModel
{
public MyPage2ViewModel()
{
MessagingCenter.Subscribe<TabbedPage>(this, TabbedPages.MyPage2 .ToString("g"), async (obj) =>
{
//API call
});
}
}
I have a carousel page and a home icon in the toolbar. When clicking the home icon, error appears.
Code in Xaml
<ContentPage.ToolbarItems>
<ToolbarItem Icon="home.png" Command="{Binding HomeCommand}"/>
</ContentPage.ToolbarItems>
Code in ViewModel
public class InfoItemViewModel : ObservableObject, INavigatedAware
{
public DelegateCommand HomeCommand { get; private set; }
private INavigationService mNavigationService;
public INavigationService navigationService;
public InfoItemViewModel(int productId, INavigationService navigationService)
: base(listenCultureChanges: true)
{
_productId = productId;
mNavigationService = navigationService;
HomeCommand = new DelegateCommand(HomeAction);
LoadData();
}
private async void HomeAction()
{
Debug.WriteLine("Button : Home");
await mNavigationService.NavigateAsync("DashboardMenuPage");
}
Code in Xaml.cs
public partial class InfoItem : ContentPage
{
public InfoItem()
: **this**(DataMyDOF.Products[0].Id)
{
}
public InfoItem(int productId, INavigationService navigationService)
{
InitializeComponent();
BindingContext = new InfoItemViewModel(productId, navigationService);
}
}
}
The error 'this'
There is no argument given that corresponds to the required formal
parameter ‘navigationService’ of ‘InfoItem.InfoItem(int,
INavigationService)’
After i add navigationService in the constructor, 'this' appears error. Actually I combine the carousel code from grial with prism. Products[0].Id is the array id. Should i add the navigation as the parameter too?
I tried to use Xamarin Forms Navigation. But still there's an error.
using Xamarin.Forms;
public INavigation Navigation { get; }
private void HomeAction()
{
Debug.WriteLine("Button : Home");
Navigation.PushAsync(new NavigationPage(new DashboardMenuPage()));
}
The error
System.NullReferenceException has been thrown Object reference not set
to an instance of an object
like the title says I want to give through the user information to my viewmodel, but the problem is that the viewmodel is registered as a dependency and I am binding its content to the xaml page itself. How do I send the user information to the viewmodel itself?
Thank you!
Xaml.cs part:
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class Calendar : ContentPage
{
public Calendar(User user)
{
InitializeComponent();
FileImageSource image = new FileImageSource
{
File = "calendar.png"
};
Icon = image;// push user information to the ICalendarViewModel
BindingContext = AppContainer.Container.Resolve<ICalendarViewModel>();
}
}
Interface:
public interface ICalendarViewModel : INotifyPropertyChanged
{
}
Bootstrap part registering dependencies:
public class Bootstrap
{
public IContainer CreateContainer()
{
var containerBuilder = new ContainerBuilder();
RegisterDependencies(containerBuilder);
return containerBuilder.Build();
}
protected virtual void RegisterDependencies(ContainerBuilder builder)
{
builder.RegisterType<CalendarViewModel>()
.As<ICalendarViewModel>()
.SingleInstance();
}
}
CalendarViewModel: I do not know if this will help
public class CalendarViewModel : ViewModelBase, ICalendarViewModel
{
public event PropertyChangedEventHandler PropertyChanged;
public string ErrorMessage { get; set; }
private CourseInformation _information;
private ICourseInformationRepository _repository;
public CalendarViewModel()
{
_repository = new CourseInformationRepository();
LoadData();
}
private ObservableCollection<CourseInformation> _courses;
public ObservableCollection<CourseInformation> Courses
{
get
{
return _courses;
}
set
{
_courses = value;
RaisePropertyChanged(nameof(Courses));
}
}
private void LoadData()
{
try
{
ObservableCollection<CourseInformation> CourseList = new ObservableCollection<CourseInformation>(_repository.GetAllCourseInformation());
Courses = new ObservableCollection<CourseInformation>();
DateTime date;
foreach (var course in CourseList)
{
string [] cour = course.Date.Split('/');
cour[2] = "20" + cour[2];
date = new DateTime(Convert.ToInt32(cour[2]), Convert.ToInt32(cour[1]), Convert.ToInt32(cour[0]));
if (date == DateTime.Now)//TESTING WITH TEST DATE, datetime.now
{
if (course.FromTime.Length < 4)
{
course.FromTime = "0" + course.FromTime;
}
if (course.UntilTime.Length < 4)
{
course.UntilTime = "0" + course.UntilTime;
}
course.FromTime = course.FromTime.Insert(2, ":");
course.UntilTime = course.UntilTime.Insert(2, ":");
Courses.Add(course);
}
}
}
catch (ServerUnavailableException e)
{
ErrorMessage = "Server is niet beschikbaar, ophalen van kalender is niet mogelijk.";
}
}
private void RaisePropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Bootstrap binding in app.xaml.cs:
public partial class App : Application
{
public App()
{
InitializeComponent();
AppContainer.Container = new Bootstrap().CreateContainer();
MainPage = new LoginView();
}
protected override void OnStart()
{
// Handle when your app starts
}
protected override void OnSleep()
{
// Handle when your app sleeps
}
protected override void OnResume()
{
// Handle when your app resumes
}
}
I wanted to comment (not enough reputation) on #LeRoy, use a framework. I would recommend FreshMVVM and you can pass objects into the ViewModel and even pass in Services. It makes it all nice and clean, and it just works.
Should not your CalendarViewModel viewModel contain BindableBase ?
public class CalendarViewModel : BindableBase, ViewModelBase, ICalendarViewModel
what framework are you using? prism, freshmvvm.
Your View and Viewmodel is normally automatically handled by the framework, all you need to do is register your page.
Container.RegisterTypeForNavigation<Views.CalendarPage>();