Android瀑布流照片墙实现,体验不规则排列的美感(2)

然后新建一个ImageLoader类,用于方便对图片进行管理,代码如下所示:

public class ImageLoader {

/**
  * 图片缓存技术的核心类,用于缓存所有下载好的图片,在程序内存达到设定值时会将最少最近使用的图片移除掉。
  */
 private static LruCache<String, Bitmap> mMemoryCache;

/**
  * ImageLoader的实例。
  */
 private static ImageLoader mImageLoader;

private ImageLoader() {
  // 获取应用程序最大可用内存
  int maxMemory = (int) Runtime.getRuntime().maxMemory();
  int cacheSize = maxMemory / 8;
  // 设置图片缓存大小为程序最大可用内存的1/8
  mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
   @Override
   protected int sizeOf(String key, Bitmap bitmap) {
    return bitmap.getByteCount();
   }
  };
 }

/**
  * 获取ImageLoader的实例。
  *
  * @return ImageLoader的实例。
  */
 public static ImageLoader getInstance() {
  if (mImageLoader == null) {
   mImageLoader = new ImageLoader();
  }
  return mImageLoader;
 }

/**
  * 将一张图片存储到LruCache中。
  *
  * @param key
  *            LruCache的键,这里传入图片的URL地址。
  * @param bitmap
  *            LruCache的键,这里传入从网络上下载的Bitmap对象。
  */
 public void addBitmapToMemoryCache(String key, Bitmap bitmap) {
  if (getBitmapFromMemoryCache(key) == null) {
   mMemoryCache.put(key, bitmap);
  }
 }

/**
  * 从LruCache中获取一张图片,如果不存在就返回null。
  *
  * @param key
  *            LruCache的键,这里传入图片的URL地址。
  * @return 对应传入键的Bitmap对象,或者null。
  */
 public Bitmap getBitmapFromMemoryCache(String key) {
  return mMemoryCache.get(key);
 }

public static int calculateInSampleSize(BitmapFactory.Options options,
   int reqWidth) {
  // 源图片的宽度
  final int width = options.outWidth;
  int inSampleSize = 1;
  if (width > reqWidth) {
   // 计算出实际宽度和目标宽度的比率
   final int widthRatio = Math.round((float) width / (float) reqWidth);
   inSampleSize = widthRatio;
  }
  return inSampleSize;
 }

public static Bitmap decodeSampledBitmapFromResource(String pathName,
   int reqWidth) {
  // 第一次解析将inJustDecodeBounds设置为true,来获取图片大小
  final BitmapFactory.Options options = new BitmapFactory.Options();
  options.inJustDecodeBounds = true;
  BitmapFactory.decodeFile(pathName, options);
  // 调用上面定义的方法计算inSampleSize值
  options.inSampleSize = calculateInSampleSize(options, reqWidth);
  // 使用获取到的inSampleSize值再次解析图片
  options.inJustDecodeBounds = false;
  return BitmapFactory.decodeFile(pathName, options);
 }

}

这里我们将ImageLoader类设成单例,并在构造函数中初始化了LruCache类,把它的最大缓存容量设为最大可用内存的1/8。然后又提供了其它几个方法可以操作LruCache,以及对图片进行压缩和读取。

接下来新建MyScrollView继承自ScrollView,代码如下所示:

public class MyScrollView extends ScrollView implements OnTouchListener {

/**
  * 每页要加载的图片数量
  */
 public static final int PAGE_SIZE = 15;

/**
  * 记录当前已加载到第几页
  */
 private int page;

/**
  * 每一列的宽度
  */
 private int columnWidth;

/**
  * 当前第一列的高度
  */
 private int firstColumnHeight;

/**
  * 当前第二列的高度
  */
 private int secondColumnHeight;

/**
  * 当前第三列的高度
  */
 private int thirdColumnHeight;

/**
  * 是否已加载过一次layout,这里onLayout中的初始化只需加载一次
  */
 private boolean loadOnce;

/**
  * 对图片进行管理的工具类
  */
 private ImageLoader imageLoader;

/**
  * 第一列的布局
  */
 private LinearLayout firstColumn;

/**
  * 第二列的布局
  */
 private LinearLayout secondColumn;

/**
  * 第三列的布局
  */
 private LinearLayout thirdColumn;

/**
  * 记录所有正在下载或等待下载的任务。
  */
 private static Set<LoadImageTask> taskCollection;

/**
  * MyScrollView下的直接子布局。
  */
 private static View scrollLayout;

/**
  * MyScrollView布局的高度。
  */
 private static int scrollViewHeight;

/**
  * 记录上垂直方向的滚动距离。
  */
 private static int lastScrollY = -1;

/**
  * 记录所有界面上的图片,用以可以随时控制对图片的释放。
  */
 private List<ImageView> imageViewList = new ArrayList<ImageView>();

/**
  * 在Handler中进行图片可见性检查的判断,以及加载更多图片的操作。
  */
 private static Handler handler = new Handler() {

public void 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 = new Message();
    message.obj = myScrollView;
    // 5毫秒后再次对滚动位置进行判断
    handler.sendMessageDelayed(message, 5);
   }
  };

};

/**
  * MyScrollView的构造函数。
  *
  * @param context
  * @param attrs
  */
 public MyScrollView(Context context, AttributeSet attrs) {
  super(context, attrs);
  imageLoader = ImageLoader.getInstance();
  taskCollection = new HashSet<LoadImageTask>();
  setOnTouchListener(this);
 }

/**
  * 进行一些关键性的初始化操作,获取MyScrollView的高度,以及得到第一列的宽度值。并在这里开始加载第一页的图片。
  */
 @Override
 protected void 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();
  }
 }

/**
  * 监听用户的触屏事件,如果用户手指离开屏幕则开始进行滚动检测。
  */
 @Override
 public boolean onTouch(View v, MotionEvent event) {
  if (event.getAction() == MotionEvent.ACTION_UP) {
   Message message = new Message();
   message.obj = this;
   handler.sendMessageDelayed(message, 5);
  }
  return false;
 }

/**
  * 开始加载下一页的图片,每张图片都会开启一个异步线程去下载。
  */
 public void 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 = new LoadImageTask();
     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();
  }
 }

/**
  * 遍历imageViewList中的每张图片,对图片的可见性进行检查,如果图片已经离开屏幕可见范围,则将图片替换成一张空图。
  */
 public void 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);
   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 = new LoadImageTask(imageView);
     task.execute(imageUrl);
    }
   } else {
    imageView.setImageResource(R.drawable.empty_photo);
   }
  }
 }

/**
  * 判断手机是否有SD卡。
  *
  * @return 有SD卡返回true,没有返回false。
  */
 private boolean hasSDCard() {
  return Environment.MEDIA_MOUNTED.equals(Environment
    .getExternalStorageState());
 }

/**
  * 异步下载图片的任务。
  *
  * @author guolin
  */
 class LoadImageTask extends AsyncTask<String, Void, Bitmap> {

/**
  * 图片的URL地址
  */
  private String mImageUrl;

/**
  * 可重复使用的ImageView
  */
  private ImageView mImageView;

public LoadImageTask() {
  }

/**
  * 将可重复使用的ImageView传入
  *
  * @param imageView
  */
  public LoadImageTask(ImageView imageView) {
   mImageView = imageView;
  }

@Override
  protected Bitmap doInBackground(String... params) {
   mImageUrl = params[0];
   Bitmap imageBitmap = imageLoader
     .getBitmapFromMemoryCache(mImageUrl);
   if (imageBitmap == null) {
    imageBitmap = loadImage(mImageUrl);
   }
   return imageBitmap;
  }

@Override
  protected void 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 加载到内存的图片。
  */
  private Bitmap loadImage(String imageUrl) {
   File imageFile = new File(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;
    }
   }
   return null;
  }

/**
  * 向ImageView中添加一张图片
  *
  * @param bitmap
  *            待添加的图片
  * @param imageWidth
  *            图片的宽度
  * @param imageHeight
  *            图片的高度
  */
  private void addImage(Bitmap bitmap, int imageWidth, int imageHeight) {
   LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
     imageWidth, imageHeight);
   if (mImageView != null) {
    mImageView.setImageBitmap(bitmap);
   } else {
    ImageView imageView = new ImageView(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);
    findColumnToAdd(imageView, imageHeight).addView(imageView);
    imageViewList.add(imageView);
   }
  }

/**
  * 找到此时应该添加图片的一列。原则就是对三列的高度进行判断,当前高度最小的一列就是应该添加的一列。
  *
  * @param imageView
  * @param imageHeight
  * @return 应该添加图片的一列
  */
  private LinearLayout 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地址。
  */
  private void downloadImage(String imageUrl) {
   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 = new BufferedInputStream(con.getInputStream());
    imageFile = new File(getImagePath(imageUrl));
    fos = new FileOutputStream(imageFile);
    bos = new BufferedOutputStream(fos);
    byte[] b = new byte[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 图片的本地存储路径。
  */
  private String getImagePath(String imageUrl) {
   int lastSlashIndex = imageUrl.lastIndexOf("/");
   String imageName = imageUrl.substring(lastSlashIndex + 1);
   String imageDir = Environment.getExternalStorageDirectory()
     .getPath() + "/PhotoWallFalls/";
   File file = new File(imageDir);
   if (!file.exists()) {
    file.mkdirs();
   }
   String imagePath = imageDir + imageName;
   return imagePath;
  }
 }

}

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:http://www.heiqu.com/f133398c7681c5026ff104be25208206.html