实现Instagram的Material Design设计系列第一篇http://blog.csdn.net/tongsdroid/article/details/51567583
这篇文章是一个实现Instagram的Material Design设计系列的第二篇文章,今天,我们将实现主页和评论活动之间的过渡(在概念录像显示为9秒之间13)。我们将跳过按钮效果(涟漪,发送完成动画等),只关注发表评论的Acitvity进入和退出动画。
这是在今天的文章中描述(适用于Android5.0和之前的版本)的最终效果:
初始化配置
首先添加需要用到的开源库和一些重要的信息到之前已有的工程。我们需要添加:
- Picasso 用于异步的图片加载(在评论列表中使用,用于加载用户头像)
- 在
AndroidMainfest.xml
中声明带有主题的CommentsActivity
.
此外,我们还应该为CommentsActivity
创建布局。一切几乎就像·MainAcitvity·相同,除了添加评论的底部组件。我们再一次使用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>
接下来,创建评论列表项的布局:
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 item底部的点击进入CommentActivity
界面的onClick
事件吧。现在,我们使用整个的feed item的底部,之后会替换成真正的按钮,在这一步,我们要在RecyclerView
的适配器中给feed item底部添加onClickListener
,并且我们会创建简单的接口开将MainActivity
和我们的适配器整合在一起,那么应该如何实现呢?
FeedAdapter.java
(仅包含改变的部分)
//.. 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.java
(仅包含改变部分)
//... 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
。。。这就是所有初始化的配置了。
CommentActivity的跳转
基于概念视频,下面是我们希望实现的效果:
- 静态的
ToolBar
:新的Activity打开时ToolBar不会移动,(我们用代码控制骗过用户,让用户以为他还在同一个窗口中) - 评论窗口应该从用户点击的位置扩展开来(不管用户从哪里点击)
- 评论列表的item应该在扩展动画结束后一条接一条的显示
静态的ToolBar
这是本篇文章最简单的部分,因为MainActivity
和CommentsActivity的`TollBar`是一样的。所以我们要做的就是禁用`Activity`之间跳转的默认动画。这样看起来就好像是静态的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);
我们可以禁用退出动画和进入动画。
从点击的位置扩展CommentsActivity
现在我们创建扩展动画(可以从屏幕任何地方开始)。包含两部分:
- 扩展的背景
- 显示评论列表
在我们编写动画代码之前,我们必须先使CommentsActivity
半透明。否则扩展动画的背景将会是窗体的背景色,而不是MainActivity
的背景色。这是因为每一个activity都有windowBackground
属性,这个属性定义在当前使用的主题中。如果我们想禁用它是我们的Activity半透明,我们必须修改style.xml文件。代码如下:
style.xml
<?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坐标,在我们的例子中我们没必要知道点击的具体位置(动画速度非常快,用户不会注意到动画开始的位置不是点击的位置)。我们可以将点击位置的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
中实现背景扩展动画。我们可以使用简单的ScaleAnimation
(这时候没有任何可见的内容,因此没人知道我们是拉伸背景而不是扩展它)。
不要忘记使用setPivoY()
方法设置正确的初始位置。
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();
}
//...
}
就像上面展示的动画只执行一次,在我们代开CommentsActivity
之后,
多亏了onPreDrawListener ,我们才可以在view树完成测量并且分配空间而绘制过程还没有开始的时候播放动画。
在以上的代码中我们实现了背景的扩展和评论列表的显示,效果如下:
好像还遗漏了什么东西似的,不是吗?
现在我们必须为评论列表中的每个item创建动画,这也非常简单,但是我们必须在心中记下这些重要的事情:
- 每一个item的动画应该延迟一点点,否则所有的动画将会在同一时刻执行,用户会看到items一起显示而不是一条一条的显示。
- Adapter应该具备封锁动画的能力,因为我们不想再用户滑动list的时候还在执行动画。
- 还有,我们必须提供方法用于暂时的解锁并执行动画(当添加新的评论时)
现在CommentsAdapter.java
应该是这个样子
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);
}
}
}
为了显示头像,我们使用Picasso库和CircleTransformation.,感谢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());
}
}
items动画被封锁当用户开始滑动RecyclerView
的时候,并且在添加item的时候短暂接触封锁。
进入的跳转到这里结束了。
退出跳转
最后一件事,我们还要实现退出的跳转,基于概念视频,没什么特别需要做的,我们必须创建滑出当前界面的跳转动画。我们还得记住我们不能移除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();
}
}
OK,这篇文章到这里就结束了,我们完成了Material Design风格的Instagram的第二步,下一篇文章我想讲解我们现在遗漏的细节处理。
源代码
作者Twitter:https://twitter.com/froger_mcs
原文链接:http://frogermcs.github.io/Instagram-with-Material-Design-concept-part-2-Comments-transition/