I created a class called AddItemBaseAdapter that adds a new row in the ListView. The problem is that when adding a new row, the previous row is deleted. To add a new row I have a editTex and a button on Main.axml. For this case it is better to use ArrayAdapter or BaseAdapter continue using?
AddItemBaseAdapter.cs:
public class AddItemBaseAdapter: BaseAdapter<string>
{
string textReceivedEditText;
Activity context;
public AddItemBaseAdapter(Activity context, string textReceivedEditText) : base()
{
this.context = context;
this.textReceivedEditText = textReceivedEditText;
}
public override long GetItemId(int position){
return position;
}
public override string this[int position] {
get { return textReceivedEditText; }
}
public override int Count {
get { return 1; }
}
public override View GetView(int position, View convertView, ViewGroup parent)
{
View view = convertView; // re-use an existing view, if one is available
if (view == null)
view = context.LayoutInflater.Inflate (Resource.Layout.newText, null);
view.FindViewById<TextView>(Resource.Id.singleText).Text = textReceivedEditText;
return view;
}
}
OnClick in MainActivity.cs
btnAddNewRow.Click += delegate(object sender, EventArgs e) {
string textFromEditText = FindViewById<EditText> (Resource.Id.editText).Text;
if(!(textFromEditText.Equals(string.Empty)))
{
_HistoryList = FindViewById<ListView>(Resource.Id.TextHistoryList);
_HistoryList.Adapter = new AddItemBaseAdapter(this, textFromEditText);
}
FindViewById<EditText> (Resource.Id.editText).Text = string.Empty;
};
You are creating an entirely new AddItemBaseAdapter every time the button is clicked.
_HistoryList.Adapter = new AddItemBaseAdapter(this, textFromEditText);
Your adapter does not even have the capacity to store more than one item as it stands now. Perhaps try using (or at least studying) the ArrayAdapter class to start. Then if you require futher functionality beyond that, you can extend it or write your own adapter.
Related
I have a custom listview in my C# Android app, each row contains a textview, ImageView and a switch. When a Listview item is clicked, I want to turn the row's item switch on.
MainActivity:
List<TableList> list = = new List<TableList>();
list.Add(new TableList("Germany"));
list.Add(new TableList("France"));
list.Add(new TableList("Finland"));
listView.ItemClick += delegate (object sender, AdapterView.ItemClickEventArgs e)
{
string selected = t.Name;
if (selected == "France")
{
// Turn the proper switch for France row ON
}
};
ListAdapter and ListClass for the Listview:
public class ListAdapter : BaseAdapter<TableList>
{
List<TableList> items;
Activity context;
public ListAdapter(Activity context, List<TableList> items)
: base()
{
this.context = context;
this.items = items;
}
public override long GetItemId(int position)
{
return position;
}
public override TableList this[int position]
{
get { return items[position]; }
}
public override int Count
{
get { return items.Count; }
}
public override View GetView(int position, View convertView, ViewGroup parent)
{
var item = items[position];
View view = convertView;
if (view == null) // no view to re-use, create new
view = context.LayoutInflater.Inflate(Resource.Layout.CoinList, null);
view.FindViewById<TextView>(Resource.Id.CoinName).Text = item.Name;
view.FindViewById<ImageView>(Resource.Id.imageView1).SetImageResource(Resource.Drawable.n);
If item is clicked set it on
{
view.FindViewById<Switch>(Resource.Id.switch).SetOn
}
else
{
view.FindViewById<Switch>(Resource.Id.switch).SetOf
}
return view;
}
}
public class TableList
{
public string Name;
public TableList(string Name)
{
this.Name = Name;
}
}
I don't know where I should set the Switch ON (in the listView.ItemClick event or in the ListAdapter) and I don't know how to set it to ON. Please help me to do so.
Here is my demo.
You can choose one to achieve your goal. I will show you how to do this by ItemClick event:
When a Listview item is clicked, I want to turn the row's item switch on.
Because, Switch will grab the focus from ViewGroup. So, I remove the focus from Switch in the MyAdapter:
holder.ms.Focusable = false;//ms is Switch
Now, this is my ItemClick event( turn switch on while click the item):
private void MListView_ItemClick(object sender, AdapterView.ItemClickEventArgs e)
{
var ll = e.View as LinearLayout;
var sw = ll.GetChildAt(1) as Switch;
if (sw.Checked)
{
sw.Checked = false;
adapter.changeState((int)sw.Tag,false);
}
else
{
sw.Checked = true;
adapter.changeState((int)sw.Tag, true);
}
}
As we all know, ListView has reuse problem, so, I add a bool property to control the Switch's state:
public class MyData:Java.Lang.Object {
public MyData(string p,bool b) {
this.position = p;
this.isCheck = b;
}
public string position { get; set; }
public bool isCheck { get; set; }
}
Below is changeState method:
internal void changeState(int position, bool v)
{
mitems[position].isCheck = v;
this.NotifyDataSetChanged();
}
And this is CheckedChange event:
private void Ms_CheckedChange(object sender, CompoundButton.CheckedChangeEventArgs e)
{
var sm = sender as Switch;
Log.Error("Ms_CheckedChange", (int)sm.Tag+"");
if (e.IsChecked&&!mitems[(int)sm.Tag].isCheck)
{
mitems[(int)sm.Tag].isCheck = true;
this.NotifyDataSetChanged();
}
else if(!e.IsChecked&& mitems[(int)sm.Tag].isCheck)
{
mitems[(int)sm.Tag].isCheck = false;
this.NotifyDataSetChanged();
}
}
I was wondering if there is a way to make a multi choice list view that is actually able to return the selected indexes. I've been able to do it with the pre-made multiplechoicelistview adapter but I need to be able to edit the style of it. So I need a custom listview.
This is my oncreate code a
protected override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
SetContentView(Resource.Layout.Options);
outList = FindViewById<ListView>(Resource.Id.outList);
var btnCheck = FindViewById<ImageButton>(Resource.Id.btnConfirm);
var btnBack = FindViewById<ImageButton>(Resource.Id.btnBack);
for (int i = 0; i < NewProfileVars.LifeStyles.Length; i++)
{
inList.Add(NewProfileVars.LifeStyles[i].Name);
}
//list contents end here
ListViewAdapter adapter = new ListViewAdapter(this, inList);
outList.Adapter = adapter;
outList.ChoiceMode = ChoiceMode.Multiple;
NewProfile main = new NewProfile();
btnCheck.Click += Confirm;
btnBack.Click += Back;
}
And here is my list view adaptor code
class ListViewAdapter: BaseAdapter<string>
{
public List<string> Items;
public Context Context;
public ListViewAdapter(Context context, List<string> items)
{
Items = items;
Context = context;
}
public override int Count
{
get { return Items.Count; }
}
public override long GetItemId(int position)
{
return position;
}
public override string this[int position]
{
get { return Items[position]; }
}
public override View GetView(int position, View convertView, ViewGroup parent)
{
View row = convertView;
if (row == null)
{
row = LayoutInflater.From(Context).Inflate(Resource.Layout.ListBox, null, false);
}
CheckBox txtName = row.FindViewById<CheckBox>(Resource.Id.cbName);
txtName.Text = Items[position];
return row;
}
}
All I need now is to figure out how that confirm button would save the things I have selected.
Thank you in advanced for the help.
I see you are using CheckBox in your ListView. You could get the Items that where Checked using something like this:
First create a class that will hold your item data and the Checked state, for the example let's call it
public class LifeStylesListItem
{
public string Name { get; set; }
public bool IsSelected { get; set; }
public LifeStylesListItem(string name)
{
Name = name;
}
}
Then modify your ListViewAdapter
Add a new private field that will hold a list of LifeStylesListItem
private List<LifeStylesListItem> _list;
Initialize the list with the Items passed in the constructor.
public ListViewAdapter(Context context, List<string> items)
{
Items = items;
_list = new List<LifeStylesListItem>();
//Your are creating a copy of your Items
foreach (var item in items)
{
_list.Add(new LifeStylesListItem(item));
}
Context = context;
}
In the GetView method subscribe to the CheckedChange event of your CheckBox. This way you will be notify when it's checked state has changed. Also you need to set the Checked Property based on the Item IsSelected value. This is necessary when the ListView will reuse your cell.
public override View GetView(int position, View convertView, ViewGroup parent)
{
View row = convertView;
if (row == null)
{
row = LayoutInflater.From(Context).Inflate(Resource.Layout.ListBox, null, false);
}
CheckBox txtName = row.FindViewById<CheckBox>(Resource.Id.cbName);
txtName.Text = _list[position].Name;
txtName.Checked = _list[position].IsSelected;
txtName.CheckedChange -= TxtName_CheckedChange;
txtName.CheckedChange += TxtName_CheckedChange;
return row;
}
Add the event handler TxtName_CheckedChange method
void TxtName_CheckedChange(object sender, CompoundButton.CheckedChangeEventArgs e)
{
//These lines are used to get the position of the control that was clicked
var obj = sender as CheckBox;
var row = obj?.Parent as View;
var parent = row?.Parent as ListView;
if (parent == null)
{
return;
}
var position = parent.GetPositionForView(row);
// Once you have the position you can get the item and change
// its IsSelected
var item = _list[position];
item.IsSelected = e.IsChecked;
}
Then a last method to add in the Adapter is the one that will return the selected Items. With the help of Linq (using System.Linq needs to be added) you can query the selected items like this.
public List<string> GetCheckedItems()
{
return _list
.Where(a => a.IsSelected)
.Select(b => b.Name)
.ToList();
}
Now in your Activity you just need to call the GetCheckedItems from the ListViewAdapter method on the Confirm button click:
private void Confirm(object sender, EventArgs e)
{
var checkedItems = adapter.GetCheckedItems();
}
Remember to change adapter as private field in your Activity
private ListViewAdapter adapter;
Hope this helps.-
I have tried to look out for an answer to the behavior of my views but I seem not find any question or solution related to it. My recycler views seemed to be set up well. I just realized that my app is not responding in the right way to the OnClickListeners.
I have set up toasts in my adapter for the recycler view click events. When i have 10 views. When i click on a view, it gives a text of another view. It seems it randomly gives me the text of another view amongst the 9 remaining views. What could be the cause of this?
Activity
protected override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
FrameLayout content = (FrameLayout)FindViewById(Resource.Id.content_frame);
LayoutInflater.Inflate(Resource.Layout.Main, content);
setUpRecyclerView();
}
public void setUpRecyclerView(){
rv = FindViewById<RecyclerView>(Resource.Id.recyclerView);
LinearLayoutManager layoutManager = new LinearLayoutManager(this);
layoutManager.Orientation = LinearLayoutManager.Vertical;
layoutManager.ReverseLayout = true;
layoutManager.StackFromEnd = true;
rv.HasFixedSize = true;
rv.SetLayoutManager(layoutManager);
}
Adapter
public class FeedViewHolder : RecyclerView.ViewHolder, View.IOnClickListener, View.IOnLongClickListener
{
public FeedViewHolder(View itemView):base(itemView)
{
//binding of variables here
itemView.SetOnClickListener(this);
itemView.SetOnLongClickListener(this);
}
public void OnClick(View v)
{
itemClickListener.OnClick(v, AdapterPosition, false);
}
public bool OnLongClick(View v)
{
itemClickListener.OnClick(v, AdapterPosition, true);
return true;
}
public class FeedAdapter : RecyclerView.Adapter, ItemClickListener
{
public FeedAdapter(RssObject rssObject, Context mContext)
{
this.mContext = mContext;
this.inflater = LayoutInflater.From(mContext);
activity = (MainActivity)mContext;
}
public override void OnBindViewHolder(RecyclerView.ViewHolder holder, int position)
{
hold = holder as FeedViewHolder;
//binding
hold.itemClickListener = this;
}
public void OnClick(View view, int position, bool isLongClick)
{
Toast.MakeText(activity, "Main text : " + hold.txtContent.Text, ToastLength.Long).Show();
}
public override int ItemCount
{
get { return rssObject.items.Count; }
}
}
}
}
Im using tab layout (with ViewPager, it's Adapter is an extension of FragmentStatePagerAdapter and TabLayout) and im trying to refresh a listview on tab2 when i click on a button on tab1.
Im doing this job from the activity that holds the viewpager for now because i dont know any other way to do it.
The way i refresh it is to:
Monitor OnTabSelected of ViewPager
When tab2 become Visible, refresh the ListView with something like this:
View _list = Activity.FindViewById(Resource.Id.MessagesListView);
var _adapter = new MessagesAdapter(Activity, null);
_adapter.NotifyDataSetChanged();
_adapter = new MessagesAdapter(Activity, _reviewList);
var _listview= (ListView)_list;
_listview.Adapter = _adapter;
_adapter.NotifyDataSetChanged();
As you might understand from the code, my problem is that the ListView doesnt replace its items, it only adds them in top of the others, how i can clear the listview?
I have also try with no luck with .RemoveViews(0, test.ChildCount);
My listview adapter is a simple extension of the BaseAdapter.
Also if you have another better way of Refreshing ListView or Fragment im open to suggestions
EDIT:
My final Adapter that works pretty well, if anyone has more suggestions to make it even better please make a comment
using Android.Support.V4.App;
using Android.Util;
using Java.Lang;
using Fragment = Android.Support.V4.App.Fragment;
namespace StaffPro
{
public class TabsFragmentPagerAdapter : FragmentStatePagerAdapter
{
private readonly Fragment[] fragments;
private readonly ICharSequence[] titles;
SparseArray<Fragment> registeredFragments = new SparseArray<Fragment>();
public TabsFragmentPagerAdapter(FragmentManager fm, Fragment[] fragments, ICharSequence[] titles) : base(fm)
{
this.fragments = fragments;
this.titles = titles;
}
public override int Count
{
get
{
return fragments.Length;
}
}
public override Fragment GetItem(int position)
{
return fragments[position];
}
public override ICharSequence GetPageTitleFormatted(int position)
{
return titles[position];
}
public override void DestroyItem(Android.Views.View container, int position, Java.Lang.Object objectValue)
{
registeredFragments.Remove(position);
base.DestroyItem(container, position, objectValue);
}
public override Java.Lang.Object InstantiateItem(Android.Views.ViewGroup container, int position)
{
Fragment fragment = (Fragment)base.InstantiateItem(container, position);
registeredFragments.Put(position, fragment);
return fragment;
}
public Fragment GetRegisteredFragment(int position)
{
return registeredFragments.Get(position);
}
}
}
You could call a refresh directly within that fragment from the parent activity.
You'll need to implement on your ViewPager Adaptor a way for returning the fragment, so something like this:
public override void DestroyItem(View container, int position, Object #object)
{
registeredFragments.Remove(position);
base.DestroyItem(container, position, #object);
}
public override Object InstantiateItem(ViewGroup container, int position)
{
Android.Support.V4.App.Fragment fragment = (Android.Support.V4.App.Fragment)base.InstantiateItem(container, position);
registeredFragments.Put(position, fragment);
return fragment;
}
public Android.Support.V4.App.Fragment GetRegisteredFragment(int position)
{
return registeredFragments.Get(position);
}
Then in your main activity:
public void UpdateFragment()
{
var activeFragment = _Adaptor.GetRegisteredFragment(1); // where in the viewpager the fragment is
((YourFragment)activeFragment).MethodOnFragment();
}
I have my fragments inside view page but I would like to reload the data on the tab selected.I tried returning PositionNone in the GetItemPosition method in my FragmentPagerAdapter but it does not work.
I tried adding notifyDataSetChanged(); on tab selected but it throws nullpointer exception.
I even tried setting the viewPager.setOffscreenPageLimit
I managed to find the link :
Replace Fragment inside a ViewPager
but it is on java.
I have managed to convert most of the code to c# but gets stuck on the commented code. I am not sure how to call the listener and instantiate the class at the same time? There is a lot of similar question but unfortunately not one in Xamarin Android.
I need to do this in xamarin android. Any help will be greatly appreciated.
This is my FragmentAdapter
public class TabsFragmentPagerAdapter : FragmentPagerAdapter
{
private readonly Android.Support.V4.App.Fragment[] fragments;
static readonly int NUM_ITEMS = 2;
private readonly Android.Support.V4.App.FragmentManager FragmentManager;
private Android.Support.V4.App.Fragment mFragmentAtPos0;
private readonly ICharSequence[] titles;
public TabsFragmentPagerAdapter(Android.Support.V4.App.FragmentManager fm, Android.Support.V4.App.Fragment[] fragments, ICharSequence[] titles) : base(fm)
{
this.fragments = fragments;
this.titles = titles;
}
public override int Count
{
get
{
return fragments.Length;
}
}
public override int GetItemPosition(Java.Lang.Object objectValue)
{
if (objectValue.GetType() == typeof(startJobFrag) && mFragmentAtPos0.GetType() == typeof(jobCaptureFrag))
{
return PositionNone;
}
return PositionUnchanged;
}
public override ICharSequence GetPageTitleFormatted(int position)
{
return titles[position];
}
public override Android.Support.V4.App.Fragment GetItem(int position)
{
if (position == 0)
{
if (mFragmentAtPos0 == null)
{
//not working
}
return mFragmentAtPos0;
}
else
{
return new jobCaptureFrag();
}
}
This is my Actvity
void FnInitTabLayout()
{
var fragments = new Android.Support.V4.App.Fragment[]
{
new frag 1(),
new frag 2(),
new frag 3(),
};
//Tab title array
var titles = CharSequence.ArrayFromStringArray(new[] {
"Page 1",
"Page 2",
"Page 3",
});
var viewPager = FindViewById<ViewPager>(Resource.Id.viewpager);
//viewpager holding fragment array and tab title text
//viewPager.
viewPager.Adapter = new TabsFragmentPagerAdapter(SupportFragmentManager, fragments, titles);
// Give the TabLayout the ViewPager
tabLayout.SetupWithViewPager(viewPager);
viewPager.PageSelected += (object sender, ViewPager.PageSelectedEventArgs e) =>
{
//FnInitTabLayout();
if(e.Position == 0)
{
//handle tab click/selected
}
};
}
public interface FirstPageFragmentListener
{
void onSwitchToNextFragment();
}
Fragment
public class Fragment1: Android.Support.V4.App.Fragment
{
public override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
// Create your fragment here
}
public override View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
// Use this to return your custom view for this Fragment
// return inflater.Inflate(Resource.Layout.YourFragment, container, false);
View view = inflater.Inflate(Resource.Layout.myview, container, false);
}
}
I managed to solve it on my own.
In My activity I called NotifyDataSetChanged() on Pageselected event
Activity
ViewPager.PageSelected += (object sender, ViewPager.PageSelectedEventArgs e) =>
{
if (e.Position == 1)
{
_pager.Adapter.NotifyDataSetChanged();
}
};
Then I changed my Adapter from FragmentPagerAdapter to FragmentStatePagerAdapter and override the GetItemPosition.
Adapter
public class TabsFragmentPagerAdapter : FragmentStatePagerAdapter
{
public override int GetItemPosition(Java.Lang.Object objectValue)
{
return PositionNone;
}
}
Refresh a specific tab - inside the Adapter
public override Android.Support.V4.App.Fragment GetItem(int position)
{
if(position == 1) //second tab selected
{
NotifyDataSetChanged();
}
return fragments[position];
}
I'm sure there's a better way to do it but this works.