Android -- How to position View off-screen?

AndroidLayoutAnimationImageview

Android Problem Overview


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>

Thanks in advance.

Android Solutions


Solution 1 - Android

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"

Solution 2 - Android

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

Solution 3 - Android

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.

Solution 4 - Android

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!

Solution 5 - Android

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">

Solution 6 - Android

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.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionRyanMView Question on Stackoverflow
Solution 1 - AndroidarunView Answer on Stackoverflow
Solution 2 - AndroidRyanMView Answer on Stackoverflow
Solution 3 - AndroidesilverView Answer on Stackoverflow
Solution 4 - AndroidSteve HaleyView Answer on Stackoverflow
Solution 5 - AndroidPablo CegarraView Answer on Stackoverflow
Solution 6 - AndroidChris SpragueView Answer on Stackoverflow