Ok so I'm at my wit's end with this.
I've encapsulated the list data update functionality inside a function called 'refreshData' inside the adapter. I then call this function on the adapter inside of onActivityResult (the activity that was started for result sets a completion flag onStop. This completion flag is supposed to determine which styles (colors and text) are applied to a particular list item in the list view. Essentially, user selects "1" and goes to new activity, closes that activity, and "1" get's set to done onStop. Returning to the list activity, the listview should make available "2" while setting "1" to complete (visual cues).
The problem I'm having is, that the update only happens the SECOND time the user clicks the list item to go to the new activity and close to return. Without fail, the listview will update the SECOND time only. I have no clue why this is happening. Any help is appreciated.
Thanks.
public class SceneAdapter extends ArrayAdapter {
Context context;
int layoutResourceId;
ArrayList<SceneItem> data;
Typeface font;
UserDatabaseHelper uDatabase;
int chapter_number;
public SceneAdapter(Context context, int layoutResourceId, int chapter, String FONT) {
super(context, layoutResourceId);
this.context = context;
this.layoutResourceId = layoutResourceId;
this.chapter_number = chapter;
this.data = getData(chapter);
this.font = Typeface.createFromAsset(context.getAssets(), FONT);
}
public int getCount() {
return data.size();
}
#Override
public int getViewTypeCount() {
int typeCount = 2;
return typeCount;
}
private ArrayList<SceneItem> getData(int chapter) {
// bucket to return data
ArrayList<SceneItem> container = new ArrayList<SceneItem>();
// titles (need to replace with master database call eventually)
String[] scenesListJapanese = getSceneTitlesInJapanese(chapter);
String[] scenesListEnglish = getSceneTitlesInEnglish(chapter);
// ready database for data retrieval
initializeUserDatabaseForUse();
// query for data
Cursor query = uDatabase.getAllScenesForChapter(chapter + 1);
// testing line, take out when done
Log.v("query length: ", ""+ query.getCount());
// fill data
for (int i = 0; i < scenesListJapanese.length; i++) {
SceneItem item = new SceneItem(""+(i+1), scenesListEnglish[i], scenesListJapanese[i],
query.getInt(4), query.getInt(3));
container.add(item);
Log.v("status for chapter " + (chapter + 1) + ": ", "available: " + query.getInt(4)
+ ", completed: " + query.getInt(3));
query.moveToNext();
}
// close db connection
query.close();
return container;
}
public void refreshData() {
data.clear();
this.data = getData(chapter_number);
Log.v("refresh data called: ", "NOW!");
}
public boolean areAllItemsEnabled() {
return false;
}
public boolean isEnabled(int position) {
if(data.get(position).available == 1 || position == 0) {
return true;
} else {
return false;
}
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View row = convertView;
SceneHolder holder = null;
if (row == null) {
LayoutInflater inflater = ((Activity)context).getLayoutInflater();
row = inflater.inflate(layoutResourceId, parent, false);
holder = new SceneHolder();
holder.chapterNumber = (TextView)row.findViewById(R.id.scene_number);
holder.englishTitleBox = (TextView)row.findViewById(R.id.scene_title_english);
holder.japaneseTitleBox = (TextView)row.findViewById(R.id.scene_title_japanese);
holder.englishTitleBox.setTypeface(font);
holder.japaneseTitleBox.setTypeface(font);
row.setTag(holder);
} else {
holder = (SceneHolder)row.getTag();
}
Resources res = context.getResources();
if(isEnabled(position)) {
holder.englishTitleBox.setTextColor(res.getColor(R.color.gray_subtext));
holder.chapterNumber.setBackgroundResource(R.drawable.number_gray);
holder.japaneseTitleBox.setTextColor(res.getColor(R.color.gray_text));
holder.chapterNumber.setTextColor(res.getColor(R.color.white_text));
row.setBackgroundColor(res.getColor(R.color.transparent));
} else {
holder.englishTitleBox.setTextColor(res.getColor(R.color.standard_gray));
holder.chapterNumber.setBackgroundResource(R.drawable.number_transparent);
holder.japaneseTitleBox.setTextColor(res.getColor(R.color.standard_gray));
holder.chapterNumber.setTextColor(res.getColor(R.color.standard_gray));
row.setBackgroundColor(res.getColor(R.color.action_gray_end));
}
SceneItem item = data.get(position);
holder.chapterNumber.setText(item.scene);
holder.englishTitleBox.setText(item.titleEnglish);
holder.japaneseTitleBox.setText(item.titleJapanese);
return row;
}
static class SceneHolder
{
TextView englishTitleBox;
TextView japaneseTitleBox;
TextView chapterNumber;
}
// Get english scene titles based on chapter number
private String[] getSceneTitlesInEnglish(int position) {
// generate and return list
}
// Get japanese scene titles based on chapter number
private String[] getSceneTitlesInJapanese(int position) {
// generate and return list
}
public void initializeUserDatabaseForUse() {
// init database
}
}
Code for calling activity:
public class SceneSelectActivity extends Activity {
ListView mListView; // list view to display scene data
int chapterNumber; // chapter number to use for displaying scenes
Intent passedIntent; // passed intent containing data on which chapter was selected
UserDatabaseHelper uDatabase; // user database reference
SceneAdapter sceneAdapter; // adapter reference
static final int REQUEST_CODE = 1; // request code for activity result
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_scene_select);
// Setup necessary variables and views for this activity
initializeUserDatabaseForUse();
handleSetup();
Button backButton = (Button)findViewById(R.id.scene_back_button);
backButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
finish();
}
});
}
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode,resultCode, data);
sceneAdapter.refreshData();
sceneAdapter.notifyDataSetChanged();
}
#Override
public void onResume() {
super.onResume();
}
// Handle set up of activity views and variables
private void handleSetup() {
// passed intent holding data on which chapter was selected
passedIntent = getIntent();
// chapter number to be used in deciding what data/views to set up
chapterNumber = getChapterNumber(passedIntent);
// setup list data, and views
setDataForThisScene(chapterNumber);
setMainImage(chapterNumber);
}
// Obtain chapter number from passed intent
private int getChapterNumber(Intent i) {
// ensure the data is a valid number for a chapter
return i.getIntExtra("chapter_number", 0);
}
// Set up data to be used for list view
private void setDataForThisScene(int position) {
// set chapter splash content
Resources res = getResources();
TextView splashJ = (TextView)findViewById(R.id.scene_splash_title_j);
TextView splashE = (TextView)findViewById(R.id.scene_splash_title_e);
final String title_english[] = res.getStringArray(R.array.chapters_english);
final String title_japanese[] = res.getStringArray(R.array.chapters_japanese);
splashJ.setText(title_japanese[chapterNumber]);
splashE.setText(title_english[chapterNumber]);
TextView completionView = (TextView) findViewById(R.id.scene_progress);
completionView.setText(getCompletionStatus(chapterNumber + 1));
// setup scene list
mListView = (ListView) findViewById(R.id.scene_list);
mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Intent i = new Intent(SceneSelectActivity.this, DialogueActivity.class);
i.putExtra("chapter_number", chapterNumber);
i.putExtra("scene_number", position);
startActivityForResult(i, REQUEST_CODE);
//startActivity(i);
// LIST ON CLICK ITEM EVENTS
}
});
//SceneAdapter sceneAdapter = new SceneAdapter(this, R.layout.scene_list_item, sceneData, "custom_font_regular.otf");
sceneAdapter = new SceneAdapter(this, R.layout.scene_list_item, chapterNumber, "custom_font_regular.otf");
mListView.setAdapter(sceneAdapter);
}
private String getCompletionStatus(int chapter) {
Cursor query = uDatabase.getAllScenesForChapter(chapter);
int completed = 0;
for(int i = 0; i < query.getCount(); i++) {
if(query.getInt(3) != 0) {
completed += 1;
}
query.moveToNext();
}
query.close();
return completed * 100 / query.getCount() + "%";
}
// fill in image for chapter bg
private void setMainImage(int chapterNumber) {
// generate image
}
// initialize database
public void initializeUserDatabaseForUse() {
// init database
}
}
The LOG.v inside the 'refreshData' function DOES get called each time the detail activity is closed and the user returns to this list view activity. Although it gets called every time, the list only updates the SECOND time the user repeats the action.
Related
I am trying to implement a "More" button for a spinner. Basically I want to show only a few items at first and when the user clicks the last item ("More...") the spinner will change and show all items.
So the feature I need here, is a way to dynamically change spinner items without closing the spinner. I've managed to do everything but the last part. Every time I change the items the spinner automatically closes (without losing focus).
The only workaround I thought was to use mSpinner.performClick() to immediately open the spinner after it closes. Of course, that's not good enough because I get this quick close-reopen effect. Not cool.
I created a custom spinner class that manages the logic:
public class ReservationStatusSpinner extends Spinner {
// --------------------------------------------------
// State
// --------------------------------------------------
private final String mMoreStatus;
private OnItemSelectedListener mUserListener;
private ArrayAdapter<String> mAdapter;
private boolean mOpenInitiated = false;
// --------------------------------------------------
// Interfaces
// --------------------------------------------------
private interface OnSpinnerEventsListener {
// Not needed, but may be needed in the future -> void onSpinnerOpened();
void onSpinnerClosed();
}
private OnSpinnerEventsListener mOnSpinnerEventsListener;
public interface OnStatusSelectedListener {
void onStatusSelected(String status);
}
private OnStatusSelectedListener mOnStatusSelectedListener;
// --------------------------------------------------
// Construction/Initialization
// --------------------------------------------------
public ReservationStatusSpinner(Context context) {
super(context);
mMoreStatus = getContext().getResources().getString(R.string.status_more);
init();
}
public ReservationStatusSpinner(Context context, AttributeSet attrs) {
super(context, attrs);
mMoreStatus = getContext().getResources().getString(R.string.status_more);
init();
}
private void init() {
// Add listener
super.setOnItemSelectedListener(new OnItemSelected());
mOnSpinnerEventsListener = new OnSpinnerEventsListener() {
#Override
public void onSpinnerClosed() {
filterAndSelect();
}
};
}
// --------------------------------------------------
// Overridden methods
// --------------------------------------------------
#Override
public boolean performClick() {
// register that the Spinner was opened so we have a status
// indicator for the activity(which may lose focus for some other
// reasons)
mOpenInitiated = true;
return super.performClick();
}
#Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
// mSpin is our custom Spinner
if (mOpenInitiated && hasFocus) {
performClosedEvent();
}
}
#Override
public void setOnItemSelectedListener(OnItemSelectedListener l) {
mUserListener = l;
}
// --------------------------------------------------
// Private methods
// --------------------------------------------------
private static ArrayList<String> getAllStatuses(Context context) {
ArrayList<String> items = new ArrayList<>();
CharSequence[] statusesCSArray = context.getResources().getTextArray(R.array.reservation_status);
for (CharSequence cs : statusesCSArray)
items.add(cs.toString());
return items;
}
private void performClosedEvent() {
mOpenInitiated = false;
if (mOnSpinnerEventsListener != null) {
mOnSpinnerEventsListener.onSpinnerClosed();
}
}
private void filterAndSelect() {
List<String> items = filterStatuses((String)getSelectedItem(), mMoreStatus);
setItems(items);
setSelection(0);
}
// --------------------------------------------------
// Public methods
// --------------------------------------------------
public void setStatus(String status) {
// Find status in adapter
int pos = -1;
for (int i = 0; i < mAdapter.getCount(); ++i) {
if (mAdapter.getItem(i).equals(status)) {
pos = i;
break;
}
}
if (pos != -1)
setSelection(pos);
}
public void setAdapter(ArrayAdapter<String> adapter) {
super.setAdapter(adapter);
mAdapter = adapter;
}
public void setOnStatusSelectedListener(OnStatusSelectedListener l) {
mOnStatusSelectedListener = l;
}
public void setItems(List<String> items) {
mAdapter.clear();
mAdapter.addAll(items);
mAdapter.notifyDataSetChanged();
}
// --------------------------------------------------
// Utilities
// --------------------------------------------------
public static ArrayList<String> filterStatuses(String selectedStatus, String moreStatus) {
ArrayList<String> list = new ArrayList<>(DataUtilities.filterStatuses(selectedStatus));
// Add selected status at start
list.add(0, selectedStatus);
// Append "More"
list.add(moreStatus);
return list;
}
// --------------------------------------------------
// Custom ItemSelectedListener for ReservationStatusSpinner
// --------------------------------------------------
private class OnItemSelected implements OnItemSelectedListener {
private String mPreviousStatus;
private boolean mMoreClicked = false;
#Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
String more = getContext().getResources().getString(R.string.status_more);
String status = getSelectedItem().toString();
ArrayList<String> items = new ArrayList<>();
if (status.equals(more)) {
items.addAll(getAllStatuses(getContext()));
items.remove(mMoreStatus);
setItems(items);
//setStatus(mPreviousStatus);
mMoreClicked = true;
// Reopen spinner (it closes after changing data) (TODO: Fix this)
ReservationStatusSpinner.this.performClick();
} else if (!mMoreClicked) {
filterAndSelect();
}
if (!status.equals(more)) {
if (mUserListener != null)
mUserListener.onItemSelected(parent, view, position, id);
if (mOnStatusSelectedListener != null)
mOnStatusSelectedListener.onStatusSelected(status);
}
mPreviousStatus = status;
}
#Override
public void onNothingSelected(AdapterView<?> parent) {
if (mUserListener != null)
mUserListener.onNothingSelected(parent);
}
}
}
and a custom adapter:
public class ImageSpinnerAdapter extends ArrayAdapter<String> {
private LayoutInflater mInflater;
public ImageSpinnerAdapter(Context context, int textViewResourceId, List<String> titles) {
super(context, textViewResourceId, titles);
mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
#Override
public View getDropDownView(int position, View convertView, #NonNull ViewGroup parent) {
View view;
if (convertView == null) {
view = mInflater.inflate(R.layout.row_image_spinner_dropdown, parent, false);
} else {
view = convertView;
}
ImageView icon = (ImageView)view.findViewById(R.id.spinner_icon);
setIcon(icon, getItem(position));
TextView text = (TextView)view.findViewById(R.id.spinner_status_text);
text.setText(DataUtilities.addWhitespacesToStatus(getItem(position)));
view.setPadding(0, 0, 0, 0);
return view;
}
#NonNull
#Override
public View getView(int position, View convertView, #NonNull ViewGroup parent) {
View view;
if (convertView == null) {
view = mInflater.inflate(R.layout.row_image_spinner_view, parent, false);
} else {
view = convertView;
}
// Set icon
ImageView icon = (ImageView)view.findViewById(R.id.spinner_icon);
setIcon(icon, getItem(position));
view.setPadding(0, 0, 0, 0);
return view;
}
private void setIcon(ImageView icon, String status) {
// Make sure there are no whitespaces in status
status = DataUtilities.removeWhitespaceFromStatus(status);
// Get the correct image for each status
icon.setImageResource(DataUtilities.statusToIconResource(status));
}
}
In my Spinner, most of the work is done in the private class OnItemSelected at the end of the snippet.
At first I thought the problem was the convert view at my adapter (I wasn't using the convert view pattern at first) but as you can see I'm using it now.
The problem occurs on 2 different devices and my emulator so it's safe to assume that it is not a device specific problem.
Anyone have any ideas or any pointers?
You have to create custom dialog with MultiSelectListview and More button.On click of more button you have to add all elements to Listview and call notifyDataSetChanged() method.
Default Spinner can't simple way to load "More" items. But "More" button has no sense. If you have 30-50 items just load all to Spinner. For 50-150 items use own ListBox/RecyclerView based Spinner. If more 150 items user too hard search necessary one item. In last case useful to add "Search" functionality.
See MultiSelect Spinner for ideas.
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.
Background:
I'm attempting to create a custom ArrayAdapter for a custom ListView. The list template I'm using to form each row contains a few TextView columns and one Button.
I'm using a SQLite table as the source of the list.
Problem:
I want the Button to capture the current time and write it to my SQLite table [Results] in the column corresponding to the row in which the Button appears. I'm at a loss for how I would do that.
Here is the class that contains the ArrayList
public class ResultsMenu extends ActionBarActivity {
private static final String LOGTAG = "Logtag: " + Thread.currentThread()
.getStackTrace()[2].getClassName(); // log tag for records
// sql elements for selecting boats
private String where = DBAdapter.KEY_RACE_ID + " = " + GlobalContent.activeRace.getId()
+ " AND " + DBAdapter.KEY_RESULTS_VISIBLE + " = 1";
private String orderBy = DBAdapter.KEY_BOAT_CLASS + " DESC ";
//instance of data source
RaceDataSource raceDataSource;
ResultDataSource resultDataSource;
// make a listview instance
ListView myList;
// make button instance for capturing finish time
Button buttonCaptureFinishTime;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_results_menu);
//wire data source and open
raceDataSource = new RaceDataSource(this);
resultDataSource = new ResultDataSource(this);
raceDataSource.open();
resultDataSource.open();
// wire list view
myList = (ListView) findViewById(R.id.lvResultList);
//wire button
buttonCaptureFinishTime = (Button) findViewById(R.id.btn_finish_time);
//set onclick listening for listview
myList.setOnItemClickListener(new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
GlobalContent.setResultsRowID(id);
Intent intent = new Intent(view.getContext(), ResultsEditForm.class);
startActivity(intent);
}
});
}
#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_results_menu, menu);
return true;
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
//noinspection SimplifiableIfStatement
if (id == R.id.action_settings) {
return true;
}
return super.onOptionsItemSelected(item);
}
#Override
protected void onResume() {
super.onResume();
Log.i(LOGTAG, " onResume Now");
raceDataSource.open(); // reopen the db
resultDataSource.open(); // reopen the db
//populateListView(); // need to build this
}
#Override
protected void onPause() {
super.onPause();
Log.i(LOGTAG, " onPause NOW");
raceDataSource.close(); // close db to reduce data leak
resultDataSource.close(); // close db to reduce data leak
}
public void populateListView(){
}
}
ArrayAdapter so far
public class ResultsAdapter extends BaseAdapter {
Context mContext; // add context
LayoutInflater inflater; // instance of inflater
// lists of result
private ArrayList<Result> arraylist;
// instance constructor
public ResultsAdapter(Context context, ResultDataSource resultDataSource) {
mContext = context;
inflater = LayoutInflater.from(mContext);
this.arraylist = new ArrayList<Result>();
}
/**
* How many items are in the data set represented by this Adapter.
*
* #return Count of items.
*/
#Override
public int getCount() {
return arraylist.size();
}
#Override
public Object getItem(int position) {
return arraylist.get(position);
}
#Override
public long getItemId(int position) {
return position;
}
#Override
public View getView( int index, View view, final ViewGroup parent) {
if (view == null) {
// build inflater to create a new row for each row in the Results table
LayoutInflater inflater = LayoutInflater.from(parent.getContext());
view = inflater.inflate(R.layout.activity_list_template_results, parent, false);
}
final Result result = arraylist.get(index);
Button btn = (Button) view.findViewById(R.id.btn_finish_time); // instance of button
// wire text views and set the associated text to them.
TextView tv = (TextView) view.findViewById(R.id.txt_hd_results_ID);
tv.setText(result.getResultsId() + "");
tv = (TextView) view.findViewById(R.id.txt_hd_results_race_id);
tv.setText(result.getResultsRaceId() + "");
tv = (TextView) view.findViewById(R.id.txt_hd_results_boat_id);
tv.setText(result.getResultsBoatId() + "");
tv = (TextView) view.findViewById(R.id.txt_hd_results_Visible);
tv.setText(result.getResultsVisible() + "");
tv = (TextView) view.findViewById(R.id.txt_hd_results_Name);
tv.setText(result.getBoatName() + "");
tv = (TextView) view.findViewById(R.id.txt_hd_results_Class);
tv.setText(result.getBoatClass() + "");
tv = (TextView) view.findViewById(R.id.txt_hd_results_SailNum);
tv.setText(result.getBoatSailNum() + "");
// set the function of each finish button
btn.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
LocalTime localTime = new LocalTime();// capture the current time
// TODO: Add SQLite statement to insert local time into Results table
}
});
return view;
}
}
Inside the onClick method you can create an instance of your database, open it and update the data accordingly.
DatabaseConnection database = new DatabaseConnection(context);
database.getWritableDatabase();
How does one go about manually expanding and collapsing an expandablelistview? I know of expandGroup(), but am not sure where to set the onClickListener(), as half of this code, is in a separate library project.
ExpandableDeliveryList
package com.goosesys.dta_pta_test;
[imports removed to save space]
public class ExpandableDeliveryList<T> extends ExpandableListActivity {
private ArrayList<GooseDeliveryItem> parentItems = new ArrayList<GooseDeliveryItem>();
private ArrayList<DeliverySiteExtras> childItems = new ArrayList<DeliverySiteExtras>();
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// CREATE THE EXPANDABLE LIST AND SET PROPERTIES //
final ExpandableListView expandList = getExpandableListView();
expandList.setDividerHeight(0);
expandList.setGroupIndicator(null);
expandList.setClickable(false);
// LIST OF PARENTS //
setGroupParents();
// CHILDREN //
setChildData();
// CREATE ADAPTER //
GooseExpandableArrayAdapter<?> adapter = new GooseExpandableArrayAdapter<Object>(
R.layout.goose_delivery_item,
R.layout.goose_delivery_item_child,
parentItems,
childItems);
adapter.setInflater((LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE), this);
expandList.setAdapter(adapter);
expandList.setOnChildClickListener(this);
}
#Override
public boolean onOptionsItemSelected(MenuItem item)
{
switch(item.getItemId())
{
case android.R.id.home:
{
Intent intent = new Intent(this, Main.class);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
startActivity(intent);
return true;
}
default:
{
return super.onOptionsItemSelected(item);
}
}
}
public void setGroupParents()
{
DatabaseHelper dbHelper = new DatabaseHelper(this);
List<DeliverySite> sites = new ArrayList<DeliverySite>();
sites = dbHelper.getAllSites();
GooseDeliveryItem[] deliveries = new GooseDeliveryItem[sites.size()];
for(int i=0; i<sites.size(); i++)
{
Delivery del = new Delivery();
try
{
del = dbHelper.getDeliveryByJobNo(sites.get(i).id);
}
catch(Exception e)
{
e.printStackTrace();
}
final GooseDeliveryItem gdi;
if((Double.isNaN(sites.get(i).lat)) || (Double.isNaN(sites.get(i).lng)))
{
gdi = new GooseDeliveryItem(sites.get(i).id, sites.get(i).company);
}
else
{
gdi = new GooseDeliveryItem(sites.get(i).id, sites.get(i).company, sites.get(i).lat, sites.get(i).lng);
}
if(del.getReportedFully() == 1)
{
gdi.isReportedFully = true;
}
deliveries[i] = gdi;
}
// FINALLY ADD THESE ITEMS TO THE PARENT ITEMS LIST ARRAY //
for(GooseDeliveryItem g : deliveries)
parentItems.add(g);
}
public void setChildData()
{
//DatabaseHelper dbHelper = new DatabaseHelper(this);
ArrayList<DeliverySiteExtras> extras = new ArrayList<DeliverySiteExtras>();
for(int i=0; i<parentItems.size(); i++)
{
DeliverySiteExtras dse = new DeliverySiteExtras();
extras.add(dse);
}
childItems = extras;
}
}
ArrayAdapter
package com.goosesys.gooselib.Views;
[imports removed to save space]
public class GooseExpandableArrayAdapter<Object> extends BaseExpandableListAdapter
{
private Activity activity;
private ArrayList<DeliverySiteExtras> childItems;
private LayoutInflater inflater;
ArrayList<GooseDeliveryItem> parentItems;
private DeliverySiteExtras child;
private int layoutId;
private int childLayoutId;
public GooseExpandableArrayAdapter(int layoutId, int childLayoutId, ArrayList<GooseDeliveryItem> parents, ArrayList<DeliverySiteExtras> children)
{
this.layoutId = layoutId;
this.childLayoutId = childLayoutId;
this.parentItems = (ArrayList<GooseDeliveryItem>) parents;
this.childItems = (ArrayList<DeliverySiteExtras>)children;
}
public GooseExpandableArrayAdapter(ArrayList<GooseDeliveryItem> parents, ArrayList<DeliverySiteExtras> children, int layoutId)
{
this.parentItems = parents;
this.childItems = children;
this.layoutId = layoutId;
}
public void setInflater(LayoutInflater inflater, Activity activity)
{
this.inflater = inflater;
this.activity = activity;
}
#Override
public Object getChild(int arg0, int arg1)
{
return null;
}
#Override
public long getChildId(int arg0, int arg1)
{
return 0;
}
/*
* Child view get method
* Utilise this to edit view properties at run time
*/
#Override
public View getChildView(int groupPosition, int childPosition, boolean isLastChild, View convertView, ViewGroup parent)
{
child = childItems.get(groupPosition);
if(convertView == null)
{
convertView = inflater.inflate(this.childLayoutId, null);
}
// GET ALL THE OBJECT VIEWS AND SET THEM HERE //
setGeoLocation(groupPosition, convertView);
convertView.setOnClickListener(new View.OnClickListener()
{
#Override
public void onClick(View arg0)
{
}
});
return convertView;
}
#Override
public void onGroupCollapsed(int groupPosition)
{
super.onGroupCollapsed(groupPosition);
}
#Override
public void onGroupExpanded(int groupPosition)
{
super.onGroupExpanded(groupPosition);
}
#Override
public int getChildrenCount(int groupPosition)
{
return 1; //childItems.get(groupPosition);
}
#Override
public Object getGroup(int groupPosition)
{
return null;
}
#Override
public int getGroupCount()
{
return parentItems.size();
}
#Override
public long getGroupId(int arg0)
{
return 0;
}
/*
* Parent View Object get method
* Utilise this to edit view properties at run time.
*/
#Override
public View getGroupView(final int groupPosition, boolean isExpanded, View convertView, ViewGroup parent)
{
if(convertView == null)
{
convertView = inflater.inflate(this.layoutId, null);
}
// GET ALL OBJECT VIEWS AND SET THEM HERE -- PARENT VIEW //
TextView name = (TextView)convertView.findViewById(R.id.customerName);
name.setText(parentItems.get(groupPosition).customerText);
ImageView go = (ImageView)convertView.findViewById(R.id.moreDetails);
go.setOnClickListener(new View.OnClickListener()
{
#Override
public void onClick(View v)
{
Intent i = new Intent(activity, DeliveryJobActivity.class);
i.putExtra("obj", parentItems.get(groupPosition));
activity.startActivity(i);
}
});
return convertView;
}
#Override
public boolean hasStableIds()
{
return false;
}
#Override
public boolean isChildSelectable(int arg0, int arg1)
{
return false;
}
private void setGeoLocation(final int groupPosition, View parent)
{
GeoLocation geoLocation = new GeoLocation(activity);
final double lat = geoLocation.getLatitude();
final double lng = geoLocation.getLongitude();
// GET OUR START LOCATION //
Location startLocation = new Location("Start");
startLocation.setLatitude(lat);
startLocation.setLongitude(lng);
// GET OUR DESTINATION //
Location destination = new Location("End");
destination.setLatitude(((GooseDeliveryItem)parentItems.get(groupPosition)).latitude);
destination.setLongitude(((GooseDeliveryItem)parentItems.get(groupPosition)).longitude);
double distanceValue = startLocation.distanceTo(destination);
TextView tv = (TextView)parent.findViewById(R.id.extraHeader);
tv.setText(parentItems.get(groupPosition).customerText + " information:");
TextView ds = (TextView)parent.findViewById(R.id.deliveryDistance);
ds.setText("Distance (from location): " + String.valueOf(Math.ceil(distanceValue * GooseConsts.METERS_TO_A_MILE)) + " Mi (approx)");
ImageView img = (ImageView)parent.findViewById(R.id.directionsImage);
img.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View arg0) {
// invoke google maps with lat / lng position
Intent navigation = new Intent(Intent.ACTION_VIEW, Uri.parse(
"http://maps.google.com/maps?saddr="
+ (lat) + "," + (lng)
+ "&daddr="
+ ((GooseDeliveryItem)parentItems.get(groupPosition)).latitude + ","
+ ((GooseDeliveryItem)parentItems.get(groupPosition)).longitude
));
activity.startActivity(navigation);
}
});
}
}
Ideally, my bosses would like to have a "+" button, that when clicked expands the listview manually. Rather than clicking anywhere on the view and it doing it automatically. Is this possible? Also, setting setClickable(false) seems to have no effect. Because it'll still expand when any list item is clicked. Am I missing something there also?
Cheers.
You can add an ExpandableListView Group Click Listener ("ExpandableListView::setOnGroupClickListener") to monitor and suppress click event for the ListView Groups. Using your code example, this would be done in your "ExpandableDeliveryList" module after you create the ExpandableListView.
Then in your "+" (and "-") Button click handlers, you can add logic to expand/collapse some or all of the ListView Groups using the "ExpandableListView::expandGroup()" and "ExpandableListView::collapseGroup()" methods.
You do not need to return "false" from the overridden "isChildSelectable()" method as this has no effect on what you are trying to accomplish (and will prevent anyone from clicking/selecting child items in the ListView).
Code examples are shown below:
// CREATE THE EXPANDABLE LIST AND SET PROPERTIES //
final ExpandableListView expandList = getExpandableListView();
//...
expandList.setOnGroupClickListener(new android.widget.ExpandableListView.OnGroupClickListener() {
#Override
public boolean onGroupClick( ExpandableListView parent,
View view,
int groupPosition,
long id) {
// some code...
// return "true" to consume the event (and prevent the Group from expanding/collapsing) / "false" to allow the Group to expand/collapse normally
return true;
}
});
To manually expand and collapse the ListView Groups:
// enumerate thru the ExpandableListView Groups
// [in this code example, a single index is used...]
int groupPosition = 0;
// expand the ListView Group/s
if (m_expandableListView.isGroupExpanded(groupPosition) == false) {
// API Level 14+ allows you to animate the Group expansion...
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
m_expandableListView.expandGroup(groupPosition, true);
}
// else just expand the Group without animation
else {
m_expandableListView.expandGroup(groupPosition);
}
}
// collapse the ListView Group/s
else {
m_expandableListView.collapseGroup(groupPosition);
}
Hope this Helps.
I'm having a trouble for using a 'baseAdapter' to fill a 'fragmentList'.
I'm trying to show a list with all the musics stored in my device using 'MediaStore.Audio.Media', but the problem is when the program calls the function setListAdapter. If the number of items into the baseAdapter is a little large, only a part of the list is correctly filled.
The baseAdapter code:
public class MListAdapter extends BaseAdapter {
public static final Integer KEY_LAYOUT_TITLE = 0;
public static final Integer KEY_LAYOUT_SUBTITLE = 1;
public static final Integer KEY_LAYOUT_OTHERS = 2;
public static final Integer KEY_LAYOUT_IMAGE_ID = 3;
public static final Integer KEY_LAYOUT_LIST = 4;
private ArrayList<String> arrayString;
private LayoutInflater lInflater = null;
private Context context;
public MListAdapter(Context ctx, ArrayList<String> arrString){
context = ctx;
arrayString = arrString;
lInflater = LayoutInflater.from(ctx);
}
public int getCount() {
return arrayString.size();
}
public String getItem(int position) {
return arrayString.get(position);
}
public long getItemId(int position) {
return position;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View mView = convertView;
if (convertView == null) {
mView = lInflater.inflate(R.layout.frag_music_list, null);
TextView mTitle = (TextView) mView.findViewById(R.id.musicNameTextView);
//TextView mSubtitle = (TextView) mView.findViewById(R.id.musicArtistAlbumTextView);
//TextView mOthers = (TextView) mView.findViewById(R.id.musicDurationTextView);
//ImageView mImage = (ImageView) mView.findViewById(R.id.thumbImageView);
mTitle.setText(getItem(position));
Log.d("DEBUG",String.valueOf(position));
Log.d("DEBUG",String.valueOf(getCount()));
//mSubtitle.setText(hashItem.get(KEY_LAYOUT_SUBTITLE));
//mOthers.setText(hashItem.get(KEY_LAYOUT_OTHERS));
}
return mView;
}
The FragmentList:
public class MListFragment extends ListFragment {
MListFragmentListener mCallback;
InterfaceFragmentMusic typeMusicCallback;
// --- Global Variables
static ArrayList<HashMap<Integer, String>> mapString = null;
static ArrayList<HashMap<Integer, Long>> mapImage = null;
// ---The URIs used to get a group of music and its informations
Uri uriMedias = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
/**
* ---The following vectors of strings are used to choose what kind of
* information will be retrieved from the database in each case (the
* columns)
*/
final String[] columnsMedias = {
MediaStore.Audio.Media.DISPLAY_NAME,
MediaStore.Audio.Media.ALBUM_ID,
MediaStore.Audio.Media.ARTIST,
MediaStore.Audio.Media.ALBUM,
MediaStore.Audio.Media.DURATION
};
// The container Activity must implement this interface so the frag can
// deliver messages
public interface MListFragmentListener {
/**
* Called by MListFragment when a list item is selected It has been
* implemented in the FragMusicActivity class!
**/
public void onMusicSelected(String musicName);
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// The system initially shows the list with all the musics
updateMList(MusicTypeFragment.KEY_POSITION_ALLSONGS);
}
#Override
public void onStart() {
super.onStart();
// When in two-pane layout, set the listview to highlight the selected
// list item
// (We do this during onStart because at the point the listview is
// available.)
//if (getFragmentManager().findFragmentById(R.id.music_fragment) != null) {
// getListView().setChoiceMode(ListView.CHOICE_MODE_SINGLE);
//}
}
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
// This makes sure that the container activity has implemented
// the callback interface. If not, it throws an exception.
try {
mCallback = (MListFragmentListener) activity;
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString()
+ " must implement MListFragmentListener");
}
}
#Override
public void onListItemClick(ListView l, View v, int position, long id) {
// Set the item as checked to be highlighted when in two-pane layout
getListView().setItemChecked(position, true);
}
/**
* This fragment will be updated/refreshed whatever the user choose a music
* option on the menu (on the left side)
**/
/* It refreshes/updates the current list with a new type */
public void updateMList(int position) {
Cursor cursor;
MListAdapter mAdapter = null;
ContentResolver cr = getActivity().getContentResolver();
cursor = cr.query(uriMedias, columnsMedias, null, null, null);
ArrayList<String> arrString = new ArrayList<String>();
populateMap(cursor, arrString);
mAdapter = new MListAdapter(getActivity(),arrString);
int a = mAdapter.getCount();
for (int i = 0; i < a; i++) {
Log.d("MLISTFRAG", mAdapter.getItem(i));
}
this.setListAdapter(mAdapter);
cursor.close();
}
/*
* It populates an arrayList with the information about the musics using the
* data passed by a cursor
*/
private void populateMap(Cursor c, ArrayList<HashMap<Integer, String>> array, ArrayList<String> arrString) {
Cursor mCursor = c;
while (mCursor.moveToNext()) {
// creating new HashMap
HashMap<Integer, String> map = new HashMap<Integer, String>();
// Values by default
//map.put(MListAdapter.KEY_LAYOUT_TITLE,
// getString(R.string.inBlank));
//map.put(MListAdapter.KEY_LAYOUT_SUBTITLE,
// getString(R.string.inBlank));
//map.put(MListAdapter.KEY_LAYOUT_OTHERS,
// getString(R.string.inBlank));
// New values
map.put(MListAdapter.KEY_LAYOUT_TITLE,
mCursor.getString(MListAdapter.KEY_LAYOUT_TITLE));
arrString.add(mCursor.getString(MListAdapter.KEY_LAYOUT_TITLE));
array.add(map);
}
mCursor.close();
}
The results of these codes are that it's showed a list partially correct, I mean the first half of the list is okay, but the second half is the repetition of the first part.
I put a Log.d into the getView (in the baseAdapter) to verify if the the size of the array is correct and how many times the setListAdapter calls this method and the results (only to exemplify) are:
--> size = 30
--> how many times the method is called: 16
Thanks for your help!
You do not recycle the view properly.
In the getView method you only affect the TextView when the convertView is null. You need to update the TextView every time. Like this :
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View mView = convertView;
if (mView == null) {
mView = lInflater.inflate(R.layout.frag_music_list, null);
}
TextView mTitle = (TextView) mView.findViewById(R.id.musicNameTextView);
mTitle.setText(getItem(position));
return mView;
}