android gridview lag on scroll - android

I'm programming an app that has a grid view with some images, and I'm using the default vertical scroll, since not all the images can be fit into the screen. Alway I do scroll, and there is a new line to show, there seems to be a lag, on the render of the new row.
All the images are drawables, so it is not cause by something like loading the image, at least, not explicitly. So, any ideads of how to solve the problem? The scrolling is lagged in my atrix, and the final devices the app is supposed to run in, are far worse than it.
Edit - Here some code:
public View getView(int position, View convertView, ViewGroup parent) {
T item = getItem(position);
Box box = convertView == null ? new Box(mContext) : (Box) convertView;
onBindModel(box, item);
return box;
}
and
#Override
public void onBindModel(Box box, Model item) {
box.param.putSerializable("model", item);
box.setDescricao(item.getNome());
box.getModelImageView().setImageResource(item.thumbResId);
}
Edit
So, as suggested, just tried this code, but still get the same results:
#Override
public void onBindModel(final Box box, final Model item) {
box.param.putSerializable("model", item);
box.setDescricao(item.getNome());
new Thread(){
public void run() {
Message msg = new Message();
msg.obj =box.getModelImageView();
msg.arg1 =item.thumbResId;
mHandler.sendMessage(msg);
}
}.start();
}
private Handler mHandler = new Handler() {
#Override
public void handleMessage(Message message) {
Bitmap bm = BitmapFactory.decodeResource(getResources(), message.arg1);
setImage((ImageView) message.obj,bm);
}
};
public void setImage(ImageView imgView, Bitmap bm) {
imgView.setImageBitmap(bm);
}
When scroll to the next line I got a hang and then it just continues normally.
Thanks in advance

All the images are drawables, so it is not cause by something like
loading the image, at least, not explicitly.
This is not true. The work is done on the UI thread.
Refer to
http://developer.android.com/reference/android/widget/ImageView.html#setImageResource(int)
You might want to consider lazy loading the images. Multiple approaches have been discussed extensively everywhere. Google for it.

Old question,but it did the trick :
Glide.with(context)
.load(stickersData[position])
.into(imageView);
Using Glide

Related

Adding items to ListView, maintaining scroll position and NOT seeing a scroll jump

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);
}
});
}

setting text in Gallery onItemSelected causes scrolling to snap

In the galleryView onItemSelected I call setText that change the text for a textview thats part of the main layout:
#Override
public void onItemSelected(EcoGalleryAdapterView<?> parent, View view, int position, long id) {
// --- run asyncTask to update gallery view here
TextView myTextView = (TextView) findViewById(R.id.myTextView);
myTextView.setText("position is: ": position);
}
if I left everything as it is and just removed myTextView.setText the gallery works as expected but if I kept it then when scrolling the gallery snaps to the selected position really fast in an ugly way. What could be the issue?
"Ugly" is a pretty subjective term for describing a user interface transition.
However, it sounds as though what you want is a custom animation when an item is selected. onItemSelected() is called before the layout happens, so you can animate your Gallery or individual Views however you want in this method.
I would suggest reading the Animation and Graphics documentation from the Android developer documentation to more fully understand animations and to help decide what you actually want.
The code will vary depending on what you decide you want it to look like and what version of Android you are targeting. A simple View animation that will fade the selected view in might look something like this:
public void onItemSelected(EcoGalleryAdapterView<?> parent, View view,
int position, long id) {
view.setAnimation(new AlphaAnimation(0, 1));
}
// try this
TextView myTextView;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.image_full);
myTextView = (TextView) findViewById(R.id.myTextView);
}
#Override
public void onItemSelected(EcoGalleryAdapterView<?> parent, View view, int position, long id) {
// --- run asyncTask to update gallery view here
myTextView.setText("position is :"+ position);
myTextView.invalidate();
}
The setText commands sits on the UI Thread, maybe it's taking a higher priority or something from the current Gallery animation which disrupts it from acting as it should.
try setting your setText inside a handler:
new Handler().post(new Runnable() {
#Override
public void run() {
myTextView.setText("position is: ": position);
}
});

android frame animation of buttons in gridview

i have made one of these tap number games for android as a test project and for this i have made a gridview with about 20 buttons in it. when one of these buttons is pressed an animation is started. this runs fine for the first few times but than becomes slower and starts stuttering.
i assume it has something to do with the animation ressource as i use it several times at the same time but i dont know how to solve the problem.
as i want to remove the button from the gridview when the animation ends i wrapped the AnimationDrawable class to be able to set a Handler which is called at the end of the animation.
public void animate() {
setBackgroundResource(R.drawable.myanimation);
final AnimationDrawable anim = (AnimationDrawable) getBackground();
final BetterAnimationDrawable better = new BetterAnimationDrawable(anim);
better.setEndHandler(new EndHandler());
better.start();
}
thanks in advance
UPDATE:
#warpzit: thanks for your answer. it's not a handler for earch click but a handler for each button. the onclick method disables the button (so it can only be pressed once) and then calls animate(). actually theres not much more code i can post, the gridview adapters getView looks like this:
#Override
public View getView(final int position, final View convertView, final ViewGroup parent) {
final MySpecialButton sb = new MySpecialButton(getApplicationContext());
sb.setOnClickListener(new SpecialButtonClickListener());
return nv;
}
and the mentioned handler looks like this (its actually not removing the button but changing the background-drawable, sorry for that):
private class MySpecialHandler extends Handler {
public MySpecialHandler() {
}
#Override
public void handleMessage(final Message msg) {
super.handleMessage(msg);
final Bitmap bitmapMask = BitmapFactory.decodeResource(getResources(), R.drawable.aspecialmask);
final BitmapDrawable d = new BitmapDrawable(bitmapMask);
d.setColorFilter([someColor], Mode.MULTIPLY);
setBackgroundDrawable(d);
}
};
You make a new handler for each click? That gotta hurt... How about making just 1 for each button or something else far smarter (need to see more code to come with other suggestions).

Android ListView scrollTo

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;
}

How do I move my image downloader into a seperate thread on Android?

I have the following code running on my Android device. It works great and displays my list items wonderfully. It's also clever in the fact it only downloads the data when it's needed by the ArrayAdapter. However, whilst the download of the thumbnail is occurring, the entire list stalls and you cannot scroll until it's finished downloading. Is there any way of threading this so it'll still scroll happily, maybe show a place holder for the downloading image, finish the download, and then show?
I think I just need to put my downloadImage class into it's own thread so its executed separately from the UI. But how to add this into my code is the mystery!
Any help with this would be really appreciated.
private class CatalogAdapter extends ArrayAdapter<SingleQueueResult> {
private ArrayList<SingleQueueResult> items;
//Must research what this actually does!
public CatalogAdapter(Context context, int textViewResourceId, ArrayList<SingleQueueResult> items) {
super(context, textViewResourceId, items);
this.items = items;
}
/** This overrides the getview of the ArrayAdapter. It should send back our new custom rows for the list */
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View v = convertView;
if (v == null) {
LayoutInflater vi = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);
v = vi.inflate(R.layout.mylists_rows, null);
}
final SingleQueueResult result = items.get(position);
// Sets the text inside the rows as they are scrolled by!
if (result != null) {
TextView title = (TextView)v.findViewById(R.id.mylist_title);
TextView format = (TextView)v.findViewById(R.id.mylist_format);
title.setText(result.getTitle());
format.setText(result.getThumbnail());
// Download Images
ImageView myImageView = (ImageView)v.findViewById(R.id.mylist_thumbnail);
downloadImage(result.getThumbnail(), myImageView);
}
return v;
}
}
// This should run in a seperate thread
public void downloadImage(String imageUrl, ImageView myImageView) {
try {
url = new URL(imageUrl);
URLConnection conn = url.openConnection();
conn.connect();
InputStream is = conn.getInputStream();
BufferedInputStream bis = new BufferedInputStream(is);
Bitmap bm = BitmapFactory.decodeStream(bis);
bis.close();
is.close();
myImageView.setImageBitmap(bm);
} catch (IOException e) {
/* Reset to Default image on any error. */
//this.myImageView.setImageDrawable(getResources().getDrawable(R.drawable.default));
}
}
Here's a simplified version of what I'm using in my apps:
// this lives in Application subclass and is reused
public static final ExecutorService EXECUTOR = Executors.newSingleThreadExecutor();
// the method itself
public void attachImage(final String fileUrl, final ImageView view) {
EXECUTOR.execute(new Runnable() {
#Override
public void run() {
final Drawable image = methodThatGetsTheImage(fileUrl);
if (image != null) {
view.post(new Runnable() {
#Override
public void run() {
view.setImageDrawable(drawable);
}
});
}
}
});
}
You can also check out cwac-thumbnail for a drop-in solution (though it has a bit more overhead than the simpler solutions above). I've used it with great success pretty much anywhere I have a web image to download; you basically set up some global caching options for your app, then just wrap your ListAdapter in a ThumbnailAdapter with a list of android:ids of the imageviews in your list, and call setTag(imageUrl) on the imageviews in your adapter's getView method, and it handles the rest.
Yes. You should avoid running on the UI Thread as much as possible. When you're in getView() you are on the UI thread. To break out do this:
new Thread(new Runnable(){
public void run() {
// your code here
}}).start();
Then when you need to modify a view in some way go back onto the UI thread like this:
runOnUiThread(new Runnable(){
public void run() {
// your view-modifying code here
}});
.. or use the View.post() mechanism.
In your example what will happen is that the item will be shown on the screen before the image is available for display. I suggest using a 'loading' spinner or some other placeholder to indicate to the user what is happening.
You can in practice nest like this several times, although you will reach a point where a simplified design is in order. Anonymous classes can be inefficient memory-wise and firing off a lot of threads without a cancel mechanism can have its own drawbacks.
You may experience some difficulties as a side effect of modifying views after getView() has exited. Some views may need invalidate() called to have their changes take effect. Also beware if you are recycling views in your getView(), you should ensure that by the time your view-altering code is executed that the view is still being used for the content you are applying. setTag()/getTag() is useful here to store some ID of the data that you can later check to make sure your View hasn't been recycled.

Categories

Resources