InstaMaterial概念设计第二部分-评论界面的过度动画
今天我们将要实现的是feed和评论界面切换以及相关的动画效果。(视频中9-13秒那部分)。我将忽略涉及到的button效果(波纹、发送完成的动画等,在下篇文章专门讨论),将重点放在comment Acitvity进入的效果上面。
初始化
我们首先将一些不太重要的东西加入前面文章所创建的项目中previously created project。
.Picasso 一个图片异步加载库(用于评论列表中显示作者的头像),
.AndroidManifest.xml
中添加评论的activity
当然我们还要创建新activity的布局文件。除了底部的评论输入框之外基本上和上篇文章是一致的。我们再次用到了Toolbar,`RecyclerView`等。没有什么好值得讲的。
activity_comments.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".CommentsActivity">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
android:elevation="@dimen/default_elevation">
<ImageView
android:id="@+id/ivLogo"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
android:scaleType="center"
android:src="@drawable/img_toolbar_logo" />
</android.support.v7.widget.Toolbar>
<LinearLayout
android:id="@+id/contentRoot"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@id/toolbar"
android:background="@color/bg_comments"
android:elevation="@dimen/default_elevation"
android:orientation="vertical">
<android.support.v7.widget.RecyclerView
android:id="@+id/rvComments"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:scrollbars="none" />
<LinearLayout
android:id="@+id/llAddComment"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/bg_comments"
android:elevation="@dimen/default_elevation">
<EditText
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1" />
<Button
android:id="@+id/btnSendComment"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Send" />
</LinearLayout>
</LinearLayout>
</RelativeLayout>
![comments_activity_preview.png](https://img.paonet.com/upload-images-old/uploads/20150206/1423158628330139.png "1423158628330139.png")
下一步,创建评论列表item的布局:
item_comment.xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:paddingBottom="8dp"
android:paddingTop="8dp">
<ImageView
android:id="@+id/ivUserAvatar"
android:layout_width="@dimen/comment_avatar_size"
android:layout_height="@dimen/comment_avatar_size"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:background="@drawable/bg_comment_avatar" />
<TextView
android:id="@+id/tvComment"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginRight="16dp"
android:layout_weight="1"
android:text="Lorem ipsum dolor sit amet" />
</LinearLayout>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_gravity="bottom"
android:layout_marginLeft="88dp"
android:background="#cccccc" />
</FrameLayout>
评论作者圆角头像部分的代码片段:
bg_comment_avatar.xml
<?xml version="1.0" encoding="utf-8"?>
<!--drawable/bg_comment_avar.xml-->
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<solid android:color="#999999" />
</shape>
最后一件事情是处理feed卡片底部评论按钮的的onClick事件,这个事件将打开显示当前照片评论的CommentsActivity。我们暂时将卡片的整个底部区域作为点击按钮,但是以后将会替换为真正的评论按钮。我们在RecyclerView adapter中添加onClick的listener,将为每个卡片的底部设置这个listener。
FeedAdapter 类(只包含改动部分):
//.. implements View.OnClickListener
public class FeedAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> implements View.OnClickListener {
private OnFeedItemClickListener onFeedItemClickListener;
//..
@Override
public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int position) {
//...
holder.ivFeedBottom.setOnClickListener(this);
holder.ivFeedBottom.setTag(position);
}
//..
@Override
public void onClick(View v) {
if (v.getId() == R.id.ivFeedBottom) {
if (onFeedItemClickListener != null) {
onFeedItemClickListener.onCommentsClick(v, (Integer) v.getTag());
}
}
}
public void setOnFeedItemClickListener(OnFeedItemClickListener onFeedItemClickListener) {
this.onFeedItemClickListener = onFeedItemClickListener;
}
//..
public interface OnFeedItemClickListener {
public void onCommentsClick(View v, int position);
}
}
MainActivity 类(只包含改动部分):
//... implements FeedAdapter.OnFeedItemClickListener
public class MainActivity extends ActionBarActivity implements FeedAdapter.OnFeedItemClickListener {
//...
private void setupFeed() {
//...
feedAdapter.setOnFeedItemClickListener(this);
//...
}
//...
@Override
public void onCommentsClick(View v, int position) {
}
}
为了防止遗漏,我们将这一部分的代码提交在了这里:[onClick on item in RecyclerView adapter](https://github.com/frogermcs/InstaMaterial/commit/ec3d47bd546f4bdcb7ba1a2a5afe58112972ea0a)。
进入动画
首先我们将创建进入的过度动画,根据概念视频上的显示,以下是我们需要实现的效果:
1.静态的Toolbar-新的activity打开的时候Actionbar不能有过渡与切换效果(我们想让用户以为他们仍然在同一个窗口中)
2.评论列表界面要根据用户点击的位置展开(不管列表滚动到何处)
3.在展开效果结束之后,评论列表中的每一项要依次展示
静态的Toolbar
这是本文最简单的部分。因为在MainActivity与CommentsActivity中Toolbar是非常相似的,所以我们只需要将Acitvity默认的切换效果屏蔽了就可以了,那样新的窗口不会有任何移动仅仅是绘制在上个窗口的上面。这样就实现了Toolbar的静态显示。下面是代码:
public class MainActivity extends ActionBarActivity implements FeedAdapter.OnFeedItemClickListener {
//...
@Override
public void onCommentsClick(View v, int position) {
final Intent intent = new Intent(this, CommentsActivity.class);
startActivity(intent);
//Disable enter transition for new Acitvity
overridePendingTransition(0, 0);
}
}
通过调用overridePendingTransition(0, 0);我们屏蔽了MainActivity的退出效果,以及CommentsActivity的进入效果。
从被点击的地方展开CommentsActivity
现在我们创建可以从任何点击的地方开始的扩展动画。这个动画分为:
.展开背景
.显示内容
在我们开始动画部分的代码之前,先将CommentsActivity设置成半透明。不然的话扩展动画将显示这默认的窗口背景之上,而不是MainAcitvity的view之上。这是因为每个activity的窗口背景都是定义在它所采用的主题中了的。如果我们想让activity变半透明,我们需要修改样式:
<?xml version="1.0" encoding="utf-8"?>
<!-- styles.xml-->
<resources>
<!--...-->
<style name="AppTheme.CommentsActivity" parent="AppTheme">
<item name="android:windowBackground">@android:color/transparent</item>
<item name="android:windowIsTranslucent">true</item>
</style>
</resources>
下面是改变背景与不改变背景的区别
现在我们可以开始制造展开的效果了。首先,我们需要得到动画的Y轴初始值。我们的例子中其实完全不需要知道点击的精确位置(动画很快,用户不会注意到几个像素的差异的),我使用被点击view的Y值来替代,并且将这个Y值传递给CommentsActivity:
public class MainActivity extends ActionBarActivity implements FeedAdapter.OnFeedItemClickListener {
//...
@Override
public void onCommentsClick(View v, int position) {
final Intent intent = new Intent(this, CommentsActivity.class);
//Get location on screen for tapped view
int\[\] startingLocation = new int\[2\];
v.getLocationOnScreen(startingLocation);
intent.putExtra(CommentsActivity.ARG_DRAWING_START_LOCATION, startingLocation\[1\]);
startActivity(intent);
overridePendingTransition(0, 0);
}
}
下一步,在CommentsActivity中实现background的展开动画。为了简便起见,我们使用Scale动画(因为在此刻还没有任何内容,因此没人知道到底是缩放开来的还是展开的),别忘了使用setPivotY() 方法设置正确的初始位置。
public class CommentsActivity extends ActionBarActivity {
public static final String ARG_DRAWING_START_LOCATION = "arg_drawing_start_location";
@InjectView(R.id.toolbar)
Toolbar toolbar;
@InjectView(R.id.contentRoot)
LinearLayout contentRoot;
@InjectView(R.id.rvComments)
RecyclerView rvComments;
@InjectView(R.id.llAddComment)
LinearLayout llAddComment;
private CommentsAdapter commentsAdapter;
private int drawingStartLocation;
@Override
protected void onCreate(Bundle savedInstanceState) {
//...
drawingStartLocation = getIntent().getIntExtra(ARG_DRAWING_START_LOCATION, 0);
if (savedInstanceState == null) {
contentRoot.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
@Override
public boolean onPreDraw() {
contentRoot.getViewTreeObserver().removeOnPreDrawListener(this);
startIntroAnimation();
return true;
}
});
}
}
//...
private void startIntroAnimation() {
contentRoot.setScaleY(0.1f);
contentRoot.setPivotY(drawingStartLocation);
llAddComment.setTranslationY(100);
contentRoot.animate()
.scaleY(1)
.setDuration(200)
.setInterpolator(new AccelerateInterpolator())
.setListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
animateContent();
}
})
.start();
}
private void animateContent() {
commentsAdapter.updateItems();
llAddComment.animate().translationY(0)
.setInterpolator(new DecelerateInterpolator())
.setDuration(200)
.start();
}
//...
}
多亏了onPreDrawListener ,我们才可以在view树完成测量并且分配空间而绘制过程还没有开始的时候播放动画。
上面的代码中我们已经实现了展开背景与显示内容的动画,下面是运行的效果:
是不是感觉还是少了点什么东西?
还需要准备评论列表中每个评论项的动画。很简单,但是需要注意几件重要的事情:
1.每个item的动画需要有一定延时。否则所有的动画将在瞬间结束用户只能感受到一个动画。
2.adapter需要有锁定动画的功能,因为在用户滚动列表的时候动画是不需要的。
3.同样的我们还要让每个单独的item能锁定与解锁动画(比如添加一个评论)
目前CommentsAdapter 的代码如下:
public class CommentsAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private Context context;
private int itemsCount = 0;
private int lastAnimatedPosition = -1;
private int avatarSize;
private boolean animationsLocked = false;
private boolean delayEnterAnimation = true;
public CommentsAdapter(Context context) {
this.context = context;
avatarSize = context.getResources().getDimensionPixelSize(R.dimen.btn_fab_size);
}
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
final View view = LayoutInflater.from(context).inflate(R.layout.item_comment, parent, false);
return new CommentViewHolder(view);
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int position) {
runEnterAnimation(viewHolder.itemView, position);
CommentViewHolder holder = (CommentViewHolder) viewHolder;
switch (position % 3) {
case 0:
holder.tvComment.setText("Lorem ipsum dolor sit amet, consectetur adipisicing elit.");
break;
case 1:
holder.tvComment.setText("Cupcake ipsum dolor sit amet bear claw.");
break;
case 2:
holder.tvComment.setText("Cupcake ipsum dolor sit. Amet gingerbread cupcake. Gummies ice cream dessert icing marzipan apple pie dessert sugar plum.");
break;
}
Picasso.with(context)
.load(R.drawable.ic_launcher)
.centerCrop()
.resize(avatarSize, avatarSize)
.transform(new RoundedTransformation())
.into(holder.ivUserAvatar);
}
private void runEnterAnimation(View view, int position) {
if (animationsLocked) return;
if (position > lastAnimatedPosition) {
lastAnimatedPosition = position;
view.setTranslationY(100);
view.setAlpha(0.f);
view.animate()
.translationY(0).alpha(1.f)
.setStartDelay(delayEnterAnimation ? 20 * (position) : 0)
.setInterpolator(new DecelerateInterpolator(2.f))
.setDuration(300)
.setListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
animationsLocked = true;
}
})
.start();
}
}
@Override
public int getItemCount() {
return itemsCount;
}
public void updateItems() {
itemsCount = 10;
notifyDataSetChanged();
}
public void addItem() {
itemsCount++;
notifyItemInserted(itemsCount - 1);
}
public void setAnimationsLocked(boolean animationsLocked) {
this.animationsLocked = animationsLocked;
}
public void setDelayEnterAnimation(boolean delayEnterAnimation) {
this.delayEnterAnimation = delayEnterAnimation;
}
public static class CommentViewHolder extends RecyclerView.ViewHolder {
@InjectView(R.id.ivUserAvatar)
ImageView ivUserAvatar;
@InjectView(R.id.tvComment)
TextView tvComment;
public CommentViewHolder(View view) {
super(view);
ButterKnife.inject(this, view);
}
}
}
展示头像我们使用带CircleTransformation的Picasso库,我们利用了RecyclerView 的notifyItemInserted方法实现了添加一个item的动画效果,其余的代码都很简单。
下面是在CommentsActivity 中使用的代码:
public class CommentsActivity extends ActionBarActivity {
//...
private void setupComments() {
//...
rvComments.setOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
if (newState == RecyclerView.SCROLL_STATE_DRAGGING) {
commentsAdapter.setAnimationsLocked(true);
}
}
});
}
@OnClick(R.id.btnSendComment)
public void onSendCommentClick() {
commentsAdapter.addItem();
commentsAdapter.setAnimationsLocked(false);
commentsAdapter.setDelayEnterAnimation(false);
rvComments.smoothScrollBy(0, rvComments.getChildAt(0).getHeight() * commentsAdapter.getItemCount());
}
}
Item的动画在用户滚动RecyclerView的时候被锁定,这就是进入动画的所有东西了。
退出动画
最后一件事是实现退出动画,没有非常特别的技巧,我们只需创建一个transition 动画来滑出activity就可以了,记住Toolbar必须是静止的,因此我们再次使用overridePendingTransition(0, 0);并且播放内容部分的动画。
public class CommentsActivity extends ActionBarActivity {
//...
@Override
public void onBackPressed() {
contentRoot.animate()
.translationY(Utils.getScreenHeight(this))
.setDuration(200)
.setListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
CommentsActivity.super.onBackPressed();
overridePendingTransition(0, 0);
}
})
.start();
}
}
以上就是我们实现概念app的第二阶段的所有内容。下一篇我们将讨论这章遗漏的内容(按钮的动画效果)
源码
讨论中例子的源码在这里: repository.
英文原文:InstaMaterial concept (part 2) - Comments window transition
转载请注明出处:http://jcodecraeer.com/a/anzhuokaifa/androidkaifa/2015/0206/2422.html