Android UI: Positioning of custom GIF View within RelativeLayout
<p>So, I have created my own custom GIFView component to show animated GIFs. My problem is that the Android wont lay it correctly to the center position. I tried to look for this from various sources, but most were about how to do this dynamically while I would like to do this purely with XML layouts.</p>

<p>The problem is this: </p>

<ol>
<li><p>If I do not overwrite "onMeasure()", then my GIFView seems to take all the space in the screen. This causes that all elements in the layout are drawn to the very top of the screen. My GIF is like a progress bar (height = 8px, width = 152px), so it does not take much height. Anyways the Android thinks so and gives it the whole screen (my interpretation based on MeasureSpecs given to onMeasure method). The animation is now just below the TextView, but not in the center area. Instead, it is located to the top left corner of the area.</p></li>
<li><p>If I do overwrite "onMeasure()", then the TextView is drawn correctly in the middle of the screen and the progress animation under it. But, also in this case the location of the animated GIF is top left of the area while it should be in the middle of it.</p></li>
<li><p>If I do overwrite the "onMeasure()", then set the measurements with call to "setMeasuredDimension()" as required, but do not call super.onMeasure(), then it behaves just like in case (1). Meaning that the layout takes the whole screen and my TextView can be found from the very top of the screen.</p></li>
</ol>

<p>I am not sure if I made any sense, so I try to give you the idea in mathematical sense here (I was not able to post screenshots because I am a new user). So, relative layout gives area just below TextView that is as wide as the screen (screenWidth) and as tall as required (paddingTop + paddingBottom + GIFView.getHeight()). Now I would like to see my animation to start drawing from position where: (x = paddingLeft + (screenWidth - paddingLeft - paddingRight - GIFView.getWidth())/2) and (y = paddingTop). Instead I see the Android drawing it to position (x = 0) and (y = 0).</p>

<p>I am sure the problem here is something that I have just overlooked, but I would really appreciate if someone had the time to look into this...</p>

<p>So, the snippets that I get this problem with:</p>

<p>Styleable declaration:</p>

<pre><code>&lt;declare-styleable name="GIFView"&gt;
    &lt;attr name="resourceId" format="reference" /&gt;
&lt;/declare-styleable&gt;
</code></pre>

<p>Main layout XML:</p>

<pre><code>&lt;RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
   android:layout_width="fill_parent"
   android:layout_height="fill_parent"
   android:orientation="vertical"
   android:paddingLeft="10dip"
   android:paddingRight="10dip"
   android:isScrollContainer="true"
   android:background="#EFEFEF" &gt;  

   &lt;RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
      xmlns:custom="http://schemas.android.com/apk/res/com.myown.smthg"
      android:layout_width="fill_parent"
      android:layout_height="wrap_content"
      android:layout_centerHorizontal="true"
      android:layout_centerVertical="true"
      android:orientation="vertical"
      android:background="@drawable/background_white_gray"
      android:paddingLeft="16dip"
      android:paddingRight="16dip"
      android:isScrollContainer="true" &gt;

  &lt;TextView
     android:id="@+id/TextViewWaitReason"
         android:layout_width="fill_parent"
         android:layout_height="wrap_content"
         android:layout_gravity="center"
         android:gravity="center"
         android:paddingTop="12dip"
         android:paddingLeft="12dip"
         android:paddingRight="12dip"
         android:textColor="#343434"
         android:textSize="20sp" /&gt;

  &lt;com.myown.smthg.util.GIFView
     custom:resourceId="@drawable/progress_bar"
     android:id="@+id/progressGIF"
     android:layout_below="@id/TextViewWaitReason"
     android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:layout_gravity="center"
         android:gravity="center"
         android:paddingTop="0dip"
         android:paddingLeft="24dip"
         android:paddingRight="24dip"
         android:paddingBottom="16dip" /&gt;
  &lt;/RelativeLayout&gt;
</code></pre>

<p></p>

<p>Then the code of GIFView.java:</p>

<pre><code>public class GIFView extends View {
// The resource id of the GIF
private int mResourceId;

// Movie to be shown
private Movie mMovie;
private long mStartTime;

// Size of this View
private int mHeight, mWidth;

/**
 * Constructor
 * 
 * @param context
 * @param attributes
 */
public GIFView( Context context, AttributeSet attributes ) {
    super( context, attributes );

    // Get the attribute for resource id
    TypedArray t = context.getTheme().obtainStyledAttributes( 
            attributes, R.styleable.GIFView, 0, 0 );

    mResourceId = -1;
    mMovie = null;
    mStartTime = 0;
    mHeight = 0;
    mWidth = 0;

    // This call might fail
    try
    {
        mResourceId = t.getResourceId ( R.styleable.GIFView_resourceId, -1 );

        mMovie = Movie.decodeStream( context.getResources().openRawResource( mResourceId ) );
        if( mMovie != null )
        {
            mWidth = mMovie.width();
            mHeight = mMovie.height();
        }
    }
    finally
    {
        t.recycle();
    }
}

@Override
protected void onMeasure (int widthMeasureSpec, int heightMeasureSpec)
{   
    final int desiredHSpec = MeasureSpec.makeMeasureSpec( mHeight, MeasureSpec.EXACTLY );
    final int desiredWSpec = MeasureSpec.makeMeasureSpec( mWidth, MeasureSpec.EXACTLY );
    setMeasuredDimension( desiredWSpec, desiredHSpec );

    super.onMeasure( desiredWSpec, desiredHSpec );
}

@Override
protected void onDraw( Canvas canvas )
{
    super.onDraw( canvas );

    // Return if we have no movie
    if( mMovie == null ) return;

    // Catch the time now
    long now = System.currentTimeMillis();

    // Catch the start time if needed
    if( mStartTime == 0 ) mStartTime = now;

    int relTime = (int)( (now- mStartTime) % mMovie.duration() );
    mMovie.setTime( relTime );
    mMovie.draw( canvas, 0, 0 );

    this.invalidate();
}
</code></pre>

<p>}</p>

<p>And if this is important, then the activity constructor looks like this:</p>

<pre><code>super.onCreate( savedInstanceState );
// Set the content view
setContentView( R.layout.layout_wait );

// Get the String to be shown
Intent intent = getIntent();
String waitStr = intent.getStringExtra( myService.EXTRA_TEXT );

// Set the done text
TextView t = (TextView)findViewById( R.id.TextViewWaitReason );
if( t != null &amp;&amp; waitStr != null )
    t.setText( waitStr );
</code></pre>

<p>So, why cannot I get that animation to run below the text and in the center of the screen? I will post the fix here if I can figure it out before... It has something to do with the onMeasure() method as overwriting it changes everything.</p>

<p>Disclaimer on snippets: I am not going to leave that invalidate() in the end of the onDraw()...</p>

<p>Br,
Teemu</p>
