I am using the Expanding-Collection-Library which uses the method
public void instantiateCard(LayoutInflater inflaterService, final ViewGroup head, ListView list, ECCardData data)
to instantiate all the views of inside the head ViewGroup.
I change the visibility of a few views inside this method, depending, on whether the card is fully expanded or collapsed.
I want to collapse the cards in the activity's onBackPressed(), which works fine, but I cannot change the visibility of the child views of the head ViewGroup.
I tried accessing the views by declaring class variables inside MainActivity which are then set in instantiateCard() and though I get no errors that the view cannot be found nothing happens on visibility change.
The same when trying to define separate variables inside the activity and instantiateCard and setting them independently.
#Override
public void instantiateCard(LayoutInflater inflaterService, final ViewGroup head, ListView list, ECCardData data) {
final LinearLayout headerButtonContainer = head.findViewById(R.id.headerButtonContainer);
final LinearLayout nameContainer = head.findViewById(R.id.nameContainer);
final TextView tvAailablePlacesHead = head.findViewById(R.id.tvAvailablePlacesHead);
final TextView tvNameHead = head.findViewById(R.id.tvNameHead);
// here I also tried setting the activities variables like
// tvAcNameHead = tvNameHead;
// since just decalring a field variable and using it like
// nameHeadFieldVariable = head.findViewById(R.id.tvNameHead);
// would lead to not being able to change the visibility here either
// Card toggling by click on head element
head.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(final View v) {
ecPagerView.toggle();
if (headerButtonContainer.getVisibility() == View.VISIBLE) {
setDetailOverlayItemsVisible(headerButtonContainer, nameContainer, tvNameHead, tvAailablePlacesHead);
} else {
setDetailOverlayItemsInvisible(headerButtonContainer, nameContainer, tvNameHead, tvAailablePlacesHead);
}
}
});
}
It is possible to get a list of all the currently displayed/on-screen android UI elements?
For example, if I have a app that look like this:
I would get a list that would contain:
TextView (Hello World!)
RelativeLayout (Or whatever the parent container is)
Etc if there were more elements
This would be great in the case I don't know the ID's, or even what UI elements will appear on screen, but I still want to hide/show them.
You can achieve this by using the following manager, nice and clean!
Use it anywhere you want and you'll get a list of the view and all its children.
(Needs to be done recursively)
LayoutManager.getViews(getWindow());
public class LayoutManager
{
private static List<View> views;
public static List<View> getViews(Window window) {
return getViews(window.getDecorView().getRootView(), true);
}
public static List<View> getViews(final View view, boolean starting)
{
if (starting) {
views = new ArrayList<>();
}
views.add(view);
/** Search in each ViewGroup children as well */
if (view instanceof ViewGroup) {
ViewGroup viewGroup = (ViewGroup) view;
for (int i = 0; i < viewGroup.getChildCount(); i++) {
getViews(viewGroup.getChildAt(i), false);
}
}
return views;
}
}
I am writing a new Android activity and am having trouble with a ListFragment. The idea here is that the parent activity hosts a ListFragment whose data is pulled from the database using an adapter. When the contextual action bar (CAB) is invoked, a CheckBox item (which is part of the layout for each row in the ListView hosted by the ListFragment) becomes visible. The CheckBox items are not visible by default. The CheckBox items are checked inside my MultiChoiceModeListener classes onItemCheckedStateChanged() method.
My goal is to preserve the state of the checkboxes when the screen orientation changes.
When the orientation changes with the CAB activated, the CheckBox items in each row of the ListView (hosted by ListFragment) become invisible. I presume this is because the ListFragment is redrawn via the onCreateView() callback.
However, my problem is that when trying to re-enable the checkboxes I am getting a NULL pointer exception. When is it appropriate to access the row view elements from the fragment? If I wait very long (i.e, re-enable inside onItemCheckedStateChanged(), which is not ideal because I dont want to have to re-click everything!), the checkbox can be accessed and set visible. If I try to access this from the fragment earlier (for example in onCreateView() or onViewCreated()) the checkbox item is NULL but the rest of the row view is not null.
Any suggestions? Here is some code:
The Null Pointer Exception is being being thrown inside restoreSelectedCheckBoxes(). The lines are identified below.
From parent Activity
// In the parent activity. Called from onCreate()
private void displayListViewTEMP() {
// add fragment to apropriate layout item, but do not overwrite!
FragmentTransaction fTrans;
FragmentManager fMan = getFragmentManager();
// fragContainer is null until something is added to it
if (fMan.findFragmentByTag(Activity_Home.NAME_LIST_FRAG_TAG) == null) {
Log.i(TAG, "Adding fragment to feed_list_container");
fTrans = fMan.beginTransaction();
fTrans.add(R.id.feed_list_container, new Name_List(), Activity_Home.NAME_LIST_FRAG_TAG);
fTrans.commit();
}
}
From ListFragment
// this is the method I call to restore the checkboxes
public void restoreSelectedCheckBoxes() {
Log.i(TAG, "restoring selected button state");
ListView v = getListView();
// this variable holds the indices of selected items in the list
int selCnt = selectedListItems.size();
CheckBox bx;
for (int i = 0; i < selCnt; i++) {
Log.i(TAG, "v = " + v); <-- prints valid oref
bx = (CheckBox) v.findViewById(R.id.item_checkbox);
Log.i(TAG, "bx = " + bx); <-- prints 'null' after orientation change
bx.setChecked(true); <-- NULL pointer exception
}
}
And here is the data I use to set up the CAB:
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
// omitted code to set up adapter
ListView listView = getListView();
listView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE_MODAL);
listView.setMultiChoiceModeListener(getChoiceListener());
}
// set up contextual action bar
private MultiChoiceModeListener getChoiceListener() {
if (this.choiceListener != null)
return this.choiceListener;
this.choiceListener = new MultiChoiceModeListener() {
#Override
// Inflate the menu for the CAB
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
Log.i(TAG, "Creating contextual action bar");
MenuInflater inflater = mode.getMenuInflater();
inflater.inflate(R.menu.main_name_context, menu);
ListView v = getListView();
int vCnt = v.getCount();
View child;
CheckBox bx;
// set checkbox subviews as visible
for (int i = 0; i < vCnt; i++) {
child = v.getChildAt(i);
if (child == null)
continue;
bx = (CheckBox) child.findViewById(R.id.item_checkbox);
bx.setVisibility(View.VISIBLE);
}
return true;
}
// omitted other overridden methods that aren't relevant
};
return this.choiceListener;
}
*Thank you so much for the help! The solution was indeed to override the adapter as suggested. See the final code: *
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View v = super.getView(position, convertView, parent);
if (selectedListItems.contains(position)) {
CheckBox bx = (CheckBox) v.findViewById(R.id.item_checkbox);
bx.setVisibility(View.VISIBLE);
bx.setChecked(true);
}
return v;
}
What kind of adapter are you using for your ListFragment?
I would create an adapter class by extending ArrayAdapter (or whatever you're using now) and add the state restoration logic to the adapter.getView() method rather than trying to muck with waiting until your views are done with layout, etc.
See this post if you aren't sure how to extend the ArrayAdapter: How can I make my ArrayAdapter follow the ViewHolder pattern?
Try this:
getView().post(new Runnable() {
#Override
public void run() {
// code you want to run when view is visible/inflated for the first time
}
}
);
Okay, bear with me this is a very odd & complex issue and I'm having a hard time explaining it. I'm using Xamarin.Android (Monodroid) although the source of the issue is likely an android thing.
I have an Activity which pages through Fragments manually by adding and removing them.
next = Fragments[nextIndex];
FragmentTransaction ftx = FragmentManager.BeginTransaction ();
ftx.SetTransition (FragmentTransit.FragmentFade);
ftx.SetCustomAnimations (Resource.Animator.slide_in_right, Resource.Animator.slide_out_left);
ftx.Remove (CurrentFragment);
ftx.Add (Resource.Id.fragmentContainer, next, next.Tag);
ftx.Commit();
The Fragments have a LinearLayout, which is populated with Row Views at run-time. (ListViews introduced too many focus issues with validating text entry).
// Manual ListView
this.Layout.RemoveAllViewsInLayout ();
for (int n = 0; n < Adapter.Count; n++)
{
View view = Adapter.GetView(n,null,Layout);
if (view != null) {
if (view.Parent != Layout) {
Layout.AddView(view);
}
}
}
Some of these Row Views have within them a GridView. (The gridview does not scroll)
TextView title = view.FindViewById<TextView>(Resource.Id.titleView);
GridView grid = view.FindViewById<GridView>(Resource.Id.gridView);
if (grid.Adapter == null) {
InlineAdapter adapter = new InlineAdapter(view.Context, list, this.Adapter);
// Set Grid's parameters
grid.Adapter = adapter;
grid.OnItemClickListener = adapter;
grid.SetNumColumns(adapter.GetColumns(((Activity)view.Context).WindowManager));
grid.StretchMode = StretchMode.StretchColumnWidth;
}
grid.ClearChoices();
// Get Current Value(s)
if (list.Mode == ListMode.SingleSelection)
{
grid.ChoiceMode = ChoiceMode.Single;
for (int b = 0; b < list.Selections.AllItems.Count; b++)
{
grid.SetItemChecked(b, false);
}
grid.SetItemChecked(list.GetSelectedIndex(DataStore), true);
}
The grid views have in them CheckedTextViews (either single or multiple choice).
// From "InlineAdapter"
public override View GetView(int position, View convertView, ViewGroup parent)
{
GridView grid = ((GridView)parent);
int layout = (list.Mode == ListMode.MultiSelection) ? Resource.Layout.RowListInclusive : Resource.Layout.RowListExclusive;
CheckedTextView view = (CheckedTextView)convertView;
if (view == null)
{
view = (CheckedTextView)inflator.Inflate(layout, parent, false);
}
view.Text = items[position];
// Calabash
view.ContentDescription = list.ContentDescription + "." + Selections.ContentDescription(items [position]);
return view;
}
When a fragment is presented the first time (before it is removed) everything performs as expected.
When a fragment is presented the second and subsequent times (after it is removed and re-added) only the last grid view accepts user input. In addition, whatever is selected on this last grid view ALL of the grid views select. (e.g. if the last one selects choice 2, then all of the grid views change to selecting choice 2)
I have:
Verified that the underlying data is correct
Verified using .GetHash() that all of the CheckedTextViews, GridViews, and Adapters are unique for their given rows.
Verified that touches propagate to the correct CheckedTextViews, and modify the correct data.
Verified that NotifyDataSetChanged() is called for the correct GridView.
I am personally stumped to gump on this one.
I have a ListView which displays news items. They contain an image, a title and some text. The image is loaded in a separate thread (with a queue and all) and when the image is downloaded, I now call notifyDataSetChanged() on the list adapter to update the image. This works, but getView() is getting called too frequently, since notifyDataSetChanged() calls getView() for all visible items. I want to update just the single item in the list. How would I do this?
Problems I have with my current approach are:
Scrolling is slow
I have a fade-in animation on the image which happens every time a single new image in the list is loaded.
I found the answer, thanks to your information Michelle.
You can indeed get the right view using View#getChildAt(int index). The catch is that it starts counting from the first visible item. In fact, you can only get the visible items. You solve this with ListView#getFirstVisiblePosition().
Example:
private void updateView(int index){
View v = yourListView.getChildAt(index -
yourListView.getFirstVisiblePosition());
if(v == null)
return;
TextView someText = (TextView) v.findViewById(R.id.sometextview);
someText.setText("Hi! I updated you manually!");
}
This question has been asked at the Google I/O 2010, you can watch it here:
The world of ListView, time 52:30
Basically what Romain Guy explains is to call getChildAt(int) on the ListView to get the view and (I think) call getFirstVisiblePosition() to find out the correlation between position and index.
Romain also points to the project called Shelves as an example, I think he might mean the method ShelvesActivity.updateBookCovers(), but I can't find the call of getFirstVisiblePosition().
AWESOME UPDATES COMING:
The RecyclerView will fix this in the near future. As pointed out on http://www.grokkingandroid.com/first-glance-androids-recyclerview/, you will be able to call methods to exactly specify the change, such as:
void notifyItemInserted(int position)
void notifyItemRemoved(int position)
void notifyItemChanged(int position)
Also, everyone will want to use the new views based on RecyclerView because they will be rewarded with nicely-looking animations! The future looks awesome! :-)
This is how I did it:
Your items (rows) must have unique ids so you can update them later. Set the tag of every view when the list is getting the view from adapter. (You can also use key tag if the default tag is used somewhere else)
#Override
public View getView(int position, View convertView, ViewGroup parent)
{
View view = super.getView(position, convertView, parent);
view.setTag(getItemId(position));
return view;
}
For the update check every element of list, if a view with given id is there it's visible so we perform the update.
private void update(long id)
{
int c = list.getChildCount();
for (int i = 0; i < c; i++)
{
View view = list.getChildAt(i);
if ((Long)view.getTag() == id)
{
// update view
}
}
}
It's actually easier than other methods and better when you dealing with ids not positions! Also you must call update for items which get visible.
get the model class first as global like this model class object
SampleModel golbalmodel=new SchedulerModel();
and initialise it to global
get the current row of the view by the model by initialising the it to global model
SampleModel data = (SchedulerModel) sampleList.get(position);
golbalmodel=data;
set the changed value to global model object method to be set and add the notifyDataSetChanged its works for me
golbalmodel.setStartandenddate(changedate);
notifyDataSetChanged();
Here is a related question on this with good answers.
The answers are clear and correct, I'll add an idea for CursorAdapter case here.
If youre subclassing CursorAdapter (or ResourceCursorAdapter, or SimpleCursorAdapter), then you get to either implement ViewBinder or override bindView() and newView() methods, these don't receive current list item index in arguments. Therefore, when some data arrives and you want to update relevant visible list items, how do you know their indices?
My workaround was to:
keep a list of all created list item views, add items to this list from newView()
when data arrives, iterate them and see which one needs updating--better than doing notifyDatasetChanged() and refreshing all of them
Due to view recycling the number of view references I'll need to store and iterate will be roughly equal the number of list items visible on screen.
int wantedPosition = 25; // Whatever position you're looking for
int firstPosition = linearLayoutManager.findFirstVisibleItemPosition(); // This is the same as child #0
int wantedChild = wantedPosition - firstPosition;
if (wantedChild < 0 || wantedChild >= linearLayoutManager.getChildCount()) {
Log.w(TAG, "Unable to get view for desired position, because it's not being displayed on screen.");
return;
}
View wantedView = linearLayoutManager.getChildAt(wantedChild);
mlayoutOver =(LinearLayout)wantedView.findViewById(R.id.layout_over);
mlayoutPopup = (LinearLayout)wantedView.findViewById(R.id.layout_popup);
mlayoutOver.setVisibility(View.INVISIBLE);
mlayoutPopup.setVisibility(View.VISIBLE);
For RecycleView please use this code
I used the code that provided Erik, works great, but i have a complex custom adapter for my listview and i was confronted with twice implementation of the code that updates the UI. I've tried to get the new view from my adapters getView method(the arraylist that holds the listview data has allready been updated/changed):
View cell = lvOptim.getChildAt(index - lvOptim.getFirstVisiblePosition());
if(cell!=null){
cell = adapter.getView(index, cell, lvOptim); //public View getView(final int position, View convertView, ViewGroup parent)
cell.startAnimation(animationLeftIn());
}
It's working well, but i dont know if this is a good practice.
So i don't need to implement the code that updates the list item two times.
exactly I used this
private void updateSetTopState(int index) {
View v = listview.getChildAt(index -
listview.getFirstVisiblePosition()+listview.getHeaderViewsCount());
if(v == null)
return;
TextView aa = (TextView) v.findViewById(R.id.aa);
aa.setVisibility(View.VISIBLE);
}
I made up another solution, like RecyclyerView method void notifyItemChanged(int position), create CustomBaseAdapter class just like this:
public abstract class CustomBaseAdapter implements ListAdapter, SpinnerAdapter {
private final CustomDataSetObservable mDataSetObservable = new CustomDataSetObservable();
public boolean hasStableIds() {
return false;
}
public void registerDataSetObserver(DataSetObserver observer) {
mDataSetObservable.registerObserver(observer);
}
public void unregisterDataSetObserver(DataSetObserver observer) {
mDataSetObservable.unregisterObserver(observer);
}
public void notifyDataSetChanged() {
mDataSetObservable.notifyChanged();
}
public void notifyItemChanged(int position) {
mDataSetObservable.notifyItemChanged(position);
}
public void notifyDataSetInvalidated() {
mDataSetObservable.notifyInvalidated();
}
public boolean areAllItemsEnabled() {
return true;
}
public boolean isEnabled(int position) {
return true;
}
public View getDropDownView(int position, View convertView, ViewGroup parent) {
return getView(position, convertView, parent);
}
public int getItemViewType(int position) {
return 0;
}
public int getViewTypeCount() {
return 1;
}
public boolean isEmpty() {
return getCount() == 0;
} {
}
}
Don't forget to create a CustomDataSetObservable class too for mDataSetObservable variable in CustomAdapterClass, like this:
public class CustomDataSetObservable extends Observable<DataSetObserver> {
public void notifyChanged() {
synchronized(mObservers) {
// since onChanged() is implemented by the app, it could do anything, including
// removing itself from {#link mObservers} - and that could cause problems if
// an iterator is used on the ArrayList {#link mObservers}.
// to avoid such problems, just march thru the list in the reverse order.
for (int i = mObservers.size() - 1; i >= 0; i--) {
mObservers.get(i).onChanged();
}
}
}
public void notifyInvalidated() {
synchronized (mObservers) {
for (int i = mObservers.size() - 1; i >= 0; i--) {
mObservers.get(i).onInvalidated();
}
}
}
public void notifyItemChanged(int position) {
synchronized(mObservers) {
// since onChanged() is implemented by the app, it could do anything, including
// removing itself from {#link mObservers} - and that could cause problems if
// an iterator is used on the ArrayList {#link mObservers}.
// to avoid such problems, just march thru the list in the reverse order.
mObservers.get(position).onChanged();
}
}
}
on class CustomBaseAdapter there is a method notifyItemChanged(int position), and you can call that method when you want update a row wherever you want (from button click or anywhere you want call that method). And voila!, your single row will update instantly..
My solution:
If it is correct*, update the data and viewable items without re-drawing the whole list. Else notifyDataSetChanged.
Correct - oldData size == new data size, and old data IDs and their order == new data IDs and order
How:
/**
* A View can only be used (visible) once. This class creates a map from int (position) to view, where the mapping
* is one-to-one and on.
*
*/
private static class UniqueValueSparseArray extends SparseArray<View> {
private final HashMap<View,Integer> m_valueToKey = new HashMap<View,Integer>();
#Override
public void put(int key, View value) {
final Integer previousKey = m_valueToKey.put(value,key);
if(null != previousKey) {
remove(previousKey);//re-mapping
}
super.put(key, value);
}
}
#Override
public void setData(final List<? extends DBObject> data) {
// TODO Implement 'smarter' logic, for replacing just part of the data?
if (data == m_data) return;
List<? extends DBObject> oldData = m_data;
m_data = null == data ? Collections.EMPTY_LIST : data;
if (!updateExistingViews(oldData, data)) notifyDataSetChanged();
else if (DEBUG) Log.d(TAG, "Updated without notifyDataSetChanged");
}
/**
* See if we can update the data within existing layout, without re-drawing the list.
* #param oldData
* #param newData
* #return
*/
private boolean updateExistingViews(List<? extends DBObject> oldData, List<? extends DBObject> newData) {
/**
* Iterate over new data, compare to old. If IDs out of sync, stop and return false. Else - update visible
* items.
*/
final int oldDataSize = oldData.size();
if (oldDataSize != newData.size()) return false;
DBObject newObj;
int nVisibleViews = m_visibleViews.size();
if(nVisibleViews == 0) return false;
for (int position = 0; nVisibleViews > 0 && position < oldDataSize; position++) {
newObj = newData.get(position);
if (oldData.get(position).getId() != newObj.getId()) return false;
// iterate over visible objects and see if this ID is there.
final View view = m_visibleViews.get(position);
if (null != view) {
// this position has a visible view, let's update it!
bindView(position, view, false);
nVisibleViews--;
}
}
return true;
}
and of course:
#Override
public View getView(final int position, final View convertView, final ViewGroup parent) {
final View result = createViewFromResource(position, convertView, parent);
m_visibleViews.put(position, result);
return result;
}
Ignore the last param to bindView (I use it to determine whether or not I need to recycle bitmaps for ImageDrawable).
As mentioned above, the total number of 'visible' views is roughly the amount that fits on the screen (ignoring orientation changes etc), so no biggie memory-wise.
In addition to this solution (https://stackoverflow.com/a/3727813/5218712) just want to add that it should work only if listView.getChildCount() == yourDataList.size();
There could be additional view inside ListView.
Example of how the child elements are populated: