如何使用适用于Android的Matisse库和Glide 4创建带有预览的图像/视频选择器

本文概述

由于明显的原因, 与Dropbox, WhatsApp, Instagram等图像兼容的应用程序不使用普通的文件选择器。这就是为什么有一些特殊的选择器允许用户查看存储在设备上的图像集合的预览。通常, 你不会自己实现所有这些, 因为要考虑很多事情, 这可能会花费你项目的宝贵开发时间, 因此, 第三方解决方案是最好的进行方式, 而Matisse可以帮助你你去做。 Matisse是一款经过精心设计的Android本地图像和视频选择器。使用此库, 你可以:

  • 在活动或片段中使用它
  • 选择包括JPEG, PNG, GIF的图像和包括MPEG, MP4的视频
  • 应用不同的主题, 包括两个内置主题和自定义主题
  • 不同的图像加载器
  • 定义自定义过滤规则
  • 进一步了解自己

在本文中, 我们将向你展示如何使用Glide 4在Android项目中安装和使用Matisse库。

1.配置依赖关系和库

你将需要包括并更新所有Android支持库, 以匹配项目的compileSdkVersion。在这种情况下, 我们使用的compileSdkVersion设置为28的新Android项目, 因此我们的库将使用Matisse所需的所有android支持库的版本28。还包括Glide引擎和Matisse库。截止到本文发布之日, 我们正在使用每个库的最新版本:

dependencies {
    implementation 'com.android.support:appcompat-v7:28.0.0'
    implementation 'com.android.support:recyclerview-v7:28.0.0'
    implementation 'com.android.support:animated-vector-drawable:28.0.0'
    implementation 'com.android.support:support-media-compat:28.0.0'
    implementation 'com.android.support.constraint:constraint-layout:1.1.3'
    implementation 'com.android.support:support-v4:28.0.0'

    implementation 'com.github.bumptech.glide:glide:4.9.0'
    annotationProcessor 'com.github.bumptech.glide:compiler:4.9.0'
    implementation 'com.zhihu.android:matisse:0.5.2-beta3'
}

保存更改并同步项目。安装库之后, 我们将可以在你的项目中继续使用该库。有关Matisse的更多信息, 请访问此处的Github官方存储库, 或有关Glide的更多信息, 也访问其在Github的存储库。

2.准备资源字符串

在你的app / src / res / values / strings.xml文件中, 添加以下资源:

<resources>
    <string name="error_gif">x or y bound size should be at least %1$dpx and file size should be no more than %2$sM</string>
</resources>

在同一目录中, 创建具有以下内容的dimens.xml文件:

<?xml version="1.0" encoding="utf-8"?>
<!--
  Copyright 2017 Zhihu Inc.

  Licensed under the Apache License, Version 2.0 (the "License");
  you may not use this file except in compliance with the License.
  You may obtain a copy of the License at

  http://www.apache.org/licenses/LICENSE-2.0

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.
  -->
<resources>
    <dimen name="grid_expected_size">120dp</dimen>
    <dimen name="item_margin_horizontal">24dp</dimen>
    <dimen name="item_margin_vertical">8dp</dimen>
</resources>

3.创建GifSizeFilter类

在选择器初始化期间, 我们将定义GifSizeFilter的实例作为选择器的图像过滤器, 该类将定义图像的大小和操作系统, 因此继续在你的应用中创建一个新类, 即GifSizeFilter。具有以下内容的java:

package com.yourcompany.yourapp;

import android.content.Context;
import android.graphics.Point;

import com.zhihu.matisse.MimeType;
import com.zhihu.matisse.filter.Filter;
import com.zhihu.matisse.internal.entity.IncapableCause;
import com.zhihu.matisse.internal.entity.Item;
import com.zhihu.matisse.internal.utils.PhotoMetadataUtils;

import java.util.HashSet;
import java.util.Set;

class GifSizeFilter extends Filter {

    private int mMinWidth;
    private int mMinHeight;
    private int mMaxSize;

    GifSizeFilter(int minWidth, int minHeight, int maxSizeInBytes) {
        mMinWidth = minWidth;
        mMinHeight = minHeight;
        mMaxSize = maxSizeInBytes;
    }

    @Override
    public Set<MimeType> constraintTypes() {
        return new HashSet<MimeType>() {{
            add(MimeType.GIF);
        }};
    }

    @Override
    public IncapableCause filter(Context context, Item item) {
        if (!needFiltering(context, item))
            return null;

        Point size = PhotoMetadataUtils.getBitmapBound(context.getContentResolver(), item.getContentUri());
        if (size.x < mMinWidth || size.y < mMinHeight || item.size > mMaxSize) {
            return new IncapableCause(
                    IncapableCause.DIALOG, context.getString(R.string.error_gif, mMinWidth, String.valueOf(PhotoMetadataUtils.getSizeInMB(mMaxSize))));
        }
        return null;
    }

}

4.创建Glide 4图像引擎

图像/视频选择器使用Glide库(一种用于Android的快速有效的开源媒体管理和图像加载框架)将媒体解码, 内存和磁盘缓存以及资源池包装到一个简单易用的界面中。在选择器初始化期间, 我们将Glide 4引擎的实例定义为首选图像引擎, 因此继续在你的应用中创建一个新类, 即Glide4Engine.java, 其内容如下:

package com.yourcompany.yourapp;

import android.content.Context;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.widget.ImageView;

import com.bumptech.glide.Glide;
import com.bumptech.glide.Priority;
import com.bumptech.glide.request.RequestOptions;
import com.zhihu.matisse.engine.ImageEngine;

/**
 * {@link ImageEngine} implementation using Glide.
 */
public class Glide4Engine implements ImageEngine {

    @Override
    public void loadThumbnail(Context context, int resize, Drawable placeholder, ImageView imageView, Uri uri) {
        Glide.with(context)
                .asBitmap() // some .jpeg files are actually gif
                .load(uri)
                .apply(new RequestOptions()
                        .override(resize, resize)
                        .placeholder(placeholder)
                        .centerCrop())
                .into(imageView);
    }

    @Override
    public void loadGifThumbnail(Context context, int resize, Drawable placeholder, ImageView imageView, Uri uri) {
        Glide.with(context)
            .asBitmap() // some .jpeg files are actually gif
            .load(uri)
            .apply(new RequestOptions()
                    .override(resize, resize)
                    .placeholder(placeholder)
                    .centerCrop())
            .into(imageView);
    }

    @Override
    public void loadImage(Context context, int resizeX, int resizeY, ImageView imageView, Uri uri) {
        Glide.with(context)
            .load(uri)
            .apply(new RequestOptions()
                    .override(resizeX, resizeY)
                    .priority(Priority.HIGH)
                    .fitCenter())
            .into(imageView);
    }

    @Override
    public void loadGifImage(Context context, int resizeX, int resizeY, ImageView imageView, Uri uri) {
        Glide.with(context)
            .asGif()
            .load(uri)
            .apply(new RequestOptions()
                    .override(resizeX, resizeY)
                    .priority(Priority.HIGH)
                    .fitCenter())
            .into(imageView);
    }

    @Override
    public boolean supportAnimatedGif() {
        return true;
    }
}

5.创建应用程序布局

在此应用程序中, 我们的布局将非常简单, 但是它基于约束布局, 因此你可以根据需要更改它, 只需在activity_main.xml文件中添加带有id按钮的按钮即可。我们的布局如下所示:

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="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:context=".MainActivity">

    <Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="49dp"
        android:text="Pick a file(s)"
        tools:layout_editor_absoluteX="16dp"
        tools:layout_editor_absoluteY="16dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent"
    />

</android.support.constraint.ConstraintLayout>

主要思想是有一个按钮, 当单击它时会打开图像选择器。

6.基本例子

使该选择器工作所需的逻辑如下:声明2个类可访问变量, 这些变量将包含活动结果, 权限等的随机标识码, 以及一个Uri的列表变量, 该变量将分别包含用户选择的文件。我们有一个按钮, 因此我们将添加一个onClick侦听器, 该侦听器将使用自定义选项(你​​可以根据需要对其进行自定义)静态调用Matisse对话框的实例, 例如, 我们将允许选择所有类型的图像并设置一个最多可选择9个文件。如前所述, 我们的示例将使用Glide 4 Engine, 因此你将需要定义一个新实例, 并将其作为Matisse的imageEngine方法的参数提供。

这将打开对话框, 用户将可以在对话框中选择图像。用户单击”确定”后, 将取决于你如何处理主要活动的onActivityResult回调上的接收数据。在我们的例子中, 我们将只在日志中显示数据:

请注意, 该示例不处理权限, 你将需要自己执行此操作。在完整的示例中检查权限。

// Random code that identifies the result of the picker
public static final int PICKER_REQUEST_CODE = 1;

// List that will contain the selected files/videos
List<Uri> mSelected;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    Button button = findViewById(R.id.button);
    button.setOnClickListener(new View.OnClickListener() {
        public void onClick(View v) {

            // When the button is clicked, open the Matisse dialog to pick images !
            Matisse.from(MainActivity.this)
                .choose(MimeType.ofAll())
                .countable(true)
                .maxSelectable(9)
                .addFilter(new GifSizeFilter(320, 320, 5 * Filter.K * Filter.K))
                .gridExpectedSize(getResources().getDimensionPixelSize(R.dimen.grid_expected_size))
                .restrictOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED)
                .thumbnailScale(0.85f)
                .imageEngine(new Glide4Engine())
                .forResult(PICKER_REQUEST_CODE);
        }
    });
}

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    if (requestCode == PICKER_REQUEST_CODE && resultCode == RESULT_OK) {
        mSelected = Matisse.obtainResult(data);

        // Display in the logs the selected items.
        // Outputs something like:
        // D/Matisse: mSelected: [
        //    content://media/external/images/media/26263, //    content://media/external/images/media/26264, //    content://media/external/images/media/26261
        // ]
        Log.d("Matisse", "mSelected: " + mSelected);
    }
}

完整的例子

你将需要在AndroidManifest.xml文件中处理READ_EXTERNAL_STORAGE和WRITE_EXTERNAL_STORAGE的权限:

<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

但是, 对于Android 6+而言, 这还不够, 因为你将需要在运行时上请求权限, 以下示例描述了具有单个活动的功能齐全的应用程序, 处理了权限并应用了本文中提到的所有步骤:

package com.yourcompany.yourapp;

import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.support.v4.app.ActivityCompat;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;

import com.zhihu.matisse.Matisse;
import com.zhihu.matisse.MimeType;
import com.zhihu.matisse.filter.Filter;

import java.util.List;

public class MainActivity extends AppCompatActivity {
    public static final int PICKER_REQUEST_CODE = 1;

    // List that will contain the selected files/videos
    List<Uri> mSelected;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Button button = findViewById(R.id.button);
        button.setOnClickListener(new View.OnClickListener() {
            public void onClick(View v) {
                String[] PERMISSIONS = {
                    android.Manifest.permission.READ_EXTERNAL_STORAGE, android.Manifest.permission.WRITE_EXTERNAL_STORAGE
                };

                if(hasPermissions(MainActivity.this, PERMISSIONS)){
                    ShowPicker();
                }else{
                    ActivityCompat.requestPermissions(MainActivity.this, PERMISSIONS, PICKER_REQUEST_CODE);
                }
            }
        });
    }

    /**
     * Method that displays the image/video chooser.
     */
    public void ShowPicker()
    {
        Matisse.from(MainActivity.this)
            .choose(MimeType.ofAll())
            .countable(true)
            .maxSelectable(9)
            .addFilter(new GifSizeFilter(320, 320, 5 * Filter.K * Filter.K))
            .gridExpectedSize(getResources().getDimensionPixelSize(R.dimen.grid_expected_size))
            .restrictOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED)
            .thumbnailScale(0.85f)
            .imageEngine(new Glide4Engine())
            .forResult(PICKER_REQUEST_CODE);
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (requestCode == PICKER_REQUEST_CODE && resultCode == RESULT_OK) {
            mSelected = Matisse.obtainResult(data);
            Log.d("Matisse", "mSelected: " + mSelected);
        }
    }

    /**
     * Helper method that verifies whether the permissions of a given array are granted or not.
     *
     * @param context
     * @param permissions
     * @return {Boolean}
     */
    public static boolean hasPermissions(Context context, String... permissions) {
        if (context != null && permissions != null) {
            for (String permission : permissions) {
                if (ActivityCompat.checkSelfPermission(context, permission) != PackageManager.PERMISSION_GRANTED) {
                    return false;
                }
            }
        }
        return true;
    }

    /**
     * Callback that handles the status of the permissions request.
     *
     * @param requestCode
     * @param permissions
     * @param grantResults
     */
    @Override
    public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) {
        switch (requestCode) {
            case PICKER_REQUEST_CODE: {
                // If request is cancelled, the result arrays are empty.
                if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                    Toast.makeText(
                            MainActivity.this, "Permission granted! Please click on pick a file once again.", Toast.LENGTH_SHORT
                    ).show();
                } else {
                    Toast.makeText(
                            MainActivity.this, "Permission denied to read your External storage :(", Toast.LENGTH_SHORT
                    ).show();
                }

                return;
            }
        }
    }
}

编码愉快!

微信公众号
手机浏览(小程序)
0
分享到:
没有账号? 忘记密码?