一、概述
Material Design 是Android5.0新出来的特征,相信大家都不陌生,下面就为大家讲讲Acivity切换的过渡效果。
二、先决条件
需要使用Android Studio开发工具 和API 21中引入的新的RecyclerView
控件
RecyclerView
控件 展示item列表,main activity会使用Android Lollipop中引入的RecyclerView
控件。 你需要做的第一件事,添加下面一行代码到你项目中build.grade文件的dependencies部分里,使得项目能向下兼容 compile
'com.android.support:recyclerview-v7:+'
为了尽可能的简洁代码,我们不会为程序定义数据库或类似的数据源。 相反,我们会使用一个自定义类,Contact
。 每一个item会有一个name、color和基本联系信息。 以下是Contact
类的大体实现。
// The fields associated to the person private final String mName, mPhone, mEmail, mCity, mColor; Contact(String name, String color, String phone, String email, String city) { mName = name; mColor = color; mPhone = phone; mEmail = email; mCity = city; } // This method allows to get the item associated to a particular id, // uniquely generated by the method getId defined below public static Contact getItem(int id) { for (Contact item : CONTACTS) { if (item.getId() == id) { return item; } } return null; } // since mName and mPhone combined are surely unique, // we don't need to add another id field public int getId() { return mName.hashCode() + mPhone.hashCode(); } public static enum Field { NAME, COLOR, PHONE, EMAIL, CITY } public String get(Field f) { switch (f) { case COLOR: return mColor; case PHONE: return mPhone; case EMAIL: return mEmail; case CITY: return mCity; case NAME: default: return mName; } }
最终你会得到一个能够很好地承载你所关注信息的类。 但是我们需要填充一些数据。 在Contact
类的起始位置,添加下面的代码以填充数据集。
定义数据为public
和static
,项目中每个类都可以读取该数据。 在现在的情况下,我们通过写进类中的代码来模拟从数据库获取数据的行为。
public
static
final
Contact[] CONTACTS =
new
Contact[] {
};
第三步:定义主布局Main Layouts
main activity的布局文件很简单,因为会把list列表数据填充满整个屏幕。 布局layout文件包含一个
RelativeLayout
相对布局作为根布局——使用LinearLayout
一样可行——并且添加一个RecyclerView
作为其唯一子布局。<
RelativeLayout
xmlns:android
=
"http://schemas.android.com/apk/res/android"
android:layout_width
=
"match_parent"
android:layout_height
=
"match_parent"
android:background
=
"#f5f5f5"
>
<
android.support.v7.widget.RecyclerView
android:layout_width
=
"match_parent"
android:layout_height
=
"match_parent"
android:id
=
"@+id/rv"
/>
因为
RelativeLayout
>
RecyclerView
只管理其子元素,你则需要一个布局文件来定义item的布局。 我们想要contact联系人列表的item布局的左边是一个有色彩的圆,所以你需要先在drawable目录下定义 circle.xml
<
shape
xmlns:android
=
"http://schemas.android.com/apk/res/android"
android:shape
=
"oval"
>
<
solid
android:color
=
"#000"
/>
<
size
android:width
=
"32dp"
android:height
=
"32dp"
/>
你现在已经集齐所有的元素,可以定义item的布局了。
shape
>
<
RelativeLayout
xmlns:android
=
"http://schemas.android.com/apk/res/android"
android:layout_width
=
"match_parent"
android:layout_height
=
"82dp"
android:padding
=
"@dimen/activity_horizontal_margin"
android:background
=
"?android:selectableItemBackground"
android:clickable
=
"true"
android:focusable
=
"true"
android:orientation
=
"vertical"
>
<
View
android:id
=
"@+id/CONTACT_circle"
android:layout_width
=
"40dp"
android:layout_height
=
"40dp"
android:background
=
"@drawable/circle"
android:layout_centerVertical
=
"true"
android:layout_alignParentLeft
=
"true"
/>
<
LinearLayout
android:layout_width
=
"wrap_content"
android:layout_height
=
"wrap_content"
android:layout_centerVertical
=
"true"
android:layout_toRightOf
=
"@+id/CONTACT_circle"
android:layout_marginLeft
=
"@dimen/activity_horizontal_margin"
android:orientation
=
"vertical"
>
<
TextView
android:id
=
"@+id/CONTACT_name"
android:layout_width
=
"wrap_content"
android:layout_height
=
"wrap_content"
android:text
=
"Jonh Doe"
android:textColor
=
"#000"
android:textSize
=
"18sp"
/>
<
TextView
android:id
=
"@+id/CONTACT_phone"
android:layout_width
=
"wrap_content"
android:layout_height
=
"wrap_content"
android:text
=
"+01 123456789"
android:textColor
=
"#9f9f9f"
android:textSize
=
"15sp"
/>
LinearLayout
>
RelativeLayout
>
第四步:通过
RecyclerView
展示数据我们就快完成本教程的第一部分了。 你还需要重写
RecyclerView.ViewHolder
和RecyclerView.Adapter
,并在main activity的onCreate
方法中为view做相应绑定。 在此,RecyclerView.ViewHolder实现类需要能够处理点击事件,所以你需要添加一个点击事件的相应实现类。 下面让我们开始定义处理点击事件的实现类吧。public
class
RecyclerClickListener
implements
RecyclerView.OnItemTouchListener {
private
OnItemClickListener mListener;
GestureDetector mGestureDetector;
public
interface
OnItemClickListener {
public
void
onItemClick(View view,
int
position);
}
public
RecyclerClickListener(Context context, OnItemClickListener listener) {
mListener = listener;
mGestureDetector =
new
GestureDetector(context,
new
GestureDetector.SimpleOnGestureListener() {
@Override
public
boolean
onSingleTapUp(MotionEvent e) {
return
true
;
}
});
}
@Override
public
boolean
onInterceptTouchEvent(RecyclerView view, MotionEvent e) {
View childView = view.findChildViewUnder(e.getX(), e.getY());
if
(childView !=
null
&& mListener !=
null
&& mGestureDetector.onTouchEvent(e)) {
mListener.onItemClick(childView, view.getChildPosition(childView));
return
true
;
}
return
false
;
}
@Override
public
void
onTouchEvent(RecyclerView view, MotionEvent motionEvent) { }
}
RecyclerView.Adapter
实现类需要做详细的解析,这个类里我称之为DataManager
。 这个类负责加载数据并将数据添加到视图列表里的视图中。 DataManager类也会包含RecyclerView.ViewHolder
的属性。
public
class
DataManager
extends
RecyclerView.Adapter
{
public
static
class
RecyclerViewHolder
extends
RecyclerView.ViewHolder {
TextView mName, mPhone;
View mCircle;
RecyclerViewHolder(View itemView) {
super
(itemView);
mName = (TextView) itemView.findViewById(R.id.CONTACT_name);
mPhone = (TextView) itemView.findViewById(R.id.CONTACT_phone);
mCircle = itemView.findViewById(R.id.CONTACT_circle);
}
}
@Override
public
RecyclerViewHolder onCreateViewHolder(ViewGroup viewGroup,
int
i) {
View v = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.contact_item, viewGroup,
false
);
return
new
RecyclerViewHolder(v);
}
@Override
public
void
onBindViewHolder(RecyclerViewHolder viewHolder,
int
i) {
// get the single element from the main array
final
Contact contact = Contact.CONTACTS[i];
// Set the values
viewHolder.mName.setText(contact.get(Contact.Field.NAME));
viewHolder.mPhone.setText(contact.get(Contact.Field.PHONE));
// Set the color of the shape
GradientDrawable bgShape = (GradientDrawable) viewHolder.mCircle.getBackground();
bgShape.setColor(Color.parseColor(contact.get(Contact.Field.COLOR)));
}
@Override
public
int
getItemCount() {
return
Contact.CONTACTS.length;
}
}
最后,在
onCreate
方法中的setContentView
代码后添加以下代码。 至此,main activity就基本完成,可以展示列表数据了。
RecyclerView rv = (RecyclerView) findViewById(R.id.rv);
// layout reference
LinearLayoutManager llm =
new
LinearLayoutManager(
this
);
rv.setLayoutManager(llm);
rv.setHasFixedSize(
true
);
// to improve performance
rv.setAdapter(
new
DataManager());
// the data manager is assigner to the RV
rv.addOnItemTouchListener(
// and the click is handled
new
RecyclerClickListener(
this
,
new
RecyclerClickListener.OnItemClickListener() {
@Override
public
void
onItemClick(View view,
int
position) {
// STUB:
// The click on the item must be handled
}
}));
编译后运行程序效果见下图。
2.创建Detail详情Activity
第一步:布局
第二个activity就简单多了。 通过contact联系人列表选中的ID来重新获取第一个activity没有展示的额外信息。
从view设计的角度来看,这个activity的布局很重要,因为它是应用程序中最重要的部分。 但是XML文件中要关注的并不太重要。 布局文件是一系列以合适方式排布的
TextView
实例,同时使用了RelativeLayout
和LinearLayout
。 以下是布局文件代码:<
LinearLayout
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"
android:orientation
=
"vertical"
>
<
ImageView
android:layout_width
=
"match_parent"
android:layout_height
=
"200dp"
android:scaleType
=
"centerCrop"
android:src
=
"@mipmap/material_wallpaper"
/>
<
RelativeLayout
android:layout_width
=
"match_parent"
android:layout_height
=
"82dp"
android:padding
=
"@dimen/activity_vertical_margin"
>
<
View
android:id
=
"@+id/DETAILS_circle"
android:layout_width
=
"48dp"
android:layout_height
=
"48dp"
android:background
=
"@drawable/circle"
android:layout_centerVertical
=
"true"
android:layout_alignParentLeft
=
"true"
/>
<
TextView
android:id
=
"@+id/DETAILS_name"
android:layout_width
=
"wrap_content"
android:layout_height
=
"wrap_content"
android:text
=
"Jonh Doe"
android:layout_toRightOf
=
"@+id/DETAILS_circle"
android:layout_marginLeft
=
"@dimen/activity_horizontal_margin"
android:layout_centerVertical
=
"true"
android:textColor
=
"#000"
android:textSize
=
"25sp"
/>
RelativeLayout
>
<
LinearLayout
android:layout_width
=
"wrap_content"
android:layout_height
=
"wrap_content"
android:layout_centerVertical
=
"true"
android:padding
=
"@dimen/activity_horizontal_margin"
android:orientation
=
"vertical"
>
<
RelativeLayout
android:layout_width
=
"match_parent"
android:layout_height
=
"wrap_content"
>
<
TextView
android:id
=
"@+id/DETAILS_phone_label"
android:layout_width
=
"wrap_content"
android:layout_height
=
"wrap_content"
android:text
=
"Phone:"
android:textColor
=
"#000"
android:textSize
=
"20sp"
/>
<
TextView
android:id
=
"@+id/DETAILS_phone"
android:layout_width
=
"wrap_content"
android:layout_height
=
"wrap_content"
android:layout_toRightOf
=
"@+id/DETAILS_phone_label"
android:layout_marginLeft
=
"@dimen/activity_horizontal_margin"
android:text
=
"+01 123456789"
android:textColor
=
"#9f9f9f"
android:textSize
=
"20sp"
/>
RelativeLayout
>
<
RelativeLayout
android:layout_width
=
"match_parent"
android:layout_height
=
"wrap_content"
android:layout_marginTop
=
"@dimen/activity_vertical_margin"
>
<
TextView
android:id
=
"@+id/DETAILS_email_label"
android:layout_width
=
"wrap_content"
android:layout_height
=
"wrap_content"
android:text
=
"Email:"
android:textColor
=
"#000"
android:textSize
=
"20sp"
/>
<
TextView
android:id
=
"@+id/DETAILS_email"
android:layout_width
=
"wrap_content"
android:layout_height
=
"wrap_content"
android:layout_toRightOf
=
"@+id/DETAILS_email_label"
android:layout_marginLeft
=
"@dimen/activity_horizontal_margin"
android:textColor
=
"#9f9f9f"
android:textSize
=
"20sp"
/>
RelativeLayout
>
<
RelativeLayout
android:layout_width
=
"match_parent"
android:layout_height
=
"wrap_content"
android:layout_marginTop
=
"@dimen/activity_vertical_margin"
>
<
TextView
android:id
=
"@+id/DETAILS_city_label"
android:layout_width
=
"wrap_content"
android:layout_height
=
"wrap_content"
android:text
=
"City:"
android:textColor
=
"#000"
android:textSize
=
"20sp"
/>
<
TextView
android:id
=
"@+id/DETAILS_city"
android:layout_width
=
"wrap_content"
android:layout_height
=
"wrap_content"
android:layout_toRightOf
=
"@+id/DETAILS_city_label"
android:layout_marginLeft
=
"@dimen/activity_horizontal_margin"
android:text
=
"Rome"
android:textColor
=
"#9f9f9f"
android:textSize
=
"20sp"
/>
RelativeLayout
>
LinearLayout
>
LinearLayout
>
第二步:通过Intent发送并接受ID数据
因为两个activity是通过intent连接的,你需要发送相应的信息给第二个activity来使其理解你需要显示哪个contact联系人的详情。
可以选择变量的位置作为引用。 item在视图列表中的位置和在数组中的位置是一致的,所以使用位置这个数字作为唯一的标识是可以的。
以上方法是可行的,但是如果你采用这种方式,出于某些原因,数据集在运行时是动态变化的,这时位置的引用关系就不会取到你想要的数据。 这就是为什么使用ID效果更好。 以下是
Contact
类中定义的getId
方法。按照下面的方法编辑
onItemClick
方法来处理列表项的点击事件。@Override
public
void
onItemClick(View view,
int
position) {
Intent intent =
new
Intent(MainActivity.
this
, DetailsActivity.
class
);
intent.putExtra(DetailsActivity.ID, Contact.CONTACTS[position].getId());
startActivity(intent);
}
DetailsActivity
会通过Intent
的extras属性收到信息,并且通过ID引用获得正确的数据来构造正确的对象。 详情见以下代码。
// Before the onCreate
public
final
static
String ID =
"ID"
;
public
Contact mContact;
12// In the onCreate, after the setContentView method
mContact = Contact.getItem(getIntent().getIntExtra(ID,
0
));
和之前
RecyclerView
中的onCreateViewHolder
方法一样,这个view使用findViewById
方法找到控件并使用setText
方法填充数据。 如下,我们通过下面的方法来给展示姓名的TextView填充数据。3. 关联变化
我们终于到了本教程的核心了,使用Lollipop中最新的共享元素过渡动画作为两个activity的过渡动画。
第一步:配置你的项目
首先你要做的是编辑values-v21目录下的style.xml文件。 通过这样的方法,你可以设置过渡动画的内容,并设置两个activity不共享view的进入和消失的方式。
010203040506070809101112131415<
style
name
=
"AppTheme"
parent
=
"AppTheme.Base"
>
style
>
<
style
name
=
"AppTheme.Base"
parent
=
"android:Theme.Material.Light"
>
<
item
name
=
"android:windowContentTransitions"
>true
item
>
<
item
name
=
"android:windowEnterTransition"
>@android:transition/slide_bottom
item
>
<
item
name
=
"android:windowExitTransition"
>@android:transition/slide_bottom
item
>
<
item
name
=
"android:windowAllowEnterTransitionOverlap"
>true
item
>
<
item
name
=
"android:windowAllowReturnTransitionOverlap"
>true
item
>
<
item
name
=
"android:windowSharedElementEnterTransition"
>@android:transition/move
item
>
<
item
name
=
"android:windowSharedElementExitTransition"
>@android:transition/move
item
>
style
>
请注意这时你的项目的最高支持版本(同时编译项目使用的最低版本)至少为Android API 21。
这个动画在低于Lollipop版本的系统中会被忽略从而不执行。 不幸的是,由于性能上的原因,AppCompat兼容包没有为这些动画提供完全的向下兼容。
第二步:在Layout文件中指定变换控件的名字
当你编辑了style.xml文件后,你需要指明视图间两个共享元素之间的关联。
在此示例中,共享的视图元素是包括contact联系人姓名、一个电话号码和一个彩色圆形的控件。 视图中的每个元素,你都要为其指定一个共同的变换名称。 出于这个原因,我们在再strings.xml资源文件中加入下列元素。
123<
string
name
=
"transition_name_name"
>transition:NAME
string
>
<
string
name
=
"transition_name_circle"
>transition:CIRCLE
string
>
<
string
name=“transition_name_phone”>transition:PHONE
string
>
然后,在layout文件的三个元素中添加
android:transitionName
属性并赋予相对应的值。 下面是彩色圆元素的代码示例:
123456789
android:id=“@+id/CONTACT_circle”
android:transitionName=“@string/transition_name_circle”
android:layout_width=“40dp”
android:layout_height=“40dp”
android:background=“@drawable/circle”
android:layout_centerVertical=“true”
android:layout_alignParentLeft=“true”/>
123456789
android:id=“@+id/DETAILS_circle”
android:transitionName=“@string/transition_name_circle”
android:layout_width=“48dp”
android:layout_height=“48dp”
android:background=“@drawable/circle”
android:layout_centerVertical=“true”
android:layout_alignParentLeft=“true”/>
正是这个属性,Android系统会知道两个activity的视图间哪个view为共享元素并且在过渡中添加正确的动画。 另外两个view采取同样的处理。
第三步:配置Intent
从view视图的编码角度看,你需要在intent中绑定特定
ActivityOptions
对象。 你所需的方法是makeSceneTransitionAnimation
,这个方法接受的参数为context上下文对象和共享元素数组。 在RecyclerView
的onItemClick
方法中,将之前的Intent
修改为下面的实现:@Override
public
void
onItemClick(View view,
int
position) {
Intent intent =
new
Intent(MainActivity.
this
, DetailsActivity.
class
);
intent.putExtra(DetailsActivity.ID, Contact.CONTACTS[position].getId());
ActivityOptionsCompat options = ActivityOptionsCompat.makeSceneTransitionAnimation(
// the context of the activity
MainActivity.
this
,
// For each shared element, add to this method a new Pair item,
// which contains the reference of the view we are transitioning *from*,
// and the value of the transitionName attribute
new
Pair
(view.findViewById(R.id.CONTACT_circle),
getString(R.string.transition_name_circle)),
new
Pair
(view.findViewById(R.id.CONTACT_name),
getString(R.string.transition_name_name)),
new
Pair
(view.findViewById(R.id.CONTACT_phone),
getString(R.string.transition_name_phone))
);
ActivityCompat.startActivity(MainActivity.
this
, intent, options.toBundle());
}
实现每个共享元素的动画,你需要在
makeSceneTransitionAnimation
方法中添加一个新的Pair
对象。 每个Pair
对象包含两个值,第一个值是动画起始view对象的引用,第二个值是该view对象transitionName
属性的值。导入Pair类的时候需要注意下。 你需要导入
android.support.v4.util
包下的Pair类,而不是android.util
包下的Pair类。 同时,谨记使用ActivityCompat.startActivity
方法而不是startActivity
方法,否则,你的程序在API低于16的系统中无法运行。完结。 撒花。 就是这么简单。
总结
在本教程中,你学到了如何给共用一个或更多的元素的两个activity添加漂亮无缝的过渡动画,获得视觉上愉悦的同时展示了富有含义的持续动画。
你通过制作两个activity中第一个展示contact联系人列表的activity开始了本教程。 随后你又完成了第二个activity,设计它的布局,并且想办法实现了在两个activity间传递一个唯一引用。 最终,你了解了
makeSceneTransitionAnimation
方法的运作方式,XML文件中的transitionName
属性简直是神来之笔。注意:最后附上的项目是将style里面的样式注释掉了,如果有需要的可以放开,笔者觉得添加不是很漂亮!
源码地址:http://download.csdn.net/detail/plxiaopan/9436002