I have the following problem. I have placed 30+ images in a HorizontalScrollView. I am correctly detecting onScroll() by overriding this method in HorizontalScrollView. The problem is I need to only load those images the user is near or there will be too many downloading/in memmory etc. So If user scrolls fast right now it starts downloading everything from start of scroll to end of scroll. This might be a hundred images and yet at end of scroll they are only next to say 4 or 5 images. How do I detect the stop of scrolling? and use this to kick off download?
EDIT: For slow scrolling it works perfect, but fast scrolling is the problem. If the scroll is slow then the behavior is perfect.
EDIT: What I am doing is updating any images that are nearby in onScroll but onScroll seems to get called many many times, and its difficult to determine if the images should be updated or not from inside onScroll. For example x=50, xPrev=49, xPrev=50, x=51 etc really for each pixel change onScroll gets called.
Thanks
Your images are moving in horizontal direction ,
what you need to do is find the velocity of moving images and based on it load images or skip them ,
get current time in onScrollChanged method save it to a variable and find difference with old time , if distance i.e. difference in horizontal x1 and x2 is more than a certain level say screen width (experiment with it ) , then calculate velocity if the velocity is below certain level load the images or otherwise ignore ,always load images when idle i.e. when velocity is zero.
Did not find anyway to detect end of scrolling but using a CountDownTimer seems to work:
I also needed to override HorizontalScrollView to add a callback IHorizontalScrollListener which is called inside onScrollView of HorizontalScrollView. viewGroupContainer is the container LinearLayout for the ImageViews. A scroll view can only contain directly one ViewGroup so I added all the images to the view group. The CountDownTimer is reference maintained in the activity.
hzScrollView.setScrollListener(new IHorizontalScrollListener(){
#Override
public void onScrollChanged(int l, int t, int oldl, int oldt) {
Log.i(TAG, "onScrollChanged to x="+l+" y="+t);
Log.i(TAG, "onScrollChanged from x="+oldl+ " y="+oldt);
if (scrollUpdateTimer == null)
{
scrollUpdateTimer = new CountDownTimer(30000, 1000){
#Override
public void onFinish() {
scrollUpdateTimerRunning = false;
}
#Override
public void onTick(long interval) {
Log.d(TAG, "onTick updateImageViews");
updateImageViews(hZScrollView, viewGroupContainer);
}};
}
if (scrollUpdateTimerRunning == false)
{
scrollUpdateTimerRunning = true;
scrollUpdateTimer.start();
}
}});
}
Related
I'm developing a simple game which uses normal android views, not openGL or other apis, simply uses views and moves them on the scren. I have a game loop which calls to AsteroidManager.updateAsteroidsPositions() which iterates in all the screen asteroids calculating it's possitions.
After that, the thread, calls to a AsteroidManager.invalidateAsteroids() method using runOnUiThread() method, because in Android you need to manipulate views on the main thread. AsteroidManager.invalidateAsteroids() method simply iterates all the asteroids and set's x,y positions to the view and calls invalidate().
The problem is that I disscovered that it gives a much more smooth and faster behaviour if you put the logic of calculatePositions inside the onDraw method of the view. Doing that, the logic of calculating possitions is not being done in the game loop thread... its being done in the main UI thread!!
How is that possible? It is breaking all the game development logic... about doing the position calculations on Game Loop thread instead of other places like main thread or onDraws...
This the slower original code:
AsteroidManager class:
public void updateAsteroidsPositions(){
for (int i = 0; i<onScreenAsteroids.size(); i++){
onScreenAsteroids.get(i).updatePosition();
}
}
public void invalidateAsteroids() {
for (int i = 0; i<onScreenAsteroids.size(); i++){
onScreenAsteroids.get(i).invalidate();
}
}
Asteroid Class:
public void updatePosition(){
currentScale = (Float) scaleX.getAnimatedValue();
factor = currentScale/MAX_SCALE;
//adding a minimum of factor, because with too low factor the movement is not realistic
if (factor < 0.250f)
factor = 0.250f;
x = x-((float)GameState.getInstance().getJoyX()*factor);
y = y-((float)GameState.getInstance().getJoyY()*factor);
}
public void invalidate(){
view.setX(x);
view.setY(y);
view.invalidate();
}
this is the trick done in Asteroid class which does the behaviour of the game smooth and faster:
Asteroid Class:
public Asteroid(Bitmap bitmap, Context context) {
view = new ImageView(context){
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
currentScale = (Float) scaleX.getAnimatedValue();
factor = currentScale/MAX_SCALE;
//adding a minimum of factor, because with too low factor the movement is not realistic
if (factor < 0.250f)
factor = 0.250f;
x = x-((float)GameState.getInstance().getJoyX()*factor);
y = y-((float)GameState.getInstance().getJoyY()*factor);
view.setX(x);
view.setY(y);
}
};
view.setLayoutParams(new RelativeLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
view.setImageBitmap(bitmap);
}
public void updatePosition(){
}
public void invalidate(){
view.invalidate();
}
If you have too many items in onScreenAsteroids list it takes some time to execute updatePosition() for each of them. Try to use single method for them:
public void updateAndInvalidateAsteroidsPositions(){
for (int i = 0; i<onScreenAsteroids.size(); i++){
onScreenAsteroids.get(i).updatePosition();
onScreenAsteroids.get(i).invalidate();
}
}
Not all games need game loop. Thread switching has its own cost.
Game Loop separates game state from rendering. Ideally the game loop has the responsibility to processes all the onscreen objects in the game and objects have the responsibility to draw itself in its place. This way we have central place to react to events(mouse click, user touch etc) and update view positions and views have the responsibility to draw themselves at updated position.
For eg consider that we have 10 moving asteroids on screen and we are updating them in onDraw(), now two of them collide, but asteroid1 does not know position of asteroid2, so how will they detect collision? By game logic the game loop knows position of all 10 asteroids, it can detect collision. If don't care about messy code, then collision can be detected in onDraw also. But consider following...
If two are colliding , then we need to check if some other asteroid is near by collision region, if so then how much impact? Mess increases linearly...
After collision we decide to show collision graphic effects. Mess increases exponentially....
Asteroids collided, game state = 'EXPLOSIONS IN THE SKY', user gets a call, game goes to background, game state is to be saved, but our asteroids are master of their own destiny, now we need to provide every asteroid's state to our activity and save it in onPause(). Its all mess now...
User returns after few hours, we can't welcome them directly with 'EXPLOSIONS IN THE SKY', need to rewind to the state where asteroids are about to collide and then show BANG-BANG... Mess goes ALL HELL BREAK LOOSE.....
Views are slaves and they should not be empowered.
Where to display view, its dimens? -> comes from outside.
What to draw in view? -> comes from outside/ can have little say here.
How to animate view? -> comes from outside.
Coming to your particular case, you are using both versions differently, in onDraw() case you are directly invalidating asteroid's (first one is drawn instantly) whereas in game loop case you are first computing all asteroid's position and then invalidating, I don't know how many asteroids you have but if they are significant number, then this coupled with thread switching costs, may trick you to believe onDraw() is faster.
I am trying to write a function that moves the view "who" from "from" to "to". But it doesn't work in some cases and in others it just starts and finishes in wrong positions. What did I do wrong by my using of this animation type?
private void moveFromToShow(View from, View to, View who, boolean repeat) {
t = new TranslateAnimation(from.getX(), to.getX(), from.getY(), to.getY());
if(repeat){
t.setRepeatMode(TranslateAnimation.INFINITE);
t.setRepeatCount(Animation.INFINITE);
t.setRepeatMode(TranslateAnimation.RESTART);
}
t.setDuration(3000);
t.setFillAfter(true);
t.setZAdjustment(TranslateAnimation.ZORDER_TOP);
who.bringToFront();
who.startAnimation(t);
currentFreeAnimIndx++;
}
edit-explantion:
For example I have 2 views and want the image that is inside one of them to move to the other view. I send moveFromToShow(view1, view2, view1.getChildrenAt(0)); and I get the animation from near to view1(but not even exactly it) moving to outside of the screen.
I wanted this function to be universal for moving view from one to other view.
I have a ViewPager which I need to move as a whole on button press. I use an animation for this.
When I press it, I translate the 'x' for it. I use setFillAfter(true) to keep the new position.
But when I change the page of the ViewPager, it jumps back to the original x-position!
I only saw this issue on Android 4.1, with Android 4.0 there is no problem! So it looks like some kind of regression in Android.
I attached a testproject where I could reproduce the issue without all my other stuff around it. I think it is best if you want to help me figure this out to import the project in your Eclipse and see it for yourself.
I also added to video's, one on my HTC One X where I see the issue, and the other on a tablet with Android 4.0, where the issue is not there.
I have been desperately looking to fix this ugly side effect, but no luck till now...
(Sorry for the big movie files...)
Video of Android 4.0 without the side effect
Video Android 4.1 with the side effect
the project where you can reproduce the issue with
Edit:
I added the following:
#Override
public void onAnimationEnd(Animation animation) {
RelativeLayout.LayoutParams lp = (android.widget.RelativeLayout.LayoutParams) myViewPager.getLayoutParams();
if (!i)
lp.setMargins(300,0,0,0);
else
lp.setMargins(0,0,0,0);
myViewPager.setLayoutParams(lp);
}
After that it stays at the correct position, but it 'flickers' quickly, like the animation is still showing at the end and when I change the margin, it still shows the offset it had after animation. Then it jumps to the correct position.
The main problem seems to be incorrect choice of animation type. You see, View Animation as a tool is not intended to be used with complex interactive objects like ViewPager. It offers only low-cost animation of the drawing place of views. The visual behaivior of the animated ViewPager in response to user-actions is undefined and should not be relied on.
Ugly flicks, when you substitute a "gost" with the real object are only natural.
The mechanism, that is intended to use in your case since API 11 is specialized property animator built in Views for optimized performance: ViewPropertyAnimator, or not specialized, but more versatile ObjectAnimator and AnimatorSet.
Property animation makes the View to really change its place and function normally there.
To make project, to use, say, ViewPropertyAnimator, change your listener setting to this:
btn.setOnClickListener(new OnClickListener() {
boolean b = false;
#Override
public void onClick(View v) {
if(b) {
myViewPager.animate().translationX(0f).setDuration(700);
}
else {
myViewPager.animate().translationX(300f).setDuration(700);
}
b=!b;
}
});
If you want to use xml configuration only, stick to |ObjectAnimator and AnimatorSet. Read through the above link for further information.
In case, you are anxious to support pre-Honeycomb devices, you can use Jake Warton's NineOldAndroids project. Hope that helps.
That's because the Animation's setFillAfter(true) doesn't actually change the position or any attributes of the View; all it does is create a Bitmap of the view's drawing cache and leaves it where the animation ends. Once the screen is invalidated again (ie. changing the page in the ViewPager), the bitmap will be removed and it will appear as if the View is returning to it's original position, when in fact it was already there.
If you want the View to retain it's position after the animation has finished, you need to actually adjust the View's LayoutParams to match your desired effect. To achieve this, you can override the onAnimationEnd method of the Animation, and adjust the LayoutParams of the View inside there.
Once you adjust the LayoutParams, you can remove your call to setFillAfter(true) and your View will actually stay where you expect it to.
Regarding the flicker issue:
I have encountered this issue before, and it stems from the possibility of the onAnimationEnd() call not syncing up with the next layout pass. Animation works by applying a transformation to a View, drawing it relative to its current position.
However, it is possible for a View to be rendered after you have moved it in your onAnimationEnd() method. In this case, the Animation's transformation is still being applied correctly, but the Animation thinks the View has not changed its original position, which means it will be drawn relative to its ENDING position instead of its STARTING position.
My solution was to create a custom subclass of Animation and add a method, changeYOffset(int change), which modifies the y translation that is applied during the Animation's applyTransformation method. I call this new method in my View's onLayout() method, and pass the new y-offset.
Here is some of my code from my Animation, MenuAnimation:
/**
* Signal to this animation that a layout pass has caused the View on which this animation is
* running to have its "top" coordinate changed.
*
* #param change
* the difference in pixels
*/
public void changeYOffset(int change) {
fromY -= change;
toY -= change;
}
#Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
float reverseTime = 1f - interpolatedTime;
float dy = (interpolatedTime * toY) + (reverseTime * fromY);
float alpha = (interpolatedTime * toAlpha) + (reverseTime * fromAlpha);
if (alpha > 1f) {
alpha = 1f;
}
else if (alpha < 0f) {
alpha = 0f;
}
t.setAlpha(alpha);
t.getMatrix().setTranslate(0f, dy);
}
And from the View class:
private int lastTop;
// ...
#Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
// the animation is expecting that its View will not be moved by the container
// during its time period. if this does happen, we need to inform it of the change.
Animation anim = getAnimation();
if (anim != null && anim instanceof MenuAnimation) {
MenuAnimation animation = (MenuAnimation) anim;
animation.changeYOffset(top - lastTop);
}
// ...
lastTop = top;
super.onLayout(changed, left, top, right, bottom);
}
Crucero has it right about setFillAfter not adjusting params post invalidation. When the view is re-layed out (which'll happen the pass after it's invalidated), its layout params will be the ones that always applied, so it should go back to the original position.
And Jschools is right about onAnimationEnd. Strongly encourage you to step through the source code with a debugger, where you'll instructively discover that an update is made that affects the drawn position of the view after onAnimationEnd is fired, at which point you've actually applied the layout params, hence the flicker caused by doubled up offset.
But this can be solved quite simply by making sure you relayout at the right time. You want to put your re-positioning logic at the end of the ui message queue at the time of animation end so that it is polled after the animation but before laying out. There's nowhere that suggests doing this, annoyingly, but I've yet find a reason in any release of the SDK reason why (when doing this just once and not incorrectly using ui thread) this shouldn't work.
Also clear the animation due to another issue we found on some older devices.
So, try:
#Override
public void onAnimationEnd(final Animation animation) {
myViewPager.post(new Runnable() {
#Override
public public void run() {
final RelativeLayout.LayoutParams lp = (android.widget.RelativeLayout.LayoutParams) myViewPager.getLayoutParams();
if (!someBooleanIPresume)
lp.setMargins(300,0,0,0);
else
lp.setMargins(0,0,0,0);
myViewPager.setLayoutParams(lp);
myViewPager.clearAnimation();
}
}
I am using multiple animations and playing animations one after other. Currently using onAnimationEnd() to play animations one after other. In case of onTouch, I need to stop the animation and need to set different bitmap to imageview in touch location. Currently using below code but facing problems in case of clearAnimation().
#Override
public boolean onTouch(View v, MotionEvent event) {
switch(event.getAction()) {
case MotionEvent.ACTION_UP:
imageArray[g_animCount - 1].clearAnimation();
break;
default:
break;
}
return true; // indicate event was handled
}
#Override
public void onAnimationEnd(Animation animation) {
layout.removeView(imageArray[g_animCount - 1]);
if ( (g_animCount < count))
{
startNextAnimation();
}
else
{
g_animCount = 0;
isBegin = false;
}
}
Problems and queries:
After clear animation, I could see image again at the beginning location, how to keep it at touch location ? tried setFillAfter(true) but no use.
How to define onAnimationEnd() in case to play animations one after other ? do we need to remove imageview?
Without clearAnimation() I do not have any problems but it does not solve my problem. Kindly correct my code.
1) to keep the ImageView to the new location you have to specify the new coordinates.
You can do it in this way:
MarginLayoutParams l = new MarginLayoutParams(v.getLayoutParams());
l.leftMargin = left;
l.topMargin = top;
v.setLayoutParams(l);
v is your ImageView, left and top are the coordinates (x,y) to place the view on the screen. The parent should be a RelativeLayout.
2) it's better to start all the animation in sequence avoid calling new start in the callback methods (onAnimationEnd, etc). You don't need to remove the view if you have to use it later in the flow
My reply may be a bit out of date, but better later than never.
I also noticed this nasty bug: when you call cancelAnimation, the AnimationListener.onAnimationEnd is called, however the view stays and there is no "legal" ways to hide it (tried remove view from layout, set visibility to View.INVISIBLE, even negative marginTop and marginBottom - nothing works). The bug is present with Andoid 2.3 and Android 4.0. It doesn't turn up with KitKat, presumably has been fixed.
Here's the good news. I've found a work around with ImageView: in AnimationListener.onAnimationEnd I say animationView.setImageDrawable(null) and the view disappears. Hopefully this will help someone.
I would like to create an deferred loading adapter for use with a Gallery widget.
That is to say getView() returns an ImageView immediately, and later some other mechanism will asynchronously call its setImageBitmap() method. I did this by creating a "lazy" ImageView that extends ImageView.
public class GalleryImageView extends ImageView {
// ... other stuff here ...
public void setImage(final Looper looper, final int position) {
final Uri uri = looper.get(position);
final String path = looper.sharePath(position);
new Thread(new Runnable() {
#Override
public void run() {
GalleryBitmap gbmp = new GalleryBitmap(context, uri, path);
final Bitmap bmp = gbmp.getBitmap(); // all the work is here
handler.post(new Runnable() {
#Override
public void run() {
if (GalleryImageView.this.getTag().equals(uri)) {
setImageBitmap(bmp);
}
}
});
}
}).start();
}
}
When I scroll slowly in the Gallery, the center image keeps popping into the center. It's hard to explain, exactly, but it's really annoying. I also tried the same approach for a spinner adapter and it works perfectly there.
Any ideas?
The solution is to implement a more intelligent method of when to fetch thumbnails - it is pointless fetching thumbnails while the user is flinging through the list. Essentially you want something like that implemented in Romain Guy's Shelves application.
To get the most responsive Gallery you'll need to implement some form of in-memory cache and do the following:
Only set an image if it exists in the in-memory cache from your getView. Set a flag indicating whether the image was set or whether a download is required. You could also maintain a memory in a cache on the SD card and internal memory, and if a fling is not currently ongoing then show a low res (inSampleSize set to 16 or 8) version which will be visible when just scrolling through - the high res version will load when the user lets go and settles on an image.
Add an OnItemSelectedListener (and make sure to call setCallbackDuringFling(false) when initializing) that downloads new thumbnails for all the visible items that require a download only if the users finger is up (you can use getFirstVisiblePosition and getLastVisiblePosition to find the range of views visible)
Also when the user lifts their finger check to see 1. if the selected position changed since the user put their finger down and if so 2. whether a download was initiated due to your OnItemSelectedListener - if it wasn't then initiate one. This is to catch the case where no flinging occurs, and thus OnItemSelected never does anything because it is always called with the finger down in this situation. I'd use a Handler to delay starting the downloading by the animation time of your gallery (make sure to clear any delayed messages posted to this handler whenever onItemSelected is called or when you get an ACTION_DOWN event.
After an image is downloaded check if any visible views requested this image then and update those views
Also be aware that the default Gallery component does not properly implement View recycling (it assumes each position in the adapter has a unique view, and also clears the recycler of these items when they go offscreen making it pretty pointless). Edit: on more looking it isn't pointless - but it's not a recycler in terms of next/previous views, rather it serves to avoid having to call getView for the current views during layout changes.
This means the convertView parameter passed to your getView method will more often that not be null, meaning you'll be inflating a lot of views (which is expensive) - see my answer to Does a replacement for Gallery with View recycling exist? for some hints on that. (PS: I have since modified that code - I would use a different recycle bin for layout phases and scroll phases, in the layout phase place and retrieve the views in the layout recycle bin according to their position, and DO NOT call getView if the view you get from the bin is non-null since it will be exactly the same view; also clear the layout recycle bin after the layout phase -- this makes things a bit more snappier)
PS: Also be very careful with what you do in OnItemSelected - namely unless it's in the places mentioned above then try to do as little as possible. For instance I was setting some text in a TextView above my Gallery in OnItemSelected. Just moving this call into the same points as where I updated thumbnails made a noticable difference.
I have an answer for you!
When any of the setImage... methods are called on ImageView in internally a layout pass is requested, for example, setImageBitmap() as above is defined as such
public void setImageBitmap(Bitmap bm) {
setImageDrawable(new BitmapDrawable(mContext.getResources(), bm));
}
which calls
public void setImageDrawable(Drawable drawable) {
if (mDrawable != drawable) {
mResource = 0;
mUri = null;
updateDrawable(drawable);
requestLayout(); //layout requested here!
invalidate();
}
}
which has the effect of the gallery 'snapping' to the center of the image thats currently closest to the center of the gallery.
What I have done to prevent this is have the View thats loading into the Gallery have a explicit height and width (in dips) and using an ImageView subclass that ignores layout requests. This works as the gallery still has a layout pass initially but does not bother doing this every time an image in the gallery changes, which I imagine would only need to happen if the gallery views had their width and height set to WRAP_CONTENT, which we dont. Note that as invalidate() is still called in setImageDrawable() the image will still be drawn when set.
My very simple ImageView subclass below!
/**
* This class is useful when loading images (say via a url or file cache) into
* ImageView that are contained in dynamic views (Gallerys and ListViews for
* example) The width and height should be set explicitly instead of using
* wrap_content as any wrapping of content will not be triggered by the image
* drawable or bitmap being set (which is normal behaviour for an ImageView)
*
*/
public class ImageViewNoLayoutRefresh extends ImageView
{
public ImageViewNoLayoutRefresh(Context context, AttributeSet attrs, int defStyle)
{
super(context, attrs, defStyle);
}
public ImageViewNoLayoutRefresh(Context context, AttributeSet attrs)
{
super(context, attrs);
}
public ImageViewNoLayoutRefresh(Context context)
{
super(context);
}
#Override
public void requestLayout()
{
// do nothing - for this to work well this image view should have its dims
// set explicitly
}
}
edit: i should mention that the onItemSelected approaches can also work, but as I needed to hook into that while flinging was taking place I came up with the above, which I think is more flexible approach
This might be a bug in the Gallery's onLayout method. Check out http://code.google.com/p/android/issues/detail?id=16171 for a possible workaround.