Android -- How to position View off-screen? - android

I'm trying to animate a simple ImageView in my application and I want it to slide in from the bottom of the screen and come to a resting position where the top 50px of the view is off the top of the screen (e.g. the final position of the ImageView should be -50px in X). I've tried to use the AbsoluteLayout to do this, but this actually cuts off the top 50px of the ImageView such that the top 50px is never rendered. I need to have the top 50px of the ImageView visible/rendered while it's animating and then simply have it come to a rest slightly off-screen. I hope I've explained that well enough.
Here is what I'm currently using as a layout and the slide-in animation (this currently doesn't render the top 50px of the ImageView):
Layout:
<?xml version="1.0" encoding="utf-8"?>
<AbsoluteLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_height="fill_parent"
android:layout_width="fill_parent"
android:id="#+id/QuickPlayClipLayout">
<ImageView android:id="#+id/Clip"
android:background="#drawable/clip"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_y="-50dp">
</ImageView>
</AbsoluteLayout>
Animation:
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate android:fromYDelta="100%p"
android:toYDelta="0"
android:duration="1000"/>
<alpha android:fromAlpha="0.0"
android:toAlpha="1.0"
android:duration="1000" />
</set>

Instead we can simple give negative values to layout_margin(Top/left/right/bottom)
eg: If you want your view to be off from top of the screen you can specify
android:layout_marginTop="-40dp"

I figured out a solution to this that should be easy to implement. It involves modifying the layout and the Activity inflating the layout... see below:
Activity (QuickPlay.java):
public class QuickPlay extends Activity implements AnimationListener
{
private ImageView myImageView;
private LinearLayout LL;
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
this.setContentView(R.layout.quick_play_screen);
myImageView = (ImageView) this.findViewById(R.id.Clip);
LL = (LinearLayout) this.findViewById(R.id.QuickPlayClipLayout);
//finally
Animation anim = AnimationUtils.loadAnimation(this, R.anim.slide_in_quickplay);
anim.setAnimationListener(this);
LL.startAnimation(anim);
}
#Override
public void onAnimationEnd(Animation animation){}
#Override
public void onAnimationRepeat(Animation animation){}
#Override
public void onAnimationStart(Animation animation)
{
// This is the key...
//set the coordinates for the bounds (left, top, right, bottom) based on the offset value (50px) in a resource XML
LL.layout(0, -(int)this.getResources().getDimension(R.dimen.quickplay_offset),
LL.getWidth(), LL.getHeight() + (int)this.getResources().getDimension(R.dimen.quickplay_offset));
}
}
New LinearLayout (CustomLinearLayout.java):
public class CustomLinearLayout extends LinearLayout
{
private Context myContext;
public CustomLinearLayout(Context context, AttributeSet attrs) {
super(context, attrs);
myContext = context;
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
super.onMeasure(widthMeasureSpec, heightMeasureSpec+((int)myContext.getResources().getDimension(R.dimen.quickplay_offset)));
}
}
Layout (/res/layout/quick_play_screen.xml):
<?xml version="1.0" encoding="utf-8"?>
<com.games.mygame.CustomLinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_height="fill_parent"
android:layout_width="fill_parent"
android:id="#+id/QuickPlayClipLayout">
<ImageView android:id="#+id/Clip"
android:background="#drawable/clip"
android:layout_width="fill_parent"
android:layout_height="wrap_content">
</ImageView>
</com.games.mygame.CustomLinearLayout>
Resource (/res/values/constants.xml):
<?xml version="1.0" encoding="utf-8"?>
<resources>
<dimen name="quickplay_offset">50dp</dimen>
</resources>
Animation (/res/anim/slide_in_quickplay.xml):
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate android:fromYDelta="100%p"
android:toYDelta="0"
android:duration="1000"/>
<alpha android:fromAlpha="0.0"
android:toAlpha="1.0"
android:duration="1000" />
</set>
The program now does exactly what I need it to do. The entire layout starts off screen at the bottom, slides in in 1 sec and comes to a rest where the top of the layout is actually 50px off the top of the screen (i.e. LL.getTop() = -50) and the bottom of the layout is resting at the bottom of the screen (i.e. LL.getBottom() = 530 = 480 + 50).

To position my view offscreen I used the following code:
View myView = /* view you want to position offscreen */
int amountOffscreen = (int)(myView.getWidth() * 0.8); /* or whatever */
boolean offscreen = /* true or false */
int xOffset = (offscreen) ? amountOffscreen : 0;
RelativeLayout.LayoutParams rlParams =
(RelativeLayout.LayoutParams)myView.getLayoutParams();
rlParams.setMargins(-1*xOffset, 0, xOffset, 0);
myView.setLayoutParams(rlParams);
This code will position myView offscreen by amountOffscreen, which in this case puts 80% of the view offscreen leaving only 20% onscreen.
Do not use the layout() method directly - Android will make subsequent calls to invalidate your view for random reasons and only layoutParams are persisted across invalidate calls. If you are curious, check out lines 904 to 912 of this file to see why you have to modify the layoutParams.

This is easy to do if you make to leap to using a Canvas; those support drawing off the screen without any trouble. However, it will be a more complicated to implement. You'd need to implement a custom View and write your own animation in code. Essentially this comes down to simple 2D graphics handling rather than Views using built-in XML animations.
There may be a way to do it with XML, but I'm much more familiar with canvases. A very good place to see how this is handled in code is the Lunar Lander example game that comes with the SDK.
Roughly the steps you'll need to follow are:
Put a custom view in the XML file, using something like <your.package.AnimatingView>, setting its size to fill-parent.
Then define a AnimatingView class, which extends SurfaceView and implements SurfaceHolder.Callback. (This gives you access to the drawing Canvas immediately rather than by using the invalidate() method. This is important because invalidate() only refreshes when the thread is idle, e.g. at the end of the loop. To implement your animation, you need to have it drawing immediately.)
You can then implement a loop which draws your moving image across the screen. The loop needs to start by drawing the whole background (because the canvas doesn't automatically get erased) and then draw the image at its new position based on the time that has passed. For example, if you want your animation to take 1 second to do, then you know that if 200ms have passed, the view should only have moved 200/1000, or 1/5, of the way from its starting position to the final position.
You can see some examples of what I mean in my other answers to animation questions: basic reply about the usefulness of using SurfaceView and and example of the loop to use. Note: the second question was about rotating, and hence some of the difficulties I talked about won't be relevant to you. Good luck!

With android:translationX and android:translationY
<RelativeLayout
android:translationX="-600dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent">

I had a viewgroup with children and was dragging the view into the screen from the bottom - kind of like a drawer. I then would let go and if the viewgroup top margin were in the top half of the screen I would animate it to the top after the user released the touch.
When this happened the images in the viewgroup's children would get cropped during the animation but would then get shown after the animation.
The problem: viewgroup's height was wrap_content. I solved this by setting the height to a value that stretched off the screen before the animation started.

Related

Scale animation with small "rebound"

I am attempting to use XML Animations to scale a view up from 0% size to 150% size (50% bigger than "real size"). Then scale back down from 150% size to "real size" of 100%. The intention is a bit of a "rebound" effect where the view will grow up from nothing to bigger than it's supposed to be and then "rebound" or "snap back" to the proper size.
Here is an image that illustrates of timeline of what I would like to accomplish.
I am using this XML animation code:
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
>
<scale android:fromXScale="0"
android:fromYScale="0"
android:toXScale="1.5"
android:toYScale="1.5"
android:duration="200"
android:pivotX="50%"
android:pivotY="100%"
android:fillAfter="false"
/>
<scale android:fromXScale="1.5"
android:fromYScale="1.5"
android:toXScale="1.0"
android:toYScale="1.0"
android:duration="100"
android:pivotX="50%"
android:pivotY="100%"
android:startOffset="200"
/>
</set>
The problem that I am running into is that after the first scale animation completes the view is correctly at 150% size, but when it runs the second animation it is scaling relative to the size that we finished the first animation at (150%) as the "real size" 100%, or 1.0.
So when the second animation runs instead of my view scaling back down from 150% to the real size of 100% it is actually scaling from 150% of 150% (225%) back down to 150%. Which means that at the end of the animation the view is still too large, sized at 150% of the real size in my case.
Is there some way to instruct the second Animation or the Animation set to ignore the ending value from the first animation and instead just use the "real" size of the view so that the code would be more literally interpreted and would scale from 150% of real size down to 100% of real size.
I have tried every combination of fillBefore, fillAfter, and fillEnabled that I can think of on each scale animation and on the set itself, but thus far I've been unable to find a combination of those settings that will make it behave how I intend for it to.
I found This Article which seems like it may be relevant to my situation, and this led me to test out some more combinations of the various fill properties. But so far I'm still not able to get the outcome I am hoping for.
shouldnt a simple Interpolator for the animation work, I mean you do not have as much fine control over the animation with the exact timings but something like this should do a similar trick without that much work:
<scale android:fromXScale="0"
android:fromYScale="0"
android:toXScale="1.0"
android:toYScale="1.0"
android:duration="300"
android:pivotX="50%"
android:pivotY="100%"
android:fillAfter="true"
android:interpolator="#android:anim/overshoot_interpolator"
/>
Facebook has a java library called Rebound that models spring dynamics and adds real world physics. Example code:
import com.facebook.rebound.BaseSpringSystem;
import com.facebook.rebound.SimpleSpringListener;
import com.facebook.rebound.Spring;
import com.facebook.rebound.SpringSystem;
import com.facebook.rebound.SpringUtil;
/**
* This Activity presents an ImageView that scales down when pressed and returns to full size when
* released. This demonstrates a very simple integrates a very Simple integration of a Rebound
* Spring model to drive a bouncy animation as the photo scales up and down. You can control the
* Spring configuration by tapping on the blue nub at the bottom of the screen to reveal the
* SpringConfiguratorView. From this view you can adjust the tension and friction of the animation
* spring and observe the effect these values have on the animation.
*/
public class MainActivity extends Activity {
private final BaseSpringSystem mSpringSystem = SpringSystem.create();
private final ExampleSpringListener mSpringListener = new ExampleSpringListener();
private FrameLayout mRootView;
private Spring mScaleSpring;
private View mImageView;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
mRootView = (FrameLayout) findViewById(R.id.root_view);
mImageView = mRootView.findViewById(R.id.image_view);
// Create the animation spring.
mScaleSpring = mSpringSystem.createSpring();
// Add an OnTouchListener to the root view.
mRootView.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
// When pressed start solving the spring to 1.
mScaleSpring.setEndValue(1);
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
// When released start solving the spring to 0.
mScaleSpring.setEndValue(0);
break;
}
return true;
}
});
}
#Override
public void onResume() {
super.onResume();
// Add a listener to the spring when the Activity resumes.
mScaleSpring.addListener(mSpringListener);
}
#Override
public void onPause() {
super.onPause();
// Remove the listener to the spring when the Activity pauses.
mScaleSpring.removeListener(mSpringListener);
}
private class ExampleSpringListener extends SimpleSpringListener {
#Override
public void onSpringUpdate(Spring spring) {
// On each update of the spring value, we adjust the scale of the image view to match the
// springs new value. We use the SpringUtil linear interpolation function mapValueFromRangeToRange
// to translate the spring's 0 to 1 scale to a 100% to 50% scale range and apply that to the View
// with setScaleX/Y. Note that rendering is an implementation detail of the application and not
// Rebound itself. If you need Gingerbread compatibility consider using NineOldAndroids to update
// your view properties in a backwards compatible manner.
float mappedValue = (float) SpringUtil.mapValueFromRangeToRange(spring.getCurrentValue(), 0, 1, 1, 0.5);
mImageView.setScaleX(mappedValue);
mImageView.setScaleY(mappedValue);
}
}
}

In the deltaX of an Android animation, should I use pixels or percentage?

I have, in design view, placed a view right outside of the right boundary of the android device screen. I want to play an animation and move it from outside of the screen into the screen to make an entrance effect, nothing fancy.
However I've been struggling with the deltaX parameters ever since. What is the correct numbers to put down? Shall I go with pixels or percentage?
When I give the correct input, the correct event was fired but the view is nowhere to be found, it doesn't show up on screen at all.
Here is my problematic code:
Animation:
<?xml version="1.0" encoding="utf-8"?>
<set
xmlns:android="http://schemas.android.com/apk/res/android"
android:interpolator="#android:anim/linear_interpolator"
android:fillAfter="true">
<translate
android:fromXDelta="100%"
android:toXDelta="0%"
android:duration="300" />
</set>
The view in question:
<GridView
android:id="#+id/android_gridview_menu"
android:layout_width="80dp"
android:layout_height="0dp"
android:background="#ffffff"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.0"
app:layout_constraintLeft_toRightOf="#+id/MainUI"
android:layout_marginLeft="0dp">
First of all translate the grid view out of the screen and then play animation
// Translating grid view out of the screen
GridView gridView = findViewById(R.id.android_gridview_menu);
Display display = getWindowManager().getDefaultDisplay();
Point size = new Point();
display.getSize(size);
gridView.setTranslationX(size.x);
// Call this method to bring grid view from right by animation
private void playEnterAnimation(){
GridView gridView = findViewById(R.id.android_gridview_menu);
gridView.animate().translationX(0).setDuration(200).setInterpolator(new AccelerateDecelerateInterpolator()).start();
}
To play an animation use Animation and AnimationUtils.
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Animation animation = AnimationUtils.loadAnimation(getApplicationContext(),
R.anim.animation); // R.anim.animation is your animation xml.
GridView layout = (GridView) findViewById(R.id.android_gridview_menu);
layout.startAnimation(animation);
}
Hope it help!

Strange behaviour when adding and animating imageViews in android

I've got a LinearLayout with a background bitmap. I'm adding 2-3 images to this layout with an animation. The problem is: everytime im adding images the FIRST image animation starts from the top left corner, for every other image directly after that animation it works just fine from the center. Im struggling for days to find a solution for this little annoying problem. Do u have any advise what could cause this?
Here my problem in a gif I made: http://imgur.com/FCPgof1
my animation:`
<scale
android:duration="750"
android:fillAfter="false"
android:fromXScale="0"
android:fromYScale="0.0"
android:interpolator="#android:anim/accelerate_decelerate_interpolator"
android:pivotX="50%"
android:pivotY="50%"
android:toXScale="1.0"
android:toYScale="1.0" />
`
my layout:
<LinearLayout
android:id="#+id/layoutCompareUppoints"
android:layout_width="140dp"
android:layout_height="28dp"
android:background="#drawable/uppballsbgnew" >
</LinearLayout>
my code:
`private void animUppointsBefore() {
if (k < Integer.valueOf(uppoints)) {
Animation a = AnimationUtils.loadAnimation(this,
R.anim.debug_grow_fast_animation);
ImageView upp = new ImageView(this);
upp.setImageResource(R.drawable.uppballlightnew);
upp.setAdjustViewBounds(true);
uppLayout.addView(upp);
upp.startAnimation(a);
a.setAnimationListener(new AnimationListener() {
#Override
public void onAnimationStart(Animation a) {
// TODO Auto-generated method stub
}
#Override
public void onAnimationRepeat(Animation a) {
// TODO Auto-generated method stub
}
#Override
public void onAnimationEnd(Animation a) {
// TODO Auto-generated method stub
k++;
animUppointsBefore();
}
});
}
}`
The issue you're most likely seeing is the image is getting added to the layout before any animation actually takes place. The image is going to start full size, shrink down to 0, then grow up to 1.
What you want to do is make it invisible before you add it then make it appear off screen. A couple of methods you can do, the usual way is to set the ImageView to View#INVISIBLE then with an Animation#AnimationListener, set the view to View#VISIBLE on animation start.
If you can, using Animators are way easier (There are some libraries in the support library that don't allow this). You can set the View's Alpha to 0 before adding it, then just set it to 1 instantly as part of the animation. Just make two ObjectAnimators, one for alpha and one for scale, then combine them in to a single AnimatorSet.
EDIT:
To put the final answer from comments to here.
Rather than adding the views just before animating, it may help to put the image views already laid out in the layout but invisible. That way, there doesn't need to be a new layout and measure pass that could be screwing up the measurements to the pivot point.

Animate only the "src" alpha attribute of ImageView instead of the entire view

I have an ImageView, initially empty. Its background is set to grey:
<ImageView
android:background="#333" />
After I fetch its bitmap from the internets, I want to set it as the src property, but fade it in gracefully. The animation examples I've seen for this do it like:
// fade_in.xml
<set xmlns:android="http://schemas.android.com/apk/res/android" >
<alpha
android:duration="2000"
android:fromAlpha="0.0"
android:interpolator="#android:anim/accelerate_interpolator"
android:toAlpha="1.0" />
</set>
but this is animating the opacity of the entire ImageView. It has the effect of first hiding the ImageView before fading it in, so you see a little pop at the start of the animation is the view as first set to alpha=0.
Is there a way to animate the opacity of just the "src" attribute instead of the entire ImageView?
Thanks
Animations on imageView's source image is tricky and in most cases involve wraping imageView into some ViewGroup, but if all you want to do is animate its alpha (fade in/out), it is very easy to do using a TransitionDrawable:
TransitionDrawable transitionDrawable = new TransitionDrawable(new Drawable[]{
new ColorDrawable(Color.TRANSPARENT),
getResources().getDrawable(R.drawable.my_image)
});
imageview.setImageDrawable(transitionDrawable);
transitionDrawable.startTransition(300);
Wrap the ImageView inside a ViewGroup such as LinearLayout which will wrap_content and will have all the settings you want to keep visible, such as the background color. Something like this:
<LinearLayout
android:layout_width='wrap_content'
android:layout_height='wrap_content'
android:background='#ff0000'>
<ImageView src='#drawable/ic_launcher'
android:layout_width='wrap_content'
android:layout_height='wrap_content'/>
</LinearLayout>
Which will result in the following results with an alpha of 0.5 and 1 respectively.
You have 2 options:
1) Put a transparent ImageView in a gray container (easy). The simplest container is FrameLayout.
2) Animate the alpha of the contained image with the ImageView.setAlpha(int) method. This method takes an integer as parameter, that ranges from 0 (fully transparent) to 255 (fully opaque). To animate a custom property like this, you'll need to use an ObjectAnimator or NineOldAndroids.

Multiple calls to invalidate with different Rects

I am extending my root layout so I can drawBitmap 4 arrows on it, one on each corner of the screen. These arrows will occassionally Blink at the same time.
I am doing this as an optimization to avoid creating 4 additional Views, so I guess it only makes sense if the areas are independently invalidated, because if the whole layout is invalidated & repainted, the performance loss will probably be bigger than the savings, as the root layout contains heavy stuff. I suppose is a good idea, but if anybody thinks it's not, please tell me why.
I tried to call invalidate(Rect) in the same function with each rect:
public MyRootViewGroup extends FrameLayout {
#Override
public void draw (Canvas c) {
super.draw(c); // Draw Viewgroup Children
draw_arrows(); // Paint Arrows over children
invalidate(rect_arrow_left);
invalidate(rect_arrow_right);
invalidate(rect_arrow_bottom);
invalidate(rect_arrow_top);
}
... but unfortunately it seems that only one region gets invalidated with this approach. I need to invalidate the whole layout to make it work.
Question If this FrameLayout contains other children (that get drawn in super.draw() ) but they don't change, will my call to invalidate() cause them all to be repainted? Or Android is somehow smart to detect that only the arrow region has changed despite triggering a full invalidate? Am I optimizing and saving 4 views, or am I breaking Android optimization pipeline?
Regards!
use this code in your next Activity onCreate() method
ImageView imageView1 = (ImageView)findViewById(R.id.imageView1);
ImageView imageView2 = (ImageView)findViewById(R.id.imageView2);
ImageView imageView3 = (ImageView)findViewById(R.id.imageView3);
ImageView imageView4 = (ImageView)findViewById(R.id.imageView4);
Animation animBlank = AnimationUtils.loadAnimation(getApplicationContext(), R.anim.blank);
imageView1.startAnimation(animBlank);
imageView2.startAnimation(animBlank);
imageView3.startAnimation(animBlank);
imageView4.startAnimation(animBlank);
Create an xml file which defines type of animation to perform. This file should be located under anim folder under res directory (res ⇒ anim ⇒ blink.xml). If you don’t have anim folder in your res directory create one.
put following code in blink.xml file
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<alpha android:fromAlpha="0.0"
android:toAlpha="1.0"
android:interpolator="#android:anim/accelerate_interpolator"
android:duration="600"
android:repeatMode="reverse"
android:repeatCount="infinite"/>
</set>

Categories

Resources