Skip to content

Latest commit

 

History

History
872 lines (676 loc) · 39.9 KB

03.md

File metadata and controls

872 lines (676 loc) · 39.9 KB

三、材质设计

这一章将教你什么是材质设计,为什么它是如此大的改进,以及为什么你应该在你的应用中使用它。

在本章中,您将了解:

  • 回收器视图和卡片视图
  • 涟漪和高地
  • 伟大的转变

简介

随着材质设计的引入,安卓应用的外观将最终成熟。他们可以很好地与 iOS 设计竞争。安卓材质应用有一个平面设计,但是有一些有趣的不同,比如高度。以下图为例:

Introduction

把它想象成多张幻灯片。它是基于,嗯,材质。每张纸都有一个特定的高度。所以,环境实际上是一个有光影等效果的 3D 世界。任何运动都应该具有真实世界的行为,就像被移动的元素是真实的物理对象一样。动画是材质设计的另一个重要元素。

先来看看https://www . Google . co . in/design/spec/material-design/introduction . html看看什么是材质设计。当然,很多东西对设计师来说都是特别有趣的,你可能只对所有这些美丽的东西的实现感兴趣;然而,这个链接为你提供了更多关于材质设计的内容。

很长一段时间以来,大多数安卓应用都遭遇了糟糕的设计,或者在早期根本没有设计。或者,它们看起来非常类似于为 iPhone 制造的产品,包括 iOS 的所有典型元素。

请看下一个应用截图:

Introduction

使用材质设计,这是现在大多数谷歌应用的样子。

现在很多谷歌的安卓应用都使用材质设计。它们都遵循相同的交互和设计准则。界面是简约的,正如人们对谷歌的期望。此外,界面变得更加统一,更容易理解和使用。

早些时候,回应是你必须照顾好自己的事情。材质设计自带涟漪等效果,做着同样的事情,就是提供用户输入的反馈,但是实现起来要容易得多,优雅得多。

至于组件,材质设计决定了特定情况下按钮的外观。想想用于操作的浮动按钮,或者对话框中使用的平面按钮。它还将列表视图替换为回收视图,这为显示列表提供了更大的灵活性。卡片视图是常见的元素,你可以看到它们经常在谷歌应用中使用。各种动画提供更自然的过渡,例如用于导航或滚动目的的动画。

材质设计不仅仅是为了最新最棒的。虽然它配备了安卓棒棒糖(5.0)和更高版本,但大多数材质设计功能都可以通过v7 support库在安卓 2.1 及更高版本中使用,这允许我们应用材质设计,并且仍然支持几乎所有安卓设备。

总的来说,材质设计为美化你的应用提供了很多。人们也想变漂亮。健康应用正因此而蓬勃发展。找出什么是健康的饮食,建议多喝水,建议跑步或健身锻炼是这类应用的共同目标。为了展示材质设计的美丽,我们将创建一个应用,帮助人们变得更健康。

那么,一款 drink water and take a selfie app 怎么样?人们需要更经常地喝水,如果他们这样做了,他们可以看到它的效果。漂亮的人值得一个漂亮的应用。这有道理,不是吗?

雷克勒视图和卡片视图

回收器视图取代了好的旧列表视图。它在列表元素的显示方式上提供了更多的灵活性,例如,作为网格和水平或垂直项目。我们现在可以选择在任何合适的地方展示卡片,而不是行。

在我们的应用中,每张卡片都应该显示一些关于条目的文本和我们拍摄的图片的缩略图。这就是这个食谱的全部内容。

做好准备

要完成这个食谱,你需要运行安卓系统。还要确保您安装了最新的软件开发工具包。(打开 SDK 管理器可以查看是否有最新的 SDK)。为此,打开工具菜单,选择安卓,然后选择 SDK 管理器选项。

怎么做...

让我们通过以下步骤来研究如何使用回收视图和卡片:

  1. 启动 Android Studio,开始新的项目。命名您的应用WaterApp,并在公司域字段中输入packtpub.com。然后,点击下一步按钮。

  2. 在下一个对话框中选择空白活动,点击下一步按钮。

  3. 在下面的对话框中,点击完成按钮。

  4. 打开app文件夹中的build.gradle文件,将回收视图的依赖项添加到dependencies部分,如下代码所示:

    dependencies {
        compile fileTree(dir: 'libs', include: ['*.jar'])
        compile 'com.android.support:appcompat-v7:22.1.1'
        compile 'com.android.support:recyclerview-v7:+'
    }
  5. Change minSdkVersion to at least 21 in the build.gradle file.

    这并不一定是最低要求的版本,但是由于向后兼容的支持库不包含所有的材质设计特性,为了安全起见,我选择在这里选择 API 级别 21。

  6. 通过点击在我们编辑build.gradle文件后出现的黄色条上的立即同步标签来同步您的项目,或者如果没有,点击工具栏上的将项目与渐变文件同步按钮。

  7. 打开 activity_main.xml布局文件,去掉Hello World TextView,在布局中添加一个RecyclerView标签,如下图:

    <android.support.v7.widget.RecyclerView
        android:id="@+id/main_recycler_view"
        android:scrollbars="vertical"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>
  8. 在你的 MainActivity类中,在setContentView后面的onCreate方法中添加以下内容:

    RecyclerView recyclerView = (RecyclerView) 
     findViewById(R.id.main_recycler_view);
  9. RecyclerView类还不是已知类。使用 Alt + 回车快捷方式添加正确的导入语句或自己添加以下行:

    import android.support.v7.widget.RecyclerView;
  10. 我们将在这个配方中使用线性布局管理器。在我们在步骤 9 中添加的行之后添加以下行:

```java
LinearLayoutManager layoutManager = new LinearLayoutManager(this);
recyclerView.setLayoutManager(layoutManager);
```
  1. Create a new package and name it models, and within this package, create a new Drink class as follows:
```java
package com.packt.waterapp.models;import java.util.Date;
public class Drink {
    public Date dateAndTime;
    public String comments;
    public String imageUri;
}
```

这里,`Date`类指的是`java.util.Date`包(这是指定的,因为还有一个与 SQL 相关的类具有相同的类名)。
  1. 让我们创建一个布局来显示项目。右键单击项目树中的layout包,创建一个新的资源文件。为此,从菜单中选择新建新建布局资源文件。命名为adapter_main.xml,点击确定按钮。
  2. 将布局切换到文本方式,将LinearLayout的方向从vertical更改为horizontal,为其添加一些填充并添加图像视图,如下图所示。我们还将添加一个默认图像,以便我们有东西可以看:
```java
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 
xmlns:android="http://schemas.android.com/apk/res/android"android:orientation="horizontal" android:layout_width="match_parent"
android:padding="8dp" android:layout_height="match_parent">
<ImageView android:id="@+id/main_image_view"
android:src="@android:drawable/ic_menu_camera"
android:scaleType="center"
android:layout_width="90dp"
android:layout_height="90dp" />
</LinearLayout>
```
  1. 在图像的旁边,我们希望使用包装在另一个LinearLayout小部件中的两个TextView小部件来显示日期和时间以及评论。在ImageView标签后面加上这些:
```java
<LinearLayoutandroid:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">
    <TextView
        android:id="@+id/main_date_time_textview"
		android:layout_marginTop="8dp"
		android:textSize="12sp"
		android:textColor="@color/material_blue_grey_800"
		android:layout_width="match_parent"
		android:layout_height="wrap_content" />
    <TextView
        android:id="@+id/main_comment_textview"
		android:layout_marginTop="16dp"
		android:maxLines="3"
		android:textSize="16sp"
		android:textColor="@color/material_deep_teal_500"
		android:layout_width="match_parent"
		android:layout_height="wrap_content" />
	</LinearLayout>
```
  1. 创建另一个包并命名为adapters。在这个包中,创建将使用ViewHolder类的MainAdapter类,帮助我们准确地显示我们希望它出现的数据。我们还包括所有需要被覆盖的方法,例如onBindViewHolder 方法和getItemCount方法:
```java
public class MainAdapter extends RecyclerView.Adapter<MainAdapter.ViewHolder> {
    private ArrayList<Drink> mDrinks;private Context mContext;public static class ViewHolder extends        RecyclerView.ViewHolder {
        public TextView mCommentTextView;
        public TextView mDateTimeTextView;
        public ImageView mImageView;
        public ViewHolder(View v) {
            super(v);
        }
    }
    public MainAdapter(Context context, 
      ArrayList<Drink> drinks) {
        mDrinks = drinks;
        mContext = context;
    }
    @Override
    public MainAdapter.ViewHolder  
     onCreateViewHolder(ViewGroup parent,  int viewType) {
        View v = LayoutInflater.from(
         parent.getContext()).inflate(
          R.layout.adapter_main, parent, false);
        ViewHolder viewHolder = new ViewHolder(v);
        viewHolder.mDateTimeTextView =  
         (TextView)v.findViewById(
          R.id.main_date_time_textview);
        viewHolder.mCommentTextView =  
         (TextView)v.findViewById(
          R.id.main_comment_textview);
        viewHolder.mImageView = 
         (ImageView)v.findViewById(
          R.id.main_image_view);
        return viewHolder;
    }
    @Override
    public int getItemCount() {
        return mDrinks.size();
    }
}
```
  1. 我们有更多的事情要做。添加onBindViewHolder方法并添加实现,将数据实际绑定到正确的小部件:
```java
@Override
public void onBindViewHolder(ViewHolder holder,int position) {
    Drink currentDrink = mDrinks.get(position);
    holder.mCommentTextView.setText(
     currentDrink.comments);
    holder.mDateTimeTextView.setText(
     currentDrink.dateAndTime.toString());
    if (currentDrink.imageUri != null){
        holder.mImageView.setImageURI(
         Uri.parse(currentDrink.imageUri));
    }
}
```
  1. MainActivity文件中,我们需要有一个适配器的实例和一些要显示的数据。添加一个私有适配器和一个包含Drink项的私有数组列表:
```java
private MainAdapter mAdapter;private ArrayList<Drink> mDrinks;
```
  1. onCreate方法的末尾,告诉recyclerView使用哪个适配器,并告诉适配器使用哪个数据集:
```java
mAdapter = new MainAdapter(this, mDrinks);
recyclerView.setAdapter(mAdapter);

```
  1. In the MainActivity file, we want to add some dummy data so that we have some idea about what things are going to look like. Add the following to the onCreate method just before the part where we create the MainAdapter class:
```java
mDrinks = new ArrayList<Drink>();
Drink firstDrink = new Drink();
firstDrink.comments = "I like water with bubbles most of the time...";
firstDrink.dateAndTime = new Date();
mDrinks.add(firstDrink);Drink secondDrink = new Drink();
secondDrink.comments = "I also like water without bubbles. It depends on my mood I guess ;-)";
secondDrink.dateAndTime = new Date();
mDrinks.add(secondDrink);
```

使用 *Alt* + *进入*快捷方式导入所需的包裹。

运行您的应用,验证到目前为止一切顺利。您的应用将显示两个条目,其中包含我们在上一步中创建的示例数据。

使用卡片视图

这个应用看起来不错但是我还不想说它漂亮。让我们看看是否能稍微改进一下。以下步骤将帮助我们使用卡片视图创建应用:

  1. Open the build.gradle file in the app folder and add a CardView dependency, just after the one for the recycler view:

    compile 'com.android.support:cardview-v7:+'

    再次同步您的项目。

    顺便说一句,如果这个应用是真的,那么通过指定一个确切的版本来避免不愉快的惊喜,而不是使用版本号中的+符号来表示你的应用可能具有的任何依赖性。目前,这是这个特定依赖项的21.0.0,但是当你读到这个的时候,一个新的版本可能会出现。

  2. 如果出现错误,表明 Gradle 未能解决卡视图依赖性,则点击安装存储库并同步项目链接,接受许可,点击下一步按钮。请稍候,直到下载完成,安装完成。完成后,点击完成按钮。再次同步您的项目。

  3. 创建新布局并命名为adapter_main_card_view.xml。在LinearLayout标签中添加一些填充,在linear layout标签中添加一个CardView:T4

  4. 从之前的布局adapter_main.xml文件中,复制ImageView和两个TextView小部件(但不复制包含两个TextView小部件的LinearLayout,并将它们粘贴到您添加到adapter_main_card_view.xml文件中的CardView中。

  5. 因为CardView的行为就像是FrameLayout一样,所以需要设置文本标签的边距。在两个文本视图中添加左边距。同时修改TextView评论的上边距:

    <TextView
        android:id="@+id/main_date_time_textview"
    	android:layout_marginTop="8dp"
    	android:layout_marginLeft="100dp"
    	android:textSize="12sp"
    	android:textColor="@color/material_blue_grey_800"
    	android:layout_width="match_parent"
    	android:layout_height="wrap_content" />
    <TextView
        android:id="@+id/main_comment_textview"
    	android:layout_marginTop="32dp"
    	android:layout_marginLeft="100dp"
    	android:maxLines="3"
    	android:textSize="16sp"
    	android:textColor="@color/material_deep_teal_500"
    	android:layout_width="match_parent"
    	android:layout_height="wrap_content" />
  6. Now you tell the MainAdapter class to use this layout by changing the layout ID in the onCreateViewHolder method:

    View v = LayoutInflater.from(parent.getContext()). inflate(R.layout.adapter_main_card_view, parent, false);

    再次运行该应用,我们将看到这次的样子:

    Using card views

  7. 在下一个食谱中,我们将添加一个升高的浮动按钮,我们将创建一个新的活动,允许我们应用的用户添加饮料、评论和自拍。

还有更多...

有很多关于材质设计的文件。浏览各种网站上的各种例子,如https://www.materialup.comhttp://materialdesignblog.comhttp://material-design.tumblr.com

或者,下载一些在 Play Store 中可用的素材设计的应用,如收件箱、谷歌+、Wunderlist、Evernote、LocalCast 和 SoundCast 应用。

波纹和高度

虽然高程和波纹并不完全是为了让人更美丽而考虑的,但是将这些和其他材质设计原则应用到我们的应用中,肯定会有助于美化它。

在前面的食谱中,我们创建了一个列表来显示所有记录的饮料。在这个食谱中,我们将添加一个升高的按钮来添加新的条目。此外,我们将创建一个新的活动。

对于每个条目,用户可以描述一些关于他喝了什么的想法。当然,用户每次都必须能够自拍,这样以后他就可以检查喝完所有的水或绿茶(或者啤酒,如果我们应用的用户确实有一个稍微不同于这个应用的目标)是否确实对他的健康和他(或她)的外表有积极的影响。

做好准备

对于这个食谱,如果你能完成之前的食谱就太好了,因为这将建立在我们之前的成就上。

怎么做...

让我们添加一个浮动按钮,并创建一个新活动来编辑新条目:

  1. res/drawable文件夹中添加一个新的可绘制资源文件,命名为button_round_teal_bg.xml,点击确定按钮。

  2. Using XML, we will create a round oval shape for the button. Remove the selector tags first (if any). Wrap it up in a ripple tag. A ripple provides visible feedback in case the button is being pressed; I have chosen a material design variant of teal as the color but you can of course pick any color that you like. For inspiration, you could check out http://www.google.com/design/spec/style/color.html. The content for the file looks like as shown in the following example:

    <ripple xmlns:android="http://schemas.android.com/apk/res/android"android:color="#009789">
        <item>
            <shape android:shape="oval">
                <solid android:color="?android:colorAccent"/>
            </shape>
        </item>
    </ripple>

    类型

    如果遇到任何错误,请检查build.gradle文件中的minSdkVersion。有关更多信息,请参考第一个配方的第 5 步。

  3. Add a button to the activity_main.xml layout file just after the recycler view:

    <ImageButton
        android:id="@+id/main_button_add"
    	android:elevation="1dp"
    	android:layout_width="48dp"
        android:layout_height="48dp"
        android:layout_alignParentBottom="true"
        android:layout_alignParentRight="true"
        android:layout_margin="16dp"
        android:tint="@android:color/white"
        android:background="@drawable/button_round_teal_bg"
        android:src="@android:drawable/ic_input_add"/>

    颜色应该在单独的颜色资源文件中定义。此外,高程和边距也应放在尺寸资源文件中。既然这超出了这个食谱的范围,我建议你以后再做。

  4. 接下来,我们想让有一些阴影,如果按钮被按下或释放,我们还想改变高度。在res文件夹中创建新目录,并命名为anim。在这个文件夹中,创建一个新的动画资源文件。命名文件button_elevation.xml并点击确定按钮:

    <selector xmlns:android="http://schemas.android.com/apk/res/android">
        <item android:state_pressed="true">
            <objectAnimator
                android:propertyName="translationZ"android:duration="@android:integer/config_shortAnimTime"
                android:valueFrom="1dp"
                android:valueTo="4dp"android:valueType="floatType"/></item>
        <item>
            <objectAnimator
                android:propertyName="translationZ"android:duration="@android:integer/config_shortAnimTime"
                android:valueFrom="4dp"
                android:valueTo="1dp"
                android:valueType="floatType"/>
        </item>
    </selector>
  5. 通知图像按钮关于这个新的资源文件。在您的activity_main.xml布局中,在您的图像按钮上添加以下行:

    android:stateListAnimator="@anim/button_elevation"
  6. 在 MainActivity 类的 onCreate 方法的末尾,在我们刚刚创建的按钮上添加一个OnClickListener并调用showEntry方法,我们将在一两分钟后创建:

    findViewById(R.id.main_button_add).setOnClickListener(new  
     View.OnClickListener() {
        @Override
        public void onClick(View v) {
            showEntry();}
    });
  7. 新建布局资源文件,命名为activity_entry.xml,使用FrameLayout作为根元素。然后点击确定按钮。

  8. 添加一个EditText小部件用于评论,一个按钮用于拍照,另一个按钮用于保存条目。然后将这些元素包装在一个CardView小部件中。在CardView小部件后添加一个ImageView小部件,如:

    <?xml version="1.0" encoding="utf-8"?> <FrameLayout xmlns:android= 
     "http://schemas.android.com/apk/res/android"
        android:padding="8dp" android:layout_width="match_parent"   
        android:layout_height="match_parent">
        <android.support.v7.widget.CardView xmlns:card_view="http://schemas.android.com/apk/res-auto"
            android:id="@+id/card_view"
            android:layout_width="match_parent"
            android:layout_height="200dp"
            card_view:cardCornerRadius="4dp">
        <EditText                                                                                                  android:id="@+id/entry_edit_text_comment"android:lines="6"android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginRight="60dp"/>
        <ImageButton 
    	    android:id="@+id/entry_image_button_camera"
            android:src="@android:drawable/ic_menu_camera"
            android:layout_gravity="right"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />
        <Button 
    	    android:id="@+id/entry_button_add"
            android:layout_gravity="bottom"
            android:text="Add entry"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />
        </android.support.v7.widget.CardView>
        <ImageView
            android:id="@+id/entry_image_view_preview"
            android:scaleType="fitCenter"
            android:layout_marginTop="210dp"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />
    </FrameLayout>
  9. 新建一个类,命名为EntryActivity,点击确定按钮。

  10. 使您的类从Activity下降,覆盖onCreate方法,并将内容视图设置为您刚刚创建的布局:

```java
public class EntryActivity extends Activity {
    @Override
	protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_entry);
    }
}
```
  1. 不要忘记在AndroidManifest.xml文件中添加你的新活动:
```java
<activity android:name=".EntryActivity"/>
```
  1. MainActivity类中,添加显示新活动所需的showEntry方法和实现。我们将在这里使用startActivityForResult方法,因为这将允许EntryActivity稍后返回数据:
```java
private int REQUEST_NEW_ENTRY = 1;
private void showEntry(){
    Intent intent = new Intent(this, EntryActivity.class);
    startActivityForResult(intent, REQUEST_NEW_ENTRY);
}
```

现在,如果你运行应用并按下按钮,你会注意到视觉反馈。要正确查看效果,您可能需要使用手写笔或放大按钮的大小。如果松开按钮,您将看到条目布局。在布局中,如果你按住添加条目按钮(或相机按钮),你会注意到涟漪效应。我们不需要为此做任何特别的事情。随着棒棒糖的引入(以及前面的描述),这是按钮的默认行为。然而,这些按钮看起来确实有点无聊,正如您在浮动按钮中看到的,有很多定制选项可用。让我们遵循以下步骤:

  1. EntryActivity类中,为相机按钮设置OnClickListener,并为add按钮做同样的事情:

    findViewById(R.id.entry_image_button_camera).setOnClickListener( 
     new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            takePicture();
        }
    });
    findViewById(R.id.entry_button_add).setOnClickListener(new 
     View.OnClickListener() {
        @Override
        public void onClick(View v) {
        }
    }
    );
  2. 为我们将要拍摄的照片添加一个包含 URI 的私人成员:

    private Uri mUri;
  3. 创建一个takePicture 方法,并为其添加实现。我们将通过使用时间戳预先创建一个具有唯一图像名称的文件,并且我们将告诉图像捕获意图对该文件使用Uri:

    private int REQUEST_IMAGE_CAPTURE = 1;
    private void takePicture(){
        File  filePhoto = new  
        File(Environment.getExternalStorageDirectory(),String.valueOf(new Date().getTime())+"selfie.jpg");
        mUri = Uri.fromFile(filePhoto);
        Intent intent = new   
         Intent("android.media.action.IMAGE_CAPTURE");
        intent.putExtra(MediaStore.EXTRA_OUTPUT, mUri);
        startActivityForResult(intent, REQUEST_IMAGE_CAPTURE);
    }
  4. 覆盖onActivityResult方法,该方法将在拍照后触发。如果一切顺利,我们需要创建一个我们刚刚通过拍照创建的文件的位图,并显示它的预览:

    @Override
        protected void onActivityResult(int requestCode, int resultCode,Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (requestCode == REQUEST_IMAGE_CAPTURE &&
            resultCode == RESULT_OK){
            Bitmap bitmap = getBitmapFromUri();
            ImageView preview = (ImageView)  
              findViewById(R.id.entry_image_view_preview);
            preview.setImageBitmap(bitmap);}
    }
  5. 接下来,执行getBitmapFromUri方法:

    public Bitmap getBitmapFromUri() {
        getContentResolver().notifyChange(mUri, null);
        ContentResolver resolver = getContentResolver();
        Bitmap bitmap;
        try {
            bitmap = android.provider.MediaStore.Images.Media.getBitmap(  
             resolver, mUri);
            return bitmap;
        } 
        catch (Exception e) {
            Toast.makeText(this, e.getMessage(),  
             Toast.LENGTH_SHORT).show();
           return null;}
    }
  6. 将适当的权限和功能添加到AndroidManifest.xml文件中:

    <uses-permission android:name="android.permission.CAMERA" />
    <uses-permission  
      android:name="android.permission.READ_EXTERNAL_STORAGE"/>
    <uses-feature android:name="android.hardware.camera" />
  7. 现在让我们执行submitEntry方法。我们将返回图片的评论和uri,并结束活动:

    private void submitEntry(){
        EditText editComment =  (EditText)
          findViewById(R.id.entry_edit_text_comment);
        Intent intent = new Intent();
        intent.putExtra("comments", editComment.getText().toString());
        if (mUri != null) {
            intent.putExtra("uri", "file://" +   
              mUri.getPath().toString());}
        setResult(Activity.RESULT_OK, intent);
        finish();
    }
  8. 添加add按钮的onClick事件的实现。只需调用submitEntry方法:

    findViewById(R.id.entry_button_add).setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            submitEntry();
        }
    });
  9. MainActivity类中,我们将通过重写onActivityResult方法来处理返回的结果。将创建一种新饮料并将其添加到饮料列表中。最后,我们将通过添加以下代码片段来通知适配器有一个要显示的更新:

    @Override
        protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (requestCode == REQUEST_NEW_ENTRY && 
            resultCode == RESULT_OK) {
            Bundle bundle = data.getExtras();
            Drink newDrink = new Drink();
            newDrink.comments = bundle.getString("comments");
            newDrink.imageUri = bundle.getString("uri");
            newDrink.dateAndTime = new Date();
            mDrinks.add(newDrink);
            mAdapter.notifyDataSetChanged();
    }
  10. MainAdapter 类中,我们需要做一些工作来显示每个图像的缩略图。将此添加到onBindViewHolder方法的末尾:

```java
if (currentDrink.imageUri != null){
    Bitmap bitmap =    
     getBitmapFromUri(Uri.parse(currentDrink.imageUri));
    holder.mImageView.setImageBitmap(bitmap);
}
```
  1. 如果该项目已知一个Uri,我们需要为其显示一个缩略图。我们将在MainAdapter实施getBitmapFromUri略有不同。方法是这样的:
```java
public Bitmap getBitmapFromUri(Uri uri) {
    mContext.getContentResolver().notifyChange(uri, null);
    ContentResolver cr = mContext.getContentResolver();
    try {
       Bitmap bitmap =   
android.provider.MediaStore.Images.Media.getBitmap(cr, uri);
       return bitmap;
    }
    catch (Exception e) {
        Toast.makeText(mContext, e.getMessage(),  
         Toast.LENGTH_SHORT).show();
        return null;
    }
}
```

现在,运行应用。你可以用一个真实的设备或基因来实现。如果您正在使用 Genymotion,您必须启用摄像头,如第 1 章中所述欢迎来到 Android Studio。点击添加按钮,喝杯水,输入一些评论,然后自拍。点击添加条目按钮,使其出现在列表中。

太神奇了!你现在已经完了。该应用远非像素完美,但我们已经做了一些有趣的举动。美化需要时间。在下一个食谱中,我们将通过添加过渡来实现一些令人惊叹的东西。

在某些设备上,但不是所有设备上,图片可能会旋转。这是安卓开发面临的挑战之一,我们将在第 6 章捕捉和分享中讨论这个话题。

还有更多...

除了在应用的生命周期内,包含条目的列表尚未持久化。如果您愿意,您可以使条目持久化,例如,通过将条目存储在 SQLite 数据库中,或者最终通过使用 Parse,这将在第 2 章带有基于云的后端的应用中讨论。因为坚持不是这个食谱的目标,这里就不再讨论了。在第 7 章内容供应器和观察者中,讨论了 SQLite 和内容供应器。

由于 API 级别为 23,所以您还可以使用一个浮动操作按钮小部件。它有两种尺寸:默认和迷你。

另见

伟大的转变

如果您点击任何一张卡片,它将再次显示条目视图,并显示我们之前拍摄的评论和图片预览。

我们不只是想从列表视图移动到详细视图。材质设计也考虑到了巨大的自然过渡。这个食谱就适用于此。

做好准备

要完成这个食谱,您需要启动并运行之前的食谱。这个食谱将会增加一些动画。

怎么做…

以下步骤将帮助我们将动画添加到应用中:

  1. MainAdapter

    public Drink mDrink;

    中为ViewHolder添加一个mDrink成员

  2. 在同一文件中的onBindViewHolder方法通知view holder关于实际饮酒情况,就在currentDrink初始化之后:

    Drink currentDrink = mDrinks.get(position);
    holder.mDrink = currentDrink;
  3. onCreateViewHolder方法中,在末尾添加一个OnClickListener:

    v.setTag(viewHolder);
    v.setOnClickListener(new View.OnClickListener() {
        @Override
    	    public void onClick(View view) {
            ViewHolder holder = (ViewHolder) view.getTag();
            if (view.getId() == holder.itemView.getId()) 
            {
            }
        }
    });
  4. 如果视图是被点击,我们希望EntryActivity类显示选择的饮料条目。为了能够通知参赛选手选择,我们需要制作Drinka 型parcelable级:

    public class Drink implements Parcelable
  5. 我们需要为此实现几个方法:

    @Override
    public int describeContents() {
        return 0;
    }
    @Override
    public void writeToParcel(Parcel out, int flags) {
        out.writeLong(dateAndTime.getTime());
        out.writeString(comments);
        out.writeString(imageUri);
    }
    public static final Parcelable.Creator<Drink> CREATOR = new 
     Parcelable.Creator<Drink>() {
        public Drink createFromParcel(Parcel in) {
            return new Drink(in);
        }
        public Drink[] newArray(int size) {
            return new Drink[size];
        }
    };
  6. Drink类添加两个构造函数,一个是默认的,一个是接受包裹的,这样我们就可以重新创建对象并用适当的值填充它:

    public Drink(){
    }
    public Drink(Parcel in) {
        dateAndTime = new Date(in.readLong());
        comments = in.readString();
        imageUri = in.readString();
    }
  7. In the MainAdapter class, add a private variable for the request. This approach makes your code more readable:

    private int REQUEST_EDIT_ENTRY = 2;

    类型

    所谓神奇的数字容易让人产生误解,要尽可能避免。这个和其他的食谱只是为了演示,但是在现实世界中,你应该尽可能使用自我解释的常量。在这里,REQUEST_EDIT_ENTRY比仅仅把数字2放在代码的某个地方更有意义。

  8. 现在,在我们之前在MainAdapteronCreateViewHolder方法中创建的 onClick方法中,我们可以开始一个新的进入活动,并将所选饮料作为参数传递。onClick方法的实现现在看起来像这样:

    v.setOnClickListener(new View.OnClickListener() {
        @Override
    	    public void onClick(View view) {
            ViewHolder holder = (ViewHolder) view.getTag();
            if (view.getId() == holder.itemView.getId()) {
                Intent intent = new Intent(mContext,    
                 EntryActivity.class);
                intent.putExtra("edit_drink", holder.mDrink);
        ((Activity)mContext).startActivityForResult(intent,  
                  REQUEST_EDIT_ENTRY); }
        }
    });
  9. EntryActivity类的onCreate方法中,我们将检索并显示所选饮料的属性。将此实现添加到方法的末尾:

    Intent intent = getIntent();
    if (intent.hasExtra("edit_drink")) {
        Drink editableDrink = intent.getParcelableExtra("edit_drink");
        EditText editComment =    
         (EditText)findViewById(R.id.entry_edit_text_comment);
        editComment.setText(editableDrink.comments);
        if (editableDrink.imageUri != null) {
            mUri = Uri.parse(editableDrink.imageUri);
            Bitmap bitmap = getBitmapFromUri();
            ImageView preview = (ImageView) 
             findViewById(R.id.entry_image_view_preview);
            preview.setImageBitmap(bitmap);
        }
    }

注释的编辑文本将填充注释,以便用户可以编辑它们。如果图像附加到饮料条目,它将显示在预览图像视图中。现在,如果我们有一个简单而酷的方法将图像的缩略图动画化到预览中就好了:

  1. 惊喜!有。在strings.xml(在res/values文件夹中)文件中添加新的字符串资源:

    <string name="transition_preview">transition_preview 
      </string>
  2. MainAdapter类的onCreateViewHolder方法中,在onClick实现中,就在startActivityForResult方法之前,我们将使用ActivityOptionsCompat类创建从缩略图(持有者的mImageView成员)到条目活动布局中预览图像的过渡:

    ActivityOptionsCompat options =  
     ActivityOptionsCompat.makeSceneTransitionAnimation(
      ((Activity)mContext), holder.mImageView,    
       mContext.getString (R.string.transition_preview));
  3. 通过用此实现替换下一行的startActivityForResult调用来提供这些选项:

    ActivityCompat.startActivityForResult(((Activity) mContext),  
     intent, REQUEST_EDIT_ENTRY, options.toBundle());
  4. 打开adapter_main_card_view.xml布局文件,将这一行添加到图像视图中(标识为main_image_view的那一行):

    android:transitionName="@string/transition_preview"
  5. activity_entry.xml布局中,也将这一行添加到ImageView小部件(具有entry_image_view_preview标识的小部件)。这样,安卓就知道缩略图向更大的预览图像的过渡应该去哪里了)。

使用字符串资源是很好的做法。我们可以在这里使用这些资源来确保我们在代码的任何地方都在谈论相同的转换,但是这对本地化来说也是很好的。

现在,如果您运行您的应用并点击MainActivity类中的任何一张卡片,您将看到缩略图被放大并适合EntryActivity类布局中预览图像的占位符。如果您选择“后退”按钮,则会显示反转的过渡。在以前的版本中,我们不能只用几行代码就做到这一点!

主题化

作为奖励,让我们通过以下步骤进行主题化:

  1. Visit http://www.materialpalette.com and pick two colors. Theming comes up with a color set that we can use for a theme as shown in the following screenshot:

    Theming

  2. res/values文件夹中创建color.xml文件,并添加建议的颜色名称和值。我在网站上选择了蓝色和靛蓝色,所以我的颜色资源文件看起来像这样:

    <?xml version="1.0" encoding="utf-8"?>
    <resources>
        <color name="primary_dark">#1976d2</color><color name="primary">#2193f3</color>
        <color name="light_primary">#bbdefb</color>
        <color name="text">#ffffff</color>
        <color name="accent">#536dfe</color>
        <color name="primary_text">#212121</color>
        <color name="secondary_text">#727272</color>
        <color name="divider_color">#b6b6b6</color>
    </resources>
  3. Edit the styles.xml file in the res/values folder and make it look like this:

    <resources><style name="AppTheme" parent="Theme.AppCompat.Light">
          <item name="android:colorPrimary">@color/primary</item>
          <item name="android:colorPrimaryDark">@color/primary_dark 
          /item>
          <item name="android:colorAccent">@color/accent</item>
          <item name="android:textColor">@color/text</item>
          <item name="android:textColorPrimary">@color/primary_text 
          </item>
         <item name="android:textColorSecondary">
            @color/secondary_text
          </item>
      </style></resources>

    前面代码的输出如下截图所示:

    Theming

  4. 修改布局文件,更改文本视图和其他元素,使其能够反映配色方案。运行应用。

它是如何工作的...

安卓的活动转换会处理好一切。我们只需要告诉他们什么,在哪里,怎么做。只需几行代码,该应用编程接口就允许您在活动之间创建有意义的转换,这将极大地改善您的应用的用户体验 ( UX )。

随着每一个新的步骤,你的应用的外观变得越来越好!不幸的是,材质设计的介绍到此结束。你想在哪里改进就在哪里改进。玩它玩得开心!动画、UX 和布局是高质量应用的重要元素。

对于可穿戴应用来说,这可能更加重要,我们将在下一章中看到。但是,我们如何在这样的小屏幕上实现出色的用户体验呢?

还有更多...

我们只看到了材质设计的几个方面。还有很多要发现的。

进一步改进应用的外观和 UX,在MainActivity类中添加实现来处理你添加的饮料条目的数据,并在你想要的地方进行增强。或者,你可以看看你现有的应用,看看你如何实现它们。