安卓布局动画[Facebook]

9

我希望能够动画化两个不同的布局。

例如:

我想要的方式

我已经按照自己的意愿完成了动画,现在只是想要动画化另一个XML布局。 有一个名为LayoutAnimationController的类,但我真的不知道如何使用它。 是否有人可以给我指点方向,提供示例或详细说明。

这是我用于动画化的代码:

 TranslateAnimation slide = new TranslateAnimation(Animation.RELATIVE_TO_SELF, 300f, 0,0 );
 slide.setAnimationListener(AL);
 slide.setFillAfter(true);   
 slide.setDuration(1000); 

 parentlayout.startAnimation(slide);

更新 由于得到了许多赞,我决定把一个示例项目放进了Git存储库。 请参见我的回答获取链接。

3个回答

16

在花费了两天时间阅读类似问题及其它人是如何解决的之后,我终于成功地创建了我想要的东西。 我无法通过2个不同的XML文件实现它,但我怀疑这是不可能的。

然而,我遇到了一些问题。

在第一个动画结束后,按钮无法被点击。 这是因为动画显示所有内容都已移动,但它没有更新布局,所以按钮仍然位于动画开始时的位置。 所以我必须计算出布局的新位置。

我记得在某处看到过,在3.0中这已经不再是问题了,但如果我错了,请您指正。

另一个问题是,当我的动画最终按照我想要的方式工作时,底层视图在动画完成之前消失了,因为我调用了 view.setVisabilty(View.GONE);。 现在的问题是,当我没有调用该方法时,动画会卡顿一秒钟,然后快速跳转到动画的最终位置。 所以我添加了一个空的LinearLayout(可以是任何内容),默认属性为GONE,在动画开始时将其设置为VISIBLE。当您撤销动画时,再次将其设置为GONE。 这样做后,动画就能按照我想要的方式工作了。

如果您正在使用Rel、Linear或任何其他布局,则无法按Z顺序堆叠视图,因此必须使用SurfaceView。

这是main.xml:

 <?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/RelativeLayout1"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical" >

    <SurfaceView
        android:id="@+id/surfaceView1"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent" />

    <RelativeLayout
        android:id="@+id/layout"
        android:layout_width="220dp"
        android:layout_height="fill_parent"
        android:background="#ffee00"
        android:orientation="vertical" >

        <LinearLayout
            android:id="@+id/fake_layouy"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:orientation="vertical" android:visibility="gone">
        </LinearLayout>

        <ListView
            android:id="@+id/listView1"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" >
        </ListView>
    </RelativeLayout>

    <RelativeLayout
        android:id="@+id/layoutTwo"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:background="#ff00ee"
        android:orientation="vertical">

        <LinearLayout
            android:id="@+id/linearLayout1"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:layout_alignParentLeft="true"
            android:layout_alignParentTop="true" android:background="#ff0000" android:layout_margin="2dp">

            <Button
                android:id="@+id/button"
                android:layout_width="50dp"
                android:layout_height="wrap_content"
                android:text="slide" />
        </LinearLayout>

    </RelativeLayout>

</RelativeLayout>

这里是Java代码

    public class MenuAnimationActivity extends Activity {

    private Button buttonSwitch;  
    private View subLayout;
    private View topLayout;
    private ListView subViewListView;
    private String listViewDummyContent[]={"Android","iPhone","BlackBerry","AndroidPeople"};
    private Display display;
    private View fakeLayout;
    private AnimationListener AL;

    // Values for after the animation
    private int oldLeft;
    private int oldTop;
    private int newleft;
    private int newTop;
    private int screenWidth;    
    private int animToPostion; 
    // TODO change the name of the animToPostion for a better explanation.

    private boolean menuOpen = false;

        /** Called when the activity is first created. */  
        @Override  
        public void onCreate(Bundle savedInstanceState) {  
            super.onCreate(savedInstanceState);  
            setContentView(R.layout.main);  

            buttonSwitch = (Button)findViewById(R.id.button);  
            subLayout = (View) findViewById(R.id.layout);  
            topLayout = (View) findViewById(R.id.layoutTwo);
            subViewListView=(ListView)findViewById(R.id.listView1);
            fakeLayout = (View)findViewById(R.id.fake_layouy);

            subViewListView.setAdapter(new ArrayAdapter<String>(this,android.R.layout.simple_list_item_1 , listViewDummyContent));

            display =  getWindowManager().getDefaultDisplay();
            screenWidth = display.getWidth();
            int calcAnimationPosition = (screenWidth /3);

            // Value where the onTop Layer has to animate
            // also the max width of the layout underneath 
            // Set Layout params for subLayout according to calculation
            animToPostion = screenWidth - calcAnimationPosition;

            RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(animToPostion, RelativeLayout.LayoutParams.FILL_PARENT);
            subLayout.setLayoutParams(params);

             topLayout.setOnTouchListener(new OnTouchListener() {

                @Override
                public boolean onTouch(View v, MotionEvent event) {

                        if(event.getAction() == MotionEvent.ACTION_DOWN) {
                            if (menuOpen == true) {
                                animSlideLeft();
                            }
                        }

                    return false;
                }
            });

            buttonSwitch.setOnClickListener(new View.OnClickListener() {  

               @Override  
               public void onClick(View v) { 
                   if(menuOpen == false){    
                       animSlideRight();
                   } else if (menuOpen == true) {
                       animSlideLeft();
                       }
                   }  
                  });  

             AL = new AnimationListener() {

                @Override
                public void onAnimationStart(Animation animation) {
                    buttonSwitch.setClickable(false);
                    topLayout.setEnabled(false);
                }           
                @Override
                public void onAnimationRepeat(Animation animation) {
                    // TODO Auto-generated method stub

                }               
                @Override
                public void onAnimationEnd(Animation animation) {
                    if(menuOpen == true) {
                        Log.d("", "Open");              
                        topLayout.layout(oldLeft, oldTop, oldLeft + topLayout.getMeasuredWidth(), oldTop + topLayout.getMeasuredHeight() );
                        menuOpen = false;
                        buttonSwitch.setClickable(true);
                        topLayout.setEnabled(true);
                    } else if(menuOpen == false) {
                        Log.d("","FALSE");
                        topLayout.layout(newleft, newTop, newleft + topLayout.getMeasuredWidth(), newTop + topLayout.getMeasuredHeight() );                    
                        topLayout.setEnabled(true);
                        menuOpen = true;
                        buttonSwitch.setClickable(true);
                    }
                }
            };
        } 

        public void animSlideRight(){

                    fakeLayout.setVisibility(View.VISIBLE);
                newleft = topLayout.getLeft() + animToPostion;
                newTop = topLayout.getTop();    
                TranslateAnimation slideRight = new TranslateAnimation(0,newleft,0,0);
                slideRight.setDuration(500);   
                slideRight.setFillEnabled(true);   
                slideRight.setAnimationListener(AL);    
                topLayout.startAnimation(slideRight);           
        }

        public void animSlideLeft() {

            fakeLayout.setVisibility(View.GONE);
            oldLeft = topLayout.getLeft() - animToPostion;
            oldTop = topLayout.getTop();        
            TranslateAnimation slideLeft = new TranslateAnimation(newleft,oldLeft,0,0);
            slideLeft.setDuration(500);   
            slideLeft.setFillEnabled(true);   
            slideLeft.setAnimationListener(AL);    
            topLayout.startAnimation(slideLeft);                
        }
}  

我在触摸视图上进行了一些额外的编码。

最终结果如下所示:

动画之前

enter image description here

第一个动画后

enter image description here

第二个动画后回到左边时,状态与第一张图片相同。

所有帮助我的帖子都应该得到一些认可,但我找不到它们中的任何一个。

编辑

GIT https://bitbucket.org/maikelbollemeijer/sidepanelswitcher

更新: https://github.com/jfeinstein10/SlidingMenu 此库与Actionbar Sherlock兼容。

希望这有所帮助。


干得好...但是怎么选择左侧菜单呢?能告诉我吗,这样我就可以打开特定的项目了。 - J_K
你的意思是如何在listView中选择一个项目?就像在我的情况下,Android、Blackberry、iPhone一样吗? - Maikel Bollemeijer
1
这很简单,例如:listView.setOnItemClickListener(new OnItemClickListener() { public void onItemClick(AdapterView<?> parent, View view, int rowID, long arg3) { // TODO Auto-generated method stub } }); - Maikel Bollemeijer
谢谢你帮我...它正在工作,但我在这方面犯了一些错误。所以它没有工作。但现在它运行良好。 - J_K
以下是有关编程的内容,请看视频链接:https://www.dropbox.com/s/4qozp83klxnddd2/2012-12-01_0017.swf - J_K
显示剩余4条评论

8
我有类似的需求,需要制作类似Facebook应用程序的布局动画。为此,我制作了一个定制的ViewGroup(称为AnimationLayout)。希望这些代码能够帮助你。
AnimationLayout需要两个子元素:Sidebar和Content。(通过将@+id/animation_sidebar和@+id/animation_content分配给相应的元素)
这是布局xml文件,SideBar有一个按钮和一个列表视图。Content有一个文本视图和一个按钮(它绑定到一个回调函数)。
<?xml version="1.0" encoding="utf-8"?>
<org.zeroxlab.widget.AnimationLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/animation_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    >
    <LinearLayout
        android:id="@+id/animation_sidebar"
        android:layout_width="200dip"
        android:layout_height="match_parent"
        android:background="#550000"
        android:orientation="vertical"
        >
        <Button
            android:id="@+id/button_test"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Sidebar Button"
            />
        <ListView
            android:id="@+id/sidebar_list"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            />
    </LinearLayout>
    <LinearLayout
        android:id="@+id/animation_content"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="#003300"
        android:clickable="true"
        >
        <Button android:id="@+id/button"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Content Button"
            android:onClick="onClickButton"
            />
        <TextView android:id="@+id/text"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="The Answer to Life, the Universe, and Everything -- is 42"
            />
    </LinearLayout>
</org.zeroxlab.widget.AnimationLayout>

这是一个测试活动。它初始化了一个 ListView,并将自身分配为 AnimationLayout 的侦听器。

package test.julian.hello;

import org.zeroxlab.widget.AnimationLayout;

import android.app.Activity;
import android.app.ActivityManager;
import android.os.Bundle;
import android.widget.*;
import android.util.Log;
import android.view.View;

public class HelloAndroid extends Activity implements AnimationLayout.Listener {
    ListView mList;
    AnimationLayout mLayout;

    String[] mStrings = {"a", "b", "c", "d", "e", "f", "g"};

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.app_layout);

        mLayout = (AnimationLayout) findViewById(R.id.animation_layout);
        mLayout.setListener(this);

        mList   = (ListView) findViewById(R.id.sidebar_list);
        mList.setAdapter(
                new ArrayAdapter<String>(
                    this, android.R.layout.simple_list_item_multiple_choice
                    , mStrings));
        mList.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
    }

    public void onClickButton(View v) {
        mLayout.toggleSidebar();
    }

    @Override
    public void onSidebarOpened() {
        Log.d("Foo", "opened");
    }

    @Override
    public void onSidebarClosed() {
        Log.d("Foo", "opened");
    }

    @Override
    public boolean onContentTouchedWhenOpening() {
        Log.d("Foo", "going to close sidebar");
        mLayout.closeSidebar();
        return true;
    }
}

这是一个动画布局。
/*
 * Copyright (C) 2012 0xlab - http://0xlab.org/
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * Authored by Julian Chu <walkingice AT 0xlab.org>
 */

package org.zeroxlab.widget;

import test.julian.hello.R;

import android.content.Context;
import android.util.AttributeSet;
import android.view.animation.Animation;
import android.view.animation.TranslateAnimation;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.MeasureSpec;
import android.view.ViewGroup;
import android.view.ViewGroup.LayoutParams;

public class AnimationLayout extends ViewGroup {

    public final static int DURATION = 500;

    protected boolean mOpened;
    protected View mSidebar;
    protected View mContent;
    protected int mSidebarWidth = 150; // by default

    protected Animation mAnimation;
    protected OpenListener    mOpenListener;
    protected CloseListener   mCloseListener;
    protected Listener mListener;

    protected boolean mPressed = false;

    public AnimationLayout(Context context) {
        this(context, null);
    }

    public AnimationLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    public void onFinishInflate() {
        super.onFinishInflate();
        mSidebar = findViewById(R.id.animation_sidebar);
        mContent = findViewById(R.id.animation_content);

        if (mSidebar == null) {
            throw new NullPointerException("no view id = animation_sidebar");
        }

        if (mContent == null) {
            throw new NullPointerException("no view id = animation_content");
        }

        mOpenListener = new OpenListener(mSidebar, mContent);
        mCloseListener = new CloseListener(mSidebar, mContent);
    }

    @Override
    public void onLayout(boolean changed, int l, int t, int r, int b) {
        /* the title bar assign top padding, drop it */
        mSidebar.layout(l, 0, l + mSidebarWidth, 0 + mSidebar.getMeasuredHeight());
        if (mOpened) {
            mContent.layout(l + mSidebarWidth, 0, r + mSidebarWidth, b);
        } else {
            mContent.layout(l, 0, r, b);
        }
    }

    @Override
    public void onMeasure(int w, int h) {
        super.onMeasure(w, h);
        super.measureChildren(w, h);
        mSidebarWidth = mSidebar.getMeasuredWidth();
    }

    @Override
    protected void measureChild(View child, int parentWSpec, int parentHSpec) {
        /* the max width of Sidebar is 90% of Parent */
        if (child == mSidebar) {
            int mode = MeasureSpec.getMode(parentWSpec);
            int width = (int)(getMeasuredWidth() * 0.9);
            super.measureChild(child, MeasureSpec.makeMeasureSpec(width, mode), parentHSpec);
        } else {
            super.measureChild(child, parentWSpec, parentHSpec);
        }
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        if (!isOpening()) {
            return false;
        }

        int action = ev.getAction();

        if (action != MotionEvent.ACTION_UP
                && action != MotionEvent.ACTION_DOWN) {
            return false;
        }

        /* if user press and release both on Content while
         * sidebar is opening, call listener. otherwise, pass
         * the event to child. */
        int x = (int)ev.getX();
        int y = (int)ev.getY();
        if (mContent.getLeft() < x
                && mContent.getRight() > x
                && mContent.getTop() < y
                && mContent.getBottom() > y) {
            if (action == MotionEvent.ACTION_DOWN) {
                mPressed = true;
            }

            if (mPressed
                    && action == MotionEvent.ACTION_UP
                    && mListener != null) {
                mPressed = false;
                return mListener.onContentTouchedWhenOpening();
            }
        } else {
            mPressed = false;
        }

        return false;
    }

    public void setListener(Listener l) {
        mListener = l;
    }

    /* to see if the Sidebar is visible */
    public boolean isOpening() {
        return mOpened;
    }

    public void toggleSidebar() {
        if (mContent.getAnimation() != null) {
            return;
        }

        if (mOpened) {
            /* opened, make close animation*/
            mAnimation = new TranslateAnimation(0, -mSidebarWidth, 0, 0);
            mAnimation.setAnimationListener(mCloseListener);
        } else {
            /* not opened, make open animation */
            mAnimation = new TranslateAnimation(0, mSidebarWidth, 0, 0);
            mAnimation.setAnimationListener(mOpenListener);
        }
        mAnimation.setDuration(DURATION);
        mAnimation.setFillAfter(true);
        mAnimation.setFillEnabled(true);
        mContent.startAnimation(mAnimation);
    }

    public void openSidebar() {
        if (!mOpened) {
            toggleSidebar();
        }
    }

    public void closeSidebar() {
        if (mOpened) {
            toggleSidebar();
        }
    }

    class OpenListener implements Animation.AnimationListener {
        View iSidebar;
        View iContent;

        OpenListener(View sidebar, View content) {
            iSidebar = sidebar;
            iContent = content;
        }

        public void onAnimationRepeat(Animation animation) {
        }

        public void onAnimationStart(Animation animation) {
            iSidebar.setVisibility(View.VISIBLE);
        }

        public void onAnimationEnd(Animation animation) {
            iContent.clearAnimation();
            mOpened = !mOpened;
            requestLayout();
            if (mListener != null) {
                mListener.onSidebarOpened();
            }
        }
    }

    class CloseListener implements Animation.AnimationListener {
        View iSidebar;
        View iContent;

        CloseListener(View sidebar, View content) {
            iSidebar = sidebar;
            iContent = content;
        }

        public void onAnimationRepeat(Animation animation) {
        }
        public void onAnimationStart(Animation animation) {
        }

        public void onAnimationEnd(Animation animation) {
            iContent.clearAnimation();
            iSidebar.setVisibility(View.INVISIBLE);
            mOpened = !mOpened;
            requestLayout();
            if (mListener != null) {
                mListener.onSidebarClosed();
            }
        }
    }

    public interface Listener {
        public void onSidebarOpened();
        public void onSidebarClosed();
        public boolean onContentTouchedWhenOpening();
    }
}

当侧边栏关闭时,它看起来像这样。 http://i.stack.imgur.com/tynLw.png 当侧边栏打开时,它看起来像这样。 http://i.stack.imgur.com/QJC8q.png

不错的解决方案,但我已经修复了,还是谢谢你的贡献。 - Maikel Bollemeijer
在您的解决方案中,视图(如按钮等)是否随布局一起移动到新位置或保持原来的位置? - 2cupsOfTech
它会移动到新的位置,以便您能够单击它。我已经在Github上创建了一个演示项目。(https://github.com/walkingice/gui-sliding-sidebar) - walkingice

0
我从walkingice(https://github.com/walkingice/gui-sliding-sidebar)那里得到了解决方案,并对其进行了修改,制作了一个小部件,其中“侧边栏”可以从顶部或底部以及左侧或右侧进入。您还可以将侧边栏宽度(或高度)指定为父宽度(或高度)的百分比。侧边栏可以静止在主内容视图后面或滑动进入。
该项目由SolutionStream开发,可在此处获得:https://github.com/solutionstream/sidebarlayout 它是开源的(Apache 2.0许可证),因此请随意查看代码并使用它(根据许可证),无论是作为示例还是直接使用。 披露:上面的链接是我在SolutionStream自己创建的项目。

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