之前群里面有朋友问我,有没有关于本地图片选择的Demo,类似微信的效果,他说网上没有这方面的Demo,问我能不能写一篇关于这个效果的Demo,于是我研究了下微信的本地图片选择的Demo,自己仿照的写了下分享给大家,希望对以后有这样子需求的朋友有一点帮助吧,主要使用的是ContentProvider扫描手机中的图片,并用GridView将图片显示出来,关于GridView和ListView显示图片的问题,一直是一个很头疼的问题,因为我们手机的内存有限,手机给每个应用程序分配的内存也有限,所以图片多的情况下很容易伴随着OOM的发生,不过现在也有很多的开源的图片显示框架,对显示很多图片进行了优化,大家有兴趣的可以去了解了解,今天我的这篇文章使用的是LruCache这个类以及对图片进行相对应的裁剪,这样也可以尽量的避免OOM的发生,我们先看下微信的效果吧
接下来我们就来实现这些效果吧,首先我们新建一个项目,取名ImageScan
首先我们先看第一个界面吧,使用将手机中的图片扫描出来,然后根据图片的所在的文件夹将其分类出来,并显示所在文件夹里面的一张图片和文件夹中图片个数,我们根据界面元素(文件夹名, 文件夹图片个数,文件夹中的一张图片)使用一个实体对象ImageBean来封装这三个属性
package com.example.imagescan; /** * GridView的每个item的数据对象 * * @author len * */ public class ImageBean{ /** * 文件夹的第一张图片路径 */ private String topImagePath; /** * 文件夹名 */ private String folderName; /** * 文件夹中的图片数 */ private int imageCounts; public String getTopImagePath() { return topImagePath; } public void setTopImagePath(String topImagePath) { this.topImagePath = topImagePath; } public String getFolderName() { return folderName; } public void setFolderName(String folderName) { this.folderName = folderName; } public int getImageCounts() { return imageCounts; } public void setImageCounts(int imageCounts) { this.imageCounts = imageCounts; } }
接下来就是主界面的布局啦,上面的导航栏我没有加进去,只有下面的GridView,所以说主界面布局中只有一个GridView
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" > <GridView android:id="@+id/main_grid" android:layout_width="match_parent" android:layout_height="match_parent" android:listSelector="@android:color/transparent" android:cacheColorHint="@android:color/transparent" android:stretchMode="columnWidth" android:horizontalSpacing="20dip" android:gravity="center" android:verticalSpacing="20dip" android:columnWidth="90dip" android:numColumns="2" > </GridView> </RelativeLayout>
接下来就是GridView的Item的布局,看上面的图也行你会认为他的效果是2张图片添加的效果,其实不是,后面的叠加效果只是一张背景图片而已,代码先贴上来
<?xml version="1.0" encoding="UTF-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="wrap_content" > <FrameLayout android:id="@+id/framelayout" android:layout_width="fill_parent" android:layout_height="wrap_content" > <com.example.imagescan.MyImageView android:id="@+id/group_image" android:background="@drawable/albums_bg" android:src="@drawable/friends_sends_pictures_no" android:paddingLeft="20dip" android:paddingRight="20dip" android:paddingTop="18dip" android:paddingBottom="30dip" android:scaleType="fitXY" android:layout_width="fill_parent" android:layout_height="150dip" /> <TextView android:id="@+id/group_count" android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="@drawable/albums_icon_bg" android:gravity="center" android:layout_marginBottom="10dip" android:text="5" android:layout_gravity="bottom|center_horizontal" /> </FrameLayout> <TextView android:id="@+id/group_title" android:layout_width="fill_parent" android:layout_height="wrap_content" android:gravity="center" android:layout_below="@id/framelayout" android:layout_centerHorizontal="true" android:ellipsize="end" android:singleLine="true" android:text="Camera" android:textSize="16sp" /> </RelativeLayout>
看到上面的布局代码,也行你已经发现了,上面使用的是自定义的MyImageView,我先不说这个自定义MyImageView的作用,待会再给大家说,我们继续看代码
第一个界面的主要代码
package com.example.imagescan; import java.io.File; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import android.app.Activity; import android.app.ProgressDialog; import android.content.ContentResolver; import android.content.Intent; import android.database.Cursor; import android.net.Uri; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.provider.MediaStore; import android.view.View; import android.widget.AdapterView; import android.widget.AdapterView.OnItemClickListener; import android.widget.GridView; public class MainActivity extends Activity { private HashMap<String, List<String>> mGruopMap = new HashMap<String, List<String>>(); private List<ImageBean> list = new ArrayList<ImageBean>(); private final static int SCAN_OK = 1; private ProgressDialog mProgressDialog; private GroupAdapter adapter; private GridView mGroupGridView; private Handler mHandler = new Handler(){ @Override public void handleMessage(Message msg) { super.handleMessage(msg); switch (msg.what) { case SCAN_OK: //关闭进度条 mProgressDialog.dismiss(); adapter = new GroupAdapter(MainActivity.this, list = subGroupOfImage(mGruopMap), mGroupGridView); mGroupGridView.setAdapter(adapter); break; } } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mGroupGridView = (GridView) findViewById(R.id.main_grid); getImages(); mGroupGridView.setOnItemClickListener(new OnItemClickListener() { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { List<String> childList = mGruopMap.get(list.get(position).getFolderName()); Intent mIntent = new Intent(MainActivity.this, ShowImageActivity.class); mIntent.putStringArrayListExtra("data", (ArrayList<String>)childList); startActivity(mIntent); } }); }
/** * 利用ContentProvider扫描手机中的图片,此方法在运行在子线程中 */ private void getImages() { //显示进度条 mProgressDialog = ProgressDialog.show(this, null, "正在加载..."); new Thread(new Runnable() { @Override public void run() { Uri mImageUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI; ContentResolver mContentResolver = MainActivity.this.getContentResolver(); //只查询jpeg和png的图片 Cursor mCursor = mContentResolver.query(mImageUri, null, MediaStore.Images.Media.MIME_TYPE + "=? or " + MediaStore.Images.Media.MIME_TYPE + "=?", new String[] { "image/jpeg", "image/png" }, MediaStore.Images.Media.DATE_MODIFIED); if(mCursor == null){ return; } while (mCursor.moveToNext()) { //获取图片的路径 String path = mCursor.getString(mCursor .getColumnIndex(MediaStore.Images.Media.DATA)); //获取该图片的父路径名 String parentName = new File(path).getParentFile().getName(); //根据父路径名将图片放入到mGruopMap中 if (!mGruopMap.containsKey(parentName)) { List<String> chileList = new ArrayList<String>(); chileList.add(path); mGruopMap.put(parentName, chileList); } else { mGruopMap.get(parentName).add(path); } } //通知Handler扫描图片完成 mHandler.sendEmptyMessage(SCAN_OK); mCursor.close(); } }).start(); }
/** * 组装分组界面GridView的数据源,因为我们扫描手机的时候将图片信息放在HashMap中 * 所以需要遍历HashMap将数据组装成List * * @param mGruopMap * @return */ private List<ImageBean> subGroupOfImage(HashMap<String, List<String>> mGruopMap){ if(mGruopMap.size() == 0){ return null; } List<ImageBean> list = new ArrayList<ImageBean>(); Iterator<Map.Entry<String, List<String>>> it = mGruopMap.entrySet().iterator(); while (it.hasNext()) { Map.Entry<String, List<String>> entry = it.next(); ImageBean mImageBean = new ImageBean(); String key = entry.getKey(); List<String> value = entry.getValue(); mImageBean.setFolderName(key); mImageBean.setImageCounts(value.size()); mImageBean.setTopImagePath(value.get(0));//获取该组的第一张图片 list.add(mImageBean); } return list; } }首先看getImages()这个方法,该方法是使用ContentProvider将手机中的图片扫描出来,我这里只扫描了手机的外部存储中的图片,由于手机中可能存在很多的图片,扫描图片又比较耗时,所以我们在这里开启了子线程去获取图片,扫描的图片都存放在Cursor中,我们先要将图片按照文件夹进行分类,我们使用了HashMap来进行分类并将结果存储到mGruopMap(Key是文件夹名,Value是文件夹中的图片路径的List)中,分类完了关闭Cursor并利用Handler来通知主线程
package com.example.imagescan; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Point; import android.os.Handler; import android.os.Message; import android.support.v4.util.LruCache; /** * 本地图片加载器,采用的是异步解析本地图片,单例模式利用getInstance()获取NativeImageLoader实例 * 调用loadNativeImage()方法加载本地图片,此类可作为一个加载本地图片的工具类 */ public class NativeImageLoader { private LruCache<String, Bitmap> mMemoryCache; private static NativeImageLoader mInstance = new NativeImageLoader(); private ExecutorService mImageThreadPool = Executors.newFixedThreadPool(1); private NativeImageLoader(){ //获取应用程序的最大内存 final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024); //用最大内存的1/4来存储图片 final int cacheSize = maxMemory / 4; mMemoryCache = new LruCache<String, Bitmap>(cacheSize) { //获取每张图片的大小 @Override protected int sizeOf(String key, Bitmap bitmap) { return bitmap.getRowBytes() * bitmap.getHeight() / 1024; } }; } /** * 通过此方法来获取NativeImageLoader的实例 * @return */ public static NativeImageLoader getInstance(){ return mInstance; }
/** * 加载本地图片,对图片不进行裁剪 * @param path * @param mCallBack * @return */ public Bitmap loadNativeImage(final String path, final NativeImageCallBack mCallBack){ return this.loadNativeImage(path, null, mCallBack); } /** * 此方法来加载本地图片,这里的mPoint是用来封装ImageView的宽和高,我们会根据ImageView控件的大小来裁剪Bitmap * 如果你不想裁剪图片,调用loadNativeImage(final String path, final NativeImageCallBack mCallBack)来加载 * @param path * @param mPoint * @param mCallBack * @return */ public Bitmap loadNativeImage(final String path, final Point mPoint, final NativeImageCallBack mCallBack){ //先获取内存中的Bitmap Bitmap bitmap = getBitmapFromMemCache(path); final Handler mHander = new Handler(){ @Override public void handleMessage(Message msg) { super.handleMessage(msg); mCallBack.onImageLoader((Bitmap)msg.obj, path); } }; //若该Bitmap不在内存缓存中,则启用线程去加载本地的图片,并将Bitmap加入到mMemoryCache中 if(bitmap == null){ mImageThreadPool.execute(new Runnable() { @Override public void run() { //先获取图片的缩略图 Bitmap mBitmap = decodeThumbBitmapForFile(path, mPoint == null ? 0: mPoint.x, mPoint == null ? 0: mPoint.y); Message msg = mHander.obtainMessage(); msg.obj = mBitmap; mHander.sendMessage(msg); //将图片加入到内存缓存 addBitmapToMemoryCache(path, mBitmap); } }); } return bitmap; } /** * 往内存缓存中添加Bitmap * * @param key * @param bitmap */ private void addBitmapToMemoryCache(String key, Bitmap bitmap) { if (getBitmapFromMemCache(key) == null && bitmap != null) { mMemoryCache.put(key, bitmap); } } /** * 根据key来获取内存中的图片 * @param key * @return */ private Bitmap getBitmapFromMemCache(String key) { return mMemoryCache.get(key); } /** * 根据View(主要是ImageView)的宽和高来获取图片的缩略图 * @param path * @param viewWidth * @param viewHeight * @return */ private Bitmap decodeThumbBitmapForFile(String path, int viewWidth, int viewHeight){ BitmapFactory.Options options = new BitmapFactory.Options(); //设置为true,表示解析Bitmap对象,该对象不占内存 options.inJustDecodeBounds = true; BitmapFactory.decodeFile(path, options); //设置缩放比例 options.inSampleSize = computeScale(options, viewWidth, viewHeight); //设置为false,解析Bitmap对象加入到内存中 options.inJustDecodeBounds = false; return BitmapFactory.decodeFile(path, options); } /** * 根据View(主要是ImageView)的宽和高来计算Bitmap缩放比例。默认不缩放 * @param options * @param width * @param height */ private int computeScale(BitmapFactory.Options options, int viewWidth, int viewHeight){ int inSampleSize = 1; if(viewWidth == 0 || viewWidth == 0){ return inSampleSize; } int bitmapWidth = options.outWidth; int bitmapHeight = options.outHeight; //假如Bitmap的宽度或高度大于我们设定图片的View的宽高,则计算缩放比例 if(bitmapWidth > viewWidth || bitmapHeight > viewWidth){ int widthScale = Math.round((float) bitmapWidth / (float) viewWidth); int heightScale = Math.round((float) bitmapHeight / (float) viewWidth); //为了保证图片不缩放变形,我们取宽高比例最小的那个 inSampleSize = widthScale < heightScale ? widthScale : heightScale; } return inSampleSize; } /** * 加载本地图片的回调接口 * * @author xiaanming * */ public interface NativeImageCallBack{ /** * 当子线程加载完了本地的图片,将Bitmap和图片路径回调在此方法中 * @param bitmap * @param path */ public void onImageLoader(Bitmap bitmap, String path); } }该类是一个单例类,提供了本地图片加载,内存缓存,裁剪等逻辑,该类在加载本地图片的时候采用的是异步加载的方式,对于大图片的加载也是比较耗时的,所以采用子线程的方式去加载,对于图片的缓存机制使用的是LruCache,使用手机分配给应用程序内存的1/4用来缓存图片,除了使用LruCache缓存图片之外,还对图片进行了裁剪,举个很简单的例子,假如我们的控件大小是100 * 100, 而我们的图片是400*400,我们加载这么大的图片需要很多的内存,所以我们采用了图片裁剪,根据控件的大小来确定图片的裁剪比例,从而减小内存的消耗,提高GridView滑动的流畅度,介绍里面几个比较重要的方法
package com.example.imagescan; import java.util.List; import android.content.Context; import android.graphics.Bitmap; import android.graphics.Point; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.BaseAdapter; import android.widget.GridView; import android.widget.ImageView; import android.widget.TextView; import com.example.imagescan.MyImageView.OnMeasureListener; import com.example.imagescan.NativeImageLoader.NativeImageCallBack; public class GroupAdapter extends BaseAdapter{ private List<ImageBean> list; private Point mPoint = new Point(0, 0);//用来封装ImageView的宽和高的对象 private GridView mGridView; protected LayoutInflater mInflater; @Override public int getCount() { return list.size(); } @Override public Object getItem(int position) { return list.get(position); } @Override public long getItemId(int position) { return position; } public GroupAdapter(Context context, List<ImageBean> list, GridView mGridView){ this.list = list; this.mGridView = mGridView; mInflater = LayoutInflater.from(context); } @Override public View getView(int position, View convertView, ViewGroup parent) { final ViewHolder viewHolder; ImageBean mImageBean = list.get(position); String path = mImageBean.getTopImagePath(); if(convertView == null){ viewHolder = new ViewHolder(); convertView = mInflater.inflate(R.layout.grid_group_item, null); viewHolder.mImageView = (MyImageView) convertView.findViewById(R.id.group_image); viewHolder.mTextViewTitle = (TextView) convertView.findViewById(R.id.group_title); viewHolder.mTextViewCounts = (TextView) convertView.findViewById(R.id.group_count); //用来监听ImageView的宽和高 viewHolder.mImageView.setOnMeasureListener(new OnMeasureListener() { @Override public void onMeasureSize(int width, int height) { mPoint.set(width, height); } }); convertView.setTag(viewHolder); }else{ viewHolder = (ViewHolder) convertView.getTag(); viewHolder.mImageView.setImageResource(R.drawable.friends_sends_pictures_no); } viewHolder.mTextViewTitle.setText(mImageBean.getFolderName()); viewHolder.mTextViewCounts.setText(Integer.toString(mImageBean.getImageCounts())); //给ImageView设置路径Tag,这是异步加载图片的小技巧 viewHolder.mImageView.setTag(path); //利用NativeImageLoader类加载本地图片 Bitmap bitmap = NativeImageLoader.getInstance().loadNativeImage(path, mPoint, new NativeImageCallBack() { @Override public void onImageLoader(Bitmap bitmap, String path) { ImageView mImageView = (ImageView) mGridView.findViewWithTag(path); if(bitmap != null && mImageView != null){ mImageView.setImageBitmap(bitmap); } } }); if(bitmap != null){ viewHolder.mImageView.setImageBitmap(bitmap); }else{ viewHolder.mImageView.setImageResource(R.drawable.friends_sends_pictures_no); } return convertView; } public static class ViewHolder{ public MyImageView mImageView; public TextView mTextViewTitle; public TextView mTextViewCounts; } }首先我们将每个item的图片路径设置Tag到该ImageView上面,然后利用NativeImageLoader来加载本地图片,但是我们显示的图片的宽和高可能远大于GirdView item中ImageView的大小,于是为了节省内存,我们需要对图片进行裁剪,需要对图片裁剪我们利用loadNativeImage(final String path, final Point mPoint, final NativeImageCallBack mCallBack)方法,我们就必须要获取ImageView的宽和高了
自定义MyImageView的代码,我们只需要设置OnMeasureListener监听,当MyImageView测量完毕之后,就会将测量的宽和高回调到onMeasureSize()中,然后我们可以根据MyImageView的大小来裁剪图片
package com.example.imagescan; import android.content.Context; import android.util.AttributeSet; import android.widget.ImageView; public class MyImageView extends ImageView { private OnMeasureListener onMeasureListener; public void setOnMeasureListener(OnMeasureListener onMeasureListener) { this.onMeasureListener = onMeasureListener; } public MyImageView(Context context, AttributeSet attrs) { super(context, attrs); } public MyImageView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); //将图片测量的大小回调到onMeasureSize()方法中 if(onMeasureListener != null){ onMeasureListener.onMeasureSize(getMeasuredWidth(), getMeasuredHeight()); } } public interface OnMeasureListener{ public void onMeasureSize(int width, int height); } }
上面这些代码就完成了第一个界面的功能了,接下来就是点击GridView的item跳转另一个界面来显示该文件夹下面的所有图片,功能跟第一个界面差不多,也是使用GridView来显示图片,第二个界面的布局代码我就不贴了,直接贴上界面的代码
package com.example.imagescan; import java.util.List; import android.app.Activity; import android.os.Bundle; import android.widget.GridView; import android.widget.Toast; public class ShowImageActivity extends Activity { private GridView mGridView; private List<String> list; private ChildAdapter adapter; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.show_image_activity); mGridView = (GridView) findViewById(R.id.child_grid); list = getIntent().getStringArrayListExtra("data"); adapter = new ChildAdapter(this, list, mGridView); mGridView.setAdapter(adapter); } @Override public void onBackPressed() { Toast.makeText(this, "选中 " + adapter.getSelectItems().size() + " item", Toast.LENGTH_LONG).show(); super.onBackPressed(); } }
GridView的item上面一个我们自定义的MyImageView用来显示图片,另外还有一个CheckBox来记录我们选中情况,Adapter的代码如下
package com.example.imagescan; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import android.content.Context; import android.graphics.Bitmap; import android.graphics.Point; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.BaseAdapter; import android.widget.CheckBox; import android.widget.CompoundButton; import android.widget.ImageView; import android.widget.CompoundButton.OnCheckedChangeListener; import android.widget.GridView; import com.example.imagescan.MyImageView.OnMeasureListener; import com.example.imagescan.NativeImageLoader.NativeImageCallBack; import com.nineoldandroids.animation.AnimatorSet; import com.nineoldandroids.animation.ObjectAnimator; public class ChildAdapter extends BaseAdapter { private Point mPoint = new Point(0, 0);//用来封装ImageView的宽和高的对象 /** * 用来存储图片的选中情况 */ private HashMap<Integer, Boolean> mSelectMap = new HashMap<Integer, Boolean>(); private GridView mGridView; private List<String> list; protected LayoutInflater mInflater; public ChildAdapter(Context context, List<String> list, GridView mGridView) { this.list = list; this.mGridView = mGridView; mInflater = LayoutInflater.from(context); } @Override public int getCount() { return list.size(); } @Override public Object getItem(int position) { return list.get(position); } @Override public long getItemId(int position) { return position; } @Override public View getView(final int position, View convertView, ViewGroup parent) { final ViewHolder viewHolder; String path = list.get(position); if(convertView == null){ convertView = mInflater.inflate(R.layout.grid_child_item, null); viewHolder = new ViewHolder(); viewHolder.mImageView = (MyImageView) convertView.findViewById(R.id.child_image); viewHolder.mCheckBox = (CheckBox) convertView.findViewById(R.id.child_checkbox); //用来监听ImageView的宽和高 viewHolder.mImageView.setOnMeasureListener(new OnMeasureListener() { @Override public void onMeasureSize(int width, int height) { mPoint.set(width, height); } }); convertView.setTag(viewHolder); }else{ viewHolder = (ViewHolder) convertView.getTag(); viewHolder.mImageView.setImageResource(R.drawable.friends_sends_pictures_no); } viewHolder.mImageView.setTag(path); viewHolder.mCheckBox.setOnCheckedChangeListener(new OnCheckedChangeListener() { @Override public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { //如果是未选中的CheckBox,则添加动画 if(!mSelectMap.containsKey(position) || !mSelectMap.get(position)){ addAnimation(viewHolder.mCheckBox); } mSelectMap.put(position, isChecked); } }); viewHolder.mCheckBox.setChecked(mSelectMap.containsKey(position) ? mSelectMap.get(position) : false); //利用NativeImageLoader类加载本地图片 Bitmap bitmap = NativeImageLoader.getInstance().loadNativeImage(path, mPoint, new NativeImageCallBack() { @Override public void onImageLoader(Bitmap bitmap, String path) { ImageView mImageView = (ImageView) mGridView.findViewWithTag(path); if(bitmap != null && mImageView != null){ mImageView.setImageBitmap(bitmap); } } }); if(bitmap != null){ viewHolder.mImageView.setImageBitmap(bitmap); }else{ viewHolder.mImageView.setImageResource(R.drawable.friends_sends_pictures_no); } return convertView; } /** * 给CheckBox加点击动画,利用开源库nineoldandroids设置动画 * @param view */ private void addAnimation(View view){ float [] vaules = new float[]{0.5f, 0.6f, 0.7f, 0.8f, 0.9f, 1.0f, 1.1f, 1.2f, 1.3f, 1.25f, 1.2f, 1.15f, 1.1f, 1.0f}; AnimatorSet set = new AnimatorSet(); set.playTogether(ObjectAnimator.ofFloat(view, "scaleX", vaules), ObjectAnimator.ofFloat(view, "scaleY", vaules)); set.setDuration(150); set.start(); } /** * 获取选中的Item的position * @return */ public List<Integer> getSelectItems(){ List<Integer> list = new ArrayList<Integer>(); for(Iterator<Map.Entry<Integer, Boolean>> it = mSelectMap.entrySet().iterator(); it.hasNext();){ Map.Entry<Integer, Boolean> entry = it.next(); if(entry.getValue()){ list.add(entry.getKey()); } } return list; } public static class ViewHolder{ public MyImageView mImageView; public CheckBox mCheckBox; } }
第二个界面的Adapter跟第一个界面差不多,无非多了一个CheckBox用来记录图片选择情况,我们只需要对CheckBox设置setOnCheckedChangeListener监听,微信的选中之后CheckBox有一个动画效果,所以我利用nineoldandroids动画库也给CheckBox加了一个动画效果,直接调用addAnimation()方法就能添加了,getSelectItems()方法就能获取我们选中的item的position了,知道了选中的position,其他的信息就都知道了,微信有对图片进行预览的功能,我这里就不添加了,如果有这个需求可以自行添加,给大家推荐一个https://github.com/chrisbanes/PhotoView
运行项目,效果如下
36079-4e3c-4b0a-a638-f604bfd572af.jpg" />
看起来还不错吧,采用的是异步读取图片,对图片进行了缓存和裁剪,使得在显示本地图片方面比较流畅,GridView滑动也挺流畅的,也有效的避免OOM的产生。
本文向大家介绍Android 下载网络图片并显示到本地,包括了Android 下载网络图片并显示到本地的使用技巧和注意事项,需要的朋友参考一下 Android下载网络图片的流程是: 发送网络请求->将图片以流的形式下载下来->将流转换为Bitmap并赋给ImageView控件。 注意点 最新的Android系统不可以在主线程上请求网络,需要使用线程来请求 下载图片属于耗时任务,最优做法是放在一个A
我们正在使用扫描操作符来执行表中的“加载更多”按钮。使用scan操作符,我们只需将新结果与以前的结果相加。然而,我们遇到了一些意想不到的行为。 为了简化,假设我们有以下代码: 生产: 而如果我们只是将扫描操作符移动到mergeMap/switchMap操作符中: 我们得到以下结果: 在第二个示例中,未执行扫描运算符。我希望switchMap/mergeMap只是将内部的可观察对象展平,扫描操作符通
我正在使用Xamarin开发一个应用程序(针对单个客户),该应用程序必须使用摄像头读取条形码(也可以选择使用外部蓝牙扫描仪),并用于测试华硕TF300T、LG4xP880和Nexus 7 second edition(目标机器)。 我测试了ScandIt和ZXing,但这是我在免费条形码扫描应用程序中得到的最好结果(我知道ZXing的库是一样的,但速度不一样)。我已经读到,我可以有目的地使用它,我
本文向大家介绍Android使用AsyncTask下载图片并显示进度条功能,包括了Android使用AsyncTask下载图片并显示进度条功能的使用技巧和注意事项,需要的朋友参考一下 在Android中实现异步任务机制有两种方式,Handler和AsyncTask。这篇文章给大家介绍Android使用AsyncTask下载图片并显示进度条功能。 AsyncTask下载图片并显示下载进度,异步类As
本文向大家介绍Android远程获取图片并本地缓存,包括了Android远程获取图片并本地缓存的使用技巧和注意事项,需要的朋友参考一下 对于客户端——服务器端应用,从远程获取图片算是经常要用的一个功能,而图片资源往往会消耗比较大的流量,对应用来说,如果处理不好这个问题,那会让用户很崩溃,不知不觉手机流量就用完了,等用户发现是你的应用消耗掉了他手机流量的话,那么可想而知你的应用将面临什么样的命运。
我刚刚编写了一个仅带有LinearLayout的简单Android应用程序。我将此LinearLayout用作类的属性和方法的本地对象(例如在onCreate方法中)。 第一种情况:作为财产的线性布局 第二种情况:作为方法对象的线性布局 在这两种情况下,都没有错误。然而,当我在模拟器或设备上运行时,第一个案例出现了如下错误。为什么?我认为这个问题是由方法引起的。在调用onCreate方法之前,该方