Android 5.0 Lollipop中新的Activity过渡效果介绍
来源:互联网 发布:mac 找不到原身 编辑:程序博客网 时间:2024/05/16 04:00
介绍
Material Design说明中最有趣的一方面就是各个Activity在切换时保持的视觉连串性。 仅需几行代码,新的Lollipop API让你可以在两个Activity之间添加富有含义的过渡效果,多亏了有无缝持续动画。 这打破了之前Android版本中经典的Activity之间的界限,并且能够让用户理解页面元素是如何从一个位置移动到另一个位置。
在本教程中,我会通过创建一个符合谷歌Material Design规范的示例应用,来向你展示如何制作这样的效果。
先决条件
在本教程中,我假设你已熟悉Android开发并且使用Android Studio作为你的开发工具。 我会广泛地使用Android Intent,采用Activity生命周期的一些基本知识,和API 21中引入的新的RecyclerView
控件。
1. 创建第一个Activity
本应用的基本结构清晰明了。 有两个Activity,一个主Activity:MainActivity.java,用作展示item列表;第二个Activity:DetailActivity.java,用作展示之前列表项选中item的详情。
第一步:RecyclerView
控件
展示item列表,main activity会使用Android Lollipop中引入的RecyclerView
控件。 你需要做的第一件事,添加下面一行代码到你项目中build.grade文件的dependencies部分里,使得项目能向下兼容。
compile 'com.android.support:recyclerview-v7:+'
第二步:定义数据
为了尽可能的简洁代码,我们不会为程序定义数据库或类似的数据源。 相反,我们会使用一个自定义类,Contact
。 每一个item会有一个name、color和基本联系信息。 以下是Contact
类的大体实现。
public class 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[] { new Contact("John", "#33b5e5", "+01 123456789", "john@example.com", "Venice"), new Contact("Valter", "#ffbb33", "+01 987654321", "valter@example.com", "Bologna"), new Contact("Eadwine", "#ff4444", "+01 123456789", "eadwin@example.com", "Verona"), new Contact("Teddy", "#99cc00", "+01 987654321", "teddy@example.com", "Rome"), new Contact("Ives", "#33b5e5", "+01 11235813", "ives@example.com", "Milan"), new Contact("Alajos", "#ffbb33", "+01 123456789", "alajos@example.com", "Bologna"), new Contact("Gianluca", "#ff4444", "+01 11235813", "me@gian.lu", "Padova"), new Contact("Fane", "#99cc00", "+01 987654321", "fane@example.com", "Venice"),};
第三步:定义主布局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"/></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<DataManager.RecyclerViewHolder> { 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 RVrv.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:text="jonh.doe@example.com" 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 onCreatepublic final static String ID = "ID";public Contact mContact;
// In the onCreate, after the setContentView methodmContact = Contact.getItem(getIntent().getIntExtra(ID, 0));
和之前
RecyclerView
中的onCreateViewHolder
方法一样,这个view使用findViewById
方法找到控件并使用setText
方法填充数据。 如下,我们通过下面的方法来给展示姓名的TextView填充数据。mName = (TextView) findViewById(R.id.DETAILS_name);mName.setText(mContact.get(Contact.Field.NAME));
其他的内容区域也使用同样的方法填充数据。 这样,第二个activity就完成啦。
3. 关联变化
我们终于到了本教程的核心了,使用Lollipop中最新的共享元素过渡动画作为两个activity的过渡动画。
第一步:配置你的项目
首先你要做的是编辑values-v21目录下的style.xml文件。 通过这样的方法,你可以设置过渡动画的内容,并设置两个activity不共享view的进入和消失的方式。
<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资源文件中加入下列元素。
<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
属性并赋予相对应的值。 下面是彩色圆元素的代码示例:<!— In the single item layout: the item we are transitioning *from* —><View 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”/>
<!— In the details activity: the item we are transitioning *to* —><View 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, String>(view.findViewById(R.id.CONTACT_circle), getString(R.string.transition_name_circle)), new Pair<View, String>(view.findViewById(R.id.CONTACT_name), getString(R.string.transition_name_name)), new Pair<View, String>(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之细节
真正创造一个Material Design风格的应用,就像之前截屏展示的那样,你还需要改变应用的主题颜色。 在values-21目录下编辑你的主题基准来实现这个效果。
<style name=“AppTheme” parent=“AppTheme.Base”> <item name=“android:windowTitleSize”>0dp</item> <item name=“android:colorPrimary”>@color/colorPrimary</item> <item name=“android:colorPrimaryDark”>@color/colorPrimaryDark</item> <item name=“android:colorAccent”>@color/colorAccent</item> <item name=“android:textColorPrimary”>#fff</item> <item name=“android:textColor”>#727272</item> <item name=“android:navigationBarColor”>#303F9F</item> </style>
- Android 5.0 Lollipop中新的Activity过渡效果介绍
- Android 5.0 Lollipop中新的Activity过渡效果介绍
- Android 5.0 Lollipop新的摄像头API
- android开发之Android 5.0 Lollipop新特性介绍
- activity跳转的过渡效果
- Android 对Activity设置统一的过渡动画效果
- android Lollipop(5.0)--activity跳转动画
- Android Lollipop 5.0 经典新特性回顾
- Android Lollipop(5.0) 一些新特性
- Android Lollipop新特性
- Android Lollipop新特性
- Android Lollipop 新特性
- 修改 Android 中两个Activity的过渡动画
- android中Activity过渡动画学习
- Android Lollipop 新特性 - Palette
- Android Lollipop 新特性 - Palette
- Android Lollipop 新特性 - Palette
- Android Lollipop 新特性 - Palette
- xxxx
- iOS开发MapKit地图两点之间的距离及线路的绘制
- 学习网站汇总
- Yii2-资源管理(Assets)
- Linux下Nagios的安装与配置
- Android 5.0 Lollipop中新的Activity过渡效果介绍
- shell 脚本执行
- Debian 6.0.4安装配置lnmp(Nginx+MySQL+PHP)第二版
- POJ 3685 Matrix (二分搜索)
- Maven 手动添加 JAR 包到本地仓库
- Android布局文件之GridLayout
- 接口和抽象类有什么区别
- maven默认路径
- to_date 和 to_char 使用区别