I am calling notifyItemChanged(position) from a for loop to set ImageViewVisibility = inVisible, but All the image views are shown at once after for loop is ended. Whereas I want to show/hide imageView one at a time.
What do I need to do to fix this?
Present output:
All image views are visible all together at once when for loops ends. (I mean
OnBindviewHolder method is getting called only after for loop is ended`
Expected output:
As for loops executed for each index, I want to show/hide imageView for each row one by one (I mean OnBindviewHolder method should call as each for loop is called)
What I have tried:
I have tried notifyItemChanged(pos);,notifyDataSetChanged();, notifyItemInserted(pos); but none of them helped me to get expected output. I also tried https://stackoverflow.com/a/35392153/1684778 but still the same ouput.
activity
private List<Multiples> items = new ArrayList<>();
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_display_result);
recyclerView = (RecyclerView) findViewById(R.id.my_recycler_view);
// use a linear layout manager
layoutManager = new LinearLayoutManager(this);
recyclerView.setLayoutManager(layoutManager);
// specify an adapter (see also next example)
items.addAll(DataGenerator.getPeopleData(this, of, value));
mAdapter = new MyAdapter(items);
recyclerView.setAdapter(mAdapter);
//----------now give few seconds untill all default data is loaded in the recyclerView----------------
final Handler handler = new Handler();
handler.postDelayed(new Runnable() {
#Override
public void run() {
//--------------Now call for loop on every 2 seconds so that we can hide or show ImageViews in each 2 seconds
MyAdapter m= (MyAdapter) mAdapter;
for (Multiples item : items) {
Multiples multiples1=items.get(items.indexOf(item));
multiples1.setImageShow(true); // set true to hide loop
Log.i("ms","----------in activity--------"+items.indexOf(item));
m.updateItem(item,items.indexOf(item)); // forward each row to adapter to take effect
try { // sleep for 2 seconds so that we can see the effect of above code(hide/show imageView for this row index items.indexOf(item)
Log.i("s","##################### going to sleep #######################"+items.indexOf(item) );
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}, 2500);
}
adapter
public void updateItem(final Multiples newItem, final int pos) {
newItem.setImageShow(true);
items.set(pos, newItem); //update passed value in your adapter's data structure
notifyItemChanged(pos);
}
// Replace the contents of a view (invoked by the layout manager)
#Override
public void onBindViewHolder(MyViewHolder holder, int position) {
// - get an element from your dataset at this position
// - replace the contents of the view with that element
if (holder instanceof MyAdapter.MyViewHolder) {
final MyAdapter.MyViewHolder view = (MyAdapter.MyViewHolder) holder;
final Multiples p = items.get(position);
view.name.setText(p.first + " X " + p.getSecond() + "= " + p.getResult());
// if(position>0) {
if (p.imageShow) {
view.image1.setVisibility(View.VISIBLE);
view.image.setVisibility(View.INVISIBLE);
} else {
view.image1.setVisibility(View.INVISIBLE);
view.image.setVisibility(View.VISIBLE);
}
}
// }
}
Your issue is that you're blocking the main thread while you try to change all of the visibilities. So although you are trying to sleep to put a delay between the operations, the previous ones can't execute because the thread is blocked. In other words, you are queuing up all of the actions but they can't take effect until you release the main thread and the system can process them, which then happens all at the same time.
public void run() {
// THIS IS HAPPENING ON THE MAIN THREAD - ANDROID CAN'T CONTINUE DRAWING
// UNTIL THIS METHOD FINISHES
//--------------Now call for loop on each 2 seconds so that we can hide or show ImageViews in each 2 seconds
MyAdapter m= (MyAdapter) mAdapter;
for (Multiples item : items) {
Multiples multiples1=items.get(items.indexOf(item));
multiples1.setImageShow(true); // set true to hide loop
Log.i("ms","----------in activity--------"+items.indexOf(item));
m.updateItem(item,items.indexOf(item)); // forward each row to adapter to take effect
try { // sleep for 2 seconds so that we can see the effect of above code(hide/show imageView for this row index items.indexOf(item)
Log.i("s","##################### going to sleep #######################"+items.indexOf(item) );
// THIS SLEEP IS BLOCKING THE MAIN THREAD - ANDROID CAN'T DRAW
// UNTIL THE OUTER FUNCTION IS DONE
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
} // THE END OF THE RUN BLOCK - NOW ANDROID CAN RUN THE INSTRUCTIONS THAT
// HAVE BEEN QUEUED UP, WHICH WILL APPEAR INSTANT
One thing you could do is fire off runnables in the for loop, like you're already doing with the main function.
public void run() {
//--------------Now call for loop on each 2 seconds so that we can hide or show ImageViews in each 2 seconds
MyAdapter m= (MyAdapter) mAdapter;
int index = 0;
for (Multiples item : items) {
// DO NOT SLEEP TO NOT BLOCK THE MAIN THREAD - SCHEDULE WORK FOR LATER INSTEAD
handler.postDelayed(new Runnabled() {
Multiples multiples1=items.get(items.indexOf(item));
multiples1.setImageShow(true); // set true to hide loop
// OTHER LOGIC FOR THIS ITEM HERE
}, 2000 * index++); // SCHEDULE EACH ITEM TO BE 2 SECONDS LATER THAN THE PREVIOUS
}
} // THE LOOP FINISHES INSTANTLY AND DOES NOT BLOCK THE MAIN THREAD
This way you schedule all of the updates to happen at the time you want and immediately free the main thread. Later, at the scheduled times, the main thread is free to process the instructions and do the animation as desired.
Hope that helps!
Related
I have to build view pager with endless auto scroll. Also I need to show a page indicator below view pager which should respond as per the scroll events. I have applied following logic for endless auto scroll currently:
public void setupAutoPager(final int size) {
final Handler handler = new Handler();
final Runnable update = new Runnable() {
public void run() {
promotionViewPager.setCurrentItem(currentPage);
if (currentPage >= size - 1) {
currentPage = 0;
} else {
++currentPage;
}
}
};
Timer timer = new Timer();
timer.schedule(new TimerTask() {
#Override
public void run() {
handler.post(update);
}
}, 500, 2500);
}
Problem: When 1st item comes again from last item on auto scroll, animation of view pager is in backward direction as position is reached again (looks like flicker as going back), whereas when auto scroll from 1st to 2nd element then animation comes in forward direction (going to next element). I want animation should always be in forward direction. Backward animation comes as I am setting current item of view pager to 0 position when it reaches to end. How to implement that.
I know this post is few months old but i am a beginner at building android applications & I am facing the same problem.
I would try the following approach even if it absolutely does not seem to be optimal....
Build an array into your adapter class which will contain all of your fragments.
Populate this array with the fragments you want to display. The trick will be to add the first fragment at the end of the list & remove it after the ViewPager has done its changes.
Override the following methods getItem(int position) and getCount() with your array : return yourFragmentArray.get(position); & return yourFragmentArray.size();
The idea is to populate the ViewPager with the same fragments over & over again (+ new ones if needed) and delete the one you used to populate the VP so you never go back.
So if i take your code i would do something like this :
private int size = yourFragmentArray.size();
public void setupAutoPager(final int size) {
final Handler handler = new Handler();
currentPage = promotionViewPager.getCurrentItem();
final Runnable update = new Runnable() {
public void run() {
if (currentPage == size - 1) {
// Add the fragment to your fragmentArray
// Only the rank 0 needs to be taking care, works like in queue
yourFragmentArray.add(yourFragmentArray.get(0));
// Notify the changes made
yourAdapter.notifyDataSetChanged();
// Set the next item
promotionViewPager.setCurrentItem(currentPage + 1);
yourFragmentArray.remove(0);
// Update the size
size += 1;
}
else {
promotionViewPager.setCurrentItem(currentPage + 1);
}
}
};
Timer timer = new Timer();
timer.schedule(new TimerTask() {
#Override
public void run() {
handler.post(update);
}
}, 500, 2500);
}
The logic is somewhere here for me, again it's probably not the best way to do it but i can't see another for now.
Good luck ! I will update this post as soon as i can test it myself.
UPDATE :
A much better way to do it is to add in this method :
Save into a specific variable firstMaxSize the size of the ViewPager before the method is called.
Use a counter (if it's the first time you loop through the adapter fragments, we won't remove them), but if(counter != 0) we will loop through the yourFragmentArray() and remove from 0 to firstMaxSize.
Call yourAdapter.notifyDataSetChanged()
Then add all over again the new fragments. If the problem of an existing fragment is already added happens, create a new fragment based on the content of this one.
Call again yourAdapter.notifyDataSetChanged()
It is indeed very heavy & i am pretty sure another easiest way exists but have not found it yet.
I`m new here. Here is my question. I have an array list and adapter (RecyclerView). I want to make pause after inserting one item after another. If I add one item, animation goes fine. And even when I send multiple items to adapter it is not look terrible, but to the usability concerns, I want to add them one by one with pause. What I did. If I do this code in my fragment, no pause at all. Items appear in one moment.
for( int i = 0; i<filteredList.size();i++){
try {
mAdapter.add(filteredList.get(i));
Thread.sleep(350);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
If I add similar code to adapter, it stacks delay and then all items appear same way. May be I missed something?
It seems that handlder is the solution.
int dlay;
Handler handler = new Handler();
for( int i = 0; i<filteredList.size();i++){
final int finalI = i;
handler.postDelayed(new Runnable() {
#Override
public void run() {
mAdapter.add(filteredList.get(finalI));
}
}, dlay);
dlay = dlay + 350;
I've a ListView with some items that users can select.
I want that the first element appears selected, and after 1 or 2 seconds, the selected item will be the second automatically.
How can I do this?
When a item is selected, it can has a bold text for example.
I have a custom adapter for the ListView.
Update:
listview.setSelection(1);
System.out.println(listview.getSelectedItemPosition());
I've tested the code with this println, but it returns "-1". Not selects any row.
For a great tutorial on ListView see this tutorial: http://www.vogella.com/tutorials/AndroidListView/article.html
Back to the original question to select an item use the following:
ListView.setSelection(int);
Calling will simply change the UI without direct user input.
For the delay, you can use the following snippet:
final Handler handler = new Handler(Looper.getMainLooper());
handler.postDelayed(new Runnable() {
#Override
public void run() {
ListView.setSelection(int);
}
}, 100);
For information on how change the Android ListView background color of selected item see this post.
If you are asking to iterate through the list and change it's selector you can use a handler like this:
private Handler handler = new Handler();
private Runnable runnable = new Runnable() {
#Override public void run() {
int currentSelectedIndex = mListView.getSelectedItemPosition();
if ((currentSelectedIndex + 1) < listCount) {
mListView.setSelection(currentSelectedIndex + 1);
} else {
mListView.setSelection(0);
}
handler.postDelayed(runnable, 1000);
}
};
And then in your code, in onCreate() for example call:
handler.postDelayed(runnable, 1000);
This will change the list selection every second as you would like.
If you do want to stop the automatic list selector, call this:
handler.removeCallbacks(runnable);
It's considered bad UX to automatically change the UI without direct user input, but if you want to select an item in a ListView programatically, you can call
ListView.setSelection(int);
Sets the currently selected item. If in touch mode, the item will not
be selected but it will still be positioned appropriately. If the
specified selection position is less than 0, then the item at position
0 will be selected.
For the delay, you will want to place this inside a Handler.
public class ListLooper {
private LoopHandler mHandler;
private ListView mListView;
public ListLooper(Activity activity) {
mListView = new ListView(activity);
mHandler = new LoopHandler(mListView);
}
private void start() {
mHandler.sendEmptyMessageDelayed(LoopHandler.MSG_LOOP, LoopHandler.DELAY);
}
private void stop() {
mHandler.removeMessages(LoopHandler.MSG_LOOP);
}
private static class LoopHandler extends Handler {
private static final int MSG_LOOP = 1;
private static final int DELAY = 2000;
/**
* Use a WeakReference so we don't keep an implicit reference to the Activity
*/
private WeakReference<ListView> mListRef;
private int mPosition;
private LoopHandler(ListView list) {
mListRef = new WeakReference<>(list);
}
#Override public void handleMessage(Message msg) {
super.handleMessage(msg);
// Check if we still have a reference to the ListView, and the Activity/Fragment hasn't been destroyed
if (mListRef.get() == null) {
return;
}
// If we're looping, run this code
if (msg.what == MSG_LOOP) {
int count = mListRef.get().getAdapter().getCount();
mListRef.get().setSelection(mPosition);
// If the position is less than the count, increment it, otherwise set it to 0
if (mPosition < count - 1) {
mPosition++;
} else {
mPosition = 0;
}
// Send the same message again, so we repeat this process
sendEmptyMessageDelayed(MSG_LOOP, DELAY);
}
}
}
Thanks so much!
This instruction:
listview.setSelection(int)
does not work in my code.
When I select some item, the background colour turns blue. That's correct.
But when the activity is just loaded, there is not item selected automatically.
I use performItemClick and the item is selected, highlighted and the getSelectedItem method will return the correct value:
ListView1.performItemClick(ListView1.getChildAt(1),1,ListView1.getItemIdAtPosition(1));
where 1 = position in the list
I'm building an interface similar to the Google Hangouts chat interface. New messages are added to the bottom of the list. Scrolling up to the top of the list will trigger a load of previous message history. When the history comes in from the network, those messages are added to the top of the list and should not trigger any kind of scroll from the position the user had stopped when the load was triggered. In other words, a "loading indicator" is shown at the top of the list:
Which is then replaced in-situ with any loaded history.
I have all of this working... except one thing that I've had to resort to reflection to accomplish. There are plenty of questions and answers involving merely saving and restoring a scroll position when adding items to the adapter attached to a ListView. My problem is that when I do something like the following (simplified but should be self-explanatory):
public void addNewItems(List<Item> items) {
final int positionToSave = listView.getFirstVisiblePosition();
adapter.addAll(items);
listView.post(new Runnable() {
#Override
public void run() {
listView.setSelection(positionToSave);
}
});
}
Then what the user will see is a quick flash to the top of the ListView, then a quick flash back to the right location. The problem is fairly obvious and discovered by many people: setSelection() is unhappy until after notifyDataSetChanged() and a redraw of ListView. So we have to post() to the view to give it a chance to draw. But that looks terrible.
I've "fixed" it by using reflection. I hate it. At its core, what I want to accomplish is reset the first position of the ListView without going through the rigamarole of the draw cycle until after I've set the position. To do that, there's a helpful field of ListView: mFirstPosition. By gawd, that's exactly what I need to adjust! Unfortunately, it's package-private. Also unfortunately, there doesn't appear to be any way to set it programmatically or influence it in any way that doesn't involve an invalidate cycle... yielding the ugly behavior.
So, reflection with a fallback on failure:
try {
Field field = AdapterView.class.getDeclaredField("mFirstPosition");
field.setAccessible(true);
field.setInt(listView, positionToSave);
}
catch (Exception e) { // CATCH ALL THE EXCEPTIONS </meme>
e.printStackTrace();
listView.post(new Runnable() {
#Override
public void run() {
listView.setSelection(positionToSave);
}
});
}
}
Does it work? Yes. Is it hideous? Yes. Will it work in the future? Who knows? Is there a better way? That's my question.
How do I accomplish this without reflection?
An answer might be "write your own ListView that can handle this." I'll merely ask whether you've seen the code for ListView.
EDIT: Working solution with no reflection based on Luksprog's comment/answer.
Luksprog recommended an OnPreDrawListener(). Fascinating! I've messed with ViewTreeObservers before, but never one of these. After some messing around, the following type of thing appears to work quite perfectly.
public void addNewItems(List<Item> items) {
final int positionToSave = listView.getFirstVisiblePosition();
adapter.addAll(items);
listView.post(new Runnable() {
#Override
public void run() {
listView.setSelection(positionToSave);
}
});
listView.getViewTreeObserver().addOnPreDrawListener(new OnPreDrawListener() {
#Override
public boolean onPreDraw() {
if(listView.getFirstVisiblePosition() == positionToSave) {
listView.getViewTreeObserver().removeOnPreDrawListener(this);
return true;
}
else {
return false;
}
}
});
}
Very cool.
As I said in my comment, a OnPreDrawlistener could be another option to solve the problem. The idea of using the listener is to skip showing the ListView between the two states(after adding the data and after setting the selection to the right position). In the OnPreDrawListener(set with listViewReference.getViewTreeObserver().addOnPreDrawListener(listener);) you'll check the current visible position of the ListView and test it against the position which the ListView should show. If those don't match then make the listener's method return false to skip the frame and set the selection on the ListView to the right position. Setting the proper selection will trigger the draw listener again, this time the positions will match, in which case you'd unregister the OnPreDrawlistener and return true.
I was breaking up my head until I found a solution similar to this.
Before adding a set of items you have to save top distance of the firstVisible item and after adding the items do setSelectionFromTop().
Here is the code:
// save index and top position
int index = mList.getFirstVisiblePosition();
View v = mList.getChildAt(0);
int top = (v == null) ? 0 : v.getTop();
// for (Item item : items){
mListAdapter.add(item);
}
// restore index and top position
mList.setSelectionFromTop(index, top);
It works without any jump for me with a list of about 500 items :)
I took this code from this SO post: Retaining position in ListView after calling notifyDataSetChanged
The code suggested by the question author works, but it's dangerous.
For instance, this condition:
listView.getFirstVisiblePosition() == positionToSave
may always be true if no items were changed.
I had some problems with this aproach in a situation where any number of elements were added both above and below the current element. So I came up with a sligtly improved version:
/* This listener will block any listView redraws utils unlock() is called */
private class ListViewPredrawListener implements OnPreDrawListener {
private View view;
private boolean locked;
private ListViewPredrawListener(View view) {
this.view = view;
}
public void lock() {
if (!locked) {
locked = true;
view.getViewTreeObserver().addOnPreDrawListener(this);
}
}
public void unlock() {
if (locked) {
locked = false;
view.getViewTreeObserver().removeOnPreDrawListener(this);
}
}
#Override
public boolean onPreDraw() {
return false;
}
}
/* Method inside our BaseAdapter */
private updateList(List<Item> newItems) {
int pos = listView.getFirstVisiblePosition();
View cell = listView.getChildAt(pos);
String savedId = adapter.getItemId(pos); // item the user is currently looking at
savedPositionOffset = cell == null ? 0 : cell.getTop(); // current item top offset
// Now we block listView drawing until after setSelectionFromTop() is called
final ListViewPredrawListener predrawListener = new ListViewPredrawListener(listView);
predrawListener.lock();
// We have no idea what changed between items and newItems, the only assumption
// that we make is that item with savedId is still in the newItems list
items = newItems;
notifyDataSetChanged();
// or for ArrayAdapter:
//clear();
//addAll(newItems);
listView.post(new Runnable() {
#Override
public void run() {
// Now we can finally unlock listView drawing
// Note that this code will always be executed
predrawListener.unlock();
int newPosition = ...; // Calculate new position based on the savedId
listView.setSelectionFromTop(newPosition, savedPositionOffset);
}
});
}
I have a LinearLayout that contains some other views and among those a ListView.
This view is loaded from another one by clicking a button.
This button somehow specify what element in the ListView needs to be the first visible one in the list. The elements that populates the list are retrieved via HTTP from an external server.
The problem is that I can get the Nth element to be the first in the list.
Please note, I do not want to move it form it current position to a new one, I want the list to scroll.
I have tried with setSelected() and scrollTo(x,y) and scrollBy(x,y) but with no luck.
I have also gave a try to this pice of code, as ugly as it is, but I just wanted to try f it was working:
ListView categoryList = (ListView)findViewById(R.id.category_list);
categoryList.post(new Runnable() {
#Override
public void run() {
Log.d(this.getClass().getName(), "CategoryActivity.scrollToIndex: " + CategoryActivity.scrollToIndex);
if(CategoryActivity.scrollToIndex>0){
ListView categoryList = (ListView)findViewById(R.id.category_list);
categoryList.setScrollContainer(true);
categoryList.scrollTo(4, CategoryActivity.scrollToIndex * 50);
categoryList.requestLayout();
}
}
});
And this gave me some success, but the ListView was then behaving crazy in a way I am not even able to describe....
Any idea?
Try to add it to the message queue
categoryList.post(new Runnable() {
public void run() {
categoryList.scrollTo(4, CategoryActivity.scrollToIndex * 50);
}
});
It worked for me in a ScrollView (check this answer).
i made functions that could be useful for others for listview scrolling, they work for me in every android version, emulator and device, here itemheight is the fixed height of view in the listview.
int itemheight=60;
public void scrollToY(int position)
{
int item=(int)Math.floor(position/itemheight);
int scroll=(int) ((item*itemheight)-position);
this.setSelectionFromTop(item, scroll);// Important
}
public void scrollByY(int position)
{
position+=getListScrollY();
int item=(int)Math.floor(position/itemheight);
int scroll=(int) ((item*itemheight)-position);
this.setSelectionFromTop(item, scroll);// Important
}
public int getListScrollY()
{
try{
//int tempscroll=this.getFirstVisiblePosition()*itemheight;// Important
View v=this.getChildAt(0);
int tempscroll=(this.getFirstVisiblePosition()*itemheight)-v.getTop();// Important
return tempscroll;
}catch(Exception e){}
return 0;
}