出处: http://blog.csdn.net/lmj623565791/article/details/39943731

1、概述

关于手机图片加载器,在当今像素随随便便破千万的时代,一张图片占据的内存都相当可观,作为高大尚程序猿的我们,有必要掌握图片的压缩,缓存等处理,以到达纵使你有万张照片,纵使你的像素再高,我们也能正确的显示所有的图片。当然了,单纯显示图片没撒意思,我们决定高仿一下微信的图片选择器,在此,感谢微信!本篇博客将基于以下两篇博客:

Android 快速开发系列 打造万能的ListView GridView 适配器  将使用我们打造的CommonAdapter作为我们例子中GridView以及ListView的适配器

Android Handler 异步消息处理机制的妙用 创建强大的图片加载类 将使用我们自己写的ImageLoader作为我们的图片加载的核心类

如果你没看过也没关系,等看完本篇博客,可以结合以上两篇再进行充分理解一下。

好了,首先贴一下效果图:

动态图实在是录不出来,大家自己打开微信点击发表图片,或者聊天窗口发送图片,大致和微信的效果一样~

简单描述一下:

1、默认显示图片最多的文件夹图片,以及底部显示图片总数量;如上图1;

2、点击底部,弹出popupWindow,popupWindow包含所有含有图片的文件夹,以及显示每个文件夹中图片数量;如上图2;注:此时Activity变暗

3、选择任何文件夹,进入该文件夹图片显示,可以点击选择图片,当然了,点击已选择的图片则会取消选择;如上图3;注:选中图片变暗

当然了,最重要的效果一定流畅,不能动不动OOM~~

本人测试手机小米2s,图片6802张,未出现OOM异常,效果也是非常流畅,堪比图库~

不过存在bug在所难免,大家可以留言说下自己发现的bug;文末会提供源码下载。

好了,下面就可以代码的征程了~

2、图片的列表页

首先对手机中图片进行扫描,拿到图片数量最多的,直接显示在GridView上;并且扫描结束,得到一个所有包含图片的文件夹信息的List;

对于文件夹信息,我们单独创建了一个Bean:

[java] view plaincopy
  1. package com.zhy.bean;
  2. public class ImageFloder
  3. {
  4. /**
  5. * 图片的文件夹路径
  6. */
  7. private String dir;
  8. /**
  9. * 第一张图片的路径
  10. */
  11. private String firstImagePath;
  12. /**
  13. * 文件夹的名称
  14. */
  15. private String name;
  16. /**
  17. * 图片的数量
  18. */
  19. private int count;
  20. public String getDir()
  21. {
  22. return dir;
  23. }
  24. public void setDir(String dir)
  25. {
  26. this.dir = dir;
  27. int lastIndexOf = this.dir.lastIndexOf("/");
  28. this.name = this.dir.substring(lastIndexOf);
  29. }
  30. public String getFirstImagePath()
  31. {
  32. return firstImagePath;
  33. }
  34. public void setFirstImagePath(String firstImagePath)
  35. {
  36. this.firstImagePath = firstImagePath;
  37. }
  38. public String getName()
  39. {
  40. return name;
  41. }
  42. public int getCount()
  43. {
  44. return count;
  45. }
  46. public void setCount(int count)
  47. {
  48. this.count = count;
  49. }
  50. }

用来存储当前文件夹的路径,当前文件夹包含多少张图片,以及第一张图片路径用于做文件夹的图标;注:文件夹的名称,我们在set文件夹的路径的时候,自动提取,仔细看下setDir这个方法。

接下来就是扫描手机图片的代码了:

[java] view plaincopy
  1. @Override
  2. protected void onCreate(Bundle savedInstanceState)
  3. {
  4. super.onCreate(savedInstanceState);
  5. setContentView(R.layout.activity_main);
  6. DisplayMetrics outMetrics = new DisplayMetrics();
  7. getWindowManager().getDefaultDisplay().getMetrics(outMetrics);
  8. mScreenHeight = outMetrics.heightPixels;
  9. initView();
  10. getImages();
  11. initEvent();
  12. }
  13. /**
  14. * 利用ContentProvider扫描手机中的图片,此方法在运行在子线程中 完成图片的扫描,最终获得jpg最多的那个文件夹
  15. */
  16. private void getImages()
  17. {
  18. if (!Environment.getExternalStorageState().equals(
  19. Environment.MEDIA_MOUNTED))
  20. {
  21. Toast.makeText(this, "暂无外部存储", Toast.LENGTH_SHORT).show();
  22. return;
  23. }
  24. // 显示进度条
  25. mProgressDialog = ProgressDialog.show(this, null, "正在加载...");
  26. new Thread(new Runnable()
  27. {
  28. @Override
  29. public void run()
  30. {
  31. String firstImage = null;
  32. Uri mImageUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
  33. ContentResolver mContentResolver = MainActivity.this
  34. .getContentResolver();
  35. // 只查询jpeg和png的图片
  36. Cursor mCursor = mContentResolver.query(mImageUri, null,
  37. MediaStore.Images.Media.MIME_TYPE + "=? or "
  38. + MediaStore.Images.Media.MIME_TYPE + "=?",
  39. new String[] { "image/jpeg", "image/png" },
  40. MediaStore.Images.Media.DATE_MODIFIED);
  41. Log.e("TAG", mCursor.getCount() + "");
  42. while (mCursor.moveToNext())
  43. {
  44. // 获取图片的路径
  45. String path = mCursor.getString(mCursor
  46. .getColumnIndex(MediaStore.Images.Media.DATA));
  47. Log.e("TAG", path);
  48. // 拿到第一张图片的路径
  49. if (firstImage == null)
  50. firstImage = path;
  51. // 获取该图片的父路径名
  52. File parentFile = new File(path).getParentFile();
  53. if (parentFile == null)
  54. continue;
  55. String dirPath = parentFile.getAbsolutePath();
  56. ImageFloder imageFloder = null;
  57. // 利用一个HashSet防止多次扫描同一个文件夹(不加这个判断,图片多起来还是相当恐怖的~~)
  58. if (mDirPaths.contains(dirPath))
  59. {
  60. continue;
  61. } else
  62. {
  63. mDirPaths.add(dirPath);
  64. // 初始化imageFloder
  65. imageFloder = new ImageFloder();
  66. imageFloder.setDir(dirPath);
  67. imageFloder.setFirstImagePath(path);
  68. }
  69. int picSize = parentFile.list(new FilenameFilter()
  70. {
  71. @Override
  72. public boolean accept(File dir, String filename)
  73. {
  74. if (filename.endsWith(".jpg")
  75. || filename.endsWith(".png")
  76. || filename.endsWith(".jpeg"))
  77. return true;
  78. return false;
  79. }
  80. }).length;
  81. totalCount += picSize;
  82. imageFloder.setCount(picSize);
  83. mImageFloders.add(imageFloder);
  84. if (picSize > mPicsSize)
  85. {
  86. mPicsSize = picSize;
  87. mImgDir = parentFile;
  88. }
  89. }
  90. mCursor.close();
  91. // 扫描完成,辅助的HashSet也就可以释放内存了
  92. mDirPaths = null;
  93. // 通知Handler扫描图片完成
  94. mHandler.sendEmptyMessage(0x110);
  95. }
  96. }).start();
  97. }

ps:运行出现空指针的话,在81行的位置添加判断,if(parentFile.list()==null)continue , 切记~~~有些图片比较诡异~~;

initView就不看了,都是些findViewById;

getImages主要就是扫描图片的代码,我们开启了一个Thread进行扫描,扫描完成以后,我们得到了图片最多文件夹路径(mImgDir),手机中图片数量(totalCount);以及所有包含图片文件夹信息(mImageFloders)

然后我们通过handler发送消息,在handleMessage里面:

1、创建GridView的适配器,为我们的GridView设置适配器,显示图片;

2、有了mImageFloders,就可以创建我们的popupWindow了

看一眼我们的Handler

[java] view plaincopy
  1. private Handler mHandler = new Handler()
  2. {
  3. public void handleMessage(android.os.Message msg)
  4. {
  5. mProgressDialog.dismiss();
  6. //为View绑定数据
  7. data2View();
  8. //初始化展示文件夹的popupWindw
  9. initListDirPopupWindw();
  10. }
  11. };

可以看到分别干了上述的两件事:

[java] view plaincopy
  1. /**
  2. * 为View绑定数据
  3. */
  4. private void data2View()
  5. {
  6. if (mImgDir == null)
  7. {
  8. Toast.makeText(getApplicationContext(), "擦,一张图片没扫描到",
  9. Toast.LENGTH_SHORT).show();
  10. return;
  11. }
  12. mImgs = Arrays.asList(mImgDir.list());
  13. /**
  14. * 可以看到文件夹的路径和图片的路径分开保存,极大的减少了内存的消耗;
  15. */
  16. mAdapter = new MyAdapter(getApplicationContext(), mImgs,
  17. R.layout.grid_item, mImgDir.getAbsolutePath());
  18. mGirdView.setAdapter(mAdapter);
  19. mImageCount.setText(totalCount + "张");
  20. };

data2View就是我们当前Activity上所有的View设置数据了。

看到这里还用到了一个Adapter,我们GridView的:

[java] view plaincopy
  1. package com.zhy.imageloader;
  2. import java.util.LinkedList;
  3. import java.util.List;
  4. import android.content.Context;
  5. import android.graphics.Color;
  6. import android.view.View;
  7. import android.view.View.OnClickListener;
  8. import android.widget.ImageView;
  9. import com.zhy.utils.CommonAdapter;
  10. public class MyAdapter extends CommonAdapter<String>
  11. {
  12. /**
  13. * 用户选择的图片,存储为图片的完整路径
  14. */
  15. public static List<String> mSelectedImage = new LinkedList<String>();
  16. /**
  17. * 文件夹路径
  18. */
  19. private String mDirPath;
  20. public MyAdapter(Context context, List<String> mDatas, int itemLayoutId,
  21. String dirPath)
  22. {
  23. super(context, mDatas, itemLayoutId);
  24. this.mDirPath = dirPath;
  25. }
  26. @Override
  27. public void convert(final com.zhy.utils.ViewHolder helper, final String item)
  28. {
  29. // 设置no_pic
  30. helper.setImageResource(R.id.id_item_image, R.drawable.pictures_no);
  31. // 设置no_selected
  32. helper.setImageResource(R.id.id_item_select,
  33. R.drawable.picture_unselected);
  34. // 设置图片
  35. helper.setImageByUrl(R.id.id_item_image, mDirPath + "/" + item);
  36. final ImageView mImageView = helper.getView(R.id.id_item_image);
  37. final ImageView mSelect = helper.getView(R.id.id_item_select);
  38. mImageView.setColorFilter(null);
  39. // 设置ImageView的点击事件
  40. mImageView.setOnClickListener(new OnClickListener()
  41. {
  42. // 选择,则将图片变暗,反之则反之
  43. @Override
  44. public void onClick(View v)
  45. {
  46. // 已经选择过该图片
  47. if (mSelectedImage.contains(mDirPath + "/" + item))
  48. {
  49. mSelectedImage.remove(mDirPath + "/" + item);
  50. mSelect.setImageResource(R.drawable.picture_unselected);
  51. mImageView.setColorFilter(null);
  52. } else
  53. // 未选择该图片
  54. {
  55. mSelectedImage.add(mDirPath + "/" + item);
  56. mSelect.setImageResource(R.drawable.pictures_selected);
  57. mImageView.setColorFilter(Color.parseColor("#77000000"));
  58. }
  59. }
  60. });
  61. /**
  62. * 已经选择过的图片,显示出选择过的效果
  63. */
  64. if (mSelectedImage.contains(mDirPath + "/" + item))
  65. {
  66. mSelect.setImageResource(R.drawable.pictures_selected);
  67. mImageView.setColorFilter(Color.parseColor("#77000000"));
  68. }
  69. }
  70. }

可以看到我们GridView的Adapter继承了我们的CommonAdapter,如果不知道CommonAdapter为何物,可以去看看万能适配器那篇博文;

我们现在只需要实现convert方法:

在convert中,我们设置图片,设置事件等,对于图片的变暗,我们使用的是ImageView的setColorFilter ;根据Url加载图片的操作封装在helper.setImageByUrl(view,url)中,内部使用的是我们自己定义的ImageLoader,包括错乱处理都已经封装了,图片策略我们使用的是LIFO后进先出;不清楚的可以看文章一开始说明的那两篇博文,对于CommonAdapter以及ImageLoader都有从无到有的详细打造过程;

到此我们的第一个Activity的所有的任务就完成了~~~

3、展现文件夹的PopupWindow

现在我们要实现,点击底部的布局弹出我们的文件夹选择框,并且我们弹出框后面的Activity要变暗;

不急着贴代码,我们先考虑下PopupWindow怎么用最好,我们的PopupWindow需要设置布局文件,需要初始化View,需要初始化事件,还需要和Activity交互~~

那么肯定的,我们使用独立的类,这个类和Activity很相似,在里面initView(),initEvent()之类的。

我们创建了一个popupWindow使用的超类:

[java] view plaincopy
  1. package com.zhy.utils;
  2. import java.util.List;
  3. import android.content.Context;
  4. import android.graphics.drawable.BitmapDrawable;
  5. import android.view.MotionEvent;
  6. import android.view.View;
  7. import android.view.View.OnTouchListener;
  8. import android.widget.PopupWindow;
  9. public abstract class BasePopupWindowForListView<T> extends PopupWindow
  10. {
  11. /**
  12. * 布局文件的最外层View
  13. */
  14. protected View mContentView;
  15. protected Context context;
  16. /**
  17. * ListView的数据集
  18. */
  19. protected List<T> mDatas;
  20. public BasePopupWindowForListView(View contentView, int width, int height,
  21. boolean focusable)
  22. {
  23. this(contentView, width, height, focusable, null);
  24. }
  25. public BasePopupWindowForListView(View contentView, int width, int height,
  26. boolean focusable, List<T> mDatas)
  27. {
  28. this(contentView, width, height, focusable, mDatas, new Object[0]);
  29. }
  30. public BasePopupWindowForListView(View contentView, int width, int height,
  31. boolean focusable, List<T> mDatas, Object... params)
  32. {
  33. super(contentView, width, height, focusable);
  34. this.mContentView = contentView;
  35. context = contentView.getContext();
  36. if (mDatas != null)
  37. this.mDatas = mDatas;
  38. if (params != null && params.length > 0)
  39. {
  40. beforeInitWeNeedSomeParams(params);
  41. }
  42. setBackgroundDrawable(new BitmapDrawable());
  43. setTouchable(true);
  44. setOutsideTouchable(true);
  45. setTouchInterceptor(new OnTouchListener()
  46. {
  47. @Override
  48. public boolean onTouch(View v, MotionEvent event)
  49. {
  50. if (event.getAction() == MotionEvent.ACTION_OUTSIDE)
  51. {
  52. dismiss();
  53. return true;
  54. }
  55. return false;
  56. }
  57. });
  58. initViews();
  59. initEvents();
  60. init();
  61. }
  62. protected abstract void beforeInitWeNeedSomeParams(Object... params);
  63. public abstract void initViews();
  64. public abstract void initEvents();
  65. public abstract void init();
  66. public View findViewById(int id)
  67. {
  68. return mContentView.findViewById(id);
  69. }
  70. protected static int dpToPx(Context context, int dp)
  71. {
  72. return (int) (context.getResources().getDisplayMetrics().density * dp + 0.5f);
  73. }
  74. }

也就是封装了一下popupWindow常用的一些设置,然后使用了类似模版方法模式,约束子类,必须实现initView,initEvent,init等方法

[java] view plaincopy
  1. package com.zhy.imageloader;
  2. import java.util.List;
  3. import android.view.View;
  4. import android.widget.AdapterView;
  5. import android.widget.AdapterView.OnItemClickListener;
  6. import android.widget.ListView;
  7. import com.zhy.bean.ImageFloder;
  8. import com.zhy.utils.BasePopupWindowForListView;
  9. import com.zhy.utils.CommonAdapter;
  10. import com.zhy.utils.ViewHolder;
  11. public class ListImageDirPopupWindow extends BasePopupWindowForListView<ImageFloder>
  12. {
  13. private ListView mListDir;
  14. public ListImageDirPopupWindow(int width, int height,
  15. List<ImageFloder> datas, View convertView)
  16. {
  17. super(convertView, width, height, true, datas);
  18. }
  19. @Override
  20. public void initViews()
  21. {
  22. mListDir = (ListView) findViewById(R.id.id_list_dir);
  23. mListDir.setAdapter(new CommonAdapter<ImageFloder>(context, mDatas,
  24. R.layout.list_dir_item)
  25. {
  26. @Override
  27. public void convert(ViewHolder helper, ImageFloder item)
  28. {
  29. helper.setText(R.id.id_dir_item_name, item.getName());
  30. helper.setImageByUrl(R.id.id_dir_item_image,
  31. item.getFirstImagePath());
  32. helper.setText(R.id.id_dir_item_count, item.getCount() + "张");
  33. }
  34. });
  35. }
  36. public interface OnImageDirSelected
  37. {
  38. void selected(ImageFloder floder);
  39. }
  40. private OnImageDirSelected mImageDirSelected;
  41. public void setOnImageDirSelected(OnImageDirSelected mImageDirSelected)
  42. {
  43. this.mImageDirSelected = mImageDirSelected;
  44. }
  45. @Override
  46. public void initEvents()
  47. {
  48. mListDir.setOnItemClickListener(new OnItemClickListener()
  49. {
  50. @Override
  51. public void onItemClick(AdapterView<?> parent, View view,
  52. int position, long id)
  53. {
  54. if (mImageDirSelected != null)
  55. {
  56. mImageDirSelected.selected(mDatas.get(position));
  57. }
  58. }
  59. });
  60. }
  61. @Override
  62. public void init()
  63. {
  64. // TODO Auto-generated method stub
  65. }
  66. @Override
  67. protected void beforeInitWeNeedSomeParams(Object... params)
  68. {
  69. // TODO Auto-generated method stub
  70. }
  71. }

好了,现在就是我们正在的popupWindow咯,布局文件夹主要是个ListView,所以在initView里面,我们得设置它的适配器;当然了,这里的适配器依然用我们的CommonAdapter,几行代码搞定~~

然后我们需要和Activity交互,当我们点击某个文件夹的时候,外层的Activity需要改变它GridView的数据源,展示我们点击文件夹的图片;

关于交互,我们从Activity的角度去看弹出框,Activity想知道什么,只想知道选择了别的文件夹来告诉我,所以我们创建一个接口OnImageDirSelected,对Activity设置回调;

这里还可以这么写:就是把popupWindow的ListView公布出去,然后在Activity里面使用popupWindow.getListView(),setOnItemClickListener,这么做,个人觉得不好,耦合度太高,客户简单改下需求“这个文件夹展示,给我们换了,换成GridView”,呵呵,此时,你需要到处去修改Activity里面的代码,因为你Activity里面竟然还有个popupWindow.getListView。

好了,扯多了,初始化事件的代码:

[java] view plaincopy
  1. @Override
  2. public void initEvents()
  3. {
  4. mListDir.setOnItemClickListener(new OnItemClickListener()
  5. {
  6. @Override
  7. public void onItemClick(AdapterView<?> parent, View view,
  8. int position, long id)
  9. {
  10. if (mImageDirSelected != null)
  11. {
  12. mImageDirSelected.selected(mDatas.get(position));
  13. }
  14. }
  15. });
  16. }

如果有人设置了回调,我们就调用;

到此,整个popupWindow就出炉了,接下来就看啥时候让它展示了;

4、选择不同的文件夹

上面说道,当扫描图片完成,拿到包含图片的文件夹信息列表;这个列表就是我们popupWindow所需的数据,所以我们的popupWindow的初始化在handleMessage(上面贴了handler的代码)里面:

在handleMessage里面调用initListDirPopupWindw

[java] view plaincopy
  1. /**
  2. * 初始化展示文件夹的popupWindw
  3. */
  4. private void initListDirPopupWindw()
  5. {
  6. mListImageDirPopupWindow = new ListImageDirPopupWindow(
  7. LayoutParams.MATCH_PARENT, (int) (mScreenHeight * 0.7),
  8. mImageFloders, LayoutInflater.from(getApplicationContext())
  9. .inflate(R.layout.list_dir, null));
  10. mListImageDirPopupWindow.setOnDismissListener(new OnDismissListener()
  11. {
  12. @Override
  13. public void onDismiss()
  14. {
  15. // 设置背景颜色变暗
  16. WindowManager.LayoutParams lp = getWindow().getAttributes();
  17. lp.alpha = 1.0f;
  18. getWindow().setAttributes(lp);
  19. }
  20. });
  21. // 设置选择文件夹的回调
  22. mListImageDirPopupWindow.setOnImageDirSelected(this);
  23. }

我们初始化我们的popupWindow,设置了关闭对话框的回调,已经设置了选择不同文件夹的回调;
这里仅仅是初始化,下面看我们合适将其弹出的,其实整个Activity也就一个事件,点击弹出该对话框,所以看Activity的initEvents方法:

[java] view plaincopy
  1. private void initEvent()
  2. {
  3. /**
  4. * 为底部的布局设置点击事件,弹出popupWindow
  5. */
  6. mBottomLy.setOnClickListener(new OnClickListener()
  7. {
  8. @Override
  9. public void onClick(View v)
  10. {
  11. mListImageDirPopupWindow
  12. .setAnimationStyle(R.style.anim_popup_dir);
  13. mListImageDirPopupWindow.showAsDropDown(mBottomLy, 0, 0);
  14. // 设置背景颜色变暗
  15. WindowManager.LayoutParams lp = getWindow().getAttributes();
  16. lp.alpha = .3f;
  17. getWindow().setAttributes(lp);
  18. }
  19. });
  20. }

可以看到,我们为底部布局设置点击事件;设置popupWindow的弹出与消失的动画;已经让Activity背景变暗变亮,通过改变Window alpha实现的。变亮在弹出框消息的监听里面~~

动画的文件就不贴了,大家自己看源码;

popupWindow弹出了,用户此时可以选择不同的文件夹,那么现在该看选择后的回调的代码了:

我们的Activity实现了该接口,直接看实现的方法:

[java] view plaincopy
  1. @Override
  2. public void selected(ImageFloder floder)
  3. {
  4. mImgDir = new File(floder.getDir());
  5. mImgs = Arrays.asList(mImgDir.list(new FilenameFilter()
  6. {
  7. @Override
  8. public boolean accept(File dir, String filename)
  9. {
  10. if (filename.endsWith(".jpg") || filename.endsWith(".png")
  11. || filename.endsWith(".jpeg"))
  12. return true;
  13. return false;
  14. }
  15. }));
  16. /**
  17. * 可以看到文件夹的路径和图片的路径分开保存,极大的减少了内存的消耗;
  18. */
  19. mAdapter = new MyAdapter(getApplicationContext(), mImgs,
  20. R.layout.grid_item, mImgDir.getAbsolutePath());
  21. mGirdView.setAdapter(mAdapter);
  22. // mAdapter.notifyDataSetChanged();
  23. mImageCount.setText(floder.getCount() + "张");
  24. mChooseDir.setText(floder.getName());
  25. mListImageDirPopupWindow.dismiss();
  26. }

我们改变了GridView的适配器,以及底部的控件上的文件夹名称,文件数量等等;

好了,到此结束;整篇由于篇幅原因没有贴任何布局文件,大家自己通过源码查看;

在此希望大家可以通过该案例,能够去其糟粕,取其精华,学习其中值得借鉴的代码风格,不要真的当作一个例子去学习~~

Android 超高仿微信图片选择器相关推荐

  1. Android 超高仿微信图片选择器 图片该这么加载

    2019独角兽企业重金招聘Python工程师标准>>> 转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/39943 ...

  2. Android 超高仿微信图片选择器 图片该这么加载

    转载请标明出处: http://blog.csdn.net/lmj623565791/article/details/39943731,本文出自: [张鸿洋的博客] 1.概述 关于手机图片加载器,在当 ...

  3. Android 超高仿微信图片选择器 图片该这么载入

    转载请标明出处: http://blog.csdn.net/lmj623565791/article/details/39943731,本文出自: [张鸿洋的博客] 1.概述 关于手机图片载入器,在当 ...

  4. Android 高仿微信图片选择器

    转载请标明出处: http://blog.csdn.net/lmj623565791/article/details/39943731,本文出自: [张鸿洋的博客] 1.概述 关于手机图片加载器,在当 ...

  5. android 微信图片选择,Android之仿微信图片选择器

    先上效果图.第一张图显示的是"相机"文件夹中的所有图片:通过点击多张图片可以到第二张图所示的效果(被选择的图片会变暗,同时选择按钮变亮):点击最下面的那一栏可以到第三张图所示的效果 ...

  6. java头像选择系统_Android+超高仿微信图片选择器(头像选择)

    [实例简介] [实例截图] [核心代码] 头像选择 package com.zhy.utils; import java.util.List; import android.content.Conte ...

  7. Android 仿微信图片选择器

    版权声明:本文为博主原创文章,未经博主允许不得转载. 1.自我介绍 这是我写的第一篇博客,首先做一下自我介绍,我去年刚毕业,大学学的是计算机专业,期间也学了一门Android相关课程,但是你懂的,一个 ...

  8. android仿微信图片选择器

    最近根据项目需求,要做一个仿微信图片选择的功能.首先我们先来整理一下思路. 1.显示选择图片的界面 1.1选择的图片数量小于9 最后一张图片是一个加号. 1.2选择的图片数量等于9,加号消失. 2.选 ...

  9. android qq底部图片选择器,Android 高仿QQ图片选择器

    当做一款APP,需要选择本地图片时,首先考虑的无疑是系统相册,但是Android手机五花八门,再者手机像素的提升,大图无法返回等异常因数,导致适配机型比较困难,微信.QQ都相继的在自己的APP里集成了 ...

最新文章

  1. 肠子的小心思(二):你坐在马桶上的姿势很可能不正确
  2. 基于Python查找图像中最常见的颜色
  3. LAMP网站架构方案分析
  4. grpc框架_分布式RPC框架dubbo、motan、rpcx、gRPC、thrift简介与性能比较
  5. ORDER BY NEWID()【原创】
  6. 连接sqlexpress
  7. react之虚拟DOM的两种创建方式
  8. Redis使用单线程却快到飞起的原因
  9. ajax回调给全局变量,jquery.Ajax回调成功后数据赋值给全局变量的问题
  10. javascript挑战编程技能-第九题:数据结构
  11. python提前退出内层循环,python with提前退出遇到的坑与解决方案
  12. MFC视类(view)、框架类(MainFrame)关系梳理
  13. 从东南亚到中东,为什么社交类产品成为游戏出海的突破口?
  14. Simulink代码生成基础体验教程
  15. python语言月份缩写_Python替换月份为英文缩写的实现方法
  16. Linux内核4.1在file_operations的read_iter和write_iter
  17. 360全景拍摄为什么要使用鱼眼镜头,与超广角镜头区别?
  18. 教培机构如何深耕种子用户从0到1-线上线下教学的有效融合
  19. bmp文件c语言压缩算法,BMP文件数据压缩与解压缩方法.pdf
  20. 【安卓开发系列 -- APP 】APP 性能优化 -- 崩溃分析

热门文章

  1. 网络安全-WEB中的常见编码
  2. 如何在Tanzu Cluster中使用vSphere with Tanzu内置容器注册表
  3. 黑苹果适合什么用途?_黑苹果系统,Ozmosis和四叶草、变色龙相比有什么区别和优势?...
  4. XXXX is not in the sudoers file. This incident will be reported解决方法
  5. ForestBlog博客源码学习笔记
  6. 大型电商网站:第一章:主要电商模式
  7. 合同法律风险管理 合同签字主体
  8. windows下强大的系统监视工具Procmon(Process Monitor)
  9. 分享四个体验不错的云游戏平台—网易云游戏、腾讯云游戏、菜鸡云游戏、格莱云游戏
  10. golang_微信头像过期失效