安卓中的瀑布流

    过年没回家,宅在家里看了很多博客,顺手写一下自己的一些收货..

    android中的瀑布流的实现原理,来自郭大神的CSDN
    实现原理:瀑布流的布局方式虽然看起来好像排列的很随意,其实它是有很科学的排列规则的。整个界面会根据屏幕的宽度划分成等宽的若干列,由于手机的屏幕不是很大,这里我们就分成三列。每当需要添加一张图片时,会将这张图片的宽度压缩成和列一样宽,再按照同样的压缩比例对图片的高度进行压缩,然后在这三列中找出当前高度最小的一列,将图片添加到这一列中。之后每当需要添加一张新图片时,都去重复上面的操作,就会形成瀑布流格局的照片墙
以下是个人收货......
具体实现
  1.     这里我们用到了LRU算法去管理图片缓存的问题,之前我们一直用比较流行的软引用和若引用,但是从android2,3开始,系统封装好了一个L实现LRU算法的类,LruCache类,今天,我们就来看看该类的用法,
  2. 通过分析源代码,发现LRU其实就是我们之前用LinkHashMap包裹软引用的,只是系统加了个壳而已,具体源码分析请移步http://www.apkbus.com/blog-56480-53922.html
  3. LruCache创建,根据当前应用的最大内存/8
      1. // 获取应用程序最大可用内存
      2. 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();
        }
        };
       
  4. 存和取
      1. /**
        * 将一张图片存储到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);
        }
       
  5. 计算图片压缩比
      1. /**
        * 这里是为了实现瀑布流效果 计算图片的压缩比
        * @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;
        }
       
  6. 加载图片
      1. /**
        * 将图片压缩并返回
        * @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);
        }
       
  7. 自定义控件MyScollView,让界面实现滚动的关键 
    1. 它是一页页加载数据的,一页加载固定张数图片,只要用户滑倒了开头,则加载一整页数据
    2. 加载第一页图片,在layout方法里面绘制,把三列的高度绘制出来,然后加载第一批15张图片
        1. /**
          * 进行一些关键性的初始化操作,获取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();
          }
          }
        2.  
    3. 开始加载第一页的15张图片,往线程池中添加线程任务
        1. /**
          * 开始加载下一页的图片,每张图片都会开启一个异步线程去下载。
          */
          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();
          }
          }
         
    4. 用户滚动手指,加载下一页
        1. /**
          * 监听用户的触屏事件,如果用户手指离开屏幕则开始进行滚动检测。
          */
          @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. 加载更多图片,这里判断是否滑动结束的方法用的是每隔5毫秒发消息,判断两次位置是否相同,相同则认为停止滑动
        1. /**
          * 在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);
          }
          };
         
    6. 把不需要的图片进行隐藏起来,节省内存空间
      注意 这里的都是getScrollY为负值 要比的是数值 所以本来是borderBottom < getScrollY()变成了borderBottom > getScrollY()了,另外一个也是一个道理
       
      1. /**
        * 遍历imageViewList中的每张图片,对图片的可见性进行检查,如果图片已经离开屏幕可见范围,则将图片替换成一张空图。
        */
        publicvoid checkVisibility(){
        for(int i =0; i &lt; 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 &lt; getScrollY()变成了borderBottom &gt; getScrollY()了,另外一个也是一个道理
        if(borderBottom &gt; getScrollY()&& borderTop &lt; 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);
        }
        }
        }
       
    7. 接下来是一个自定义的AsyncTask类
        1. classLoadImageTaskextendsAsyncTask&lt;String,Void,Bitmap&gt;{
          /**
          * 图片的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 &lt;= secondColumnHeight){
          if(firstColumnHeight &lt;= 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 &lt;= 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;
          }
          }
         
 





静以修身 俭以养德
原文地址:https://www.cnblogs.com/Android-MR-wang/p/4297258.html