需求:近段时间公司有要求写一个类似于微信发送图片时,用来选择照片的一个图片浏览器,本来想在网上找一个直接拿来用,找寻无果,只能自己写了。相信有很多网页也有这样的需求,这里我将写好的源码打包成library工程分享给大家!!
转载请注明出处:http://blog.csdn.net/a740169405/article/details/41622025
说明:
①本来打算自己写图片异步加载代码,后来因为赶时间,就改成直接引用开源框架universal-image-loader,一个 android上知名度很高的图片加载框架。
②预览图片的时候,使用到了一个开源框架,忘了叫什么啦,只记得包名叫polites.android,这里直接引入了源 码,对不住作者了 - _ - 。
③为了兼容低版本,library以及测试项目的开发平台均为android2.3.3。
④点击查看照片时,为了防止手机上照片太对,使用bundle在activity之间传递会导致崩溃,所以使用进入预览 界面之后重新从本地读取图片信息。
⑤项目中使用了一个PhotoModel实体类,用来存放照片的路径信息,以及照片被选中的状态。
简单入门:
大家只需要引入library工程,并在清单文件中注册两个activity以及加入读取SD卡权限即可,使用例子以及源码放在文章最后
功能介绍:
大致实现了图片选择、预览、切换相册、拍照。并将选中的照片信息返回。
预览效果:
照片选择
预览
相册选择
关键实现:
〇:首先要说明的是如何使用我的源码,引入library,并在你自己的清单文件中加入读取SD卡权限,以及注册两个activity。
- <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
- <activity
- android:name="com.photoselector.ui.PhotoSelectorActivity"
- android:label="图片选择" >
- </activity>
- <activity
- android:name="com.photoselector.ui.PhotoPreviewActivity"
- android:label="图片预览" >
- </activity>
①首先,一进入照片选择界面,需要将最近照片显示出来,使用ContentResolver查询最近照片,这里我采用了异步加载的方式获取数据,并调用主界面的回调函数对适配器adapter进行更新。
说明:过滤掉小于10kb的照片
- /** 获取最近照片列表 */
- public List<PhotoModel> getCurrent() {
- Cursor cursor = resolver.query(Media.EXTERNAL_CONTENT_URI, new String[] { ImageColumns.DATA,
- ImageColumns.DATE_ADDED, ImageColumns.SIZE }, null, null, ImageColumns.DATE_ADDED);
- if (cursor == null || !cursor.moveToNext())
- return new ArrayList<PhotoModel>();
- List<PhotoModel> photos = new ArrayList<PhotoModel>();
- cursor.moveToLast();
- do {
- if (cursor.getLong(cursor.getColumnIndex(ImageColumns.SIZE)) > 1024 * 10) {
- PhotoModel photoModel = new PhotoModel();
- photoModel.setOriginalPath(cursor.getString(cursor.getColumnIndex(ImageColumns.DATA)));
- photos.add(photoModel);
- }
- } while (cursor.moveToPrevious());
- return photos;
- }
- public void getReccent(final OnLocalReccentListener listener) {
- final Handler handler = new Handler() {
- @SuppressWarnings("unchecked")
- @Override
- public void handleMessage(Message msg) {
- listener.onPhotoLoaded((List<PhotoModel>) msg.obj);
- }
- };
- new Thread(new Runnable() {
- @Override
- public void run() {
- List<PhotoModel> photos = albumController.getCurrent();
- Message msg = new Message();
- msg.obj = photos;
- handler.sendMessage(msg);
- }
- }).start();
- }
- private OnLocalReccentListener reccentListener = new OnLocalReccentListener() {
- @Override
- public void onPhotoLoaded(List<PhotoModel> photos) {
- if (tvAlbum.getText().equals(RECCENT_PHOTO))
- photos.add(0, new PhotoModel());
- photoAdapter.update(photos);
- gvPhotos.smoothScrollToPosition(0); // 滚动到顶端
- reset();
- }
- };
②在图片选择界面,新建一个ArrayList集合变量selected用来存放当前选中的照片信息。
接着将获取到的照片显示在GridView上,这里我对GridView的每一个Item进行了封装,大致就是一个ImageView上面浮着一个CheckBox。在每一个CheckBox状态改变时,调用回调函数往集合中插入当前选中照片的信息。
- @Override
- public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
- if (!isCheckAll) {
- listener.onCheckedChanged(photo, buttonView, isChecked); // 调用主界面回调函数
- }
- // 让图片变暗或者变亮
- if (isChecked) {
- setDrawingable();
- ivPhoto.setColorFilter(Color.GRAY, PorterDuff.Mode.MULTIPLY);
- } else {
- ivPhoto.clearColorFilter();
- }
- photo.setChecked(isChecked);
- }
- private ArrayList<PhotoModel> selected;
- @Override
- /** 照片选中状态改变之后 */
- public void onCheckedChanged(PhotoModel photoModel, CompoundButton buttonView, boolean isChecked) {
- if (isChecked) {
- selected.add(photoModel);
- tvPreview.setEnabled(true);
- } else {
- selected.remove(photoModel);
- }
- tvPreview.setText("预览(" + selected.size() + ")"); //修改预览数量
- if (selected.isEmpty()) {
- tvPreview.setEnabled(false);
- tvPreview.setText("预览");
- }
- }
③因为使用了图片加载框架,需要在使用之前进行配置。使用该框架加载图片的代码很简洁。
因为对GridView的每一个Item进行了封装,同一时刻加载太多个会导致内存崩溃,这里我采用在一定时间内随机选择一个时间进行加载,这也是为什么有时候后面的Item会先加载完成(不排除小图片先加载完成的可能)
- DisplayImageOptions defaultDisplayImageOptions = new DisplayImageOptions.Builder() //
- .considerExifParams(true) // 调整图片方向
- .resetViewBeforeLoading(true) // 载入之前重置ImageView
- .showImageOnLoading(R.drawable.ic_picture_loading) // 载入时图片设置为黑色
- .showImageOnFail(R.drawable.ic_picture_loadfailed) // 加载失败时显示的图片
- .delayBeforeLoading(0) // 载入之前的延迟时间
- .build(); //
- ImageLoaderConfiguration config = new ImageLoaderConfiguration.Builder(getApplicationContext())
- .defaultDisplayImageOptions(defaultDisplayImageOptions).memoryCacheExtraOptions(480, 800)
- .threadPoolSize(5).build();
- ImageLoader.getInstance().init(config);
- new Handler().postDelayed(new Runnable() {
- @Override
- public void run() {
- ImageLoader.getInstance().displayImage("file://" + photo.getOriginalPath(), ivPhoto);
- }
- }, new Random().nextInt(10));
④点击照片与选中对对张照片之后进行预览都会开启预览界面,但是传递给预览界面的数据是不一样的,这里需要进行处理。
说明:CommonUtils是我封装的一个常用工具类。
- /** 预览照片 */
- private void priview() {
- Bundle bundle = new Bundle();
- bundle.putSerializable("photos", selected);
- CommonUtils.launchActivity(this, PhotoPreviewActivity.class, bundle);
- }
- @Override
- /** 点击查看照片 */
- public void onItemClick(int position) {
- Bundle bundle = new Bundle();
- if (tvAlbum.getText().toString().equals(RECCENT_PHOTO))
- bundle.putInt("position", position - 1);
- else
- bundle.putInt("position", position);
- bundle.putString("album", tvAlbum.getText().toString());
- CommonUtils.launchActivity(this, PhotoPreviewActivity.class, bundle);
- }
- @SuppressWarnings("unchecked")
- protected void init(Bundle extras) {
- if (extras == null)
- return;
- if (extras.containsKey("photos")) { // 预览图片
- photos = (List<PhotoModel>) extras.getSerializable("photos");
- current = extras.getInt("position", 0);
- updatePercent();
- bindData();
- } else if (extras.containsKey("album")) { // 点击图片查看
- String albumName = extras.getString("album"); // 相册
- this.current = extras.getInt("position");
- if (!CommonUtils.isNull(albumName) && albumName.equals(PhotoSelectorActivity.RECCENT_PHOTO)) {
- photoSelectorDomain.getReccent(this);
- } else {
- photoSelectorDomain.getAlbum(albumName, this);
- }
- }
- }
⑤相册选择界面我采用的是一个ListView进行显示,并采用动画的形式弹出和收回。相册信息同样是使用 ContentResolver进行获取,这里同样是采用异步回去数据,接着调用主界面回调函数对adapter适配器进行更新
说明:这里的AnimationUtil是我封装的一个用来显示动画的工具类,具体代码可以看源代码。
- /** 弹出相册列表 */
- private void popAlbum() {
- layoutAlbum.setVisibility(View.VISIBLE);
- new AnimationUtil(getApplicationContext(), R.anim.translate_up_current).setLinearInterpolator().startAnimation(
- layoutAlbum);
- }
- /** 隐藏相册列表 */
- private void hideAlbum() {
- new AnimationUtil(getApplicationContext(), R.anim.translate_down).setLinearInterpolator().startAnimation(
- layoutAlbum);
- layoutAlbum.setVisibility(View.GONE);
- }
- private OnLocalAlbumListener albumListener = new OnLocalAlbumListener() {
- @Override
- public void onAlbumLoaded(List<AlbumModel> albums) {
- albumAdapter.update(albums);
- }
- };
- /** 获取所有相册列表 */
- public List<AlbumModel> getAlbums() {
- List<AlbumModel> albums = new ArrayList<AlbumModel>();
- Map<String, AlbumModel> map = new HashMap<String, AlbumModel>();
- Cursor cursor = resolver.query(Media.EXTERNAL_CONTENT_URI, new String[] { ImageColumns.DATA,
- ImageColumns.BUCKET_DISPLAY_NAME, ImageColumns.SIZE }, null, null, null);
- if (cursor == null || !cursor.moveToNext())
- return new ArrayList<AlbumModel>();
- cursor.moveToLast();
- AlbumModel current = new AlbumModel("最近照片", 0, cursor.getString(cursor.getColumnIndex(ImageColumns.DATA)), true); // "最近照片"相册
- albums.add(current);
- do {
- if (cursor.getInt(cursor.getColumnIndex(ImageColumns.SIZE)) < 1024 * 10)
- continue;
- current.increaseCount();
- String name = cursor.getString(cursor.getColumnIndex(ImageColumns.BUCKET_DISPLAY_NAME));
- if (map.keySet().contains(name))
- map.get(name).increaseCount();
- else {
- AlbumModel album = new AlbumModel(name, 1, cursor.getString(cursor.getColumnIndex(ImageColumns.DATA)));
- map.put(name, album);
- albums.add(album);
- }
- } while (cursor.moveToPrevious());
- return albums;
- }
- /** 获取对应相册下的照片 */
- public List<PhotoModel> getAlbum(String name) {
- Cursor cursor = resolver.query(Media.EXTERNAL_CONTENT_URI, new String[] { ImageColumns.BUCKET_DISPLAY_NAME,
- ImageColumns.DATA, ImageColumns.DATE_ADDED, ImageColumns.SIZE }, "bucket_display_name = ?",
- new String[] { name }, ImageColumns.DATE_ADDED);
- if (cursor == null || !cursor.moveToNext())
- return new ArrayList<PhotoModel>();
- List<PhotoModel> photos = new ArrayList<PhotoModel>();
- cursor.moveToLast();
- do {
- if (cursor.getLong(cursor.getColumnIndex(ImageColumns.SIZE)) > 1024 * 10) {
- PhotoModel photoModel = new PhotoModel();
- photoModel.setOriginalPath(cursor.getString(cursor.getColumnIndex(ImageColumns.DATA)));
- photos.add(photoModel);
- }
- } while (cursor.moveToPrevious());
- return photos;
- }
⑥最后,如果不想选择图库里的照片,可以使用拍照获取照片并返回。这里的拍照按钮在适配器里进行了处理。
- @Override
- public View getView(int position, View convertView, ViewGroup parent) {
- PhotoItem item = null;
- TextView tvCamera = null;
- if (position == 0 && CommonUtils.isNull(models.get(position).getOriginalPath())) { // 当时第一个时,显示按钮
- if (convertView == null || !(convertView instanceof TextView)) {
- tvCamera = (TextView) LayoutInflater.from(context).inflate(R.layout.view_camera, null);
- tvCamera.setHeight(itemWidth);
- tvCamera.setWidth(itemWidth);
- convertView = tvCamera;
- }
- convertView.setOnClickListener(cameraListener);
- } else { // 显示图片
- if (convertView == null || !(convertView instanceof PhotoItem)) {
- item = new PhotoItem(context, listener);
- item.setLayoutParams(itemLayoutParams);
- convertView = item;
- } else {
- item = (PhotoItem) convertView;
- }
- item.setImageDrawable(models.get(position));
- item.setSelected(models.get(position).isChecked());
- item.setOnClickListener(mCallback, position);
- }
- return convertView;
- }
- /** 拍照 */
- private void catchPicture() {
- CommonUtils.launchActivityForResult(this, new Intent(MediaStore.ACTION_IMAGE_CAPTURE), REQUEST_CAMERA);
- }
- @Override
- protected void onActivityResult(int requestCode, int resultCode, Intent data) {
- if (requestCode == REQUEST_CAMERA && resultCode == RESULT_OK) {
- PhotoModel photoModel = new PhotoModel(CommonUtils.query(getApplicationContext(), data.getData()));
- selected.clear();
- selected.add(photoModel);
- ok();
- }
- }
⑦接着,就是将选中或者拍摄的照片路径信息返回,这里我采用setResult的方式放回,所以在使用时需要在需要在调用照片选择器的Activity重写onActivityResult函数。
- /** 完成 */
- private void ok() {
- if (selected.isEmpty()) {
- setResult(RESULT_CANCELED);
- } else {
- Intent data = new Intent();
- Bundle bundle = new Bundle();
- bundle.putSerializable("photos", selected);
- data.putExtras(bundle);
- setResult(RESULT_OK, data);
- }
- finish();
- }
⑧最后,如何调用照片选择器以及在调用照片选择器的界面重写onActivityResult函数接受照片信息。
说明:这里的tvPath是我写的Demo项目里,一个用来显示照片路径的TextView。
- @Override
- public void onClick(View v) {
- //CommonUtils是library中的一个工具类
- CommonUtils.launchActivityForResult(this, PhotoSelectorActivity.class, 0);
- tvPath.setText("");
- }
- @Override
- protected void onActivityResult(int requestCode, int resultCode, Intent data) {
- super.onActivityResult(requestCode, resultCode, data);
- if (requestCode == 0 && resultCode == RESULT_OK) {
- if (data != null && data.getExtras() != null) {
- @SuppressWarnings("unchecked")
- List<PhotoModel> photos = (List<PhotoModel>) data.getExtras().getSerializable("photos");
- if (photos == null || photos.isEmpty())
- return;
- StringBuffer sb = new StringBuffer();
- for (PhotoModel photo : photos) {
- sb.append(photo.getOriginalPath() + "\r\n");
- }
- tvPath.setText(sb.toString());
- }
- }
- }