android应用框架系列二,图形界面

来源:互联网 发布:cocos2d js demo 编辑:程序博客网 时间:2024/06/06 19:44

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,是一个执行视图注入的类库。

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

findViewById orsetOnClickListener(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,它能够提取图像的主要颜色。


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

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

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

在这个应用中我用到了: VibrantSwatchDarkVibrantSwatch 和LightVibrantSwatch.


注意:你不能总是提取到一张图片的特定颜色,所以记住检测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> 和 drawable vd_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

4 0