9

在Android中实现复杂动画(附完整代码)

 3 years ago
source link: http://www.androidchina.net/7202.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client
在Android中实现复杂动画(附完整代码) – Android开发中文站
你的位置:Android开发中文站 > Android开发 > 开发进阶 > 在Android中实现复杂动画(附完整代码)

Android对动画有着极好的支持,但有时你会看到这样的效果:

Untitled.gif

你可能会在此卡住不知从何开始。本文将带你一步一步尝试完整这个漂亮的动画。

第一次看到这个效果的时候可能会觉得很复杂,但是我们可以把它拆分为三个主要的动画。

1.用户点击卡片时的动画:

1-OVMHo8MTEal3dBvZpc4kKA (1).gif

2.打开详情界面的动画:

1-t_oijMCzE6TQsc39dSgEXQ (1).gif

3.向上滚动时头像收缩为圆点的动画:

1-GqAI58V9pWwC4o-JUWkcuw.gif

我将实现第二和第三个动画,第一个很简单留给读者自己练习吧

记得Android 5.0 (API level 21)添加的r Shared Element Transition 吗?你只需告诉OS当前界面与下一界面共享的view,OS就会处理好view从旧状态到新状态的过渡,包括 translation, rotation, scale 以及 visibility等。它甚至还可以在ImageView上做矩阵动画。

第一个动画中我们将利用 Shared Element Transition。我们有一个显示圆形image的RecyclerView。我们想点击任意一个image时,所有的item都过渡动画到下一屏的恰当位置。为此我们需要从LayoutManager那里得到可见item的position:

int firstVisibleItemPosition = layoutManager.findFirstVisibleItemPosition();
int lastVisibleItemPosition = layoutManager.findLastVisibleItemPosition();

一旦有了这些position,可以得到与之关联的itemView,然后把这些view作为共享元素启动下一个activity:

List<Pair<View, String>> pairs = new ArrayList<Pair<View, String>>();
for (int i = firstVisibleItemPosition; i <= lastVisibleItemPosition; i++) {
    ViewHolder holderForAdapterPosition = (ViewHolder) list.findViewHolderForAdapterPosition(i);
    View itemView = holderForAdapterPosition.image;
    pairs.add(Pair.create(itemView, "unique_key_" + i));
}
Bundle bundle = ActivityOptions.makeSceneTransitionAnimation(CurrentActivity.this, pairs.toArray(new Pair[]{})).toBundle();
startActivity(intent, bundle);

在下一个activity中,你只需要把unique_key_x设置到一些view上,系统就会处理好动画了。而下一个activity中的相应图片我们是用ViewPager的IconPageIndicator来显示的。因此需要在IconPageIndicator的同一position设置与上一个activity相同的transition name。

在IconPageIndicator类的notifyDataSetChanged方法中:

  public void notifyDataSetChanged() {
  ...
        IconPagerAdapter iconAdapter = (IconPagerAdapter) mViewPager.getAdapter();
        int count = iconAdapter.getCount();
        LayoutInflater inflater = LayoutInflater.from(getContext());
        for (int i = 0; i < count; i++) {
            final View parent = inflater.inflate(R.layout.indicator, mIconsLayout, false);
            final ImageView view = (ImageView) parent.findViewById(R.id.icon);
            //// TODO: 25/04/2017 Use ViewCompat to support pre-lollipop
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                view.setTransitionName("tab_" + i);
            }

        }
    ...
    }

瞧,第一个动画我们就完成了。

第二个动画就非常复杂了。列表滚动的时候伴随着太多的事情。scroll up的时候图标缩小成点,scroll down的时候点慢慢扩展成图标。另一个有趣的事情就是indicator始终在Toolbar中垂直居中。

首先,我们希望在Toolbar折叠或者展开的时候IconPageIndicator是居中的(显然我们用的是CoordinatorLayout+CollapsingToolbarLayout)。如官网所说,CoordinatorLayout是一个超级强大的FrameLayout。CoordinatorLayout中的每个child都可以通过CoordinatorLayout.Behavior监听其它child发生的事件,并且做出响应。我们将利用这个来实现在CollapsingToolbarLayout中垂直居中。

首先我们让Behavior知道我们对AppBarLayout感兴趣:

@Override
public boolean layoutDependsOn(CoordinatorLayout parent, IconPageIndicator child, View dependency) {
    return dependency instanceof AppBarLayout;
}

每当onDependentViewChanged() 被调用的时候,我们做一些处理,同时考虑android:fitsSystemWindows=”true”:

@Override
public boolean onDependentViewChanged(CoordinatorLayout parent, IconPageIndicator child, View dependency) {
    //keep child centered inside dependency respecting android:fitsSystemWindows="true"
    int systemWindowInsetTop = 0;
    if (lastInsets != null) {
        systemWindowInsetTop = lastInsets.getSystemWindowInsetTop();
    }
    int bottom = dependency.getBottom();
    float center = (bottom - systemWindowInsetTop) / 2F;
    float halfChild = child.getHeight() / 2F;
    setTopAndBottomOffset((int)(center + systemWindowInsetTop - halfChild));
    return true;
}

1-CGPC5niiuV_e2tiLQllHFw.png

这就可以使indicator居中了。现在我们需要让用户滚动的时候icon缩小为点。但如果你仔细点的话,就会发现这里还有一个细节;点是横向居中的,但是头像图标indicator是从中间开始的。也就是说当收缩到点的时候要让indicator居中。

我们各个击破。这里我们将为indicator添加startpadding和endpadding ,从而让它从中间开始显示。我们在OnPreDrawListener中做这个事情,因为必须在view测量完成之后才可以做这个事情。

indicator.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
    @Override
    public boolean onPreDraw() {
        indicator.getViewTreeObserver().removeOnPreDrawListener(this);
        int parentWidth = getWidth();
        int indicatorWidth = indicator.getWidth();
        int leftRightPadding = (parentWidth - indicatorWidth) / 2;
        //just touch left and right padding
        setPadding(leftRightPadding, getPaddingTop(), leftRightPadding, getPaddingBottom());
        return true;
    }
});

1-ZW3xdRlvz7dOT6xcdjG8fw.png

现在,让我们回到缩小图标的部分。记住我们的Behavior中重写了onDependentViewChanged()方法。每当CollapsingToolbarLayout发生变化的时候这个方法都会被调用。我们可以获得滚动的总距离和当前的滚动位置,这是我们把动画和滚动绑定所需要的仅有两个东西:

child.collapse(-appBar.getTop(), appBar.getTotalScrollRange());

而在collapse()内部我们可以把icon缩小为点。注意别太小,对我来说除以 1.2就可以了。

public void collapse(float current, float total) {
    //do not scale to 0
    float newTop = current / 1.2F;
    float scale = (total - newTop) / (float) total;
    ViewCompat.setScaleX(this, scale);
    ViewCompat.setScaleY(this, scale);
}

我们还希望上滚的时候icon变成灰色的indicator,因此:

public void collapse(float top, float total) {
    ...
    //alpha can be zero
    percentExpanded = (total - top) / (float) total;
    float alpha = 1 - percentExpanded;
    for (int i = 0; i < tabCount; i++) {
        View parent = mIconsLayout.getChildAt(i);
        //start showing our gray foreground when scrolling
        View child = parent.findViewById(R.id.foreground);
        ViewCompat.setAlpha(child, alpha);
    }
    updateScroll();
}

到此我们的app几乎具有了gif图中看到的所有效果。不过仍然有改进空间!我们用p代表当前的收缩比例,c代表indicator的中点,s代表当前选中的icon position,sx代表横向滚动的距离,那么就可以写出下面的公式:

sx = (p x c) + ((1 - p) x s

这个公司代表的意思就是,当p从 0 到 1 时,我们要么让c完全居中,要么让当前选中的position s居中。代码看起来更丑:

int center = iconsLayout.getWidth() / 2;
int scrollTo = (int)((center * (1 - p)) + (p * iconsLayout.getChildAt(selectedIndex).getLeft()));
smoothScrollTo(scrollTo, 0);

点击运行你将看到下面的效果:

1-BbgeLHkKiRoOIoDeDQO6Wg.gif

完整代码:

写作本文的动机来源于这个问题: https://stackoverflow.com/q/43542302/826606

如果你有更好的方式来实现这个动画,请留言或者 pull request。

转载请注明:Android开发中文站 » 在Android中实现复杂动画(附完整代码)


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK