Android - 在LinearLayout或RelativeLayout中动画化视图的topMargin/bottomMargin等

11

我正在尝试创建一个从底部滑出的菜单。它开始于屏幕底部只有菜单视图可见,然后点击它会使其向上滑动。我尝试使用TranslateAnimation,但虽然像素移动了,但菜单的点击区域仍在原来的位置。因此我认为如果在动画完成后可以调整菜单的边距,就可以实现我想要的效果。但是,我无法弄清如何调整边距。

我尝试创建了一个LinearLayout.LayoutMargins对象,然后设置了它的边距并将其应用到菜单的视图(即LinearLayout),但这没有起作用。

有什么想法吗?


没有 LinearLayout.LayoutMargins 类。我猜你指的是 LinearLayout.LayoutParams。如果你能够发布一下你正在调整参数的代码,那可能会帮助我们回答你的问题。 - CommonsWare
抱歉,你是对的,我指的是LayoutParams。最终我使用了另一种方法来实现我想要的效果。我建立了两个视图,一个用于“菜单打开”状态,另一个用于“菜单关闭”状态。然后我使用平移动画将打开的菜单向上滑动并隐藏关闭的菜单,反之亦然。 - karnage
5个回答

21

以下方法对我有效。首先决定菜单上下的底部边距(以dips为单位),分别对应于完全可见和隐藏大部分的状态。

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

然后,在 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;
    }
});

这里有个神奇的秘诀 ;-)

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;
}

我使用两个实用函数:

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);
}

就是这样!


很棒的答案。但是是否可以使用另一种在每个帧中修改边距的 Animation?我正在使用您的方法从底部滑入一个 Fragment,但它只会在动画结束时出现,当边距在 onAnimationEnd 中设置时(我认为它在动画期间不会被调整大小)。不过将其滑出效果很好。 - Jukurrpa
@Jukurrpa 抱歉,我一点都不清楚。我目前正在进行Android编程。 - Arne Evertsson

3

使用 ViewPropertyAnimator

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

3
我的解决方案是创建两个LinearLayout,一个在它的上方(设置为gone),另一个在菜单下方。然后当用户点击按钮将菜单向上滑动时,我调用了一个TranslateAnimation来显示菜单向上滑动。我在动画上设置了监听器,当动画结束时,使上状态可见,同时下状态不可见。关闭操作时,我反过来做同样的操作。虽然这不是我最初想象中的方式,但它确实有效。

2

我觉得这种方法不算是hack。基本上,它是在创建自己的动画器。下面的设置仅修改RelativeLayout中的topMargin,但很容易推广到其他情况。

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;
            }
        }
    }

}

这里不起作用,在Logcat中我可以看到它设置了几次边距,但只有最后一次对视图产生了影响。我尝试在mV.setLayoutParams(p)之后加上mV.invalidate(),但它没有起作用,有什么建议吗? - Paulo Cesar
我这样调用它:MarginAnimator ma = new MarginAnimator(EfeitosActivity.this, img2, lp.topMargin, 150, 500, new AccelerateInterpolator()); ma.run(); - Paulo Cesar
我曾经遇到过同样的问题 - 在AsyncTask中使用相同的思路对我有用。在run函数中的代码应该在doInBackground中运行,在uiThread runnable中的代码应该在onProgressUpdate函数中运行。 - Matt McMinn

1

这篇文章帮助我让我的动画正常工作。在我的情况下,我有一个满屏的 Webview。在一些用户操作后,这个 Webview 向右滑动约 70%,然后一个新的 Webview 从左侧出现并占用可用空间。最终结果是,新的 Webview 覆盖了 70%(x 轴),旧的 Webview 覆盖了其余部分。Arne 的解决方案对我帮助很大,当动画完成后,将边距设置为旧视图。伪代码:

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

但我遇到了一种奇怪的行为,我猜测我的旧webview(现在只占据30%的空间),正在重新格式化其内容,可能认为它现在被挤压到了更小的空间中,而不是像刚刚向右滑动那样行事。换句话说,当我设置这个边距时,webview的html布局发生了变化。同样,我不知道为什么,我的猜测是它认为其父窗口大小已经改变。基于这个假设,我添加了一行代码:

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

这样做就可以了,无需重新格式化HTML内容,我可以同时与两个Web视图进行交互。

我发表这篇文章是为了感谢Arne的想法,并获得对我所看到的行为和我的假设的任何输入。实际上,我喜欢最终的解决方案,它很有逻辑,但我可能错了……任何想法和意见都将不胜感激。非常感谢。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接