Layout Animation Android[Facebook]

2019-02-02 16:19发布

问题:

I want to Animate two Different layouts.

Example

I already have the animation the way I want, I just want to animate a different XML Layout. There is a class LayoutAnimationController, but I really dont know how to use it. Can some one point me in the right direction, with an example or good explanation.

heres the code I use to animate.

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

 parentlayout.startAnimation(slide);

Update Because of the many up-votes I decided to put a example project into a Git repository. See my answers for the link.

回答1:

Ok After spending 2 days reading about similair problems and how people solved them I finally was able to create the thing I wanted. I was not able to do it with 2 diffrent XML files, but I doubt it is not possible.

I did encountert some problems tho.

After the first animation ended, the button was not clickable. This is because the animation shows that everything is moved but it does not update the layout, so the button is still at the position where the animation started. So I had to calculate the new position of the layout.

I think I read somewhere that this is no longer an issue in 3.0, but correct me if I am wrong

Another was that when I had my animation finally working the way I wanted my underlaying view did disapear before the animation was finished because I invoked view.setVisabilty(View.GONE);. Now the problem was when I did not invoke that method, the animation just hang for a second and then shooter to the end position of the animation. So I added a empty LinearLayout (can be anything) , Default property on GONE, when the animation starts set it on Visible. when you revert the animation, set it again to gone. after doing this the animation was working the way I wanted.

And if you are using a Rel, Linear, or any other layout. then you cant stack views in the Z order so you have to use an SurfaceView.

so heres 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>

heres the java code

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

I did some extra coding on touching views and stuff.

And the final result

before Animation

after First Animation

And after the second Animation back to the left it states returns as the first Image.

Al those posts that helped me really deserve some credit but I cant find any of them.

Edit

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

Update: https://github.com/jfeinstein10/SlidingMenu this lib is compatible with Actionbar Sherlock.

hope this helps



回答2:

I had the similar requirement that make layout animation like Facebook app. To do that, I made a customized ViewGroup (called AnimationLayout). Hope these code helping.

The AnimationLayout needs two child: Sidebar and Content. (by assigning @+id/animation_sidebar and @+id/animation_content to corresponding one)

This is the layout xml, the SideBar has a button and a listview. The Content has a textview and a button(it binds to a callback function).

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

This is the testing Activity. It initialize a ListView and assigns itself as a Listener to 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;
    }
}

This is the AnimationLayout.

/*
 * 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();
    }
}

When the SideBar closed, it looks like this.

When the SideBar opened, it looks like this.



回答3:

I took the solution from walkingice (https://github.com/walkingice/gui-sliding-sidebar) and added to it, making a widget in which the "sidebar" can come in from the top or bottom as well as the left or right. You can also specify the sidebar width (or height) as a percent of the parent width (or height). The sidebar can be stationary behind the main content view or slide in.

The project is by SolutionStream, and it's available here: https://github.com/solutionstream/sidebarlayout

It's open source (Apache 2.0 license), so feel free to check out the code and use it (under the license), either as an example or directly.

DISCLOSURE: The above link is to a project that I created myself at SolutionStream.