Android 垃圾分类APP(四)垃圾分类之图像输入

您所在的位置:网站首页 安卓垃圾分类源码 Android 垃圾分类APP(四)垃圾分类之图像输入

Android 垃圾分类APP(四)垃圾分类之图像输入

2024-07-15 12:09| 来源: 网络整理| 查看: 265

图像输入 前言正文一、创建平台应用二、新建图像识别页面三、网络订阅四、编写页面代码五、识别网络图片六、识别相册图片七、识别拍照图片八、垃圾分类九、源码

前言

  在上一篇文章中完成了语音输入,这一篇来写图像输入

正文

  图像输入无非就是图片识别嘛,再通俗一点就是识别手机中的照片,分析里面的物品,然后进行垃圾分类。图像识别还是有很多的SDK可以使用的,这里面我目前用过的就是百度的图像识别,感觉还是蛮好的,而且有我之前的文章做普遍,那么本文是属于APP功能编写,这与单独写介绍SDK使用的文章完全是两回事。那么就来看看实践中怎么插入这个图像识别了。

如果你还有时间的话,不妨先去看看Android 百度图像识别(详细步骤+源码)

因为毕竟是写过一次的东西了,只是应用环境不同,所以下面就只是介绍业务逻辑和贴代码,不再去详细讲解。

一、创建平台应用

既然要用百度的SDK,自然要先去百度智能云注册登录,登录之后呢。点击管理控制台,然后点击左侧产品服务箭头左侧展开,找到图像识别点进去。 在这里插入图片描述 点击创建应用 在这里插入图片描述 输入相关的信息就可以了。

在这里插入图片描述 填写好资料后点击立即创建。 在这里插入图片描述 查看应用详情。 在这里插入图片描述 这里有三个关键的信息:AppID、API Key、Secret Key,这三个值在后面会用到,请使用自己创建应用时生成的值。现在先把它们放到常量里面,打开Constant,这里的四个常量,对应的值就是你在平台上申请应用产生的,记得使用自己的。 在这里插入图片描述

二、新建图像识别页面

在ui包下新建一个ImageInputActivity,对应的xml为activity_image_input.xml,创建好之后,再MainActivity页面中写一个按钮,点击之后进入刚才创建的这个图像识别页面。 修改activity_main.xml,在语音输入的下面加一个图像输入的按钮,代码如下:

进入到MainActivity中,新增一个方法jumpImageInput。

/** * 进入图像输入页面 */ public void jumpImageInput(View view) { gotoActivity(ImageInputActivity.class); }

下面来写ImageInputActivity页面的代码,写代码之前,先完成布局编写,修改activity_image_input,里面的代码如下:

然后回到ImageInputActivity页面,再写代码之前,先想一下这个页面要做什么?首先是获取百度的鉴权Token,然后进行图片识别,最后进行物品的垃圾分类。那么就需要三次网络请求,这里需要重写一个订阅。

三、网络订阅

这里需要增加一个网络访问地址,因为使用的是百度的API,而本身有一个天行的API地址,这里需要对两个地址进行一个控制。打开NetworkApi,在里面增加如下方法。

/** * 修改访问地址 * @param type */ private static void getBaseUrl(int type) { switch (type) { case 0://天行API地址 mBaseUrl = "http://api.tianapi.com"; break; case 1://百度SDK地址 mBaseUrl = "https://aip.baidubce.com"; break; default: break; } }

这个方法根据传入类型的不同,使用不同的网络地址,之前写博客时疏忽了,写的嗨了,漏掉了这一部分,现在补上。然后在createService方法中增加一个type参数,之后调用getBaseUrl方法获取访问地址。 在这里插入图片描述 现在你的这个createService方法改动了,那么其他调用了这个方法的地方也要做相应的改动,比如之前在做文字输入进行垃圾分类识别时,TextContract中的调用,之前是没有type的,现在你加一个0就可以了,0表示就是访问天行API。 在这里插入图片描述 其他的地方记也要修改,否则会报错的。改好之后,就可以来写这个图像识别到的订阅器了,如下:

在contract包下新建一个ImageContract类,里面的代码如下:

package com.llw.goodtrash.contract; import android.annotation.SuppressLint; import com.llw.goodtrash.api.ApiService; import com.llw.goodtrash.model.GetDiscernResultResponse; import com.llw.goodtrash.model.GetTokenResponse; import com.llw.goodtrash.model.TrashResponse; import com.llw.mvplibrary.base.BasePresenter; import com.llw.mvplibrary.base.BaseView; import com.llw.mvplibrary.network.NetworkApi; import com.llw.mvplibrary.network.observer.BaseObserver; import static com.llw.goodtrash.utils.Constant.*; /** * 图像输入页面访问网络 * * @author llw * @date 2021/3/30 15:28 */ public class ImageContract { public static class ImagePresenter extends BasePresenter { /** * 获取鉴权Token */ @SuppressLint("CheckResult") public void getToken() { ApiService service = NetworkApi.createService(ApiService.class, 1); service.getToken(GRANT_TYPE, API_KEY, API_SECRET) .compose(NetworkApi.applySchedulers(new BaseObserver() { @Override public void onSuccess(GetTokenResponse getTokenResponse) { if (getView() != null) { getView().getTokenResponse(getTokenResponse); } } @Override public void onFailure(Throwable e) { if (getView() != null) { getView().getTokenFailed(e); } } })); } /** * 获取图像识别结果 * * @param token 鉴权Token * @param image 图片base64 * @param url 网络图片url */ @SuppressLint("CheckResult") public void getDiscernResult(String token, String image, String url) { ApiService service = NetworkApi.createService(ApiService.class, 1); service.getDiscernResult(token, image, url) .compose(NetworkApi.applySchedulers(new BaseObserver() { @Override public void onSuccess(GetDiscernResultResponse getTokenResponse) { if (getView() != null) { getView().getDiscernResultResponse(getTokenResponse); } } @Override public void onFailure(Throwable e) { if (getView() != null) { getView().getDiscernResultFailed(e); } } })); } /** * 搜索物品 * * @param word 物品名 */ @SuppressLint("CheckResult") public void searchGoods(String word) { ApiService service = NetworkApi.createService(ApiService.class, 0); service.searchGoods(word).compose(NetworkApi.applySchedulers(new BaseObserver() { @Override public void onSuccess(TrashResponse groupResponse) { if (getView() != null) { getView().getSearchResponse(groupResponse); } } @Override public void onFailure(Throwable e) { if (getView() != null) { getView().getSearchResponseFailed(e); } } })); } } public interface ImageView extends BaseView { /** * 获取鉴权Token * * @param response GetTokenResponse */ void getTokenResponse(GetTokenResponse response); /** * 获取鉴权Token异常返回 * * @param throwable 异常 */ void getTokenFailed(Throwable throwable); /** * 获取图像识别结果 * * @param response GetDiscernResultResponse */ void getDiscernResultResponse(GetDiscernResultResponse response); /** * 获取图像识别结果失败 * * @param throwable 异常 */ void getDiscernResultFailed(Throwable throwable); /** * 搜索物品返回 * * @param response TrashResponse */ void getSearchResponse(TrashResponse response); /** * 搜索物品异常返回 * * @param throwable 异常 */ void getSearchResponseFailed(Throwable throwable); } }

鉴权方法中的几个全局变量在Constant中定义,

/** * 鉴权Token */ public static final String TOKEN = "accessToken"; /** * 获取Token的时间 */ public static final String GET_TOKEN_TIME = "getTokenTime"; /** * Token有效期 */ public static final String TOKEN_VALID_PERIOD = "tokenValidPeriod"; /** * 百度鉴权认证参数值 */ public static final String GRANT_TYPE = "client_credentials"; /** * 百度图像识别 APP ID GoodTrash */ public static final String APP_ID = "23943795"; /** * 百度图像识别 APP Key GoodTrash */ public static final String API_KEY = "PAUCX7vSAd4ZBwv897GAfhEQ";

请注意,这里的值是我在百度开放平台上注册应用时生成的,请替换为自己的。

下面回到ImageInputActivity,修改代码后如下:

package com.llw.goodtrash.ui; import android.os.Bundle; import com.llw.goodtrash.R; import com.llw.goodtrash.contract.ImageContract; import com.llw.goodtrash.model.GetDiscernResultResponse; import com.llw.goodtrash.model.GetTokenResponse; import com.llw.goodtrash.model.TrashResponse; import com.llw.mvplibrary.mvp.MvpActivity; /** * 图像输入物品进行垃圾分类 * * @author llw * @date 2021/4/7 11:04 */ public class ImageInputActivity extends MvpActivity implements ImageContract.ImageView { @Override public void initData(Bundle savedInstanceState) { } @Override public int getLayoutId() { return R.layout.activity_image_input; } @Override protected ImageContract.ImagePresenter createPresenter() { return new ImageContract.ImagePresenter(); } @Override public void getTokenResponse(GetTokenResponse response) { } @Override public void getTokenFailed(Throwable throwable) { } @Override public void getDiscernResultResponse(GetDiscernResultResponse response) { } @Override public void getDiscernResultFailed(Throwable throwable) { } @Override public void getSearchResponse(TrashResponse response) { } @Override public void getSearchResponseFailed(Throwable throwable) { } }

这里使用了MVP,通过P来处理M和V,三个网络请求对应六个返回,下面进行页面的初始化

四、编写页面代码

先声明一些变量

private static final String TAG = "ImageInputActivity"; /** * 打开相册 */ private static final int OPEN_ALBUM_CODE = 100; /** * 打开相机 */ private static final int TAKE_PHOTO_CODE = 101; /** * 鉴权Toeken */ private String accessToken; private Toolbar toolbar; private ImageView ivPicture; private EditText etImageUrl; private LinearLayout layRecognitionResult,layClassificationResult; private RecyclerView rvRecognitionResult,rvClassificationResult; private RxPermissions rxPermissions; private File outputImage;

然后新增一个initView的方法。

/** * 初始化 */ private void initView() { toolbar = findViewById(R.id.toolbar); ivPicture = findViewById(R.id.iv_picture); etImageUrl = findViewById(R.id.et_image_url); findViewById(R.id.btn_web_picture).setOnClickListener(this); findViewById(R.id.btn_open_album).setOnClickListener(this); findViewById(R.id.btn_take_photo).setOnClickListener(this); layRecognitionResult = findViewById(R.id.lay_recognition_result); layClassificationResult = findViewById(R.id.lay_classification_result); rvRecognitionResult = findViewById(R.id.rv_recognition_result); rvClassificationResult = findViewById(R.id.rv_classification_result); //设置页面状态栏 setStatubar(this, R.color.white, true); back(toolbar, true); rxPermissions = new RxPermissions(this); }

然后在initData中调用它。

@Override public void initData(Bundle savedInstanceState) { initView(); }

然后继承控件的点击监听回调 在这里插入图片描述 重写onClick方法。

@Override public void onClick(View v) { switch (v.getId()) { case R.id.btn_web_picture://网络图片 break; case R.id.btn_open_album://相册图片 break; case R.id.btn_take_photo://拍照图片 break; default: break; } }

由于Token存在有效期的关系,因此不需要每一次都去获取,所以可以在第一次获取之后存入缓存,只要Token在有效期内都可以直接从缓存中获取,这样就可以少请求一次网络。 下面先写一个缓存的工具类。 在utils包下新增一个SPUtils类,里面的代码如下:

package com.llw.goodtrash.utils; import android.content.Context; import android.content.SharedPreferences; /** * SharedPreferences工具类 * * @author llw */ public class SPUtils { private static final String NAME = "config"; public static void putBoolean(String key, boolean value, Context context) { SharedPreferences sp = context.getSharedPreferences(NAME, Context.MODE_PRIVATE); sp.edit().putBoolean(key, value).commit(); } public static boolean getBoolean(String key, boolean defValue, Context context) { SharedPreferences sp = context.getSharedPreferences(NAME, Context.MODE_PRIVATE); return sp.getBoolean(key, defValue); } public static void putString(String key, String value, Context context) { SharedPreferences sp = context.getSharedPreferences(NAME, Context.MODE_PRIVATE); sp.edit().putString(key, value).commit(); } public static String getString(String key, String defValue, Context context) { if (context != null) { SharedPreferences sp = context.getSharedPreferences(NAME, Context.MODE_PRIVATE); return sp.getString(key, defValue); } return ""; } public static void putInt(String key, int value, Context context) { SharedPreferences sp = context.getSharedPreferences(NAME, Context.MODE_PRIVATE); sp.edit().putInt(key, value).commit(); } public static int getInt(String key, int defValue, Context context) { SharedPreferences sp = context.getSharedPreferences(NAME, Context.MODE_PRIVATE); return sp.getInt(key, defValue); } public static void putLong(String key, long value, Context context) { SharedPreferences sp = context.getSharedPreferences(NAME, Context.MODE_PRIVATE); sp.edit().putLong(key, value).commit(); } public static long getLong(String key, long defValue, Context context) { SharedPreferences sp = context.getSharedPreferences(NAME, Context.MODE_PRIVATE); return sp.getLong(key, defValue); } public static void remove(String key, Context context) { SharedPreferences sp = context.getSharedPreferences(NAME, Context.MODE_PRIVATE); sp.edit().remove(key).commit(); } }

然后在ImageInputActivity中写一个获取Token的方法,代码如下:

/** * 获取鉴权Token */ private String getAccessToken() { String token = SPUtils.getString(Constant.TOKEN, null, this); if (token == null) { //访问API获取接口 mPresenter.getToken(); } else { //则判断Token是否过期 if (isTokenExpired()) { //过期 mPresenter.getToken(); } else { accessToken = token; } } return accessToken; }

这里你的isTokenExpired()方法会报红,这是一个用来判断Token是否过期的方法。里面的代码如下:

/** * Token是否过期 * * @return */ private boolean isTokenExpired() { //获取Token的时间 long getTokenTime = SPUtils.getLong(Constant.GET_TOKEN_TIME, 0, this); //获取Token的有效时间 long effectiveTime = SPUtils.getLong(Constant.TOKEN_VALID_PERIOD, 0, this); //获取当前系统时间 long currentTime = System.currentTimeMillis() / 1000; return (currentTime - getTokenTime) >= effectiveTime; }

刚才在获取Token方法中,通过mPresenter.getToken();发起了一个网络请求,返回的结果有成功和失败,成功后会有Token返回,失败了会有失败原因返回。

修改如下方法:

/** * 获取鉴权Token成功返回 * @param response GetTokenResponse */ @Override public void getTokenResponse(GetTokenResponse response) { if (response != null) { //鉴权Token accessToken = response.getAccess_token(); //过期时间 秒 long expiresIn = response.getExpires_in(); //当前时间 秒 long currentTimeMillis = System.currentTimeMillis() / 1000; //放入缓存 SPUtils.putString(Constant.TOKEN, accessToken, this); SPUtils.putLong(Constant.GET_TOKEN_TIME, currentTimeMillis, this); SPUtils.putLong(Constant.TOKEN_VALID_PERIOD, expiresIn, this); } else { showMsg("Token为null"); } } /** * 获取Token失败返回 * @param throwable 异常 */ @Override public void getTokenFailed(Throwable throwable) { Log.d(TAG, "Token获取失败:"+throwable.toString()); }

我已经写了注释了,那么你就知道这个方法是做什么的了。

五、识别网络图片

我的想法是当我点击这个网络图片的按钮时,页面出现一个输入框,当我输入完成之后,点击键盘的回车直接识别,虽后隐藏这个输入框,嗯,就是这样。 首先来写点击网络图片时的业务逻辑代码。

case R.id.btn_web_picture://网络图片 etImageUrl.setVisibility(View.VISIBLE); etImageUrl.setOnKeyListener((view, keyCode, keyEvent) -> { if (keyCode == KeyEvent.KEYCODE_ENTER && keyEvent.getAction() == KeyEvent.ACTION_UP) { String webImageUrl = etImageUrl.getText().toString().trim(); String defaultWebImageUrl = "https://bce-baiyu.cdn.bcebos.com/14ce36d3d539b6004ef2e45fe050352ac65cb71e.jpeg"; String imageUrl = "".equals(webImageUrl) ?defaultWebImageUrl : webImageUrl; //识别网络图片Url showLoadingDialog(); Glide.with(context).load(imageUrl).into(ivPicture); mPresenter.getDiscernResult(accessToken,null,imageUrl); etImageUrl.setVisibility(View.GONE); } return false; }); break;

在这里发起了一个图片识别的请求,下面来看返回的方法处理。

/** * 图片识别成功返回 * @param response GetDiscernResultResponse */ @Override public void getDiscernResultResponse(GetDiscernResultResponse response) { if(response == null){ hideLoadingDialog(); showMsg("未获得相应的识别结果"); return; } ivPicture.setVisibility(View.VISIBLE); List result = response.getResult(); if (result != null && result.size() > 0) { //显示识别结果 showDiscernResult(result); } else { hideLoadingDialog(); showMsg("未获得相应的识别结果"); } } /** * 图片识别成功失败 * @param throwable 异常 */ @Override public void getDiscernResultFailed(Throwable throwable) { Log.d(TAG, "图片识别失败:"+throwable.toString()); }

返回成功之后,如果数据不为空则显示要识别的图片,然后通过列表展示识别结果数据, 首先得有一个识别的结果列表item布局,item_distinguish_result_rv.xml,代码如下:

下面写适配器代码,在adapter下新建一个DiscernResultAdapter类,代码如下:

package com.llw.goodtrash.adapter; import androidx.annotation.Nullable; import com.chad.library.adapter.base.BaseQuickAdapter; import com.chad.library.adapter.base.BaseViewHolder; import com.llw.goodtrash.R; import com.llw.goodtrash.model.GetDiscernResultResponse; import java.util.List; /** * 识别结果列表适配器 * @author llw */ public class DiscernResultAdapter extends BaseQuickAdapter { public DiscernResultAdapter(int layoutResId, @Nullable List data) { super(layoutResId, data); } @Override protected void convert(BaseViewHolder helper, GetDiscernResultResponse.ResultBean item) { helper.setText(R.id.tv_keyword,item.getKeyword()) .setText(R.id.tv_root,item.getRoot()) .setText(R.id.tv_score,String.valueOf(item.getScore())) .addOnClickListener(R.id.item_distinguish_rv); } }

适配器和列表item都写好了,下面回到ImageInputActivity中新增方法showDiscernResult(),代码如下:

/** * 显示识别的结果列表 * * @param result */ private void showDiscernResult(List result) { DiscernResultAdapter adapter = new DiscernResultAdapter(R.layout.item_distinguish_result_rv, result); rvRecognitionResult.setLayoutManager(new LinearLayoutManager(this)); rvRecognitionResult.setAdapter(adapter); //隐藏加载 hideLoadingDialog(); //显示弹窗 layRecognitionResult.setVisibility(View.VISIBLE); layClassificationResult.setVisibility(View.GONE); }

下面来运行一下: 在这里插入图片描述 不一定第一次就能识别出来,看你的人品。下面识别相册图片

六、识别相册图片

下面写点击这个相册图片按钮的业务逻辑,如下:

case R.id.btn_open_album://相册图片 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { rxPermissions.request( Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE) .subscribe(grant -> { if (grant) { //获得权限 openAlbum(); } else { showMsg("未获取到权限"); } }); } else { openAlbum(); } break;

打开相册的openAlbum()方法,代码如下:

/** * 打开相册 */ private void openAlbum() { Intent intent = new Intent(); intent.setAction(Intent.ACTION_PICK); intent.setType("image/*"); startActivityForResult(intent, OPEN_ALBUM_CODE); }

下面就是从相册返回的处理

@Override protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { super.onActivityResult(requestCode, resultCode, data); if (resultCode == RESULT_OK) { showLoadingDialog(); if (requestCode == OPEN_ALBUM_CODE) { //打开相册返回 String[] filePathColumns = {MediaStore.Images.Media.DATA}; final Uri imageUri = Objects.requireNonNull(data).getData(); Cursor cursor = getContentResolver().query(imageUri, filePathColumns, null, null, null); cursor.moveToFirst(); int columnIndex = cursor.getColumnIndex(filePathColumns[0]); //获取图片路径 String imagePath = cursor.getString(columnIndex); cursor.close(); //识别 localImageDiscern(imagePath); } } else { showMsg("什么都没有"); } }

通过相册图片获取图片的路径,通过localImageDiscern方法将这个路径去转字节,再转base64。代码如下:

/** * 本地图片识别 */ private void localImageDiscern(String imagePath) { try { String token = getAccessToken(); //通过图片路径显示图片 Glide.with(this).load(imagePath).into(ivPicture); //按字节读取文件 byte[] imgData = FileUtil.readFileByBytes(imagePath); //字节转Base64 String imageBase64 = Base64Util.encode(imgData); //本地图片识别 mPresenter.getDiscernResult(token, imageBase64, null); } catch (IOException e) { e.printStackTrace(); } }

这里面还有两个工具类FileUtil和Base64Util。下面在utils包下新建一个FileUtil类,里面的代码如下:

package com.llw.goodtrash.utils; import java.io.BufferedInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; /** * 文件读取工具类 */ public class FileUtil { /** * 读取文件内容,作为字符串返回 */ public static String readFileAsString(String filePath) throws IOException { File file = new File(filePath); if (!file.exists()) { throw new FileNotFoundException(filePath); } if (file.length() > 1024 * 1024 * 1024) { throw new IOException("File is too large"); } StringBuilder sb = new StringBuilder((int) (file.length())); // 创建字节输入流 FileInputStream fis = new FileInputStream(filePath); // 创建一个长度为10240的Buffer byte[] bbuf = new byte[10240]; // 用于保存实际读取的字节数 int hasRead = 0; while ( (hasRead = fis.read(bbuf)) > 0 ) { sb.append(new String(bbuf, 0, hasRead)); } fis.close(); return sb.toString(); } /** * 根据文件路径读取byte[] 数组 */ public static byte[] readFileByBytes(String filePath) throws IOException { File file = new File(filePath); if (!file.exists()) { throw new FileNotFoundException(filePath); } else { ByteArrayOutputStream bos = new ByteArrayOutputStream((int) file.length()); BufferedInputStream in = null; try { in = new BufferedInputStream(new FileInputStream(file)); short bufSize = 1024; byte[] buffer = new byte[bufSize]; int len1; while (-1 != (len1 = in.read(buffer, 0, bufSize))) { bos.write(buffer, 0, len1); } byte[] var7 = bos.toByteArray(); return var7; } finally { try { if (in != null) { in.close(); } } catch (IOException var14) { var14.printStackTrace(); } bos.close(); } } } }

然后是Base64Util类,代码如下:

package com.llw.goodtrash.utils; /** * Base64 工具类 */ public class Base64Util { private static final char last2byte = (char) Integer.parseInt("00000011", 2); private static final char last4byte = (char) Integer.parseInt("00001111", 2); private static final char last6byte = (char) Integer.parseInt("00111111", 2); private static final char lead6byte = (char) Integer.parseInt("11111100", 2); private static final char lead4byte = (char) Integer.parseInt("11110000", 2); private static final char lead2byte = (char) Integer.parseInt("11000000", 2); private static final char[] encodeTable = new char[]{'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'}; public Base64Util() { } public static String encode(byte[] from) { StringBuilder to = new StringBuilder((int) ((double) from.length * 1.34D) + 3); int num = 0; char currentByte = 0; int i; for (i = 0; i > 2); case 1: case 3: case 5: default: break; case 2: currentByte = (char) (from[i] & last6byte); break; case 4: currentByte = (char) (from[i] & last4byte); currentByte = (char) (currentByte 6); } break; case 6: currentByte = (char) (from[i] & last2byte); currentByte = (char) (currentByte 4); } } to.append(encodeTable[currentByte]); } } if (to.length() % 4 != 0) { for (i = 4 - to.length() % 4; i > 0; --i) { to.append("="); } } return to.toString(); } }

下面可以直接运行了。 在这里插入图片描述 下面该识别拍照图片

七、识别拍照图片

点击拍照图片按钮的业务逻辑代码,

case R.id.btn_take_photo://拍照图片 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { rxPermissions.request( Manifest.permission.CAMERA) .subscribe(grant -> { if (grant) { //获得权限 turnOnCamera(); } else { showMsg("未获取到权限"); } }); } else { turnOnCamera(); } break;

turnOnCamera是用来打开相机的方法,里面的代码如下:

/** * 打开相机 */ private void turnOnCamera() { SimpleDateFormat timeStampFormat = new SimpleDateFormat("HH_mm_ss"); String filename = timeStampFormat.format(new Date()); //创建File对象 outputImage = new File(getExternalCacheDir(), "takePhoto" + filename + ".jpg"); Uri imageUri; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { imageUri = FileProvider.getUriForFile(this, "com.llw.goodtrash.fileprovider", outputImage); } else { imageUri = Uri.fromFile(outputImage); } //打开相机 Intent intent = new Intent(); intent.setAction(MediaStore.ACTION_IMAGE_CAPTURE); intent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri); startActivityForResult(intent, TAKE_PHOTO_CODE); }

然后同样要在onActivityResult方法中添加相机返回的分支判断。

else if (requestCode == TAKE_PHOTO_CODE) { //拍照返回 String imagePath = outputImage.getAbsolutePath(); //识别 localImageDiscern(imagePath); }

添加位置如下: 在这里插入图片描述 下面就可以直接运行了。 在这里插入图片描述 OK,现在就来进行垃圾分类了。

八、垃圾分类

刚才通过图像识别已经拿到物品结果了,下面通过点击这个物品去进行垃圾分类。添加adapter的适配器,在showDiscernResult方法中添加如下代码:

//添加列表Item点击 adapter.setOnItemChildClickListener((adapter1, view, position) -> { showLoadingDialog(); //垃圾分类 mPresenter.searchGoods(result.get(position).getKeyword()); });

添加位置如下: 在这里插入图片描述 下面进行搜索物品的返回数据处理。

/** * 搜索物品进行垃圾分类成功返回 * * @param response TrashResponse */ @Override public void getSearchResponse(TrashResponse response) { //请求成功 进行数据的渲染 if (response.getCode() == Constant.SUCCESS_CODE) { List result = response.getNewslist(); if (result != null && result.size() > 0) { //显示分类结果 showClassificationResult(result); } else { showMsg("触及到了知识盲区"); } } else { hideLoadingDialog(); showMsg(response.getMsg()); } } @Override public void getSearchResponseFailed(Throwable throwable) { Log.d(TAG, "搜索物品进行垃圾分类失败:" + throwable.toString()); }

通过showClassificationResult方法进行物品垃圾分类结果显示,代码如下:

/** * 显示物品分类结果 * @param result */ private void showClassificationResult(List result) { SearchGoodsAdapter adapter = new SearchGoodsAdapter(R.layout.item_search_rv,result); rvClassificationResult.setLayoutManager(new LinearLayoutManager(context)); rvClassificationResult.setAdapter(adapter); //隐藏加载 hideLoadingDialog(); //显示弹窗 layClassificationResult.setVisibility(View.VISIBLE); }

下面运行一下: 在这里插入图片描述 下面再优化一下,就是让数据显示之后,滑动到屏幕底部, 修改activity_image_input.xml 给NestedScrollView控件添加一个id。

android:id="@+id/nestedScrollView"

然后进入ImageInputActivity中,初始化。 在这里插入图片描述 写一个方法

/** * 滑动到屏幕底部 */ private void scrollToEnd() { nestedScrollView.post(() -> { nestedScrollView.fullScroll(View.FOCUS_DOWN);//滚到底部 //nestedScrollView.fullScroll(ScrollView.FOCUS_UP);//滚到顶部 }); }

在显示列表数据时后调用,有两处。 第一处,图像识别的结果列表显示之后 在这里插入图片描述 第二处,显示物品垃圾分类结果显示之后 在这里插入图片描述 运行一下: 在这里插入图片描述 那么这个页面的功能就写完了。 下一篇:垃圾分类APP(五)垃圾分类新闻展示

九、源码

GitHub源码地址如下:GoodTrash



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3