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);
}
}
}
}
这是关于Y轴的动画效果。在设计指南中,您还可以添加大小的动画效果。如果您不想它们同时开始,请设置startDelay并像这样添加额外的东西:setStartDelay(i * 10)
。这样每个项目都会稍后开始动画效果。我已经将此代码段放入代码片段中,但请按照您的喜好进行微调。
我创建了一个开源库,为View和MenuItem提供了过渡/动画支持:
菜单项转换
视图转换
使用方法:
在Android Studio中,将以下代码添加到Gradle依赖项中:
compile 'com.github.kaichunlin.transition:core:0.8.1'
带解释的示例代码:
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);
}
以上实现的活动来源可以在这里找到,演示应用程序可以在这里找到。
以下是一种更通用的解决方案,正是Google Drive、Google Docs、Google Sheets和Google Slides中的菜单项淡出效果的实现方式。
优点在于,当用户从屏幕左边缘滑入手动打开抽屉或在打开抽屉时向右滑动以关闭它时,动画状态与抽屉的打开/关闭方式集成在一起。
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:控制过渡进程。
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: 这个监听器用于监听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);
}
在Activity中设置: 下面的示例代码在打开和关闭状态之间切换两个不同的菜单选项。如果需要自己的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:这是将自定义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" />
mAnimationAdapter=new AnimationAdapter();
mAnimationAdapter.setupOpenOption(this, new MenuOptionConfiguration(mShrinkOpen, R.menu.drawer));
运行方法如下:mAnimationAdapter.startAnimation(duration);
- Kai