Xamarin Android TabLayout Badge: Bind Text Mvvmcross - c#

I made a Custom Layout to add a badge to a tab on Android. This badge is a TextView.
How do I bind this component?
It's possible?
My custom badge layout:
<LinearLayout
android:id="#+id/badgeCotainer"
android:layout_width="wrap_content"
android:layout_height="20dp"
android:layout_marginStart="90dp"
android:background="#drawable/notifications_background"
android:gravity="center"
android:layout_gravity="right"
android:minWidth="16dp">
<TextView
android:id="#+id/badge"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAlignment="center"
android:textColor="#011f7a"
android:textSize="10sp"
android:text="0"
app:MvxBind="Text ContMaterial"/>
</LinearLayout>
My Activity: use MvxViewPagerFragmentInfo
private void InitTabs()
{
var viewPager = FindViewById<ViewPager>(Resource.Id.materiais_viewpager);
if (viewPager != null)
{
var fragments = new List<MvxViewPagerFragmentInfo>
{
new MvxViewPagerFragmentInfo("Requisição",
typeof(CadastraRequisicaoFragment), ViewModel),
new MvxViewPagerFragmentInfo("Materiais",
typeof(ListaMateriaisFragment), ViewModel),
};
viewPager.Adapter = new MvxCachingFragmentStatePagerAdapter(this, SupportFragmentManager, fragments);
}
var tabLayout = FindViewById<TabLayout>(Resource.Id.requisicao_tabs);
tabLayout.SetupWithViewPager(viewPager);
tabLayout.GetTabAt(1).SetCustomView(Resource.Layout.tab_header_badge);
}
On view Model I used raisedPropertyChanged()
Badge appears normally, just don't change value.
Tablayout

Out of the box there is nothing in mvvmcross to bind the content of a TabLayout when you provide a custom view. You could however try something custom using MvxFrameControl
Basically, replace your LinearLayout by the MvxFrameControl, then after
tabLayout.SetupWithViewPager(viewPager);
inflate your custom view using the BindingInflate method, and set a value to your MvxFrameControl's BindingContext, then set your custom tablayout view with the result that operation.
That might do the trick.
Another way to do it, is to listen to your ViewModel's "PropertyChanged" events in your Activity, and manually make changes to your tab layouts when it applies.

Related

How can you set AnimateLayoutChanges to True via C# in Xamarin.Android?

How can you set AnimateLayoutChanges to True via C# in Xamarin.Android for a linear layout? I cannot use an xml resource file when building this custom view. The xml for this linear layout would be this:
<LinearLayout
android:orientation="vertical"
android:minWidth="25px"
android:minHeight="25px"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:animateLayoutChanges="true"
android:id="#+id/linearLayoutOne"
android:layout_weight="1"/>
All of the other attributes can be set via C# via the following:
LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MatchParent, LinearLayout.LayoutParams.WrapContent, 1);
linearLayoutOne = new LinearLayout(Context);
linearLayoutOne.Orientation = Orientation.Vertical;
linearLayoutOne.LayoutParameters = layoutParams;
linearLayoutOne.SetMinimumHeight(25);
linearLayoutOne.SetMinimumWidth(25);
So how in the world do you set AnimateLayoutChanges in C#?
The C# equivalent of setting that is:
linearLayoutOne.LayoutTransition = new LayoutTransition();
linearLayoutOne.LayoutTransition.EnableTransitionType(LayoutTransitionType.Changing);

Add icons to TabLayout using MvvmCross

I've been looking at the Playground project on MvvmCross (https://github.com/MvvmCross/MvvmCross/tree/develop/Projects/Playground)
The way to have a fragment loaded into the tabs is to set the attribute the following way:
[MvxTabLayoutPresentation(TabLayoutResourceId = Resource.Id.tabs, ViewPagerResourceId = Resource.Id.viewpager, Title = "Tab 1", ActivityHostViewModelType = typeof(TabsRootViewModel))]
[MvxTabLayoutPresentation(TabLayoutResourceId = Resource.Id.tabs, ViewPagerResourceId = Resource.Id.viewpager, Title = "Tab 1", FragmentHostViewType = typeof(TabsRootBView))]
[Register(nameof(Tab1View))]
public class Tab1View : MvxFragment<Tab1ViewModel>
My questions is, besides the title that can be specified on the MvxTabLayoutPresentation, how can I add and icon to each one of the tabs?
That is not provided out-of-the-box and it's more about TabLayout customization.
You can achieve it like this.
Create a CustomView for your tab layout item, myCustomTab.axml:
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/txtTab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:textColor="#FFFFFF"
android:textSize="12"
android:textStyle="bold" />
Then in your view that has the TabLayout you configure it on the OnCreate / OnCreateView:
var tabLayout = view.FindViewById<TabLayout>(Resource.Id.tabs);
var customTab = inflater.Inflate(Resource.Layout.myCustomTab, null);
customTab.Text = "MyText";
// this sets the icon above the text
customTab.SetCompoundDrawablesWithIntrinsicBounds(0, Resource.Drawable.my_icon, 0, 0);
tabLayout.GetTabAt(0).SetCustomView(customTab);
Obviously you have to do this as many times as tab layout items you have.
Furthermore using this you can add any customization to your tab layout items.
Source (in java): https://mobikul.com/make-custom-tabs-icons-android/
HIH

How to automatically add layout elements with c#?

I want to add elements into my layout automatically, how do I do that? For example a textview appears when a user clicks a button
To add a TextView or any other view in Android you must add the view you want to a view that support addition, one example is the LinearLayout.
If you have this layout:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/lnrRootView"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent" />
And then in you Activity add a TextView programaticlly to your LinearLayout.
public class MainActivity : Activity
{
protected override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
// Set our view from the "main" layout resource
SetContentView(Resource.Layout.Main);
var linearLayout = FindViewById<LinearLayout>(Resource.Id.lnrRootView);
var textView = new TextView(this);
textView.Text = "Added programaticlly";
linearLayout.AddView(textView);
}
}
You will get something like this:
Much depends on what you're trying to accomplish. You could simulate adding to layout by turning visibility of elements after certain event, eg. button click.
If you want to add elements dynamically during runtime, consider using
ObservableCollection<T>
Link: https://developer.xamarin.com/api/type/System.Collections.ObjectModel.ObservableCollection%601/
I don't know Xamarin.Android very well but I think the right way is to add controls to layout with "IsVisible = false", then set IsVisible = true.
Otherwise you can take a look to
LinearLayout principalview = FindViewById(Resource.Id.mainlayout);
LinearLayout.LayoutParams parametros = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MatchParent, LinearLayout.LayoutParams.MatchParent);
var valueB = new Button(this);
valueB.Text = "Teste";
valueB.SetBackgroundColor(Color.Aqua);
principalview.AddView(valueB, parametros);
OR
var layout = new LinearLayout (this);
layout.Orientation = Orientation.Vertical;
var aLabel = new TextView (this);
aLabel.Text = "Hello, World!!!";
var aButton = new Button (this);
aButton.Text = "Say Hello!";
aButton.Click +=(sender, e) =>
{aLabel.Text="Hello Android!";};
layout.AddView (aLabel);
layout.AddView (aButton);
SetContentView (layout);

MvvmCross : Empty binding target passed to MvxTargetBindingFactoryRegistry

I created an Android application with Xamarin and MvvmCross.
I want to bind some views (text, editing text, button) to my ViewModel. Nothing strange well so far. But my bindings don’t apply… When I use typed FindViewById, I don’t get the traced error but bindings doesn’t apply.
When I run the application, I have the following trace:
MvxBind:Error: Empty binding target passed to MvxTargetBindingFactoryRegistry
MvxBind:Warning: Failed to create target binding for binding for TextProperty
My override of OnCreate(Bundle bundle) void is :
SetContentView(Resource.Layout.Reference);
var referenceTextView = FindViewById(Resource.Id.referenceEditView); // untyped FindViewById
var siteTextView = FindViewById<TextView>(Resource.Id.siteTextView); // typed FindViewById<T>
//var goButton = FindViewById<Button>(Resource.Id.goButton);
var bindingsSet = this.CreateBindingSet<ReferenceView, ReferenceViewModel>();
bindingsSet.Bind(referenceTextView).To(vm => vm.Reference).Mode(MvxBindingMode.TwoWay);
bindingsSet.Bind(siteTextView).To(vm => vm.Site);
//bindingsSet.Bind(goButton).To(vm => vm.GoCommand);
bindingsSet.Apply();
base.OnCreate(bundle);
I’ve tried to do in the AXML :
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="#+id/siteTextView"
android:text="####"
local:MvxBind="Text Site"
android:gravity="center" />
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="#+id/referenceTextView"
android:hint="Numéro de dossier"
local:MvxBind="Text Reference" />
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Accéder"
android:id="#+id/goButton"
local:MvxBind="Click GoCommand" />
The getters and setters of my properties use RaiseAndSetIfChanged method :
private string _reference;
public string Reference
{
get { return _reference; }
set { this.RaiseAndSetIfChanged(ref _reference, value, () => Reference); }
}
I’ve the same LinkerPleaseInclude class than LinkerPleaseInclude original class.
My setup inherits from MvxAndroidSetup class
And on other ViewModels, the bindings are applied correctly.
You need to call base.OnCreate(bundle); before SetContentView as the ViewModel is located and attached inside of that call. Failing to do so will obviously give you the exact errors you see. Source will be null and will not bind to the target.
So you can either do:
base.OnCreate(bundle);
SetContentView(Resource.Layout.Reference);
And have all your bindings in your AXML. Or you can do the other approach setting the bindings behind the scenes:
base.OnCreate(bundle);
SetContentView(Resource.Layout.Reference);
var referenceTextView = FindViewById<TextView>(Resource.Id.referenceEditView);
var siteTextView = FindViewById<TextView>(Resource.Id.siteTextView);
var bset = this.CreateBindingSet<ReferenceView, ReferenceViewModel>();
bset.Bind(referenceTextView).To(vm => vm.Reference);
bset.Bind(siteTextView).To(vm => vm.Site);
bset.Apply();
Just make sure to call base.OnCreate to begin with.
The warnings
MvxBind:Error: 2,20 Empty binding target passed to MvxTargetBindingFactoryRegistry
MvxBind:Warning: 2,20 Failed to create target binding for binding for Text
are caused by var referenceTextView = FindViewById(Resource.Id.referenceEditView); resulting in referenceTextView to be of type View.
MvvmCross is searching for the default binding target property of the type TTarget when calling Bind<TTArget> without For(targetProperty). This is just a look up in a table like:
TTarget Property
----------------------
TextView Text
Button Click
... ...
In your case TTarget is View instead of TextView, because you pass it into bindingsSet.Bind(referenceTextView) wich is the implicit call of bindings.Bind<View>(btnNumber). View has no default binding target property. You have to set it explicitly like
bindings.Bind(btnNumber).For("Text")
or use the typed FindViewById<TextView>.
I dont think you need to bind twice, remove these lines:
var referenceTextView = FindViewById(Resource.Id.referenceEditView); // untyped FindViewById
var siteTextView = FindViewById<TextView>(Resource.Id.siteTextView); // typed FindViewById<T>
//var goButton = FindViewById<Button>(Resource.Id.goButton);
var bindingsSet = this.CreateBindingSet<ReferenceView, ReferenceViewModel>();
bindingsSet.Bind(referenceTextView).To(vm => vm.Reference).Mode(MvxBindingMode.TwoWay);
bindingsSet.Bind(siteTextView).To(vm => vm.Site);
//bindingsSet.Bind(goButton).To(vm => vm.GoCommand);
bindingsSet.Apply();
So your on create is just this:
SetContentView(Resource.Layout.Reference);
base.OnCreate(bundle);
And keep the bindings in the axml file.
Make sure you have this at the top of your xaml file:
xmlns:local="http://schemas.android.com/apk/res-auto"
Also if you are doing bindings in the cs file, the MvvmCross binding mode is TwoWay by default. So you dont need .Mode(MvxBindingMode.TwoWay);

Replacing a fragment - New instance of fragment class isn't instantiated

I'm trying to replace a fragment with a another fragment with the following lines of code:
FragmentTransaction ft = Activity.FragmentManager.BeginTransaction();
UploadCompleted ucompleted = new UploadCompleted();
ft.Replace(Resource.Id.CameraFragmentContainer, ucompleted, "uploadcompleted");
ft.AddToBackStack(null);
ft.Commit();
The fragment is being replaced inside another fragment which uses the Activity's fragment manager. The issue is that the OnCreate and OnCreateView methods of the replaced fragment aren't invoked and an old instance of fragment (the fragment which was instantiated the first time I replaced it) get's instantiated which is why I'm not able to pass any new values to the replaced fragment. Any idea what I'm doing wrong? I'm new to android development so my knowledge of fragments isn't that great.
As per your logic, OnCreate and OnCreateView of the new Fragment should get invoked. It is a good practice to do FragmentTransaction at Activity level rather than, at Fragment level. In the above snippet you are creating an instance of the new Fragment from within the already displayed Fragment and replacing itself from the parent Activity. This is not a recommended approach. I would suggest the following change.
public class ParentActivity:Activity
{
public void ChangeToUploadCompleted()
{
FragmentTransaction ft = FragmentManager.BeginTransaction();
UploadCompleted ucompleted = new UploadCompleted();
ft.Replace(Resource.Id.CameraFragmentContainer, ucompleted, "uploadcompleted");
ft.AddToBackStack(null);
ft.Commit();
}
}
public FirstFragment:Fragment
{
void CaptureCompleted ()
{
((ParentActivity)Activity).ChangeToUploadCompleted();
}
}
This is the good practice to switch fragments. This might fix your problem too
ft.Replace(Resource.Id.CameraFragmentContainer, ucompleted, "uploadcompleted");
In this R.id is the layout that you place in Fragment Activity or Activity and UploadCompleted is Fragment class which you want to replace so use xml like this in FRagmentActivity or ACtivity class
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<LinearLayout
android:id="#+id/lin"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"/>
</LinearLayout>
when you want to replace use this
FragmentTransaction ft = Activity.FragmentManager.BeginTransaction();
UploadCompleted ucompleted = new UploadCompleted();
ft.Replace(Resource.Id.lin, ucompleted, "uploadcompleted");
ft.AddToBackStack(null);
ft.Commit();

Categories