微信朋友圈的图片上传,多图上传怎么去撸才合适?我们一起来实现吧!
图片上传是非常常见的功能,而多图上传在大多数应用中也是非常常见的,比如微信的朋友圈,微博的动态,都是有九宫格图片的,那这里肯定涉及了多图上传,所以今天我们来一起撸一下,怎么去思考这个实现逻辑!
这里我想到的思路是比较简单的,首先,我们有一个按钮,按钮是上传图片,点击之后弹出某个界面进行图片的选择,一般是九张图片或者十二张,选完之后就直接上传了,大致的流程应该是这个样子,那我们首先来写个按钮
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="10dp">
<Button
android:id="@+id/btnAddPhoto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="上传图片"/>
</LinearLayout>
他只是一个主页,我们只要实现它的点击事件就好了,点击之后跳转到我们的上传图片的Activcity
MainActivity
package com.liuguilin.uploadphotossample;
import android.content.Intent;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
public class MainActivity extends AppCompatActivity {
private Button btnAddPhoto;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//点击事件
findViewById(R.id.btnAddPhoto).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
startActivity(new Intent(MainActivity.this,UploadPhotoActivity.class));
}
});
}
}
这些都是可以一笔带过的,真正的逻辑全部都在这个UploadPhotoActivity,我们用GridView显示图片,并且进行多选,下面有一个按钮负责显示已选图片的数量以及完成上传的功能
activity_upload.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_horizontal"
android:orientation="vertical">
<GridView
android:id="@+id/mGradView"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:numColumns="3"/>
<Button
android:id="@+id/btnOk"
android:layout_width="200dp"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:background="@color/colorAccent"
android:text="2/12 完成"
android:textColor="@android:color/white"/>
</LinearLayout>
我们现在就要分析我们怎么去实现了,这个GridView肯定是要写的,但是我们首先得要拿到我们的图片,图片怎么拿?肯定是看相册的源码来分析他是怎么去拿的,这里呢,我们使用的是ContentResolver内容访问者,我们查看下源码,我们主要还是看MediaProvider这个项目
我们只要看他最先的一段静态块
static
{
URI_MATCHER.addURI("media", "*/images/media", IMAGES_MEDIA);
URI_MATCHER.addURI("media", "*/images/media/#", IMAGES_MEDIA_ID);
URI_MATCHER.addURI("media", "*/images/thumbnails", IMAGES_THUMBNAILS);
URI_MATCHER.addURI("media", "*/images/thumbnails/#", IMAGES_THUMBNAILS_ID);
URI_MATCHER.addURI("media", "*/audio/media", AUDIO_MEDIA);
URI_MATCHER.addURI("media", "*/audio/media/#", AUDIO_MEDIA_ID);
URI_MATCHER.addURI("media", "*/audio/media/#/genres", AUDIO_MEDIA_ID_GENRES);
URI_MATCHER.addURI("media", "*/audio/media/#/genres/#", AUDIO_MEDIA_ID_GENRES_ID);
URI_MATCHER.addURI("media", "*/audio/media/#/playlists", AUDIO_MEDIA_ID_PLAYLISTS);
URI_MATCHER.addURI("media", "*/audio/media/#/playlists/#", AUDIO_MEDIA_ID_PLAYLISTS_ID);
URI_MATCHER.addURI("media", "*/audio/genres", AUDIO_GENRES);
URI_MATCHER.addURI("media", "*/audio/genres/#", AUDIO_GENRES_ID);
URI_MATCHER.addURI("media", "*/audio/genres/#/members", AUDIO_GENRES_ID_MEMBERS);
URI_MATCHER.addURI("media", "*/audio/genres/all/members", AUDIO_GENRES_ALL_MEMBERS);
URI_MATCHER.addURI("media", "*/audio/playlists", AUDIO_PLAYLISTS);
URI_MATCHER.addURI("media", "*/audio/playlists/#", AUDIO_PLAYLISTS_ID);
URI_MATCHER.addURI("media", "*/audio/playlists/#/members", AUDIO_PLAYLISTS_ID_MEMBERS);
URI_MATCHER.addURI("media", "*/audio/playlists/#/members/#", AUDIO_PLAYLISTS_ID_MEMBERS_ID);
URI_MATCHER.addURI("media", "*/audio/artists", AUDIO_ARTISTS);
URI_MATCHER.addURI("media", "*/audio/artists/#", AUDIO_ARTISTS_ID);
URI_MATCHER.addURI("media", "*/audio/artists/#/albums", AUDIO_ARTISTS_ID_ALBUMS);
URI_MATCHER.addURI("media", "*/audio/albums", AUDIO_ALBUMS);
URI_MATCHER.addURI("media", "*/audio/albums/#", AUDIO_ALBUMS_ID);
URI_MATCHER.addURI("media", "*/audio/albumart", AUDIO_ALBUMART);
URI_MATCHER.addURI("media", "*/audio/albumart/#", AUDIO_ALBUMART_ID);
URI_MATCHER.addURI("media", "*/audio/media/#/albumart", AUDIO_ALBUMART_FILE_ID);
URI_MATCHER.addURI("media", "*/video/media", VIDEO_MEDIA);
URI_MATCHER.addURI("media", "*/video/media/#", VIDEO_MEDIA_ID);
URI_MATCHER.addURI("media", "*/video/thumbnails", VIDEO_THUMBNAILS);
URI_MATCHER.addURI("media", "*/video/thumbnails/#", VIDEO_THUMBNAILS_ID);
URI_MATCHER.addURI("media", "*/media_scanner", MEDIA_SCANNER);
URI_MATCHER.addURI("media", "*/fs_id", FS_ID);
URI_MATCHER.addURI("media", "*/version", VERSION);
URI_MATCHER.addURI("media", "*/mtp_connected", MTP_CONNECTED);
URI_MATCHER.addURI("media", "*", VOLUMES_ID);
URI_MATCHER.addURI("media", null, VOLUMES);
// Used by MTP implementation
URI_MATCHER.addURI("media", "*/file", FILES);
URI_MATCHER.addURI("media", "*/file/#", FILES_ID);
URI_MATCHER.addURI("media", "*/object", MTP_OBJECTS);
URI_MATCHER.addURI("media", "*/object/#", MTP_OBJECTS_ID);
URI_MATCHER.addURI("media", "*/object/#/references", MTP_OBJECT_REFERENCES);
/**
* @deprecated use the ‘basic‘ or ‘fancy‘ search Uris instead
*/
URI_MATCHER.addURI("media", "*/audio/" + SearchManager.SUGGEST_URI_PATH_QUERY,
AUDIO_SEARCH_LEGACY);
URI_MATCHER.addURI("media", "*/audio/" + SearchManager.SUGGEST_URI_PATH_QUERY + "/*",
AUDIO_SEARCH_LEGACY);
// used for search suggestions
URI_MATCHER.addURI("media", "*/audio/search/" + SearchManager.SUGGEST_URI_PATH_QUERY,
AUDIO_SEARCH_BASIC);
URI_MATCHER.addURI("media", "*/audio/search/" + SearchManager.SUGGEST_URI_PATH_QUERY +
"/*", AUDIO_SEARCH_BASIC);
// used by the music app‘s search activity
URI_MATCHER.addURI("media", "*/audio/search/fancy", AUDIO_SEARCH_FANCY);
URI_MATCHER.addURI("media", "*/audio/search/fancy/*", AUDIO_SEARCH_FANCY);
}
这段代码块就是我们访问系统数据库索要获取任意数据的URI,而我们访问图片的URI是
URI_MATCHER.addURI("media", "*/images/media", IMAGES_MEDIA);
不过既然是内容提供者,google也是封装好了一些方法供我们使用
/**
* 初始化数据
*/
private void initData() {
//获取手机相册图片 访问本机数据库
ContentResolver mContentResolver = getContentResolver();
//图片数据的url(外部存储)
Uri uri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
//查询
Cursor cursor = mContentResolver.query(uri,null,null,null,null);
//遍历
while (cursor.moveToNext()){
PhotoBean bean = new PhotoBean();
//获取路径
String path = cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media.DATA));
//设置路径
bean.setPath(path);
//默认fasle
bean.setSelect(false);
//保存
mList.add(bean);
}
}
OK,当我们拿到这些相册的图片肯定是要去存储,那我们怎么去存储?一般都是用个实体对象的
PhotoBean
package com.liuguilin.uploadphotossample;
/*
* 项目名: UploadPhotosSample
* 包名: com.liuguilin.uploadphotossample
* 文件名: PhotoBean
* 创建者: LGL
* 创建时间: 2016/8/31 13:14
* 描述: 图片存储对象
*/
import android.graphics.Bitmap;
public class PhotoBean {
//路径
private String path;
//是否选择
private boolean isSelect;
//位图转换
private Bitmap bitmap;
public String getPath() {
return path;
}
public void setPath(String path) {
this.path = path;
}
public boolean isSelect() {
return isSelect;
}
public void setSelect(boolean select) {
isSelect = select;
}
public Bitmap getBitmap() {
return bitmap;
}
public void setBitmap(Bitmap bitmap) {
this.bitmap = bitmap;
}
}
OK,现在可以专心的来写我们的Adapter,也就是数据适配器了
GridAdapter
package com.liuguilin.uploadphotossample;
/*
* 项目名: UploadPhotosSample
* 包名: com.liuguilin.uploadphotossample
* 文件名: GridAdapter
* 创建者: LGL
* 创建时间: 2016/8/31 13:32
* 描述: 数据适配器
*/
import android.content.Context;
import android.graphics.Bitmap;
import android.os.AsyncTask;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import java.util.List;
public class GridAdapter extends BaseAdapter {
//数据
private List<PhotoBean> mList;
//布局加载器
private LayoutInflater mInflater;
//实体类
private PhotoBean bean;
//上下文
private Context mContext;
//屏幕宽高
private int w, h;
/**
* @param mContext
* @param mList
*/
public GridAdapter(Context mContext, List<PhotoBean> mList) {
this.mContext = mContext;
this.mList = mList;
//系統服務
mInflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
getPhoneWH();
}
/**
* 获取手机屏幕的宽高
*/
private void getPhoneWH() {
w = mContext.getResources().getDisplayMetrics().widthPixels;
h = mContext.getResources().getDisplayMetrics().heightPixels;
}
@Override
public int getCount() {
return mList.size();
}
@Override
public Object getItem(int i) {
return mList.get(i);
}
@Override
public long getItemId(int i) {
return i;
}
@Override
public View getView(int i, View view, ViewGroup viewGroup) {
ViewHolder viewHolder = null;
if (view == null) {
viewHolder = new ViewHolder();
view = mInflater.inflate(R.layout.list_item, null);
//初始化
viewHolder.img = (ImageView) view.findViewById(R.id.img);
//设置图片最小宽高
viewHolder.img.setMinimumWidth(w / 3);
viewHolder.img.setMinimumHeight(h / 3);
viewHolder.img_select = (ImageView) view.findViewById(R.id.img_select);
view.setTag(viewHolder);
} else {
viewHolder = (ViewHolder) view.getTag();
}
//获取position
bean = mList.get(i);
//是否选中
if (bean.isSelect()) {
viewHolder.img_select.setVisibility(View.VISIBLE);
} else {
viewHolder.img_select.setVisibility(View.INVISIBLE);
}
//是否有图片
if (bean.getBitmap() == null) {
//图片加载,异步加载
new ImgTask().execute(bean.getPath(), String.valueOf(i));
} else {
//设置图片
viewHolder.img.setImageBitmap(bean.getBitmap());
}
return view;
}
/**
* 緩存
*/
static class ViewHolder {
private ImageView img;
private ImageView img_select;
}
/**
* 异步任务
*/
private class ImgTask extends AsyncTask<String, Void, Bitmap> {
/**
* 后台加载
*
* @param strings
* @return
*/
@Override
protected Bitmap doInBackground(String... strings) {
//图片路径
String path = strings[0];
//position
int position = Integer.parseInt(strings[1]);
//图片压缩
Bitmap bitmap = BitmapUtils.getScaleBitmapPath(mContext, path);
//设置图片
mList.get(position).setBitmap(bitmap);
return bitmap;
}
/**
* 刷新视图
*
* @param bitmap
*/
@Override
protected void onPostExecute(Bitmap bitmap) {
super.onPostExecute(bitmap);
//刷新
GridAdapter.this.notifyDataSetChanged();
}
}
}
我们在里面做了很多的事情,首选是ViewHolder的优化,然后就是异步加载图片了,接着获取屏幕的高宽去适配,当然,这里做了一个bitmap的工具类
BitmapUtils
package com.liuguilin.uploadphotossample;
/*
* 项目名: UploadPhotosSample
* 包名: com.liuguilin.uploadphotossample
* 文件名: BitmapUtils
* 创建者: LGL
* 创建时间: 2016/8/31 14:18
* 描述: 图片压缩处理
*/
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
public class BitmapUtils {
/**
* 本地图片压缩处理
*
* @param mContext 上下文
* @param path 路径
* @return
*/
public static Bitmap getScaleBitmapPath(Context mContext, String path) {
Bitmap bitmap;
int w;
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
bitmap = BitmapFactory.decodeFile(path, options);
w = options.outWidth;
if (w < 50) {
options.inSampleSize = w / 50;
options.inJustDecodeBounds = false;
bitmap = BitmapFactory.decodeFile(path, options);
} else {
bitmap = BitmapFactory.decodeFile(path);
}
return bitmap;
}
}
现在我们可以把我们的数据加载进去了
/**
* 初始化View
*/
private void initView() {
btnOk = (Button) findViewById(R.id.btnOk);
btnOk.setOnClickListener(this);
mGridView = (GridView) findViewById(R.id.mGridView);
//设置数据
adapter = new GridAdapter(this, mList);
mGridView.setAdapter(adapter);
}
这样,我们其实是可以看到加载的效果的,这就是相册实现的最基本原理了
到这里,基本上就成功了一半了,现在开始做点击了,只要点击图片,就显示勾选,那我们就要监听他的点击事件了
/**
* 点击事件
*/
mGridView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
count =0;
PhotoBean bean = mList.get(i);
bean.setSelect(!bean.isSelect());
//遍历
for (PhotoBean p : mList) {
//如果
if (p.isSelect()) {
count++;
}
}
//刷新
adapter.notifyDataSetChanged();
btnOk.setText(count + "/ 9 完成");
}
});
到这里,我们大致的模样是不是已经出来了,我们看下效果
现在只要点击做的就是上传了,我们怎么上传?其实很简单,我们只要在遍历的时候同时拿到路径就好了,所以我们的GradView的点击事件应该是这样写的
/**
* 点击事件
*/
mGridView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
count = 0;
mListPath.clear();
PhotoBean bean = mList.get(i);
bean.setSelect(!bean.isSelect());
//遍历
for (PhotoBean p : mList) {
//如果
if (p.isSelect()) {
count++;
mListPath.add(p.getPath());
}
}
//刷新
adapter.notifyDataSetChanged();
btnOk.setText(count + "/ 9 完成");
}
});
而我们的按钮点击事件
/**
* 点击事件
*
* @param view
*/
@Override
public void onClick(View view) {
switch (view.getId()) {
//上传图片
case R.id.btnOk:
finish();
Toast.makeText(this, mListPath.toString(), Toast.LENGTH_LONG).show();
break;
}
}
我就直接Toast了,因为我们有路径了,只要往服务器一扔就完事了,对吧,这里就推荐使用RxVolley了,很方便
//post请求简洁版实现
HttpParams params = new HttpParams();
//文件上传
params.put("image", new File("path"))
RxVolley.post(url, params, new HttpCallback() {
@Override
public void onSuccess(String t) {
Loger.debug("请求到的数据:" + t);
}
});
OK,我们来最后看一遍效果图
记得在清单文件里添加一个小权限哦!
好的,这篇博客就到这里了,每次写博客都写到深更半夜,太痛苦了!!!