Question

I am trying to create a menu that slides up from the bottom. It starts with the menu's view just visible at the bottom of the screen, and then clicking it causes it to slide up. I tried using a TranslateAnimation, but although the pixels move, the hit areas of the menu are in the same position as before. So I think that if I can adjust the menu's margins after the animation is complete, this will accomplish what I want. However, I can't figure out how to adjust the margins.

I've tried to create a LinearLayout.LayoutMargins object and then set its margins and apply it to the menu's view (which is a LinearLayout), but this doesn't work.

Any ideas?

Was it helpful?

Solution 2

My solution was to create two LinearLayouts, one in it's up state (set to gone) and the other in the menu's down state. Then when the user clicks on the button to slide the menu up, I call a TranslateAnimation showing the menu slide up. I put a listener on the animation that causes the up state to be visible and the down state to be gone when the animation finishes. I reversed this for the "closing" action.

Not exactly the way I had originally imagined doing it, but it worked.

OTHER TIPS

The following worked for me. First decide the bottom margins (in dips) for the menu being up (completely visible) or down (most of it hidden).

private static final int BOTTOM_MARGIN_UP = -50; // My menu view is a bit too tall.
private static final int BOTTOM_MARGIN_DOWN = -120;

Then, in onCreate():

menuLinearLayout = (LinearLayout)findViewById(R.id.menuLinearLayout);
setBottomMargin(menuLinearLayout, BOTTOM_MARGIN_DOWN);

upAnimation = makeAnimation(BOTTOM_MARGIN_DOWN, BOTTOM_MARGIN_UP);
downAnimation = makeAnimation(BOTTOM_MARGIN_UP, BOTTOM_MARGIN_DOWN);

Button toggleMenuButton = (Button)findViewById(R.id.toggleMenuButton);
toggleMenuButton.setOnTouchListener(new View.OnTouchListener()
{
    public boolean onTouch(View view, MotionEvent motionEvent)
    {
        if (motionEvent.getAction() != MotionEvent.ACTION_DOWN) return false;
        ViewGroup.MarginLayoutParams layoutParams =
            (ViewGroup.MarginLayoutParams)menuLinearLayout.getLayoutParams();
        boolean isUp = layoutParams.bottomMargin == dipsToPixels(BOTTOM_MARGIN_UP);
        menuLinearLayout.startAnimation(isUp ? downAnimation : upAnimation);
        return true;
    }
});

And here comes the secret sauce ;-)

private TranslateAnimation makeAnimation(final int fromMargin, final int toMargin)
{
    TranslateAnimation animation = 
        new TranslateAnimation(0, 0, 0, dipsToPixels(fromMargin - toMargin));
    animation.setDuration(250);
    animation.setAnimationListener(new Animation.AnimationListener()
    {
        public void onAnimationEnd(Animation animation)
        {
            // Cancel the animation to stop the menu from popping back.
            menuLinearLayout.clearAnimation();

            // Set the new bottom margin.
            setBottomMargin(menuLinearLayout, toMargin);
        }

        public void onAnimationStart(Animation animation) {}

        public void onAnimationRepeat(Animation animation) {}
    });
    return animation;
}

I use two utility functions:

private void setBottomMargin(View view, int bottomMarginInDips)
{
    ViewGroup.MarginLayoutParams layoutParams =    
        (ViewGroup.MarginLayoutParams)view.getLayoutParams();
    layoutParams.bottomMargin = dipsToPixels(bottomMarginInDips);
    view.requestLayout();
}

private int dipsToPixels(int dips)
{
    final float scale = getResources().getDisplayMetrics().density;
    return (int)(dips * scale + 0.5f);
}

Voila!

Use the ViewPropertyAnimator:

if (view.getY() != margin) {
    view.animate().y(margin).setDuration(150).start();
}

I feel like this is a bit less of a hack. Basically, it's making your own animator. Below it's setup for only modifying topMargin in a RelativeLayout, but it wouldn't be hard to generalize it.

import java.util.Calendar;

import android.app.Activity;
import android.util.Log;
import android.view.View;
import android.view.animation.Interpolator;
import android.widget.RelativeLayout.LayoutParams;

public class MarginAnimation extends Thread {
    final String TAG = "MarginAnimation";
    long mStartTime;
    long mTotalTime;
    Interpolator mI;

    int mStartMargin;
    int mEndMargin;

    View mV;
    Activity mA;
    public MarginAnimation(Activity a, View v, 
            int startMargin, int endMargin, int totaltime,
            Interpolator i) {
        mV = v;
        mStartMargin = startMargin;
        mEndMargin = endMargin;
        mTotalTime = totaltime;
        mI = i;
        mA = a;
    }

    @Override
    public void run() {
        mStartTime = Calendar.getInstance().getTimeInMillis();
        while(!this.isInterrupted() && 
                Calendar.getInstance().getTimeInMillis() - mStartTime < mTotalTime) {

            mA.runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    if(MarginAnimation.this.isInterrupted())
                        return;
                    long cur_time = Calendar.getInstance().getTimeInMillis();
                    float perc_done = 1.0f*(cur_time-mStartTime)/mTotalTime;

                    final int new_margin = (int)(1.0f*(mEndMargin-mStartMargin)*mI.getInterpolation(perc_done)) + mStartMargin;
                    LayoutParams p = (LayoutParams) mV.getLayoutParams();
                    Log.v(TAG, String.format("Setting Margin to %d", new_margin));
                    p.topMargin = new_margin;
                    mV.setLayoutParams(p);                  
                }
            });

            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                return;
            }
        }
    }

}

This post helped me get my animation working. In my case, I have a full screen webview. On some user action, this webview slides to the right, about 70% and a new webview emerges from the left and takes the available space. End result, a new webview covers the 70% (x-axis) and the old webview the rest. Arne's solution helped me immensely, setting up the margin to the old view as soon as the animation is done. Pseudo code:

       marginParams.leftMargin = 336; //(70% of 480px)

But I faced a weird behavior, I guess my old webview (which now only occupies 30% space), was reformatting its contents, may be thinking that it is now squeezed into a smaller space, rather than behaving as if its just slid to the right. In other words, as soon as I set this margin, the html layout of the webview changed. Again, I had no idea why and my guess is it thought its parent window size changed. Based on that assumption, I added one more line of code:

      marginParams.rightMargin = -336; //(same amount, but negative margin!)

And that did the trick, no reformatting of the html contents and I can interact with both webviews in parallel.

I am posting this as a big Thank you to Arne for the idea and also to get any inputs for the behavior I saw and my assumptions for it. I actually like the end solution, makes logical sense, but I may be wrong . . . any thoughts and input would be greatly appreciated. Thank you very much.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top