Android: ListView elements with multiple clickable buttons - android

I've a ListView where every element in the list contains a TextView and two different Buttons. Something like this:
ListView
--------------------
[Text]
[Button 1][Button 2]
--------------------
[Text]
[Button 1][Button 2]
--------------------
... (and so on) ...
With this code I can create an OnItemClickListener for the whole item:
listView.setOnItemClickListener(new OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> list, View view, int position, long id) {
Log.i(TAG, "onListItemClick: " + position);
}
}
});
However, I don't want the whole item to be clickable, but only the two buttons of each list element.
So my question is, how do I implement a onClickListener for these two buttons with the following parameters:
int button (which button of the element has been clicked)
int position (which is the element in the list on which the button click happened)
Update: I found a solution as described in my answer below. Now I can click/tap the button via the touch screen. However, I can't manually select it with the trackball. It always selects the whole list item and from there goes directly to the next list item ignoring the buttons, even though I set .setFocusable(true) and setClickable(true) for the buttons in getView().
I also added this code to my custom list adapter:
#Override
public boolean areAllItemsEnabled() {
return false;
}
#Override
public boolean isEnabled(int position) {
return false;
}
This causes that no list item is selectable at all any more. But it didn't help in making the nested buttons selectable.
Anyone an idea?

The solution to this is actually easier than I thought. You can simply add in your custom adapter's getView() method a setOnClickListener() for the buttons you're using.
Any data associated with the button has to be added with myButton.setTag() in the getView() and can be accessed in the onClickListener via view.getTag()
I posted a detailed solution on my blog as a tutorial.

This is sort of an appendage #znq's answer...
There are many cases where you want to know the row position for a clicked item AND you want to know which view in the row was tapped. This is going to be a lot more important in tablet UIs.
You can do this with the following custom adapter:
private static class CustomCursorAdapter extends CursorAdapter {
protected ListView mListView;
protected static class RowViewHolder {
public TextView mTitle;
public TextView mText;
}
public CustomCursorAdapter(Activity activity) {
super();
mListView = activity.getListView();
}
#Override
public void bindView(View view, Context context, Cursor cursor) {
// do what you need to do
}
#Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
View view = View.inflate(context, R.layout.row_layout, null);
RowViewHolder holder = new RowViewHolder();
holder.mTitle = (TextView) view.findViewById(R.id.Title);
holder.mText = (TextView) view.findViewById(R.id.Text);
holder.mTitle.setOnClickListener(mOnTitleClickListener);
holder.mText.setOnClickListener(mOnTextClickListener);
view.setTag(holder);
return view;
}
private OnClickListener mOnTitleClickListener = new OnClickListener() {
#Override
public void onClick(View v) {
final int position = mListView.getPositionForView((View) v.getParent());
Log.v(TAG, "Title clicked, row %d", position);
}
};
private OnClickListener mOnTextClickListener = new OnClickListener() {
#Override
public void onClick(View v) {
final int position = mListView.getPositionForView((View) v.getParent());
Log.v(TAG, "Text clicked, row %d", position);
}
};
}

For future readers:
To select manually the buttons with the trackball use:
myListView.setItemsCanFocus(true);
And to disable the focus on the whole list items:
myListView.setFocusable(false);
myListView.setFocusableInTouchMode(false);
myListView.setClickable(false);
It works fine for me, I can click on buttons with touchscreen and also alows focus an click using keypad

I don't have much experience than above users but I faced this same issue and I Solved this with below Solution
<Button
android:id="#+id/btnRemove"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_toRightOf="#+id/btnEdit"
android:layout_weight="1"
android:background="#drawable/btn"
android:text="#string/remove"
android:onClick="btnRemoveClick"
/>
btnRemoveClick Click event
public void btnRemoveClick(View v)
{
final int position = listviewItem.getPositionForView((View) v.getParent());
listItem.remove(position);
ItemAdapter.notifyDataSetChanged();
}

Probably you've found how to do it, but you can call
ListView.setItemsCanFocus(true)
and now your buttons will catch focus

I am not sure about be the best way, but works fine and all code stays in your ArrayAdapter.
package br.com.fontolan.pessoas.arrayadapter;
import java.util.List;
import android.content.Context;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.EditText;
import android.widget.ImageView;
import br.com.fontolan.pessoas.R;
import br.com.fontolan.pessoas.model.Telefone;
public class TelefoneArrayAdapter extends ArrayAdapter<Telefone> {
private TelefoneArrayAdapter telefoneArrayAdapter = null;
private Context context;
private EditText tipoEditText = null;
private EditText telefoneEditText = null;
private ImageView deleteImageView = null;
public TelefoneArrayAdapter(Context context, List<Telefone> values) {
super(context, R.layout.telefone_form, values);
this.telefoneArrayAdapter = this;
this.context = context;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View view = inflater.inflate(R.layout.telefone_form, parent, false);
tipoEditText = (EditText) view.findViewById(R.id.telefone_form_tipo);
telefoneEditText = (EditText) view.findViewById(R.id.telefone_form_telefone);
deleteImageView = (ImageView) view.findViewById(R.id.telefone_form_delete_image);
final int i = position;
final Telefone telefone = this.getItem(position);
tipoEditText.setText(telefone.getTipo());
telefoneEditText.setText(telefone.getTelefone());
TextWatcher tipoTextWatcher = new TextWatcher() {
#Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
#Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
#Override
public void afterTextChanged(Editable s) {
telefoneArrayAdapter.getItem(i).setTipo(s.toString());
telefoneArrayAdapter.getItem(i).setIsDirty(true);
}
};
TextWatcher telefoneTextWatcher = new TextWatcher() {
#Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
#Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
#Override
public void afterTextChanged(Editable s) {
telefoneArrayAdapter.getItem(i).setTelefone(s.toString());
telefoneArrayAdapter.getItem(i).setIsDirty(true);
}
};
tipoEditText.addTextChangedListener(tipoTextWatcher);
telefoneEditText.addTextChangedListener(telefoneTextWatcher);
deleteImageView.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
telefoneArrayAdapter.remove(telefone);
}
});
return view;
}
}

I Know it's late but this may help, this is an example how I write custom adapter class for different click actions
public class CustomAdapter extends BaseAdapter {
TextView title;
Button button1,button2;
public long getItemId(int position) {
return position;
}
public int getCount() {
return mAlBasicItemsnav.size(); // size of your list array
}
public Object getItem(int position) {
return position;
}
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
convertView = getLayoutInflater().inflate(R.layout.listnavsub_layout, null, false); // use sublayout which you want to inflate in your each list item
}
title = (TextView) convertView.findViewById(R.id.textViewnav); // see you have to find id by using convertView.findViewById
title.setText(mAlBasicItemsnav.get(position));
button1=(Button) convertView.findViewById(R.id.button1);
button1.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
//your click action
// if you have different click action at different positions then
if(position==0)
{
//click action of 1st list item on button click
}
if(position==1)
{
//click action of 2st list item on button click
}
});
// similarly for button 2
button2=(Button) convertView.findViewById(R.id.button2);
button2.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
//your click action
});
return convertView;
}
}

Isn't the platform solution for this implementation to use a context menu that shows on a long press?
Is the question author aware of context menus? Stacking up buttons in a listview has performance implications, will clutter your UI and violate the recommended UI design for the platform.
On the flipside; context menus - by nature of not having a passive representation - are not obvious to the end user. Consider documenting the behaviour?
This guide should give you a good start.
http://www.mikeplate.com/2010/01/21/show-a-context-menu-for-long-clicks-in-an-android-listview/

Related

Set values of components of all Child views in Expandable List

I have an Expandable List that represents settings in my program. The group view has a TextView description with a Button that represents a reset. The child view has a TextView description with an EditText that represents an individual setting.
The values of the EditText are saved in external variables and updated upon any change. Upon click of the reset Button in the group view, I want to reset all individual settings contained in the EditTexts to default values. This works but of course the EditTexts views are not updated until the child views are redrawn. I want the views to all change immediately upon click of the reset button.
I have tried a myriad of things but keep running into issues. I can't seem to access the EditText views within the reset Button listener to programmatically set the text.
My Expandable List (abridged):
public class ExpandableListAdapter extends BaseExpandableListAdapter
{
private String[] listGroups;
private String[][] listChilds;
private Activity context;
final EditText[] etIndividualLetterValues = new EditText[26];
public ExpandableListAdapter(Activity context, String[] listGroups, String[][] listChilds)
{
this.context = context;
this.listGroups = listGroups;
this.listChilds = listChilds;
#Override
public View getChildView(final int listGroupPosition, final int listChildPosition, boolean isLastChild,
View view, ViewGroup viewGroup)
{
LayoutInflater inflater = context.getLayoutInflater();
TextView tvListItemGeneral;
tvListItemGeneral = (TextView) view.findViewById(R.id.TV_list_item_general);
tvListItemGeneral.setText(getChild(listGroupPosition, listChildPosition).toString());
etIndividualLetterValues[listChildPosition] = (EditText) view.findViewById(R.id.ET_list_item_settings_value);
etIndividualLetterValues[listChildPosition].setText(String.format("%d", Settings.settingsCurrent[listGroupPosition][listChildPosition]));
etIndividualLetterValues[listChildPosition].addTextChangedListener(new TextWatcher()
{
#Override
public void beforeTextChanged(CharSequence s, int start, int count, int after)
{
}
#Override
public void onTextChanged(CharSequence s, int start, int before, int count)
{
Settings.settingsCurrent[listGroupPosition][listChildPosition] = Integer.parseInt(s.toString());
}
#Override
public void afterTextChanged(Editable s)
{
}
});
}
#Override
public View getGroupView(final int listGroupPosition, boolean isExpanded, View view, ViewGroup viewGroup)
{
LayoutInflater layoutInflater = (LayoutInflater) this.context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
if(view == null)
{
view = layoutInflater.inflate(R.layout.list_group, viewGroup, false);
}
TextView tvListGroup = (TextView) view.findViewById(R.id.TV_list_group);
Button buttonSettingsReset = (Button) view.findViewById(R.id.B_settings_reset);
buttonSettingsReset.setOnClickListener(new View.OnClickListener()
{
#Override
public void onClick(View v)
{
if(listGroupPosition == C.INDIVIDUAL_LETTER_VALUES)
{
for(int i=0; i<26; i++)
{
etIndividualLetterValues[i].setText(String.format("%d", Settings.settingsDefault[C.INDIVIDUAL_LETTER_VALUES][i]));
}
}
}
});
tvListGroup.setText(getGroup(listGroupPosition).toString());
return view;
}
The line of code:
etIndividualLetterValues[i].setText(String.format("%d", Settings.settingsDefault[C.INDIVIDUAL_LETTER_VALUES][i]));
gives a NPE.
OK after much searching (and rephrasing my searches!) I found out about the method notifyDataSetChanged();
So all I did was add a method to the Button click listener that called a method to reset settings and then called notifyDataSetChanged():
buttonSettingsReset.setOnClickListener(new View.OnClickListener()
{
#Override
public void onClick(View v)
{
if(listGroupPosition == C.INDIVIDUAL_LETTER_VALUES)
{
refresh(listGroupPosition);
}
}
});
//...
public void refresh(int settingsID)
{
resetSettingsClick(context, settingsID);
this.notifyDataSetChanged();
}

How to show an AlertDialog from a Button Clicked in a ListView row?

I am populating a ListView with a Base Adapter in such a way that all except the last item will be checkboxes and the last item will be a TextView with a button.
Here are the XML files.
Final Item:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal" >
<TextView
android:id="#+id/tv_newitem"
android:layout_height="wrap_content"
android:layout_width="0dp"
android:layout_weight="3"
android:text="#string/new_account_text"
/>
<Button
android:id="#+id/b_newitem"
android:layout_height="wrap_content"
android:layout_width="0dp"
android:layout_weight="2"
android:text="#string/add_button_text"
android:onClick="showNewAccountDialog"
/>
</LinearLayout>
Checkboxes:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
>
<CheckBox
android:focusable="true"
android:id="#+id/account_item_cb"
android:layout_height="wrap_content"
android:layout_width="match_parent"
></CheckBox>
</LinearLayout>
Here is the Class file for the base adapter:
import java.util.ArrayList;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.CompoundButton.OnCheckedChangeListener;
public class AccountListAdapter extends BaseAdapter{
private static final int TYPE_ACCOUNT = 0;
private static final int TYPE_NEW_ACCOUNT = 1;
private static final int TYPE_MAX_COUNT = 2;
private LayoutInflater mInflator;
private ArrayList<String> mStrings;
private ArrayList<String> mSelectedStrings;
public AccountListAdapter(Context context, ArrayList<String> array)
{
mInflator = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
mStrings = array;
mSelectedStrings = new ArrayList<String>();
}
public void addNewAccount(final String accountName)
{
mStrings.add(mStrings.size()-2, accountName);
notifyDataSetChanged();
}
#Override
public int getCount()
{
return mStrings.size();
}
#Override
public String getItem(int position)
{
return mStrings.get(position);
}
#Override
public long getItemId(int position)
{
return position;
}
#Override
public int getViewTypeCount()
{
return TYPE_MAX_COUNT;
}
#Override
public int getItemViewType(int position)
{
return position == mStrings.size()-1 ? TYPE_NEW_ACCOUNT : TYPE_ACCOUNT;
}
#Override
public View getView(int position, View convertView, ViewGroup parent)
{
int type = getItemViewType(position);
System.out.println(position + ": " + type);
switch (type) {
case TYPE_ACCOUNT:
convertView = mInflator.inflate(R.layout.account_item, null);
CheckBox tv = (CheckBox) convertView.findViewById(R.id.account_item_cb);
tv.setText(getItem(position));
tv.setOnCheckedChangeListener(new OnCheckedChangeListener() {
#Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
// TODO Auto-generated method stub
if (isChecked)
{
mSelectedStrings.add(buttonView.getText().toString());
}else {
mSelectedStrings.remove(buttonView.getText().toString());
}
}
});
break;
case TYPE_NEW_ACCOUNT:
convertView = mInflator.inflate(R.layout.list_new_item_add_button, null);
break;
default:
break;
}
return convertView;
}
public ArrayList<String> getSelectedStrings()
{
return mSelectedStrings;
}
}
There is an Activity calls which Populates this base adapter will an Array list of String. I am trying to show a dialog box to the user when the Add button is clicked. But I am not able to show it. I tried:
Adding android:onClick=method in the XML file and writing corresponding method in the main activity file, but Eclipse cannot find the function. I think it is looking for the function in the base adapter class. But the problem is I can't write code to show a AlertBox in the Base Adapter class because getSupportFragmentManager cannot be accessed from there.
Adding onClickListener to Button using findViewById, but Eclipse gives me NullPointerException here. I think this is because the button is placed in the ListView and not the Activity directly.
Can someone help me here?
Thanks!
Check this, follow the examples to implement an interface in the activity and pass it to your adapter when you create it.
All you need is to place the open dialog code in the interface method in the activity and call it in the adapter when you click the button.
Place this somewhere in your activity: (this could also be done by making the activity implement the interface)
public interface DialogCreatorInterface{
public void showDialog();
}
DialogCreatorInterface dialogCreatorInterface = new DialogCreatorInterface() {
#Override
public void showDialog() {
//Create and show the dialog code
}
};
Change the adapter constructor to include the interface:
AccountListAdapter(Context context, ArrayList<String> array, DialogCreatorInterface dialogCreatorInterface)
Add this under your TYPE_NEW_ACCOUNT in the getView method:
Button button = (Button) convertView.findViewById(R.id.b_newitem);
button.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
//Call open AlerDialog in activity via the interface
dialogCreatorInterface.showDialog();
}
});
You need to implement
OnItemClickListener
#Override
public void onItemClick(AdapterView<?> arg0, View arg1, int position, long arg3)

Android Handling many EditText fields in a ListView

Just a basic question: If I have several dozen EditText fields that are part of a ListAdapter, how can the individual EditText fields know to which row they belong?
Currently I am using TextWatcher to listen for text input. I have tried extending TextWatcher so that I can pass in the position of the EditText to TextWatcher's constructor.
However, when the soft keyboard pops up, the positions that correspond to the various EditText fields shuffle.
How can I track the EditText fields to their proper position?
I am using a GridView to lay things out. The layout of each item is an ImageView with a TextView and EditText field below it.
The text for each EditText is held in a global String array called strings. It is initially empty, and is updated by my TextWatcher class.
public void initList()
{
ArrayAdapter<String> listAdapter = new ArrayAdapter<String>(this, R.layout.shape, strings)
{
#Override
public View getView(final int position, View convertView, ViewGroup parent) {
if (convertView == null) {
convertView = LayoutInflater.from(getContext()).inflate(R.layout.shape, null);
}
final String theData = getItem(position);
final EditText editText = (EditText) convertView.findViewById(R.id.shape_edittext);
editText.setText(theData);
editText.addTextChangedListener(
new MyTextWatcher(position, editText)
);
ImageView image = (ImageView) convertView.findViewById(R.id.shape_image);
image.setBackgroundResource(images[position]);
TextView text = (TextView) convertView.findViewById(R.id.shape_text);
if (gameType == SHAPES_ABSTRACT)
text.setText("Seq:");
else
text.setVisibility(View.GONE);
return convertView;
}
#Override
public String getItem(int position) { return strings[position]; }
};
grid.setAdapter(listAdapter);
}
private class MyTextWatcher implements TextWatcher {
private int index;
private EditText edittext;
public MyTextWatcher(int index, EditText edittext) {
this.index = index;
this.edittext = edittext;
}
public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
public void onTextChanged(CharSequence s, int start, int before, int count) {}
public void afterTextChanged(Editable s) { strings[index] = s.toString(); }
public void setIndex(int newindex) { index = newindex; }
}
When I click into the first EditText (see picture), the EditText shifts to the one under the smiley face.
Not taking into account if this is a good UI design, here's how you'd do it:
public class TestList
{
public void blah()
{
ArrayAdapter<DataBucket> listAdapter = new ArrayAdapter<DataBucket>()
{
#Override
public View getView(int position, View convertView, ViewGroup parent)
{
if (convertView == null)
{
convertView = LayoutInflater.from(getContext()).inflate(R.layout.testlayout, null);
}
final DataBucket dataBucket = getItem(position);
final EditText editText = (EditText) convertView.findViewById(R.id.theText);
editText.setText(dataBucket.getSomeData());
editText.addTextChangedListener(new TextWatcher()
{
public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2)
{
}
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2)
{
}
public void afterTextChanged(Editable editable)
{
dataBucket.setSomeData(editable.toString());
}
});
return convertView;
}
};
}
public static class DataBucket
{
private String someData;
public String getSomeData()
{
return someData;
}
public void setSomeData(String someData)
{
this.someData = someData;
}
}
}
'DataBucket' is a placeholder. You need to use whatever class you created to store the data that gets put into and edited in the edit text. The TextWatcher will have a reference to the data object referenced. As you scroll, the edit text boxes should get updated with current data, and text changes should be saved. You may want to track which objects were changed by the user to make data/network updates more efficient.
* Edit *
To use an int position rather than directly referencing the object:
ArrayAdapter<DataBucket> listAdapter = new ArrayAdapter<DataBucket>()
{
#Override
public View getView(final int position, View convertView, ViewGroup parent)
{
if (convertView == null)
{
convertView = LayoutInflater.from(getContext()).inflate(R.layout.testlayout, null);
}
final DataBucket dataBucket = getItem(position);
final EditText editText = (EditText) convertView.findViewById(R.id.theText);
editText.setText(dataBucket.getSomeData());
editText.addTextChangedListener(new TextWatcher()
{
public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2)
{
}
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2)
{
}
public void afterTextChanged(Editable editable)
{
getItem(position).setSomeData(editable.toString());
}
});
return convertView;
}
};
* Edit Again *
I feel compelled to say for posterity, I wouldn't actually code it this way. I'd guess you want a little more structured data than a String array, and you're maintaining the String array outside, as well as an ArrayAdapter, so its sort of a weird parallel situation. However, this will work fine.
I have my data in a single String array rather than a multi-dimensional array. The reason is because the data model backing the GridView is just a simple list. That may be counterintuitive, but that's the way it is. GridView should do the layout itself, and if left to its own devices, will populate the row with variable numbers of cells, depending on how much data you have and how wide your screen is (AFAIK).
Enough chat. The code:
public class TestList extends Activity
{
private String[] guess;
//Other methods in here, onCreate, etc
//Call me from somewhere else. Probably onCreate.
public void initList()
{
ArrayAdapter<String> listAdapter = new ArrayAdapter<String>(this, /*some resourse id*/, guess)
{
#Override
public View getView(final int position, View convertView, ViewGroup parent)
{
if (convertView == null)
{
convertView = LayoutInflater.from(getContext()).inflate(R.layout.testlayout, null);
}
final String theData = getItem(position);
final EditText editText = (EditText) convertView.findViewById(R.id.theText);
editText.setText(theData);
editText.addTextChangedListener(
new MyTextWatcher(position)
);
return convertView;
}
};
gridView.setAdapter(listAdapter);
}
class MyTextWatcher extends TextWatcher {
private int position;
public MyTextWatcher(int position) {
this.position = position;
}
public void afterTextChanged(Editable s) {
guess[position] = s.toString();
}
// other methods are created, but empty
}
}
To track the row number, each listener in EditText has to keep a reference to an item in a list and use getPosition(item) to get the position in a ListView. My example uses Button but I think that it can be applied to EditText.
class DoubleAdapter extends ArrayAdapter<Double> {
public DoubleAdapter(Context context, List<Double> list) {
super(context, android.R.layout.simple_list_item_1, list);
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
convertView = LayoutInflater.from(getContext()).inflate(R.layout.item_row, null);
}
// keep a reference to an item in a list
final Double d = getItem(position);
TextView lblId = (TextView) convertView.findViewById(R.id.lblId);
lblId.setText(d.toString());
Button button1 = (Button) convertView.findViewById(R.id.button1);
button1.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
// the button listener has a reference to an item in the list
// so it can know its position in the ListView
int i = getPosition(d);
Toast.makeText(getContext(), "" + i, Toast.LENGTH_LONG).show();
remove(d);
}
});
return convertView;
}
}
It might be worth considering whether you need the edit texts to be stored in the list cells? It seems a little bit unnecessary when the user will only be editing one at a time.
Whilst I do not know how your app is designed I would recommend rethinking your user experience slightly so that when an list item is pressed a single text edit appears for them to edit. That way you can just get the list items reference as you normally would with a list adapter, store it whilst the user is editing and update it when they have finished.
i'm not sure if that's a nice design you have, as the EditText content will have a good chance of having problems (shuffling content, missing text) once your listview is scrolled. consider trying out m6tt's idea.
but if you really want to go your way, can you post some code, specifically of your TextWatcher?
I tried to solve this and as you can see there is a simple method - I am posting the answer here as it might be useful for someone.
Not able to get the position when list view -> edit text has a text watcher.
This is the solution that worked for me :
In get view -
when I add the text watcher listener to edit text, I also added the below line
edittext.setTag(R.id.position<any unique string identitiy>, position)
in your afterTextChanged -
int position = edittext.getTag(R.id.position)
Gives the correct position number and you can do modifications based on the position number.

Filtered ListView not updated

I have a ListView with a custom Adapter that extends ArrayAdapter. It's a ArrayAdapter of Type Artist.
Artist is a very small class that has a name and an id. The Artist Class has toString() overridden to return just the name.
I have an EditText. The EditText has an TextChangeListener where i call .getFilter().filter(chars, callback) on my adapter.
In the Filter.Filterlistener().onComplete() callback i print the count and it looks really good. As i type the count decreases. So it seams everything works as advertised, but the List stays the same. I tried to call artistAdapter.notifyDataSetChanged() to force the list to redraw, but nothing happens. [see 2.)]
I am tinkering around for days now! I am desperate.. Hopefully someone can have a look on my code and tell me what i am doing wrong!
Thanks!
Here is what i have done:
1.) Defined a ListView and an EditText like this:
<EditText xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/list_search_text"
android:layout_width="fill_parent"
android:layout_height="35dip"
android:layout_below="#id/header">
</EditText>
<ListView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/list_search"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
</ListView>
2.) Setup my ListView in the Activities onCreate():
private ListView listView = null;
private ArtistAdapter artistAdapter = null;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.search_artists);
artistAdapter = new ArtistAdapter(this, R.layout.row, list); // 'list' is an ArrayList<Artist>
listView = (ListView) findViewById(R.id.list_search);
listView.setAdapter(artistAdapter);
listView.setFastScrollEnabled(true);
listView.setTextFilterEnabled(true);
listView.setOnItemClickListener(new OnItemClickListener() {
public void onItemClick(AdapterView<?> av, View v, int position, long id) {
// do something
}
});
EditText txtSearch = (EditText) findViewById(R.id.list_search_text);
txtSearch.addTextChangedListener(new TextWatcher() {
public void afterTextChanged(Editable arg0) { }
public void beforeTextChanged(CharSequence arg0, int arg1, int arg2, int arg3) { }
public void onTextChanged(CharSequence chars, int start, int before, int count) {
artistAdapter.getFilter().filter(chars, new Filter.FilterListener() {
public void onFilterComplete(int count) {
Log.d(Config.LOG_TAG, "filter complete! count: " + count);
artistAdapter.notifyDataSetChanged();
}
});
}
});
}
3.) This is my ArtistAdapter in short. I added an remove() and add() method:
public class ArtistAdapter extends ArrayAdapter<Artist> implements SectionIndexer {
private List<Artist> items;
/* other stuff like overridden getView, getPositionForSection, getSectionForPosition and so on */
#Override
public void remove(Artist object) {
super.remove(object);
items.remove(object);
}
#Override
public void add(Artist object) {
super.add(object);
items.add(object);
}
}
4.) My artist has also the toString() overridden:
public class Artist implements Comparable<Artist> {
public String uid;
public String name;
public Artist(String id, String name) {
this.uid = id;
this.name = name;
}
public int compareTo(Artist another) {
return this.name.compareToIgnoreCase(another.name);
}
#Override
public String toString() {
return this.name;
}
}
I found the solution. I have rewritten the whole code from scratch and found the problem.
In my custom ArrayAdapter called ArtistAdapter (see 3. in question) if have a var to store the items:
private List items;
And in my overridden getView() when i want to get the current item i did:
items.getItem(position)
now i do a:
getItems(position) (so the item is retrieved from the storage of the base class and not my own) and this seems to do the trick!
This is my getView() as it is now (the version, that is working):
#Override
public View getView(int position, View convertView, ViewGroup parent) {
LinearLayout artistView;
Artist art = getItem(position);
if (convertView == null) {
artistView = new LinearLayout(getContext());
String inflater = Context.LAYOUT_INFLATER_SERVICE;
LayoutInflater vi;
vi = (LayoutInflater)getContext().getSystemService(inflater);
vi.inflate(resId, artistView, true);
} else {
artistView = (LinearLayout) convertView;
}
TextView nameView = (TextView)artistView.findViewById(R.id.txt_name);
TextView uidView = (TextView)artistView.findViewById(R.id.txt_uid);
nameView.setText(art.name);
uidView.setText(art.uid);
return artistView;
}
Pfu, this was a really annoying bug...
Try using listView.setFilterText() rather then adapter filter.
You have to override the toString() method, to return the text you want to filter.

How to get the Value out of an RecyclerView item

I have a Fragment which carries a Button and a RecyclerView, set up by an RecyclerView Adapter. In the RecyclerView are several Items, one of it is a EditText. Now I want that when the Button is clicked(which is NOT in the RecyclerView object), that I get the values of the EditTexts.
I already tried to get the recyclerView.getItemAtPosition() but there is no function like that, also tried the same for the adapter. So I would need something like
ArrayList s.add(recyclerView.getItemAtPosition(position).getEditText().getText().toString());
This is my Adapter:
public class RVSetAdapter extends RecyclerView.Adapter<RVSetAdapter.ViewHolder> {
private Exercise exercise;
public static class ViewHolder extends RecyclerView.ViewHolder {
public EditText et_weight;
public TextView tv_sets,tv_indication;
public ViewHolder(#NonNull final View itemView) {
super(itemView);
tv_sets = itemView.findViewById(R.id.tv_sets);
tv_indication = itemView.findViewById(R.id.tv_indication);
et_weight = itemView.findViewById(R.id.et_weight);
}
}
public RVSetAdapter(Exercise exercise) {
this.exercise = exercise;
}
#NonNull
#Override
public RVSetAdapter.ViewHolder onCreateViewHolder(#NonNull ViewGroup viewGroup, int i) {
View view = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.rv_set,viewGroup,false);
RVSetAdapter.ViewHolder vh_set = new RVSetAdapter.ViewHolder(view);
return vh_set;
}
#Override
public void onBindViewHolder(#NonNull final RVSetAdapter.ViewHolder viewHolder,final int i) {
if(exercise.getKind() == 80) {
viewHolder.tv_sets.setText("");
viewHolder.tv_indication.setText("sec.");
}else if(exercise.getKind() == 90) {
viewHolder.tv_sets.setText("");
viewHolder.tv_indication.setText("min.");
}else {
viewHolder.tv_sets.setText(Integer.toString(i + 1) + ".");
}
viewHolder.et_weight.setText(Integer.toString(exercise.getWeights().get(i)));
}
#Override
public int getItemCount() {
return exercise.getWeights().size();
}
}
this is my Fragment:
final View view = layoutInflater.inflate(R.layout.fragment_exercise, container,false);
ImageView iv_exercise = view.findViewById(R.id.iv_exercise);
ImageView iv_musclekind = view.findViewById(R.id.iv_musclekind);
ImageView iv_save = view.findViewById(R.id.iv_save);
TextView tv_exercisename = view.findViewById(R.id.tv_exercisename);
TextView tv_exercisedescription = view.findViewById(R.id.tv_exercisedescription);
iv_exercise.setImageResource(exercises.get(position).getImage());
iv_musclekind.setImageResource(exercises.get(position).getMusclekindImage());
tv_exercisename.setText(exercises.get(position).getName());
tv_exercisedescription.setText(exercises.get(position).getDescription());
iv_save.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
//here I want to get the Values of the EditTexts and put them into an Array
}
});
recyclerView = view.findViewById(R.id.rv_sets);
recyclerView.setHasFixedSize(true); //maybe change this
layoutManager = new LinearLayoutManager(view.getContext());
adapter = new RVSetAdapter(exercises.get(position));
recyclerView.setLayoutManager(layoutManager);
recyclerView.setAdapter(adapter);
container.addView(view);
return view;
I don't have any ideas to go on so I would appreciate your help. If there is any uncertainty with my description of the problem please don't hesitate to ask.
Greetings Alexander
With RecyclerView, you have to understand that your EditTexts will be recycled. For example, if you have a list of 200 items, and it shows 2 items at one time, you will only ever have 2 EditText. They will reuse the higher EditText for the lower elements.
For example, here is a list that contains EditText showing only 2 at a time, and as the user scrolls, it will recycle and reuse them.
EditText A
Edittext B
EditText A (recycled)
EditText B (recycled)
....
This means you cannot just loop over all the elements later and get the values, as they don't store their values.
So, what you want to do, is when the user modifies an EditText, you want to store that value right away. You can do this by adding a TextWatcher to your EditText.
Note - I did assume you store your weights as String values, so I just took the value from the EditText and stored it into your Exercise Object. You may want to convert it before that.
public class RVSetAdapter extends RecyclerView.Adapter<RVSetAdapter.ViewHolder> {
private Exercise exercise;
// ...
#Override
public void onBindViewHolder(#NonNull final RVSetAdapter.ViewHolder viewHolder,final int i) {
// ...
viewHolder.et_weight.setText(Integer.toString(exercise.getWeights().get(i)));
viewHolder.et_weight..addTextChangedListener(new TextWatcher() {
public void afterTextChanged(Editable s) {
// This will be the text from the EditText
String text = s.toString();
// Store the value back into your exercise Object.
exercise.getWeights().get(i).setWeight(text);
}
public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
public void onTextChanged(CharSequence s, int start, int before, int count) {}
});
}
// ...
// Add a method for easy access to your weights.
public ArrayList<String> getWeights() {
return exercise.getWeights();
}
}
And now, within your Fragment, you can easily get the values out of your RVSetAdapter.
public View onCreateView() {
final View view = layoutInflater.inflate(R.layout.fragment_exercise, container,false);
// ...
iv_save.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
// Use the method we added to your adapter to return the weights.
ArrayList<String> weights = adapter.getWeights();
}
});
// ...
return view;
}
I think you should use ArrayList in Adapter class to keep your items (or just Strings of EditText components). Add String to ArrayList in your onBindViewHolder() after you set text for editext. Then make a function which will get item from your ArrayList like:
public String getItem(int position){
arrayList.get(position);
}
and call it from your onClick() function in Fragment.
I think you can create static button and you can then access that button in your adapter then implement the functionality on the onclick of your button.
static Button btn;
Then implement like this in your adapter...
btn.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
for(int i=0;i<arraylist.size();i++)
{
arr[i]= holder.edit_Text.getText().toString();
}
}
});
and put this onclick in your onbindviewholder method.

Categories

Resources