I am new to android. I have implemented custom ArrayAdapter in my Android Application using view holder.
The getView() function of my ArrayAdapter is as follows for reference:
#Override
public View getView(int position, View convertView, final ViewGroup parent) {
View row = convertView;
MyClassViewHolder myClassViewHolder;
MyClass myClass;
if(row == null) {
LayoutInflater inflater = ((Activity)mContext).getLayoutInflater();
row = inflater.inflate(resourceId, parent, false);
if(resourceId == R.layout.my_row_item) {
myClassViewHolder = new MyClassViewHolder();
myClassViewHolder.title = (EditText) row.findViewById(R.id.title);
myClassViewHolder.switch = (Switch) row.findViewById(R.id.switch);
}
} else {
myViewHolder = (MyViewHolder) row.getTag();
}
if(resourceId == R.layout.my_row_item) {
myClass = (MyClass) myClassList.get(position); //myClassList sent as parameter to constructor of adapter
if(myClassViewHolder != null && myClass != null) {
myClassViewHolder.title.setText(myClass.getTitle());
myClassViewHolder.switch.setChecked(myClass.isEnabled());
myClassViewholder.id = myClass.getId();
myClassViewHolder.switch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
#Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
//GET ID OF THE ROW ITEM HERE
}
});
}
}
}
First of all I want to associate an id which is from database to
every row item to perform actions on them. So please confirm if the
way I have done is is right or wrong.
Secondly in the above code I have a String as title and a Switch in every row
item. I want to set an onClickListener on each switch. On toggling
the switch i want to get the id of the row item which is associated as per point 1.
Thanks in advance. Please let me know if I haven't described my problem properly.
Yes your code looks fine as for second part you should make a listener on switch and then get the id form the row and do switch from one id to another.
You may need to set a tag for each row item and thus you can identify each row. Here is an example -
row.setTag(1);
and to retrive the tag -
row.getTag();
Related
I'm using listview custom adapter which with row click i'm changing row color. But when i'm scrolling bot and up again it doesnt have the right position.
It changes color in other rows...
public override View GetView(int position, View convertView, ViewGroup parent)
{
DataViewHolder holder = null;
if (convertView == null)
{
convertView = LayoutInflater.From(mContext).Inflate(Resource.Layout.TableItems, null, false);
holder = new DataViewHolder();
holder.txtDescription = convertView.FindViewById<TextView>(Resource.Id.txtDescription);
holder.txtDescription.Click += delegate
{
holder.txtDescription.SetBackgroundColor(Color.Red);
};
convertView.Tag = holder;
}
else
{
holder = convertView.Tag as DataViewHolder;
}
holder.txtDescription.Text = mitems[position].Description;
return convertView;
}
public class DataViewHolder : Java.Lang.Object
{
public TextView txtDescription { get; set; }
}
It looks like it doesnt keep in memory specific row situation.
Don't change the color in the click handler directly, instead change the data from which the adapter draws from and use that to change the color when GetView is called again.
ListView recycles the views it uses to optimize scrolling, instead it just expects the view to represent the data. If you change a color of one view directly, the view then gets recycled and you'll see "another view" (another part of the data) with a different background color.
So in summary: give each data point a color attribute and use that to set the color of each view in GetView, change the data and notify the adapter about the changes to the data.
Edit
I've never used Xamarin but maybe something like this would work
public override View GetView(int position, View convertView, ViewGroup parent)
{
DataViewHolder holder = null;
if (convertView == null)
{
convertView = LayoutInflater.From(mContext).Inflate(Resource.Layout.TableItems, null, false);
holder = new DataViewHolder();
holder.txtDescription = convertView.FindViewById<TextView>(Resource.Id.txtDescription);
holder.txtDescription.Click += delegate
{
// instead of setting the color directly here, just modify the data
(holder.txtDescription.Tag as ItemType).ItemColor = Color.Red
notifyDataSetChanged();
};
convertView.Tag = holder;
}
else
{
holder = convertView.Tag as DataViewHolder;
}
holder.txtDescription.Text = mitems[position].Description;
holder.txtDescription.Tag = mitems[position]; // this so that the click handler knows which item to modify
holder.txtDescription.SetBackgroundColor(mitems[position].ItemColor);
return convertView;
}
public class DataViewHolder : Java.Lang.Object
{
public TextView txtDescription { get; set; }
}
ListView will reuse the item layout, you can use List and View.Tag to avoid the problem caused by reusing.
I have posted my demo on github.
My objective is to convert a working spinner (populated via a cursor adapter) to have alternating backgrounds. Similar to :-
Currently I have this, where everything works fine :-
This is the relevant working code within the cursor adpater (i.e. with the plain dropdowns) :-
#Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
return LayoutInflater.from(context).inflate(R.layout.activity_aisle_shop_list_selector, parent, false);
}
#Override
public void bindView(View view,Context context, Cursor cursor) {
determineViewBeingProcessed(view,"BindV",-1);
TextView shopname = (TextView) view.findViewById(R.id.aaslstv01);
shopname.setText(cursor.getString(shops_shopname_offset));
}
I have tried adding an override of the getDropDownView (code as below). I get the alternating row colors as I want but the dropdown views are blank. However, if I click outside of the selector, then they get populated with data (hence how I managed to get the screen shot, shown above, of what I want). Selection sets the correct Item.
If I remove the return after inflating the layout, then the dropdown views are populated but with data from other rows (however,selection selects the correct item)
public View getDropDownView(int position, View convertview, ViewGroup parent) {
View v = convertview;
determineViewBeingProcessed(v,"GetDDV",position);
if( v == null) {
v = LayoutInflater.from(parent.getContext()).inflate(R.layout.activity_aisle_shop_list_entry, parent, false);
return v;
}
Context context = v.getContext();
TextView shopname = (TextView) v.findViewById(R.id.aasletv01);
shopname.setText(getCursor().getString(shops_shopname_offset));
if(position % 2 == 0) {
v.setBackgroundColor(ContextCompat.getColor(context,R.color.colorlistviewroweven));
} else {
v.setBackgroundColor(ContextCompat.getColor(context,R.color.colorlistviewrowodd));
}
return v;
}
The clues were they I just didn't think hard enough. The issue is with the cursor being in the wrong position because the cursor needs to be obtained via getCursor().
Additionally, the return after the inflate, is premature (this has been commented out).
Adding getCursor().moveToPosition(position); before accessing data from the cursor resolves the problem.
Alternately (perhaps more correctly, comments appreciated on whether or not one method is more correct than the other). Adding:-
Cursor cursor = getCursor();
cursor.moveToPosition(position);
and then replacing subsequent getCursor() with cursor (not mandatory) also works.
So the final code for getDropDownView method could be:-
public View getDropDownView(int position, View convertview, ViewGroup parent) {
View v = convertview;
determineViewBeingProcessed(v,"GetDDV",position);
if( v == null) {
v = LayoutInflater.from(parent.getContext()).inflate(R.layout.activity_aisle_shop_list_entry, parent, false);
//return v;
}
Context context = v.getContext();
Cursor cursor = getCursor();
cursor.moveToPosition(position);
TextView shopname = (TextView) v.findViewById(R.id.aasletv01);
shopname.setText(cursor.getString(shops_shopname_offset));
if(position % 2 == 0) {
v.setBackgroundColor(ContextCompat.getColor(context,R.color.colorlistviewroweven));
} else {
v.setBackgroundColor(ContextCompat.getColor(context,R.color.colorlistviewrowodd));
}
return v;
}
I don't know what's wrong with my code, that it always returns null when I use getParseObject().
I'm using parse.com to save my data, and in one table I used one file as a pointer. I have a Game class that has ImgName as a Pointer<Gallery> to a gallery class.
Now I want to retrieve the ImgName value, so this is what I did:
public Adapter(Context context) {
super(context, new ParseQueryAdapter.QueryFactory<ParseObject>() {
public ParseQuery create() {
ParseQuery query = new ParseQuery("Game");
query.include("ImgName");
return query;
}
});
}
// Customize the layout by overriding getItemView
#Override
public View getItemView(final ParseObject object, View v, ViewGroup parent) {
if (v == null) {
v = View.inflate(getContext(), R.layout.list_item_landing_cards, null);
}
ParseObject gallery = object.getParseObject("ImgName");
String name=gallery.getString("name");
TextView nameTextView = (TextView) v.findViewById(R.id.text);
nameTextView.setText(name);
But I'm getting null all the time.
Any suggestions?
Use this for the re-use issue:
ParseObject gallery = object.getParseObject("ImgName");
if (gallery != null) {
String name=gallery.getString("name");
TextView nameTextView = (TextView) v.findViewById(R.id.text);
nameTextView.setText(name);
} else {
nameTextView.setText(""); // or any other default value you want to set
}
NOTE:
The cell re-use issue is not on Parse. Cell re-use is a general concept used by the ListView. The cells are recycled for performance by Android. We just have to protect it from re-using old values.
I'm creating a custom adapter and using the getView method in attempt to display a default text only ONCE. Now I'm having a problem such that when I click the first Item in the list, the default text is kept but that doesn't hold for any other items? Any suggestions?
Thanks! (My code is a bit messy as I was just trying to debug)
boolean firstTime = true;
#Override
public View getView(int position, View convertView, ViewGroup parent) {
if (firstTime) {
firstTime = false;
TextView firstView = new TextView(ForgotPasswordActivity.this);
firstView.setText("Please select School");
return firstView;
}
TextView view = new TextView(ForgotPasswordActivity.this);
view.setText("Hello");
return view;
}
You must play with the getCount function :
#Override
public int getCount() {
return super.getCount() -1; // This makes the trick;
}
this trick will not show last item that you've added inside your spinner(so when you finish adding your text inside the spinner, add the text that will not be shown in the spinner, and by that it will be show as a default value before clicking the spinner).
Good luck
I'm not exactly sure what your trying to do but you could force the top row to always show the select message by checking if the position is 0. Also notice in the code below that I'm reusing the convertView if it is not null. It's faster to reuse the convertView if it is available than to recreate a new view every time.
#Override
public View getView(int position, View convertView, ViewGroup parent) {
if(convertView == null) {
convertView = new TextView(ForgotPasswordActivity.this);
}
if(position == 0) {
convertView.setText("Please select School");
} else {
convertView.setText("Hello");
}
return convertView;
}
Also remember that by forcing position zero to show the select message you are not showing the actual data in the adapter at position 0. Make sure this is what you want to do or insert a dummy piece of data in the first position of the backing data array.
I have a ListView which uses a custom adapter as shown:
private class CBAdapter extends BaseAdapter implements OnCheckedChangeListener{
Context context;
public String[] englishNames;
LayoutInflater inflater;
CheckBox[] checkBoxArray;
LinearLayout[] viewArray;
private boolean[] checked;
public CBAdapter(Context con, String[] engNames){
context=con;
englishNames=engNames;
inflater=(LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
checked= new boolean[englishNames.length];
for(int i=0; i<checked.length; i++){
checked[i]=false;
//Toast.makeText(con, checked.toString(),Toast.LENGTH_SHORT).show();
}
checkBoxArray = new CheckBox[checked.length];
viewArray = new LinearLayout[checked.length];
}
public int getCount() {
return englishNames.length;
}
public Object getItem(int position) {
// TODO Auto-generated method stub
return null;
}
public long getItemId(int position) {
// TODO Auto-generated method stub
return 0;
}
public View getView(int position, View convertView, ViewGroup parent) {
if(viewArray[position] == null){
viewArray[position]=(LinearLayout)inflater.inflate(R.layout.record_view_start,null);
TextView tv=(TextView)viewArray[position].findViewById(R.id.engName);
tv.setText(englishNames[position]);
checkBoxArray[position]=(CheckBox)viewArray[position].findViewById(R.id.checkBox1);
}
checkBoxArray[position].setChecked(checked[position]);
checkBoxArray[position].setOnCheckedChangeListener(this);
return viewArray[position];
}
public void checkAll(boolean areChecked){
for(int i=0; i<checked.length; i++){
checked[i]=areChecked;
if(checkBoxArray[i] != null)
checkBoxArray[i].setChecked(areChecked);
}
notifyDataSetChanged();
}
public void onCheckedChanged(CompoundButton cb, boolean isChecked) {
for(int i=0; i<checked.length; i++){
if(cb == checkBoxArray[i])
checked[i]=isChecked;
}
}
public boolean itemIsChecked(int i){
return checked[i];
}
}
The layouts are fairly simple so I won't post them unless anyone thinks they are relevant.
The problem is that some of the CheckBoxes are not responding. It seems to be the ones that are visible when the layout is first displayed. Any that you have to scroll down to work as expected.
Any pointers appreciated.
Your code from the answer works but is inefficient(you can actually see this, just scroll the ListView and check the Logcat to see the garbage collector doing it's work). An improved getView method which will recycle views is the one below:
#Override
public View getView(int position, View convertView, ViewGroup parent) {
LinearLayout view = (LinearLayout) convertView;
if (view == null) {
view = (LinearLayout) inflater.inflate(R.layout.record_view_start, parent, false);
}
TextView tv = (TextView) view.findViewById(R.id.engName);
tv.setText(getItem(position));
CheckBox cBox = (CheckBox) view.findViewById(R.id.checkBox1);
cBox.setTag(Integer.valueOf(position)); // set the tag so we can identify the correct row in the listener
cBox.setChecked(mChecked[position]); // set the status as we stored it
cBox.setOnCheckedChangeListener(mListener); // set the listener
return view;
}
OnCheckedChangeListener mListener = new OnCheckedChangeListener() {
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
mChecked[(Integer)buttonView.getTag()] = isChecked; // get the tag so we know the row and store the status
}
};
Regarding your code from your question, at first I thought it was wrong because of the way you setup the rows but I don't see why the adapter will have that behavior as you detached the row view from the list. Also, I even tested the code and it works quite well regarding CheckBoxes(but with very poor memory handling). Maybe you're doing something else that makes the adapter to not work?
Let me first say that you have thrown away one of the main benefits of using an adapter: Reusable views. Holding a hard reference to each created View holds a high risk of hitting the memory ceiling. You should be reusing convertView when it is non-null, and creating your view when convertView is null. There are many tutorials around which show you how to do this.
Views used in an adapter typically have an OnClickListener attached to them by the parent View so that you can set a OnItemClickListener on the ListView. This will supersede any touch listeners on the individual views. Try setting android:clickable="true" on the CheckBox in XML.
This may not be the most elegant or efficient solution but it works for my situation. For some reason attempting to reuse the views either from an array of views or using convertView makes every thing go wobbley and the CheckBoxes fail to respond.
The only thing that worked was creating a new View everytime getView() is called.
public View getView(final int position, View convertView, ViewGroup parent) {
LinearLayout view;
view=(LinearLayout)inflater.inflate(R.layout.record_view_start,null);
TextView tv=(TextView)view.findViewById(R.id.engName);
tv.setText(englishNames[position]);
CheckBox cBox=(CheckBox)view.findViewById(R.id.checkBox1);
cBox.setChecked(checked[position]);
cBox.setOnCheckedChangeListener(new OnCheckedChangeListener(){
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
checked[position]=isChecked;
}
});
return view;
}
Finding this solution was also hampered by the fact that I was calling a separately defined onCheckedChangedListener, that then identified which CheckBox by id, rather than having a new listener for each CheckBox.
As yet I haven't marked this as the correct answer as I'm hoping that others may have some input regarding the rather wasteful rebuilding the view every time.