My expandable listview scroll very slow and take a while when I click on the parent category until I see the child view.
Group List Activity:
public class GroupsListActivity extends Activity {
String loggedUserId = Model.getInstance().getLoggedUserId();
List<String> groupsList;
static ExpandableListView expandableListView;
HashMap<String, List<Group>> groupCategories = new HashMap<String, List<Group>>();
static ProgressBar spinner;
static TextView textLoading;
ImageButton createCategoryButton;
static Adapter adapter;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Set layout for this activity
setContentView(R.layout.expandable_list);
// Set actionbar title
getActionBar().show();
getActionBar().setTitle(Html.fromHtml("<font color='#fffffff'>Groups</font>"));
if (loggedUserId != null)
Log.d("TAG", "My Groups for user ID: " + loggedUserId);
// Connect between buttons to layout id
expandableListView = (ExpandableListView) findViewById(R.id.exp_list);
spinner = (ProgressBar) findViewById(R.id.spinner);
createCategoryButton = (ImageButton) findViewById(R.id.createCategory);
textLoading = (TextView) findViewById(R.id.textLoading);
// Loading data to expandable group list asynchronously
AsyncTask<String, String, HashMap<String, List<Group>>> task = new AsyncTask<String, String, HashMap<String, List<Group>>>() {
#Override
protected HashMap<String, List<Group>> doInBackground(String... params) {
return DataProvider.getInfo();
}
#Override
protected void onPostExecute(HashMap<String, List<Group>> listHashMap) {
super.onPostExecute(listHashMap);
// Setting adapter and creating group list
groupCategories = listHashMap;
groupsList = new ArrayList<String>(groupCategories.keySet());
adapter = new Adapter(GroupsListActivity.this, groupCategories, groupsList, GroupsListActivity.this);
expandableListView.setAdapter(adapter);
// Hide spinner after loading
spinner.setVisibility(View.GONE);
textLoading.setVisibility(View.GONE);
}
};
task.execute();
// Setting listener for group click
expandableListView.setOnChildClickListener(new ExpandableListView.OnChildClickListener() {
#Override
public boolean onChildClick(ExpandableListView parent, View v, int parentPosition, int childPosition, long id) {
// After selecting a group on row - open contacts list for this group
expandableListView.setEnabled(false);
openContactListForGroup(groupCategories.get(groupsList.get(parentPosition)).get(childPosition).getGroupID());
return true;
}
});
// Setting listener for create group click
createCategoryButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
createCategoryButton.setEnabled(false);
onCategoryCreate(GroupsListActivity.this, createCategoryButton);
}
});
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.menu_actionbar_groups, menu);
return true;
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.action_create:
onCreate();
return true;
case R.id.action_search:
onSearch();
return true;
case R.id.action_favorites:
onFavorites();
return true;
case R.id.action_settings:
onSettings();
return true;
default:
return super.onOptionsItemSelected(item);
}
}
// Menu methods
private void onCreate() {
Log.d("TAG", "Create button was pressed");
Intent i = new
Intent(getApplicationContext(),
CreateGroupActivity.class);
startActivity(i);
overridePendingTransition(R.animator.slide_out_right, R.animator.slide_in_right);
}
private void onSearch() {
Log.d("TAG", "Search button was pressed");
Intent i = new
Intent(getApplicationContext(),
SearchActivity.class);
startActivity(i);
overridePendingTransition(R.animator.slide_out_right, R.animator.slide_in_right);
}
private void onFavorites() {
Log.d("TAG", "Favorites button was pressed");
Intent i = new
Intent(getApplicationContext(),
FavoritesListActivity.class);
startActivity(i);
overridePendingTransition(R.animator.slide_out_right, R.animator.slide_in_right);
}
private void onSettings() {
Log.d("TAG", "Settings button was pressed");
// Settings activity
Intent i = new
Intent(getApplicationContext(),
SettingsActivity.class);
startActivity(i);
overridePendingTransition(R.animator.slide_out_right, R.animator.slide_in_right);
}
// Methods to handle action buttons
private void onCategoryCreate(final Activity activity, final ImageButton createCategoryButton) {
final AlertDialog.Builder builder = new AlertDialog.Builder(activity);
createCategoryButton.setEnabled(true);
final String title = "Create a new category";
String message = "Type a name for your new category";
// Set dialog edit_text
final EditText categoryNameTextView = new EditText(activity);
categoryNameTextView.setHint("Type your category name");
LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.MATCH_PARENT);
categoryNameTextView.setLayoutParams(lp);
builder.setView(categoryNameTextView);
// Set dialog title and message
if (title != null)
builder.setTitle(Html.fromHtml("<font color='#dc1c1c'>" + title + "</font>")).setMessage(message);
// Set dialog buttons
builder.setPositiveButton("OK", new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int which) {
final String newCategoryName = categoryNameTextView.getText().toString();
// Check if contains only spaces
if (!(newCategoryName.trim().length() > 0))
Toast.makeText(activity, "Type at least 1 letter to create the category", Toast.LENGTH_LONG).show();
// Check if category name already exists
else if (groupsList.contains(newCategoryName))
Toast.makeText(activity, newCategoryName + " already exist. Please type another category name", Toast.LENGTH_LONG).show();
else {
// Create a new category in server and add user to a sample group
adapter.getCategoriesList().add(newCategoryName);
adapter.getGroupsList().put(newCategoryName, Collections.<Group>emptyList());
// Update adapter and show toast to user
GroupsListActivity.updateAdapter();
Toast.makeText(activity, "You created " + newCategoryName + " category", Toast.LENGTH_LONG).show();
}
}
});
builder.setNegativeButton(
"Cancel",
new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
}
});
builder.setIcon(R.mipmap.edit);
builder.show();
}
public void openContactListForGroup(String groupId) {
// Contacts List activity
Intent i = new
Intent(getApplicationContext(),
ContactsListActivity.class);
// Pass to details activity the logged group id and start activity
Bundle b = new Bundle();
b.putString("groupId", groupId);
i.putExtras(b);
startActivity(i);
overridePendingTransition(R.animator.slide_out_right, R.animator.slide_in_right);
}
// Static methods to use from other activities
public static void updateAdapter() {
spinner.setVisibility(View.VISIBLE);
adapter.notifyDataSetChanged();
// Hide spinner after adapter finish the update
expandableListView.post(new Runnable() {
#Override
public void run() {
spinner.setVisibility(View.GONE);
}
});
}
public static void addGroupToList(String groupId) {
Model.getInstance().getGroup(groupId, new Model.groupReturnedListener() {
#Override
public void addGroupToLocal(Group group) {
// Add group to category Others in Group List Activity
if (adapter.getGroupsList().get("Others").size() == 0) {
// Add group to empty list
List<Group> list = new LinkedList<Group>();
list.add(group);
adapter.getGroupsList().put("Others", list);
adapter.notifyDataSetChanged();
} else {
// Add group to an existing list
adapter.getGroupsList().get("Others").add(group);
adapter.notifyDataSetChanged();
}
}
});
}
public static void removeGroupFromList(String groupId) {
int position = -1;
// Get category position
String oldCategoryName = Model.getInstance().getCategoryNameByGroupId(groupId);
List<Group> data = adapter.getGroupsList().get(oldCategoryName);
// Search for group position
for (Group group : data) {
if (group.getGroupID().equals(groupId)) {
position = data.indexOf(group);
break;
}
}
// Groups was found
if (position != -1) {
data.remove(position);
adapter.notifyDataSetChanged();
}
}
public static void updateGroupFromList(Group group) {
int position = -1;
// Get category position
String oldCategoryName = Model.getInstance().getCategoryNameByGroupId(group.getGroupID());
List<Group> data = adapter.getGroupsList().get(oldCategoryName);
// Search for group position
for (Group groupIterator : data) {
if (groupIterator.getGroupID().equals(group.getGroupID())) {
position = data.indexOf(groupIterator);
break;
}
}
// Groups was found
if (position != -1) {
data.remove(position);
data.add(group);
adapter.notifyDataSetChanged();
}
}
// Other methods
#Override
protected void onResume() {
super.onResume();
expandableListView.setEnabled(true);
}
#Override
public void onBackPressed() {
ExitDialog exitDialog = new ExitDialog(GroupsListActivity.this);
exitDialog.show();
}
}
This is the activity of the Adapter where I load the expandable listview :
#Override
public View getGroupView(final int parent, boolean isExpanded, View convertView, ViewGroup parentView) {
final String categoryName = (String)getGroup(parent);
ParentViewHolder pHolder = null;
if(convertView == null) {
pHolder = new ParentViewHolder();
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
convertView = inflater.inflate(R.layout.expandable_list_parent, parentView, false);
// Connect between buttons to layout id
pHolder.categoryNameTextView = (TextView) convertView.findViewById(R.id.categoryName);
pHolder.editCategory = (ImageButton) convertView.findViewById(R.id.editCategory);
pHolder.deleteCategory = (ImageButton) convertView.findViewById(R.id.deleteCategory);
convertView.setTag(pHolder);
}
else {
pHolder = (ParentViewHolder) convertView.getTag();
}
// Hide edit and delete button for category name Others
if(categoriesList.get(parent).equals("Others")){
pHolder.editCategory.setVisibility(View.GONE);
pHolder.deleteCategory.setVisibility(View.GONE);
}
else {
pHolder.editCategory.setVisibility(View.VISIBLE);
pHolder.deleteCategory.setVisibility(View.VISIBLE);
}
// Set category name on row
pHolder.categoryNameTextView.setTypeface(null, Typeface.BOLD);
pHolder.categoryNameTextView.setText(categoryName + ": " + getChildrenCount(parent));
// Set edit category button listener
final ParentViewHolder finalPHolder = pHolder;
pHolder.editCategory.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
finalPHolder.editCategory.setEnabled(false);
editCategoryName(activity, finalPHolder.categoryNameTextView.getText().toString().toString().split(": ")[0], finalPHolder.editCategory, parent);
}
});
// Set delete category button listener
pHolder.deleteCategory.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
finalPHolder.deleteCategory.setEnabled(false);
deleteCategory(activity, categoryName, finalPHolder.deleteCategory);
}
});
return convertView;
}
#Override
public View getChildView(final int parent, final int child, boolean lastChild, View convertView, ViewGroup parentView) {
final Group group = (Group)getChild(parent, child);
ChildViewHolder cHolder = null;
if(convertView == null){
cHolder = new ChildViewHolder();
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
convertView = inflater.inflate(R.layout.expandable_list_child, parentView, false);
// Connect between buttons to layout id
cHolder.groupImage = (ImageView) convertView.findViewById(R.id.groupImage);
cHolder.groupName = (TextView) convertView.findViewById(R.id.groupName);
cHolder.moveCategory = (ImageButton) convertView.findViewById(R.id.moveCategory);
cHolder.groupFavoritesButton = (ImageButton) convertView.findViewById(R.id.groupFavorites);
cHolder.groupLeaveGroupButton = (Button) convertView.findViewById(R.id.groupLeave);
cHolder.groupImageProgressbar = (ProgressBar) convertView.findViewById(R.id.groupImageProgressBar);
convertView.setTag(cHolder);
} else {
cHolder = (ChildViewHolder) convertView.getTag();
}
// Set group name on row
cHolder.groupName.setText(group.getName());
// Load group image
cHolder.groupImageProgressbar.setVisibility(View.VISIBLE);
final ChildViewHolder finalHolder = cHolder;
Model.getInstance().getGroupImage(group.getImageName(), new Model.LoadImageListener() {
#Override
public void onResult(Bitmap imageBmp) {
finalHolder.groupImage.setImageBitmap(imageBmp);
finalHolder.groupImageProgressbar.setVisibility(View.GONE);
finalHolder.groupImage.setVisibility(View.VISIBLE);
}
});
// Set move category button listener
cHolder.moveCategory.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
finalHolder.moveCategory.setEnabled(false);
showDialogMoveCategory(activity, group.getGroupID(), finalHolder.moveCategory);
}
});
// After click on group image - open profile for this group
cHolder.groupImage.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
onGroupSelected(group.getGroupID());
}
});
// Setting favorite Button Image
boolean isFavorite = Model.getInstance().groupIsFavorite(loggedUserId, group.getGroupID());
if(isFavorite)
cHolder.groupFavoritesButton.setBackgroundResource(R.mipmap.favorites_on);
else
cHolder.groupFavoritesButton.setBackgroundResource(R.mipmap.favorites_off);
// Setting favorite Button Action
cHolder.groupFavoritesButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
// Add group to favorites
if (!Model.getInstance().groupIsFavorite(loggedUserId, group.getGroupID())) {
finalHolder.groupFavoritesButton.setBackgroundResource(R.mipmap.favorites_on);
Toast.makeText(activity,
"The group " + group.getName() + " was added to favorites", Toast.LENGTH_SHORT).show();
Model.getInstance().changeFavoriteStatus(loggedUserId, group.getGroupID(), "true");
} else {
// Delete group from favorites
finalHolder.groupFavoritesButton.setBackgroundResource(R.mipmap.favorites_off);
Toast.makeText(activity,
"The group " + group.getName() + " was removed from favorites", Toast.LENGTH_SHORT).show();
Model.getInstance().changeFavoriteStatus(loggedUserId, group.getGroupID(), "false");
}
}
});
// After click on group action - leave group
cHolder.groupLeaveGroupButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
finalHolder.groupLeaveGroupButton.setEnabled(false);
showDialogLeaveGroup(activity, "Are you sure ?", "This action will remove yourself from the group " + group.getName(), group.getGroupID(), parent, child);
finalHolder.groupLeaveGroupButton.setEnabled(true);
}
});
return convertView;
}
The loading image method:
public void getGroupImage(final String imageName, final LoadImageListener listener) {
AsyncTask<String, String, Bitmap> task = new AsyncTask<String, String, Bitmap>() {
#Override
protected Bitmap doInBackground(String... params) {
Bitmap bmp = loadImageFromFile(imageName); //first try to find the image on the device
// Bitmap bmp = null;
if (bmp == null) { //if image not found - try downloading it from parse
bmp = modelParse.getGroupImage(imageName);
if (bmp != null)
saveImageToFile(bmp, imageName); //save the image locally for next time *****
}
Bitmap scaledBitmap = scaleDown(bmp, 200, true);
return scaledBitmap;
}
#Override
protected void onPostExecute(Bitmap result) {
listener.onResult(result);
}
};
task.execute();
}
private void saveImageToFile(Bitmap imageBitmap, String imageFileName) {
FileOutputStream fos;
OutputStream out = null;
try {
File dir = context.getExternalFilesDir(null);
out = new FileOutputStream(new File(dir, imageFileName + ".jpg"));
imageBitmap.compress(Bitmap.CompressFormat.PNG, 100, out);
out.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
private Bitmap loadImageFromFile(String fileName) {
Bitmap bitmap = null;
try {
File dir = context.getExternalFilesDir(null);
InputStream inputStream = new FileInputStream(new File(dir, fileName + ".jpg"));
bitmap = BitmapFactory.decodeStream(inputStream);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
return bitmap;
}
I'll try to give the general (abstract) idea about listviews and adapters which should help you figure out the wrong parts yourself.
The whole purpose of the adapter is to display the correct data for the corresponding list item and do as little other job as possible. In this process, anything that is related to data manipulation is requiring cpu cycles that will cause lags and slow scrolling.
Specifically, android apps should run at a smooth 60 frames-per-second and each frame should take no longer then 16.6 milliseconds to render in order to achieve this 60FPS rate. So if you are creating extra load for the CPU it may come on the account of frame rendering and from here the path to lags in rendering is short.
What am I saying - there are probably some methods in your adapter that are manipulating data on the spot, synchronously and it's taxing the cpu. The adapter should take represent the data that is ALREADY prepared for display and just show it in the correct view. An example for performance issues might be as easy as using String.replace() method everytime for every view or another bad example will be loading images Synchronously instead of Asynchronously.
I see two major issues for performance.
First, your use of findViewById everywhere. You're walking the entire view tree doing that. That's why people use ViewHolder patterns or custom view patterns. IF you ever use findViewById more than once per view in the lifetime of your app, you're doing it wrong.
Second, you're allocating new objects every time the user scrolls. Don't. Use a ViewHolder or vustom view pattern so you can reuse the same OnClickListener's for all scroll events, creating them only once per row and updating the values as needed. If you ever create an object in your getView, other than when convertView is null, you're being really inefficient.
Related
I want to make my expandable list smooth.
After looking a lot in the internet,
I added a viewHolder and changed my method where I load the data to be asynchronously. But my list is still slow!!!.
Can you take a look? I added the activity where I load asynchronously the data with my DataProvider and then I init my adapter. This help me to see my activity and a spinner until the data is loaded and then updated on my view.
But I dont have a fluid list when I scroll or when I try to expand a category. Can you help and tell me what to change ? I think that it can be the images but I save them locally to make it faster (I added my methods) and it can be something else....
MyActivity:
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Set layout for this activity
setContentView(R.layout.expandable_list);
// Set actionbar title
getActionBar().show();
getActionBar().setTitle(Html.fromHtml("<font color='#fffffff'>Groups</font>"));
if (loggedUserId != null)
Log.d("TAG", "My Groups for user ID: " + loggedUserId);
// Connect between buttons to layout id
expandableListView = (ExpandableListView) findViewById(R.id.exp_list);
spinner = (ProgressBar) findViewById(R.id.spinner);
createCategoryButton = (ImageButton) findViewById(R.id.createCategory);
textLoading = (TextView) findViewById(R.id.textLoading);
// Loading data to expandable group list asynchronously
AsyncTask<String, String, HashMap<String, List<Group>>> task = new AsyncTask<String, String, HashMap<String, List<Group>>>() {
#Override
protected HashMap<String, List<Group>> doInBackground(String... params) {
return DataProvider.getInfo();
}
#Override
protected void onPostExecute(HashMap<String, List<Group>> listHashMap) {
super.onPostExecute(listHashMap);
// Setting adapter and creating group list
groupCategories = listHashMap;
groupsList = new ArrayList<String>(groupCategories.keySet());
adapter = new Adapter(GroupsListActivity.this, groupCategories, groupsList, GroupsListActivity.this);
expandableListView.setAdapter(adapter);
// Hide spinner after loading
spinner.setVisibility(View.GONE);
textLoading.setVisibility(View.GONE);
}
};
task.execute();
My adapter:
#Override
public View getGroupView(final int parent, boolean isExpanded, View convertView, ViewGroup parentView) {
final String categoryName = (String) getGroup(parent);
ParentViewHolder pHolder = null;
if (convertView == null) {
// Creates a ViewHolder and store references to layouts we want to bind data to.
pHolder = new ParentViewHolder();
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
convertView = inflater.inflate(R.layout.expandable_list_parent, parentView, false);
// Connect between buttons to layout id
pHolder.categoryNameTextView = (TextView) convertView.findViewById(R.id.categoryName);
pHolder.editCategory = (ImageButton) convertView.findViewById(R.id.editCategory);
pHolder.deleteCategory = (ImageButton) convertView.findViewById(R.id.deleteCategory);
//Save holder
convertView.setTag(pHolder);
} else {
// Get the ViewHolder back
pHolder = (ParentViewHolder) convertView.getTag();
}
// Hide edit and delete button for category name Others
if (categoriesList.get(parent).equals("Others")) {
pHolder.editCategory.setVisibility(View.GONE);
pHolder.deleteCategory.setVisibility(View.GONE);
} else {
pHolder.editCategory.setVisibility(View.VISIBLE);
pHolder.deleteCategory.setVisibility(View.VISIBLE);
}
// Set category name on row
pHolder.categoryNameTextView.setTypeface(null, Typeface.BOLD);
pHolder.categoryNameTextView.setText(categoryName + ": " + getChildrenCount(parent));
// Set edit category button listener
final ParentViewHolder finalPHolder = pHolder;
pHolder.editCategory.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
finalPHolder.editCategory.setEnabled(false);
editCategoryName(activity, finalPHolder.categoryNameTextView.getText().toString().toString().split(": ")[0], finalPHolder.editCategory, parent);
}
});
// Set delete category button listener
pHolder.deleteCategory.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
finalPHolder.deleteCategory.setEnabled(false);
deleteCategory(activity, categoryName, finalPHolder.deleteCategory);
}
});
return convertView;
}
#Override
public View getChildView(final int parent, final int child, boolean lastChild, View convertView, ViewGroup parentView) {
final Group group = (Group) getChild(parent, child);
ChildViewHolder cHolder = null;
if (convertView == null) {
// Creates a ViewHolder and store references to layouts we want to bind data to.
cHolder = new ChildViewHolder();
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
convertView = inflater.inflate(R.layout.expandable_list_child, parentView, false);
// Connect between buttons to layout id
cHolder.groupImage = (ImageView) convertView.findViewById(R.id.groupImage);
cHolder.groupName = (TextView) convertView.findViewById(R.id.groupName);
cHolder.moveCategory = (ImageButton) convertView.findViewById(R.id.moveCategory);
cHolder.groupFavoritesButton = (ImageButton) convertView.findViewById(R.id.groupFavorites);
cHolder.groupLeaveGroupButton = (Button) convertView.findViewById(R.id.groupLeave);
cHolder.groupImageProgressbar = (ProgressBar) convertView.findViewById(R.id.groupImageProgressBar);
//Save holder
convertView.setTag(cHolder);
} else {
// Get the ViewHolder back
cHolder = (ChildViewHolder) convertView.getTag();
}
// Set group name on row
cHolder.groupName.setText(group.getName());
// Load group image
cHolder.groupImageProgressbar.setVisibility(View.VISIBLE);
final ChildViewHolder finalHolder = cHolder;
Model.getInstance().getGroupImage(group.getImageName(), new Model.LoadImageListener() {
#Override
public void onResult(Bitmap imageBmp) {
if (imageBmp != null) {
finalHolder.groupImage.setImageBitmap(imageBmp);
finalHolder.groupImageProgressbar.setVisibility(View.GONE);
finalHolder.groupImage.setVisibility(View.VISIBLE);
}
}
});
// Set move category button listener
cHolder.moveCategory.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
finalHolder.moveCategory.setEnabled(false);
showDialogMoveCategory(activity, group.getGroupID(), finalHolder.moveCategory);
}
});
// After click on group image - open profile for this group
cHolder.groupImage.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
onGroupSelected(group.getGroupID());
}
});
// Setting favorite Button Image
boolean isFavorite = Model.getInstance().groupIsFavorite(loggedUserId, group.getGroupID());
if (isFavorite)
cHolder.groupFavoritesButton.setBackgroundResource(R.mipmap.favorites_on);
else
cHolder.groupFavoritesButton.setBackgroundResource(R.mipmap.favorites_off);
// Setting favorite Button Action
cHolder.groupFavoritesButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
// Add group to favorites
if (!Model.getInstance().groupIsFavorite(loggedUserId, group.getGroupID())) {
finalHolder.groupFavoritesButton.setBackgroundResource(R.mipmap.favorites_on);
Toast.makeText(activity,
"The group " + group.getName() + " was added to favorites", Toast.LENGTH_SHORT).show();
Model.getInstance().changeFavoriteStatus(loggedUserId, group.getGroupID(), "true");
} else {
// Delete group from favorites
finalHolder.groupFavoritesButton.setBackgroundResource(R.mipmap.favorites_off);
Toast.makeText(activity,
"The group " + group.getName() + " was removed from favorites", Toast.LENGTH_SHORT).show();
Model.getInstance().changeFavoriteStatus(loggedUserId, group.getGroupID(), "false");
}
}
});
// After click on group action - leave group
cHolder.groupLeaveGroupButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
finalHolder.groupLeaveGroupButton.setEnabled(false);
showDialogLeaveGroup(activity, "Are you sure ?", "This action will remove yourself from the group " + group.getName(), group.getGroupID(), parent, child);
finalHolder.groupLeaveGroupButton.setEnabled(true);
}
});
return convertView;
}
Images methods:
public void getGroupImage(final String imageName, final LoadImageListener listener) {
AsyncTask<String, String, Bitmap> task = new AsyncTask<String, String, Bitmap>() {
#Override
protected Bitmap doInBackground(String... params) {
Bitmap bmp = loadImageFromFile(imageName); //first try to find the image on the device
// Bitmap bmp = null;
if (bmp == null) { //if image not found - try downloading it from parse
bmp = modelParse.getGroupImage(imageName);
if (bmp != null)
saveImageToFile(bmp, imageName); //save the image locally for next time *****
}
Bitmap scaledBitmap = scaleDown(bmp, 200, true);
return scaledBitmap;
}
#Override
protected void onPostExecute(Bitmap result) {
listener.onResult(result);
}
};
task.execute();
}
private void saveImageToFile(Bitmap imageBitmap, String imageFileName) {
FileOutputStream fos;
OutputStream out = null;
try {
File dir = context.getExternalFilesDir(null);
out = new FileOutputStream(new File(dir, imageFileName + ".jpg"));
imageBitmap.compress(Bitmap.CompressFormat.PNG, 100, out);
out.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
private Bitmap loadImageFromFile(String fileName) {
Bitmap bitmap = null;
try {
File dir = context.getExternalFilesDir(null);
InputStream inputStream = new FileInputStream(new File(dir, fileName + ".jpg"));
bitmap = BitmapFactory.decodeStream(inputStream);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
return bitmap;
}
I have and android app that works on android 4.0 great, but it crashes on android 4.3 and 4.4. I get this from the logCat
01-11 14:40:27.669: E/ACRA(25835): ACRA caught a IllegalStateException exception for quran. Building report.
01-11 14:40:27.789: E/AndroidRuntime(25835): java.lang.IllegalStateException: The content of the adapter has changed but ListView did not receive a notification. Make sure the content of your adapter is not modified from a background thread, but only from the UI thread. [in ListView(2131099688, class android.widget.ListView) with Adapter(class quran.functions.PlaylistAdapter)]
Here is my code:
public class Playlist extends FragmentActivity {
private ListView list;
private Button manager, downloadAll;
private TextView reciter;
public static PlaylistAdapter adapter;
private ArrayList<Songs> songs;
private int RECITER_ID;
private String url, title, label;
private SlidingMenu slidingMenu;
private DatabaseHelper db;
private ImageView nowPlaying, back;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.playlist);
initWidgets();
list.setOnItemClickListener(new OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> arg0, View arg1,
int position, long arg3) {
Intent intent = new Intent(Playlist.this, PlayerFinal.class);
intent.putExtra("songs", songs);
if (getIntent().getIntExtra("duaa", -1) == 115)
intent.putExtra("lang", 115);
intent.putExtra("position", position);
intent.putExtra("fromClass", this.getClass() + "");
// intent.putExtra("mp3link", mp3link);
startActivity(intent);
}
});
new AsyncTask<Void, Void, Void>() {
#Override
protected Void doInBackground(Void... params) {
XmlMapParser m = new XmlMapParser(Playlist.this, RECITER_ID);
HashMap<String, ArrayList<String>> map = m.convert();
map.keySet();
label = map.get("RecitorLabel").get(0);
title = map.get("Title").get(0);
url = map.get("Link").get(0);
back = (ImageView) findViewById(R.id.playlist_back);
back.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View arg0) {
finish();
}
});
db.openDB();
for (int i = 1; i < map.get("Link").size(); i++) {
if (db.isDownloaded(i, title, RECITER_ID)) {
songs.add(new Songs(i, map.get("Title").get(i),
Environment.getExternalStorageDirectory()
.getPath()
+ "/"
+ getString(R.string.app_name)
+ "/"
+ title
+ "/"
+ map.get("Title").get(i)
+ ".mp3", title, true, RECITER_ID,
false));
} else
songs.add(new Songs(i, map.get("Title").get(i), url
+ label + "/"
+ new DecimalFormat("000").format(i) + ".mp3",
title, false, RECITER_ID, false));
}
db.closeDB();
// Log.v("--",m.convert().get("Link").get(1));
// [RecitorLabel, Title, Link] THIS ARE THE KEYS m
// Log.v("--", map.get("RecitorLabel").get(0));
// Log.v("--", map.get("Link").get(1));
return null;
}
protected void onPostExecute(Void result) {
adapter = new PlaylistAdapter(Playlist.this, songs);
list.setAdapter(adapter);
reciter.setText(songs.get(0).getRecitorName());
};
}.execute();
}
#Override
public void onBackPressed() {
if (slidingMenu.isMenuShowing()) {
slidingMenu.toggle();
} else {
super.onBackPressed();
}
}
#Override
protected void onResume() {
super.onResume();
try {
if (Tplayer.getInstance().isPlaying()) {
adapter = new PlaylistAdapter(this, songs);
list.setAdapter(adapter);
adapter.notifyDataSetChanged();
}
} catch (Exception e) {
e.printStackTrace();
}
}
#Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_MENU) {
this.slidingMenu.toggle();
return true;
}
return super.onKeyDown(keyCode, event);
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
this.slidingMenu.toggle();
return true;
default:
return super.onOptionsItemSelected(item);
}
}
private void initWidgets() {
db = new DatabaseHelper(this);
manager = (Button) findViewById(R.id.playlist_download_manager);
manager.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
Intent intent = new Intent(Playlist.this, DownloadManager.class);
startActivity(intent);
}
});
reciter = (TextView) findViewById(R.id.playlist_reciter_name_top);
list = (ListView) findViewById(R.id.playlist_list);
downloadAll = (Button) findViewById(R.id.playlist_download_all);
manager = (Button) findViewById(R.id.playlist_download_manager);
songs = new ArrayList<Songs>();
RECITER_ID = getIntent().getIntExtra("filename", -1);
// downloadAll.setOnClickListener(new OnClickListener() {
//
// #Override
// public void onClick(View v) {
// new DownloadAll(Playlist.this, songs);
// db.openDB();
// for (int i = 0; i < songs.size(); i++) {
// db.addDownloaded(songs.get(i).getNumber(), songs.get(i)
// .getLink(), 0, songs.get(i).getRecitorID(), "",
// songs.get(i).getTitle());
// }
// db.closeDB();
// }
// });
nowPlaying = (ImageView) findViewById(R.id.playlist_now_playing);
nowPlaying.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
Tplayer tplayer = Tplayer.getInstance();
if (tplayer.isPlaying()) {
Intent intent = new Intent(Playlist.this, PlayerFinal.class);
if (tplayer.isPlaying())
intent.putExtra("songs", tplayer.getSongs());
else
intent.putExtra("songs", songs);
if (tplayer.getSongs().size() == 14)
intent.putExtra("lang", 115);
intent.putExtra("position", tplayer.getPosition());
startActivity(intent);
}
}
});
// Jeremy Feinstein slidinglistadapter line 94
slidingMenu = new SlidingMenu(this);
slidingMenu.setMode(SlidingMenu.LEFT);
slidingMenu.setTouchModeAbove(SlidingMenu.TOUCHMODE_FULLSCREEN);
slidingMenu.setShadowWidthRes(R.dimen.slidingmenu_shadow_width);
slidingMenu.setShadowDrawable(R.drawable.slidingmenu_shadow);
slidingMenu.setBehindOffsetRes(R.dimen.slidingmenu_offset);
slidingMenu.setFadeDegree(0.35f);
slidingMenu.attachToActivity(this, SlidingMenu.SLIDING_CONTENT);
slidingMenu.setMenu(R.layout.slidingmenu);
}
}
and my playlist adapter class:
public class PlaylistAdapter extends BaseAdapter {
private Activity activity;
private static LayoutInflater inflater = null;
private ArrayList<Songs> data;
private DatabaseHelper db;
private SharedPreferences prefs;
int playpos;
int recitorID;
public PlaylistAdapter(Activity a, ArrayList<Songs> songs) {
activity = a;
data = songs;
db = new DatabaseHelper(a);
inflater = (LayoutInflater) activity
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
prefs = activity.getSharedPreferences("quantic.Quran",
Context.MODE_PRIVATE);
recitorID = prefs.getInt("recID", -1);
playpos = prefs.getInt("posPlaying", -1);
}
public int getCount() {
return data.size();
}
public Object getItem(int position) {
return position;
}
public long getItemId(int position) {
return position;
}
public View getView(final int position, View convertView,
final ViewGroup parent) {
View vi = convertView;
if (convertView == null)
vi = inflater.inflate(R.layout.song_item, parent, false);
ImageView download = (ImageView) vi
.findViewById(R.id.playlist_item_download);
db.openDB();
if (db.isDownloaded(data.get(position).getNumber(), data.get(position)
.getRecitorName(), data.get(position).getRecitorID()))
download.setImageResource(R.drawable.download_yes);
else {
download.setImageResource(R.drawable.download_no);
download.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
new DownloadFileFromURL(activity, data.get(position)
.getRecitorName(), data.get(position).getTitle(),
data.get(position).getLink(), data.get(position)
.getNumber(), data.get(position)
.getRecitorID()).execute();
if (!db.isDBOpen())
db.openDB();
db.addDownloaded(data.get(position).getNumber(),
data.get(position).getLink(), 0, data.get(position)
.getRecitorID(), "", data.get(position)
.getTitle());
Toast.makeText(activity,
"Downloading " + data.get(position).getTitle(),
Toast.LENGTH_SHORT).show();
}
});
}
db.closeDB();
TextView number = (TextView) vi.findViewById(R.id.playlist_item_num);
TextView reciterName = (TextView) vi
.findViewById(R.id.playlist_item_reciterName);
reciterName.setText(data.get(position).getRecitorName());
if (activity.getClass() == Playlist.class) {
reciterName.setVisibility(View.GONE);
}
TextView title = (TextView) vi.findViewById(R.id.playlist_item_reciter);
title.setText(data.get(position).getTitle());
number.setText((position + 1) + "");
ImageView eq = (ImageView) vi.findViewById(R.id.playlist_item_equlizer);
if (Tplayer.getInstance().isPlaying())
if (Tplayer.getInstance().getPosition() == position
&& data.get(position).getRecitorID() == Tplayer
.getInstance().getSong().getRecitorID()) {
eq.setVisibility(View.VISIBLE);
Ion.with(eq).load("http://darkodev.info/quran/dots.gif");
} else {
eq.setVisibility(View.GONE);
}
return vi;
}
}
Very old question, but no answer. I'm sure you have found a fix by now, but anyway.
You're changing the songs list object in background, and if Android decides to redraw your list (user scrolls your list), effectively accessing the songs list object, it may have changed and cause this exception to be thrown.
You need to use a temporary songs list and create a new adapter with it to update your list, thus not changing the current adapter list until you set a new adapter from the main UI thread.
I use https://github.com/bulletnoid/StaggeredGridView this library for make a pinterest style layout and use pulltorefresh on this layout. Also ı have a slide menu for choose different category and according to category which is user choose, refresh and refill the staggred again.
Pulltorefresh is work fine.
if user top of the layout and choose a category on slide menu it's work correctly. But if user bottom of the layout and choose a category on slide menu it's work not correctly .
the scenario, top of layout and select category on slidemenu and refill staggered layout. it's work correctly
the scenario, bottom of layout and select category on slidemenu and refill staggered layout. it's not work correctly
-->listviewAdapter
public void onItemClick(AdapterView<?> parent, View view, int position,
long arg3) {
// TODO Auto-generated method stub
switch (parent.getId()) {
case R.id.listView_sliding_menu:
smenu.toggle();
slidingMenuControl = true;
String categoryId = ((TextView) view.findViewById(R.id.categoryID))
.getText().toString();
parameters[0] = categoryId;
Toast.makeText(getApplicationContext(), categoryId,
Toast.LENGTH_LONG).show();
new PARSEJSONCATEGORYCONTENT().execute(parameters);
break;
default:
break;
}
}
-->parser
private class PARSEJSONCATEGORYCONTENT extends
AsyncTask<String[], Void, ArrayList<Utils>> {
#Override
protected void onPreExecute() {
super.onPreExecute();
processDialoge();
}
protected ArrayList<Utils> doInBackground(String[]... params) {
String catId = params[0][0];
String startCount = params[0][5];
String count = params[0][6];
String urlCatContent = "http://212.58.8.109/webservice/api/content/cat/";
jArray = jsonParser.getJSONFromUrltoCategoryContent(urlCatContent,
Token, tokenValue, catId, startCount, count);
if (utilsArray == null) {
utilsArray = new ArrayList<Utils>();
} else if (slidingMenuControl == true) {
utilsArray.clear();
} else if (contentItemSelection != null) {
utilsArray.clear();
}
try {
// looping through All Contacts
for (int i = 0; i < jArray.length(); i++) {
JSONObject k = jArray.getJSONObject(i);
utils = new Utils();
// Storing each json item in variable
utils.imageUrl = k.getString("ipad_URL");
utils.imageWidth = k.getInt("ipad_width");
utils.imageHeight = k.getInt("ipad_height");
utils.categoryHeader = k.getString("contentHeader");
utils.contentDesc = k.getString("contentDesc");
utils.categoryContentId = k.getInt("id");
utils.contentTxt = k.getString("contentTxt");
Log.d("ipad_URL", utils.imageUrl);
utilsArray.add(utils);
}
String arrayLenght = Integer.toString(utilsArray.size());
Log.d("arrayLenght", arrayLenght);
} catch (JSONException e) {
e.printStackTrace();
}
return utilsArray;
}
protected void onPostExecute(ArrayList<Utils> utilsArray) {
staggeredAdapter.getMoreItemm(utilsArray);
// staggeredAdapter.setRefreshListener(false);
super.onPostExecute(utilsArray);
slidingMenuControl = false;
dialog.cancel();
}
}
-->BaseAdapter.java
#TargetApi(Build.VERSION_CODES.JELLY_BEAN)
#SuppressLint("NewApi")
public class StaggeredAdapter extends BaseAdapter implements OnClickListener {
Typeface tf;
boolean refreshListener = false;
Utils utils;
private Context mContext;
private Application mAppContext;
private ArrayList<Utils> mUtilsArraylist = new ArrayList<Utils>();
public StaggeredAdapter(Context context, Application application) {
mContext = context;
mAppContext = application;
tf = Typeface.createFromAsset(mContext.getAssets(),
"font/Klavika-Medium.otf");
notifyDataSetChanged();
}
public void getMoreItemm(ArrayList<Utils> arrayList) {
mUtilsArraylist.clear();
mUtilsArraylist.addAll(arrayList);
this.notifyDataSetChanged();
}
public int getCount() {
return mUtilsArraylist == null ? 0 : mUtilsArraylist.size();
}
#Override
public Object getItem(int position) {
return mUtilsArraylist.get(position);
}
#Override
public long getItemId(int position) {
return mUtilsArraylist.indexOf(getItem(position));
}
#SuppressLint("NewApi")
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View view = null;
utils = mUtilsArraylist.get(position);
if (convertView == null) {
Holder holder = new Holder();
view = View.inflate(mContext, R.layout.staggered_item, null);
holder.imgUrl_content = (STGVImageView) view
.findViewById(R.id.imgUrl_content);
holder.tv_info = (TextView) view.findViewById(R.id.contentHeader);
holder.tv_info.setTypeface(tf);
holder.tv_info2 = (TextView) view.findViewById(R.id.contentDesc);
holder.tv_info2.setTypeface(tf);
view.setTag(holder);
} else {
return convertView;
}
final Holder holder = (Holder) view.getTag();
holder.imgUrl_content.mHeight = utils.imageHeight;
holder.imgUrl_content.mWidth = utils.imageWidth;
holder.imgUrl_content.setOnClickListener(this);
ImageLoader imgLoader = new ImageLoader(mAppContext);
imgLoader.DisplayImage(utils.imageUrl, holder.imgUrl_content);
holder.tv_info.setText(utils.categoryHeader);
holder.tv_info.setOnClickListener(this);
holder.tv_info2.setText(utils.contentDesc);
holder.tv_info2.setOnClickListener(this);
return view;
}
class Holder {
public STGVImageView imgUrl_content;
public TextView tv_info;
public TextView tv_info2;
}
public boolean isRefreshListener() {
return refreshListener;
}
public void setRefreshListener(boolean refreshListener) {
this.refreshListener = refreshListener;
}
#Override
public void onClick(View view) {
// TODO Auto-generated method stub
switch (view.getId()) {
case R.id.imgUrl_content:
sendDataItemContentActivity();
break;
case R.id.contentHeader:
sendDataItemContentActivity();
break;
case R.id.contentDesc:
sendDataItemContentActivity();
break;
default:
break;
}
}
public void sendDataItemContentActivity() {
Intent intent = new Intent(mContext, ItemContent.class)
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.putExtra("contentTxt", utils.contentTxt);
intent.putExtra("contentHeader", utils.categoryHeader);
intent.putExtra("contentİmageUrl", utils.imageUrl);
intent.putExtra("contentCategoryName", utils.categoryName);
Bundle animBundle = ActivityOptions.makeCustomAnimation(mContext,
R.anim.anim, R.anim.anim2).toBundle();
mContext.startActivity(intent, animBundle);
}
}
I guess i had a same problem with using the same pinterest style library with you. i solve my problem by put this
#Override
public void onResume() {
// TODO Auto-generated method stub
super.onResume();
mAdapter = new SearchSTGVAdapter(getActivity(), adsList, (Fragment)this);
ptrstgv.setAdapter(mAdapter);
}
which previously I put it in the onCreate method
For your information, I am implement this pinterest style in a view pager not a sliding menu
I am facing problem with listview in android. I've a custom adapter with 3 textviews and a button whose text changes as per response from server. Actually I've a feature of search friend in my app,so the list appears with each user and its status on button text. Like if already friend then button text is Friend and button is disabled.Else Add Friend, and button enabled. After clicking Add Friend text of button changes to Request Sent. But the problem is that when i click on a button text of some other buttons also changes on scrolling. Please help me. If needed I'll put the code.
Here is my adapter class:
class ListViewCustomAdapter extends BaseAdapter {
private static final String REQUEST = "Request";
private static final String ACCEPT = "Accepted";
private static final String RECEIVE = "Receive";
private Activity context;
private ArrayList<FriendList> friendList;
private SessionManager sessionManager;
private String authToken;
private ProgressDialog progressDialog;
ViewHolder mViewHolder;
private HashMap<Integer, String> buttonTextMap;
public ListViewCustomAdapter(Activity activity,
ArrayList<FriendList> _friendList) {
this.context = activity;
this.friendList = _friendList;
sessionManager = new SessionManager(context);
authToken = sessionManager.getAuthorizationKey();
buttonTextMap = new HashMap<Integer, String>();
}
public int getCount() {
// TODO Auto-generated method stub
return friendList.size();
}
public Object getItem(int position) {
// TODO Auto-generated method stub
return friendList.get(position);
}
public long getItemId(int position) {
// TODO Auto-generated method stub
return position;
}
public View getView(final int position, View convertView, ViewGroup parent) {
LayoutInflater layoutInflater = (LayoutInflater) context
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
if (convertView == null) {
convertView = layoutInflater.inflate(
R.layout.friend_list_view_item, null);
mViewHolder = new ViewHolder();
mViewHolder.profilePicture = (ImageView) convertView
.findViewById(R.id.friendPicureImageView);
mViewHolder.friendName = (TextView) convertView
.findViewById(R.id.firstNameTextView);
mViewHolder.email = (TextView) convertView
.findViewById(R.id.emailTextView);
mViewHolder.gender = (TextView) convertView
.findViewById(R.id.genderTextView);
mViewHolder.addButton = (Button) convertView
.findViewById(R.id.addFriendButton);
convertView.setTag(mViewHolder);
} else {
mViewHolder = (ViewHolder) convertView.getTag();
}
byte[] imageByteArray = Base64.decode(friendList.get(position)
.getFriendProfilePic(), Base64.DEFAULT);
mViewHolder.profilePicture.setImageBitmap(BitmapFactory
.decodeByteArray(imageByteArray, 0, imageByteArray.length));
if (friendList.get(position).getFriendFirstName()
.equalsIgnoreCase("null")) {
mViewHolder.friendName.setText(friendList.get(position)
.getFriendLastName());
} else if (friendList.get(position).getFriendLastName()
.equalsIgnoreCase("null")) {
mViewHolder.friendName.setText(friendList.get(position)
.getFriendFirstName());
} else if (friendList.get(position).getFriendLastName()
.equalsIgnoreCase("null")
&& friendList.get(position).getFriendFirstName()
.equalsIgnoreCase("null")) {
mViewHolder.friendName.setText("No Name");
} else {
mViewHolder.friendName.setText(friendList.get(position)
.getFriendFirstName()
+ " "
+ friendList.get(position).getFriendLastName());
}
if (!friendList.get(position).getFriendEmail().equalsIgnoreCase("null")) {
mViewHolder.email
.setText(friendList.get(position).getFriendEmail());
}
if (!friendList.get(position).getFriendGender()
.equalsIgnoreCase("null")) {
if (friendList.get(position).getFriendGender()
.equalsIgnoreCase(Constants.MALE))
mViewHolder.gender.setText(Constants.SET_MALE);
else if (friendList.get(position).getFriendGender()
.equalsIgnoreCase(Constants.FEMALE)) {
mViewHolder.gender.setText(Constants.SET_FEMALE);
}
}
if (friendList.get(position).getFriendRequestStatus()
.equalsIgnoreCase(REQUEST)) {
/*
* buttonTextMap.put(position, "Request sent");
* buttonActiveStateMap.put(position, false);
*/
mViewHolder.addButton.setText("Request Sent");
mViewHolder.addButton.setEnabled(false);
} else if (friendList.get(position).getFriendRequestStatus()
.equalsIgnoreCase(ACCEPT)) {
/*
* buttonTextMap.put(position, "Add friend");
* buttonActiveStateMap.put(position, true);
*/
mViewHolder.addButton.setText("Friend");
mViewHolder.addButton.setEnabled(false);
} else if (friendList.get(position).getFriendRequestStatus()
.equalsIgnoreCase(RECEIVE)) {
/*
* buttonTextMap.put(position, "Add friend");
* buttonActiveStateMap.put(position, true);
*/
mViewHolder.addButton.setText("Accept");
mViewHolder.addButton.setEnabled(true);
}
buttonTextMap.put(position, mViewHolder.addButton.getText().toString());
Log.d("FriendList", "position in getview===== " + position);
mViewHolder.addButton.setTag(position);
mViewHolder.addButton.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
int which = -1;
Object obj = v.getTag();
if(obj instanceof Integer){
which = ((Integer)obj).intValue();
Log.e("FriendListActivity", "position button 1 ======= "+which);
}
if(which >-1){
Log.e("FriendListActivity", "position button 2======= "+which);
}
Button button = (Button) FriendListActivity.listView.getChildAt(which).findViewById(R.id.addFriendButton);
//Button button = (Button) v;
if (button.getText().toString().equalsIgnoreCase("Accept")) {
Intent intent = new Intent(context,
NotificationsActivity.class);
context.startActivity(intent);
context.finish();
} else {
int id = button.getId();
addFriend(button, friendList.get(position)
.getFriendUserId(),which);
}
}
});
return convertView;
}
static class ViewHolder {
TextView friendName;
TextView email;
TextView gender;
ImageView profilePicture;
Button addButton;
}
private void addFriend(final Button _button, final String userId,
final int _position) {
final JSONObject jsonObject = new JSONObject();
Log.e("FriendListActivity", "position in addFriend=== " + _position);
try {
jsonObject.put("authToken", authToken);
jsonObject.put("targetFriendId", userId);
jsonObject.put("requestType", "FriendRequest");
jsonObject.put("status", "Requested");
} catch (JSONException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
final Handler handler = new Handler() {
#Override
public void handleMessage(Message msg) {
// TODO Auto-generated method stub
super.handleMessage(msg);
progressDialog.dismiss();
if (msg.what == 1) {
_button.setText("Request sent");
_button.setEnabled(false);
Toast.makeText(context, "Request sent successfully.",
Toast.LENGTH_LONG).show();
} else {
Toast.makeText(context, "Request unsuccessfull.",
Toast.LENGTH_LONG).show();
}
}
};
progressDialog = ProgressDialog.show(context, "", "Loading...");
new Thread() {
#Override
public void run() {
String response = DoFriendRequest
.makeHttpPostRequest(jsonObject);
Message message = new Message();
if (response != null) {
message.what = 1;
handler.sendEmptyMessage(message.what);
} else {
handler.sendEmptyMessage(0);
}
}
}.start();
}
So finally I found the solution after a long research and completely understanding listview's recycling and use of convertview. As I am getting the status for each button from server, so in my addFriend() method I was just updating the text of the button(of the view only) which is pressed, but not in the list from where I am getting the data for the listview(each row of listview). So what I did, whenever I update the label-status of the button for a row, I've to update my datalist(in my case friendList by setting friendList.get(position).setFriendStatus("null")) and call adapter.notifyDatasetChanged() after that. I also forgot to add a check-filter for the "null" status of button. If anyone has any confusion please ask me.
This is the link which I referred for understanding listview getView() method-
How ListView's recycling mechanism works
I have a bug that I think is due to the way that lists reuse objects. I have a list of download buttons that get a progress readout when downloading. The update code is in a background thread.
When I scroll the list, the buttons that are showing progress jump into other list elements. How can I stop this? It's difficult to visualize, so I made a video of it:
http://www.youtube.com/watch?v=EiT2YWb2Prs&feature=youtu.be
BTW, the video was made by my client, so disregard the "samsung bug" bit. I'm the developer...
Here is the code:
public class VideosActivity extends Activity {
ListView video_list;
CustomList2 adapter;
File storage_dir;
String s3_bucket = "xx";
String s3_dir = "android/vol1/"; //the directory after the boucket that the files are stored in (do not add first slash)
Handler handler = new Handler(); //'tunnel' through whci other threads communicate with main thread
ArrayList<String> arr_videos = new ArrayList<String>();
ArrayList<String> arr_sdcardvideos = new ArrayList<String>();
int images[] = {R.drawable.kr115,R.drawable.kr200,R.drawable.kr201,R.drawable.kr202,R.drawable.kr203,R.drawable.kr205,R.drawable.kr206,R.drawable.kr207,R.drawable.kr208,R.drawable.kr209,R.drawable.kr210,R.drawable.kr211,R.drawable.kr212,R.drawable.kr213,R.drawable.kr213,R.drawable.kr214,R.drawable.kr215,R.drawable.kr216,R.drawable.kr210,R.drawable.kr211,R.drawable.kr212,R.drawable.kr213};
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
storage_dir = getApplicationContext().getExternalFilesDir(null); //private to app, removed with uninstall
adapter = new CustomList2(this, R.layout.customlist, arr_videos);
video_list = (ListView)findViewById(R.id.list);
video_list.setAdapter(adapter); //set adapter that specifies list contents
ensureStorageDirExists( storage_dir ); //make sure storage dir exists
set_sdcard_video_data(); //store vids arleady on card
set_video_data(); //store vid dat in array
if(!have_connection())
{
Toast.makeText(this, "No Internet connection", Toast.LENGTH_LONG).show();
return;
}
}
protected void ensureStorageDirExists( File dir )
{
if (!dir.exists())
{
dir.mkdirs();
}
}
public void set_sdcard_video_data()
{
arr_sdcardvideos.clear();
for(File f:storage_dir.listFiles())
{
arr_sdcardvideos.add( f.getName() );
}
}
public void set_video_data()
{
arr_videos.add("02Posture_and_Walk.m4v");
arr_videos.add("03Embrace_Connection_and_Musicality.m4v");
arr_videos.add("04Left_Turning_Check_Step.m4v");
arr_videos.add("05Basic_Right_Turn.m4v");
arr_videos.add("06Ocho_Cortado.m4v");
arr_videos.add("07Media_Vuelta.m4v");
arr_videos.add("08The_Cross.m4v");
arr_videos.add("09Front_Ochos.m4v");
arr_videos.add("10The_Cross_in_Cross_System.m4v");
arr_videos.add("11Back_Ochos.m4v");
arr_videos.add("12Molinete_Giro.m4v");
arr_videos.add("13Right_Turn.m4v");
arr_videos.add("14Combining_All_the_Elements_1.m4v");
arr_videos.add("15Combining_All_the_Elements_2.m4v");
arr_videos.add("16Combining_All_the_Elements_3.m4v");
arr_videos.add("17Combining_All_the_Elements_4.m4v");
arr_videos.add("18Combining_All_the_Elements_5.m4v");
arr_videos.add("19Combining_All_the_Elements_6.m4v");
arr_videos.add("20Demo_Using_All_the_Elements.m4v");
arr_videos.add("36Etiquette.m4v");
}
public SharedPreferences stored_vals()
{
return PreferenceManager.getDefaultSharedPreferences(this);
}
public boolean have_connection()
{
ConnectivityManager cm = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
if(cm.getActiveNetworkInfo()!=null && cm.getActiveNetworkInfo().isConnected() && cm.getActiveNetworkInfo().isAvailable())
{
return true;
}
else
{
return false;
}
}
public void download_video(int position, View btn)
{
}
public class CustomList2 extends ArrayAdapter<String>
{
View view;
int position;
Button btn;
public CustomList2(Context context, int layout_id, ArrayList<String> objects)
{
super(context, layout_id, objects);
}
#Override
public View getView(final int position, View convertView, ViewGroup view_group)
{
set_view(convertView);
this.position = position;
TextView text_view = (TextView) view.findViewById(R.id.name);
ImageView image = (ImageView) view.findViewById(R.id.img);
btn = (Button) view.findViewById(R.id.play);
prepare_btn();
text_view.setText( list_text() );
image.setImageResource(images[position]);
return view;
}
public String list_text()
{
String s = arr_videos.get( position ).replace("_", " ").replace(".m4v", "");
s = s.substring(2, s.length());
return s;
}
public void set_view(View convertView)
{
if(convertView == null)
{
LayoutInflater inflater = getLayoutInflater();
view = inflater.inflate(R.layout.customlist, null);
}
else
{
view = convertView;
}
}
public Boolean is_downloaded()
{
return arr_sdcardvideos.contains(arr_videos.get(position));
}
public void prepare_btn()
{
btn.setTag((Integer) position);
if(is_downloaded() == true)
{
btn.setText("Play ");
btn.setEnabled(true);
btn.setOnClickListener( new OnClickListener()
{
public void onClick(View btn)
{
int position = (Integer) btn.getTag();
Intent i = new Intent(VideosActivity.this, PlayVideoActivity.class);
String video_path = storage_dir + "/" + arr_videos.get(position);
Log.v("video_path", video_path);
i.putExtra("video_path", video_path);
startActivity(i);
}
});
}
else
{
btn.setText("Download ");
btn.setOnClickListener( new OnClickListener()
{
public void onClick(View btn)
{
int position = (Integer) btn.getTag();
btn.setEnabled(false);
//download_video( position, btn );
Download d = new Download();
d.start(position, (Button) btn);
}
});
}
}
}
public class Download
{
File new_video_file;
Button btn; //the progress meter needs to know what button called this. set via setter method below.
int position;
com.amazonaws.services.s3.transfer.Download download;
protected void start(int position, Button btn)
{
this.btn = (Button) btn;
this.position = position;
this.new_video_file = new File(storage_dir, arr_videos.get(position)); //local file to be writtent to
AWSCredentials credentials = new BasicAWSCredentials("xx", "xx" );
TransferManager tx = new TransferManager(credentials);
this.download = tx.download(s3_bucket, s3_dir + arr_videos.get(position), new_video_file);
download.addProgressListener(new ProgressListener()
{
public void progressChanged(final ProgressEvent pe)
{
handler.post( new Runnable()
{
#Override
public void run()
{
if ( pe.getEventCode() == ProgressEvent.COMPLETED_EVENT_CODE )
{
Download.this.onComplete();
}
else
{
Download.this.onProgressUpdate();
}
}
});
}
});
}
//protected void onProgressUpdate(Double progress)
protected void onProgressUpdate()
{
Double progress = this.download.getProgress().getPercentTransfered();
String percent = progress.intValue() + "%";
Log.v("runnable", percent);
btn.setText(percent);
Log.v("dev", progress + "");
}
protected void onComplete()
{
Log.v("dev", "download complete!!!");
set_sdcard_video_data();
adapter.notifyDataSetChanged();
// this.download.abort();
}
}
}
Your Download Thread keeps a reference to the Button. The Buttons get reused though for different list elements when you scroll. You need to find a way to update the Button in the Thread somehow. The Button in the Thread can also be null, when the list element is not visible.
Edit:
It's really not that complicated. First of all: you should use a ViewHolder, that's gonna make your life much easier. Then you could do something like that
#Override
public View getView(final int position, View convertView, ViewGroup view_group) {
View view = null;
ViewHolder holder = null;
if(convertView == null){
view = {inflate view}
view.setTag(holder = new ViewHolder());
holder.view1 = view.findViewById(...);
{get rest of the views}
}else{
view = convertView;
holder = (ViewHolder)view.getTag();
}
if(holder.getDownloadThread() != null){
holder.getDownloadThread().setButton(null);
}
...
}
You get the idea.