I'm currently creating an app for Android using MvvmCross. Part of that app requires a MvxListView, where each item has 2 TextViews. One of these TextViews are hidden by default. I'm looking to implement an accordion like functionality, where clicking on the first TextView will show/hide the other TextView.
I've currently gotten most of this to work with the MvvmCross Visibility Plugin, but the click event is bound to the MvxListView instead of the TextView inside it. What I've currently gotten to work looks like this:
FirstViewModel:
public class FirstViewModel
: MvxViewModel
{
public FirstViewModel(IListService listService)
{
Interests = new ObservableCollection<Interest>();
List<Interest> tempInterests = listService.GetInterestFeeds("");
foreach (var interest in tempInterests)
{
interest._parent = this;
Interests.Add(interest);
}
var pluginLoader = new PluginLoader();
pluginLoader.EnsureLoaded();
}
private ObservableCollection<Interest> _interests;
public ObservableCollection<Interest> Interests
{
get { return _interests; }
set { _interests = value; RaisePropertyChanged(() => Interests); }
}
public ICommand ItemVisibleCommand
{
get
{
return new MvxCommand<Interest>(item => item.IsVisible = !item.IsVisible);
}
}
}
FirstView:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:local="http://schemas.android.com/apk/res-auto"
...>
<Mvx.MvxListView
...
local:MvxBind="ItemsSource Interests"
local:MvxItemTemplate="#layout/item_interests" />
</LinearLayout>
item_interests:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:local="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:textSize="30dp"
local:MvxBind="Text InterestName" />
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:textSize="20dp"
local:MvxBind="Text InterestDescription; Visibility IsVisible, Converter=Visibility" />
</LinearLayout>
In order to bind it to the TextView inside the MvxListView, I've been trying to modify my code to something similar to How to bind ItemClick in MvxListView in MvxListView as per the anwser by Stuart, resulting in the following code:
item_interest:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:local="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:textSize="30dp"
local:MvxBind="Text InterestName; Click ItemVisibleCommand" />
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:textSize="20dp"
local:MvxBind="Text InterestDescription; Visibility IsVisible, Converter=Visibility" />
</LinearLayout>
FirstViewModel:
public class FirstViewModel
: MvxViewModel
{
public FirstViewModel(IListService listService)
{
Interests = new ObservableCollection<Interest>();
List<Interest> tempInterests = listService.GetInterestFeeds("");
foreach (var interest in tempInterests)
{
interest._parent = this;
Interests.Add(interest);
}
var pluginLoader = new PluginLoader();
pluginLoader.EnsureLoaded();
}
private ObservableCollection<Interest> _interests;
public ObservableCollection<Interest> Interests
{
get { return _interests; }
set { _interests = value; RaisePropertyChanged(() => Interests); }
}
public void MakeItemVisible(bool isVisible)
{
isVisible = !isVisible;
}
Interest:
public class Interest : INotifyPropertyChanged
{
public string InterestId { get; set; }
public string InterestName { get; set; }
public string InterestDescription { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
public FirstViewModel _parent { get; set; }
private bool _isVisible;
public bool IsVisible
{
get { return _isVisible; }
set
{
_isVisible = value;
onPropertyChanged(this, "IsVisible");
}
}
private void onPropertyChanged(object sender, string propertyName)
{
if (this.PropertyChanged != null)
{
PropertyChanged(sender, new PropertyChangedEventArgs(propertyName));
}
}
public Interest(string id, string name, string description)
{
//Initialisers
}
private MvxCommand<bool> _itemVisible;
public ICommand ItemVisibleCommand
{
get
{
_itemVisible = _itemVisible ?? new MvxCommand<bool>(IsVisible => _parent.MakeItemVisible(IsVisible));
return _itemVisible;
}
}
}
resulting in the following exception:
04-04 15:05:40.575 I/MonoDroid(18011): UNHANDLED EXCEPTION: System.NullReferenceException: Object reference not set to an instance of an object
04-04 15:05:40.575 I/MonoDroid(18011): at Cirrious.MvvmCross.ViewModels.MvxCommand`1<bool>.Execute (object) <IL 0x00010, 0x00088>
04-04 15:05:40.575 I/MonoDroid(18011): at Cirrious.MvvmCross.Binding.Droid.Target.MvxViewClickBinding.ViewOnClick (object,System.EventArgs) <IL 0x0001f, 0x000fb>
04-04 15:05:40.575 I/MonoDroid(18011): at Android.Views.View/IOnClickListenerImplementor.OnClick (Android.Views.View) [0x0000d] in /Users/builder/data/lanes/monodroid-mlion-monodroid-4.12-series/a1e3982a/source/monodroid/src/Mono.Android/platforms/android-15/src/generated/Android.Views.View.cs:1615
04-04 15:05:40.575 I/MonoDroid(18011): at Android.Views.View/IOnClickListenerInvoker.n_OnClick_Landroid_view_View_ (intptr,intptr,intptr) [0x00011] in /Users/builder/data/lanes/monodroid-mlion-monodroid-4.12-series/a1e3982a/source/monodroid/src/Mono.Android/platforms/android-15/src/generated/Android.Views.View.cs:1582
04-04 15:05:40.575 I/MonoDroid(18011): at (wrapper dynamic-method) object.a963c1ac-b573-4022-b41d-f0f002438c84 (intptr,intptr,intptr) <IL 0x00017, 0x00043>
Unhandled Exception:
System.NullReferenceException: Object reference not set to an instance of an object
Thanks in advance to anyone who's taken the time to read all that :)
UPDATE - I tried to do as Stuart suggested, and got the following solution:
First off, to preserve the original Interest entity, it got wrapped in an InterestWrapper.
public class InterestWrapper : INotifyPropertyChanged
{
private Interest _interest;
private InterestAndroidViewModel _parent; //TO-DO
public Interest Item { get { return _interest; } }
public event PropertyChangedEventHandler PropertyChanged;
private bool _isVisible;
public bool IsVisible
{
get { return _isVisible; }
set
{
_isVisible = value;
onPropertyChanged(this, "IsVisible");
}
}
private void onPropertyChanged(object sender, string propertyName)
{
if (this.PropertyChanged != null)
{
PropertyChanged(sender, new PropertyChangedEventArgs(propertyName));
}
}
public InterestWrapper(Interest interest, InterestAndroidViewModel parent)
{
IsVisible = false;
_interest = interest;
_parent = parent;
}
public IMvxCommand ItemVisibleCommand
{
get
{
return new MvxCommand(() => _parent.MakeItemVisible(_interest));
}
}
}
FirstViewModel
public class FirstViewModel
: MvxViewModel
{
public FirstViewModel(IListService listService)
{
Interests = new ObservableCollection<InterestWrapper>();
List<Interest> tempInterests = listService.GetInterestFeeds("");
foreach (var interest in tempInterests)
{
InterestWrapper wrapper = new InterestWrapper(interest, this);
Interests.Add(wrapper);
}
}
private ObservableCollection<InterestWrapper> _interests;
public ObservableCollection<InterestWrapper> Interests
{
get { return _interests; }
set { _interests = value; RaisePropertyChanged(() => Interests); }
}
public void MakeItemVisible(Interest interest)
{
if (interest.IsVisible)
{
interest.IsVisible = !interest.IsVisible;
}
else
{
foreach (var _interest in _interests)
{
_interest.Item.IsVisible = false;
}
interest.IsVisible = !interest.IsVisible;
}
}
}
item_interest:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:local="http://schemas.android.com/apk/res-auto"
...
<RelativeLayout
...
<TextView
...
local:MvxBind="Text Item.InterestName; Click ItemVisibleCommand" />
<Mvx.MvxImageView
...
local:MvxBind="Visibility Item.IsVisible, Converter=InvertedVisibility; Click ShowEducationsCommand" />
</RelativeLayout>
</RelativeLayout>
The local:MvxBind="Text InterestName; Click ItemVisibleCommand" can only really call a non-parameterized MvxCommand - it can't call MvxCommand<bool> as it doesn't know what the bool value is.
If you wanted to, you could use the CommandParameter converter to pass in the value - e.g. local:MvxBind="Text InterestName; Click CommandParameter(ItemVisibleCommand, IsVisible)"
But overall, in this case I'd probably recommend rewriting ItemVisibleCommand as just a "toggle visible" command instead
Look through this question it's the as your one and has the solution suggested by Stuart, so think it's what you are looking for.
Binding button click in ListView template MvvMCross
Related
I have the following screen:
When I click on Ponentes ImageButton, ProgressBar appears while the data inside Ponentes activity is loading. The problem is that it stops spinning at some point. What may cause the issue and how can I fix it?
Here is the ProgressBar in the Ponentes axml:
<ProgressBar
android:id = "#+id/loadingSpinner"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone"
android:gravity="center"/>
Here is the OnCreate method of Ponentes Activity:
protected override async void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
SetContentView(Resource.Layout.SpeakersActivity);
Android.Widget.Toolbar toolbar = FindViewById<Android.Widget.Toolbar>(Resource.Id.toolbar);
toolbar.NavigationOnClick += delegate
{
this.OnBackPressed();
};
loadingSpinner = FindViewById<ProgressBar>(Resource.Id.loadingSpinner);
loadingSpinner.Visibility = ViewStates.Visible;
await Task.Run(() =>
{
//get all the speakers from the db
allSpeakers = DatabaseHelper.GetAllFromTable<Speaker>("speakers.db");
//get only the international spakers
internationalSpeakers = allSpeakers.Where(x => x.Nationality.Equals("international")).ToList();
//get only the national speakers
nationalSpeakers = allSpeakers.Where(x => x.Nationality.Equals("national")).ToList();
RunOnUiThread(() =>
{
speakersRecyclerView = FindViewById<RecyclerView>(Resource.Id.speakersRecyclerView);
speakersLayoutManager = new LinearLayoutManager(this);
speakersRecyclerView.SetLayoutManager(speakersLayoutManager);
speakersAdapter = new SpeakersAdapter(internationalSpeakers);
speakersAdapter.ItemClick += OnItemClick;
speakersRecyclerView.SetAdapter(speakersAdapter);
loadingSpinner.Visibility = ViewStates.Gone;
});
});
internationalSpeakersTextView = FindViewById<TextView>(Resource.Id.internationalSpeakersTextView);
internationalSpeakersTextView.Click += delegate
{
//change TextViews's style when selected/not-selected
internationalSpeakersTextView.SetBackgroundResource(Resource.Drawable.textView_selected);
nationalSpeakersTextView.SetBackgroundResource(Resource.Drawable.textView_unselected);
LoadSpeakers(internationalSpeakers);
};
nationalSpeakersTextView = FindViewById<TextView>(Resource.Id.nationalSpeakersTextView);
nationalSpeakersTextView.Click += delegate
{
//change TextViews's style when selected/not-selected
nationalSpeakersTextView.SetBackgroundResource(Resource.Drawable.textView_selected);
internationalSpeakersTextView.SetBackgroundResource(Resource.Drawable.textView_unselected);
LoadSpeakers(nationalSpeakers);
};
}
If you want I can paste you the entire Ponentes Activity
SpeakersActivity.cs :
[Activity(Label = "SpeakersActivity", ScreenOrientation = ScreenOrientation.Portrait)]
public class SpeakersActivity : BaseActivity
{
TextView internationalSpeakersTextView;
TextView nationalSpeakersTextView;
RecyclerView speakersRecyclerView;
// Layout manager that lays out each card in the RecyclerView:
RecyclerView.LayoutManager speakersLayoutManager;
// Adapter that accesses the data set (speakers):
SpeakersAdapter speakersAdapter;
/// <summary>
/// List that contains all the speakers
/// </summary>
List<Speaker> allSpeakers;
/// <summary>
/// List that contains the international speakers
/// </summary>
List<Speaker> internationalSpeakers;
/// <summary>
/// List that contains the national speakers
/// </summary>
List<Speaker> nationalSpeakers;
ProgressBar loadingSpinner;
protected override async void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
SetContentView(Resource.Layout.SpeakersActivity);
Android.Widget.Toolbar toolbar = FindViewById<Android.Widget.Toolbar>(Resource.Id.toolbar);
toolbar.NavigationOnClick += delegate
{
this.OnBackPressed();
};
loadingSpinner = FindViewById<ProgressBar>(Resource.Id.loadingSpinner);
loadingSpinner.Visibility = ViewStates.Visible;
await Task.Run(() =>
{
//get all the speakers from the db
allSpeakers = GetAllSpeakers();
//get only the international spakers
internationalSpeakers = allSpeakers.Where(x => x.Nationality.Equals("international")).ToList();
//get only the national speakers
nationalSpeakers = allSpeakers.Where(x => x.Nationality.Equals("national")).ToList();
RunOnUiThread(() =>
{
speakersRecyclerView = FindViewById<RecyclerView>(Resource.Id.speakersRecyclerView);
speakersLayoutManager = new LinearLayoutManager(this);
speakersRecyclerView.SetLayoutManager(speakersLayoutManager);
LoadSpeakers(internationalSpeakers);
loadingSpinner.Visibility = ViewStates.Gone;
});
});
internationalSpeakersTextView = FindViewById<TextView>(Resource.Id.internationalSpeakersTextView);
internationalSpeakersTextView.Click += delegate
{
//change TextViews's style when selected/not-selected
internationalSpeakersTextView.SetBackgroundResource(Resource.Drawable.textView_selected);
nationalSpeakersTextView.SetBackgroundResource(Resource.Drawable.textView_unselected);
LoadSpeakers(internationalSpeakers);
};
nationalSpeakersTextView = FindViewById<TextView>(Resource.Id.nationalSpeakersTextView);
nationalSpeakersTextView.Click += delegate
{
//change TextViews's style when selected/not-selected
nationalSpeakersTextView.SetBackgroundResource(Resource.Drawable.textView_selected);
internationalSpeakersTextView.SetBackgroundResource(Resource.Drawable.textView_unselected);
LoadSpeakers(nationalSpeakers);
};
}
/// <summary>
/// Load speakers inside activity
/// </summary>
/// <param name="speakers">speakers</param>
private void LoadSpeakers(List<Speaker> speakers)
{
speakersAdapter = new SpeakersAdapter(speakers);
speakersAdapter.ItemClick += OnItemClick;
speakersRecyclerView.SetAdapter(speakersAdapter);
}
private void OnItemClick(object sender, string speakerResumeUrl)
{
var speakerDetailsActivity = new Intent(this, typeof(SpeakerDetailsActivity));
speakerDetailsActivity.PutExtra("speakerResumeUrl", speakerResumeUrl);
StartActivity(speakerDetailsActivity);
}
public override void OnBackPressed()
{
StartActivity(typeof(CongressActivity));
Finish();
}
private List<Speaker> GetAllSpeakers()
{
System.Threading.Thread.Sleep(4000);
List<Speaker> speakers = new List<Speaker>();
speakers.Add(new Speaker { Photo = "https://i.imgur.com/DDH2Ckk.png", Name = "Anne-Maree Keenan", Resume = "https://51congresopodologia.com/?page_id=4004", Nationality = "international" });
speakers.Add(new Speaker { Photo = "https://i.imgur.com/DDH2Ckk.png", Name = "Anthony Redmond", Resume = "https://51congresopodologia.com/?page_id=4004", Nationality = "international" });
speakers.Add(new Speaker { Photo = "https://i.imgur.com/DDH2Ckk.png", Name = "Chris Nester", Resume = "https://51congresopodologia.com/?page_id=4004", Nationality = "national" });
speakers.Add(new Speaker { Photo = "https://i.imgur.com/DDH2Ckk.png", Name = "Amol Saxena", Resume = "https://51congresopodologia.com/?page_id=4004", Nationality = "international" });
return speakers;
}
}
SpeakersActivity.axml :
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:weightSum="100">
<Toolbar
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="?attr/actionBarSize"
android:background="#color/appColor"
android:navigationIcon="#drawable/ic_toolbar_back_button">
<TextView
android:text="Ponentes"
android:textAppearance="?android:attr/textAppearanceLarge"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#FFFFFF"/>
</Toolbar>
<LinearLayout
android:orientation="horizontal"
android:minWidth="25px"
android:minHeight="25px"
android:layout_width="match_parent"
android:layout_height="0dp"
android:weightSum="100"
android:layout_weight="10">
<TextView
android:text="Internacionales"
android:textAppearance="?android:attr/textAppearanceMedium"
android:layout_width="0dp"
android:layout_height="match_parent"
android:id="#+id/internationalSpeakersTextView"
android:layout_weight="50"
android:gravity="center"
android:background="#drawable/textView_selected"/>
<TextView
android:text="Nacionales"
android:textAppearance="?android:attr/textAppearanceMedium"
android:layout_width="0dp"
android:layout_height="match_parent"
android:id="#+id/nationalSpeakersTextView"
android:layout_weight="50"
android:gravity="center"
android:background="#drawable/textView_unselected"/>
</LinearLayout>
<android.support.v7.widget.RecyclerView
android:minWidth="25px"
android:minHeight="25px"
android:layout_width="match_parent"
android:layout_height="0dp"
android:id="#+id/speakersRecyclerView"
android:layout_weight="90"/>
<ProgressBar
android:id = "#+id/loadingSpinner"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone"
android:gravity="center"/>
<!--android:indeterminateDrawable="#drawable/animdraw"-->
</LinearLayout>
SpeakersAdapter.cs :
public class SpeakersAdapter : RecyclerView.Adapter
{
public event EventHandler<string> ItemClick;
public List<Speaker> listOfSpeakers;
public SpeakersAdapter(List<Speaker> speakers)
{
listOfSpeakers = speakers;
}
public override int ItemCount
{
get { return listOfSpeakers.Count; }
}
void OnClick(int position)
{
if (ItemClick != null)
ItemClick(this, listOfSpeakers[position].Resume);
}
public override void OnBindViewHolder(RecyclerView.ViewHolder holder, int position)
{
SpeakerViewHolder vh = holder as SpeakerViewHolder;
var imageBitmap = GetImageBitmapFromUrl(listOfSpeakers[position].Photo);
vh.speakerPhotoImageView.SetImageBitmap(imageBitmap);
vh.speakerNameTextView.Text = listOfSpeakers[position].Name;
}
public override RecyclerView.ViewHolder OnCreateViewHolder(ViewGroup parent, int viewType)
{
View itemView = LayoutInflater.From(parent.Context).
Inflate(Resource.Layout.SpeakerCardView, parent, false);
SpeakerViewHolder vh = new SpeakerViewHolder(itemView, OnClick);
return vh;
}
private Bitmap GetImageBitmapFromUrl(string url)
{
Bitmap imageBitmap = null;
using (var webClient = new WebClient())
{
var imageBytes = webClient.DownloadData(url);
if (imageBytes != null && imageBytes.Length > 0)
{
imageBitmap = BitmapFactory.DecodeByteArray(imageBytes, 0, imageBytes.Length);
}
}
return imageBitmap;
}
}
SpeakerViewHolder.cs :
public class SpeakerViewHolder : RecyclerView.ViewHolder
{
public ImageView speakerPhotoImageView { get; private set; }
public TextView speakerNameTextView { get; private set; }
public TextView speakerResumeTextView { get; private set; }
// Get references to the views defined in the CardView layout.
public SpeakerViewHolder(View itemView, Action<int> listener)
: base(itemView)
{
// Locate and cache view references:
speakerPhotoImageView = itemView.FindViewById<ImageView>(Resource.Id.speakerPhotoImageView);
speakerNameTextView = itemView.FindViewById<TextView>(Resource.Id.speakerNameTextView);
// Detect user clicks on the item view and report which item
// was clicked (by layout position) to the listener:
itemView.Click += (sender, e) => listener(base.LayoutPosition);
}
}
textView_selected.xml :
<?xml version="1.0" encoding="utf-8" ?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle" >
<solid android:color="#6495ED" />
<stroke
android:width="1dp"
android:color="#DCDCDC" />
</shape>
textView_unselected.xml :
<?xml version="1.0" encoding="utf-8" ?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle" >
<stroke
android:width="1dp"
android:color="#DCDCDC" />
</shape>
I've put System.Threading.Thread.Sleep(4000); in the GetAllSpeakers method in SpeakersActivity in order to imitate the delay when I get the records from a real database. Tell me if I am missing some file.
I have a TabLayout with Fragments. In one of the Fragments, I want to add a RecyclerView. I have implemented the code for it and deployed it, but when I start the app I get the following error at tabLayout.SetupWithViewPager(viewPager):
Android.Views.InflateException: Binary XML file line #15: android.support.v7.widget.AppCompatTextView cannot be cast to android.widget.Button
and sometimes just
Android.Views.InflateException: Timeout exceeded getting exception details
I tried to copy the same code for the RecyclerView in a new Project but there just in the MainActivity and it worked. So I'm doing something else wrong...
My Code:
Main.axml:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.design.widget.TabLayout
android:id="#+id/tablayout_navigation"
android:layout_width="match_parent"
app:tabTextColor="#ffffff"
app:tabSelectedTextColor="#08aeab"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:background="?attr/colorPrimary"
android:elevation="4dp" />
<android.support.v4.view.ViewPager
android:id="#+id/pager"
android:layout_width="match_parent"
android:layout_height="fill_parent"
android:layout_margin="0dp" />
</LinearLayout>
Overview.axml:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_horizontal|center_vertical"
android:minWidth="25px"
android:minHeight="25px">
<android.support.v7.widget.RecyclerView
android:id="#+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbars="vertical">
</android.support.v7.widget.RecyclerView>
</LinearLayout>
AppointmentListItem.axml:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="wrap_content">
<TextView
android:text="Name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="#+id/txtName"
android:textColor="#fff"/>
</LinearLayout>
RecyclerAdapter.cs:
class RecyclerAdapter : RecyclerView.Adapter
{
private List<Appointment> mAppointments;
public RecyclerAdapter(List<Appointment> appointments)
{
mAppointments = appointments;
}
public class MyView : RecyclerView.ViewHolder
{
public View mMainView { get; set; }
public TextView mName { get; set; }
public MyView(View view) : base(view)
{
mMainView = view;
}
}
public override RecyclerView.ViewHolder OnCreateViewHolder(ViewGroup parent, int viewType)
{
View row = LayoutInflater.From(parent.Context).Inflate(Resource.Layout.AppointmentListItem, parent, false);
TextView txtName = row.FindViewById<TextView>(Resource.Id.txtName);
MyView view = new MyView(row) { mName = txtName };
return view;
}
public override void OnBindViewHolder(RecyclerView.ViewHolder holder, int position)
{
MyView myHolder = holder as MyView;
myHolder.mName.Text = mAppointments[position].Name;
}
public override int ItemCount
{
get { return mAppointments.Count; }
}
}
OverviewFragment.cs
class OverviewFragment : Android.Support.V4.App.Fragment
{
private RecyclerView mRecyclerView;
private RecyclerView.LayoutManager mLayoutManager;
private RecyclerView.Adapter mAdapter;
private List<Appointment> mAppointments;
public override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
}
public override View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
// Use this to return your custom view for this Fragment
View view = inflater.Inflate(Resource.Layout.Overview, container, false);
mAppointments = new List<Appointment>();
mAppointments.Add(new Appointment("Test1"));
mAppointments.Add(new Appointment("Test2"));
mAppointments.Add(new Appointment("Test3"));
mAppointments.Add(new Appointment("Test4"));
mRecyclerView = view.FindViewById<RecyclerView>(Resource.Id.recyclerView);
mLayoutManager = new LinearLayoutManager(view.Context);
mRecyclerView.SetLayoutManager(mLayoutManager);
mAdapter = new RecyclerAdapter(mAppointments);
mRecyclerView.SetAdapter(mAdapter);
return view;
}
}
MainActivity.cs:
public class MainActivity : AppCompatActivity
{
private TabLayout tabLayout;
protected override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
SetContentView(Resource.Layout.Main);
tabLayout = FindViewById<TabLayout>(Resource.Id.tablayout_navigation);
ViewPager viewPager = FindViewById<ViewPager>(Resource.Id.pager);
SetupViewPager(viewPager);
tabLayout.SetupWithViewPager(viewPager);
}
private void SetupViewPager(ViewPager viewPager)
{
viewPager.OffscreenPageLimit = 3;
PageAdapter adapter = new PageAdapter(SupportFragmentManager);
adapter.AddFragment(new OverviewFragment(), Resources.GetString(Resource.String.overview));
adapter.AddFragment(new CalendarFragment(), Resources.GetString(Resource.String.calendar));
adapter.AddFragment(new SharesFragment(), Resources.GetString(Resource.String.share));
viewPager.Adapter = adapter;
}
}
I have to display the following table:
So I want to know what is the best widget to use for the table. I want the table to be dynamic I mean if I choose some other group from the dropdown the number of items in the table to increase or to decrease and the total price to change depending on what I have in column price. What widget is the best widget to achieve that?
RecyclerView is the best UI control to achieve it. Because RecyclerView is Adapter pattern, so you can use the adapter to achieve dynamic what you have said.
I want the table to be dynamic I mean if I choose some other group from the dropdown the number of items in the table to increase or to decrease and the total price to change depending on what I have in column price
I have made a demo for you, you can test it and do some changes for your project:
Three .axml files:
Main.axml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
>
<Button
android:id="#+id/bt1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="group1"/>
<Button
android:id="#+id/bt2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="group2"/>
<Button
android:id="#+id/bt3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="group3"
/>
</LinearLayout>
<android.support.v7.widget.RecyclerView
android:id="#+id/rv"
android:layout_width="match_parent"
android:layout_height="match_parent">
</android.support.v7.widget.RecyclerView>
</LinearLayout>
item_layout, every item's layout:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="#+id/item_tv"
android:layout_width="match_parent"
android:textSize="30dp"
android:layout_height="wrap_content" />
</LinearLayout>
item_layout, total price's layout:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="#+id/foot_tv"
android:gravity="end"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
MainActivity:
using Android.App;
using Android.Widget;
using Android.OS;
using System;
using System.Collections.Generic;
using Android.Support.V7.Widget;
using Android.Views;
using Android.Content;
namespace RecycleTest
{
[Activity(Label = "RecycleTest", MainLauncher = true)]
public class MainActivity : Activity
{
RecyclerView mRecyclerView;
MyAdapter adapter;
Button bt1, bt2, bt3;
List<double> list1, list2, list3;
protected override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
// Set our view from the "main" layout resource
SetContentView(Resource.Layout.Main);
initView();
initData();
mRecyclerView = FindViewById<RecyclerView>(Resource.Id.rv);
mRecyclerView.SetLayoutManager(new LinearLayoutManager(this));
adapter = new MyAdapter(this, list1);
mRecyclerView.SetAdapter(adapter);
}
private void initData()
{
list1 = new List<double>();
list2 = new List<double>();
list3 = new List<double>();
list1.Add(1.1);
list1.Add(1.2);
list1.Add(1.3);
list2.Add(2.1);
list2.Add(2.2);
list2.Add(2.3);
list2.Add(2.4);
list3.Add(3.1);
list3.Add(3.2);
list3.Add(3.3);
list3.Add(3.4);
list3.Add(3.5);
}
public void initView() {
bt1 = FindViewById<Button>(Resource.Id.bt1);
bt2 = FindViewById<Button>(Resource.Id.bt2);
bt3 = FindViewById<Button>(Resource.Id.bt3);
bt1.Click += Bt1_Click;
bt2.Click += Bt2_Click;
bt3.Click += Bt3_Click;
}
private void Bt3_Click(object sender, System.EventArgs e)
{
adapter.change(list3);
}
private void Bt2_Click(object sender, System.EventArgs e)
{
adapter.change(list2);
}
private void Bt1_Click(object sender, System.EventArgs e)
{
adapter.change(list1);
}
}
class MyViewHolder : RecyclerView.ViewHolder
{
public TextView mtv;
public MyViewHolder(View itemView): base(itemView)
{
mtv = itemView.FindViewById<TextView>(Resource.Id.item_tv);
}
}
class MyFootViewHolder : RecyclerView.ViewHolder
{
public TextView mtv;
public MyFootViewHolder(View itemView):base(itemView)
{
mtv = itemView.FindViewById<TextView>(Resource.Id.foot_tv);
}
}
class MyAdapter :RecyclerView.Adapter{
Context mContext;
List<double> mList=new List<double>();
public override int ItemCount => mList.Count + 1;
public MyAdapter(Context context, List<Double> list)
{
this.mContext = context;
this.mList.AddRange(list);
}
public override void OnBindViewHolder(RecyclerView.ViewHolder holder, int position)
{
if (GetItemViewType(position) == 0)
{
((MyViewHolder)holder).mtv.Text=(mList[position].ToString());
}
else
{
double price = 0;
for (int i = 0; i < mList.Count; i++)
{
price += mList[i];
}
((MyFootViewHolder)holder).mtv.Text=price + "";
}
}
public override RecyclerView.ViewHolder OnCreateViewHolder(ViewGroup parent, int viewType)
{
RecyclerView.ViewHolder holder;
if (viewType == 0)
{
holder = new MyViewHolder(LayoutInflater.From(mContext).Inflate(Resource.Layout.item_layout, parent, false));
}
else
{
holder = new MyFootViewHolder(LayoutInflater.From(mContext).Inflate(Resource.Layout.foot_layout, parent, false));
}
return holder;
}
public override int GetItemViewType(int position)
{
int type;
if (position != mList.Count)
{
type = 0;
}
else
{
type = 1;
}
return type;
}
public void change(List<Double> list1)
{
mList.Clear();
mList.AddRange(list1);
NotifyDataSetChanged();
}
}
}
I want to implement the NavigationTabStrip (https://github.com/martijn00/NavigationTabStripXamarin) to my app, but I don't know how. I've seen also the original project, but I'm new in this world. Has anyone a project to help me to implement this? Or is there anyone that knows how to do that? I'm trying to create a TabLayout in material design.
The Easiest way to do it would be like this :
http://camposha.info/source/xamarin-android-swipe-tabs-viewpager-fragments-images-actionbartabs/
http://www.c-sharpcorner.com/article/creating-sliding-tab-layout-interface-using-xamarin-android-using-visual-studio/
The code is in xamarin so it would be an easy implementation for you!
Goodluck!
UPDATE:
The code for your activities Axml file :
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
>
<android.support.v7.widget.Toolbar
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?actionBarSize"
android:minHeight="?attr/actionBarSize"
android:background="?attr/colorPrimary"
android:theme="#style/MyTheme"
app:popupTheme="#style/ThemeOverlay.AppCompat.Light" />
<android.support.design.widget.TabLayout
android:id="#+id/tabs"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:tabGravity="fill"
app:tabMode="fixed"
/>
<FrameLayout
android:id="#+id/frame_container"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
Code for your Activities cs file:
public class GoogleMapsActivity : AppCompatActivity
{
private Fragment MapsFragment;
private Fragment ListFragment;
private TabLayout allTabs;
FragmentManager fm;
FragmentTransaction ft;
protected override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
SetContentView(Resource.Layout.your_activity);
bindAllWidgets();
setupTabLayout();
setUpToolbar();
}
void setUpToolbar()
{
#region "Tool bar"
var toolbar = FindViewById<Android.Support.V7.Widget.Toolbar>(Resource.Id.toolbar);
toolbar.SetTitleTextColor(ResourcesCompat.GetColor(Resources, Resource.Color.colorRedText, null));
Drawable upArrow = ResourcesCompat.GetDrawable(Resources, Resource.Drawable.abc_ic_ab_back_mtrl_am_alpha, null);
upArrow.SetColorFilter(Color.ParseColor(Resources.GetString(Resource.Color.colorRedText)), PorterDuff.Mode.SrcAtop);
SetSupportActionBar(toolbar);
SupportActionBar.Title = Resources.GetString(Resource.String.toolbar_title_dhl_service_points);
SupportActionBar.SetHomeButtonEnabled(true);
SupportActionBar.SetDisplayHomeAsUpEnabled(true);
SupportActionBar.SetHomeAsUpIndicator(upArrow);
#endregion
}
private void setupTabLayout()
{
allTabs.AddTab(allTabs.NewTab().SetText(Resources.GetString(Resource.String.google_maps_page_title_map)), true);
allTabs.AddTab(allTabs.NewTab().SetText(Resources.GetString(Resource.String.google_maps_page_title_list)));
}
private void bindAllWidgets()
{
allTabs = FindViewById<TabLayout>(Resource.Id.tabs);
allTabs.TabSelected += AllTabs_TabSelected;
}
private void AllTabs_TabSelected(object sender, TabSelectedEventArgs e)
{
var tab = e.Tab;
setCurrentTabFragment(tab.Position);
}
public void replaceFragment(Fragment fragment)
{
fm = FragmentManager;
ft = fm.BeginTransaction();
if (fragment == MapsFragment)
{
if (MapsFragment == null)
{
MapsFragment = new PoSLocationMapView(this);
ft.Add(Resource.Id.frame_container, MapsFragment, "MapsFragment");
}
else
{
ft.Detach(FragmentManager.FindFragmentByTag("ListFragment"));
ft.Attach(MapsFragment);
}
}
else if (fragment == ListFragment)
{
if (ListFragment == null)
{
ListFragment = new ServicePointDHLList(this);
ft.Add(Resource.Id.frame_container, ListFragment, "ListFragment");
}
else
{
ft.Detach(FragmentManager.FindFragmentByTag("MapsFragment"));
ft.Attach(ListFragment);
}
}
ft.SetTransition(FragmentTransit.FragmentOpen);
ft.Commit();
}
private void setCurrentTabFragment(int tabPosition)
{
switch (tabPosition)
{
case 0:
replaceFragment(MapsFragment);
break;
case 1:
replaceFragment(ListFragment);
break;
}
}
}
Try this library it is more clearly.
https://github.com/ahoefling/TabStrip/blob/master/README.md
In my example below I want to bind an ItemClick Command to the Item in the MvxListView.
Here I have in my ViewModel a List of Person that contains a List of Dog.
The ItemsSource HasDogs binding works fine.
When MvvmCross is trying to bind ItemClick SelectDogCommand to the ICommand in the Viewmodel I get this Exception.
[0:]
MvxBind:Warning: 11,30 Unable to bind: source property source not found Property:SelectDogCommand on Person
[0:] MvxBind:Warning: 11,30 Unable to bind: source property source not found Property:SelectDogCommand on Person
12-04 15:05:03.062 I/mono-stdout(16338): MvxBind:Warning: 11,30 Unable to bind: source property source not found Property:SelectDogCommand on Person
Hope you can Help.
Here is my example:
public class FirstViewModel:MvxViewModel
{
private List<Person> _persons;
public List<Person> Persons
{
get { return _persons; }
set { _persons = value; }
}
private Cirrious.MvvmCross.ViewModels.MvxCommand<Dog> _selectDog;
public System.Windows.Input.ICommand SelectDogCommand
{
get
{
_selectDog = _selectDog ?? new Cirrious.MvvmCross.ViewModels.MvxCommand<Dog>(SelectDog);
return _selectDog;
}
}
private void SelectDog(Dog item)
{
ShowViewModel<DetailViewModel>(new DetailViewModel.Parameters{dog = item});
}
}
public class Person
{
private string _name;
private List<Dog> _hasDogs;
public List<Dog> HasDogs
{
get { return _hasDogs; }
set { _hasDogs = value; }
}
public string Name
{
get { return _name; }
set { _name = value; }
}
}
public class Dog{...}
The Android View Xml:
FirstView:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:local="http://schemas.android.com/apk/res-auto"
...>
<TextView ...
local:MvxBind="Text Persons"
<Mvx.MvxListView
...
local:MvxBind="ItemsSource Persons"
local:MvxItemTemplate="#layout/item_person" />
</LinearLayout>
item_person:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:local="http://schemas.android.com/apk/res-auto"
...
android:layout_height="200dp">
<TextView
...
local:MvxBind="Text Name" />
<Mvx.MvxListView
...
local:MvxBind="ItemsSource HasDogs; ItemClick SelectDogCommand"
local:MvxItemTemplate="#layout/item_dog" />
</LinearLayout>
The DataContext for your person list item is a Person - so your SelectDogCommand needs to be part of the Person class - e.g. something like:
public class Person
{
private string _name;
private List<Dog> _hasDogs;
public List<Dog> HasDogs
{
get { return _hasDogs; }
set { _hasDogs = value; }
}
public string Name
{
get { return _name; }
set { _name = value; }
}
private Cirrious.MvvmCross.ViewModels.MvxCommand<Dog> _selectDog;
public System.Windows.Input.ICommand SelectDogCommand
{
get
{
_selectDog = _selectDog ?? new Cirrious.MvvmCross.ViewModels.MvxCommand<Dog>(dog => _parent.SelectDog(dog));
return _selectDog;
}
}
private FirstViewModel _parent;
public Person(FirstViewModel parent)
{
_parent = parent;
}
}
or alternatively you could get Person to inherit from MvxNavigatingObject (or MvxPropertyChanged or MvxViewModel) - in which case the ShowViewModel methods will be available there too.