您的位置:首页 > 移动开发 > Android开发

Android瀑布流照片墙实现

2014-11-14 09:23 357 查看
瀑布流的布局方式虽然看起来好像排列的很随意,其实它是有排列规则的。整个界面会根据屏幕的宽度划分成等宽的若干列,由于手机的屏幕不是很大,这里我们就分成三列。每当需要添加一张图片时,会将这张图片的宽度压缩成和列一样宽,再按照同样的压缩比例对图片的高度进行压缩,然后在这三列中找出当前高度最小的一列,将图片添加到这一列中。之后每当需要添加一张新图片时,都去重复上面的操作,就会形成瀑布流格局的照片墙,示意图如下所示。



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

[java] view
plaincopy

package com.example.photowall;

import android.graphics.Bitmap;

import android.graphics.BitmapFactory;

import android.util.LruCache;

/**

* 对图片进行管理的工具类。

*

* @author Tony

*/

public class ImageLoader {

/**

* 图片缓存技术的核心类,用于缓存所有下载好的图片,在程序内存达到设定值时会将最少最近使用的图片移除掉。

*/

private static LruCache<String, Bitmap> mMemoryCache;

/**

* ImageLoader的实例。

*/

private static ImageLoader mImageLoader;

/**

* 所有图片url

*/

public final static String[] imageUrls = new String[] {

"http://img.my.csdn.net/uploads/201309/01/1378037235_3453.jpg",

"http://img.my.csdn.net/uploads/201309/01/1378037235_7476.jpg",

"http://img.my.csdn.net/uploads/201309/01/1378037235_9280.jpg",

"http://img.my.csdn.net/uploads/201309/01/1378037234_3539.jpg",

"http://img.my.csdn.net/uploads/201309/01/1378037234_6318.jpg" };

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,以及对图片进行压缩和读取。

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

[java] view
plaincopy

package com.example.photowall;

import java.io.BufferedInputStream;

import java.io.BufferedOutputStream;

import java.io.File;

import java.io.FileOutputStream;

import java.io.IOException;

import java.net.HttpURLConnection;

import java.net.URL;

import java.util.ArrayList;

import java.util.HashSet;

import java.util.List;

import java.util.Set;

import android.content.Context;

import android.graphics.Bitmap;

import android.os.AsyncTask;

import android.os.Environment;

import android.os.Handler;

import android.os.Message;

import android.util.AttributeSet;

import android.util.Log;

import android.view.MotionEvent;

import android.view.View;

import android.view.View.OnTouchListener;

import android.widget.ImageView;

import android.widget.ImageView.ScaleType;

import android.widget.LinearLayout;

import android.widget.ScrollView;

import android.widget.Toast;

/**

* 自定义的ScrollView,在其中动态地对图片进行添加。

*

*/

public class PhotoWallScrollView 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) {

PhotoWallScrollView myScrollView = (PhotoWallScrollView) 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);

}

};

};

/**

* PhotoWallScrollView的构造函数。

*

* @param context

* @param attrs

*/

public PhotoWallScrollView(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 < ImageLoader.imageUrls.length) {

Toast.makeText(getContext(), "正在加载...", Toast.LENGTH_SHORT).show();

if (endIndex > ImageLoader.imageUrls.length) {

endIndex = ImageLoader.imageUrls.length;

}

for (int i = startIndex; i < endIndex; i++) {

LoadImageTask task = new LoadImageTask();

taskCollection.add(task);

task.execute(ImageLoader.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) {

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 = 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;

}

}

}

设置瀑布流的布局方式,如下所示:

[java] view
plaincopy

<com.example.photowall.PhotoWallScrollView xmlns:android="http://schemas.android.com/apk/res/android"

android:id="@+id/my_scroll_view"

android:layout_width="match_parent"

android:layout_height="match_parent" >

<LinearLayout

android:layout_width="match_parent"

android:layout_height="wrap_content"

android:orientation="horizontal" >

<LinearLayout

android:id="@+id/first_column"

android:layout_width="0dp"

android:layout_height="wrap_content"

android:layout_weight="1"

android:orientation="vertical" >

</LinearLayout>

<LinearLayout

android:id="@+id/second_column"

android:layout_width="0dp"

android:layout_height="wrap_content"

android:layout_weight="1"

android:orientation="vertical" >

</LinearLayout>

<LinearLayout

android:id="@+id/third_column"

android:layout_width="0dp"

android:layout_height="wrap_content"

android:layout_weight="1"

android:orientation="vertical" >

</LinearLayout>

</LinearLayout>

</com.example.photowall.PhotoWallScrollView>

使用了刚才编写好的PhotoWallScrollView作为根布局,然后在里面放入了一个直接子布局LinearLayout用于统计当前滑动布局的高度,然后在这个布局下又添加了三个等宽的LinearLayout分别作为第一列、第二列和第三列的布局,在AndroidManifest.xml中添加以下权限:

[java] view
plaincopy

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

<uses-permission android:name="android.permission.INTERNET" />

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: