I have a big problem deleting a group from an ExpandableListView. Even after google a lot and trying a lot of tutorials and examples I was not able to solve it.
Though I have a lot of programming experiences I am relative knew to Android programming. So I am sure there are many things in the source which are not yet well done. But as of now I wanted to focus on the problem with a wrong view after deleting a group from the list.
To give a good overview to the problem here are some screenshots
Start of the App
List after click to the button List All Budgets
All Groups Expandet
Before delete the last child of the last group
Remaining group show children twice
Last group this time with two children
Before deleting the last children of the last group
Correct result after deleting last child of last group
I hope the problem becomes clear. If the last group has only one child and this was deleted the whole group will be deleted by the app - but than the children of the first group show up twice.
During a debugging session I checked all the ressources behind the data and they are all ok. If I go back to MainActivity and start the list again the view ist totally correct. So it must be a problem of an incorrect population after deleting a whole group.
As you can see if I only delete the last child from a last group with two childs the populating of the whole list is correct.
Here are some more information about the app:
I use a room database with two tables holding the data.
One tabel contains the categories with name and id and the other tabel is for single budget records with the category id as an foreign key
In onCreate of the BudgetListActivity I created two DAO's budgetDAO and categoryDAO to get the data and fill the lists allBudgetsList and all CatList.
With this informations I create a new array List allGroups with the structure I need for the view
- Categories as header
- budgets as children due to the foreign key
(just one remark here:
meanwhile I tried already using a hashmap for the data given to the ExpandableListAdapter - but the result was the same wrong view population!)
There is a contentView "budget_expandable_list" which is set to the ExpandableListAdapter. The adapter should populate the groups and childs for this list using the data from the ArrayList "allGroups"
This is the structure of the app
It could be that there are some ressources which are not used actually.
I will give now the soure code for the importand classes
BudgetListActivity:
package com.wbapps.WBEasyBudgetManagement;
import android.content.Intent;
import android.os.Bundle;
import android.support.design.widget.CoordinatorLayout;
import android.support.v7.app.AppCompatActivity;
import android.view.ContextMenu;
import android.view.MenuItem;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.ExpandableListView;
import android.widget.Toast;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class BudgetListActivity extends AppCompatActivity implements AdapterView.OnItemClickListener {
CoordinatorLayout coordinatorLayout;
private SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yyyy");
//wb, 23Oct2018: now using an array list for the expandable list adapter
ArrayList<Group> allGroups = new ArrayList();
private ArrayAdapter adapter;
private final int REQUEST_CODE_EDIT = 1;
private BudgetDAO budgetDAO;
private CategoryDAO categoryDAO;
List<Budget> allBudgetsList;
List<Category> allCatsList;
ExpandableListView expListView;
List<String> expListViewTitle;
ExpandableListAdapter expAdapter;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.budget_expandable_list);
if (allGroups.size() > 0 ) {allGroups.clear();}
//get instances for DAO's of db from MainActivity
budgetDAO = MainActivity.getBudgetDAO();
categoryDAO = MainActivity.getCategoryDAO();
//the list for budgets and categories
allBudgetsList = budgetDAO.getBudgets();
allCatsList = categoryDAO.getCategories();
//temporary Group-Object for the ArrayList allGroups
Group tmpGroup;
double sumExpenses = 0;
//Start with reading all categories
for (int i=0;i<allCatsList.size(); i++) {
String tmpCat = allCatsList.get(i).getCategory();
tmpGroup = new Group(tmpCat);
sumExpenses = 0.0;
//now read all budgets for the current category and fill the rest of the temporary Group-Object
for (int j=0;j<allBudgetsList.size();j++){
if (allBudgetsList.get(j).getCategoryId() == allCatsList.get(i).getId()){
//tmpGroup.budgetId = allBudgetsList.get(j).getId();
tmpGroup.catId = allBudgetsList.get(j).getCategoryId();
tmpGroup.children.add(Arrays.asList
(
" Date: " + sdf.format(allBudgetsList.get(j).getDateTime())
+ " - Expenses: " + Double.toString(allBudgetsList.get(j).getExpenses()),
Long.toString(allBudgetsList.get(j).getId())
)
);
sumExpenses = sumExpenses + allBudgetsList.get(j).getExpenses();
tmpGroup.sumExpenses = sumExpenses;
}
}
//if at least one children for the current category was found
// =>> write all the group information the the array list
if (tmpGroup.children.size() > 0 ) {allGroups.add(tmpGroup);}
}
expListView = (ExpandableListView) findViewById(R.id.expandableList);
expAdapter = new ExpandableListAdapter(this, allGroups);
expListView.setAdapter(expAdapter);
expListView.setOnItemClickListener(this);
registerForContextMenu(expListView);
}
#Override
public void onCreateContextMenu(ContextMenu contMenu, View v,
ContextMenu.ContextMenuInfo contextMenuInfo) {
super.onCreateContextMenu(contMenu, v, contextMenuInfo);
ExpandableListView.ExpandableListContextMenuInfo info = (ExpandableListView.ExpandableListContextMenuInfo) contextMenuInfo;
int type = ExpandableListView.getPackedPositionType(info.packedPosition);
int groupPosition = ExpandableListView.getPackedPositionGroup(info.packedPosition);
int childPosition = ExpandableListView.getPackedPositionChild(info.packedPosition);
// Show context menu for groups
if (type == ExpandableListView.PACKED_POSITION_TYPE_GROUP) {
contMenu.setHeaderTitle("Budget");
contMenu.add(R.string.context_editBudget);
contMenu.add(R.string.context_delBudget);
// Show context menu for children
} else if (type == ExpandableListView.PACKED_POSITION_TYPE_CHILD) {
contMenu.setHeaderTitle("Child");
contMenu.add(R.string.context_editChild);
contMenu.add(R.string.context_delChild);
}
}
#Override
public boolean onContextItemSelected(MenuItem item) {
Integer tmpInt = item.getItemId();
ExpandableListView.ExpandableListContextMenuInfo info = (ExpandableListView.ExpandableListContextMenuInfo) item
.getMenuInfo();
int type = ExpandableListView.getPackedPositionType(info.packedPosition);
int groupPosition = ExpandableListView.getPackedPositionGroup(info.packedPosition);
int childPosition = ExpandableListView.getPackedPositionChild(info.packedPosition);
//TextView vItem = info.targetView.findViewById(R.id.context_editBudget);
if (type == ExpandableListView.PACKED_POSITION_TYPE_GROUP) {
//Toast.makeText(this, "Click auf Group: " + Integer.toString(item.getGroupId()), Toast.LENGTH_SHORT).show();
if (item.getTitle().toString().equals(getString(R.string.context_editBudget))){
Toast.makeText(this, "Edit Budget clicked in Budget Context Menu", Toast.LENGTH_SHORT).show();
}
if (item.getTitle().toString().equals(getString(R.string.context_delBudget))){
int size = allGroups.get(groupPosition).children.size();
for (int i = 0; i<size; i++) {
budgetDAO.delAllBudgetsForCategory(allGroups.get(groupPosition).catId);
}
allGroups.remove(groupPosition);
//expAdapter.notifyDataSetChanged();
if (allGroups.size() == 0){
Intent intent = new Intent(BudgetListActivity.this, MainActivity.class);
startActivity(intent);
}
}
}
if (type == ExpandableListView.PACKED_POSITION_TYPE_CHILD) {
if (item.getTitle().toString().equals(getString(R.string.context_editChild))){
Toast.makeText(this, "Edit Child clicked in Child Context Menu", Toast.LENGTH_SHORT).show();
}
if (item.getTitle().toString().equals(getString(R.string.context_delChild))){
//wb, 27Oct2018: Delete the selected child for a budget with given category
budgetDAO.delBudgetChildForCategory(Integer.parseInt(allGroups.get(groupPosition).children.get(childPosition).get(1)));
allGroups.get(groupPosition).children.remove(childPosition);
//expAdapter.notifyDataSetChanged();
//wb, 28Oct2018: If no more budget rows available delete the whole budget for category
if (allGroups.get(groupPosition).children.size() == 0) {
allGroups.remove(groupPosition);
//expAdapter.notifyDataSetChanged();
//expAdapter.notifyDataSetChanged();
if (allGroups.size() ==0){
Intent intent = new Intent(BudgetListActivity.this, MainActivity.class);
startActivity(intent);
}
}
/*
else {
//allGroups.get(groupPosition).sumExpenses = 0.0;
//allGroups.get(groupPosition) = expAdapter.getSum(groupPosition)
for (int i = 0; i < allBudgetsList.size(); i++) {
if (allBudgetsList.get(i).getCategoryId() == allGroups.get(groupPosition).catId) {
allGroups.get(groupPosition).sumExpenses =
allGroups.get(groupPosition).sumExpenses + allBudgetsList.get(i).getExpenses();
}
}
}*/
}
}
expAdapter.notifyDataSetChanged();
//return super.onContextItemSelected(item);
return true;
}
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Budget budget = (Budget)adapter.getItem(position);
editEntry(budget, position);
}
private void editEntry(Budget budget, int position) {
Intent intent = new Intent(this, EditBudgetActivity.class);
intent.putExtra("position", position);
startActivityForResult(intent, REQUEST_CODE_EDIT);
}
}
As you can see I use a context menu for editing and deleting groups and/or childs. Some features are not yet fully implemented. Please understand that I will first focus on my main problem with the correct population of the ExpandableView.
Also other things - like the incorrect update of the summery of the expences after deleting a child - are not yet very important and will be done later.
Here the class for a Group Object:
package com.wbapps.WBEasyBudgetManagement;
import java.util.ArrayList;
import java.util.List;
public class Group {
public long budgetId;
public long catId;
public String category;
public final List<List<String>> children = new ArrayList<List<String>>();
public final List<Long> BudIds = new ArrayList<Long>();
public double sumExpenses;
public Group(String pcategory) {
category = pcategory;
}
}
Here is the ExpandableListAdapter source:
package com.wbapps.WBEasyBudgetManagement;
import android.content.Context;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseExpandableListAdapter;
import android.widget.CheckedTextView;
import android.widget.TextView;
import android.widget.Toast;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Currency;
import java.util.Locale;
public class ExpandableListAdapter extends BaseExpandableListAdapter{
Context context;
Locale locale;
Currency curr;
//array list to take the data for the list from the activity
private final ArrayList<Group> allGroups;
public LayoutInflater inflater;
public AppCompatActivity activity;
public int times = 0;
//Constructor for ExpandableListAdapter
//public ExpandableListAdapter(AppCompatActivity act, SparseArray<Group> groups) {
public ExpandableListAdapter(AppCompatActivity act, ArrayList<Group> allGroups) {
this.activity = act;
this.allGroups = allGroups;
inflater = act.getLayoutInflater();
}
#Override
public View getGroupView(int groupPosition, boolean isExpanded,
View convertView, ViewGroup parent) {
times = times + 1;
Log.d("Info getGroupView","In getGroupView " + Integer.toString(times) + " times");
for (Locale wbLocale : Locale.getAvailableLocales()) {
//Log.d("LOCALES", wbLocale.getLanguage() + "_" + wbLocale.getCountry() + " [" + wbLocale.getDisplayName() + "]");
if (wbLocale.getCountry().equals("PH")) {
curr = Currency.getInstance(wbLocale);
curr.getSymbol(wbLocale);
break;
}
}
if (convertView == null || convertView.findViewById(R.id.tvCatGroup)==null){
convertView = inflater.inflate(R.layout.list_row_group, null);
}
convertView = inflater.inflate(R.layout.list_row_group, null);
String tmpCat = allGroups.get(groupPosition).category;
Group tmpGroup = new Group(tmpCat);
sortList();
Group group = (Group) getGroup(groupPosition);
//((CheckedTextView) convertView).setText(group.category + "\nTotal Expenses: " + group.sumExpenses + " " + curr.getSymbol());
((CheckedTextView) convertView).setText(group.category + "\nTotal Expenses: " + getSum(groupPosition) + " " + curr.getSymbol());
((CheckedTextView) convertView).setChecked(isExpanded);
return convertView;
}
/* wb, 18Sep2017: sort the list_selectedShoppingItems list */
public void sortList() {
Collections.sort(allGroups, new Comparator<Group>() {
#Override
public int compare(Group content1, Group content2) {
/* ignore case sensitivity */
return content1.category.compareToIgnoreCase(content2.category);
}
});
}
#Override
public View getChildView(int groupPosition, final int childPosition,
boolean isLastChild, View convertView, ViewGroup parent)
{
if(childPosition < getChildrenCount(groupPosition)-1) {
//holds the detail string for one child
final String children = (String) getChild(groupPosition, childPosition);
if (convertView == null || convertView.findViewById(R.id.tvChildRow)==null)
convertView = inflater.inflate(R.layout.list_row_details, null);
convertView = inflater.inflate(R.layout.list_row_details, null);
TextView txtChildRow = (TextView)convertView.findViewById(R.id.tvChildRow);
txtChildRow.setText(children + " " + curr.getSymbol());
convertView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Toast.makeText(activity, children + " " + curr.getSymbol(),
Toast.LENGTH_SHORT).show();
}
});
}
//children is the last one
if(childPosition == getChildrenCount(groupPosition)-1)
{
if (convertView == null || convertView.findViewById(R.id.tvSum)==null)
convertView = inflater.inflate(R.layout.listview_footer,null);
TextView txtFooter = (TextView)convertView.findViewById(R.id.tvSum);
//txtFooter.setText("Total expenses: " + allGroups.get(groupPosition).sumExpenses + " " + curr.getSymbol() );
txtFooter.setText("Total expenses: " + getSum(groupPosition) + " " + curr.getSymbol() );
//Log.e(TAG, "getChildView - sumExpenses: "+txtFooter.getText().toString());
}
convertView.setLongClickable( true);
return convertView;
}
#Override
public Object getChild(int groupPosition, int childPosition) {
return allGroups.get(groupPosition).children.get(childPosition).get(0);
}
public Object getSum(int groupPosition) {
return allGroups.get(groupPosition).sumExpenses;
}
#Override
public long getChildId(int groupPosition, int childPosition) {
return 0;
}
//Add 1 to childCount. The last row is used as footer to childView
#Override
public int getChildrenCount(int groupPosition) {
return allGroups.get(groupPosition).children.size() +1;
}
#Override
public Object getGroup(int groupPosition) {
return allGroups.get(groupPosition);
}
#Override
public int getGroupCount() {
return allGroups.size();
}
#Override
public void onGroupCollapsed(int groupPosition) {
super.onGroupCollapsed(groupPosition);
}
#Override
public void onGroupExpanded(int groupPosition) {
super.onGroupExpanded(groupPosition);
}
#Override
public long getGroupId(int groupPosition) {
return 0;
}
#Override
public boolean hasStableIds() {
return false;
}
#Override
public boolean isChildSelectable(int groupPosition, int childPosition) {
return true;
}
#Override
public void notifyDataSetChanged() {
super.notifyDataSetChanged();
}
}
Some remarks might be helpful:
- in getChildrenCount I added 1 to the number of size because I use one last children as a footer to show the summary of expenses
for a better understanding here is a picture of the list "allGroups"
I hope I could support you with all the neccessary informations. Please let me know if some is missing. I will add it soon.
Hopefully there is someone out there with a solution for me.
Have a nice day
Andreas
meanwhile I found the reason for that behaviour. There is a method "getGroupID" at the end of the source code of the adapter. The return value here was set to 0 which caused the trouble. It has to be set to the groupPosition and then it works!
#Override
public long getGroupId(int groupPosition) {
/* wb, 10Nov2018: this statement was due to the error of deleting a last child of a group
With "return 0" the children of the remaining group was shown twice !!!
return 0;
*/
return groupPosition;
}
This is hopefully helpful to all who also run into this problem.
Have a nice time
Andreas
Related
For each item of the list ("itemList")
ArrayList<Item> itemList = new ArrayList<>();
shown by "recyclerView" that is controlled by "itemAdapter", its quantity is displayed, as well as a plus and minus button which allows the user to respectively increase and decrease the quantity.
In the set-up of "itemAdapter" these buttons are given functionality by:
itemAdapter.setOnItemClickListener(new ItemAdapter.OnItemClickListener() {
#Override
public void onPlusClick(int position) {
itemList.get(position).setQuantity(itemList.get(position).getQuantity() + 1);
itemAdapter.notifyItemChanged(position);
Log.d(TAG, "onPlusClick: position = " + position + " quantity = " + itemList.get(position).getQuantity());
}
#Override
public void onMinusClick(int position) {
itemList.get(position).setQuantity(itemList.get(position).getQuantity() - 1);
if (itemList.get(position).getQuantity() < 1) {
itemList.get(position).setQuantity(1);
}
itemAdapter.notifyItemChanged(position);
Log.d(TAG, "onMinusClick: position = " + position + " quantity = " + itemList.get(position).getQuantity());
}
})
The display of the quantity is set up in the onBindViewHolder() of the adapter class ItemAdapter with:
holder.textviewQuantity.setText(String.valueOf(currentItem.getQuantity()));
And this is the code for the adapter class
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.cardview.widget.CardView;
import androidx.recyclerview.widget.RecyclerView;
import java.util.ArrayList;
public class ItemAdapter extends RecyclerView.Adapter<ItemAdapter.ItemListHolder> {
private ArrayList<Item> mItemList;
private OnItemClickListener mListener;
public interface OnItemClickListener {
void onPlusClick(int position);
void onMinusClick(int position);
}
public void setOnItemClickListener(OnItemClickListener listener) {
mListener = listener;
}
#NonNull
#Override
public ItemListHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.item, parent, false);
return new ItemListHolder(v, mListener);
}
#Override
public void onBindViewHolder(#NonNull ItemListHolder holder, int position) {
Item currentItem = mItemList.get(position);
holder.imageviewCategoryIcon.setImageResource(Main.getIconID(currentItem.getCategoryNumber()));
holder.textviewItemName.setText(currentItem.getItemName());
holder.textviewQuantity.setText(String.valueOf(currentItem.getQuantity()));
}
}
#Override
public int getItemCount() {
return mItemList.size();
}
public static class ItemListHolder extends RecyclerView.ViewHolder {
CardView cardviewItem;
ImageView imageviewCategoryIcon;
TextView textviewItemName;
Button buttonPlus;
Button buttonMinus;
TextView textviewQuantity;
public ItemListHolder(#NonNull View view, OnItemClickListener listener) {
super(view);
cardviewItem = view.findViewById(R.id.cardview_item);
imageviewCategoryIcon = view.findViewById(R.id.imageview_category_icon);
textviewItemName = view.findViewById(R.id.textview_item_name);
buttonPlus = view.findViewById(R.id.button_plus);
buttonMinus = view.findViewById(R.id.button_minus);
buttonPlus.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if (listener != null) {
int position = getBindingAdapterPosition();
if (position != RecyclerView.NO_POSITION) {
listener.onPlusClick(position);
}
}
}
});
buttonMinus.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if (listener != null) {
int position = getBindingAdapterPosition();
if (position != RecyclerView.NO_POSITION) {
listener.onMinusClick(position);
}
}
}
});
}
}
public ItemAdapter(ArrayList<Item> itemList) {
mItemList = itemList;
}
}
When the app starts up, the list of items is displayed, with each item showing the correct quantity.
But when the user clicks a plus or minus button, the quantity displayed doesn't change, though the logs clearly indicate the correct position in "itemList" has been chosen, and that the quantity of the corresponding item has indeed changed correctly.
It looks like itemAdapter.notifyItemChanged(position) has no effect. I also tried itemAdapter.notifyDataSetChanged(), but with no effect either. The recycler display is not being refreshed with the data changes.
What could be wrong?
PS: my very unworkable workaround is to run the whole recycler set-up each time that plus or minus button is clicked. Works fine if the list of items is no longer than the screen, but of course, resets to the top of the list if you press a button of an item that was found scrolling down.
This is an answer by the author.
The problem could ultimately be narrowed down to itemAdapter.notifyDataSetChanged() stalling when the entire "itemList" has been changed, like with a for loop. Also itemAdapter.notifyItemChanged() then become irresponsive. As workaround, I have everywhere replaced itemAdapter.notifyDataSetChanged() with setUpRecycler(), an own-defined method that sets up the whole RecyclerView again. Inevitably, the view will go to the top of the list, but that's a quirk I'm (gladly) prepared to accept. itemAdapter.notifyItemChanged() works fine then.
I did not find any right solutions for my case on the internet.
I have a couple of items from a database in my listview object. For this I created a custom BaseAdapter and implemented a OnLongClickListener() inside the adapter class (I know that I would overwrite how a listview handle the animations etc. but this is just for learning).
I can select multiple items with a long click and change the background color of each of the rows. But I can not reset the rows (unchecked and "normal" background color) although I implemented these behaviors in my listener.
What am I doing wrong?
Here is my custom adapter class
package com.dacmas.shoppingapp;
import android.content.Context;
import android.text.AndroidCharacter;
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.ImageView;
import android.widget.ListView;
import android.widget.TextView;
import com.amulyakhare.textdrawable.TextDrawable;
import com.amulyakhare.textdrawable.util.ColorGenerator;
import java.util.ArrayList;
import java.util.List;
public class ShoppingListAdapter extends BaseAdapter {
private ArrayList< ShoppingListEntry > m_itemList;
private Context m_context;
private LayoutInflater m_inflater;
private View m_convertView;
public ShoppingListAdapter(Context context, ArrayList< ShoppingListEntry > shoppingListEntryArrayList ) {
m_itemList = shoppingListEntryArrayList;
m_context = context;
m_inflater = ( LayoutInflater.from( context ) );
}
#Override
public int getCount() { return m_itemList.size(); }
#Override
public Object getItem( int position ) { return m_itemList.get( position ); }
#Override
public long getItemId( int position ) { return position; }
#Override
public View getView(final int position, View convertView, final ViewGroup parent ) {
if ( convertView == null ) {
m_convertView = LayoutInflater.from( m_context ).inflate( R.layout.list_row_shoppinglist, parent, false );
}
ImageView accountImage = (ImageView) m_convertView.findViewById(R.id.text_drawable);
String firstLetterOfName = "A";
ColorGenerator generator = ColorGenerator.MATERIAL;
int color = generator.getRandomColor();
TextDrawable drawable = TextDrawable.builder()
.buildRound(firstLetterOfName, color);
accountImage.setBackground(drawable);
TextView title = ( TextView ) m_convertView.findViewById( R.id.txt_title );
TextView info = ( TextView ) m_convertView.findViewById( R.id.txt_info );
String itemTitle;
String productBrand = m_itemList.get( position ).getProductBrand().toString();
if ( !productBrand.isEmpty() ) {
itemTitle = productBrand + " " + m_itemList.get( position ).getProductName().toString();
} else {
itemTitle = m_itemList.get( position ).getProductName().toString();
}
title.setText( itemTitle );
String itemInfo;
String productAmount = m_itemList.get( position ).getProductAmount();
itemInfo = productAmount;
info.setText( itemInfo );
m_convertView.setOnLongClickListener(new View.OnLongClickListener() {
#Override
public boolean onLongClick(View v) {
if ( ((ListView)parent).isItemChecked(position) ) {
( ( ListView )parent ).setItemChecked( position, false );
v.setBackgroundColor(m_convertView.getResources().getColor(R.color.colorWhite));
} else {
( ( ListView )parent ).setItemChecked( position, true );
v.setBackgroundColor(m_convertView.getResources().getColor(R.color.colorSelectedItem));
}
return false;
}
});
return m_convertView;
}
}
The question is - When do you want the deselection to happen?
Ideally, once you perform a long press, you enter in to a single selection mode. From that point on wards, it should handle clicks (touches) to toggle selection (and optionally change color)
Now that you are done with your selection choices, you may want to
Perform an action like deleting the selected items.
Wait for some other action to exit the selection mode.
In case of option two, trapping the back button press(onBackPressed) might do the trick. in that you can write your logic clear all selections (color and check box)
It might be a good idea if you can bake that logic into your custom adapter for purposes of re-usability.
I have Activity that shows only listview (He pulls the data from SQLite database), There is no limit for the data he can shows, and it work out well.
Now, I want to add a new little listview to the MainActivity which will show only the last five of the data.
I could not find anywhere on the network how to do it..
DataListActivity.java
package com.example.ido.grades;
import android.content.Intent;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.support.v7.app.ActionBarActivity;
import android.os.Bundle;
import android.view.ContextMenu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ListView;
import android.widget.Toast;
public class DataListActivity extends ActionBarActivity {
ListView listView;
SQLiteDatabase sqLiteDatabase;
CourseDbHelper courseDbHelper;
Cursor cursor;
ListDataAdaptar listDataAdaptar;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.update_course);
hideActionBar();
listView = (ListView) findViewById(R.id.list_view);
listDataAdaptar = new ListDataAdaptar(getApplicationContext(),R.layout.row_layout);
listView.setAdapter(listDataAdaptar);
registerForContextMenu(listView);
courseDbHelper = new CourseDbHelper(getApplicationContext());
sqLiteDatabase = courseDbHelper.getReadableDatabase();
cursor = courseDbHelper.getInformation(sqLiteDatabase);
registerForContextMenu(listView);
if (!cursor.moveToFirst()){
}
else {
do {
String year,semester,course,points,grade;
year = cursor.getString(0);
semester = cursor.getString(1);
course = cursor.getString(2);
points = cursor.getString(3);
grade = cursor.getString(4);
DataProvider dataProvider = new DataProvider(year,semester,course,points,grade);
listDataAdaptar.add(dataProvider);
}
while (cursor.moveToNext());
}
}
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.menu_data_list, menu);
}
public boolean onContextItemSelected(MenuItem item) {
AdapterView.AdapterContextMenuInfo info =
(AdapterView.AdapterContextMenuInfo) item.getMenuInfo();
int mySelectedRowIndex = info.position;
switch (item.getItemId()) {
case R.id.update_item:
courseDbHelper = new CourseDbHelper(getApplicationContext());
sqLiteDatabase = courseDbHelper.getReadableDatabase();
DataProvider raw2 = (DataProvider)listDataAdaptar.getItem(mySelectedRowIndex);
Intent i = new Intent(DataListActivity.this, UpdateCourseActivity.class);
String year = raw2.getYear();
String semester = raw2.getSemester();
String course = raw2.getCourse();
String points = raw2.getPoints();
String grade = raw2.getGrade();
int semIndex;
if (semester.equals("A'")){
semIndex =1;
}
else if (semester.equals("B'")){
semIndex =2;
}
else{
semIndex=3;
}
i.putExtra("YEAR", year);
i.putExtra("SEMESTER", Integer.toString(semIndex));
i.putExtra("COURSE", course);
i.putExtra("POINTS", points);
i.putExtra("GRADE", grade);
i.putExtra("POS", Integer.toString(mySelectedRowIndex));
startActivity(i);
return true;
case R.id.delete_item:
courseDbHelper = new CourseDbHelper(getApplicationContext());
sqLiteDatabase = courseDbHelper.getReadableDatabase();
DataProvider raw = (DataProvider)listDataAdaptar.getItem(mySelectedRowIndex);
courseDbHelper.deleteInformation(raw.getYear(), raw.getSemester(), raw.getCourse(), raw.getPoints(), raw.getGrade());
Toast.makeText(this,"delete, pos["+mySelectedRowIndex+"]",Toast.LENGTH_LONG).show();
finish();
startActivity(getIntent());
return true;
default:
return super.onOptionsItemSelected(item);
}
}
public void onBackPressed() {
startActivity(new Intent(this, MainActivity.class));
}
private void hideActionBar() {
//Hide the action bar only if it exists
if (getSupportActionBar() != null) {
getSupportActionBar().hide();
}
}
}
ListDataAdapter.java
package com.example.ido.grades;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.TextView;
import java.util.ArrayList;
import java.util.List;
/**
* Created by Ido on 08/08/2015.
*/
public class ListDataAdaptar extends ArrayAdapter{
List list = new ArrayList();
public ListDataAdaptar(Context context, int resource) {
super(context, resource);
}
static class LayoutHandler{
TextView YEAR,SEMESTER,COURSE,POINTS,GRADE;
}
#Override
public void add(Object object) {
super.add(object);
list.add(object);
}
#Override
public int getCount() {
return list.size();
}
#Override
public Object getItem(int position) {
return list.get(position);
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View row = convertView;
LayoutHandler layoutHandler;
if (row == null){
LayoutInflater layoutInflater = (LayoutInflater) this.getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
row = layoutInflater.inflate(R.layout.row_layout,parent,false);
layoutHandler = new LayoutHandler();
layoutHandler.YEAR = (TextView)row.findViewById(R.id.textYear);
layoutHandler.SEMESTER = (TextView)row.findViewById(R.id.textSemester);
layoutHandler.COURSE = (TextView)row.findViewById(R.id.textCourse);
layoutHandler.POINTS = (TextView)row.findViewById(R.id.textPoints);
layoutHandler.GRADE = (TextView)row.findViewById(R.id.textGrade);
row.setTag(layoutHandler);
}
else{
layoutHandler = (LayoutHandler) row.getTag();
}
DataProvider dataProvider = (DataProvider) this.getItem(position);
layoutHandler.YEAR.setText(dataProvider.getYear());
layoutHandler.SEMESTER.setText(dataProvider.getSemester());
layoutHandler.COURSE.setText(dataProvider.getCourse());
layoutHandler.POINTS.setText(dataProvider.getPoints());
layoutHandler.GRADE.setText(dataProvider.getGrade());
return row;
}
}
You can move to the last position of your current cursor and move to previous rows getting required data. Sort of:
ArrayList string;
if (cursor.moveToLast()){
for (int i = 1; i<=6; i++) {
string.add(cursor.getString(cursor.getColumnIndex("your_data")));
cursor.moveToPrevious();
}
}
cursor.close();
The result will be ArrayList with last 6 strings of your cursor data. You can feed it to the new ArrayAdapter and show it on the screen.
Instead of ArrayList you can create another cursor as well with all existing data and feed it to already existing adapter for example as it's described here:
https://stackoverflow.com/a/18290921/4890659
Ignoring your code, you obviously have an Array of Objects your passing into the constructor of your Array Adapter.
Object[] data = new Object[]{...}
MyArrayAdapter myAdapter = new MyArrayAdapter(data ,... else)
You simply get this data array and append the last 6 elements to a new array.
Object[] data2 = new Object[6]
for(int i = data.length()-7; i<data.length(); i++){
data2[data.length()-i] = data[i]
Then create a new Adapter passing the new data2 array.
MyArrayAdapter myAdapter = new MyArrayAdapter(data2, ... else)
Create method that will return the last 6 rows i cursor [SQL query].
cursor = courseDbHelper.getInformationLast_6(sqLiteDatabase);
SQL:
SELECT * FROM table ORDER BY column DESC [ASC] LIMIT 6;
by this you can get 6 items in your list. Try with following code in your adapter class.
public int getCount() {
return 6;
}
than change your query to limit 6 by ascending order or descending order.
I am a new android developer and I need your help. I created a simple listview. User can add some item in listview (type in EditText and click on Button "OK"). When user made onItemClick , the app will strike out text and set background green.
But then , when I add one more item,I see that it applies that strike out and background option from previously item.
Can you advise me what I need to do in this situation? How to improve it?
package com.example.boytsov.foodbasketapp;
import android.app.Activity;
import android.graphics.Color;
import android.os.Bundle;
import android.util.Log;
import android.view.KeyEvent;
import android.view.View;
import android.view.inputmethod.EditorInfo;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
import java.util.ArrayList;
import java.util.List;
/**
* Created by Boytsov on 23.07.2015.
*/
public class ProductList extends Activity implements View.OnClickListener,AdapterView.OnItemClickListener,AdapterView.OnItemLongClickListener,TextView.OnEditorActionListener {
EditText myText;
ListView lvMain;
ArrayList<String> catnames;
ArrayAdapter<String> adapter;
Button button;
DataBase db;
final String LOG_TAG = "myLogs";
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.productlist);
db = new DataBase(this);
myText = (EditText)findViewById(R.id.editText);
lvMain = (ListView) findViewById(R.id.lvMain);
button=(Button)findViewById(R.id.button);
catnames= new ArrayList<String>();
// создаем адаптер
adapter = new ArrayAdapter<String>(this,
android.R.layout.simple_list_item_1, catnames);
// присваиваем адаптер списку
lvMain.setAdapter(adapter);
lvMain.setOnItemClickListener(this);
lvMain.setOnItemLongClickListener(this);
// Прослушиваем нажатия клавиш
button.setOnClickListener(this);
//слушаем edittext
myText.setOnEditorActionListener(this);
}
#Override
public void onClick(View view) {
switch (view.getId()){
case R.id.button : {
//TextView myText = (TextView) view;
catnames.add(0, myText.getText().toString());
adapter.notifyDataSetChanged();
//myText.setBackgroundColor(Color.TRANSPARENT);
//myText.setPaintFlags(0);
Log.d("Insert: ", "Inserting ..");
db.addProduct(new Product(myText.getText().toString()));
myText.setText("");
Log.d("Reading: ", "Reading all contacts..");
List<Product> products = db.getAllProducts();
for (Product cn : products) {
String log = "Id: "+cn.getID_product()+" ,Name: " + cn.getName_product();
// Writing Contacts to log
Log.d("Name: ", log);
}
}
break;
}
}
#Override
public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
TextView textview= (TextView) view;
if (textview.getPaintFlags() != 16){
textview.setPaintFlags(16);
textview.setBackgroundColor(Color.parseColor("#77dd77"));
Toast.makeText(this, "Куплено", Toast.LENGTH_SHORT).show();
adapter.notifyDataSetChanged();
Log.d(LOG_TAG, "itemClick: position = " + i + ", id = "
+ l);
} else {
textview.setPaintFlags(0);
textview.setBackgroundColor(Color.TRANSPARENT);
Toast.makeText(this, "Не куплено", Toast.LENGTH_SHORT).show();
}
}
#Override
public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
catnames.remove(position);
adapter.notifyDataSetChanged();
Toast.makeText(this, "Удалено", Toast.LENGTH_SHORT).show();
Log.d(LOG_TAG, "onItemClick: position = " + position + ", id = "
+ id);
return true;
}
#Override
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
boolean handled = false;
if (actionId == EditorInfo.IME_ACTION_SEND) {
Log.d(LOG_TAG, "onItemClick: position = " + actionId + ", id = "
+ event);
handled = true;
}
return handled;
}
}
You apply your changes on the view at position 0.
When you add your item you also add it to position 0, hence the entire list is pushed down by 1 and the new item get the already changed view.
Edited Answer
Sorry, I was short on time, but now I can address it more thoroughly.
One important thing you must understand is that the view which shows your data in the list view DOES NOT NECESSARILY correspond with your data.
If you click on an item in your list and change it's views attributes, it doesn't change the state for the item or object which represents the data, but the view itself.
For example if you click on item at position 0 it will change the view's background at position 0. Then, in your example, you add an item at the top of the list, which puts the newly created object at position 0 (with the already modified view) and pushes THE REST OF THE ALREADY CREATED DATA by one, And you end up with an already changed view at position 0 with new data at position 0.
What you should do is as follows:
1)Make sure your object has a boolean member which states if item is "strike out" or not. like for example mIsStrikeOut.
2)create a custom adapter from any android adapter, like from ArrayAdapter.
3)Override it's getView method, for example:
#Override
public View getView(int position, View convertView, ViewGroup parent)
{
View view = convertView;
if (view == null)
{
view = (LinearLayout) inflater.inflate(R.layout. simple_list_item_1, parent, false);
}
TextView textview = (TextView) view.findViewById(R.id.text1);
if (catnames.get(position).isStrikeOut())
{
textview.setPaintFlags(16);
textview.setBackgroundColor(Color.parseColor("#77dd77"));
Toast.makeText(this, "Куплено", Toast.LENGTH_SHORT).show();
Log.d(LOG_TAG, "itemClick: position = " + i + ", id = "
+ l);
}
else
{
textview.setPaintFlags(0);
textview.setBackgroundColor(Color.TRANSPARENT);
Toast.makeText(this, "Не куплено", Toast.LENGTH_SHORT).show();
}
return view;
}
Side note:
When you query your data from your DB make sure the order is correct, I would suggest ORDER BY id dec or something like that.
I do not really know if this is the right answer but it is a suggestion :
#Override
public void onClick(View view) {
switch (view.getId()){
case R.id.button : {
//TextView myText = (TextView) view;
catnames.add(0, myText.getText().toString());
View view = listView.getChildAt(0);
TextView textview= (TextView) view;
textview.setBackgroundColor(Color.TRANSPARENT);
adapter.notifyDataSetChanged();
//myText.setBackgroundColor(Color.TRANSPARENT);
//myText.setPaintFlags(0);
Log.d("Insert: ", "Inserting ..");
db.addProduct(new Product(myText.getText().toString()));
myText.setText("");
Log.d("Reading: ", "Reading all contacts..");
List<Product> products = db.getAllProducts();
for (Product cn : products) {
String log = "Id: "+cn.getID_product()+" ,Name: " + cn.getName_product();
// Writing Contacts to log
Log.d("Name: ", log);
}
}
break;
}
}
I have an app with a list view. The listview works fine. The problem starts, when I want the list to start with some of the rows marked. I can mark a row, if I press on it. But, don't seem to find a way to mark any row on initialization.
This is my code:
listViewOfBluetooth = getListView();
setInitialEnabledDevices();
listViewOfBluetooth.setOnItemClickListener(new OnItemClickListener() {
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
String chosenBluetoothDevice = (String) ((TextView) view).getText();
BluetoothEnableOrDisable(view, chosenBluetoothDevice);
Toast.makeText(getApplicationContext(), chosenBluetoothDevice, Toast.LENGTH_SHORT).show();
editor.putString("bluetooth_name_from_list1", chosenBluetoothDevice);
editor.putBoolean("have_the_cars_bluetooth", true);
editor.commit();
Intent intent = new Intent(List.this, ParkOGuardActivity.class);
startActivity(intent);
}
});
}
public static void setInitialEnabledDevices(){
int length = listViewOfBluetooth.getChildCount();
View view = null;
String first = prefs.getString("bluetooth_name_from_list0", "");
String second = prefs.getString("bluetooth_name_from_list1", "");
String third = prefs.getString("bluetooth_name_from_list2", "");
for(int i = 0; i < length; i++){
view = listViewOfBluetooth.getChildAt(i);
if(view.equals(first) || view.equals(second) || view.equals(third)) {
view.setBackgroundColor(Color.GRAY);
}
}
}
How can I fix it?
Thanks!
You can achive this by using custom adapter. Here is the workaround.
Initialize your custom adapter
Add some flag for marked device names.
Override the getView() & check for the flag. And set the background of the list item accordingly.
Reply if you don't get it or face any complexity.
Update:
Here is a sample adapter. I didn't compile the code. So there might be some errors.
import java.util.ArrayList;
import android.content.Context;
import android.graphics.Color;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.TextView;
public class TestAdapter extends BaseAdapter
{
ArrayList<String> deviceNames;
ArrayList<Boolean> selected;
Context context;
public TestAdapter(Context context)
{
this.context = context;
deviceNames = new ArrayList<String>();
selected = new ArrayList<Boolean>();
}
public void addDeviceToList(String deviceName, boolean isSelected)
{
deviceNames.add(deviceName);
selected.add(isSelected);
notifyDataSetChanged();
}
public int getCount()
{
return deviceNames.size();
}
public Object getItem(int position)
{
return deviceNames.get(position);
}
public long getItemId(int position)
{
return position;
}
public View getView(int position, View convertView, ViewGroup parent)
{
TextView tv = new TextView(context);
tv.setText(deviceNames.get(position));
if(selected.get(position) == true)
{
tv.setBackgroundColor(Color.parseColor("#ff0000"));
}
return tv;
}
}
Now create adapter object and set the adapter to listView. And add single item by calling addDeviceToList() method.
That seems to nasty
but i think you want to modify the views inside listview before loading it
The thing is, that your list won't have children as long as the list is not displayed to the user.so you may not modify the view before showing it to the user
But if you really need to communicate with the views on a such low level you could try to attach a scroll listener to your list:
#Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
for (int i = 0; i < visibleItemCount; i++) {
View child = getChildAt(i);
Now edit this view
}
}