过年没回家,宅在家里看了很多博客,顺手写一下自己的一些收货..
android中的瀑布流的实现原理,来自郭大神的CSDN
实现原理:瀑布流的布局方式虽然看起来好像排列的很随意,其实它是有很科学的排列规则的。整个界面会根据屏幕的宽度划分成等宽的若干列,由于手机的屏幕不是很大,这里我们就分成三列。每当需要添加一张图片时,会将这张图片的宽度压缩成和列一样宽,再按照同样的压缩比例对图片的高度进行压缩,然后在这三列中找出当前高度最小的一列,将图片添加到这一列中。之后每当需要添加一张新图片时,都去重复上面的操作,就会形成瀑布流格局的照片墙
以下是个人收货......
具体实现
- 这里我们用到了LRU算法去管理图片缓存的问题,之前我们一直用比较流行的软引用和若引用,但是从android2,3开始,系统封装好了一个L实现LRU算法的类,LruCache类,今天,我们就来看看该类的用法,
- 通过分析源代码,发现LRU其实就是我们之前用LinkHashMap包裹软引用的,只是系统加了个壳而已,具体源码分析请移步http://www.apkbus.com/blog-56480-53922.html
- LruCache创建,根据当前应用的最大内存/8
-
// 获取应用程序最大可用内存
-
int maxMemory =(int)Runtime.getRuntime().maxMemory(); int cacheSize = maxMemory /8; // 设置图片缓存大小为程序最大可用内存的1/8 mMemoryCache =newLruCache<String,Bitmap>(cacheSize){ @Override protectedint sizeOf(String key,Bitmap bitmap){ return bitmap.getByteCount(); } };
-
- 存和取
-
-
/** * 将一张图片存储到LruCache中。 * * @param key * LruCache的键,这里传入图片的URL地址。 * @param bitmap * LruCache的键,这里传入从网络上下载的Bitmap对象。 */ publicvoid addBitmapToMemoryCache(String key,Bitmap bitmap){ if(getBitmapFromMemoryCache(key)==null){ mMemoryCache.put(key, bitmap); } } /** * 从LruCache中获取一张图片,如果不存在就返回null。 * * @param key * LruCache的键,这里传入图片的URL地址。 * @return 对应传入键的Bitmap对象,或者null。 */ publicBitmap getBitmapFromMemoryCache(String key){ return mMemoryCache.get(key); }
-
-
- 计算图片压缩比
-
-
/** * 这里是为了实现瀑布流效果 计算图片的压缩比 * @param options 图片参数 * @param reqWidth 瀑布流的宽度 * @return */ publicstaticint calculateInSampleSize(BitmapFactory.Options options, int reqWidth){ // 源图片的宽度 finalint width = options.outWidth; int inSampleSize =1; if(width > reqWidth){ // 计算出实际宽度和目标宽度的比率 finalint widthRatio =Math.round((float) width /(float) reqWidth); inSampleSize = widthRatio; } return inSampleSize; }
-
-
- 加载图片
-
-
/** * 将图片压缩并返回 * @param pathName 图片地址 * @param reqWidth * @return */ publicstaticBitmap decodeSampledBitmapFromResource(String pathName, int reqWidth){ // 第一次解析将inJustDecodeBounds设置为true,来获取图片大小 finalBitmapFactory.Options options =newBitmapFactory.Options(); options.inJustDecodeBounds =true; BitmapFactory.decodeFile(pathName, options); // 调用上面定义的方法计算inSampleSize值 options.inSampleSize = calculateInSampleSize(options, reqWidth); // 使用获取到的inSampleSize值再次解析图片 options.inJustDecodeBounds =false; returnBitmapFactory.decodeFile(pathName, options); }
-
-
- 自定义控件MyScollView,让界面实现滚动的关键
- 它是一页页加载数据的,一页加载固定张数图片,只要用户滑倒了开头,则加载一整页数据
- 加载第一页图片,在layout方法里面绘制,把三列的高度绘制出来,然后加载第一批15张图片
-
-
/** * 进行一些关键性的初始化操作,获取MyScrollView的高度,以及得到第一列的宽度值。并在这里开始加载第一页的图片。 */ @Override protectedvoid onLayout(boolean changed,int l,int t,int r,int b){ super.onLayout(changed, l, t, r, b); if(changed &&!loadOnce){ scrollViewHeight = getHeight(); scrollLayout = getChildAt(0); firstColumn =(LinearLayout) findViewById(R.id.first_column); secondColumn =(LinearLayout) findViewById(R.id.second_column); thirdColumn =(LinearLayout) findViewById(R.id.third_column); columnWidth = firstColumn.getWidth(); loadOnce =true; loadMoreImages(); } }
-
-
- 开始加载第一页的15张图片,往线程池中添加线程任务
-
-
/** * 开始加载下一页的图片,每张图片都会开启一个异步线程去下载。 */ publicvoid loadMoreImages(){ if(hasSDCard()){ //这里有一个算术 int startIndex = page * PAGE_SIZE; int endIndex = page * PAGE_SIZE + PAGE_SIZE; if(startIndex <Images.imageUrls.length){ Toast.makeText(getContext(),"正在加载...",Toast.LENGTH_SHORT).show(); if(endIndex >Images.imageUrls.length){ endIndex =Images.imageUrls.length; } for(int i = startIndex; i < endIndex; i++){ LoadImageTask task =newLoadImageTask(); taskCollection.add(task); task.execute(Images.imageUrls[i]); } page++; }else{ Toast.makeText(getContext(),"已没有更多图片",Toast.LENGTH_SHORT).show(); } }else{ Toast.makeText(getContext(),"未发现SD卡",Toast.LENGTH_SHORT).show(); } }
-
-
- 用户滚动手指,加载下一页
-
-
/** * 监听用户的触屏事件,如果用户手指离开屏幕则开始进行滚动检测。 */ @Override publicboolean onTouch(View v,MotionEvent event){ if(event.getAction()==MotionEvent.ACTION_UP){ Message message =newMessage(); message.obj =this; handler.sendMessageDelayed(message,5); } returnfalse; }
-
-
- 加载更多图片,这里判断是否滑动结束的方法用的是每隔5毫秒发消息,判断两次位置是否相同,相同则认为停止滑动
-
-
/** * 在Handler中进行图片可见性检查的判断,以及加载更多图片的操作。 */ privatestaticHandler handler =newHandler(){ publicvoid handleMessage(android.os.Message msg){ MyScrollView myScrollView =(MyScrollView) msg.obj; int scrollY = myScrollView.getScrollY(); // 如果当前的滚动位置和上次相同,表示已停止滚动 并且滚动到最底部拉 if(scrollY == lastScrollY){ // 当滚动的最底部,并且当前没有正在下载的任务时,开始加载下一页的图片 if(scrollViewHeight + scrollY >= scrollLayout.getHeight() && taskCollection.isEmpty()){ myScrollView.loadMoreImages(); } myScrollView.checkVisibility(); }else{ lastScrollY = scrollY; Message message =newMessage(); message.obj = myScrollView; // 5毫秒后再次对滚动位置进行判断 handler.sendMessageDelayed(message,5); } };
-
-
- 把不需要的图片进行隐藏起来,节省内存空间
注意 这里的都是getScrollY为负值 要比的是数值 所以本来是borderBottom < getScrollY()变成了borderBottom > getScrollY()了,另外一个也是一个道理
-
/** * 遍历imageViewList中的每张图片,对图片的可见性进行检查,如果图片已经离开屏幕可见范围,则将图片替换成一张空图。 */ publicvoid checkVisibility(){ for(int i =0; i < imageViewList.size(); i++){ ImageView imageView = imageViewList.get(i); int borderTop =(Integer) imageView.getTag(R.string.border_top); int borderBottom =(Integer) imageView.getTag(R.string.border_bottom); //注意 这里的都是getScrollY为负值 要比的是数值 所以本来是borderBottom < getScrollY()变成了borderBottom > getScrollY()了,另外一个也是一个道理 if(borderBottom > getScrollY()&& borderTop < getScrollY()+ scrollViewHeight){ String imageUrl =(String) imageView.getTag(R.string.image_url); Bitmap bitmap = imageLoader.getBitmapFromMemoryCache(imageUrl); if(bitmap !=null){ imageView.setImageBitmap(bitmap); }else{ LoadImageTask task =newLoadImageTask(imageView); task.execute(imageUrl); } }else{ imageView.setImageResource(R.drawable.empty_photo); } } }
-
- 接下来是一个自定义的AsyncTask类
-
-
classLoadImageTaskextendsAsyncTask<String,Void,Bitmap>{ /** * 图片的URL地址 */ privateString mImageUrl; /** * 可重复使用的ImageView */ privateImageView mImageView; publicLoadImageTask(){ } /** * 将可重复使用的ImageView传入 * * @param imageView */ publicLoadImageTask(ImageView imageView){ mImageView = imageView; } @Override protectedBitmap doInBackground(String... params){ mImageUrl = params[0]; Bitmap imageBitmap = imageLoader.getBitmapFromMemoryCache(mImageUrl); if(imageBitmap ==null){ imageBitmap = loadImage(mImageUrl); } return imageBitmap; } @Override protectedvoid onPostExecute(Bitmap bitmap){ if(bitmap !=null){ double ratio = bitmap.getWidth()/(columnWidth *1.0); int scaledHeight =(int)(bitmap.getHeight()/ ratio); addImage(bitmap, columnWidth, scaledHeight); } taskCollection.remove(this); } /** * 根据传入的URL,对图片进行加载。如果这张图片已经存在于SD卡中,则直接从SD卡里读取,否则就从网络上下载。 * * @param imageUrl * 图片的URL地址 * @return 加载到内存的图片。 */ privateBitmap loadImage(String imageUrl){ File imageFile =newFile(getImagePath(imageUrl)); if(!imageFile.exists()){ downloadImage(imageUrl); } if(imageUrl !=null){ Bitmap bitmap =ImageLoader.decodeSampledBitmapFromResource(imageFile.getPath(), columnWidth); if(bitmap !=null){ imageLoader.addBitmapToMemoryCache(imageUrl, bitmap); return bitmap; } } returnnull; } /** * 向ImageView中添加一张图片 * * @param bitmap * 待添加的图片 * @param imageWidth * 图片的宽度 * @param imageHeight * 图片的高度 */ privatevoid addImage(Bitmap bitmap,int imageWidth,int imageHeight){ LinearLayout.LayoutParams params =newLinearLayout.LayoutParams(imageWidth, imageHeight); if(mImageView !=null){ mImageView.setImageBitmap(bitmap); }else{ ImageView imageView =newImageView(getContext()); imageView.setLayoutParams(params); imageView.setImageBitmap(bitmap); imageView.setScaleType(ScaleType.FIT_XY); imageView.setPadding(5,5,5,5); imageView.setTag(R.string.image_url, mImageUrl); imageView.setOnClickListener(newOnClickListener(){ @Override publicvoid onClick(View v){ Intent intent =newIntent(getContext(),ImageDetailsActivity.class); intent.putExtra("image_path", getImagePath(mImageUrl)); getContext().startActivity(intent); } }); findColumnToAdd(imageView, imageHeight).addView(imageView); imageViewList.add(imageView); } } /** * 找到此时应该添加图片的一列。原则就是对三列的高度进行判断,当前高度最小的一列就是应该添加的一列。 * * @param imageView * @param imageHeight * @return 应该添加图片的一列 */ privateLinearLayout findColumnToAdd(ImageView imageView,int imageHeight){ if(firstColumnHeight <= secondColumnHeight){ if(firstColumnHeight <= thirdColumnHeight){ imageView.setTag(R.string.border_top, firstColumnHeight); firstColumnHeight += imageHeight; imageView.setTag(R.string.border_bottom, firstColumnHeight); return firstColumn; } imageView.setTag(R.string.border_top, thirdColumnHeight); thirdColumnHeight += imageHeight; imageView.setTag(R.string.border_bottom, thirdColumnHeight); return thirdColumn; }else{ if(secondColumnHeight <= thirdColumnHeight){ imageView.setTag(R.string.border_top, secondColumnHeight); secondColumnHeight += imageHeight; imageView.setTag(R.string.border_bottom, secondColumnHeight); return secondColumn; } imageView.setTag(R.string.border_top, thirdColumnHeight); thirdColumnHeight += imageHeight; imageView.setTag(R.string.border_bottom, thirdColumnHeight); return thirdColumn; } } /** * 将图片下载到SD卡缓存起来。 * * @param imageUrl * 图片的URL地址。 */ privatevoid downloadImage(String imageUrl){ if(Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)){ Log.d("TAG","monted sdcard"); }else{ Log.d("TAG","has no sdcard"); } HttpURLConnection con =null; FileOutputStream fos =null; BufferedOutputStream bos =null; BufferedInputStream bis =null; File imageFile =null; try{ URL url =new URL(imageUrl); con =(HttpURLConnection) url.openConnection(); con.setConnectTimeout(5*1000); con.setReadTimeout(15*1000); con.setDoInput(true); con.setDoOutput(true); bis =newBufferedInputStream(con.getInputStream()); imageFile =newFile(getImagePath(imageUrl)); fos =newFileOutputStream(imageFile); bos =newBufferedOutputStream(fos); byte[] b =newbyte[1024]; int length; while((length = bis.read(b))!=-1){ bos.write(b,0, length); bos.flush(); } }catch(Exception e){ e.printStackTrace(); }finally{ try{ if(bis !=null){ bis.close(); } if(bos !=null){ bos.close(); } if(con !=null){ con.disconnect(); } }catch(IOException e){ e.printStackTrace(); } } if(imageFile !=null){ Bitmap bitmap =ImageLoader.decodeSampledBitmapFromResource(imageFile.getPath(), columnWidth); if(bitmap !=null){ imageLoader.addBitmapToMemoryCache(imageUrl, bitmap); } } } /** * 获取图片的本地存储路径。 * * @param imageUrl * 图片的URL地址。 * @return 图片的本地存储路径。 */ privateString getImagePath(String imageUrl){ int lastSlashIndex = imageUrl.lastIndexOf("/"); String imageName = imageUrl.substring(lastSlashIndex +1); String imageDir =Environment.getExternalStorageDirectory().getPath() +"/PhotoWallFalls/"; File file =newFile(imageDir); if(!file.exists()){ file.mkdirs(); } String imagePath = imageDir + imageName; return imagePath; } }
-
-