InstaMaterial 概念设计(第五部分) - like操作相关效果

作者: Miroslaw Stanek

英文原文 InstaMaterial concept (part 5) - Like action effects

译文原文 http://jcodecraeer.com/a/anzhuokaifa/androidkaifa/2015/0412/2709.html 

转载译文务必注明出处

这篇文章是实现Material 风格的INSTAGRAM 系列文章第第五部分。今天我们将实现概念视频20到27秒里面 新闻item中like操作的效果。

下面是今天所要达到的效果(分别是Lollipop以及Lollipop之前的效果):

视频暂略

Like the feed item

根据概念视频,我们有两种“like”一条新闻的方式-通过点击心形按钮或者点击图片(实际上应该是双击才对,真实的Instagram app也是双击,不过这点就留给你们自己下去做练习吧,嘻嘻)。

如以往一样,我需要在项目中添加一些资源文件,这次是一串心形图片:

Small blue heartRead heartWhite heart outline

从左到右的心形依次是like计数按钮,like按钮,图片like按钮。

like计数按钮


我们从最简单的效果开始做。like计数按钮的数字更新需要伴随这进/出动画(旧数字向上滚出,同时新数字由下到上移动),下面是实际效果图:

Likes counter

看起来眼熟?那就对了,这和我们在 第三篇文章  中实现的评论发送按钮是一样的效果。

但是这次我们使用TextSwitcher - 一个帮助我们动画切换两个TextView文字的ViewGroup。这两个TextView均有用(我们需要通过调用TestSwitcher.setText()方法来实现在两个TextView之间动画切换并更新值)。

首先,我们修改item_feed.xml布局,在底部添加一个like计数按钮:

<!--...-->
<LinearLayout
    android:layout_width="0dp"
    android:layout_height="match_parent"
    android:layout_weight="1"
    android:gravity="center_vertical|right">
     
    <ImageView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@drawable/ic_heart_small_blue" />
     
    <TextSwitcher
        android:id="@+id/tsLikesCounter"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginLeft="8dp"
        android:layout_marginRight="8dp"
        android:inAnimation="@anim/slide_in_likes_counter"
        android:outAnimation="@anim/slide_out_likes_counter">
         
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="123 likes"
            android:textColor="@color/text_like_counter" />
         
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textColor="@color/text_like_counter" />
    </TextSwitcher>
</LinearLayout>
<!--...-->

下面是添加的view在Android Studio中的预览效果:

Likes counter preview

代码中TextSwitcher将让两个子TextView动画切换,我们不需要担心隐藏的问题-内部已经实现了。android:inAnimation 和 android:outAnimation用于指定前一个子view和后一个子view的过度动画。

用0dp来达到更好的性能

包含了like计数按钮的LinearLayout被赋予了layout_weight="1"的属性,其父布局依然是LinearLayout,当LinearLayout只有一个子view使用了weight值,那么这个子view将吸收剩余的所有空间。而使用layout_width="0dp"可以让性能更高,因为这个view不需要被测量宽度了。

现在,我们添加一些代码到FeedAdapter。唯一值得一提的代码是updateLikesCounter()方法,这里是 包含所有改变的一次提交

private void updateLikesCounter(CellFeedViewHolder holder, boolean animated) {
    int currentLikesCount = likesCount.get(holder.getPosition()) + 1;
    String likesCountText = context.getResources().getQuantityString(
        R.plurals.likes_count, currentLikesCount, currentLikesCount
    );
     
    if (animated) {
        holder.tsLikesCounter.setText(likesCountText);
    } else {
        holder.tsLikesCounter.setCurrentText(likesCountText);
    }
     
    likesCount.put(holder.getPosition(), currentLikesCount);
}

这个方法用两种方式更新like计数按钮:

带动画的:(用于动态更新,当用户点击like按钮的时候使用) - 使用 TextSwitcher.setText() 方法。

不带动画的:(onBindViewHolder()中被调用,这种情况不需要动画) - 使用方法 - 这个方法更新当前TextView的值。

其实你可能还是不了解这两个方法的区别,看看官网的解释就一目了然了。

TextSwitcher.setCurrentText() : 设置当前正在显示的TextView的值。    

TextSwitcher.setText() :  设置下一个TextView的值,并切换到下一个TextView。                             

你可能注意到了,我们使用了_Quantity_字符串来处理单复数的问题,这里你可以找到更多关于android中使用Plurals 的知识。

like按钮动画

现在我们将专注于like按钮的动画,它的行为比概念视频上的看起来还要炫一些,截图如下:

Heart button

同时播放多个动画

这个动画效果由捆绑在一起的多个动画组成。因此我们不能像以前那样使用 ViewPropertyAnimator。幸好有播放多个动画的简便方法。 AnimatorSet 这个类可以将多个动画放在一起,然后可以选择同时播放,顺序播放,或者是混合播放。这里有更多的描述 Choreographing Multiple Animations

我们的like按钮效果由3个动画组成:

360度的旋转动画  - 这个是最开始播放的动画

Bounce动画 - 由两个动画组成( scaleX 和 scaleY 动画),当旋转动画结束后触发这个动画。

下面是设置like按钮的所有相关代码(和like计数按钮一样,分为动画与静态两种方式):

private void updateHeartButton(final CellFeedViewHolder holder, boolean animated) {
    if (animated) {
        if (!likeAnimations.containsKey(holder)) {
            AnimatorSet animatorSet = new AnimatorSet();
            likeAnimations.put(holder, animatorSet);
             
            ObjectAnimator rotationAnim = ObjectAnimator.ofFloat(holder.btnLike, "rotation", 0f, 360f);
            rotationAnim.setDuration(300);
            rotationAnim.setInterpolator(ACCELERATE_INTERPOLATOR);
             
            ObjectAnimator bounceAnimX = ObjectAnimator.ofFloat(holder.btnLike, "scaleX", 0.2f, 1f);
            bounceAnimX.setDuration(300);
            bounceAnimX.setInterpolator(OVERSHOOT_INTERPOLATOR);
             
            ObjectAnimator bounceAnimY = ObjectAnimator.ofFloat(holder.btnLike, "scaleY", 0.2f, 1f);
            bounceAnimY.setDuration(300);
            bounceAnimY.setInterpolator(OVERSHOOT_INTERPOLATOR);
            bounceAnimY.addListener(new AnimatorListenerAdapter() {
                @Override
                public void onAnimationStart(Animator animation) {
                    holder.btnLike.setImageResource(R.drawable.ic_heart_red);
                }
            });
             
            animatorSet.play(rotationAnim);
            animatorSet.play(bounceAnimX).with(bounceAnimY).after(rotationAnim);
             
            animatorSet.addListener(new AnimatorListenerAdapter() {
                @Override
                public void onAnimationEnd(Animator animation) {
                    resetLikeAnimationState(holder);
                }
            });
             
            animatorSet.start();
        }
    } else {
        if (likedPositions.contains(holder.getPosition())) {
            holder.btnLike.setImageResource(R.drawable.ic_heart_red);
        } else {
            holder.btnLike.setImageResource(R.drawable.ic_heart_outline_grey);
        }
    }
}

25和26行组成了我们的最终效果。

这次我们使用ObjectAnimator,它可以“动画”目标对象的属性。可以使用int,float,argb值去播放动画,任何带有setter方法的属性都可以用它。本例中我们通过ObjectAnimator.ofFloat()方法来来实现rotation值的动画,在内部,其实是不断调用setRotation(Float f)方法。

这里是添加 like按钮动画  提交的代码。

图片like 动画

图片like动画也稍微有点复杂。这次我们动画播放两个不同的对象(但仍然在一个AnimatorSet中):

.心形底部圆形背景(缩放以及渐变动画)

.心形图标(放大缩小动画)

The effect looks like below:

photo_like.gif

下面是实现上面动画的代码:

private void animatePhotoLike(final CellFeedViewHolder holder) {
    if (!likeAnimations.containsKey(holder)) {
        holder.vBgLike.setVisibility(View.VISIBLE);
        holder.ivLike.setVisibility(View.VISIBLE);
         
        holder.vBgLike.setScaleY(0.1f);
        holder.vBgLike.setScaleX(0.1f);
        holder.vBgLike.setAlpha(1f);
        holder.ivLike.setScaleY(0.1f);
        holder.ivLike.setScaleX(0.1f);
         
        AnimatorSet animatorSet = new AnimatorSet();
        likeAnimations.put(holder, animatorSet);
         
        ObjectAnimator bgScaleYAnim = ObjectAnimator.ofFloat(holder.vBgLike, "scaleY", 0.1f, 1f);
        bgScaleYAnim.setDuration(200);
        bgScaleYAnim.setInterpolator(DECCELERATE_INTERPOLATOR);
        ObjectAnimator bgScaleXAnim = ObjectAnimator.ofFloat(holder.vBgLike, "scaleX", 0.1f, 1f);
        bgScaleXAnim.setDuration(200);
        bgScaleXAnim.setInterpolator(DECCELERATE_INTERPOLATOR);
        ObjectAnimator bgAlphaAnim = ObjectAnimator.ofFloat(holder.vBgLike, "alpha", 1f, 0f);
        bgAlphaAnim.setDuration(200);
        bgAlphaAnim.setStartDelay(150);
        bgAlphaAnim.setInterpolator(DECCELERATE_INTERPOLATOR);
         
        ObjectAnimator imgScaleUpYAnim = ObjectAnimator.ofFloat(holder.ivLike, "scaleY", 0.1f, 1f);
        imgScaleUpYAnim.setDuration(300);
        imgScaleUpYAnim.setInterpolator(DECCELERATE_INTERPOLATOR);
        ObjectAnimator imgScaleUpXAnim = ObjectAnimator.ofFloat(holder.ivLike, "scaleX", 0.1f, 1f);
        imgScaleUpXAnim.setDuration(300);
        imgScaleUpXAnim.setInterpolator(DECCELERATE_INTERPOLATOR);
         
        ObjectAnimator imgScaleDownYAnim = ObjectAnimator.ofFloat(holder.ivLike, "scaleY", 1f, 0f);
        imgScaleDownYAnim.setDuration(300);
        imgScaleDownYAnim.setInterpolator(ACCELERATE_INTERPOLATOR);
        ObjectAnimator imgScaleDownXAnim = ObjectAnimator.ofFloat(holder.ivLike, "scaleX", 1f, 0f);
        imgScaleDownXAnim.setDuration(300);
        imgScaleDownXAnim.setInterpolator(ACCELERATE_INTERPOLATOR);
         
        animatorSet.playTogether(bgScaleYAnim, bgScaleXAnim, bgAlphaAnim, imgScaleUpYAnim, imgScaleUpXAnim);
        animatorSet.play(imgScaleDownYAnim).with(imgScaleDownXAnim).after(imgScaleUpYAnim);
         
        animatorSet.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                resetLikeAnimationState(holder);
            }
        });
        animatorSet.start();
    }
}

再一次用到了ObjectAnimatorAnimatorSet。(40-41行)。

这就是今天的全部内容,谢谢阅读。

来自:InstaMaterial概念设计