66

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>
starball
  • 20,030
  • 7
  • 43
  • 238
RyanM
  • 5,680
  • 9
  • 45
  • 55

6 Answers6

37

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"
林果皞
  • 7,539
  • 3
  • 55
  • 70
arun
  • 463
  • 4
  • 4
  • 4
    Negative margin can be used with RelativeLayout and LinearLayout, but does not work with ConstraintLayout; this is according to Romain Guy, lead of the Android Graphics team at Google: https://stackoverflow.com/a/10673572/2559202 – Kevin Cronly Aug 09 '18 at 15:42
33

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).

Josh
  • 12,448
  • 10
  • 74
  • 118
RyanM
  • 5,680
  • 9
  • 45
  • 55
30

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.

Mike
  • 47,263
  • 29
  • 113
  • 177
esilver
  • 27,713
  • 23
  • 122
  • 168
  • This is a great technique, but it does result in flicker in myView when setLayoutParams is called on it (because setLayoutParams in turn calls requestLayout). Is there a good way to avoid flickering? – PacificSky Jun 06 '12 at 22:13
  • @PacificSky I remove and add the view to the layout, followed by invalidate(). That works for me, and I don't see any flickering. Code: layout.removeView(view);layout.addView(view, layoutParams);view.invalidate(); – Niels Nov 24 '12 at 16:53
  • Margins with minus values do the job :) – ArturOlszak Apr 03 '14 at 08:59
  • 3
    Warning: You cannot use this code in onCreate() because myView.getWidth() will always return 0, as the view has not been setup yet. The top two answers on this post give you two strategies for getting the proper width of the view: http://stackoverflow.com/questions/4142090/how-do-you-to-retrieve-dimensions-of-a-view-getheight-and-getwidth-always-r?rq=1 – Dick Lucas Mar 05 '15 at 20:22
  • It worked for me too. Make sure that you swap `-1*xOffset` with `xOffset` if you want your view to go off screen towards right. – Anuraag Vaidya Sep 14 '15 at 16:11
  • +1 _very_ cool. like in html. Maybe @Richard's note should be included in the answer so nobody forgets it. ;-) – palsch Dec 11 '15 at 16:46
9

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:

  1. Put a custom view in the XML file, using something like <your.package.AnimatingView>, setting its size to fill-parent.

  2. 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.)

  3. 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!

Community
  • 1
  • 1
Steve Haley
  • 55,374
  • 17
  • 77
  • 85
  • First off, thanks for replying. What you described above was what I thought I had to do too, but hoped there was some other/easier way of getting the same results. As it turns out, I did figured out an easier way (see my posted solution). It might not be the best or most elegant, but it works. – RyanM Apr 02 '10 at 02:43
8

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">
Pablo Cegarra
  • 20,955
  • 12
  • 92
  • 110
  • Didn't realize you could set translations from the xml, this was just what I needed. Thanks! – Dave May 08 '19 at 16:55
2

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.

Chris Sprague
  • 3,158
  • 33
  • 24