I have an app which I am converting from iOS only to iOS & Droid using MVVMCross.
In my current app I have a map view that uses a UISearchController that allows the user to search for locations nearby. This is based on the Xamarin example and works fine:
Xamarin Map Example
For the conversion I have:
a MapView bound to a MapViewModel.
A search service which is injected into MapViewModel.
Created a UISearchController and bound the search text to a property on the MapViewModel.
When the text is updated the search is called and the results are retrieved. What I am struggling with is how to bind the results back to a SearchResultsView as this is presented by the UISearchController.
Can anyone give me advice or point me in the right direction to solve this.
I have the code snippet below to give an idea of what I have relied so far.
[MvxFromStoryboard]
public partial class MapView : MvxViewController<MapViewModel>
{
public MapView(IntPtr handle) : base(handle)
{
}
public override void ViewDidLoad()
{
base.ViewDidLoad();
var searchResultsController = new SearchResultsView();
//Not sure if this is required
//var searchUpdater.UpdateSearchResults += searchResultsController.Search;
var searchController = new UISearchController(searchResultsController)
{
//Nore sure if this is required
//SearchResultsUpdater = searchUpdater
};
searchController.SearchBar.SizeToFit();
searchController.SearchBar.SearchBarStyle = UISearchBarStyle.Minimal;
searchController.SearchBar.Placeholder = "Enter a search query";
searchController.HidesNavigationBarDuringPresentation = false;
DefinesPresentationContext = true;
NavigationItem.TitleView = searchController.SearchBar;
//Bind to View Model
var set = this.CreateBindingSet<MapView, MapViewModel>();
set.Bind(searchController.SearchBar).To(vm => vm.SearchQuery);
set.Apply();
}
}
public class SearchResultsUpdator : UISearchResultsUpdating
{
public event Action<string> UpdateSearchResults = delegate { };
public override void UpdateSearchResultsForSearchController(UISearchController searchController)
{
this.UpdateSearchResults(searchController.SearchBar.Text);
}
}
[MvxFromStoryboard]
public partial class SearchResultsView : MvxTableViewController<SearchResultsViewModel>
{
public SearchResultsView() { }
public SearchResultsView(IntPtr handle) : base(handle)
{
}
public override void ViewDidLoad()
{
base.ViewDidLoad();
var source = new SearchResultsTableViewSource(TableView);
TableView.Source = source;
var set = this.CreateBindingSet<SearchResultsView, SearchResultsViewModel>();
set.Bind(source).To(vm => vm.Results);
set.Apply();
}
}
[MvxFromStoryboard]
public partial class SearchResultsView : MvxTableViewController<SearchResultsViewModel>
{
public SearchResultsView() { }
public SearchResultsView(IntPtr handle) : base(handle)
{
}
public override void ViewDidLoad()
{
base.ViewDidLoad();
var source = new SearchResultsTableViewSource(TableView);
TableView.Source = source;
var set = this.CreateBindingSet<SearchResultsView, SearchResultsViewModel>();
set.Bind(source).To(vm => vm.Results);
set.Apply();
}
}
I have posted this in case someone else is looking for an example. I decided the best way to do this was to let iOS handle the search view controller for the results. Code follows. Feel free to correct or suggest a better alternative
View
[MvxFromStoryboard]
public partial class MapView : MvxViewController
{
UISearchController _searchController;
SearchResultsViewController _searchResultsController;
private IDisposable _searchResultsUpdateSubscription;
private IMvxInteraction _searchResultsUpdatedInteraction;
public IMvxInteraction SearchResultsUpdatedInteraction
{
get => _searchResultsUpdatedInteraction;
set
{
if (_searchResultsUpdateSubscription != null)
{
_searchResultsUpdateSubscription.Dispose();
_searchResultsUpdateSubscription = null;
}
_searchResultsUpdatedInteraction = value;
if (_searchResultsUpdatedInteraction != null)
{
_searchResultsUpdateSubscription = _searchResultsUpdatedInteraction.WeakSubscribe(OnSearchResultsUpdated);
}
}
}
private void OnSearchResultsUpdated(object sender, EventArgs e)
{
_searchResultsController.SearchResults = Results;
_searchResultsController.ReloadSearchTable();
}
public List<Placemark> Results { get; set; }
public MapView(IntPtr handle) : base(handle)
{
}
public override void ViewDidLoad()
{
base.ViewDidLoad();
//Bind to View Model
var set = this.CreateBindingSet<MapView, MapViewModel>();
set.Bind(_searchController.SearchBar).To(vm => vm.SearchQuery);
set.Bind(this).For(v => v.Results).To(vm => vm.Results);
set.Bind(this).For(v => v.SearchResultsUpdatedInteraction).To(vm => vm.SearchResultsUpdatedInteraction).OneWay();
set.Apply();
}
ViewModel
public class MapViewModel : MvxViewModel
{
readonly ILocationService _locationService;
private MvxInteraction _searchResultsUpdatedInteraction = new MvxInteraction();
public IMvxInteraction SearchResultsUpdatedInteraction => _searchResultsUpdatedInteraction;
public MapViewModel(ILocationService locationService)
{
_locationService = locationService;
}
//***** Properties *****
private List<Placemark> _results;
public List<Placemark> Results
{
get => _results;
set
{
_results = value;
RaisePropertyChanged();
}
}
private string _searchQuery;
public string SearchQuery
{
get => _searchQuery;
set
{
_searchQuery = value;
//Task.Run(UpdateResultsAsync).Wait();
RaisePropertyChanged();
UpdateResultsAsync();
}
}
//***** Privates *****
private async Task UpdateResultsAsync()
{
Results = await _locationService.SearchForPlacesAsync(_searchQuery);
_searchResultsUpdatedInteraction.Raise();
}
}
SearchResultsViewController
public class SearchResultsViewController : UITableViewController
{
static readonly string mapItemCellId = "mapItemCellId";
public List<Placemark> SearchResults { get; set; }
public SearchResultsViewController()
{
SearchResults = new List<Placemark>();
}
public override nint RowsInSection(UITableView tableView, nint section)
{
return SearchResults == null ? 0 : SearchResults.Count;
}
public override UITableViewCell GetCell(UITableView tableView, NSIndexPath indexPath)
{
var cell = tableView.DequeueReusableCell(mapItemCellId);
if (cell == null)
cell = new UITableViewCell();
cell.TextLabel.Text = SearchResults[indexPath.Row].FeatureName;
return cell;
}
public override void RowSelected(UITableView tableView, NSIndexPath indexPath)
{
//Do stuff here
}
public void ReloadSearchTable()
{
this.TableView.ReloadData();
}
}
Related
I am working to achieve a recycler view interface that looks like this:
Presently, am only using a recyclerview with LinearLayoutManager using an adapter with two viewholders, i tried gridLayout manager too but i did not achieve the target interface: I need help achieving this, Do i have to create a custom Layout Manager? or What exactly do i have to do? Please, I am really stuck on this.
This is my adapter code
public class SimpleStringRecyclerViewAdapter : RecyclerView.Adapter
{
private List<Data> mValues;
private Context context;
private const int TYPE_FULL = 0;
private const int TYPE_HALF = 1;
private const int TYPE_QUARTER = 2;
public SimpleStringRecyclerViewAdapter(Context context, List<Data> items)
{
this.context = context;
mValues = items;
}
public override int ItemCount
{
get
{
return mValues.Count();
}
}
public override void OnBindViewHolder(RecyclerView.ViewHolder holder, int position)
{
if (holder is SimpleViewHolder)
try
{
Data item = mValues.ElementAt(position);
var simpleHolder = holder as SimpleViewHolder;
simpleHolder.mTxtView.Text = Android.Text.Html.FromHtml(item.article.Title).ToString();
simpleHolder.mTxtView2.Text = item.article.Description;
using (var imageView = simpleHolder.mImageView)
{
string url = Android.Text.Html.FromHtml(item.article.UrlToImage).ToString();
//Download and display image
UrlImageViewHelper.SetUrlDrawable(imageView,
url, Resource.Drawable.cheese_1
);
}
// simpleHolder.mprogressbar.Visibility = ViewStates.Gone;
}
catch (Exception e)
{
//Toast.MakeText(this.context, e.ToString(), ToastLength.Long).Show();
}
else
{
try
{
Data item = mValues.ElementAt(position);
var simpleHolder = holder as SimpleViewHolder2;
simpleHolder.mTxtView.Text = Android.Text.Html.FromHtml(item.youTubeItem.Title).ToString();
// simpleHolder.mTxtView2.Text = item.DescriptionShort;
using (var imageView = simpleHolder.mImageView)
{
string url = Android.Text.Html.FromHtml(item.youTubeItem.MaxResThumbnailUrl).ToString();
//Download and display image
UrlImageViewHelper.SetUrlDrawable(imageView,
url, Resource.Drawable.cheese_1
);
}
}
catch (Exception e)
{
//Toast.MakeText(this.context, e.ToString(), ToastLength.Long).Show();
}
}
}
public override int GetItemViewType(int position)
{
if (mValues.ElementAt(position).type == 1)
{
return Resource.Layout.ItemsList;
}
else
{
return Resource.Layout.VideoList;
}
}
public override RecyclerView.ViewHolder OnCreateViewHolder(ViewGroup parent, int viewType)
{
if (viewType == Resource.Layout.ItemsList)
{
View view = LayoutInflater.From(parent.Context).Inflate(Resource.Layout.ItemsList, parent, false);
view.SetBackgroundColor(Color.White);
SimpleViewHolder holder = new SimpleViewHolder(view);
// holder.mprogressbar = view.FindViewById<ProgressBar>(Resource.Id.progressBar);
// holder.mprogressbar.Visibility = ViewStates.Visible;
//Showing loading progressbar
return holder;
}
else
{
View view = LayoutInflater.From(parent.Context).Inflate(Resource.Layout.VideoList, parent, false);
view.SetBackgroundColor(Color.White);
SimpleViewHolder2 holder = new SimpleViewHolder2(view);
return holder;
}
}
}
public class SimpleViewHolder : RecyclerView.ViewHolder
{
public string mBoundString;
public readonly View mView;
public readonly ImageView mImageView;
public readonly TextView mTxtView;
public readonly TextView mTxtView2;
// public ProgressBar mprogressbar;
public SimpleViewHolder(View view) : base(view)
{
mView = view;
mImageView = view.FindViewById<ImageView>(Resource.Id.avatar2);
mTxtView = view.FindViewById<TextView>(Resource.Id.Text11);
mTxtView2 = view.FindViewById<TextView>(Resource.Id.Text12);
// mprogressbar = view.FindViewById<ProgressBar>(Resource.Id.progressBar);
}
public override string ToString()
{
return base.ToString() + " '" + mTxtView.Text;
}
}
public class SimpleViewHolder2 : RecyclerView.ViewHolder
{
public string mBoundString;
public readonly View mView;
public readonly ImageView mImageView;
public readonly TextView mTxtView;
public readonly TextView mTxtView2;
public SimpleViewHolder2(View view) : base(view)
{
mView = view;
mImageView = view.FindViewById<ImageView>(Resource.Id.videoavatar);
mTxtView = view.FindViewById<TextView>(Resource.Id.videoText1);
// mprogressbar = view.FindViewById<ProgressBar>(Resource.Id.progressBar);
}
}
SetUpRecyclerView Method:
dataUse = OfflineDeserializer.OfflineData(content, json2);
recyclerView.SetLayoutManager(new LinearLayoutManager(recyclerView.Context));
recyclerView.SetAdapter(new SimpleStringRecyclerViewAdapter(recyclerView.Context, dataUse));
if (vp.IsShown)
{
vp.Visibility = ViewStates.Invisible;
}
This is what I have presently:
you can use recycler view with multiple view type
here is the link you can refer
Recyclerview with multiple view types
Here is a sample code you can modify it according to your need
public class MyFeedsAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
final int
VIDEO = 1,
IMAGE = 2,
AUDIO = 3;
public MyFeedsAdapter(HomeActivity homeActivity, ArrayList<MyFeedsModel> list, MyFeedsFragment myFeedsFragment) {
this.activity = homeActivity;
this.list = list;
this.myFeedsFragment = myFeedsFragment;
metrics = new DisplayMetrics();
activity.getWindowManager().getDefaultDisplay().getMetrics(metrics);
}
#Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view;
switch (viewType) {
case VIDEO:
view = LayoutInflater.from(parent.getContext()).inflate(R.layout.row_myfeeds, parent, false);
return new ViewForVideo(view);
case IMAGE:
view = LayoutInflater.from(parent.getContext()).inflate(R.layout.row_myfeeds, parent, false);
return new ViewForImage(view);
case AUDIO:
view = LayoutInflater.from(parent.getContext()).inflate(R.layout.row_myfeeds, parent, false);
return new ViewForAudio(view);
default:
return null;
}
}
#Override
public void onBindViewHolder(final RecyclerView.ViewHolder holder, final int position) {
if (list.get(position).getFile_type().equals(IntentString.VIDEO)) {
setViewForVideo((ViewForVideo) holder, position);
} else if (list.get(position).getFile_type().equals(IntentString.IMAGE)) {
setViewForImage((ViewForImage) holder, position);
} else if (list.get(position).getFile_type().equals(IntentString.AUDIO)) {
setViewForAudio((ViewForAudio) holder, position);
}
}
#Override
public int getItemCount() {
return list.size();
}
#Override
public int getItemViewType(int position) {
if (list.get(position).getFile_type().equals(IntentString.VIDEO)) {
return VIDEO;
} else if (list.get(position).getFile_type().equals(IntentString.IMAGE)) {
return IMAGE;
} else if (list.get(position).getFile_type().equals(IntentString.AUDIO)) {
return AUDIO;
} else {
return 0;
}
}
public void setViewForVideo(final ViewForVideo holder, final int position) {
}
public void setViewForImage(final ViewForImage holder, final int position) {
}
public void setViewForAudio(final ViewForAudio holder, int position) {
}
public class ViewForVideo extends RecyclerView.ViewHolder {
public ViewForVideo(View itemView) {
super(itemView);
}
}
public class ViewForImage extends RecyclerView.ViewHolder {
public ViewForImage(View itemView) {
super(itemView);
ButterKnife.bind(this, itemView);
}
}
public class ViewForAudio extends RecyclerView.ViewHolder {
public ViewForAudio(View itemView) {
super(itemView);
ButterKnife.bind(this, itemView);
}
}
}
I'm using xamarin.ios with XamarinSidebar, but I have a problem with the MvxTableViewSource in EditActionsForRow event, if I don't use the presentation using XamarinSidebar, for example presenting in a modal, it detects the event perfectly, and shows me the content, but if I show it with the XamarinSidebar doesn't detect the horizontal swipe.
CustomViewController:
[MvxSidebarPresentation(MvxPanelEnum.Center, MvxPanelHintType.ResetRoot, true, MvxSplitViewBehaviour.Detail)]
public partial class CustomViewController : MvxViewController<CustomViewModel>
{
public CustomViewController() : base("CustomViewController", null)
{
}
public override void ViewDidLoad()
{
base.ViewDidLoad();
var set = this.CreateBindingSet<CustomViewController, CustomViewModel>();
var source = new TableViewSource(TableView);
set.Bind(source).For(s => s.ItemsSource).To(vm => vm.CustomList);
TableView.Source = source;
set.Apply();
}
}
and TableViewSource:
public class TableViewSource : MvxTableViewSource
{
public List<CustomItems> TableItems { get { return ItemsSource as List<CustomItems>; } }
string CellIdentifier = "CustomCell";
public ChatContactTableViewSource(UITableView tableView) : base(tableView)
{
}
public override nint RowsInSection(UITableView tableview, nint section)
{
return TableItems.Count;
}
public override void RowSelected(UITableView tableView, NSIndexPath indexPath)
{
Contact item = TableItems[indexPath.Row];
Console.WriteLine("Item selected: " + item.ContactName);
}
public override UITableViewRowAction[] EditActionsForRow(UITableView tableView, NSIndexPath indexPath)
{
List<UITableViewRowAction> items = new List<UITableViewRowAction>();
//Button
var Button = UITableViewRowAction.Create(UITableViewRowActionStyle.Normal, "Delete",
delegate
{
Console.WriteLine("DeleteButton");
});
Button.BackgroundColor = UIColor.Red;
items.Add(Button);
//I have more than one button here
return items.ToArray();
}
public override bool CanEditRow(UITableView tableView, NSIndexPath indexPath)
{
return true;
}
public override void CommitEditingStyle(UITableView tableView, UITableViewCellEditingStyle editingStyle, NSIndexPath indexPath)
{
Console.WriteLine("EDITING STYLE");
if (editingStyle == UITableViewCellEditingStyle.Delete)
{
TableItems.RemoveAt(indexPath.Row);
}
}
protected override UITableViewCell GetOrCreateCellFor(UITableView tableView, NSIndexPath indexPath, object item)
{
var cell = tableView.DequeueReusableCell(CellIdentifier) as CustomCell;
CustomItems ItemCustom = TableItems[indexPath.Row];
//---- if there are no cells to reuse, create a new one
if (cell == null)
{
cell = CustomCell.Create();
}
cell.LabelText.Text = ItemCustom.Name;
return cell;
}
}
Solved implementing:
public class BaseMenuViewController<TViewModel> : BaseMenuView<TViewModel>, IMvxSidebarMenu where TViewModel : class, IMvxViewModel
{
public virtual UIImage MenuButtonImage => UIImage.FromBundle("threelines");
public virtual bool AnimateMenu => true;
public virtual float DarkOverlayAlpha => 0.7f;
public virtual bool HasDarkOverlay => true;
public virtual bool HasShadowing => false;
public virtual bool DisablePanGesture => true; //Here the solution
public virtual bool ReopenOnRotate => true;
private int MaxMenuWidth = 304;
private int MinSpaceRightOfTheMenu = 55;
public int MenuWidth => UserInterfaceIdiomIsPhone ?
int.Parse(UIScreen.MainScreen.Bounds.Width.ToString()) - MinSpaceRightOfTheMenu : MaxMenuWidth;
private bool UserInterfaceIdiomIsPhone
{
get { return UIDevice.CurrentDevice.UserInterfaceIdiom == UIUserInterfaceIdiom.Phone; }
}
public virtual void MenuWillOpen()
{
}
public virtual void MenuDidOpen()
{
}
public virtual void MenuWillClose()
{
}
public virtual void MenuDidClose()
{
}
}
I'm having a trouble with both ListView and RecyclerView
Initially, I created a ListView, everything is fine. Then I set onClick event for it so that every time I click an item, it changes its color to yellow. The OnClick function I wrote in the MainActivity. Problem is that when I test, not only that item changes its color but 2 items change. I read that it's because I reuse the view.
So I switch my tactics, using RecyclerView instead but same problem occurs. When I click one item to change its color, another below also changes. I guess it's because both ListView and RecyclerView reuse those Item so they confuse when I click one.
I don't know how to solve this problem, I found a solution is to add an array of boolean which marks which item is clicked but it doesn't work. Any idea guys?
So here is the code
MainActivity
class MainActivity : Activity
{
public RecyclerView recyclerView;
public RecyclerView.LayoutManager manager;
public RecyclerView.Adapter adapter;
List<Row> lst;
protected override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
// Set our view from the "main" layout resource
SetContentView(Resource.Layout.Main);
init();
recyclerView = (RecyclerView)FindViewById(Resource.Id.recyclerView);
manager = new LinearLayoutManager(this);
recyclerView.SetLayoutManager(manager);
CustomAdapter adapter = new CustomAdapter(lst, this);
adapter.ItemClick += onItemClick;
recyclerView.SetAdapter(adapter);
}
public void init()
{
lst = new List<Row>();
for (int i = 0; i < 15; i++)
{
Row row = new Row() { field1="1:43:00", field2="09-Apr-16", field3="KPI/Overflow", field4="Kevin Bacon", field5="Unowned", field6= "People Counting # IPCAM-ID-C-1-1" };
lst.Add(row);
}
}
public void onItemClick(object sender, int position)
{
int itemPos = position + 1;
//Toast.MakeText(this, "this is " + itemPos, ToastLength.Short).Show();
recyclerView.GetChildAt(position).SetBackgroundColor(Android.Graphics.Color.Green);
}
}
Custom adapter
public class CustomAdapter : RecyclerView.Adapter
{
public Activity _activity;
public List<Row> lst;
public event EventHandler<int> ItemClick;
public CustomAdapter(List<Row> lst, Activity activity)
{
this.lst = lst;
this._activity = activity;
}
public override int ItemCount
{
get
{
return lst.Count;
}
}
public void OnClick(int position)
{
if (ItemClick!=null)
{
ItemClick(this, position);
}
}
public override void OnBindViewHolder(RecyclerView.ViewHolder holder, int position)
{
MyViewHolder myholder = holder as MyViewHolder;
myholder.textView1.Text = lst[position].field1;
myholder.textView2.Text = lst[position].field2;
myholder.textView3.Text = lst[position].field3;
myholder.textView4.Text = lst[position].field4;
myholder.textView5.Text = lst[position].field5;
myholder.textView6.Text = lst[position].field6;
}
public override RecyclerView.ViewHolder OnCreateViewHolder(ViewGroup parent, int viewType)
{
View v = this._activity.LayoutInflater.Inflate(Resource.Layout.item, parent, false);
TextView tv1 = (TextView)v.FindViewById(Resource.Id.textView1);
TextView tv2 = (TextView)v.FindViewById(Resource.Id.textView2);
TextView tv3 = (TextView)v.FindViewById(Resource.Id.textView3);
TextView tv4 = (TextView)v.FindViewById(Resource.Id.textView4);
TextView tv5 = (TextView)v.FindViewById(Resource.Id.textView5);
TextView tv6 = (TextView)v.FindViewById(Resource.Id.textView6);
MyViewHolder holder = new MyViewHolder(v, OnClick) { textView1 = tv1, textView2 = tv2, textView3 = tv3, textView4 = tv4, textView5 = tv5, textView6 = tv6 };
return holder;
}
}
class MyViewHolder : RecyclerView.ViewHolder
{
public TextView textView1, textView2, textView3, textView4, textView5, textView6;
public View mainView;
public MyViewHolder(View view, Action<int> listener) : base(view)
{
mainView = view;
mainView.Click += (sender, e) => listener(base.Position);
}
}
I followed the example for the OnClick handler on Xamarin site
https://developer.xamarin.com/guides/android/user_interface/recyclerview/
Your issue is with your code. You send the correct position to your event handler, but then you increment it by one in the Activity. Both ends should be using the 0-based index of the item position. There is no need to increment by one.
For changing the background color of the selected item, you can use a selector in XML so you wouldn't even need to do this in code.
Here is an example.
row_selector.xml
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_selected="true" android:color="#android:color/green" />
<item android:state_selected="false" android:color="#android:color/transparent"/>
</selector>
row_content.axml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="#+id/row_layout_parent"
android:background="#drawable/row_selector">
<!-- your row content -->
</LinearLayout>
Then your view holder would be updated to this...
class MyViewHolder : RecyclerView.ViewHolder
{
public TextView textView1, textView2, textView3, textView4, textView5, textView6;
public View mainView;
private LinearLayout _layoutParent;
public MyViewHolder(View view, Action<int> listener) : base(view)
{
mainView = view;
_layoutParent = mainView.FindViewById<LinearLayout>(Resource.Id.row_layout_parent);
_layoutParent.Click += (sender, e) => _layoutParent.Selected = true;
}
}
I removed the other click event. If you still need it for other reasons, then you can add it back, but it's not necessary for just setting the item background color when selected.
For Listview you should set choiceMode as below.
listView.ChoiceMode = ChoiceMode.Single;
Hope it help you :)-
Create a reusable recycleview adapter GENERIC
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using Android.App;
using Android.Content;
using Android.Graphics;
using Android.OS;
using Android.Runtime;
using Android.Support.V7.App;
using Android.Support.V7.Widget;
using Android.Text;
using Android.Text.Style;
using Android.Util;
using Android.Views;
using Android.Widget;
using Java.Util.Zip;
using ActionMenuView = Android.Support.V7.Widget.ActionMenuView;
namespace Android.Basic.Core
{
public class GenericRecyclerViewAdapter<T> : RecyclerView.Adapter
{
/// <summary>
/// You can set this for different custom cardview
/// </summary>
private int CardViewResourceLayout { get; set; }
public ObservableCollection<T> Items { get; private set; }
public event EventHandler<RecyclerViewViewHolder> ItemViewTemplated;
public RecyclerView.LayoutManager layoutManager;
public GenericRecyclerViewAdapter(RecyclerView recyclerView, IEnumerable<T> items, int cardViewResourceLayout, bool isList = true, bool isVertical = true) : base()
{
if(isList)
{
var vertical = isVertical ? LinearLayoutManager.Vertical : LinearLayoutManager.Horizontal;
layoutManager = new LinearLayoutManager(recyclerView.Context, vertical, false);
}
else
{
var vertical = isVertical ? GridLayoutManager.Vertical : GridLayoutManager.Horizontal;
layoutManager = new GridLayoutManager(recyclerView.Context, 3, vertical, false);
}
recyclerView.SetLayoutManager(layoutManager);
this.Items = new ObservableCollection<T>(items);
this.CardViewResourceLayout = cardViewResourceLayout;
this.Items.CollectionChanged += delegate
{
this.NotifyDataSetChanged();
};
}
public override RecyclerView.ViewHolder OnCreateViewHolder(ViewGroup parent, int viewType)
{
var itemView = LayoutInflater.From(parent.Context).Inflate(CardViewResourceLayout, parent, false);
#if DEBUG
Log.Info("GenericRecyclerViewAdapter - ", CardViewResourceLayout.ToString());
#endif
RecyclerViewViewHolder vh = new RecyclerViewViewHolder(itemView);
return vh;
}
public override void OnBindViewHolder(RecyclerView.ViewHolder holder, int position)
{
RecyclerViewViewHolder vh = holder as RecyclerViewViewHolder;
vh.ItemPosition = position;
vh.TemplateView.Tag = position;
vh.TemplateView.Click -= TemplateView_Click;
vh.TemplateView.Click += TemplateView_Click;
ItemViewTemplated?.Invoke(this, vh);
}
public event EventHandler<T> ItemClicked;
private void TemplateView_Click(object sender, EventArgs e)
{
var position = (int)((View)sender).Tag;
this.ItemClicked?.Invoke(sender, this.Items[position]);
}
public override int ItemCount
{
get { return this.Items.Count; }
}
public override long GetItemId(int position)
{
return base.GetItemId(position);
}
}
public class RecyclerViewViewHolder : RecyclerView.ViewHolder, View.IOnCreateContextMenuListener,
IMenuItemOnMenuItemClickListener
{
public View TemplateView { get; private set; }
public int ItemPosition { get; set; }
public event EventHandler<MenuInfo> ContextMenuCreated;
public event EventHandler<object> MenuItemClicked;
public MenuInfo MenuInfo { get; private set; }
public object Data { get; set; }
public RecyclerViewViewHolder(View itemView) : base(itemView)
{
// Locate and cache view references:
this.TemplateView = itemView;
this.TemplateView.SetOnCreateContextMenuListener(this);
}
public void OnCreateContextMenu(IContextMenu menu, View v, IContextMenuContextMenuInfo menuInfo)
{
MenuInfo = new MenuInfo(menu, v, menuInfo);
ContextMenuCreated?.Invoke(this, MenuInfo);
}
private Android.Views.MenuInflater menuInflater = null;
/// <summary>
/// After ContextMenuCreated
/// </summary>
/// <param name="resourcemenu"></param>
public void InflateMenu(int resourcemenu, SpannableString titleColor = null, object dta = null)
{
if (dta != null)
this.Data = dta;
if (this.TemplateView.Context is AppCompatActivity activity)
{
menuInflater = activity.MenuInflater;
}
else if (this.TemplateView.Context is Activity activity2)
{
menuInflater = activity2.MenuInflater;
}
var contextMenu = this.MenuInfo.ContextMenu;
contextMenu.Clear();
menuInflater.Inflate(resourcemenu, contextMenu);
var num = contextMenu.Size() - 1;
for (int i = 0; i <= num; i++)
{
var men = contextMenu.GetItem(i);
if(titleColor != null)
{
if (i == 0)
{
men.SetTitle(titleColor);
men.SetChecked(true);
}
}
if (i != 0)
{
men.SetOnMenuItemClickListener(this);
}
}
}
public bool OnMenuItemClick(IMenuItem item)
{
this.MenuItemClicked?.Invoke(item, this.Data);
return true;
}
public float PosX;
public float PosY;
}
public class MenuInfo
{
public IContextMenu ContextMenu { get; }
public View View { get; }
public IContextMenuContextMenuInfo ContextMenuInfo { get; }
public MenuInfo(IContextMenu contextMenu, View view, IContextMenuContextMenuInfo menuInfo)
{
this.ContextMenu = contextMenu;
this.View = view;
this.ContextMenuInfo = menuInfo;
}
}
}
Usage
RecyclerView recyclerView = new RecyclerView(this);
var viewAdapter = new Android.Basic.Core.GenericRecyclerViewAdapter<Java.IO.File>(recyclerView, files, Resource.Layout.directory_item);
var indiColor = ThemeHelper.IsDark ? ColorHelper.GetRandomLightColor() : ColorHelper.GetRandomDarkColor();
viewAdapter.ItemViewTemplated += (dd, holder) =>
{
var file = files[holder.ItemPosition];
var view = holder.ItemView;
var expanded = view.FindViewById<ExpandedView>(Resource.Id.expandedView);
expanded.SetToggleColor(indiColor);
expanded.SetTitle(file.Name);
GenerateRecycler(expanded, file);
};
recyclerView.SetAdapter(viewAdapter);
expandedView.AddExpandedView(recyclerView);
Is iOS Picker available in the Xamarin.iOS? I have searched throughly but there is neither example nor information has been founded; however, it is available in Xamarin.Form.
A real quickie example of a UIPickerView: (iOS SDK)
Add a UIPickerView to your xib or Storyboard called slotMachineView:
using System;
using UIKit;
namespace Slots
{
public partial class ViewController : UIViewController
{
public ViewController (IntPtr handle) : base (handle)
{
}
public override void ViewDidLoad ()
{
base.ViewDidLoad ();
slotMachineView.Model = new StackOverflowModel (selectedLbl);
}
public override void DidReceiveMemoryWarning ()
{
base.DidReceiveMemoryWarning ();
}
}
public class StackOverflowModel : UIPickerViewModel
{
static string[] names = new string [] {
"pscorlib.dll",
"pscorlib_aot.dll",
"Mono.PlayScript.dll",
"PlayScript.Dynamic.dll",
"PlayScript.Dynamic_aot.dll",
"PlayScript.Optimization.dll",
"playshell.exe",
"psc.exe"
};
UILabel lbl;
public StackOverflowModel (UILabel lbl)
{
this.lbl = lbl;
}
public override nint GetComponentCount (UIPickerView v)
{
return 3;
}
public override nint GetRowsInComponent (UIPickerView pickerView, nint component)
{
return names.Length;
}
public override string GetTitle (UIPickerView picker, nint row, nint component)
{
switch (component) {
case 0:
return names [row];
case 1:
return row.ToString ();
case 2:
return new string ((char)('A' + row), 1);
default:
throw new NotImplementedException ();
}
}
public override void Selected (UIPickerView picker, nint row, nint component)
{
lbl.Text = String.Format ("{0} : {1} : {2}",
names [picker.SelectedRowInComponent (0)],
picker.SelectedRowInComponent (1),
picker.SelectedRowInComponent (2));
}
public override nfloat GetComponentWidth (UIPickerView picker, nint component)
{
if (component == 0)
return 220f;
else
return 30f;
}
}
}
Yes it is. The native iOS control is called UIPickerView
public class GenderPicker : UIViewController
{
public TestVC() { }
UITextField SelectGenderTextField = new UITextField();
UIPickerView GenderPicker = new UIPickerView();
public override void ViewDidLoad()
{
base.ViewDidLoad();
AddTextField();
GenderTextField();
Constraint();
}
}
Textfield To Show The Selected Data
private void AddTextField()
{
SelectGenderTextField.Placeholder = "Select Gender";
SelectGenderTextField.Layer.BorderWidth = 1;
SelectGenderTextField.Layer.BorderColor = UIColor.Black.CGColor;
SelectGenderTextField.Layer.MasksToBounds = true;
SelectGenderTextField.Layer.SublayerTransform = CATransform3D.MakeTranslation(5, 0, 0); //to Create a Space At The beginning of the text field
SelectGenderTextField.InputView = GenderPicker; //To Start The UIPickerView from The bottom.
}
Gender Picker
private void GenderTextField()
{
var genderList = new List<string> {
"Male","Female"
};
var picker = new GenderPickerModel(genderList);
GenderPicker.Model = picker;
picker.ValueChanged += (sender, e) => {
SelectGenderTextField.Text = picker.SelectedGenderByUser; //Update The Selected Value In the TextField
View.EndEditing(true);// To Dismiss the Picker View Once The User Select The Value
};
}
Constraints
// Used Cirrious.FluentLayouts.Touch For Constraints
private void Constraint()
{
View.AddSubviews(SelectGenderTextField);
View.SubviewsDoNotTranslateAutoresizingMaskIntoConstraints();
View.AddConstraints(
SelectGenderTextField.WithRelativeWidth(View, 0.80f),
SelectGenderTextField.WithRelativeHeight(View, 0.05f),
SelectGenderTextField.WithSameCenterX(View),
SelectGenderTextField.WithSameCenterY(View)
);
}
Picker Model Class
class GenderPickerModel : UIPickerViewModel
{
public EventHandler ValueChanged;
public string SelectedGenderByUser;
private List<string> genderList;
public GenderPickerModel(List<string> genderList)
{
this.genderList = genderList;
}
public override nint GetRowsInComponent(UIPickerView pickerView, nint component)
{
return genderList.Count;
}
public override nint GetComponentCount(UIPickerView pickerView)
{
return 1;
}
public override string GetTitle(UIPickerView pickerView, nint row, nint component)
{
return genderList[(int)row];
}
public override void Selected(UIPickerView pickerView, nint row, nint component)
{
var gender = genderList[(int)row];
SelectedGenderByUser = gender;
ValueChanged(null,null);
}
}
I am getting wrong position in custom ListView while scrolling.
I have tried the ViewHolder pattern and an ArrayAdapter but both giving the same problem.
If I reproduce the code using Java then I am getting the proper position while scrolling.
So is it Xamarin architecture bug ?
Below is my sample code:
Activity Class
namespace ArrayAdapterDemoApp
{
[Activity(Label = "ArrayAdapterDemoApp", MainLauncher = true,
Icon ="#drawable/icon")]
public class MainActivity : Activity
{
private static List<DataBean> _ItemsList = new List<DataBean>();
private static CustomAdapter _adapter;
private ListView _listview;
protected override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
// Set our view from the "main" layout resource
SetContentView(Resource.Layout.Main);
// Get our button from the layout resource,
// and attach an event to it
_listview = FindViewById<ListView>(Resource.Id.mylist);
DataBean obj1 = new DataBean();
obj1.Name = "AA";
obj1.City = "11";
_ItemsList.Add(obj1);
DataBean obj2 = new DataBean();
obj2.Name = "BB";
obj2.City = "22";
_ItemsList.Add(obj2);
DataBean obj3 = new DataBean();
obj3.Name = "CC";
obj3.City = "33";
_ItemsList.Add(obj3);
...
DataBean obj15 = new DataBean();
obj15.Name = "OO";
obj15.City = "1010";
_ItemsList.Add(obj15);
_adapter = new CustomAdapter(this, _ItemsList);
_listview.Adapter = _adapter;
}
}
}
Custom Adapter
namespace ArrayAdapterDemoApp
{
public class CustomAdapter : ArrayAdapter<DataBean>
{
private class TaskViewHolder : Java.Lang.Object
{
public TextView tvName;
public TextView tvCity;
}
List<DataBean> listData;
Activity _context;
int _position;
public CustomAdapter(Activity context, List<DataBean> dataList)
: base(context, Resource.Layout.adapter_row, dataList)
{
this._context = context;
this.listData = dataList;
}
public override long GetItemId(int position)
{
return position;
}
public override int Count
{
get { return listData.Count; }
}
//With View Holder
public override View GetView(int position, View convertView, ViewGroup parent)
{
DataBean data = listData[position];
TaskViewHolder viewHolder= null; // view lookup cache stored in tag
if (convertView == null)
{
viewHolder = new TaskViewHolder();
LayoutInflater inflater = LayoutInflater.From(_context);
convertView = inflater.Inflate(Resource.Layout.adapter_row, parent, false);
viewHolder.tvName = convertView.FindViewById<TextView>(Resource.Id.text1);
viewHolder.tvCity = convertView.FindViewById<TextView>(Resource.Id.text2);
convertView.Tag = viewHolder;
}
if(viewHolder==null)
{
viewHolder = (TaskViewHolder)convertView.Tag;
}
viewHolder.tvName.Text = data.Name;
viewHolder.tvCity.Text = data.City;
return convertView;
}
}
}
DataBean Class
namespace ArrayAdapterDemoApp
{
public class DataBean
{
public string Name { get; set; }
public string City { get; set; }
}
}
I had also same issue so I resolved it by just Tag the position to the view.for example.
//iv_delete is a imageview
holder.iv_delete.Tag = position;
and get the position from the Tag
For me it was like that
int finalPosition = (Integer)holder.iv_delete.Tag.IntValue();
Enjoy!!!!!
Add in your adapter class this method:
public DataBean GetItemAtPosition(int position)
{
return this.listData[position];
}
You can tag the position of each row to the control to get the correct position or
Another method is by using action event binding to each view row. This also solves duplicate method call issue.
this might be helpful: http://appliedcodelog.blogspot.in/2015/07/working-on-issues-with-listview-in.html#WrongPosition