I am a beginner level android developer and was working on creating a basic To-Do list app. The app contains custom listview with a coloured circle to show priority, the title and a checkbox to check and delete the item when the user has done it. The app stores these to-dos using SQLite database and uses Loader to retrieve data.
What I'm trying is to delete the item from the list as soon as the checkbox is checked.
I tried to implement deletion task in the Adapter class like this (Also tried to animate deletion.):
CheckBox itemCheck = (CheckBox)view.findViewById(R.id.todo_checked);
itemCheck.setChecked(false);
itemCheck.setTag(priority);
itemCheck.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
#Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
if(isChecked){
int idIndex = cursor.getColumnIndex(TodoTable.ID);
final int id = cursor.getInt(idIndex);
final Uri uriTodDelete = ContentUris.withAppendedId(TodoTable.TABLE_URI,id);
Animation slideOut = AnimationUtils.loadAnimation(context,android.R.anim.slide_out_right);
slideOut.setAnimationListener(new Animation.AnimationListener() {
#Override
public void onAnimationStart(Animation animation) {
}
#Override
public void onAnimationEnd(Animation animation) {
context.getContentResolver().delete(uriTodDelete,null,null);
}
#Override
public void onAnimationRepeat(Animation animation) {
}
});
view.startAnimation(slideOut);
The problem is, whenever I delete the top item of the listview, the item just below it gets deleted. But it works fine if I delete from the bottom.
How do I fix this? Any help will be appreciated.
P.S the delete method of the Content Resolver class is handling notifyDataSetChanged() method.
A wild guess, you are not moving the cursor to the relevant row
so cursor is on pos x but you may click -on listview- on item at pos y.
so you need to move cursor to the relevant position before you try to get the ID value.
i assume you are doing this code in getView() ? as you are usign an adapter.
You need to make use of position parameter passed in getView() as below:
#Override
public View getView(int position, View convertView, ViewGroup parent) {
:
:
if(isChecked){
cursor.moveToPosition(position);
int idIndex = cursor.getColumnIndex(TodoTable.ID);
final int id = cursor.getInt(idIndex);
:
:
}
:
:
}
you also need to make sure the cursor is being re query otherwise you may get some problems with positions once you remove items from list
-- i think this is auto handled if you are using a cursor adapter?
just double check on that.
Related
I have a list view with a custom adapter. Each item has a delete icon which prompts a delete dialog fragment. on deleting the item, I am performing a slide animation and on animation end. the item is deleted from the list and adapter is notified about the deletion like below:
// dialog fragment on clicking "delete"
positive.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
final FoldingCell fView = (FoldingCell) thisItem.getParent().getParent();
// wait for fold to finish then delete item
fView.postDelayed(new Runnable() {
#Override
public void run() {
deleteCell(fView, pos);
}
}, 850);
my adapter is of type FoldingCell so I am folding back the cell before deletion, hence the postDelayed. The deleteCell is the simple animation below:
private void deleteCell(final View v, final int index) {
TranslateAnimation transanim = new TranslateAnimation(0, 800, 0, 0);
transanim.setDuration(700);
transanim.setAnimationListener(new Animation.AnimationListener() {
#Override
public void onAnimationStart(Animation animation) {
}
#Override
public void onAnimationEnd(Animation animation) {
items.remove(index);
// update array adapter
adapter.notifyDataSetChanged();
v.setVisibility(View.GONE);
}
#Override
public void onAnimationRepeat(Animation animation) {
}
});
v.startAnimation(transanim);
}
An important not is that I also am using stableIds which might be causing the problem. After the animation deletion occurs, the adapter is deleting two items instead of just the one clicked on. when I disable the animation in the above code, and just write
items.remove(index);
adapter.notifyDataSetChanged();
exactly on delete, the deletion works perfectly even with stable ids (there is just no animation) why is that? The problem seems like its a combination of the animation and stable ids, since if stableids is false, the deletion works with the animation.
I cannot find a good solution for this other than making stable ids false and solve other issues that arise from doing that.
Solved! The reason why with stableids the adapter was deleting an extra fields was that when I override getItemId, I was returning the adapter position itself which, I guess, during the animation when an item is deleted, that same position id is taken by another list item which gets deleted as well. By returning a different itemId that is unique to the list item, this error does not occur. so I changed this:
#Override
public long getItemId(int position) {
return position;
}
to this:
#Override
public long getItemId(int position) {
Item item = items(position);
return item.getId();
}
where items is the arraylist I am passing to the adapter. Item is my class that holds the elements/views of each item and getId() is the getter I have to return the id integer of each Item
I have added Spinner inside RecyclerView , when i am trying to get spinner selected item data, its getting another/wrong position data, any one suggest me to get correct selected item and position from Spinner onItemSelected
Here is my code
#Override
public void onBindViewHolder(final QuestionHolder holder, final int position) {
if (position % 2 == 1)
holder.itemView.setBackgroundColor(Color.parseColor("#F8F8F8"));
adapter = new ArrayAdapter<Option>(binding.getRoot().getContext(),
R.layout.item_spinner, questionList.get(position).getOptions());
adapter.setDropDownViewResource(R.layout.item_spinner);
binding.optionSpinner.setAdapter(adapter);
binding.serialNo.setText((position + 1) + ".");
binding.setQuestion(questionList.get(position));
binding.executePendingBindings();
binding.optionSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
#Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
Toast.makeText(holder.itemView.getContext(), position+" : "+binding.optionSpinner.getSelectedItem().toString(), Toast.LENGTH_SHORT).show();
spinnerData.setSelectedData(position, binding.optionSpinner.getSelectedItem().toString());
}
#Override
public void onNothingSelected(AdapterView<?> parent) {
}
});
}
I think you have to try this
showSpinnerList.setOnItemSelectedListener(new
AdapterView.OnItemSelectedListener() {
#Override
public void onItemSelected(AdapterView<?> parent, View view, int
position, long id) {
// On selecting a spinner item
String item = parent.getItemAtPosition(position).toString();
}
#Override
public void onNothingSelected(AdapterView<?> parent) {
// todo for nothing selected
}
});
Check this, can be helpful and may fix your problem. If this won't fix your problem at least you get rid of Lint error. Lint error “Do not treat position as fixed; only use immediately…”. So everywhere you are using final int position** change it to getAdapterPosition();
The documentation of RecyclerView.Adapter.onBindViewHolder()
states:
Note that unlike ListView, RecyclerView will not call this method
again if the position of the item changes in the data set unless the
item itself is invalidated or the new position cannot be determined.
For this reason, you should only use the position parameter while
acquiring the related data item inside this method and should not keep
a copy of it. If you need the position of an item later on (e.g. in a
click listener), use getAdapterPosition() which will have the updated
adapter position
So, technically items may be re-arranged and binding will not be
necessary because items are not invalidated yet. The position
variable received holds true only for the scope of bind function and
will not always point to correct position in the data set. That's why
the function getAdapterPosition() must be called everytime updated
position is needed.
IMHO, mLastPosition = holder.getAdapterPosition(); is still
potentially wrong. Because item may be re-arranged and mLastPosition
still points to old position.
About why lint is silent, perhaps Lint rule is not that thorough. Its
only checking whether position parameter is being copied or not.
I am working on an application where I need a spinner if the user chooses some values from the spinner then I need to smooth scroll for that particular position and highlight the item of that position . I have done something for that I have a method that will highlight the position if the view is visible if the view is not visible it will not highlight. So, I am facing some issues like this.
This is the place where I get the position from the spinner
versesSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
#Override
public void onItemSelected(AdapterView<?> adapterView, View view, int i, long l) {
final int possition = (int) versesSpinner.getSelectedItemId();
recyclerView.smoothScrollToPosition(possition);
setBackgroundColor(possition);
}
#Override
public void onNothingSelected(AdapterView<?> adapterView) {
}
});
And this is the method where I highlight the item
public void setBackgroundColor(int position) {
for (int i = 0; i < versesList.size(); i++) {
View view = mLayoutManager.findViewByPosition(i);
try {
if (view != null) {
if (i == position)
view.setBackgroundColor(Color.parseColor("#504cbee7"));
else
view.setBackgroundColor(Color.WHITE);
}
} catch (Exception e) {}
}
}
Here the problem is while calling the setBackground() method before the smooth scroll method reaches that particular item.
The highlighting will be done only if the view is visible and if the view is not null.
Please tell me if there is any other way is there to achieve this else please tell me how can I find out if the smooth scroll to position has reached the particular item
I hope it make sense.
You are trying to change the background of item before it has completed the scrolling. Use a handler to schedule that task after some time.
recyclerView.postDelayed(new Runnable(){
public void run(){
setBackgroundColor(possition);
}
},1000);
You might consider having a loom at my answer here to check how to highlight the items in your RecyclerView on demand. Now if you want a smooth scroll to the position highlighted just add smoothScrollToPosition(position) right after calling the notifyDataSetChanged() function.
So the highLightTheListItems() function might look like this which I'm referring from my answer there.
public void highLightTheListItems() {
// Modify your list items.
// Call notifyDataSetChanged in your adapter
yourAdapter.notifyDataSetChanged();
// Now smooth scroll to the position you want.
}
Hope that helps.
I have a ExpandableListView in my activity that works properly. I set in my adapter a clickListener on RelativeLayout, so when I click on it, my group get expanded. My group list is a ArrayList, my children is a HashMap> where Integer is groupPosition of ArrayList. Children is a comment list that any user can see. The user can add a comment by EditText at end of comment list. My goal is to position at top of screen the groupView clicked. I tried to do this but my problem is:
My first group has 10 comments. If i expand the first group I can see them and the position of my group clicked is right. If I try to expand my fourth group, when the first is already expanded, the ExpandableListView position on my screen goes to the fourth children of first group and not on fourth group just expanded. After if I scroll down the list the fourth group is open, but I would to position on the fourth group and not on fourth children of first group. All works properly if I open only group without comments...
small part of method getGroupView:
scanCommFav=(RelativeLayout)v.findViewById(R.id.feedcell_scancommentfavorite);
scanCommFav.setOnClickListener(new OnClickListener(){
#Override
public void onClick(View v)
{
if(!isExp)
{
if(mess==0)
{
setIdx(idx);
addIndex(idx);
getCommentNoList();
}
else
{
callCommentScript(snapId);
setIdx(idx);
addIndex(idx);
}
}
else if(isExp)
{
setIdx(idx);
removeIndex(idx);
closeGroup(idx);
}
}
});
addIndex(idx) and removeIndex, add and remove in LinkedList the position of groups open. setIdx(idx) set to int variable the group position that I use for different purpose.
this is openGroup():
private void openGroup(int pos)
{
feedlistView.setSelectedGroup(pos);
}
this is closeGroup():
private void closeGroup(int idx)
{
feedlistView.collapseGroup(idx);
}
this is my listener:
feedlistView.setOnItemSelectedListener(new OnItemSelectedListener(){
#Override
public void onItemSelected(AdapterView<?> arg0, View view,int position, long id) {
if(ExpandableListView.getPackedPositionForGroup(position)==ExpandableListView.PACKED_POSITION_TYPE_GROUP)
{
int pos = feedlistView.getSelectedItemPosition();
feedlistView.expandGroup(pos);
}
}
#Override
public void onNothingSelected(AdapterView<?> arg0) {
// TODO Auto-generated method stub
}
});
I tried also to implement setOnGroupClickListener but doesn't works. Somebody has any idea?
Thanks
I solved saving Instance State and restoring Istance State every time I set my customer adapter to my Expandable ListView in this way:
Parcelable state = myExpList.onSaveIstanceState();
myExpList.setAdapter(myAdapter);
myExpList.onRestoreIstanceState();
after in my method openGroup(int position)
myExpList.expandGroup(position);
myExpList.setSelectedGroup(position);
I didn't use setOnItemSelectedListener.
I have a listview that contains a checkedtextview. My app moves from the top of the list view to the bottom. I want to check if the item is checked before calling an action. If it is not checked I want to move to the next item in the list.
E.g.
Item 1 - Checked
item 2 - Checked
Item 3 - Not Checked
Item 4 - Checked
So, I want the app to process as follows:
Item 1
Item 2
Item 4.
I am not sure how to access the checked status of the item from the listview position.
The logic that I want is as follows:
Is Current Item checked?
Yes:
Call action
No:
Move to next item.
Reloop to top of void.
I will need something in there to stop an infinite loop.
1.) First create an array, what indicates the items checked state in your adapter
(assuming you extend the BaseAdapter class for this purpose):
private boolean [] itemsChecked = new boolean [getCount()];
2.) Then create an OnCheckedChangeListener:
private OnCheckedChangeListener listener = new OnCheckedChangeListener()
{
#Override
public void onCheckedChanged(CompoundButton button, boolean checked)
{
Integer index = (Integer)button.getTag();
itemsChecked[index] = checked;
}
}
3.) In your adapters getView() method:
public View getView(int index, View view, ViewGroup parent)
{
/*...*/
CheckBox checkBox = /*get the checkbox*/;
checkbox.setTag(index);
checkBox.setOnCheckedChangeListener(listener);
/*...*/
}
4.) In the onClick() method:
public void onClick(View view)
{
//just get the boolean array somehow
boolean [] itemsChecked = adapter.getItemsCheckedArray();
for(int i=0; i<itemsChecked.length; i++)
{
if(itemsChecked[i])
{
//the i th item was checked
}
else
{
//it isnt checked
}
}
}
One solution will be to use an ArrayList of positions.
When the user check/uncheck a checkbox, add/remove accordingly the position in your ArrayList.
Then when the user is over, just iterate through the list to know which position have been selected.