watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvZnJhbmNpc3NoaQ==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" alt="">

能够想象的出,不规则的瀑布照片墙是ScrollView内嵌一个横向的LinearLayout再内嵌三个纵向的LinearLayout。

假设不停地往LinearLayout里加入图片。程序非常快就会OOM。因此我们还须要一个合理的方案来对图片资源进行释放,这里仍然是准备使用LruCache算法

TravlesFragment:

package com.francis.changtravels.fragment;

import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.util.DisplayMetrics;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.TextView; import com.francis.changtravels.R; /**
* Created by Francis on 14-9-18.
*/
public class TravelsFragment extends Fragment { @Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.photo_wall_falls_demo,container,false); return rootView;
}
}

TravlesFragment直接返回一个自己定义的View:

<com.francis.changtravels.view.MyScrollView
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.francis.changtravels.view.MyScrollView>

以下是MyScrollView.java:

package com.francis.changtravels.view;

import android.content.Context;
import android.content.Intent;
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.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ScrollView;
import android.widget.Toast; import com.francis.changtravels.utils.Images;
import com.francis.changtravels.R;
import com.francis.changtravels.activity.ImageDetailsActivity;
import com.francis.changtravels.utils.ImageLoader; 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; /**
* Created by Francis on 14-9-19.
*/
public class MyScrollView extends ScrollView implements View.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(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(i);
}
} 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<Integer, Void, Bitmap> { /**
* 记录每一个图片相应的位置
*/
private int mItemPosition; /**
* 图片的URL地址
*/
private String mImageUrl; /**
* 可反复使用的ImageView
*/
private ImageView mImageView; public LoadImageTask() {
} /**
* 将可反复使用的ImageView传入
*
* @param imageView
*/
public LoadImageTask(ImageView imageView) {
mImageView = imageView;
} @Override
protected Bitmap doInBackground(Integer... params) {
mItemPosition = params[0];
mImageUrl = Images.imageUrls[mItemPosition];
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(ImageView.ScaleType.FIT_XY);
imageView.setPadding(5, 5, 5, 5);
imageView.setTag(R.string.image_url, mImageUrl);
imageView.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(getContext(), ImageDetailsActivity.class);
intent.putExtra("image_position", mItemPosition);
getContext().startActivity(intent);
}
});
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;
}
} }

ImageLoader.java:

package com.francis.changtravels.utils;

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.util.LruCache; /**
* Created by Francis on 14-9-19.
*/
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();
//设置为true 得到宽和高
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(pathName, options);
// 调用上面定义的方法计算inSampleSize值
options.inSampleSize = calculateInSampleSize(options, reqWidth);
// 使用获取到的inSampleSize值再次解析图片
// 设置为false 得到Bitmap
options.inJustDecodeBounds = false;
return BitmapFactory.decodeFile(pathName, options);
} }

最新文章

  1. 查找html节点的方法
  2. 10 months then free? 10个月,然后自由
  3. spring+mybatis多数据源切换
  4. jquery.validate.js
  5. word编号库中找不到带圈编号“①②③......&quot;了怎么办?
  6. HDU1028Ignatius and the Princess III(母函数)
  7. (转)C#与Android通过adb实现usb通讯
  8. 阅读Facebook POP框架 笔记(一)
  9. MySQL 远程连接问题
  10. 在Linux(Centos7)系统上对进行Hadoop分布式配置以及运行Hadoop伪分布式实例
  11. python 类的定义 实例化 实例完后初始化
  12. K8S RBAC
  13. Lint found fatal errors while assembling a release target
  14. [opentwebst]一个简单的登陆脚本
  15. python接口自动化测试十一:传参数:data与json
  16. 多线程练习,深刻体会了一次变量的BUG.
  17. 在postgresqlz中查看与删除索引
  18. XTU 1267 - Highway - [树的直径][2017湘潭邀请赛H题(江苏省赛)]
  19. iOS多线程---NSOperation介绍和使用
  20. 如何使用curl命令指定ip访问url

热门文章

  1. sql server遍历表不用游标和临时表的方法
  2. stream~filestream
  3. 【思考题】CSDN第四届在线编程大赛2014初赛:带通配符的数
  4. linux内核交互,设备驱动控制管理接口
  5. png图片的loading旋转
  6. java/struts/Servlet文件下载与ftp文件上传下载
  7. Chrome for Android在Chromium代码库中的提交patch
  8. jQuery Custom Selector JQuery自定义选择器
  9. JavaScript中forEach的用法相关
  10. Broadcast详解