一个实用的android框架(二)—— UI
来源:互联网 发布:dedecms中js如何使用 编辑:程序博客网 时间:2024/06/10 09:19
原文出处:http://saulmm.github.io/a-useful-stack-on-android-2-user-interface/
原码github地址:https://github.com/saulmm/Material-Movies
作者:Saúl Molinero
系列文章:
- 一个实用的android框架(一)——架构
- 一个实用的android框架(二)—— UI
- 一个实用的android框架(三)—— 兼容性
这是“一个实用的android架构”系列的第二章节。在第一章节中,我主要介绍了项目的整体架构。在这个章节,我将主要介绍这个项目的UI和设计。
怎么利用材料设计(MaterialDesign)去材料化(materialize)一个安卓应用不在本章的范围之内,在这里有一个David Gonzalez关于这方面做得精彩演讲,你可以用来参考。(译者注:演讲网址可能需要翻墙,题目是What Material Design means to Android,可以百度到对应墙内转载)
通过阅读项目的目录结构可以发现,项目中只有两个Activity:MoviesActivity
和MovieDetailActivity
。其中,MoviesActivity
使用RecyclerView来显示所有的电影,MovieDetailActivity
则用来显示选中电影的全部信息。
项目地址:Github
库
app/build.gradle
// Google librariescompile 'com.android.support:appcompat-v7:21.0.3'compile 'com.android.support:recyclerview-v7:21.0.3'compile 'com.android.support:palette-v7:21.0.0'// Square librariescompile 'com.squareup.picasso:picasso:2.4.0'compile 'com.jakewharton:butterknife:6.0.0'
AppCompat
在Google提供的新的AppCompat中,一个全新的元素Toolbar被引入了。
简单来说,Toolbar
是一个一般化的ActionBar。这个新的控件实际上是一个ViewGroup,所以我们可以让其包含任意的子View。在这个项目中,我让它包含了一个自定义TextView
,用来显示特定的字体。
在布局中使用这个控件的好处就是:当用户往下滑动的时候,Toolbar
会隐藏起来;而当用户向上滑的时候,Toolbar
会再次出现。
activity_main.xml
<android.widget.Toolbar android:id="@+id/activity_main_toolbar" android:layout_height="wrap_content" android:layout_width="match_parent" android:minHeight="?attr/actionBarSize" android:background="@color/theme_primary" android:elevation="10dp" > <com.hackvg.android.views.custom_views.LobsterTextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/app_name" android:textSize="22sp" android:textColor="#FFF" /></android.widget.Toolbar>
MoviesActivity.java
private RecyclerView.OnScrollListener recyclerScrollListener = new RecyclerView.OnScrollListener() { public boolean flag; @Override public void onScrolled(RecyclerView recyclerView, int dx, int dy) { super.onScrolled(recyclerView, dx, dy); // Is scrolling up if (dy > 10) { if (!flag) { showToolbar(); flag = true; } // Is scrolling down } else if (dy < -10) { if (flag) { hideToolbar(); flag = false; } } }};private void showToolbar() { toolbar.startAnimation(AnimationUtils.loadAnimation(this, R.anim.translate_up_off));}private void hideToolbar() { toolbar.startAnimation(AnimationUtils.loadAnimation(this, R.anim.translate_up_on));}
translate_up_off.xml
<?xml version="1.0" encoding="utf-8"?><set xmlns:android="http://schemas.android.com/apk/res/android" android:interpolator="@android:interpolator/fast_out_linear_in" android:fillAfter="true"> <translate android:duration="@integer/anim_trans_duration_millis" android:startOffset="0" android:fromXDelta="0" android:fromYDelta="0" android:toXDelta="0" android:toYDelta="-100%" /></set>
ButterKnife
Jake Wharton开发的ButterKnife是一个用来给View进行注入的库。它避免了重复的书写findViewById
和setOnClickListener
的过程。使用ButterKnife,代码的可读性会大大提高,也更加的简洁。(译者注:请一定要使用ButterKnifeZelezny!请一定要使用ButterKnifeZelezny!请一定要使用ButterKnifeZelezny!)
MovieDetailActivity.java
@InjectViews({ R.id.activity_detail_title, R.id.activity_detail_content, R.id.activity_detail_homepage, R.id.activity_detail_company, R.id.activity_detail_tagline, R.id.activity_detail_confirmation_text,}) List<TextView> movieInfoTextViews;@InjectViews({ R.id.activity_detail_header_tagline, R.id.activity_detail_header_description}) List<TextView> headers;@InjectView(R.id.activity_detail_book_info) View overviewContainer;@InjectView(R.id.activity_detail_fab) ImageView fabButton;@InjectView(R.id.activity_detail_cover) ImageView coverImageView;@InjectView(R.id.activity_detail_confirmation_image) ImageView confirmationView;@InjectView(R.id.activity_detail_confirmation_container) FrameLayout confirmationContainer;
使用这个库一个有用的技巧:;利用@InjectViews
可以将多个View存放到一个List中,所以你可以使用Setter
或者Actions
一次性的给列表中全部的View设定某一属性。
GUIUtils.java
public static final ButterKnife.Setter<TextView, Integer> setter = new ButterKnife.Setter<TextView, Integer>() { @Override public void set(TextView view, Integer value, int index) { view.setTextColor(value); }};
在这个项目中,所有用来显示电影信息的TextView都被设成一种特定的颜色。
MoviesActivity.java
ButterKnife.apply(movieInfoTextViews, GUIUtils.setter, lightSwatch.getTitleTextColor());
通过ButterKnife
,你也可以处理一些View的事件:
@OnClick(R.id.activity_movie_detail_fab)public void onClick() { showConfirmationView();}
Palette
在发布Android L的同时,Google也介绍了一个新的库Palette(调色板)。它可以用来提取一张图片中的主要色调。
这些颜色被保存在了一个叫作Swatch的类中。这个类里面包含了其他的各种属性,如:背景色,一段可读文字放在背景色之上的颜色。
使用Palette
,你还可以获取到下列几种类型的颜色:
MutedSwatch
VibrantSwatch
DarkVibrantSwatch
DarkMutedSwatch
LightMutedSwatch
LightVibrantSwatch
在这个项目中,我使用到了VibrantSwatch
,DarkVibrantSwatch
和LightVibrantSwatch
。
需要注意的是,有的时候可能无法从一张图片中提取到某一颜色。所以使用的时候,必须要检查Palette
的返回结果是否为空。
另外一方面需要考虑的是,抽取颜色的这个过程是很复杂的,因此Palette
提供了一个异步的方式来获取这些颜色。
MoviesActivity.java
Palette.generateAsync(bookCoverBitmap, this);public class MovieDetailActivity extends Activity implements MVPDetailView, Palette.PaletteAsyncListener { ... @Override public void onGenerated(Palette palette) { if (palette != null) { Palette.Swatch vibrantSwatch = palette .getVibrantSwatch(); Palette.Swatch darkVibrantSwatch = palette .getDarkVibrantSwatch(); Palette.Swatch lightSwatch = palette .getLightVibrantSwatch(); if (lightSwatch != null) { // awesome palette code } } }}
在Lollipop的一个典型程序Dialer(联系人)中,我发现了一个有意思的特性。在完整的视图中,所有icon的颜色也都被设置成了联系人图片的颜色。
这个效果可以通过给TextView
设置一个带有ColorFilter的CompoundDrawable
实现。
GUIUtils.java
public static void tintAndSetCompoundDrawable (Context context, @DrawableRes int drawableRes, int color, TextView textview) { Resources res = context.getResources(); int padding = (int) res.getDimension( R.dimen.activity_horizontal_margin); Drawable drawable = res.getDrawable(drawableRes); drawable.setColorFilter(color, PorterDuff.Mode.MULTIPLY); textview.setCompoundDrawablesRelativeWithIntrinsicBounds( drawable, null, null, null); textview.setCompoundDrawablePadding(padding); }
结果:
过渡效果
过渡效果在于,MoviesActivity和MovieDetailActivity有一个共享的元素:选定电影的封面。
在RecyclerView
的Adapter中,指定了需要展示过渡效果控件的transitionName
。
@Overridepublic void onBindViewHolder(MovieViewHolder holder, int position) { TvMovie selectedMovie = movieList.get(position); holder.titleTextView.setText(selectedMovie.getTitle()); holder.coverImageView.setTransitionName("cover" + position); String posterURL = Constants.POSTER_PREFIX + selectedMovie.getPoster_path(); Picasso.with(context) .load(posterURL) .into(holder.coverImageView);}
在跳转到详情页面之前,在intent中已经通过ActivityOptionis
声明好了需要被共享的元素。
@Overridepublic void onClick(View v, int position) { Intent i = new Intent (MoviesActivity.this, MovieDetailActivity.class); String movieID = moviesAdapter.getMovieList() .get(position).getId(); i.putExtra("movie_id", movieID); i.putExtra("movie_position", position); ImageView coverImage = (ImageView) v.findViewById( R.id.item_movie_cover); photoCache.put(0, coverImage .getDrawingCache()); // Setup the transition to the detail activity ActivityOptions options = ActivityOptions .makeSceneTransitionAnimation(this, new Pair<View, String>(v, "cover" + position)); startActivity(i, options.toBundle());}
最后,在详情页面中指定了一个view是被共享的,并从intent中去获取相应的数据。
@Overridepublic void onCreate(Bundle savedInstanceState) { ... int moviePosition = getIntent() .getIntExtra("movie_position", 0); coverImageView.setTransitionName( "cover" + moviePosition); ...
任何包含列表页和详情页的应用都可能需要这种过渡效果。但是,如果列表页和详情页之间有可能出现其他的中间页呢。(译者注:即列表页不一定跳到详情页,详情页也不一定返回到列表页)
当用户点击浮动按钮(Floating Action Button)
去给一个电影标注为“喜欢”的时候,一个短暂的过渡页展示了出来,从而告知用户这个操作成功了。
这样一来,返回到列表页的时候,我就不再需要设置sharedElementReturnTransition
来指定过渡效果了。我现在需要考虑是使用一个动画来提高用户体验。只是把电影标记为“喜欢”而不对展示作任何改变是一个糟糕的设计,所以我需要使它看起来更加独特。
当确认页被展示的时候,返回的过渡效果就被覆盖了。因此,共享元素的动画效果不会被展示。这个时候,返回的效果仅仅是activity向下滑动退出:getWindow().setReturnTransition(new Slide());
VectorDrawable
Lollipop中引入的一个有趣的特性就是VectorDrawable。这个新的drawable将带给我们全新的体验:矢量图,图片缩放等。Lollipop也包含了实用的工具来处理这些新的图片。VectorDrawable
支持使用SVG
来定义的图片。例如,这就是一个SVG
格式的星星:
<?xml version="1.0" encoding="utf-8"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="300px" height="300px" > <g id="star_group"> <path fill="#000000" d="M 200.30535,69.729172 C 205.21044,69.729172 236.50709,141.52218 240.4754,144.40532 C 244.4437,147.28846 322.39411,154.86809 323.90987,159.53312 C 325.42562,164.19814 266.81761,216.14828 265.30186,220.81331 C 263.7861,225.47833 280.66544,301.9558 276.69714,304.83894 C 272.72883,307.72209 205.21044,268.03603 200.30534,268.03603 C 195.40025,268.03603 127.88185,307.72208 123.91355,304.83894 C 119.94524,301.9558 136.82459,225.47832 135.30883,220.8133 C 133.79307,216.14828 75.185066,164.19813 76.700824,159.53311 C 78.216581,154.86809 156.16699,147.28846 160.13529,144.40532 C 164.1036,141.52218 195.40025,69.729172 200.30535,69.729172 z"/> </g></svg>
这里是VectorDrawable
的实现:
vd_star.xml
<?xml version="1.0" encoding="utf-8"?><vector xmlns:android="http://schemas.android.com/apk/res/android" android:viewportWidth="400" android:viewportHeight="400" android:width="300px" android:height="300px"> <group android:name="star_group" android:pivotX="200" android:pivotY="200" android:scaleX="0.0" android:scaleY="0.0"> <path android:name="star" android:fillColor="#FFFFFF" android:pathData="@string/star_data"/> </group></vector>
strings.xml
<string name="star_data"> M 200.30535,69.729172 C 205.21044,69.729172 236.50709,141.52218 240.4754,144.40532 C 244.4437,147.28846 322.39411,154.86809 323.90987,159.53312 C 325.42562,164.19814 266.81761,216.14828 265.30186,220.81331 C 263.7861,225.47833 280.66544,301.9558 276.69714,304.83894 C 272.72883,307.72209 205.21044,268.03603 200.30534,268.03603 C 195.40025,268.03603 127.88185,307.72208 123.91355,304.83894 C 119.94524,301.9558 136.82459,225.47832 135.30883,220.8133 C 133.79307,216.14828 75.185066,164.19813 76.700824,159.53311 C 78.216581,154.86809 156.16699,147.28846 160.13529,144.40532 C 164.1036,141.52218 195.40025,69.729172 200.30535,69.729172 z</string>
和之前Vector不同的是,这其中包括group和path等标签。android:viewport{Width|Height}
指定了画布(Canvas)的宽高, android:width
和android:height
指定了图片的宽高。
<animated-vector>
支持各种动画效果:通过一组<path>
规定的效果,简单位移,旋转以及其他动画效果和形变。
在这个项目中,一个星星被展示的时候带有放大的效果。当这个页面结束的时候,一个旋转的动画效果展示了出来。同时,这个星星的形状逐渐变成了棒棒糖的形状,然后就转变成了原本的形状(星星)。需要注意的是,想要实现形变的效果,那么数据必须放在同一个SVG
文件中。不然的话,程序就会报错。
`avd_star.xm`<?xml version="1.0" encoding="utf-8"?><animated-vector xmlns:android="http://schemas.android.com/apk/res/android" android:drawable="@drawable/vd_star"> <target android:name="star_group" android:animation="@anim/appear_rotate" /> <target android:name="star" android:animation="@anim/star_morph" /></animated-vector>
这个<animated-vector>
是和vd_star.xml
联合在一起的。其中的target就是需要演示的动画效果:
- 第一个target是
star_group
,它被定义在vd_star.xml
中,它会启动一个缩放和旋转的动画。
appear_rotate.xml
<set xmlns:android="http://schemas.android.com/apk/res/android" android:ordering="sequentially" android:interpolator="@android:anim/decelerate_interpolator" > <set android:ordering="together" > <objectAnimator android:duration="300" android:propertyName="scaleX" android:valueFrom="0.0" android:valueTo="1.0"/> <objectAnimator android:duration="300" android:propertyName="scaleY" android:valueFrom="0.0" android:valueTo="1.0"/> </set> <objectAnimator android:propertyName="rotation" android:duration="500" android:valueFrom="0" android:valueTo="360" android:valueType="floatType"/></set>
- 第二个target是一个形变的动画。它是通过另外一个
<objectAnimator>
来讲一个SVG变换成另一个SVG
在此,我想强调的是:形变能够成功的条件是,SVG
文件中的元素必须是一样的,仅仅是在数值上会有差别。
在这个<Set>
中,就定义了将星星的形状转变成棒棒糖,然后再转变回星星。
star_morph.xml
<set xmlns:android="http://schemas.android.com/apk/res/android" android:ordering="sequentially" android:fillAfter="true"> <objectAnimator android:duration="500" android:propertyName="pathData" android:valueFrom="@string/star_data" android:valueTo="@string/star_lollipop" android:valueType="pathType" android:interpolator="@android:anim/accelerate_interpolator"/> <objectAnimator android:duration="500" android:propertyName="pathData" android:valueFrom="@string/star_lollipop" android:valueTo="@string/star_data" android:valueType="pathType" android:interpolator="@android:anim/accelerate_interpolator"/></set>
MovieDetailActivity.java
@Overridepublic void animateConfirmationView() { Drawable drawable = confirmationView.getDrawable(); if (drawable instanceof Animatable) ((Animatable) drawable).start();}
Sticky headers
Google联系人(Dialer)中,另一个引起我注意的是:在联系人页面滚动的时候,标题栏的高度逐渐变小,直到小到一定程度就不再变化。
为了实现这个效果,我找了Roman Nurik发布的一段代码(译者注:需要翻墙,文件名为StickyFragment.java,可自行寻找墙内链接)。在这个代码中,通过设置ScrollView
的listener以及View.setTranslationY(float translationY)
实现了这个效果。
MovieDetailActivity.xml
@Overridepublic void onScrollChanged(ScrollView scrollView, int x, int y, int oldx, int oldy) { if (y > coverImageView.getHeight()) { movieInfoTextViews.get(TITLE).setTranslationY( y - coverImageView.getHeight()); if (!isTranslucent) { GUIUtils.setTheStatusbarNotTranslucent(this); getWindow().setStatusBarColor(mBrightSwatch.getRgb()); isTranslucent = true; } } if (y < coverImageView.getHeight() && isTranslucent) { GUIUtils.makeTheStatusbarTranslucent(this); isTranslucent = false; }}
*这个部分还有一些小bug。例如,当你快速滑动的时候,封面图片和标题栏之间会产生一个间隔。欢迎大家上传(Pull)改进代码到Github。*
参考
First look at AnimatedVectorDrawable - Chiu-Ki Chan
VectorDrawables series - Styling android
appcompat v21: material design for pre-Lollipop devices! - Chris Banes
译者总结
这个章节主要介绍了Material Design以及Android L的一些新特性。就目前来说,是开发人员中比较流行的话题。这个项目中涉及到的页面效果都还比较酷炫,很有参考价值。
另外,作者的另一个思路也值得借鉴,那就是参考Google官方应用。在这个章节中,作者多次提到了他的灵感是来源于联系人应用。这些Google官方应用作为Android新特性的首个使用者,当中一定会包含最新的技术,非常适合用来学习。
- 一个实用的android框架(二)—— UI
- ym——Android酷炫实用的开源框架(UI框架)(终)
- ym——Android酷炫实用的开源框架(UI框架)(终)
- ym——Android酷炫实用的开源框架(UI框架)(终)
- ym——Android酷炫实用的开源框架(UI框架)
- ym——Android酷炫实用的开源框架(UI框架)
- ym——Android酷炫实用的开源框架(UI框架)
- 一个实用的android框架(一)——架构
- 一个实用的android框架(三)—— 兼容性
- Android实用的开源框架(UI框架)
- 一个实用的android框架——架构
- Android酷炫实用的开源框架(UI)
- Android实用的UI开源框架
- Android酷炫实用的开源框架(UI框架)
- Android开发中文站 » Android酷炫实用的开源框架(UI框架)
- Android实用框架(二)
- Android酷炫实用的开源框架(UI框架)(转载,只为保存)
- Android酷炫实用的开源框架(UI框架)
- cocos2d-x 3.4版本游戏打包AKP (重点记录如何解决打包过程中遇到的各种问题)
- Tomcat在Linux上的安装与配置
- 疯狂ios讲义之选择器(UIPickerView)
- 使用注册表方式:建立一个注册表DWORD为1键值:
- 《深入理解计算机系统》读书笔记3---关于程序运行的思考
- 一个实用的android框架(二)—— UI
- RabbitMQ Consumer获取消息的两种方式(poll,subscribe)解析
- 五种开源协议的比较(BSD,Apache,GPL,LGPL,MIT) – 整理
- Knockout应用开发指南 第六章:加载或保存JSON数据
- jquery中prop&attr之disabled问题
- 重写hashCode()方法
- springmvc配置
- Android Gson解析JSON数据
- Ehcache配置详解及CacheManager使用