my question is how to access and change the checkBox mode for any item in a listactivity. I have an XML template file with a checkbox and a textview, and these define a row. Here's what I'm trying so far:
#Override
protected void onListItemClick(ListView l, View v, int position, long id) {
super.onListItemClick(l, v, position, id);
Toast.makeText(this, "You selected: " + Integer.toString(position), Toast.LENGTH_LONG).show();
CheckBox checkbox = (CheckBox) findViewById(R.id.checkbox);
if (checkbox.isChecked() == false) {
checkbox.setChecked(true);
} else {
checkbox.setChecked(false);
}
}
Obviously though using R.id.checkbox only toggles the first checkbox (actually, it does the first checkbox of whatever part of the list I'm looking at on my screen). I'm not sure what function to use to get the checkbox of any row though. The Toast works fine btw, so at least it registers position properly.
Thanks for any help.
EDIT - I'm now trying to subclass the SimpleCursorAdapter to better control the behaviour I want. Here is that subclass:
public class musicPlaylist extends SimpleCursorAdapter {
private Cursor c;
private Context context;
private ArrayList<String> checkList = new ArrayList<String>();
private final static int SELECTED = 1;
private final static int NOT_SELECTED = 0;
public musicPlaylist(Context context, int layout, Cursor c,
String[] from, int[] to) {
super(context, layout, c, from, to);
this.c = c;
this.context = context;
}
public View getView(int pos, View inView, ViewGroup parent) {
View v = inView;
if (v == null) {
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
v = inflater.inflate(R.layout.song_item, null);
}
this.c.moveToPosition(pos);
int columnIndex = this.c.getColumnIndexOrThrow(MediaStore.Audio.Media.DISPLAY_NAME);
String song = this.c.getString(columnIndex);
TextView sTitle = (TextView) v.findViewById(R.id.text1);
sTitle.setText(song);
v.setId(NOT_SELECTED);
v.setTag(song);
v.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
// TODO Auto-generated method stub
if (v.getId() == NOT_SELECTED) {
v.setId(SELECTED);
Toast.makeText(context, "Test: " + v.getId(), Toast.LENGTH_SHORT).show();
v.setBackgroundColor(Color.parseColor("#FFFFFF"));
} else {
v.setId(NOT_SELECTED);
Toast.makeText(context, "Test: " + v.getId(), Toast.LENGTH_SHORT).show();
v.setBackgroundColor(Color.parseColor("#000000"));
}
}
});
return v;
}
}
And for reference, here is the XML of the ListActivity I'm making:
<?xml version="1.0" encoding="utf-8"?>
<ListView android:id="#android:id/list"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_weight="1"
android:drawSelectorOnTop="false"
android:fastScrollEnabled="true"
android:cacheColorHint="#00000000"
/>
<TextView android:id="#android:id/empty"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:text="No data"/>
Current behaviour: the list of songs from the SD card is made into a nice scrollable list. I do get somewhat proper responses from getView()'s onClick: The first time I click an item, it Toasts that its tag is "1" and its background goes white, while the second time I toast the same item, I get "0" and the background goes black, which is as expected.
The problem is if I select item1 (making its background white) and then scroll down, I'll notice that item11, item21, item31, ... , etc ALSO have white backgrounds. But when I click on them, their ID attribute goes to "1", meaning they've technically never been clicked before! So basically when the scroll "refreshes" to the next list of 10, it copies the color scheme of the first 10...?
Hope I explained it clearly.
I think this is bit deeper question and not direct answer is needed.
What do you want to achieve? Do you really want to make selected ONLY the checkboxes that you see on screen? Mind that this might be pretty random - list view only holds item views for the checkboxes that are visible on screen and they are reused for other items whenever the item is scrolled outside the screen.
I'd say that almost for sure you need to change state of all the checkboxes in your list (even those not visible) or some subset of them (like section). Which really translates into the proper way it should be done:
modify your data model appropriately
marking the appropriate flags selected in corresponding data model elements
(some boolean values you store per item)
call notifyDataSetChanged() on your adapter.
As a result, list view will recreate all the views which are visible on screen. Assuming that your "getView()" in adapter is written correctly, it will read the right model and update checked state on the item appropriately.
By notifyDataSetChanged - if you have 10 items visible on screen you will have 10 times getView() called for every item visible.
I recommend using android:choiceMode="multipleChoice" instead of manually manipulating your rows this way. The row widget will need to implement the Checkable interface, which can either be done by using CheckedTextView as the row itself, or creating a subclass of your desired container and implementing Checkable on it.
You use "global" view instead your row view. Try like that:
#Override
protected void onListItemClick(ListView l, View v, int position, long id) {
super.onListItemClick(l, v, position, id);
CheckBox checkbox = (CheckBox) v.findViewById(R.id.checkbox);
if (checkbox.isChecked() == false) {
checkbox.setChecked(true);
} else {
checkbox.setChecked(false);
}
}
Related
I have implemented a list View with CheckedTextView . When I am selecting a particular row and clicking on checkbox then checkbox becomes invisible. Also sometimes any other row get selected.I want to select multiple items.
please help...
List_row Layout is..
<CheckedTextView
android:id="#+id/service_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="TextView"
android:textSize="20sp"
android:layout_marginBottom="10dp"
android:checkMark="?android:attr/listChoiceIndicatorMultiple"
android:textColor="#000000"
android:checked= "false"
>
CustomListView is:
import java.util.ArrayList;
public class CustomListView extends ArrayAdapter {
//to reference the Activity
private final Activity context;
String value;
//to store the list items
private final String[] nameArray;
CheckedTextView nameTextField;
public CustomListView(Activity context, ArrayList nameArra) {
super(context, R.layout.row_list_view, nameArrayParam);
this.context = context;
this.nameArray = nameArrayParam;
}
public View getView(int position, View view, ViewGroup parent)
{
LayoutInflater inflater = context.getLayoutInflater();
View rowView = inflater.inflate(R.layout.row_list_view, null, true);
//this code gets references to objects in the listview_row.xml file
nameTextField = (CheckedTextView)
rowView.findViewById(R.id.service_name);
//this code sets the values of the objects to values from the arrays
nameTextField.setText(nameArray[position]);
// perform on Click Event Listener on CheckedTextView
nameTextField.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Log.v("customList",";clicked row is " +
nameTextField.getText().toString());
if (nameTextField.isChecked()) {
// set check mark drawable and set checked property to false
value = "un-Checked";
nameTextField.setCheckMarkDrawable(R.color.colorAccent);
nameTextField.setChecked(false);
}
else {
// set check mark drawable and set checked property to true
value = "Checked";
nameTextField.setChecked(true);
}
Toast.makeText(context, value, Toast.LENGTH_SHORT).show();
}
});
return rowView;
}
In my MainActivity.java, I have used listView setOnItemClickListener
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> arg0, View arg1, int arg2,
long arg3) {
selected_Item = (String) arg0.getItemAtPosition(arg2);
Toast.makeText(AskForService.this, "Clicked item is" + selected_Item, Toast.LENGTH_LONG).show();
}
});
After implementing these code I can see -
ListView with checked TextView
ListView Can Scroll showing all the list items
on clicking a particular row..listview click listener is called and showing the Toast but checkbox becomes white(invisible) and sometimes another row get selected...Please help
Create a model class which contains a flag "isChecked" and set the data according to the model inside adapter
This link might be helpful : https://stackoverflow.com/a/40285759/8770539
Use an arraylist to store checked item's position.
call notifyDataSetChanged() for onclick()
Use this,
if(list.contains(position)) {
nameTextField.setChecked(true);
}
else {
nameTextField.setChecked(false);
}
nameTextField.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if(list.contains(position))
{
list.remove(position);
}
else
{
list.add(position);
}
notifyDataSetChanged();
}
});
How does one achieve a consistent item selection with RecyclerView ? I get the whole idea of RecyclerView recycling old views and what not. My problem is I have a list of items with an icon on each item, when an item is clicked, the icon changes colour. I managed to achieve all of this, but I just realised, as I scroll down the list, that other items have their icons changed too, and when I scroll back to the item I clicked on, the icon is no longer in the "clicked color".
Does anyone know how to keep track of the selected items? I keep seeing something called SparseBooleanArray, but I am not sure how to implement it.
Here is my adapter code:
public class TableRVAdapter extends RecyclerView.Adapter<TableRVAdapter.TableHolder> {
List<Tables> tableList;
private SparseBooleanArray selectedItems;
public TableRVAdapter(List<Tables> tableList)
{
this.tableList = tableList;
selectedItems = new SparseBooleanArray();
// setHasStableIds(true);
}
class TableHolder extends RecyclerView.ViewHolder
{
TextView tableTV;
CardView tableCV;
View circle;
View parentView;
TableHolder(final View itemView)
{
super(itemView);
tableTV = (TextView)itemView.findViewById(R.id.tableTV);
tableCV = (CardView)itemView.findViewById(R.id.tableCV);
circle = itemView.findViewById(R.id.statusCircle);
itemView.setClickable(true);
parentView = itemView;
tableCV.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
circle.setBackgroundResource(R.drawable.circle);
}
});
}
/*
#Override
public void onClick(View view) {
if (selectedItems.get(getAdapterPosition(), false)) {
tableCV.setSelected(false);
circle.setBackgroundResource(R.drawable.circle2);
}
else {
selectedItems.put(getAdapterPosition(), true);
tableCV.setSelected(true);
circle.setBackgroundResource(R.drawable.circle);
}
}*/
}
#Override
public TableHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.table_item,parent,false);
TableHolder tableHolder = new TableHolder(view);
return tableHolder;
}
#Override
public void onAttachedToRecyclerView(RecyclerView recyclerView)
{
super.onAttachedToRecyclerView(recyclerView);
}
#Override
public void onBindViewHolder(TableHolder holder, int position) {
holder.tableTV.setText(tableList.get(position).getTableNumber());
}
/**
* Returns the total number of items in the data set hold by the adapter.
*
* #return The total number of items in this adapter.
*/
#Override
public int getItemCount() {
return tableList.size();
}
}
I know this question is already answered but someone might find this answer useful and easier.
There is one useful method in the recycler view is onViewRecycled.
This can help you to get away from the pressed state. When working with the images/drawables in the recylcer view when some of the elements doesn't use the drawables, previews recycler view's drawn image will be present and it makes look all the data wrong as it happened with you with your selected view.
To achieve this in a more simpler way you can use this method to clear the drawn images to reset its values.
For example, you mark you selected element's background to green, and some of your views contains images(Note: Only some of your elements), than use this method as like this:
#Override
public void onViewRecycled(MyViewHolder holder) {
super.onViewRecycled(holder);
// Set ImageView's Drawable as transperent
holder.myImageView.setBackgroundColor(ContextCompat.getColor(getApplicationContext(), android.R.color.transparent));
// Set background color as transperent
holder.bgContainer.setBackgroundColor(ContextCompat.getColor(getApplicationContext(), android.R.color.transparent));
}
And now you can manage an ArrayList of integers with ids/position of selected elements (using onClick method on recycler view's item).
With onBindViewHolder(MyViewHolder myViewHolder, int position) you can normally fill the data. And for setting background you can simply check if the id/position is in ArrayList or not (If true, set background color green else leave it as it is. As it will be managed by onViewRecylcer method and wont affect other scrolled items with green background even if it is not selected).
Hope it will help someone and make the recyler view with selection a bit more easier.
RecyclerView.adapter has got 2 important functions to override:
onCreateViewHolder(parent, viewType)
onBindVIewHolder(viewholder, position)
The first function is used to inflate views that will be used inside the recyclerview, the second is used to bind the data you have to this view, and thus set the correct viewstate on the view.
The recyclerview itself will only inflate a certain amount of views and then will start to re-use already inflated views (hence recyclerview). So you need to set the correct state for each item in onBindViewholder() and use the item in your collection on that position to set the correct viewState.
For your example: change the color of the icon, dependent on a boolean value in your objects, eg : isPressed
You have to save the instance like this
if (isPressed) {
icon.setCustomIcon();
} else {
icon.setDefaultIcon();
}
if you have a custom object to have a boolean isPressed or something similar, this should work
In the case of a ListView if we want to make a particular item selected we use the setSelection method. How do we do this in case of RecyclerView?
Use RecyclerView LayoutManager to scroll item at position
recyclerView.getLayoutManager().scrollToPosition(position)
Check
scrollToPositionWithOffset(int position, int offset)
scrollToPositionWithOffset(5,0);
from LinearLayoutManager
Scroll to the specified adapter position with the given offset from resolved layout start.
pass offset as 0 if you want selection at top
This worked for me
Check
recyclerView.scrollToPosition(cursor.getcount() - 1);
ListView.setSelected() does (at least) two things:
It sets the item in the list to be selected (while removing the selection from another item - if such exists)
It scrolls the list so that the item will be visible on the screen.
To achieve 2. either call scrollToPosition() method of RecyclerView (as indicated by Loser), or call one of the scrolling methods of the LayoutManager object depending on your desired scrolling behavior.
For example,
recyclerView.getLayoutManager().smoothScrollToPosition()
You may want to scroll the minimum so that the selected item shows on the screen.
If so and you are using LinearLayoutManager or GridLayoutManager, you can build such scroll logic based on
findFirstCompletelyVisibleItemPosition() and findLastCompletelyVisibleItemPosition() defined in these classes.
Achieving 1. is more tricky. You may want to use the following recipe:
First define a background color in colors.xml, item_state_selected_color, to be used when an item is selected.
In your onCreateViewHolder() implementation create a StateListDrawalbe and set it as the background of the view.
Say something like this:
public ItemViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) {
// inflate the item view
View itemView = LayoutInflater.from(viewGroup.getContext()).
inflate(itemResourceId,viewGroup, false);
// create color drawable by a resorce id
ColorDrawable colorDrawableSelected =
new ColorDrawable(resources.getColor(R.color.item_state_selected_color));
// create StateListDrawable object and define its states
StateListDrawable stateListDrawable = new StateListDrawable();
stateListDrawable.addState(new int[]{android.R.attr.state_selected}, colorDrawableSelected);
stateListDrawable.addState(StateSet.WILD_CARD, null);
// set the StateListDrawable as background of the item view
if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.JELLY_BEAN) {
itemView.setBackgroundDrawable(stateListDrawable);
}
else {
itemView.itemView.setBackground(stateListDrawable);
}
// create view holder object providing it with the item view
return new YourViewHolder(itemView);
}
In YourAdapter object (or elsewhere) save a variable, mCurrentSelectedPosition (probably initialized to -1) that holds the current selected position.
Still in the adapter, define handler for clicks on recycler view items, depending on your click logic. For example:
void onItemClick(int position) {
YourViewHolder yourViewHolder;
int oldSelectedPosition = mCurrentSelectedPosition;
if (position != mCurrentSelectedPosition) {
mCurrentSelectedPosition = position;
if (oldSelectedPosition != -1) {
yourViewHolder = findViewHolderForPosition(oldSelectedPosition);
yourViewHolder.itemView.setSelected(false);
}
yourViewHolder = findViewHolderForPosition(mCurrentSelectedPosition);
yourViewHolder.itemView.setSelected(true);
}
}
Next, in the constructor of YourViewHolder set listener to clicks on the item:
public YourViewHolder(View itemView,YourAdapter adapter) {
mAdapter = adapter;
// ... other code here ...
itemView.setOnClickListener(this);
}
Still in YourViewHolder override the onClick() method to delegate handling to the adapter. like this
#Override
public void onClick(View v) {
mAdapter.onItemClick(getPosition());
}
Now there is just last problem to solve - we need to keep track of the selected item with respect to recycling.
#Override
public void onBindViewHolder(YourViewHolder yourViewHolder, int position) {
if (position == mCurrentSelectedPosition) {
yourViewHolder.itemView.setSelected(true);
}
else {
yourViewHolder.itemView.setSelected(false);
}
// ... other code here ...
}
Good luck!
I want my ListView to contain buttons, but setting the button's xml property, onClick="myFunction" and then placing a public void myFunction(android.view.View view) method in the activity causes an NoSuchMethodException (the stack trace is null) to be thrown, as although the onclick listener is there, it doesn't fire myFunction(...) and cause the activity to close.
How do I create a custom Adapter that connects a View.OnClickListener to a button on each row of a ListView?
My ListView is created as follows...
[activity.java content..]
public void myFunction(android.view.View view)
{
//Do stuff
}
[activity.xml content..]
<LinearLayout xmlns:tools="http://schemas.android.com/tools" xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" tools:context=".FrmCustomerDetails" >
<ListView android:id="#+id/LstCustomerDetailsList" android:layout_width="fill_parent" android:layout_height="0dip" android:layout_weight="1" android:clickable="true" android:clipChildren="true" android:divider="#null" android:dividerHeight="0dp" android:fastScrollEnabled="true" android:footerDividersEnabled="false" android:headerDividersEnabled="false" android:requiresFadingEdge="vertical" android:smoothScrollbar="true" />
</LinearLayout>
[activity_row_item.xml content..]
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:id="#+id/Llt" android:layout_width="match_parent" android:layout_height="match_parent" >
<Button android:id="#+id/Btn" android:text="Click me" android:onClick="myFunction" />
</LinearLayout>
Here is how to create the custom Adapter, connecting View.OnClickListener to a ListView with a button per row...
1. Create a layout for a typical row
In this case, the row is composed of three view components:
name (EditText)
value (EditText:inputType="numberDecimal")
delete (Button)
Xml
pay_list_item.xml layout is as follows:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent" >
<EditText
android:id="#+id/pay_name"
android:layout_width="0dp"
android:layout_height="fill_parent"
android:layout_weight="2"
android:hint="Name" />
<EditText
android:id="#+id/pay_value"
android:layout_width="0dp"
android:layout_height="fill_parent"
android:layout_weight="1"
android:inputType="numberDecimal"
android:text="0.0" />
<Button
android:id="#+id/pay_removePay"
android:layout_width="100dp"
android:layout_height="fill_parent"
android:text="Remove Pay"
android:onClick="removePayOnClickHandler" />
</LinearLayout>
Note: the button has onClick handler defined in xml layout file, because we want to refer its action to a specific list item.
Doing this means that the handler will be implemented in Activity file and each button will know which list item it belongs to.
2. Create list item adapter
This is the java class that is the controller for pay_list_item.xml.
It keeps references for all of its views, and it also puts these references in tags, extending the ArrayAdapter interface.
The Adapter:
public class PayListAdapter extends ArrayAdapter<Payment> {
private List<Payment> items;
private int layoutResourceId;
private Context context;
public PayListAdapter(Context context, int layoutResourceId, List<Payment> items) {
super(context, layoutResourceId, items);
this.layoutResourceId = layoutResourceId;
this.context = context;
this.items = items;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View row = convertView;
PaymentHolder holder = null;
LayoutInflater inflater = ((Activity) context).getLayoutInflater();
row = inflater.inflate(layoutResourceId, parent, false);
holder = new PaymentHolder();
holder.Payment = items.get(position);
holder.removePaymentButton = (ImageButton)row.findViewById(R.id.pay_removePay);
holder.removePaymentButton.setTag(holder.Payment);
holder.name = (TextView)row.findViewById(R.id.pay_name);
holder.value = (TextView)row.findViewById(R.id.pay_value);
row.setTag(holder);
setupItem(holder);
return row;
}
private void setupItem(PaymentHolder holder) {
holder.name.setText(holder.Payment.getName());
holder.value.setText(String.valueOf(holder.Payment.getValue()));
}
public static class PaymentHolder {
Payment Payment;
TextView name;
TextView value;
ImageButton removePaymentButton;
}
}
Here we list the Payment class items.
There are three most important elements here:
PayListAdapter constructor: sets some private fields and calls superclass constructor. It also gets the List of Payment objects. Its implementation is obligatory.
PaymentHolder: static class that holds references to all views that I have to set in this list item. I also keep the Payment object that references to this particular item in list. I set it as tag for ImageButton, that will help me to find the Payment item on list, that user wanted to remove
Overriden getView method: called by superclass. Its goal is to return the single List row. We create its fields and setup their values and store them in static holder. Holder then is put in row’s tag element. Note that there is a performance issue, as the row is being recreated each time it is displayed. I used to add some flag in holder like isCreated, and set it to true after row was already created. then you can add if statement and read tag’s holder instead of creating it from scratch.
Payment.java is quite simple as for now and it looks a bit like BasicNameValuePair:
public class Payment implements Serializable {
private String name = "";
private double value = 0;
public Payment(String name, double value) {
this.setName(name);
this.setValue(value);
}
...
}
There are additional gets and sets for each private field not shown.
3. Add ListView to the activity layout xml file
In its simpliest form, it will be enough to add this view to activity layout:
<ListView
android:id="#+id/EnterPays_PaysList"
android:layout_width="fill_parent"
android:layout_height="wrap_content">
</ListView>
4. Set up adapter to this list view in Activity Java code
In order to display items in ListView you need to set up its adapter and map it to some other ArrayList of Payment objects (as I am extending an Array adapter here). Here is code that is responsible for binding adapter to editPersonData.getPayments() ArrayList:
PayListAdapter adapter = new PayListAdapter(AddNewPerson.this, R.layout.pay_list_item, editPersonData.getPayments());
ListView PaysListView = (ListView)findViewById(R.id.EnterPays_PaysList);
PaysListView.setAdapter(adapter);
5. Adding / removing items to ListView (and its adapter)
Adapter is handled just like any other ArrayList, so adding new element to it is as simple as:
Payment testPayment = new Payment("Test", 13);
adapter.add(testPayment);
adapter.remove(testPayment);
6. Handle Remove Payment button click event
In an activity’s code, where ListView is displayed, add public method that will handle remove button click action. The method name has to be exactly the same as it was in pay_list_item.xml:
android:onClick="removePayOnClickHandler"
The method body is as follows:
public void removePayOnClickHandler(View v) {
Payment itemToRemove = (Payment)v.getTag();
adapter.remove(itemToRemove);
}
The Payment object was stored in ImageButton’s Tag element. Now it is enough to read it from Tag, and remove this item from the adapter.
7. Incorporate remove confirmation dialog window
Probably you need also make sure that user intentionally pressed the remove button by asking him additional question in confirmation dialog.
Dialogue
a) Create dialog’s id constant
This is simply dialog’s ID. it should be unique among any other dialog window that is handled by current activity. I set it like that:
protected static final int DIALOG_REMOVE_CALC = 1;
protected static final int DIALOG_REMOVE_PERSON = 2;
b) Build dialog
I use this method to build dialog window:
private Dialog createDialogRemoveConfirm(final int dialogRemove) {
return new AlertDialog.Builder(getApplicationContext())
.setIcon(R.drawable.trashbin_icon)
.setTitle(R.string.calculation_dialog_remove_text)
.setPositiveButton(R.string.calculation_dialog_button_ok, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
handleRemoveConfirm(dialogRemove);
}
})
.setNegativeButton(R.string.calculation_dialog_button_cancel, null)
.create();
}
AlertDialog builder pattern is utilized here. I do not handle NegativeButton click action – by default the dialog is just being hidden. If dialog’s confirm button is clicked, my handleRemoveConfirm callback is called and action is performed based on dialog’s ID:
protected void handleRemoveConfirm(int dialogType) {
if(dialogType == DIALOG_REMOVE_PERSON){
calc.removePerson();
}else if(dialogType == DIALOG_REMOVE_CALC){
removeCalc();
}
}
c) Show Dialog
I show dialog after my remove button click. The showDialog(int) is Android’s Activity’s method:
OnClickListener removeCalcButtonClickListener = new OnClickListener() {
public void onClick(View v) {
showDialog(DIALOG_REMOVE_CALC);
}
};
the showDialog(int) method calls onCreateDialog (also defined in Activity’s class). Override it and tell your app what to do if the showDialog was requested:
#Override
protected Dialog onCreateDialog(int id) {
switch (id) {
case DIALOG_REMOVE_CALC:
return createDialogRemoveConfirm(DIALOG_REMOVE_CALC);
case DIALOG_REMOVE_PERSON:
return createDialogRemoveConfirm(DIALOG_REMOVE_PERSON);
}
}
Take a look at this blog post I wrote on exactly this matter:
Create custom ArrayAdapter
There are comments that explain every action I make in the adapter.
Here is the explanation in short:
So lets for example take a row where you want to place a CheckBox, ImageView
and a TextView while all of them are clickable. Meaning that you can click the
row it self for going to another Actvity for more details on the row, check its
CheckBox or press the ImageView to perform another operation.
So what you should do is:
1. First create an XML layout file for your ListView row:
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal" >
<CheckBox
android:id="#+id/cbCheckListItem"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp" />
<TextView
android:id="#+id/tvItemTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="item string" />
<ImageView
android:id="#+id/iStatus"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:clickable="true"
android:contentDescription="#drawable/ic_launcher"
android:src="#drawable/ic_launcher" />
</LinearLayout>
2. Second in your java code define a ViewHolder, a ViewHolder
is designed to hold the row views and that way operating more quickly:
static class ViewHolder
{
TextView title;
CheckBox checked;
ImageView changeRowStatus;
}
3. Now we have to define CustomArrayAdapter, using the array adapter
we can define precisely what is the desired output for each row based on the content of this
row or it’s position. We can do so by overriding the getView method:
private class CustomArrayAdapter extends ArrayAdapter<RowData>
{
private ArrayList<RowData> list;
//this custom adapter receives an ArrayList of RowData objects.
//RowData is my class that represents the data for a single row and could be anything.
public CustomArrayAdapter(Context context, int textViewResourceId, ArrayList<RowData> rowDataList)
{
//populate the local list with data.
super(context, textViewResourceId, rowDataList);
this.list = new ArrayList<RowData>();
this.list.addAll(rowDataList);
}
public View getView(final int position, View convertView, ViewGroup parent)
{
//creating the ViewHolder we defined earlier.
ViewHolder holder = new ViewHolder();)
//creating LayoutInflator for inflating the row layout.
LayoutInflater inflator = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);
//inflating the row layout we defined earlier.
convertView = inflator.inflate(R.layout.row_item_layout, null);
//setting the views into the ViewHolder.
holder.title = (TextView) convertView.findViewById(R.id.tvItemTitle);
holder.changeRowStatus = (ImageView) convertView.findViewById(R.id.iStatus);
holder.changeRowStatus.setTag(position);
//define an onClickListener for the ImageView.
holder.changeRowStatus.setOnClickListener(new OnClickListener()
{
#Override
public void onClick(View v)
{
Toast.makeText(activity, "Image from row " + position + " was pressed", Toast.LENGTH_LONG).show();
}
});
holder.checked = (CheckBox) convertView.findViewById(R.id.cbCheckListItem);
holder.checked.setTag(position);
//define an onClickListener for the CheckBox.
holder.checked.setOnClickListener(new OnClickListener()
{
#Override
public void onClick(View v)
{
//assign check-box state to the corresponding object in list.
CheckBox checkbox = (CheckBox) v;
rowDataList.get(position).setChecked(checkbox.isChecked());
Toast.makeText(activity, "CheckBox from row " + position + " was checked", Toast.LENGTH_LONG).show();
}
});
//setting data into the the ViewHolder.
holder.title.setText(RowData.getName());
holder.checked.setChecked(RowData.isChecked());
//return the row view.
return convertView;
}
}
4. Now you need to set this adapter, as the adapter of your ListView.
this ListView can be created in java or using an XML file, in this case I’m using a list that was
defined in the XML file using the “list” id:
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_layout);
ListView list = (ListView)findViewById(R.id.list);
CustomArrayAdapter dataAdapter = new CustomArrayAdapter(this, R.id.tvItemTitle, rowDataList);
list.setAdapter(dataAdapter);
}
5. Finally if we want to be able to press the row it self and not only a certain view in it
we should assign an onItemClickListener to the ListView:
list.setOnItemClickListener(new OnItemClickListener()
{
public void onItemClick(AdapterView<?> parent, View view,int position, long id)
{
Toast.makeText(activity, "row " + position + " was pressed", Toast.LENGTH_LONG).show();
}
});
First, the way of adding listeners in xml using onClick="function" is deprecated. You need a ViewHolder class to link the button in the xml to your java code. Then you can implement onClickListener for that.
Inside your getView() implementation of CustomAdapter, you can try like below.
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;
if (convertView == null) {
convertView = inflater.inflate(R.layout.xxxxx, null);
holder = new ViewHolder();
holder.invite = (Button) convertView.findViewById(R.id.button);
} else {
holder = (ViewHolder) convertView.getTag();
}
final int pos = position;
holder.button.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
handleClick(pos);
}
});
}
class ViewHolder {
Button button;
}
So I have a custom ListView object. The list items have two textviews stacked on top of each other, plus a horizontal progress bar that I want to remain hidden until I actually do something. To the far right is a checkbox that I only want to display when the user needs to download updates to their database(s). When I disable the checkbox by setting the visibility to Visibility.GONE, I am able to click on the list items. When the checkbox is visible, I am unable to click on anything in the list except the checkboxes. I've done some searching but haven't found anything relevant to my current situation. I found this question but I'm using an overridden ArrayAdapter since I'm using ArrayLists to contain the list of databases internally. Do I just need to get the LinearLayout view and add an onClickListener like Tom did? I'm not sure.
Here's the listview row layout XML:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="?android:attr/listPreferredItemHeight"
android:padding="6dip">
<LinearLayout
android:orientation="vertical"
android:layout_width="0dip"
android:layout_weight="1"
android:layout_height="fill_parent">
<TextView
android:id="#+id/UpdateNameText"
android:layout_width="wrap_content"
android:layout_height="0dip"
android:layout_weight="1"
android:textSize="18sp"
android:gravity="center_vertical"
/>
<TextView
android:layout_width="fill_parent"
android:layout_height="0dip"
android:layout_weight="1"
android:id="#+id/UpdateStatusText"
android:singleLine="true"
android:ellipsize="marquee"
/>
<ProgressBar android:id="#+id/UpdateProgress"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:indeterminateOnly="false"
android:progressDrawable="#android:drawable/progress_horizontal"
android:indeterminateDrawable="#android:drawable/progress_indeterminate_horizontal"
android:minHeight="10dip"
android:maxHeight="10dip"
/>
</LinearLayout>
<CheckBox android:text=""
android:id="#+id/UpdateCheckBox"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/>
</LinearLayout>
And here's the class that extends the ListActivity. Obviously it's still in development so forgive the things that are missing or might be left laying around:
public class UpdateActivity extends ListActivity {
AccountManager lookupDb;
boolean allSelected;
UpdateListAdapter list;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
lookupDb = new AccountManager(this);
lookupDb.loadUpdates();
setContentView(R.layout.update);
allSelected = false;
list = new UpdateListAdapter(this, R.layout.update_row, lookupDb.getUpdateItems());
setListAdapter(list);
Button btnEnterRegCode = (Button)findViewById(R.id.btnUpdateRegister);
btnEnterRegCode.setVisibility(View.GONE);
Button btnSelectAll = (Button)findViewById(R.id.btnSelectAll);
btnSelectAll.setOnClickListener(new Button.OnClickListener() {
#Override
public void onClick(View v) {
allSelected = !allSelected;
for(int i=0; i < lookupDb.getUpdateItems().size(); i++) {
lookupDb.getUpdateItem(i).setSelected(!lookupDb.getUpdateItem(i).isSelected());
}
list.notifyDataSetChanged();
// loop through each UpdateItem and set the selected attribute to the inverse
} // end onClick
}); // end setOnClickListener
Button btnUpdate = (Button)findViewById(R.id.btnUpdate);
btnUpdate.setOnClickListener(new Button.OnClickListener() {
#Override
public void onClick(View v) {
} // end onClick
}); // end setOnClickListener
lookupDb.close();
} // end onCreate
#Override
protected void onDestroy() {
super.onDestroy();
for (UpdateItem item : lookupDb.getUpdateItems()) {
item.getDatabase().close();
}
}
#Override
protected void onListItemClick(ListView l, View v, int position, long id) {
super.onListItemClick(l, v, position, id);
UpdateItem item = lookupDb.getUpdateItem(position);
if (item != null) {
item.setSelected(!item.isSelected());
list.notifyDataSetChanged();
}
}
private class UpdateListAdapter extends ArrayAdapter<UpdateItem> {
private List<UpdateItem> items;
public UpdateListAdapter(Context context, int textViewResourceId, List<UpdateItem> items) {
super(context, textViewResourceId, items);
this.items = items;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View row = null;
if (convertView == null) {
LayoutInflater li = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);
row = li.inflate(R.layout.update_row, null);
} else {
row = convertView;
}
UpdateItem item = items.get(position);
if (item != null) {
TextView upper = (TextView)row.findViewById(R.id.UpdateNameText);
TextView lower = (TextView)row.findViewById(R.id.UpdateStatusText);
CheckBox cb = (CheckBox)row.findViewById(R.id.UpdateCheckBox);
upper.setText(item.getName());
lower.setText(item.getStatusText());
if (item.getStatusCode() == UpdateItem.UP_TO_DATE) {
cb.setVisibility(View.GONE);
} else {
cb.setVisibility(View.VISIBLE);
cb.setChecked(item.isSelected());
}
ProgressBar pb = (ProgressBar)row.findViewById(R.id.UpdateProgress);
pb.setVisibility(View.GONE);
}
return row;
}
} // end inner class UpdateListAdapter
}
edit: I'm still having this problem. I'm cheating and adding onClick handlers to the textviews but it seems extremely stupid that my onListItemClick() function is not being called at all when I am not clicking on my checkbox.
The issue is that Android doesn't allow you to select list items that have elements on them that are focusable. I modified the checkbox on the list item to have an attribute like so:
android:focusable="false"
Now my list items that contain checkboxes (works for buttons too) are "selectable" in the traditional sense (they light up, you can click anywhere in the list item and the "onListItemClick" handler will fire, etc).
EDIT: As an update, a commenter mentioned "Just a note, after changing the visibility of the button I had to programmatically disable the focus again."
In case you have ImageButton inside the list item you should set the descendantFocusability value to 'blocksDescendants' in the root list item element.
android:descendantFocusability="blocksDescendants"
And the focusableInTouchMode flag to true in the ImageButton view.
android:focusableInTouchMode="true"
I've had a similar issue occur and found that the CheckBox is rather finicky in a ListView. What happens is it imposes it's will on the entire ListItem, and sort of overrides the onListItemClick. You may want to implement a click handler for that, and set the text property for the CheckBox as well, instead of using the TextViews.
I'd say look into this View object as well, it may work better than the CheckBox
Checked Text View
use this line in the root view of the list item
android:descendantFocusability="blocksDescendants"