IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> android开发拍照/相册选择照片,接口上传单张图片,页面显示照片 -> 正文阅读

[移动开发]android开发拍照/相册选择照片,接口上传单张图片,页面显示照片

项目场景:

1.本项目是MVVP模式,使用retrofit

2.ActivityA点击imageView控件后进入CaptureImgActivity中,CaptureImgActivity中支持拍照/相册,点击拍照/相册后,需要把拍照/相册后的照片通过接口上传到文件服务器上,同时将图片显示到activityA的页面上;

3.因为我是写完项目之后回来写的这篇博客,如果你在复制代码后有报红的,请不要烦躁,仔细看代码,主要实现代码都详细贴上来了;

4.这里我绑定对应的控件用的是databinding形式

开发前需简单了解的基础知识

1.MediaStore这个类是android系统提供的一个多媒体数据库,android中多媒体信息都可以从这里提取。

2.通过隐式intent意图打开对应的拍照或者相册选择功能

Intent intent = new Intent(Intent.ACTION_PICK, null);//打开系统相册
Intent intent = new Intent("android.media.action.IMAGE_CAPTURE");//打卡系統相機

3.https://blog.csdn.net/juer2017/article/details/102933451这篇博客中解释的A与B中requestcode和resultcode中的对应关系

4.重写onActivityResult方法,onActivityResult可以可以接收到拍照/相册选择后自动返回来的数据(Intent data)

(startActivityForResult,setResult方法)

具体实现代码:

在写代码之前你需要注意的点:

1.代码中mView.hideLoading();mView.onError(err);之类的代码你可以注释掉没有影响;
2.需要添加相机、相册查看等权限,具体啥权限自己找吧…
3.AndroidManifest需要添加provider
4.AndroidManifest中配置权限file_paths,file_paths中external-path中的xxx是:AndroidManifest中package名字;
5.requestcode resultcode在不同的activity中对应
6.gradle(:app)中添加glid相关
//Glid圖片加載庫
implementation ‘com.github.bumptech.glide:glide:4.8.0’

1.AndroidManifest添加相机、相册查看等权限
AndroidManifest除了需要添加相机相册权限外还需要添加:

<provider
            android:name="androidx.core.content.FileProvider"
            android:authorities="${applicationId}.fileprovider"
            android:exported="false"
            android:grantUriPermissions="true"
            tools:replace="android:authorities">
            <meta-data
                android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/file_paths" />
</provider>

2.res->xml文件中配置新增file_paths.xml
注意:external-path中的xxx是:AndroidManifest中package名字

<?xml version="1.0" encoding="utf-8"?>
<resources>
<paths>
    <external-path path="xxx" name="files_root" />
    <external-path path="." name="external_storage_root" />
    <root-path name="root_path" path="."/>
    <external-path  name="camera_photos"  path="." />
</paths>
</resources>

3.Constanst全局中:

public static int PICTURE_REQUEST_CODE = 1;
public static int PICTURE_RESULT_CODE = 2;
public static int  CAMERA_REQUEST_CODE= 3;//相冊

4.activityA中:

//**跳转到activityB:CaptureImgActivity中
binding.ivImage.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    Intent intent = new Intent(context, CaptureImgActivity.class);
                    context.startActivityForResult(intent, PICTURE_REQUEST_CODE);//隐intent
                    //PICTURE_REQUEST_CODE是自己定义的,这个字段表示PICTURE_REQUEST_CODE对应的就是CaptureImgActivity返回的数据
//                    要重新返回此界面,因此不關閉該界面
                }
                    
            });


解释:
为什么要用PICTURE_REQUEST_CODE来标识呢?(如果看不懂这个解释,那请跳过…)
1.可以先看完上面基础知识第三点以及知道onActivityResult是用来干啥的之后解释起来就容易懂,
activityA中的onActivityResult会接受到多个不同的activity返回的intent数据,
2.通过你自己定义的标识就能知道是哪个activity传回来的值了,在本代码中,当onActivityResult中判断if (requestCode == PICTURE_REQUEST_CODE && resultCode == RESULT_CANCELED)就说明intent是由CaptureImgActivity这个activity传递过来的

5.CaptureImgActivity中:xml样式
请添加图片描述

6.CaptureImgActivity代码:

private String imageSavePath;
@Override
    public void initView() {
        binding.tvCapture.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                File photoFile = createImageSaveFile(getApplicationContext());
                //启动相机程序
                Intent intent = new Intent("android.media.action.IMAGE_CAPTURE");//打卡系統相機
                Uri imageUri = FileProvider.getUriForFile(
                        getApplicationContext(),
                        getPackageName() + ".fileprovider",
                        photoFile);
                intent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri);
                startActivityForResult(intent, PICTURE_REQUEST_CODE);
            }
        });
        
        binding.tvAlbum.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (ContextCompat.checkSelfPermission(CaptureImgActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE)!= PackageManager.PERMISSION_GRANTED){
                    ActivityCompat.requestPermissions(CaptureImgActivity.this,new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},1);
                }else {
                    Intent intent = new Intent(Intent.ACTION_PICK, null);//打开系统相册
                    intent.setDataAndType(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, "image/*");
                    startActivityForResult(intent, 3);
                }
            }
        });
        
        binding.ivBack.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent();
                intent.putExtra("image","0");
                setResult(RESULT_CANCELED,intent);
                finish();
                return;
            }
        });
    }

//拍照:創建存放拍照得到的照片的文件
    private File createImageSaveFile(Context ctx) {
        File myCaptureFile = null;
        try {
            // 已挂载
            File pic = ctx.getExternalFilesDir(Environment.DIRECTORY_PICTURES + "/save");
            if (!pic.exists()) {
                pic.mkdirs();
            }
            
            myCaptureFile = File.createTempFile("utc", ".jpg", pic);
            if (!myCaptureFile.exists()) {
                myCaptureFile.createNewFile();
            }
            
            imageSavePath = myCaptureFile.getAbsolutePath();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return myCaptureFile;
    }
    
@Override
    protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (requestCode == PICTURE_REQUEST_CODE && resultCode == RESULT_CANCELED){//打开相机不拍照返回
            Intent intent = new Intent();
            intent.putExtra("image","0");
            setResult(Constans.PICTURE_RESULT_CODE,intent);
            finish();
            return;
        }
        if (requestCode == PICTURE_REQUEST_CODE && resultCode == RESULT_OK) {//打开相机拍照
            Intent intent = new Intent();
            intent.putExtra("image",imageSavePath);
            setResult(Constans.PICTURE_RESULT_CODE,intent);
            finish();
            return;
        }

        if (requestCode == 3 && resultCode == RESULT_OK) {//打开相冊選擇了圖片
            Uri originalUri = data.getData(); //获得图片的uri
            // 这里开始的第二部分,获取图片的路径:
            String[] proj = {MediaStore.Images.Media.DATA};
            //managedQuery在Android4.4之后被弃用了,用getContentResolver().query代替,
            // 但是在该项目中,使用getContentResolver().query会报空指针异常
            Cursor cursor = managedQuery(originalUri, proj, null, null, null);
            if (cursor == null){
                 cursor = getContentResolver().query(originalUri, proj, null, null, null);
            }
            //按我个人理解 这个是获得用户选择的图片的索引值
            int column_index = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);
            cursor.moveToFirst();
            //最后根据索引值获取图片路径
            String path = cursor.getString(column_index);
            Intent intent = new Intent();
            intent.putExtra("image",path);
            setResult(Constans.PICTURE_RESULT_CODE,intent);//要传递的结果resultcode, intent

            finish();
            return;
        }
        //打開相冊但是沒選擇照片就返回了
        if (requestCode == 3 && resultCode == RESULT_CANCELED){
            Intent intent = new Intent();
            intent.putExtra("image","0");
            setResult(RESULT_CANCELED,intent);
            finish();
            return;
        }

    }
    

7.activityA中:
//接收返回来的数据
//当没有拍照或者没有选择相册就返回到A中,则不执行任何操作
//若拍照了或者选择了照片,则将照片通过接口上传到文件服务器上

//actionDetailAdapter调用壳子的扫码功能后返回的扫码结果
    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);

        if (requestCode == PICTURE_REQUEST_CODE && resultCode == RESULT_CANCELED){//打开相机不拍照返回 或者不選擇照片返回
            return;
        }
        if (requestCode == PICTURE_REQUEST_CODE && resultCode == -2){//打开相册不拍照不选择照片返回
            return;
        }

        if (requestCode == PICTURE_REQUEST_CODE && resultCode == RESULT_CANCELED){//打开相册不拍照不选择照片返回
        }
        //拍照or相冊選擇照片
        if (requestCode == PICTURE_REQUEST_CODE && resultCode == Constans.PICTURE_RESULT_CODE) {//打开相册返回图片或者拍照返回
            
                String imageLocalPath = data.getStringExtra("image");//获取由CaptureActivity中的intent传递过来的图片路径
                if (imageLocalPath!=null){
                    SPUtils.getInstance().save(Constans.PICTURE_LOCAL_PATH,imageLocalPath);//保存本地照片的本地路径
                    mPresenter.postImage(imageLocalPath,1);//將圖片路徑給接口上傳到服務器
                }
        } 
    }

8.通过接口上传图片到服务器
新手可以先去看这篇文章:
retrofit2 -Multipart上传图片,文件, 带多参数上传:
https://blog.csdn.net/qq_36767261/article/details/108886357

APIService接口:

/**
     * 圖片上傳
     * @param file
     * @param number 創建文件夾
     * 
     * @return
     */
     // number参数根据接口的要求而定,如果你不需要,那就不用加这个参数喽
     
    @Multipart
    @POST("v1/upload/")//你自己的接口
    //以文件形式上传图片
    //接口返回上传到文件服务器的图片的路径
    Observable<Response<String>> uploadImage(@Part MultipartBody.Part file, @Part("number") int number);
    //因为是将图片以文件的形式上传的,通过 MultipartBody.Part file;

注意:下面的9中,因为每个人的项目不太一样,9这一项仅作为参考!!!!!!!按照你自己项目的规范来写就行
因为文件服务器使用的地址是单独的,所以还需要在retrofitClient中单独写一个文件服务器的Url地址
9.RetrofitClient中:

public class RetrofitClient {
	private APIService apiServiceUploadImg;
	private Retrofit retrofitUploadImg;
	public APIService getUploadImgApi() {
        //初始化一个client,不然retrofit会自己默认添加一个
        if (retrofitUploadImg == null) {
            retrofitUploadImg = new Retrofit.Builder()
                    //设置网络请求的Url地址
                    .baseUrl(baseUrl_uploadimge)//你自己的文件服务器的地址**
                    //设置数据解析器
                    .addConverterFactory(CheckGsonConverterFactory.create())
                    //设置网络请求适配器,使其支持RxJava与RxAndroid
                    .addCallAdapterFactory(RxJava3CallAdapterFactory.create())
//                    .client(ClientFactory.getInstance().getOkHttpClient())
                    .client(getOkHttpClient())
                    .build();
        }
        //创建—— 网络请求接口—— 实例
        if (apiServiceUploadImg == null) {
            apiServiceUploadImg = retrofitUploadImg.create(APIService.class);
        }
        return apiServiceUploadImg;
    }
}

10.model层:
//因为项目不一样,这里的return RetrofitClient.getInstance().getUploadImgApi().uploadImage(partFile,number);可能在你的项目中不能用,这里仅作为参考,按照你自己的项目的规范来就可以!!

@Override
    public Observable<Response<String>> uploadImage(String imagePath, int number) {
    //接口需要的类型是file文件类型,所以需要把返回的图片路径转换为file文件类型传递给接口
        File file = new File(imagePath);
        RequestBody requestBody = RequestBody.create(MediaType.parse("multipart/form-data"),file);
        MultipartBody.Part partFile = MultipartBody.Part.createFormData("file",file.getName(),requestBody);
        **return RetrofitClient.getInstance().getUploadImgApi().uploadImage(partFile,number);
    }
    

11.Contract层:

public interface Contract {
    interface IModel {
        Observable<Response<String>> uploadImage(String imagePath,int number);
    }
    interface View extends BaseView {
    }
    interface Presenter {
    }

12.presenter层:

private CheckDetailContract_CHUJIAN.IModel model;

	/**
     * 上傳圖片
     * 將本地的圖片路徑轉換為文件上傳到文件服務器,文件服务器会返回上传的图片的路径
     * **number:后端接口要求传递的number,自定义上传图片到文件服务器的number文件夹下**
     * **这个参数你在开发的过程中可以不加这个参数**
     * 直接public void postImage(String imageLocalPath)
     */
    public void postImage(String imageLocalPath, int number) {
        model.uploadImage(imageLocalPath, number)
                .subscribeOn(Schedulers.io())
//                .retryWhen(new RetryWithDelay(3, 2))//遇到错误时重试,第一个参数为重试几次,第二个参数为重试的间隔
                .doOnSubscribe(disposable -> {
                    //mView.showLoading();//显示进度条
                }).subscribeOn(AndroidSchedulers.mainThread())
                .observeOn(AndroidSchedulers.mainThread())
                .doFinally(() -> {
                   // mView.hideLoading();//隐藏下进度条
                })
                .subscribe(new Observer<Response<String>>(){
                    @Override
                    public void onSubscribe(@NonNull Disposable d) {
                    }
                    @Override
                    public void onNext(@NonNull Response<String> stringResponse) {
                        String imageSeverUrl = "";
                        if (stringResponse.getCode() == 100) {
                            imageSeverUrl = stringResponse.getData();//服务器返回的服务器图片路径、
                            notifyPicture(imageSeverUrl );
                        } else {
                            String str = "";
                            if (stringResponse.getMsg() == null || stringResponse.getMsg().length() < 1) {
                            } else {
                                str = stringResponse.getMsg();
                            }
                        }
                    }

                    @Override
                    public void onError(@NonNull Throwable e) {
                        mView.hideLoading();
                        if (e instanceof HttpException) {
                            ResponseBody body = ((HttpException) e).response().errorBody();
                            try {
                                String err = JsonFormatUtils.getErrMsg(body.string());
                                mView.onError(err);
                            } catch (IOException IOe) {
                                IOe.printStackTrace();
                            }
                        } else if (e instanceof UnknownHostException) {
                        }
                    }

                    @Override
                    public void onComplete() {
                    }
                });
    }

上传图片之后需要将服务器返回的相当于网络图片显示在页面上:

ivImage是imageview要展示图片的控件
将手机拍完的照片上传到图片服务器之后,这个图片就相当于是网络图片了,文件服务器返回这张图片的路径,将网络图片显示在对应的imageview中:

通过glide图片加载框架

首先需要在添加配置
build.gradle(:app)中添加:

//Glid圖片加載庫
    implementation 'com.github.bumptech.glide:glide:4.8.0'

activityA:

public void notifyPicture(String imageSeverUrl) {
	Glide .with(context).load(imageSeverUrl).into(ivImage);
    }
                
  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2022-07-04 23:03:58  更:2022-07-04 23:04:29 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/25 2:45:42-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码