美文网首页Android项目
Android使用RecyclerView创建类似收件箱的Gma

Android使用RecyclerView创建类似收件箱的Gma

作者: ListenToCode | 来源:发表于2018-08-15 16:16 被阅读149次

    RecyclerView的介绍是android世界发生的最好的事情。您可以使用RecyclerView创建令人惊叹的美丽列表和网格。当UI非常简单时,很多人都非常熟悉渲染基本列表。但它涉及更复杂的列表,其中包含多个UI元素以及动画,并非每个人都可以实现他们正在寻找的最终输出。

    本文旨在通过一个包含带有交互式动画的复杂列表设计的Gmail样式收件箱示例,即兴发挥您对RecyclerView的了解。

    下载源代码:https://pan.baidu.com/s/1uGlBdRC4kGwQwPAYOOJJwQ

    下载代码下载.APK

    1.概述

    仅使用RecyclerView无法实现Gmail应用程序等所需的输出。它需要其他几个android概念的组合。总的来说,我们将使用下面提到的组件来获得最好的外观和功能。

    > RecyclerView

    此应用程序所需的基本组件是RecyclerView,因为我们的主要任务是以列表方式显示数据。列表的外观是自定义的,就像Gmail应用程序显示缩略图图标,三行消息,时间戳和星形图标一样,将消息标记为重要。

    > SwipeRefreshLayout

    为了刷新收件箱,SwipeRefreshLayout包裹在RecyclerView中。本文不解释数据的持久性。因此,在刷新时,收件箱将重置为初始状态。

    > ActionMode 

    ActionMode用于在列表中长按行时显示上下文工具栏。这使我们能够在回收器视图处于多选模式时提供一组备用工具栏图标。这里我们提供删除选项来删除所选邮件。

    > Object Animators 

    Object Animators允许我们为目标元素设置动画。在这里,我们使用对象动画师在长按行时执行列表缩略图图标的翻转动画

    > Retrofit

    在生产应用程序中,所有收件箱消息都是动态的,即它们是从RESTAPI 获取的。为了证明这一点,我使用了一个JSON url来列出消息。我们使用Retrofit库来获取和反序列化JSON。

    2.收件箱邮件的示例JSON

    我创建了一个端点,以JSON格式提供收件箱消息。JSON包含诸如个人资料图片,来自主题,消息,时间戳以及呈现列表所需的其他详细信息之类的信息。实时地,应该使用任何服务器端语言从数据库生成此json。

    https://api.androidhive.info/json/inbox.json

    [  {    "id": 1,

        "isImportant": false,

    "picture": "https://api.androidhive.info/json/google.png",

        "from": "Google Alerts",

        "subject": "Google Alert - android",

        "message": "Android N update is released to Nexus Family!",

        "timestamp": "10:30 AM",

        "isRead": false

      },]

    3.创建新项目

    我们首先在Android Studio中创建新项目并进行所需的基本设置。以下是我为本文计划的最终项目结构。这篇文章似乎很冗长,但相信我这会增强你的知识,当你达到最低点时,你会看到令人惊讶的结果。

    1。从File⇒NewProject在Android Studio中创建一个新项目,并填写项目详细信息。在创建项目时,我选择了基本活动作为默认活动来获取工具栏,FAB和其他元素。

    2。打开位于app模块下的build.gradle,添加RecyclerViewRetrofitGlide依赖项并同步项目。

    dependencies {

        compile fileTree(dir: 'libs', include: ['*.jar'])

        androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {

            exclude group: 'com.android.support', module: 'support-annotations'

        })

        compile 'com.android.support:appcompat-v7:24.2.1'

        compile 'com.android.support.constraint:constraint-layout:1.0.0-alpha7'

        compile 'com.android.support:design:24.2.1'

        testCompile 'junit:junit:4.12'

        // RecyclerView

        compile 'com.android.support:recyclerview-v7:24.2.1'

        // retrofit, gson

        compile 'com.google.code.gson:gson:2.6.2'

        compile 'com.squareup.retrofit2:retrofit:2.0.2'

        compile 'com.squareup.retrofit2:converter-gson:2.0.2'

        // glide

        compile 'com.github.bumptech.glide:glide:3.7.0'

    }

    3。下载此res文件夹并将内容粘贴到项目的res文件夹中。此文件夹包含RecyclerView和工具栏所需的所有必要图标。

    4。将以下颜色字符串尺寸添加到相应的文件中。

    colors.xml

    colors.xml

        #db4437

        #b93221

        #FFFFFF

        #000000

        #111111

        #4285f4

        #7a7a7a

        #7a7a7a

        #fed776

        #e0e0e0

        #757575

        #666666

    dimens.xml

    dimens.xml

        16dp

        16dp

        72dp

        40dp

        16sp

        14sp

        25dp

        22dp

        12dp

    strings.xml中

    strings.xml中

        Gmail

        Settings

        Search

        Delete

    5。打开styles.xml并添加以下样式。这里添加windowActionModeOverlay以将ActionMode重叠到工具栏上。

    styles.xml

         @color/colorPrimary

            @color/colorPrimaryDark

            @color/colorAccent

            false

            true

            true

            @color/bg_action_mode

    6。由于我们要进行网络调用,因此我们需要清单文件中的INTERNET权限。打开AndroidManifest.xml并添加权限。

    AndroidManifest.xml中

    http://schemas.android.com/apk/res/android"

        package="info.androidhive.gmail">

         android:allowBackup="true"

            android:icon="@mipmap/ic_launcher"

            android:label="@string/app_name"

            android:supportsRtl="true"

            android:theme="@style/AppTheme">

                android:name=".activity.MainActivity"

                android:label="@string/app_name"

                android:theme="@style/AppTheme.NoActionBar">

    7。创建五个名为activityadapterhelpermodelnetwork的包。我们使用这些包来保持项目的有序性。创建包后,将MainActivity移动到活动包。

    4.添加改造 - 获取JSON

    现在我们的项目已准备好基本资源。让我们使用Retrofit库添加网络层。如果您不熟悉Retrofit,我强烈建议您阅读我之前关于Retrofit的文章。

    8。在模型包下,创建一个名为Message.java的类。此POJO类用于在解析时反序列化json。

    Message.java

    packageinfo.androidhive.gmail.model;

    publicclassMessage {

        privateintid;

        privateString from;

        privateString subject;

        privateString message;

        privateString timestamp;

        privateString picture;

        privatebooleanisImportant;

        privatebooleanisRead;

        privateintcolor = -1;

      publicMessage() {

        }

       publicintgetId() {

            returnid;

        }

        publicvoidsetId(intid) {

            this.id = id;

        }

        publicString getFrom() {

            returnfrom;

        }

        publicvoidsetFrom(String from) {

            this.from = from;    }

        publicString getSubject() {

            returnsubject;

        }

       publicvoidsetSubject(String subject) {

            this.subject = subject;

        }

        publicString getMessage() {

            returnmessage;

        }

        publicvoidsetMessage(String message) {

            this.message = message;

        }

        publicString getTimestamp() {

            returntimestamp;

        }

    publicvoidsetTimestamp(String timestamp) {

            this.timestamp = timestamp;

        }

        publicbooleanisImportant() {

            returnisImportant;

        }

        publicvoidsetImportant(booleanimportant) {

            isImportant = important;

        }

        publicString getPicture() {

            returnpicture;

        }

        publicvoidsetPicture(String picture) {

            this.picture = picture;

        }

        publicbooleanisRead() {

            returnisRead;

        }

        publicvoidsetRead(booleanread) {

            isRead = read;

        }

        publicintgetColor() {

            returncolor;

        }

        publicvoidsetColor(intcolor) {

            this.color = color;

        }

    }

    9。在网络包下,创建一个名为ApiClient.java的新类。此类创建静态改造实例。

    ApiClient.java

    packageinfo.androidhive.gmail.network;

    importretrofit2.Retrofit;

    importretrofit2.converter.gson.GsonConverterFactory;

    publicclassApiClient {

    publicstaticfinalString BASE_URL = "https://api.androidhive.info/json/";

        privatestaticRetrofit retrofit = null;

       publicstaticRetrofit getClient() {

            if(retrofit == null) {

                retrofit = newRetrofit.Builder()

                        .baseUrl(BASE_URL)

                        .addConverterFactory(GsonConverterFactory.create())

                        .build();

            }

            returnretrofit;

        }

    }

    10。在网络包下,创建一个名为ApiInterface.java的新类。此类包含其余的api端点以及它所期望的响应类型。在我们的例子中,我们只有一个端点,即inbox.json

    ApiInterface.java

    packageinfo.androidhive.gmail.network;

    importjava.util.List;

    importinfo.androidhive.gmail.model.Message;

    importretrofit2.Call;

    importretrofit2.http.GET;

    publicinterfaceApiInterface {

        @GET("inbox.json")

        Call> getInbox();

    }

    这样就完成了改造整合。现在让我们添加一些助手类来帮助渲染列表。

    11。在帮助程序包下,创建一个名为CircleTransform.java的类。此类用于使用Glide库以圆形形状显示缩略图图像。

    CircleTransform.java

    packageinfo.androidhive.gmail.helper;

    importandroid.content.Context;

    importandroid.graphics.Bitmap;

    importandroid.graphics.BitmapShader;

    importandroid.graphics.Canvas;

    importandroid.graphics.Paint;

    importcom.bumptech.glide.load.engine.bitmap_recycle.BitmapPool;

    importcom.bumptech.glide.load.resource.bitmap.BitmapTransformation;

    publicclassCircleTransform  extendsBitmapTransformation {

        publicCircleTransform(Context context) {

            super(context);

        }

        @OverrideprotectedBitmap transform(BitmapPool pool, Bitmap toTransform, intoutWidth, intoutHeight) {

            returncircleCrop(pool, toTransform);

        }

        privatestaticBitmap circleCrop(BitmapPool pool, Bitmap source) {

            if(source == null) returnnull;

            intsize = Math.min(source.getWidth(), source.getHeight());

            intx = (source.getWidth() - size) / 2;

            inty = (source.getHeight() - size) / 2;

            // TODO this could be acquired from the pool too

            Bitmap squared = Bitmap.createBitmap(source, x, y, size, size);

            Bitmap result = pool.get(size, size, Bitmap.Config.ARGB_8888);

            if(result == null) {

                result = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888);

            }

            Canvas canvas = newCanvas(result);

            Paint paint = newPaint();

            paint.setShader(newBitmapShader(squared, BitmapShader.TileMode.CLAMP, BitmapShader.TileMode.CLAMP));

            paint.setAntiAlias(true);

            floatr = size / 2f;

            canvas.drawCircle(r, r, r, paint);

            returnresult;

        }

        @OverridepublicString getId() {

            returngetClass().getName();

        }

    }

    12。在帮助程序包下,创建另一个名为DividerItemDecoration.java的类。这有助于在回收器视图中添加分隔线。

    DividerItemDecoration

    packageinfo.androidhive.gmail.helper;

    importandroid.content.Context;

    importandroid.content.res.TypedArray;

    importandroid.graphics.Canvas;

    importandroid.graphics.Rect;

    importandroid.graphics.drawable.Drawable;

    importandroid.support.v7.widget.LinearLayoutManager;

    importandroid.support.v7.widget.RecyclerView;

    importandroid.view.View;

    /**

     * Created by Ravi Tamada on 21/02/17.

     * www.androidhive.info

     */

    publicclassDividerItemDecoration extendsRecyclerView.ItemDecoration {

        privatestaticfinalint[] ATTRS = newint[]{

                android.R.attr.listDivider

        };

        publicstaticfinalintHORIZONTAL_LIST = LinearLayoutManager.HORIZONTAL;

        publicstaticfinalintVERTICAL_LIST = LinearLayoutManager.VERTICAL;

        privateDrawable mDivider;

        privateintmOrientation;

        publicDividerItemDecoration(Context context, intorientation) {

            finalTypedArray a = context.obtainStyledAttributes(ATTRS);

            mDivider = a.getDrawable(0);

            a.recycle();

            setOrientation(orientation);

        }

        publicvoidsetOrientation(intorientation) {

            if(orientation != HORIZONTAL_LIST && orientation != VERTICAL_LIST) {

                thrownewIllegalArgumentException("invalid orientation");

            }

            mOrientation = orientation;

        }

        @Override

        publicvoidonDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {

            if(mOrientation == VERTICAL_LIST) {

                drawVertical(c, parent);

            } else{

                drawHorizontal(c, parent);

            }

        }

        publicvoiddrawVertical(Canvas c, RecyclerView parent) {

            finalintleft = parent.getPaddingLeft();

            finalintright = parent.getWidth() - parent.getPaddingRight();

            finalintchildCount = parent.getChildCount();

            for(inti = 0; i < childCount; i++) {

                finalView child = parent.getChildAt(i);

                finalRecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child

                        .getLayoutParams();

                finalinttop = child.getBottom() + params.bottomMargin;

                finalintbottom = top + mDivider.getIntrinsicHeight();

                mDivider.setBounds(left, top, right, bottom);

                mDivider.draw(c);

            }

        }

        publicvoiddrawHorizontal(Canvas c, RecyclerView parent) {

            finalinttop = parent.getPaddingTop();

            finalintbottom = parent.getHeight() - parent.getPaddingBottom();

           finalintchildCount = parent.getChildCount();

            for(inti = 0; i < childCount; i++) {

                finalView child = parent.getChildAt(i);

                finalRecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child

                        .getLayoutParams();

                finalintleft = child.getRight() + params.rightMargin;

                finalintright = left + mDivider.getIntrinsicHeight();

                mDivider.setBounds(left, top, right, bottom);

                mDivider.draw(c);

            }

        }

        @Override

        publicvoidgetItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {

            if(mOrientation == VERTICAL_LIST) {

                outRect.set(0, 0, 0, mDivider.getIntrinsicHeight());

            } else{            outRect.set(0, 0, mDivider.getIntrinsicWidth(), 0);

            }

        }

    }

    5.生成随机材料颜色

    您将遇到的另一个令人兴奋的事情是为每个行图标指定一个随机背景颜色。为实现这一目标,我们需要在数组中预定义一组材质颜色,并在准备RecyclerView时选择随机颜色。感谢daniellevass提供这样一个有用的颜色代码。

    13。在res⇒ 下创建一个名为array.xml的xml 。这个xml包含很少的材料颜色,可以在列表中随机加载。

    array.xml

            #e84e40

            #ec407a

            #ab47bc

            #7e57c2

            #5c6bc0

            #738ffe

            #29b6f6

            #26c6da

            #26a69a

            #2baf2b

            #9ccc65

            #d4e157

            #ffee58

            #ffa726

            #ff7043

            #8d6e63

            #bdbdbd

            #78909c

            #e51c23

            #e91e63

            #9c27b0

            #673ab7

            #3f51b5

            #5677fc

            #03a9f4

            #00bcd4

            #009688

            #259b24

            #8bc34a

            #cddc39

            #ffeb3b

            #ff9800

            #ff5722

            #795548

            #9e9e9e

            #607d8b

    要随机加载这些颜色,可以使用以下功能。您将很快看到如何使用此功能。

    privateintgetRandomMaterialColor(String typeColor) {

        intreturnColor = Color.GRAY;

        intarrayId = getResources().getIdentifier("mdcolor_"+ typeColor, "array", getPackageName());

        if(arrayId != 0) {

            TypedArray colors = getResources().obtainTypedArray(arrayId);

            intindex = (int) (Math.random() * colors.length());

            returnColor = colors.getColor(index, Color.GRAY);

            colors.recycle();

        }

        returnreturnColor;

    }

    6.使用Object Animators翻转动画

    如果你观察gmail应用程序,当你长按并选择一行时,缩略图图标将以翻转动画显示动画,显示图标的另一面。我们可以使用ObjectAnimator概念来做同样的事情。在项目中仔细创建下面提到的文件。

    14。在res⇒值下,创建一个名为integer.xml的xml 。这里我们定义动画持续时间。

    integer.xml

        500

        200

    15。在res文件夹下创建名为animator的文件夹。在此目录中,我们放置与动画相关的所有xml资源。

    16。在animator下,创建四个名为card_flip_left_in.xmlcard_flip_left_out.xmlcard_flip_right_in.xmlcard_flip_right_out.xml的xml文件。

    card_flip_left_in.xml

    card_flip_left_in.xml

    http://schemas.android.com/apk/res/android">

       android:valueFrom="1.0"

            android:valueTo="0.0"

            android:propertyName="alpha"

            android:duration="0"/>

          android:valueFrom="-180"

            android:valueTo="0"

            android:propertyName="rotationY"

            android:interpolator="@android:interpolator/accelerate_decelerate"

            android:duration="@integer/card_flip_time_full"/>

       android:valueFrom="0.0"

            android:valueTo="1.0"

            android:propertyName="alpha"

            android:startOffset="@integer/card_flip_time_half"

            android:duration="1"/>

    card_flip_left_out.xml

    card_flip_left_out.xml

    http://schemas.android.com/apk/res/android">

          android:valueFrom="0"

            android:valueTo="180"

            android:propertyName="rotationY"

            android:interpolator="@android:interpolator/accelerate_decelerate"

            android:duration="@integer/card_flip_time_full"/>

        android:valueFrom="1.0"

            android:valueTo="0.0"

            android:propertyName="alpha"

            android:startOffset="@integer/card_flip_time_half"

            android:duration="1"/>

    card_flip_right_in.xml

    card_flip_right_in.xml

    http://schemas.android.com/apk/res/android">

         android:valueFrom="1.0"

            android:valueTo="0.0"

            android:propertyName="alpha"

            android:duration="0"/>

         android:valueFrom="180"

            android:valueTo="0"

            android:propertyName="rotationY"

            android:interpolator="@android:interpolator/accelerate_decelerate"

            android:duration="@integer/card_flip_time_full"/>

       android:valueFrom="0.0"

            android:valueTo="1.0"

            android:propertyName="alpha"

            android:startOffset="@integer/card_flip_time_half"

            android:duration="1"/>

    card_flip_right_out.xml

    card_flip_right_out.xml

    http://schemas.android.com/apk/res/android">

         android:valueFrom="0"

            android:valueTo="-180"

            android:propertyName="rotationY"

            android:interpolator="@android:interpolator/accelerate_decelerate"

            android:duration="@integer/card_flip_time_full"/>

        android:valueFrom="1.0"

            android:valueTo="0.0"

            android:propertyName="alpha"

            android:startOffset="@integer/card_flip_time_half"

            android:duration="1"/>

    17。在帮助程序包下,创建一个名为FlipAnimator.java的类。这个类有一个静态方法flipView(),它执行翻转动画。

    FlipAnimator.java

    packageinfo.androidhive.gmail.helper;

    importandroid.animation.AnimatorInflater;

    importandroid.animation.AnimatorSet;

    importandroid.content.Context;

    importandroid.view.View;

    importinfo.androidhive.gmail.R;

    publicclassFlipAnimator {

        privatestaticString TAG = FlipAnimator.class.getSimpleName();

        privatestaticAnimatorSet leftIn, rightOut, leftOut, rightIn;

        /**

         * Performs flip animation on two views

         */

        publicstaticvoidflipView(Context context, finalView back, finalView front, booleanshowFront) {

            leftIn = (AnimatorSet) AnimatorInflater.loadAnimator(context, R.animator.card_flip_left_in);

            rightOut = (AnimatorSet) AnimatorInflater.loadAnimator(context, R.animator.card_flip_right_out);

            leftOut = (AnimatorSet) AnimatorInflater.loadAnimator(context, R.animator.card_flip_left_out);

            rightIn = (AnimatorSet) AnimatorInflater.loadAnimator(context, R.animator.card_flip_right_in);

        finalAnimatorSet showFrontAnim = newAnimatorSet();

            finalAnimatorSet showBackAnim = newAnimatorSet();

           leftIn.setTarget(back);

            rightOut.setTarget(front);

            showFrontAnim.playTogether(leftIn, rightOut);

         leftOut.setTarget(back);

            rightIn.setTarget(front);

            showBackAnim.playTogether(rightIn, leftOut);

         if(showFront) {

                showFrontAnim.start();

            } else{

                showBackAnim.start();

            }

        }

    }

    7.最后在Re​​cyclerView中渲染收件箱

    哦,最后我们已经达到了文章的核心部分,即渲染实际的列表数据。有更多的耐心

    并跟随我。

    现在让我们创建RecyclerView所需的少数剩余文件。我们需要的只是主要活动,列表行,背景drawable和适配器类的xml布局。

    18。在res⇒ 可绘制下,创建两个名为bg_circle.xmlbg_list_row.xml的可绘制资源。

    bg_circle.xml(这为缩略图图标提供了实心圆形背景颜色)

    bg_circle.xml

    xmlns:android="http://schemas.android.com/apk/res/android"

        android:shape="oval">

           android:color="@color/bg_circle_default"/>

         android:width="120dp"

            android:height="120dp"/>

    bg_list_row.xml(提供背景颜色以列出具有正常和活动状态的项目)

    bg_list_row.xml

    http://schemas.android.com/apk/res/android">

    19。打开主活动的布局文件(content_main.xml)并添加RecyclerView元素。

    activity_main.xml中

    activity_main.xml中

    http://schemas.android.com/apk/res/android"

    xmlns:app="http://schemas.android.com/apk/res-auto"

    xmlns:tools="http://schemas.android.com/tools"

        android:layout_width="match_parent"

        android:layout_height="match_parent"

        tools:mContext="info.androidhive.gmail.activity.MainActivity">

       android:layout_width="match_parent"

            android:layout_height="wrap_content"

            android:theme="@style/AppTheme.AppBarOverlay">

          android:id="@+id/toolbar"

                android:layout_width="match_parent"

                android:layout_height="?attr/actionBarSize"

                android:background="?attr/colorPrimary"

                app:popupTheme="@style/AppTheme.PopupOverlay"/>

       android:id="@+id/fab"

            android:layout_width="wrap_content"

            android:layout_height="wrap_content"

            android:layout_gravity="bottom|end"

            android:layout_margin="@dimen/fab_margin"

            app:backgroundTint="@color/colorPrimary"

            app:srcCompat="@drawable/ic_edit_white_24dp"/>

    content_main.xml

    content_main.xml

    http://schemas.android.com/apk/res/android"

    xmlns:app="http://schemas.android.com/apk/res-auto"

    xmlns:tools="http://schemas.android.com/tools"

        android:layout_width="match_parent"

        android:layout_height="match_parent"

        app:layout_behavior="@string/appbar_scrolling_view_behavior"

        tools:mContext="info.androidhive.gmail.activity.MainActivity"

        tools:showIn="@layout/activity_main">

      android:id="@+id/swipe_refresh_layout"

            android:layout_width="match_parent"

            android:layout_height="wrap_content">

           android:id="@+id/recycler_view"

                android:layout_width="match_parent"

                android:layout_height="match_parent"

                android:scrollbars="vertical"/>

    20。在res⇒布局下,使用以下代码创建名为message_list_row.xml的xml布局。此布局将在回收器视图中呈现每一行。回收器视图行的实际自定义发生在此处。

    message_list_row.xml

    http://schemas.android.com/apk/res/android"

        android:layout_width="match_parent"

        android:layout_height="wrap_content"

        android:background="@drawable/bg_list_row"

        android:clickable="true"

        android:focusable="true"

        android:orientation="vertical"

        android:paddingBottom="@dimen/padding_list_row"

        android:paddingLeft="?listPreferredItemPaddingLeft"

        android:paddingRight="?listPreferredItemPaddingRight"

        android:paddingTop="@dimen/padding_list_row">

        android:id="@+id/message_container"

            android:layout_width="wrap_content"

            android:layout_height="wrap_content"

            android:clickable="true"

            android:orientation="vertical"

            android:paddingLeft="72dp"

            android:paddingRight="@dimen/padding_list_row">

           android:id="@+id/from"

                android:layout_width="match_parent"

                android:layout_height="wrap_content"

                android:ellipsize="end"

                android:lines="1"

                android:textColor="@color/from"

                android:textSize="@dimen/msg_text_primary"

                android:textStyle="bold"/>

         android:id="@+id/txt_primary"

                android:layout_width="match_parent"

                android:layout_height="wrap_content"

                android:ellipsize="end"

                android:lines="1"

                android:textColor="@color/subject"

                android:textSize="@dimen/msg_text_secondary"

                android:textStyle="bold"/>

          android:id="@+id/txt_secondary"

                android:layout_width="match_parent"

                android:layout_height="wrap_content"

                android:ellipsize="end"

                android:lines="1"

                android:textColor="@color/message"

                android:textSize="@dimen/msg_text_secondary"/>

         android:id="@+id/icon_container"

            android:layout_width="wrap_content"

            android:layout_height="wrap_content"

            android:orientation="vertical">

         android:id="@+id/icon_back"

                android:layout_width="wrap_content"

                android:layout_height="wrap_content">

            android:layout_width="@dimen/icon_width_height"

                    android:layout_height="@dimen/icon_width_height"

                    android:src="@drawable/bg_circle"/>

            android:layout_width="25dp"

                    android:layout_height="wrap_content"

                    android:layout_centerInParent="true"

                    android:src="@drawable/ic_done_white_24dp"/>

         android:id="@+id/icon_front"

                android:layout_width="wrap_content"

                android:layout_height="wrap_content">

       android:id="@+id/icon_profile"

                    android:layout_width="@dimen/icon_width_height"

                    android:layout_height="@dimen/icon_width_height"/>

       android:id="@+id/icon_text"

                    android:layout_width="wrap_content"

                    android:layout_height="wrap_content"

                    android:layout_centerInParent="true"

                    android:textColor="@android:color/white"

                    android:textSize="@dimen/icon_text"/>

       android:id="@+id/timestamp"

            android:layout_width="wrap_content"

            android:layout_height="wrap_content"

            android:layout_alignParentRight="true"

            android:textColor="@color/timestamp"

            android:textSize="@dimen/timestamp"

            android:textStyle="bold"/>

           android:id="@+id/icon_star"

            android:layout_width="@dimen/icon_star"

            android:layout_height="@dimen/icon_star"

            android:layout_alignParentBottom="true"

            android:layout_alignParentRight="true"

            android:tint="@color/icon_tint_normal"/>

    我们还需要两个菜单文件来渲染工具栏图标。一种是在工具栏处于正常模式时显示图标。另一种是在启用ActionMode时显示图标。

    21。在水库⇒菜单文件夹中创建一个名为两个菜单文件menu_main.xmlmenu_action_mode.xml

    menu_main.xml

    menu_main.xml

    http://schemas.android.com/apk/res/android"

    xmlns:app="http://schemas.android.com/apk/res-auto"

    xmlns:tools="http://schemas.android.com/tools"

        tools:mContext="info.androidhive.gmail.activity.MainActivity">

           android:id="@+id/action_search"

            android:icon="@drawable/ic_search_white_24dp"

            android:orderInCategory="100"

            android:title="@string/action_search"

            app:showAsAction="always"/>

    menu_action_mode.xml

    menu_action_mode.xml

    http://schemas.android.com/apk/res/android"

    xmlns:app="http://schemas.android.com/apk/res-auto"

    xmlns:tools="http://schemas.android.com/tools"

        tools:mContext="info.androidhive.gmail.activity.MainActivity">

          android:id="@+id/action_delete"

            android:icon="@drawable/ic_delete_white_24dp"

            android:orderInCategory="100"

            android:title="@string/action_delete"

            app:showAsAction="always"/>

    您需要注意的另一个重要的类是适配器类。RecyclerView的功能完全取决于您管理适配器类的效率。

    22。在适配器包下,创建一个名为MessagesAdapter.java的类并粘贴以下代码。这个课非常重要,花时间去探索和理解代码。所有的魔法都发生在onBindViewHolder()方法中。

    > applyReadStatus()方法根据读取状态使文本变为粗体。如果邮件未读,则将以粗体显示。

    > applyImportant()- 如果消息标记为重要,则以黄色显示星形图标。

    > applyIconAnimation()- Method在缩略图图标上执行翻转动画。

    > applyProfilePicture()- 以循环方式显示缩略图图标配置文件图片/背景。

    MessagesAdapter.java

    packageinfo.androidhive.gmail.adapter;

    importandroid.content.Context;

    importandroid.graphics.Typeface;

    importandroid.support.v4.content.ContextCompat;

    importandroid.support.v7.widget.RecyclerView;

    importandroid.text.TextUtils;

    importandroid.util.SparseBooleanArray;

    importandroid.view.HapticFeedbackConstants;

    importandroid.view.LayoutInflater;

    importandroid.view.View;

    importandroid.view.ViewGroup;

    importandroid.widget.ImageView;

    importandroid.widget.LinearLayout;

    importandroid.widget.RelativeLayout;

    importandroid.widget.TextView;

    importcom.bumptech.glide.Glide;

    importcom.bumptech.glide.load.engine.DiskCacheStrategy;

    importjava.util.ArrayList;

    importjava.util.List;

    importinfo.androidhive.gmail.R;

    importinfo.androidhive.gmail.helper.CircleTransform;

    importinfo.androidhive.gmail.helper.FlipAnimator;

    importinfo.androidhive.gmail.model.Message;

    publicclassMessagesAdapter extendsRecyclerView.Adapter {

        privateContext mContext;

        privateList messages;

        privateMessageAdapterListener listener;

        privateSparseBooleanArray selectedItems;

       // array used to perform multiple animation at once

        privateSparseBooleanArray animationItemsIndex;

        privatebooleanreverseAllAnimations = false;

     // index is used to animate only the selected row

        // dirty fix, find a better solution

        privatestaticintcurrentSelectedIndex = -1;

      publicclassMyViewHolder extendsRecyclerView.ViewHolder implementsView.OnLongClickListener {

            publicTextView from, subject, message, iconText, timestamp;

            publicImageView iconImp, imgProfile;

            publicLinearLayout messageContainer;

            publicRelativeLayout iconContainer, iconBack, iconFront;

          publicMyViewHolder(View view) {

                super(view);

                from = (TextView) view.findViewById(R.id.from);

                subject = (TextView) view.findViewById(R.id.txt_primary);

                message = (TextView) view.findViewById(R.id.txt_secondary);

                iconText = (TextView) view.findViewById(R.id.icon_text);

                timestamp = (TextView) view.findViewById(R.id.timestamp);

                iconBack = (RelativeLayout) view.findViewById(R.id.icon_back);

                iconFront = (RelativeLayout) view.findViewById(R.id.icon_front);

                iconImp = (ImageView) view.findViewById(R.id.icon_star);

                imgProfile = (ImageView) view.findViewById(R.id.icon_profile);

                messageContainer = (LinearLayout) view.findViewById(R.id.message_container);

                iconContainer = (RelativeLayout) view.findViewById(R.id.icon_container);

                view.setOnLongClickListener(this);

            }

           @Override

            publicbooleanonLongClick(View view) {

                listener.onRowLongClicked(getAdapterPosition());

                view.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);

                returntrue;

            }

        }

        publicMessagesAdapter(Context mContext, List messages, MessageAdapterListener listener) {

            this.mContext = mContext;

            this.messages = messages;

            this.listener = listener;

            selectedItems = newSparseBooleanArray();

            animationItemsIndex = newSparseBooleanArray();

        }

       @Override

        publicMyViewHolder onCreateViewHolder(ViewGroup parent, intviewType) {

            View itemView = LayoutInflater.from(parent.getContext())

                    .inflate(R.layout.message_list_row, parent, false);

           returnnewMyViewHolder(itemView);

        }

        @Override

        publicvoidonBindViewHolder(finalMyViewHolder holder, finalintposition) {

            Message message = messages.get(position);

        // displaying text view data

            holder.from.setText(message.getFrom());

            holder.subject.setText(message.getSubject());

            holder.message.setText(message.getMessage());

            holder.timestamp.setText(message.getTimestamp());

         // displaying the first letter of From in icon text

            holder.iconText.setText(message.getFrom().substring(0, 1));

          // change the row state to activated

            holder.itemView.setActivated(selectedItems.get(position, false));

     // change the font style depending on message read status

            applyReadStatus(holder, message);

        // handle message star

            applyImportant(holder, message);

        // handle icon animation

            applyIconAnimation(holder, position);

        // display profile image

            applyProfilePicture(holder, message);

       // apply click events

            applyClickEvents(holder, position);

        }

       privatevoidapplyClickEvents(MyViewHolder holder, finalintposition) {

            holder.iconContainer.setOnClickListener(newView.OnClickListener() {

                @Override

                publicvoidonClick(View view) {

                    listener.onIconClicked(position);

                }

            });

           holder.iconImp.setOnClickListener(newView.OnClickListener() {

                @Override

                publicvoidonClick(View view) {

                    listener.onIconImportantClicked(position);

                }

            });

          holder.messageContainer.setOnClickListener(newView.OnClickListener() {

                @Override

                publicvoidonClick(View view) {

                    listener.onMessageRowClicked(position);

                }

            });

        holder.messageContainer.setOnLongClickListener(newView.OnLongClickListener() {

                @Override

                publicbooleanonLongClick(View view) {

                    listener.onRowLongClicked(position);

                    view.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);

                    returntrue;

                }

            });

        }

      privatevoidapplyProfilePicture(MyViewHolder holder, Message message) {

            if(!TextUtils.isEmpty(message.getPicture())) {

                Glide.with(mContext).load(message.getPicture())

                        .thumbnail(0.5f)

                        .crossFade()

                        .transform(newCircleTransform(mContext))

                        .diskCacheStrategy(DiskCacheStrategy.ALL)

                        .into(holder.imgProfile);

                holder.imgProfile.setColorFilter(null);

                holder.iconText.setVisibility(View.GONE);

            } else{

                holder.imgProfile.setImageResource(R.drawable.bg_circle);

                holder.imgProfile.setColorFilter(message.getColor());

                holder.iconText.setVisibility(View.VISIBLE);

            }

        }

        privatevoidapplyIconAnimation(MyViewHolder holder, intposition) {

            if(selectedItems.get(position, false)) {

                holder.iconFront.setVisibility(View.GONE);

                resetIconYAxis(holder.iconBack);

                holder.iconBack.setVisibility(View.VISIBLE);

                holder.iconBack.setAlpha(1);

                if(currentSelectedIndex == position) {

                    FlipAnimator.flipView(mContext, holder.iconBack, holder.iconFront, true);

                    resetCurrentIndex();

                }

            } else{

                holder.iconBack.setVisibility(View.GONE);

                resetIconYAxis(holder.iconFront);

                holder.iconFront.setVisibility(View.VISIBLE);

                holder.iconFront.setAlpha(1);

                if((reverseAllAnimations && animationItemsIndex.get(position, false)) || currentSelectedIndex == position) {

                    FlipAnimator.flipView(mContext, holder.iconBack, holder.iconFront, false);

                    resetCurrentIndex();

                }

            }

        }

      // As the views will be reused, sometimes the icon appears as

        // flipped because older view is reused. Reset the Y-axis to 0

        privatevoidresetIconYAxis(View view) {

            if(view.getRotationY() != 0) {

                view.setRotationY(0);

            }

        }

        publicvoidresetAnimationIndex() {

            reverseAllAnimations = false;

            animationItemsIndex.clear();

        }

        @Override

        publiclonggetItemId(intposition) {

            returnmessages.get(position).getId();

        }

        privatevoidapplyImportant(MyViewHolder holder, Message message) {

            if(message.isImportant()) {

                holder.iconImp.setImageDrawable(ContextCompat.getDrawable(mContext, R.drawable.ic_star_black_24dp));

                holder.iconImp.setColorFilter(ContextCompat.getColor(mContext, R.color.icon_tint_selected));

            } else{

                holder.iconImp.setImageDrawable(ContextCompat.getDrawable(mContext, R.drawable.ic_star_border_black_24dp));

                holder.iconImp.setColorFilter(ContextCompat.getColor(mContext, R.color.icon_tint_normal));

            }

        }

        privatevoidapplyReadStatus(MyViewHolder holder, Message message) {

            if(message.isRead()) {

                holder.from.setTypeface(null, Typeface.NORMAL);

                holder.subject.setTypeface(null, Typeface.NORMAL);

                holder.from.setTextColor(ContextCompat.getColor(mContext, R.color.subject));

                holder.subject.setTextColor(ContextCompat.getColor(mContext, R.color.message));

            } else{

                holder.from.setTypeface(null, Typeface.BOLD);

                holder.subject.setTypeface(null, Typeface.BOLD);

                holder.from.setTextColor(ContextCompat.getColor(mContext, R.color.from));

                holder.subject.setTextColor(ContextCompat.getColor(mContext, R.color.subject));

            }

        }

      @Override

        publicintgetItemCount() {

            returnmessages.size();

        }

       publicvoidtoggleSelection(intpos) {

            currentSelectedIndex = pos;

            if(selectedItems.get(pos, false)) {

                selectedItems.delete(pos);

                animationItemsIndex.delete(pos);

            } else{

                selectedItems.put(pos, true);

                animationItemsIndex.put(pos, true);

            }

            notifyItemChanged(pos);

        }

        publicvoidclearSelections() {

            reverseAllAnimations = true;

            selectedItems.clear();

            notifyDataSetChanged();

        }

       publicintgetSelectedItemCount() {

            returnselectedItems.size();

        }

        publicList getSelectedItems() {

            List items =

                    newArrayList<>(selectedItems.size());

            for(inti = 0; i < selectedItems.size(); i++) {

                items.add(selectedItems.keyAt(i));

            }

            returnitems;

        }

        publicvoidremoveData(intposition) {

            messages.remove(position);

            resetCurrentIndex();

        }

       privatevoidresetCurrentIndex() {

            currentSelectedIndex = -1;

        }

        publicinterfaceMessageAdapterListener {

            voidonIconClicked(intposition);

        voidonIconImportantClicked(intposition);

       voidonMessageRowClicked(intposition);

         voidonRowLongClicked(intposition);

        }

    }

    23。最后打开您的主要活动(MainActivity.java)并修改代码,如下所示。

    >添加SwipeRefreshLayout以在刷新时获取json消息。

    > getInbox()方法获取并解析附加到数组列表的JSON。

    >创建适配器实例并将其附加到RecyclerView

    >长按列表项时启用ActionMode

    MainActivity.java

    packageinfo.androidhive.gmail.activity;

    importandroid.content.res.TypedArray;

    importandroid.graphics.Color;

    importandroid.os.Bundle;

    importandroid.support.design.widget.FloatingActionButton;

    importandroid.support.design.widget.Snackbar;

    importandroid.support.v4.widget.SwipeRefreshLayout;

    importandroid.support.v7.app.AppCompatActivity;

    importandroid.support.v7.view.ActionMode;

    importandroid.support.v7.widget.DefaultItemAnimator;

    importandroid.support.v7.widget.LinearLayoutManager;

    importandroid.support.v7.widget.RecyclerView;

    importandroid.support.v7.widget.Toolbar;

    importandroid.view.Menu;

    importandroid.view.MenuItem;

    importandroid.view.View;

    importandroid.widget.Toast;

    importjava.util.ArrayList;

    importjava.util.List;

    importinfo.androidhive.gmail.R;

    importinfo.androidhive.gmail.adapter.MessagesAdapter;

    importinfo.androidhive.gmail.helper.DividerItemDecoration;

    importinfo.androidhive.gmail.model.Message;

    importinfo.androidhive.gmail.network.ApiClient;

    importinfo.androidhive.gmail.network.ApiInterface;

    importretrofit2.Call;

    importretrofit2.Callback;

    importretrofit2.Response;

    publicclassMainActivity extendsAppCompatActivity implementsSwipeRefreshLayout.OnRefreshListener, MessagesAdapter.MessageAdapterListener {

        privateList messages = newArrayList<>();

        privateRecyclerView recyclerView;

        privateMessagesAdapter mAdapter;

        privateSwipeRefreshLayout swipeRefreshLayout;

        privateActionModeCallback actionModeCallback;

        privateActionMode actionMode;

       @Override

        protectedvoidonCreate(Bundle savedInstanceState) {

            super.onCreate(savedInstanceState);

            setContentView(R.layout.activity_main);

            Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);

            setSupportActionBar(toolbar);

       FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);

            fab.setOnClickListener(newView.OnClickListener() {

                @Override

                publicvoidonClick(View view) {

                    Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)

                            .setAction("Action", null).show();

                }

            });

        recyclerView = (RecyclerView) findViewById(R.id.recycler_view);

            swipeRefreshLayout = (SwipeRefreshLayout) findViewById(R.id.swipe_refresh_layout);

            swipeRefreshLayout.setOnRefreshListener(this);

      mAdapter = newMessagesAdapter(this, messages, this);

            RecyclerView.LayoutManager mLayoutManager = newLinearLayoutManager(getApplicationContext());

            recyclerView.setLayoutManager(mLayoutManager);

            recyclerView.setItemAnimator(newDefaultItemAnimator());

            recyclerView.addItemDecoration(newDividerItemDecoration(this, LinearLayoutManager.VERTICAL));

            recyclerView.setAdapter(mAdapter);

      actionModeCallback = newActionModeCallback();

        // show loader and fetch messages

            swipeRefreshLayout.post(

                    newRunnable() {

                        @Override

                        publicvoidrun() {

                            getInbox();

                        }

                    }

            );

        }

       /**

         * Fetches mail messages by making HTTP request

    * url:https://api.androidhive.info/json/inbox.json

         */

        privatevoidgetInbox() {

            swipeRefreshLayout.setRefreshing(true);

      ApiInterface apiService =

                    ApiClient.getClient().create(ApiInterface.class);

         Call> call = apiService.getInbox();

            call.enqueue(newCallback>() {

                @Override

                publicvoidonResponse(Call> call, Response> response) {

                    // clear the inbox

                    messages.clear();

          // add all the messages

                    // messages.addAll(response.body());

          // TODO - avoid looping

                    // the loop was performed to add colors to each message

                    for(Message message : response.body()) {

                        // generate a random color

                        message.setColor(getRandomMaterialColor("400"));

                        messages.add(message);

                    }

        mAdapter.notifyDataSetChanged();

                    swipeRefreshLayout.setRefreshing(false);

                }

                @Override

                publicvoidonFailure(Call> call, Throwable t) {

                    Toast.makeText(getApplicationContext(), "Unable to fetch json: "+ t.getMessage(), Toast.LENGTH_LONG).show();

                    swipeRefreshLayout.setRefreshing(false);

                }

            });

        }

        /**

         * chooses a random color from array.xml

         */

        privateintgetRandomMaterialColor(String typeColor) {

            intreturnColor = Color.GRAY;

            intarrayId = getResources().getIdentifier("mdcolor_"+ typeColor, "array", getPackageName());

            if(arrayId != 0) {

                TypedArray colors = getResources().obtainTypedArray(arrayId);

                intindex = (int) (Math.random() * colors.length());

                returnColor = colors.getColor(index, Color.GRAY);

                colors.recycle();

            }

            returnreturnColor;

        }

        @Override

        publicbooleanonCreateOptionsMenu(Menu menu) {

            // Inflate the menu; this adds items to the action bar if it is present.

            getMenuInflater().inflate(R.menu.menu_main, menu);

            returntrue;

        }

        @Override

        publicbooleanonOptionsItemSelected(MenuItem item) {

            // Handle action bar item clicks here. The action bar will

            // automatically handle clicks on the Home/Up button, so long

            // as you specify a parent activity in AndroidManifest.xml.

            intid = item.getItemId();

            //noinspection SimplifiableIfStatement

            if(id == R.id.action_search) {

                Toast.makeText(getApplicationContext(), "Search...", Toast.LENGTH_SHORT).show();

                returntrue;

            }

            returnsuper.onOptionsItemSelected(item);

        }

        @Override

        publicvoidonRefresh() {

            // swipe refresh is performed, fetch the messages again

            getInbox();

        }

        @Override

        publicvoidonIconClicked(intposition) {

            if(actionMode == null) {

                actionMode = startSupportActionMode(actionModeCallback);

            }

            toggleSelection(position);

        }

        @Override

        publicvoidonIconImportantClicked(intposition) {

            // Star icon is clicked,

            // mark the message as important

            Message message = messages.get(position);

            message.setImportant(!message.isImportant());

            messages.set(position, message);

            mAdapter.notifyDataSetChanged();

        }

        @Override

        publicvoidonMessageRowClicked(intposition) {

            // verify whether action mode is enabled or not

            // if enabled, change the row state to activated

            if(mAdapter.getSelectedItemCount() > 0) {

                enableActionMode(position);

            } else{

                // read the message which removes bold from the row

                Message message = messages.get(position);

                message.setRead(true);

                messages.set(position, message);

                mAdapter.notifyDataSetChanged();

                Toast.makeText(getApplicationContext(), "Read: "+ message.getMessage(), Toast.LENGTH_SHORT).show();

            }

        }

        @Override

        publicvoidonRowLongClicked(intposition) {

            // long press is performed, enable action mode

            enableActionMode(position);

        }

        privatevoidenableActionMode(intposition) {

            if(actionMode == null) {

                actionMode = startSupportActionMode(actionModeCallback);

            }

            toggleSelection(position);

        }

        privatevoidtoggleSelection(intposition) {

            mAdapter.toggleSelection(position);

            intcount = mAdapter.getSelectedItemCount();

            if(count == 0) {

                actionMode.finish();

            } else{

                actionMode.setTitle(String.valueOf(count));

                actionMode.invalidate();

            }

        }

        privateclassActionModeCallback implementsActionMode.Callback {

            @Override

            publicbooleanonCreateActionMode(ActionMode mode, Menu menu) {

                mode.getMenuInflater().inflate(R.menu.menu_action_mode, menu);

                // disable swipe refresh if action mode is enabled

                swipeRefreshLayout.setEnabled(false);

                returntrue;

            }

            @Override

            publicbooleanonPrepareActionMode(ActionMode mode, Menu menu) {

                returnfalse;

            }

            @Override

            publicbooleanonActionItemClicked(ActionMode mode, MenuItem item) {

                switch(item.getItemId()) {

                    caseR.id.action_delete:

                        // delete all the selected messages

                        deleteMessages();

                        mode.finish();

                        returntrue;

                    default:

                        returnfalse;

                }

            }

            @Override

            publicvoidonDestroyActionMode(ActionMode mode) {

                mAdapter.clearSelections();

                swipeRefreshLayout.setEnabled(true);

                actionMode = null;

                recyclerView.post(newRunnable() {

                    @Override

                    publicvoidrun() {

                        mAdapter.resetAnimationIndex();

                        // mAdapter.notifyDataSetChanged();

                    }

                });

            }

        }

        // deleting the messages from recycler view

        privatevoiddeleteMessages() {

            mAdapter.resetAnimationIndex();

            List selectedItemPositions =

                    mAdapter.getSelectedItems();

            for(inti = selectedItemPositions.size() - 1; i >= 0; i--) {

                mAdapter.removeData(selectedItemPositions.get(i));

            }

            mAdapter.notifyDataSetChanged();

        }

    }

    运行项目并查看输出的实际效果。确保您的设备具有良好的互联网连接。如果您有任何疑问,请随时在下面的评论部分询问。

    快乐的编码 

    下一步是什么?

    本文涵盖了所有内容,但缺少一件事,即添加Swipe以删除和撤消功能。但不要担心,下一篇文章解释了向RecyclerView 添加Swipe Delete和Undo

    Ravi Tamada

    你好!我是androidhive和编程爱好者的创始人。我的技能包括Android,iOS,PHP,Ruby on Rails等等。如果您有任何想法,我希望我发展?我们来聊聊吧:ravi@androidhive.info

    相关文章

      网友评论

        本文标题:Android使用RecyclerView创建类似收件箱的Gma

        本文链接:https://www.haomeiwen.com/subject/kpuwbftx.html