简体   繁体   English

如何在操作栏使用Appcompat更改内容时添加动画?

[英]How to add animation while actionbar are changing their content with Appcompat?

I'm looking at Google's Material Design guidelines and I want add animated action bar. 我正在查看Google的Material Design指南,我想要添加动画操作栏。 My goal is do something like this: 我的目标是做这样的事情:

在此输入图像描述

How can I add transition for action bar's content? 如何为操作栏的内容添加转换? I'm using Appcompat to keep backward compatibility. 我正在使用Appcompat来保持向后兼容性。

I think I finally found your answer. 我想我终于找到了答案。 This was way harder to find than I thought. 这比我想象的要难得多。 If you take a look at this link: http://suhan.in/material-design-toolbar-animation/ 如果你看一下这个链接: http//suhan.in/material-design-toolbar-animation/

The first one explains how this is done. 第一个解释了如何做到这一点。

Below you find my own code snippet of how it could be done with only the menu items: 您可以在下面找到我自己的代码片段,了解如何仅使用菜单项:

for(int i = 0; i < toolbarView.getChildCount(); i++) 
{
    final View v = toolbarView.getChildAt(i);

    if(v instanceof ActionMenuView) 
    {
        for(int j = 0; j < ((ActionMenuView)v).getChildCount(); j++) 
        {
            final View innerView = ((ActionMenuView)v).getChildAt(j);

            if(innerView instanceof ActionMenuItemView) 
            {
                innerView.setTranslationY(-30);
                innerView.animate().setStartDelay(100 + (j * 10)).setDuration(200).translationY(0);
            }
        }
    }
}

This is the animation for the Y-axis. 这是Y轴的动画。 You can also add the animation for size, which I think they do in the design guidelines. 您还可以添加大小的动画,我认为它们在设计指南中也是如此。 Also if you don't want them to start simultaniously, you can set startDelay and add extra like this: setStartDelay(i * 10) . 此外,如果您不希望它们同时启动,您可以设置startDelay并添加额外的内容: setStartDelay(i * 10) This way each item starts the animation a little later. 这样,每个项目稍后都会启动动画。 I already put this in the code snippet, but tweak it as how you would like it. 我已经把它放在代码片段中,但是按照你想要的方式调整它。

Update: 更新:

I've created an open source library that provides transition/animation support to both View and MenuItem: 我创建了一个开源库 ,为View和MenuItem提供过渡/动画支持:

MenuItem转换 MenuItem transition MenuItem转换

查看转换 View transition 查看转换

Instructions: 说明:

On Android Studio, add the code below to Gradle dependencies: 在Android Studio上,将以下代码添加到Gradle依赖项:

compile 'com.github.kaichunlin.transition:core:0.8.1'

Sample code with explanations: 示例代码及解释:

  protected void onCreate(Bundle savedInstanceState) {
    //...
    //standard onCreate() stuff that creates set configs toolbar, mDrawerLayout & mDrawerToggle

    //Use the appropriate adapter that extends MenuBaseAdapter:
    DrawerListenerAdapter mDrawerListenerAdapter = new DrawerListenerAdapter(mDrawerToggle, R.id.drawerList);
    mDrawerListenerAdapter.setDrawerLayout(mDrawerLayout);

    //Add desired transition to the adapter, MenuItemTransitionBuilder is used to build the transition:
    //Creates a shared configuration that: applies alpha, the transition effect is applied in a cascading manner (v.s. simultaneously), MenuItems will resets to enabled when transiting, and invalidates menu on transition completion 
    MenuItemTransitionBuilder builder = MenuItemTransitionBuilder.transit(toolbar).alpha(1f, 0.5f).scale(1f, 0f).cascade(0.3f).visibleOnStartAnimation(true).invalidateOptionOnStopTransition(this, true);
    MenuItemTransition mShrinkClose = builder.translationX(0, 30).build();
    MenuItemTransition mShrinkOpen = builder.reverse().translationX(0, 30).build();
    mDrawerListenerAdapter.setupOptions(this, new MenuOptionConfiguration(mShrinkOpen, R.menu.drawer), new MenuOptionConfiguration(mShrinkClose, R.menu.main));
  }

  //Let the adapter manage the creation of options menu:
  @Override
  public boolean onCreateOptionsMenu(Menu menu) {
      mDrawerListenerAdapter.onCreateOptionsMenu(this, menu);

      return super.onCreateOptionsMenu(menu);
  }

Source of the activity implementing the above is here , and a demo app here . 实施上述活动的来源是在这里 ,和演示应用程序在这里


Originally Accepted Answer: 最初接受的答案:

Here's a solution that's more versatile and is exactly how the MenuItem fade-out of Google Drive, Google Docs, Google Sheets, and Google Slides work. 这是一个更加通用的解决方案,它正是Google Drive,Google Docs,Google表格和Google幻灯片的MenuItem淡出效果。

The advantage is that when the user slide in from the left edge of the screen to open the drawer manually, or slide right when the drawer is opened to close it, the animation state is integrated with how the drawer is being opened/closed . 优点在于,当用户从屏幕的左边缘滑入以手动打开抽屉时,或者当打开抽屉以将其关闭时向右滑动, 动画状态与抽屉如何打开/关闭一体化


ProgressAnimator.java : This is the meat of the implementation, it translates a float based progression value (0f~1f) into a value that Android Animator understands. ProgressAnimator.java :这是实现的核心,它将基于浮动的进度值(0f~1f)转换为Android Animator理解的值。

public class ProgressAnimator implements TimeAnimator.TimeListener {
    private final List<AnimationControl> animationControls = new ArrayList<>();
    private final MenuItem mMenuItem; //TODO shouldn't be here, add animation end listener
    private final ImageView mImageView;
    private final TimeAnimator mTimeAnim;
    private final AnimatorSet mInternalAnimSet;

    public ProgressAnimator(Context context, MenuItem mMenuItem) {
    if (mMenuItem == null) {
        mImageView = null;
    } else {
        mImageView = (ImageView) LayoutInflater.from(context).inflate(R.layout.menu_animation, null).findViewById(R.id.menu_animation);
        mImageView.setImageDrawable(mMenuItem.getIcon());
    }
    this.mMenuItem = mMenuItem;
    this.mInternalAnimSet = new AnimatorSet();

    mTimeAnim = new TimeAnimator();
    mTimeAnim.setTimeListener(this);
    }

    public void addAnimatorSet(AnimatorSet mAnimSet, float start, float end) {
    animationControls.add(new AnimationControl(mImageView, mAnimSet, start, end));
    }

    public void addAnimatorSet(Object target, AnimatorSet mAnimSet, float start, float end) {
    animationControls.add(new AnimationControl(target, mAnimSet, start, end));
    }

    public void start() {
    ValueAnimator colorAnim = ObjectAnimator.ofInt(new Object() {
        private int dummy;

        public int getDummy() {
        return dummy;
        }

        public void setDummy(int dummy) {
        this.dummy = dummy;
        }
    }, "dummy", 0, 1);
    colorAnim.setDuration(Integer.MAX_VALUE);
    mInternalAnimSet.play(colorAnim).with(mTimeAnim);
    mInternalAnimSet.start();
    if (mMenuItem != null) {
        mMenuItem.setActionView(mImageView);
    }
    for (AnimationControl ctrl : animationControls) {
        ctrl.start();
    }
    }

    public void end() {
    mTimeAnim.end();
    if (mMenuItem != null) {
        mMenuItem.setActionView(null);
    }
    }

    public void updateProgress(float progress) {
    for (AnimationControl ctrl : animationControls) {
        ctrl.updateProgress(progress);
    }
    }

    @Override
    public void onTimeUpdate(TimeAnimator animation, long totalTime, long deltaTime) {
    for (AnimationControl ctrl : animationControls) {
        ctrl.updateState();
    }
    }
}

AnimationControl.java : Controls the progression of transition. AnimationControl.java :控制转换的进程。

public class AnimationControl {
    private AnimatorSet mAnimSet;
    private Object target;
    private float start;
    private float end = 1.0f;
    private float progressWidth;
    private long time;
    private boolean started;
    private long mStartDelay;
    private long mDuration;
    private long mTotalDuration;

    public AnimationControl(AnimatorSet mAnimSet, float start, float end) {
    this(null, mAnimSet, start, end);
    }

    public AnimationControl(Object target, AnimatorSet mAnimSet, float start, float end) {
    for (Animator animator : mAnimSet.getChildAnimations()) {
        if (!(animator instanceof ValueAnimator)) {
        throw new UnsupportedOperationException("Only ValueAnimator and its subclasses are supported");
        }
    }
    this.target = target;
    this.mAnimSet = mAnimSet;
    mStartDelay = mAnimSet.getStartDelay();
    mDuration = mAnimSet.getDuration();
    if (mAnimSet.getDuration() >= 0) {
        long duration = mAnimSet.getDuration();
        for (Animator animator : mAnimSet.getChildAnimations()) {
        animator.setDuration(duration);
        }
    } else {
        for (Animator animator : mAnimSet.getChildAnimations()) {
        long endTime = animator.getStartDelay() + animator.getDuration();
        if (mDuration < endTime) {
            mDuration = endTime;
        }
        }
    }
    mTotalDuration = mStartDelay + mDuration;
    this.start = start;
    this.end = end;
    progressWidth = Math.abs(end - start);
    }

    public void start() {
    if (target != null) {
        for (Animator animator : mAnimSet.getChildAnimations()) {
        animator.setTarget(target);
        }
    }
    }

    public void updateProgress(float progress) {
    if (start < end && progress >= start && progress <= end || start > end && progress >= end && progress <= start) {
        if (start < end) {
        time = (long) (mTotalDuration * (progress - start) / progressWidth);
        } else {
        time = (long) (mTotalDuration - mTotalDuration * (progress - end) / progressWidth);
        }
        time -= mStartDelay;
        if (time > 0) {
        started = true;
        }
        Log.e(getClass().getSimpleName(), "updateState: " + mTotalDuration + ";" + time+"/"+start+"/"+end);
    } else {
        //forward
        if (start < end) {
        if (progress < start) {
            time = 0;
        } else if (progress > end) {
            time = mTotalDuration;
        }
        //backward
        } else if (start > end) {
        if (progress > start) {
            time = 0;
        } else if (progress > end) {
            time = mTotalDuration;
        }
        }
        started = false;
    }
    }

    public void updateState() {
    if (started) {
        for (Animator animator : mAnimSet.getChildAnimations()) {
        ValueAnimator va = (ValueAnimator) animator;
        long absTime = time - va.getStartDelay();
        if (absTime > 0) {
            va.setCurrentPlayTime(absTime);
        }
        }
    }
    }
}

ProgressDrawerListener.java : This listens for DrawerLayout state update and setup the required animation. ProgressDrawerListener.java :这将侦听DrawerLayout状态更新并设置所需的动画。

public class ProgressDrawerListener implements DrawerLayout.DrawerListener {
private final List<ProgressAnimator> mAnimatingMenuItems = new ArrayList<>();
private final Toolbar mToolbar;
private final ActionBarDrawerToggle mDrawerToggle;
private DrawerLayout.DrawerListener mDrawerListener;
private MenuItemAnimation mMenuItemAnimation;
private Animation mAnimation;
private boolean started;
private boolean mOpened;
private Activity mActivity;
private boolean mInvalidateOptionOnOpenClose;

public ProgressDrawerListener(Toolbar mToolbar, ActionBarDrawerToggle mDrawerToggle) {
    this.mToolbar = mToolbar;
    this.mDrawerToggle = mDrawerToggle;
}

@Override
public void onDrawerOpened(View view) {
    mDrawerToggle.onDrawerOpened(view);
    clearAnimation();
    started = false;

    if (mDrawerListener != null) {
        mDrawerListener.onDrawerOpened(view);
    }
    mToolbar.getMenu().setGroupVisible(0, false); //TODO not always needed
    mOpened=true;
    mActivity.invalidateOptionsMenu();
}

@Override
public void onDrawerClosed(View view) {
    mDrawerToggle.onDrawerClosed(view);
    clearAnimation();
    started = false;

    if (mDrawerListener != null) {
        mDrawerListener.onDrawerClosed(view);
    }
    mOpened=false;
    mActivity.invalidateOptionsMenu();
}

@Override
public void onDrawerStateChanged(int state) {
    mDrawerToggle.onDrawerStateChanged(state);
    switch (state) {
        case DrawerLayout.STATE_DRAGGING:
        case DrawerLayout.STATE_SETTLING:
            if (mAnimatingMenuItems.size() > 0 || started) {
                break;
            }
            started = true;

            setupAnimation();
            break;
        case DrawerLayout.STATE_IDLE:
            clearAnimation();
            started = false;
            break;
    }

    if (mDrawerListener != null) {
        mDrawerListener.onDrawerStateChanged(state);
    }
}

private void setupAnimation() {
    mToolbar.getMenu().setGroupVisible(0, true); //TODO not always needed
    mAnimatingMenuItems.clear();
    for (int i = 0; i < mToolbar.getChildCount(); i++) {
        final View v = mToolbar.getChildAt(i);
        if (v instanceof ActionMenuView) {
            int menuItemCount = 0;
            int childCount = ((ActionMenuView) v).getChildCount();
            for (int j = 0; j < childCount; j++) {
                if (((ActionMenuView) v).getChildAt(j) instanceof ActionMenuItemView) {
                    menuItemCount++;
                }
            }
            for (int j = 0; j < childCount; j++) {
                final View innerView = ((ActionMenuView) v).getChildAt(j);
                if (innerView instanceof ActionMenuItemView) {
                    MenuItem mMenuItem = ((ActionMenuItemView) innerView).getItemData();
                    ProgressAnimator offsetAnimator = new ProgressAnimator(mToolbar.getContext(), mMenuItem);

                    if(mMenuItemAnimation!=null) {
                        mMenuItemAnimation.setupAnimation(mMenuItem, offsetAnimator, j, menuItemCount);
                    }
                    if(mAnimation!=null) {
                        mAnimation.setupAnimation(offsetAnimator);
                    }

                    offsetAnimator.start();
                    mAnimatingMenuItems.add(offsetAnimator);
                }
            }
        }
    }
    onDrawerSlide(null, mOpened ? 1f : 0f);
    Log.e(getClass().getSimpleName(), "setupAnimation: "+mAnimatingMenuItems.size()); //TODO
}

@Override
public void onDrawerSlide(View view, float slideOffset) {
    for (ProgressAnimator ani : mAnimatingMenuItems) {
        ani.updateProgress(slideOffset);
    }

    if(view==null) {
        return;
    }
    mDrawerToggle.onDrawerSlide(view, slideOffset);

    if (mDrawerListener != null) {
        mDrawerListener.onDrawerSlide(view, slideOffset);
    }
}

private void clearAnimation() {
    for (ProgressAnimator ani : mAnimatingMenuItems) {
        ani.end();
    }
    mAnimatingMenuItems.clear();
}

public void setDrawerListener(DrawerLayout.DrawerListener mDrawerListener) {
    this.mDrawerListener = mDrawerListener;
}

public MenuItemAnimation getMenuItemAnimation() {
    return mMenuItemAnimation;
}

public void setMenuItemAnimation(MenuItemAnimation mMenuItemAnimation) {
    this.mMenuItemAnimation = mMenuItemAnimation;
}

public Animation getAnimation() {
    return mAnimation;
}

public void setAnimation(Animation mAnimation) {
    this.mAnimation = mAnimation;
}

public void setmInvalidateOptionOnOpenClose(Activity activity, boolean invalidateOptionOnOpenClose) {
    mActivity=activity;
    mInvalidateOptionOnOpenClose = invalidateOptionOnOpenClose;
}

public interface MenuItemAnimation {

    public void setupAnimation(MenuItem mMenuItem, ProgressAnimator offsetAnimator, int itemIndex, int menuCount);
}

public interface Animation {

    public void setupAnimation(ProgressAnimator offsetAnimator);
}

} }

Set up in Activity : The example code below switches between two different menu options between opened and closed state. 在活动中设置 :下面的示例代码在打开和关闭状态之间切换两个不同的菜单选项。 Optionally add offsetDrawerListener.setDrawerListener(DrawerListener) if you need to have your own DrawerListener .: 如果您需要拥有自己的DrawerListener ,可以选择添加offsetDrawerListener.setDrawerListener(DrawerListener)

@Override
protected void onCreate(Bundle savedInstanceState) {
    //other init

    mProgressDrawerListener =new ProgressDrawerListener(toolbar, mDrawerToggle);
    mProgressDrawerListener.setmInvalidateOptionOnOpenClose(this, true);
    mOpenAnimation = new ProgressDrawerListener.MenuItemAnimation() {
        @Override
        public void setupAnimation(MenuItem mMenuItem, ProgressAnimator offsetAnimator, int itemIndex, int menuCount) {
            MainActivity.this.setupAnimation(true, offsetAnimator, itemIndex);
        }
    };
    mCloseAnimation = new ProgressDrawerListener.MenuItemAnimation() {
        @Override
        public void setupAnimation(MenuItem mMenuItem, ProgressAnimator offsetAnimator, int itemIndex, int menuCount) {
            MainActivity.this.setupAnimation(false, offsetAnimator, itemIndex);
        }
    };
    mDrawerLayout.setDrawerListener(mProgressDrawerListener);
}

//customize your animation here
private void setupAnimation(boolean open, ProgressAnimator offsetAnimator, int itemIndex) {
    AnimatorSet set = new AnimatorSet();
    set.playTogether(
            ObjectAnimator.ofFloat(null, "alpha", 1.0f, 0f),
            ObjectAnimator.ofFloat(null, "scaleX", 1.0f, 0f)
    );
    set.setStartDelay(itemIndex * 200);
    set.setDuration(1000 - itemIndex * 200); //not the actual time the animation will be played
    if(open) {
        offsetAnimator.addAnimatorSet(set, 0, 1);
    } else {
        offsetAnimator.addAnimatorSet(set, 1, 0);
    }
}

@Override
public boolean onCreateOptionsMenu(Menu menu) {
    // Only show items in the action bar relevant to this screen
    // if the drawer is not showing. Otherwise, let the drawer
    // decide what to show in the action bar.
    if(mDrawerLayout.isDrawerOpen(findViewById(R.id.drawerList))) {
        getMenuInflater().inflate(R.menu.drawer, menu);
        mProgressDrawerListener.setMenuItemAnimation(
                mCloseAnimation);
    } else {
        getMenuInflater().inflate(R.menu.main, menu);
        mProgressDrawerListener.setMenuItemAnimation(
                mOpenAnimation);
        mDrawerLayout.setDrawerListener(mProgressDrawerListener);
    }

    return super.onCreateOptionsMenu(menu);
}

menu_animation.xml : This is to get the custom ActionView to have the same layout as the view used by MenuIem menu_animation.xml :这是为了使自定义ActionView具有与MenuIem使用的视图相同的布局

<?xml version="1.0" encoding="utf-8"?>
    <ImageView xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@+id/menu_animation"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:minWidth="@*android:dimen/action_button_min_width"
        android:padding="8dp"
        style="@style/Widget.AppCompat.ActionBar" />

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM