照片墙的实现,是需要往手机里面添加很多图片的,如果没有对资源进行合理的释放,程序很快就会出现OOM.所以需要用到LruCache算法来缓存图片.
1,首先是图片资源类,这个类中包含了很多图片链接.
public class AllImages {
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",
"http://img.my.csdn.net/uploads/201309/01/1378037194_2965.jpg",
"http://img.my.csdn.net/uploads/201309/01/1378037193_1687.jpg",
"http://img.my.csdn.net/uploads/201309/01/1378037193_1286.jpg",
"http://img.my.csdn.net/uploads/201309/01/1378037192_8379.jpg",
"http://img.my.csdn.net/uploads/201309/01/1378037178_9374.jpg",
"http://img.my.csdn.net/uploads/201309/01/1378037177_1254.jpg",
"http://img.my.csdn.net/uploads/201309/01/1378037177_6203.jpg",
"http://img.my.csdn.net/uploads/201309/01/1378037152_6352.jpg",
"http://img.my.csdn.net/uploads/201309/01/1378037151_9565.jpg",
"http://img.my.csdn.net/uploads/201309/01/1378037151_7904.jpg",
"http://img.my.csdn.net/uploads/201309/01/1378037148_7104.jpg",
"http://img.my.csdn.net/uploads/201309/01/1378037129_8825.jpg",
"http://img.my.csdn.net/uploads/201309/01/1378037128_5291.jpg",
"http://img.my.csdn.net/uploads/201309/01/1378037128_3531.jpg",
"http://img.my.csdn.net/uploads/201309/01/1378037127_1085.jpg",
"http://img.my.csdn.net/uploads/201309/01/1378037095_7515.jpg",
"http://img.my.csdn.net/uploads/201309/01/1378037094_8001.jpg",
"http://img.my.csdn.net/uploads/201309/01/1378037093_7168.jpg",
"http://img.my.csdn.net/uploads/201309/01/1378037091_4950.jpg",
"http://img.my.csdn.net/uploads/201308/31/1377949643_6410.jpg",
"http://img.my.csdn.net/uploads/201308/31/1377949642_6939.jpg",
"http://img.my.csdn.net/uploads/201308/31/1377949630_4505.jpg",
"http://img.my.csdn.net/uploads/201308/31/1377949630_4593.jpg",
"http://img.my.csdn.net/uploads/201308/31/1377949629_7309.jpg",
"http://img.my.csdn.net/uploads/201308/31/1377949629_8247.jpg",
"http://img.my.csdn.net/uploads/201308/31/1377949615_1986.jpg",
"http://img.my.csdn.net/uploads/201308/31/1377949614_8482.jpg",
"http://img.my.csdn.net/uploads/201308/31/1377949614_3743.jpg",
"http://img.my.csdn.net/uploads/201308/31/1377949614_4199.jpg",
"http://img.my.csdn.net/uploads/201308/31/1377949599_3416.jpg",
"http://img.my.csdn.net/uploads/201308/31/1377949599_5269.jpg",
"http://img.my.csdn.net/uploads/201308/31/1377949598_7858.jpg",
"http://img.my.csdn.net/uploads/201308/31/1377949598_9982.jpg",
"http://img.my.csdn.net/uploads/201308/31/1377949578_2770.jpg",
"http://img.my.csdn.net/uploads/201308/31/1377949578_8744.jpg",
"http://img.my.csdn.net/uploads/201308/31/1377949577_5210.jpg",
"http://img.my.csdn.net/uploads/201308/31/1377949577_1998.jpg",
"http://img.my.csdn.net/uploads/201308/31/1377949482_8813.jpg",
"http://img.my.csdn.net/uploads/201308/31/1377949481_6577.jpg",
"http://img.my.csdn.net/uploads/201308/31/1377949480_4490.jpg",
"http://img.my.csdn.net/uploads/201308/31/1377949455_6792.jpg",
"http://img.my.csdn.net/uploads/201308/31/1377949455_6345.jpg",
"http://img.my.csdn.net/uploads/201308/31/1377949442_4553.jpg",
"http://img.my.csdn.net/uploads/201308/31/1377949441_8987.jpg",
"http://img.my.csdn.net/uploads/201308/31/1377949441_5454.jpg",
"http://img.my.csdn.net/uploads/201308/31/1377949454_6367.jpg",
"http://img.my.csdn.net/uploads/201308/31/1377949442_4562.jpg" };
}
2.有了图片,还需要一个图片的工具类.新建一个ImageTools.
//图片处理的工具类
public class ImageTools {
//图片缓存,用于缓存所有下载好的图片
private static LruCache<String, Bitmap> mMemoryCache;
//ImageTools实例
private static ImageTools imageTools;
public ImageTools() {
//获取应用程序的最大可用内存
int maxMemory=(int)Runtime.getRuntime().maxMemory();
int cacheMemory=maxMemory/8;
mMemoryCache=new LruCache<String, Bitmap>(cacheMemory){
@Override
protected int sizeOf(String key, Bitmap bitmap) {
return bitmap.getByteCount();
}
};
}
//获取ImageTools的实例
public static ImageTools getInstance(){
if(imageTools==null){
imageTools=new ImageTools();
}
return imageTools;
}
//将图片放入LruCache中
public void addBitmapToMemoryCache(String key,Bitmap bitmap){
if(getBitmapFromMemoryCache(key)==null){
mMemoryCache.put(key, bitmap);
}
}
//从LruCache中获取一张图片,如果不存在就返回null
public Bitmap getBitmapFromMemoryCache(String key){
return mMemoryCache.get(key);
}
//求出图片需要压缩的比例
public static int calculateInSampleSize(BitmapFactory.Options option,int reqWidth){
//得到原图片的宽度
final int width=option.outWidth;
int inSampleSize=1;
if(width>reqWidth){
//计算出实际宽度与目标宽度的比例
final int widthRadio=Math.round((float)width/(float)reqWidth);
inSampleSize=widthRadio;
}
return inSampleSize;
}
//压缩图片
public static Bitmap decodeSampedBitmapFromResource(String pathName,int reqWidth){
// 第一次解析将inJustDecodeBounds设置为true,来获取图片大小
final BitmapFactory.Options options=new Options();
options.inJustDecodeBounds=true;
BitmapFactory.decodeFile(pathName, options);
options.inSampleSize = calculateInSampleSize(options, reqWidth);
// 使用获取到的inSampleSize值再次解析图片
options.inJustDecodeBounds = false;
return BitmapFactory.decodeFile(pathName, options);
}
}
这里将ImageTools设为单例,并在构造函数中初始化了LruCache类,提供了操作LruCache的读取和增加方法 以及两个对图片进行处理的方法.
3.接下来就是最重要的MyScrollView类.核心类.
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
private boolean loadOnce;
//对图片进行管理的工具类
private ImageTools imageTools;
//第一列的布局
private LinearLayout firstColumn;
//第二列布局
private LinearLayout secondColumn;
//第三列布局
private LinearLayout thirdColumn;
//MyScrollView下的子布局
private static View scrollLayout;
//MyScrollView的高度
private static int scrollViewHeight;
//记录上次垂直滚动的距离
private static int lastScrollY=-1;
//记录界面上所有的图片
private List<ImageView> imageList=new ArrayList<ImageView>();
//记录所有正在下载的任务
private static Set<LoadImageTask> imageTaskList;
private static Handler mh=new Handler(){
@Override
public void handleMessage(Message msg) {
MyScrollView mScrollView=(MyScrollView) msg.obj;
int scrollY=mScrollView.getScrollY();
if(scrollY==lastScrollY){ //如果当前滚动位置与上次滚动位置相同,则说明滚动停止
// 当滚动的最底部,并且当前没有正在下载的任务时,开始加载下一页的图片
if(scrollViewHeight+scrollY>=scrollLayout.getHeight()&&imageTaskList.isEmpty()){
mScrollView.loadMoreImage();
}
mScrollView.checkVisiblility();
}
else{
lastScrollY=scrollY;
Message message = new Message();
message.obj = mScrollView;
// 5毫秒后再次对滚动位置进行判断
mh.sendMessageDelayed(message, 5);
}
}
};
public MyScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
imageTools=ImageTools.getInstance();
imageTaskList=new HashSet<MyScrollView.LoadImageTask>();
setOnTouchListener(this);
}
@Override
public boolean onTouch(View v, MotionEvent event) {
if(event.getAction()==MotionEvent.ACTION_UP){
Message msg=new Message();
msg.obj=this;
mh.sendMessageDelayed(msg,5);
}
return false;
}
@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;
loadMoreImage();
}
}
//加载更多图片
public void loadMoreImage(){
if(hasSDCard()){
int startIndex=PAGE_SIZE*page;
int endIndex=PAGE_SIZE*page+PAGE_SIZE;
if(startIndex<AllImages.imageUrls.length){ //判断图片是否已经加载完毕
Toast.makeText(getContext(), "正在加载",Toast.LENGTH_SHORT).show();
if(endIndex>AllImages.imageUrls.length){
endIndex=AllImages.imageUrls.length;
}
for (int i = startIndex; i < endIndex; i++) {
LoadImageTask task=new LoadImageTask();
imageTaskList.add(task);
task.execute(AllImages.imageUrls[i]);
}
page++;
}
else{
Toast.makeText(getContext(), "加载完毕",Toast.LENGTH_SHORT).show();
}
}
else{
Toast.makeText(getContext(), "没有内存卡",Toast.LENGTH_SHORT).show();
}
}
//对图片的可见性进行检查,如果已经移出视线外,则将图片设为一张空白图片
public void checkVisiblility(){
for (int i = 0; i < imageList.size(); i++) {
ImageView imageView=imageList.get(i);
int border_top=(Integer) imageView.getTag(R.string.border_top);
int border_bottom=(Integer) imageView.getTag(R.string.border_bottom);
if(border_bottom>getScrollY()&&border_top<getScrollY()+scrollViewHeight){ //检查是不是在可见视图之内
String imageUrl=(String) imageView.getTag(R.string.image_url);
Bitmap imageBitmap=imageTools.getBitmapFromMemoryCache(imageUrl);
if(imageBitmap==null){
LoadImageTask task=new LoadImageTask(imageView);
task.execute(imageUrl);
}
else{
imageView.setImageBitmap(imageBitmap);
}
}
else{ //如果已经不再屏幕内
imageView.setImageResource(R.drawable.ic_launcher);
}
}
}
//判断手机是否有SD卡
private boolean hasSDCard(){
return Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState());
}
//异步加载图片的任务
class LoadImageTask extends AsyncTask<String, Void, Bitmap>{
//图片地址Url
private String imageUrl;
//可重复使用的ImageView
private ImageView mImageView;
public LoadImageTask() {
}
//将可重复使用的ImageView传入
public LoadImageTask(ImageView imageView) {
this.mImageView=imageView;
}
@Override
protected Bitmap doInBackground(String... params) { //根据imageUrl作为key值查找Bitmap,如果没有,调用loadImage去加载
imageUrl=params[0];
Bitmap imageBitmap=imageTools.getBitmapFromMemoryCache(imageUrl);
if(imageBitmap==null){
imageBitmap=loadImage(imageUrl);
}
return imageBitmap;
}
@Override
protected void onPostExecute(Bitmap imageBitmap) {
if(imageBitmap!=null){
double ratio=imageBitmap.getWidth()/(columnWidth*1.0); //得到宽度的压缩比
int scaleHeight=(int)(imageBitmap.getHeight()/ratio); //根据宽度的压缩比 得到这张图片的高度
addImage(imageBitmap, columnWidth, scaleHeight);
}
imageTaskList.remove(this);
}
//根据图片的Url去加载图片 首先判断该图片是否在内存卡里面,如果没有,则去下载,如果有,则经过压缩处理后返回
private Bitmap loadImage(String imageUrl){
File imageFile=new File(getImagePath(imageUrl));
if(!imageFile.exists()){
downloadImage(imageUrl);
}
if(imageUrl!=null){
Bitmap bitmap=ImageTools.decodeSampedBitmapFromResource(imageFile.getPath(), columnWidth);
if(bitmap!=null){
imageTools.addBitmapToMemoryCache(imageUrl, bitmap);
return bitmap;
}
}
return null;
}
//向ImageView中添加一张图片
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,imageUrl);
findColumnToAdd(imageView, imageHeight).addView(imageView);
imageList.add(imageView);
}
}
//找到应该添加图片的一列,选取三列中高度最小的一列返回.
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;
}
else{
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;
}
else{
imageView.setTag(R.string.border_top,thirdColumnHeight);
thirdColumnHeight+=imageHeight;
imageView.setTag(R.string.border_bottom,thirdColumnHeight);
return thirdColumn;
}
}
}
//将图片下载到SD卡缓存
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 (MalformedURLException e) {
e.printStackTrace();
} catch (IOException 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){
//裁剪图片
Log.e("info", "path---"+imageFile.getPath());
Bitmap bitmap=ImageTools.decodeSampedBitmapFromResource(imageFile.getPath(), columnWidth);
if(bitmap!=null){
imageTools.addBitmapToMemoryCache(imageUrl, bitmap);
}
}
}
}
//获取图片的本地缓存路径
private String getImagePath(String imageUrl){
int lastIndex=imageUrl.lastIndexOf("/");
String imageName=imageUrl.substring(lastIndex+1);
String imageDir=Environment.getExternalStorageDirectory().getPath()+"/PhotoWallFall/";
File file=new File(imageDir);
if(!file.exists()){
file.mkdirs();
}
String imagePath=imageDir+imageName;
return imagePath;
}
}
这个自定义控件是继承自ScrollView类的,这样就允许用户可以通过滚动的方式浏览更多的图片,loadMoreImage()是专门用来浏览下一页图片的.
看一看loadMoreImages()方法的内部细节了。在这个方法中,使用了一个循环来加载这一页中的每一张图片,每次都会开启一
个LoadImageTask,用于对图片进行异步加载。然后在LoadImageTask中,首先会先检查一下这张图片是不是已经存在于SD卡中了,如果还没
存在,就从网络上下载,然后把这张图片存放在LruCache中。接着将这张图按照一定的比例进行压缩,并找出当前高度最小的一列,把压缩
后的图片添加进去就可以了。
另外,为了保证照片墙上的图片都能够合适地被回收,这里还加入了一个可见性检查的方法,即checkVisibility()方法。这个方法的核心思
想就是检查目前照片墙上的所有图片,判断出哪些是可见的,哪些是不可见。然后将那些不可见的图片都替换成一张空图,这样就可以保证
程序始终不会占用过高的内存。当这些图片又重新变为可见的时候,只需要再从LruCache中将这些图片重新取出即可。如果某张图片已经从
LruCache中被移除了,就会开启一个LoadImageTask,将这张图片重新加载到内存中.
4.打开activity_main.xml
<com.example.photodemo.MyScrollView 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"
android:id="@+id/scroll_view"
tools:context=".MainActivity" >
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
>
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:id="@+id/first_column"
android:orientation="vertical"
></LinearLayout>
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:id="@+id/second_column"
android:orientation="vertical"
></LinearLayout>
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:id="@+id/third_column"
android:orientation="vertical"
></LinearLayout>
</LinearLayout>
</com.example.photodemo.MyScrollView>
5.当然,因为是获取网络图片,所以必须加上联网权限 ,以及将图片缓存到SD卡,所以需要写权限.
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.INTERNET" />