How to display image with text in 2 columns GridView - xamarin

How to display image with text ( like name, price.. etc )
Below code only display images with no text.
--- UI :
<?xml version="1.0" encoding="utf-8"?>
<GridView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/gridview"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:columnWidth="230dp"
android:numColumns="2"
android:verticalSpacing="10dp"
android:horizontalSpacing="10dp"
android:background="#ffffff"
android:stretchMode="columnWidth"
android:gravity="center" />
-- Code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Android.App;
using Android.Content;
using Android.OS;
using Android.Runtime;
using Android.Views;
using Android.Widget;
namespace ModSpforce
{
class ImageAdapter : BaseAdapter
{
Context context;
public ImageAdapter(Context c)
{
context = c;
}
public override int Count
{
get { return thumbIds.Length; }
}
public override Java.Lang.Object GetItem(int position)
{
return null;
}
public override long GetItemId(int position)
{
return 0;
}
// create a new ImageView for each item referenced by the Adapter
public override View GetView(int position, View convertView, ViewGroup parent)
{
ImageView imageView;
if (convertView == null)
{ // if it's not recycled, initialize some attributes
imageView = new ImageView(context);
imageView.LayoutParameters = new GridView.LayoutParams(200, 200);
imageView.SetScaleType(ImageView.ScaleType.CenterCrop);
imageView.SetPadding(3, 3, 3, 3);
}
else
{
imageView = (ImageView)convertView;
}
imageView.SetImageResource(thumbIds[position]);
return imageView;
}
// references to our images
int[] thumbIds = {
Resource.Drawable.sample_2, Resource.Drawable.sample_3,
Resource.Drawable.sample_4, Resource.Drawable.sample_5,
Resource.Drawable.sample_6, Resource.Drawable.sample_7,
Resource.Drawable.sample_0, Resource.Drawable.sample_1,
Resource.Drawable.sample_2, Resource.Drawable.sample_3,
Resource.Drawable.sample_4, Resource.Drawable.sample_5,
Resource.Drawable.sample_6, Resource.Drawable.sample_7,
Resource.Drawable.sample_0, Resource.Drawable.sample_1,
Resource.Drawable.sample_2, Resource.Drawable.sample_3,
Resource.Drawable.sample_4, Resource.Drawable.sample_5,
Resource.Drawable.sample_6, Resource.Drawable.sample_7
};
}
}

You can inflate your GridView's cell in the GetView method of your adapter, so you can simply design your item's template in xml.
For example:
Code behind of your GridView:
public ObservableCollection<MyItemModel> items = new ObservableCollection<MyItemModel>();
public MyGridViewAdapter adapter;
protected override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
SetContentView(Resource.Layout.Main);
//add your items here.
for (int i = 0; i < 50; i++)
{
items.Add(new MyItemModel { ImageSource = Resource.Drawable.Pika, Name = "Name " + i });
}
adapter = new MyGridViewAdapter(this, items);
GridView gv = FindViewById<GridView>(Resource.Id.gridview);
gv.Adapter = adapter;
}
MyItemModel is for image resource and name of this image, like this:
public class MyItemModel
{
public string Name { get; set; }
public int ImageSource { get; set; }
}
And MyGridViewAdapter is like this:
public class MyGridViewAdapter : BaseAdapter<MyItemModel>
{
private ObservableCollection<MyItemModel> items;
private Activity context;
public MyGridViewAdapter(Activity context, ObservableCollection<MyItemModel> items)
{
this.items = items;
this.context = context;
}
public override MyItemModel this[int position]
{
get
{
return items[position];
}
}
public override int Count
{
get
{
return items.Count;
}
}
public override long GetItemId(int position)
{
return position;
}
public override View GetView(int position, View convertView, ViewGroup parent)
{
View view = convertView;
if (view == null)
{
view = context.LayoutInflater.Inflate(Resource.Layout.MyGridViewCell, null);
}
var image = view.FindViewById<ImageView>(Resource.Id.image);
image.SetImageResource(items[position].ImageSource);
var name = view.FindViewById<TextView>(Resource.Id.name);
name.Text = items[position].Name;
return view;
}
}
Finally the layout of MyGridViewCell is like this:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<ImageView android:id="#+id/image"
android:layout_height="200dp"
android:layout_width="200dp" />
<TextView android:id="#+id/name"
android:layout_height="wrap_content"
android:layout_width="200dp"
android:textColor="#android:color/holo_blue_light" />
</LinearLayout>

Related

How to navigate between view models via BottomNavigationView in Xamarin MvvmCross

Let's say we have MvvmCross 6.0.1 native app with one Android Activity containing BottomNavigationView implemented as in this blog post by James Montemagno but without navigating and replacing fragments.
What I would like to do is to bind BottomNavigationView items to MvxCommands (or MvxAsyncCommands) in ViewModel in order to navigate between several ViewModels.
What kind of architecture should I apply to achieve this? Is my approach correct or am I doing something against MVVM pattern and MvvmCross possibilities?
Full working example with several additions can be found here on github.
At the moment I have (scaffolded with MvxScaffolding).
MainContainerActivity and corresponding MainContainerViewModel - here I would like to store commands to navigate between view models
MainFragment and corresponding MainViewModel - this is the first fragment/view model
SettingsFragment and corresponding SettingsViewModel - I would like to navigate to it from MainViewModel and vice versa
FavoritesFragment and corresponding FavoritesViewModel
The main activity is as follows:
using Android.App;
using Android.OS;
using Android.Views;
using PushNotifTest.Core.ViewModels.Main;
using Microsoft.AppCenter;
using Microsoft.AppCenter.Analytics;
using Microsoft.AppCenter.Crashes;
using Microsoft.AppCenter.Push;
using Android.Graphics.Drawables;
using Android.Support.Design.Widget;
using MvvmCross.Binding.BindingContext;
using System;
using System.Windows.Input;
namespace PushNotifTest.Droid.Views.Main
{
[Activity(
Theme = "#style/AppTheme",
WindowSoftInputMode = SoftInput.AdjustResize | SoftInput.StateHidden)]
public class MainContainerActivity : BaseActivity<MainContainerViewModel>
{
protected override int ActivityLayoutId => Resource.Layout.activity_main_container;
BottomNavigationView bottomNavigation;
public ICommand GoToSettingsCommand { get; set; }
public ICommand GoToFavoritesCommand { get; set; }
public ICommand GoToHomeCommand { get; set; }
protected override void OnCreate(Bundle bundle)
{
base.OnCreate();
AddBottomNavigation();
}
private void AddBottomNavigation()
{
bottomNavigation = (BottomNavigationView)FindViewById(Resource.Id.bottom_navigation);
if (bottomNavigation != null)
{
bottomNavigation.NavigationItemSelected += BottomNavigation_NavigationItemSelected;
// trying to bind command to view model property
var set = this.CreateBindingSet<MainContainerActivity, MainContainerViewModel>();
set.Bind(this).For(v => v.GoToSettingsCommand).To(vm => vm.NavigateToSettingsCommand);
set.Bind(this).For(v => v.GoToHomeCommand).To(vm => vm.NavigateToHomeCommand);
set.Bind(this).For(v => v.GoToFavoritesCommand).To(vm => vm.NavigateToFavoritesCommand);
set.Apply();
}
else
{
System.Diagnostics.Debug.WriteLine("Bottom navigation menu is null");
}
}
private void BottomNavigation_NavigationItemSelected(object sender, BottomNavigationView.NavigationItemSelectedEventArgs e)
{
try
{
System.Diagnostics.Debug.WriteLine($"Bottom navigation menu is selected: {e.Item.ItemId}");
if (e.Item.ItemId == Resource.Id.menu_settings)
if (GoToSettingsCommand != null && GoToSettingsCommand.CanExecute(null))
GoToSettingsCommand.Execute(null);
if (e.Item.ItemId == Resource.Id.menu_list)
if (GoToFavoritesCommand != null && GoToFavoritesCommand.CanExecute(null))
GoToFavoritesCommand.Execute(null);
if (e.Item.ItemId == Resource.Id.menu_home)
if (GoToHomeCommand != null && GoToHomeCommand.CanExecute(null))
GoToHomeCommand.Execute(null);
}
catch (Exception exception)
{
System.Diagnostics.Debug.WriteLine($"Exception: {exception.Message}");
Crashes.TrackError(exception);
}
}
}
}
The bottom navigation elements are:
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="#+id/menu_home"
android:enabled="true"
android:icon="#drawable/ic_history"
android:title="#string/tab1_title"
app:showAsAction="ifRoom" />
<item
android:id="#+id/menu_list"
android:enabled="true"
android:icon="#drawable/ic_list"
android:title="#string/tab2_title"
app:showAsAction="ifRoom" />
<item
android:id="#+id/menu_settings"
android:enabled="true"
android:icon="#drawable/ic_settings"
android:title="#string/tab3_title"
app:showAsAction="ifRoom" />
</menu>
And the commands in view model are just:
public IMvxAsyncCommand NavigateToSettingsCommand => new MvxAsyncCommand(async () => await _navigationService.Navigate<SettingsViewModel>());
public IMvxAsyncCommand NavigateToFavoritesCommand => new MvxAsyncCommand(async () => await _navigationService.Navigate<FavoritesViewModel>());
public IMvxAsyncCommand NavigateToHomeCommand => new MvxAsyncCommand(async () => await _navigationService.Navigate<MainViewModel>());
Instead of using Fluent Binding, you could create a targeted binding for the BottomNavigationView and handle the navigation in your MainViewModel. Use Swiss binding in your XML.
TargetBinding Class:
public class MvxBottomNavigationItemChangedBinding : MvxAndroidTargetBinding
{
readonly BottomNavigationView _bottomNav;
IMvxCommand _command;
public override MvxBindingMode DefaultMode => MvxBindingMode.TwoWay;
public override Type TargetType => typeof(MvxCommand);
public MvxBottomNavigationItemChangedBinding(BottomNavigationView bottomNav) : base(bottomNav)
{
_bottomNav = bottomNav;
_bottomNav.NavigationItemSelected += OnNavigationItemSelected;
}
public override void SetValue(object value)
{
_command = (IMvxCommand)value;
}
protected override void SetValueImpl(object target, object value)
{
}
void OnNavigationItemSelected(object sender, BottomNavigationView.NavigationItemSelectedEventArgs e)
{
if (_command != null)
_command.Execute(e.Item.TitleCondensedFormatted.ToString());
}
protected override void Dispose(bool isDisposing)
{
if (isDisposing)
_bottomNav.NavigationItemSelected -= OnNavigationItemSelected;
base.Dispose(isDisposing);
}
}
Setup.cs :
protected override void FillTargetFactories(IMvxTargetBindingFactoryRegistry registry)
{
MvxAppCompatSetupHelper.FillTargetFactories(registry);
base.FillTargetFactories(registry);
registry.RegisterCustomBindingFactory<BottomNavigationView>("BottomNavigationSelectedBindingKey",
view => new MvxBottomNavigationItemChangedBinding(view));
}
BottomNavigationView XML :
Note the target binding key that we added in Setup.cs is used when binding.
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="#+id/navigation"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#color/white"
app:labelVisibilityMode="labeled"
app:menu="#menu/bottom_nav_menu"
app:elevation="10dp"
local:MvxBind="BottomNavigationSelectedBindingKey BottomNavigationItemSelectedCommand"/>
MainViewModel :
public class MainViewModel : BaseViewModel
{
public IMvxCommand<string> BottomNavigationItemSelectedCommand { get; private set; }
List<TabViewModel> _tabs;
public List<TabViewModel> Tabs
{
get => _tabs;
set => SetProperty(ref _tabs, value);
}
public MainViewModel(IMvxNavigationService navigationService) : base(navigationService)
{
//these are for android - start
BottomNavigationItemSelectedCommand = new MvxCommand<string>(BottomNavigationItemSelected);
var tabs = new List<TabViewModel>
{
Mvx.IoCProvider.IoCConstruct<FirstViewModel>(),
Mvx.IoCProvider.IoCConstruct<SecondViewModel>(),
Mvx.IoCProvider.IoCConstruct<ThirdViewModel>()
};
Tabs = tabs;
//end
}
// Android-only, not used on iOS
private void BottomNavigationItemSelected(string tabId)
{
if (tabId == null)
{
return;
}
foreach (var item in Tabs)
{
if (tabId == item.TabId)
{
_navigationService.Navigate(item);
break;
}
}
}
}
TabViewModel :
public class TabViewModel : BaseViewModel
{
public string TabName { get; protected set; }
public string TabId { get; protected set; }
public TabViewModel(IMvxNavigationService navigationService) : base(navigationService)
{
}
}
BottomNavigation Elements:
Add "android:titleCondensed", which will be used as the Id.
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="#+id/menu_home"
android:enabled="true"
android:icon="#drawable/ic_history"
android:title="#string/tab1_title"
android:titleCondensed ="tab_first"
app:showAsAction="ifRoom" />
<item
android:id="#+id/menu_list"
android:enabled="true"
android:icon="#drawable/ic_list"
android:title="#string/tab2_title"
android:titleCondensed ="tab_second"
app:showAsAction="ifRoom" />
<item
android:id="#+id/menu_settings"
android:enabled="true"
android:icon="#drawable/ic_settings"
android:title="#string/tab3_title"
android:titleCondensed ="tab_third"
app:showAsAction="ifRoom" />
</menu>
ViewModel Examples:
public class FirstViewModel : TabViewModel
{
public FirstViewModel(IMvxNavigationService navigationService) : base(navigationService)
{
TabId = "tab_first";
}
}
public class SecondViewModel : TabViewModel
{
public SecondViewModel(IMvxNavigationService navigationService) : base(navigationService)
{
TabId = "tab_second";
}
}
Hope this helps someone else who comes into this later on ! :)

CustomFragment in MVVMCross

I have used the following template in my Android application which has navigation drawer has a list of options such as Settings.
https://github.com/MvvmCross/MvvmCross-Samples/tree/master/XPlatformMenus
Source code could be downloaded from the following url
https://github.com/MvvmCross/MvvmCross-Samples
I wonder how could I able to make Settings page as a Dialog or CustomFragment which will look like similar to following image.
One approach that you could make use of is to create a custom implementation of Dialog. Following the XPlatformMenus sample you linked to, you could implement something as follows:
Generic Custom Dialog
This class inherits android Dialog control, and can be used with any XML/AXML layout you want. You could tightly couple it to a particular ViewModel/Layout or you can make it handle a generic ViewModel type. Here is an example of the generic type:
public class CustomDialog : Dialog, IMvxBindingContextOwner
{
public CustomDialog(Context context, int layout, IMvxViewModel viewModel)
: this(context, Resource.Style.CustomDialog)
{
this.BindingContext = new MvxAndroidBindingContext(context, (context as IMvxLayoutInflaterHolder));
ViewModel = viewModel;
Init(layout);
}
public CustomDialog(Context context, int themeResId)
: base(context, themeResId)
{
}
protected CustomDialog(IntPtr javaReference, JniHandleOwnership transfer)
: base(javaReference, transfer)
{
}
protected CustomDialog(Context context, bool cancelable, IDialogInterfaceOnCancelListener cancelListener)
: base(context, cancelable, cancelListener)
{
}
protected CustomDialog(Context context, bool cancelable, EventHandler cancelHandler)
: base(context, cancelable, cancelHandler)
{
}
private void Init(int layout)
{
SetContentView(layout);
}
public override void SetContentView(int layoutResID)
{
var view = this.BindingInflate(layoutResID, null);
base.SetContentView(view);
}
public IMvxBindingContext BindingContext { get; set; }
public object DataContext
{
get { return this.BindingContext.DataContext; }
set { this.BindingContext.DataContext = value; }
}
public IMvxViewModel ViewModel
{
get { return this.DataContext as IMvxViewModel; }
set { this.DataContext = value; }
}
}
XML layout for modal:
<?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"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#color/colorPrimary">
<Button
android:id="#+id/btn_option"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Show"
local:MvxBind="Click ShowSettingsCommand"/>
<Button
android:id="#+id/btn_close"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="#id/btn_option"
android:text="CLOSE"
local:MvxBind="Click ShowCloseCommand"/>
</RelativeLayout>
CustomDialog style:
<resources>
<style name="CustomDialog">
<item name="android:windowIsFloating">true</item>
<item name="android:windowNoTitle">true</item>
</style>
</resources>
Custom Presenter
Create a custom presenter to handle the navigation to show/hide the dialog:
public class CustomPresenter : MvxFragmentsPresenter
{
protected IMvxViewModelLoader MvxViewModelLoader => Mvx.Resolve<IMvxViewModelLoader>();
CustomDialog _modal;
public CustomPresenter(IEnumerable<Assembly> AndroidViewAssemblies) : base(AndroidViewAssemblies)
{
}
protected override void ShowActivity(MvxViewModelRequest request, MvxViewModelRequest fragmentRequest = null)
{
if (!Intercept(request))
base.ShowActivity(request, fragmentRequest);
}
protected override void ShowFragment(MvxViewModelRequest request)
{
if (!Intercept(request))
base.ShowFragment(request);
}
private bool Intercept(MvxViewModelRequest request)
{
if (request.ViewModelType == typeof(ThirdViewModel))
{
var activity = Mvx.Resolve<IMvxAndroidCurrentTopActivity>().Activity;
var viewModel = MvxViewModelLoader.LoadViewModel(request, null) as ThirdViewModel;
_modal = new CustomDialog(activity, Resource.Layout.modal_popup, viewModel);
_modal.Show();
return true;
}
if (_modal != null)
{
_modal.Dismiss();
_modal = null;
}
return false;
}
}
Register your custom presenter in the setup class:
protected override IMvxAndroidViewPresenter CreateViewPresenter()
{
var mvxFragmentsPresenter = new CustomPresenter(AndroidViewAssemblies);
Mvx.RegisterSingleton<IMvxAndroidViewPresenter>(mvxFragmentsPresenter);
return mvxFragmentsPresenter;
}
ViewModel
public class ThirdViewModel : BaseViewModel
{
private MvxCommand _showSettingsCommand;
public MvxCommand ShowSettingsCommand =>
_showSettingsCommand ?? (_showSettingsCommand = new MvxCommand(() => ShowViewModel<HomeViewModel>()));
private MvxCommand _showCloseCommand;
public MvxCommand ShowCloseCommand =>
_showCloseCommand ?? (_showCloseCommand = new MvxCommand(() => ShowViewModel<SettingsViewModel>()));
}

Xamarin, Both ListView and RecyclerView, click one item, another one selected

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);

How to make a Single Row Horizontal ListView/List?

What I want to do is Create a Single Row Listview/List with a image and text is this is what i have right now
I have Changed the numColumns to 1 but how do I change it so it is Horizontal and Scrollable? the image should be one top and the text should be below it?
This is the Layout
<?xml version="1.0" encoding="utf-8"?>
<GridView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/gridview"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:columnWidth="90dp"
android:numColumns="1"
android:verticalSpacing="10dp"
android:horizontalSpacing="10dp"
android:stretchMode="columnWidth"
android:gravity="center"
/>
Single Grid Item
<?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="wrap_content">
<ImageView
android:layout_width="match_parent"
android:layout_height="250dp"
android:scaleType="centerCrop"
android:id="#+id/my_image_vieww" />
<TextView
android:textAppearance="?android:attr/textAppearanceLarge"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="#+id/my_text_view" />
</LinearLayout>
Main Activity
var gridview = FindViewById<GridView> (Resource.Id.gridview);
gridview.Adapter = new ImageAdapter (this);
gridview.ItemClick += delegate (object sender, AdapterView.ItemClickEventArgs args) {
Toast.MakeText (this, args.Position.ToString (), ToastLength.Short).Show ();
};
public class ImageAdapter : BaseAdapter
{
Context context;
public ImageAdapter (Context c)
{
context = c;
}
public override int Count {
get { return thumbIds.Length; }
}
public override Java.Lang.Object GetItem (int position)
{
return null;
}
public override long GetItemId (int position)
{
return 0;
}
// create a new ImageView for each item referenced by the Adapter
public override View GetView (int position, View convertView, ViewGroup parent)
{
ImageView imageView;
View view;
if (convertView == null) { // if it's not recycled, initialize some attributes
view = LayoutInflater.From(parent.Context).Inflate(Resource.Layout.my_grid_row, parent, false);
} else {
view = convertView;
}
var imageView2 = view.FindViewById<ImageView>(Resource.Id.my_image_vieww);
imageView2.SetImageResource (thumbIds[position]);
var textView = view.FindViewById<TextView>(Resource.Id.my_text_view);
textView.Text = "Text to Display";
return view;
}
}
Please Dont user horizontal list view.
There is predefined android controll to achieve this. View Pager
Sample Code:
var _viewPager = view.FindViewById<ViewPager>(Resource.Id.viewPager);
var _viewPageIndicatorCircle = view.FindViewById<CirclePageIndicator>(Resource.Id.viewPageIndicator);
_viewPager.Adapter = new TestFragmentAdapter(fm);
_viewPageIndicatorCircle.SetViewPager(_viewPager);
public class TestFragmentAdapter : FragmentPagerAdapter
{
public TestFragmentAdapter(Android.Support.V4.App.FragmentManager fm)
: base(fm)
{
}
public override Android.Support.V4.App.Fragment GetItem(int position)
{
return new TestFragment();
}
public override int Count
{
get
{
return 5;
}
}
}
class TestFragment : Android.Support.V4.App.Fragment
{
public TestFragment()
{
}
public override View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
var view = inflater.Inflate(Resource.Layout.MyViewPager , container, false);
return view;
}
}
Viewpager.xml
<android.support.v4.view.ViewPager
android:id="#+id/viewPager"
android:layout_width="fill_parent"
android:layout_height="209.6dp" />
If you have any queries please feel free to ask me

MVVMCross Custom Control and Binding

I have created a custom control (CustomCard) which is a subclass of the CardView control. I would like to use this control within my project in different places.
For example, I may place the CustomCard within an xml layout manually, or I may want the CustomCard to be an item in an MvxListView. The key is that I would like to re-use the code as much as possible and benefit from having control over the CustomCard class.
When the CustomCard is instantiated, I am inflating it's layout using the standard layout inflater, see code:
using System;
using Android.Animation;
using Android.Content;
using Android.Support.V7.Widget;
using Android.Util;
using Android.Views;
using Android.Widget;
public class Card : CardView
{
private readonly Context _context;
public Card(Context context)
: base(context)
{
_context = context;
Init();
}
public Card(Context context, IAttributeSet attrs)
: base(context, attrs)
{
_context = context;
Init();
}
private void Init()
{
var inflater = (LayoutInflater) _context.GetSystemService(Context.LayoutInflaterService);
CardView = inflater.Inflate(Resource.Layout.base_card, this);
}
}
Within the layout base_card.xml, I have some elements that I would like to bind using MVVMCross, for example,
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:local="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#android:color/white">
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:local="http://schemas.android.com/apk/res-auto"
android:id="#+id/basecard_title"
android:layout_width="match_parent"
android:layout_height="match_parent">
<!-- Title Text-->
<TextView
android:id="#+id/tv_basecard_header_title"
style="#style/card.title"
android:text="title text"
local:MvxBind="Text Title"
/>
<!-- ImageView -->
<MvxImageView
android:id="#+id/ib_basecard_header_button_expand"
style="#style/card.image"
local:MvxBind="Bitmap ImageBytes,Converter=InMemoryImage"/>
</RelativeLayout>
</FrameLayout>
My actual base_card layout is much more complex.
If I try to use my CustomCard within another XML Layout, none of the binding takes place. I think this is because I am using the standard layout inflater to inflate my base_card within my CustomCard rather than BindingInflate() but I can't be sure.
I have searched on SO and through the forums but I can't find any references to anyone using a custom control that inflates it's own view when instantiated with MVVMCross binding.
Has anyone done it, or am I trying to do something that isn't possible?
I ran into similar issue with CardView control. Since CardView directly inherits from FrameLayout I decided to use implementation almost identical to MvxFrameControl (Thanks Stuart for pointing out MvxFrameControl sample):
public class MvxCardView : CardView, IMvxBindingContextOwner
{
private object _cachedDataContext;
private bool _isAttachedToWindow;
private readonly int _templateId;
private readonly IMvxAndroidBindingContext _bindingContext;
public MvxCardView(Context context, IAttributeSet attrs)
: this(MvxAttributeHelpers.ReadTemplateId(context, attrs), context, attrs)
{
}
public MvxCardView(int templateId, Context context, IAttributeSet attrs)
: base(context, attrs)
{
_templateId = templateId;
if (!(context is IMvxLayoutInflater))
{
throw Mvx.Exception("The owning Context for a MvxCardView must implement LayoutInflater");
}
_bindingContext = new MvxAndroidBindingContext(context, (IMvxLayoutInflater)context);
this.DelayBind(() =>
{
if (Content == null && _templateId != 0)
{
Mvx.Trace("DataContext is {0}", DataContext == null ? "Null" : DataContext.ToString());
Content = _bindingContext.BindingInflate(_templateId, this);
}
});
}
protected MvxCardView(IntPtr javaReference, JniHandleOwnership transfer)
: base(javaReference, transfer)
{
}
protected IMvxAndroidBindingContext AndroidBindingContext
{
get { return _bindingContext; }
}
public IMvxBindingContext BindingContext
{
get { return _bindingContext; }
set { throw new NotImplementedException("BindingContext is readonly in the list item"); }
}
protected View Content { get; set; }
protected override void Dispose(bool disposing)
{
if (disposing)
{
this.ClearAllBindings();
_cachedDataContext = null;
}
base.Dispose(disposing);
}
protected override void OnAttachedToWindow()
{
base.OnAttachedToWindow();
_isAttachedToWindow = true;
if (_cachedDataContext != null
&& DataContext == null)
{
DataContext = _cachedDataContext;
}
}
protected override void OnDetachedFromWindow()
{
_cachedDataContext = DataContext;
DataContext = null;
base.OnDetachedFromWindow();
_isAttachedToWindow = false;
}
[MvxSetToNullAfterBinding]
public object DataContext
{
get { return _bindingContext.DataContext; }
set
{
if (_isAttachedToWindow)
{
_bindingContext.DataContext = value;
}
else
{
_cachedDataContext = value;
if (_bindingContext.DataContext != null)
{
_bindingContext.DataContext = null;
}
}
}
}
}
Usage:
<YourNamespace.MvxCardView
android:layout_width="match_parent"
android:layout_height="match_parent"
local:MvxTemplate="#layout/base_card"
local:MvxBind="DataContext ." />
Note: Using custom implementation also solved my problem with binding click command to CardView control using local:MvxBind="Click MyCommand", which wasn't working until subclassing CardView.

Resources