A useful stack on android #2, user interface

我不愿意谈论如何使用Material Design实现APP,你可以在这里找到David Gonzalez的非常不错的描述。

看一下结构设计,这里仅仅两个Activity:MoviesActivity拥有一个RecyclerView包涵了所有的电影,MovieDetailActivity用来显示所选电影的详细内容。

这个项目在GitHub上可看见。


Libraries


app/build.gradle

    // Google libraries    compile '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 libraries    compile 'com.squareup.picasso:picasso:2.4.0'    compile 'com.jakewharton:butterknife:6.0.0'


AppCompat

一定要说的是新的Google AppCompat类库,在这个类库里包涵了一个名字为Toolbar的元素。

Toolbar是为了对旧的Action Bar泛化。

这个新的widget是一个ViewGroup,所以我们可以在其放入views,在我的例子中包涵了一个自定义的拥有特殊字体的TextView

这个组件的优势是,当用户向下滑动时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

ButterKnife的作者是Jake Wharton,是一个执行视图注入的类库。

这样可以避免重复写下面的代码:

findViewByIdorsetOnClickListener(new OnClick...).

使用ButterKnife,代码可读性更强,另外代码变少了。


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,能允许注解多个views到一个list,所以可以用接口例如Setters或者Actions对列表中的所有views执行一次操作。


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);    }};

在我的例子里所有的TextViews显示的电影信息都被设置了一个确定的字体颜色。

MoviesActivity.java

ButterKnife.apply(movieInfoTextViews, GUIUtils.setter,     lightSwatch.getTitleTextColor());

使用ButterKnife你同样可以处理一些view事件:

@OnClick(R.id.activity_movie_detail_fab)public void onClick() {    showConfirmationView();}


Palette

在Google的Android L发布时候介绍了一个新的类库叫做Palette,它能够提取图像的主要颜色。



这些颜色集中在Swatch容器中,它包涵了背景色和文字的背景色。

使用Palette你能获取下面的Swatch设置颜色

  • MutedSwatch
  • VibrantSwatch
  • DarkVibrantSwatch
  • DarkMutedSwatch
  • LightMutedSwatch
  • LightVibrantSwatch

在这个应用中我用到了:VibrantSwatch,DarkVibrantSwatchLightVibrantSwatch.


注意:你不能总是提取到一张图片的特定颜色,所以记住检测Palette有没有返回null集合。

另一面,需要考虑提取颜色是一个复杂的任务,所以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这个APP一些有趣的想法,在联系人详细界面,图标被渲染为联系人的背景色:

这个效果能通过ColorFilter动态获取,然后给TextView设置颜色。


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);    }

效果:


Transitions

MoviesActivityMovieDetailActivity两个activity之间的transition,利用所选的电影图片作为shared element

RecyclerView的adapter 指定transitionName将用来执行transition。

    @Override    public 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);    }

在跳转到详细activity之前,需要指定一个intent元素,用来共享ActivityOptions.

    @Override    public 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());    }

最后,在详细activity里,从进来的intent获取meta data数据,来指定显示那个view(电影图片等)。

    @Override    public void onCreate(Bundle savedInstanceState) {        ...        int moviePosition = getIntent()            .getIntExtra("movie_position", 0);        coverImageView.setTransitionName(            "cover" + moviePosition);        ...

在任何包涵list和详细view的应用中,这些都是必须的,但是如果在详细视图与返回列表之间有一个中间状态呢?

当用户按下Floating Action Button收藏该电影,一个简短的视图展示用来通知用户保存成功。

在这之后我不想利用sharedElementReturnTransition实现transition,为了返回主activity,我更喜欢显示一个动画来改变用户体验。

不能让被标记为喜欢(收藏)的电影在返回的时候不做任何事情,所以设计它表现的不同。


当确认视图被显示,返回的transition需要重写,电影照片切换动画效果不会显示,给activity设置新返回效果:getWindow().setReturnTransition(new Slide());

VectorDrawable

Lollipop 介绍了一个非常有趣的特性VectorDrawables.

使用新的drawables,打开一个矢量绘图,图片缩放等等的新世界。

Lollipop同样包涵了一些强力的工具去处理这些新的图形al。

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>

这里有一点不同,这里是groups,paths,等,android:viewport{Width|Height}指定了canvas大小, 而android:width&android:height指定了图片大小。

animated-vector>允许<paths>动画组,平移、旋转,和其它动画,变形。

在例子中,一个星星执行缩放动画,当其停止,一个旋转动画展示,同时星星的形状变为一个棒棒糖的形状,当再次改变又会变为原来星星的形状。

值得注意的是,显示一个变形动画,数据需要包涵相同的SVG命令,否则,会发生异常。

avd_star.xml

<?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>drawablevd_star.xml是有关联的,targets是被动画显示的元素:

  • 首先在动画group:start_group定义在 vector: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>
  • 其次一个变形动画被使用,这是另一个<objectAnimator>SVG数据变为另一个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>

activity_detail.xml

    <ImageView        android:id="@+id/activity_movie_detail_confirmation_image"        android:layout_width="300dp"        android:layout_height="300dp"        android:layout_gravity="center"        android:src="@drawable/avd_star"        />

MovieDetailActivity.java

    @Override    public void animateConfirmationView() {        Drawable drawable = confirmationView.getDrawable();        if (drawable instanceof Animatable)            ((Animatable) drawable).start();    }

效果:



Sticky headers

另一方面,Google Dialer吸引我的地方是当你在一个联系人信息里scroll(下拉滑动),这个图片会变小直到滑动到一个点,在这个点图像会被恢复。



为了复制这个效果,我找到了Roman Nurik的一个代码提交,通过设置属性:在ScrollView监听器中使用View.setTranslationY(float translationY)方法来实现这个目标。

为获得这个效果,translationY的值与ScrollView共同作用允许设置title的位置。

MovieDetailActivity.java

    @Override    public 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,例如:如果你非常快的滑动会在图片和标题间出现空白,欢迎回复!

Resources:

  • First look at AnimatedVectorDrawable- Chiu-Ki Chan

  • VectorDrawables series- Styling android

  • appcompat v21: material design for pre-Lollipop devices!- Chris Banes

更多相关文章

  1. Android动画TweenAnimation的使用方法
  2. Android(安卓)用Animation-list实现逐帧动画
  3. Android(安卓)在代码中动态设置字体颜色需要注意的问题
  4. android selector 需要顺序的啊
  5. android 实现ImageView按压效果和解决背景图片拉申问题
  6. Button 有按下效果
  7. android TextView怎么设置个别字体颜色并换行?
  8. Android开发:Listview 多屏显示 item多选 操作错误 的解决
  9. Android动态设置主题样式

随机推荐

  1. android中的jar包反编译修改心得
  2. 一个效果很华丽的仿桌面APP,却胜似Launche
  3. Android(安卓)Asynchronous HTTPClient的
  4. Android(安卓)StrictMode详解
  5. Android(安卓)游戏开发入门
  6. Android(安卓)ActivityManagerService(AMS
  7. Android(安卓)插件框架 xCombine
  8. android中Toast重复显示bug解决方法
  9. Android(安卓)BLE 蓝牙编程(一)
  10. Android(安卓)Java Framework显示Toast(无