Android不规则瀑布流照片墙的实现+LruCache算法

简介:




能够想象的出,不规则的瀑布照片墙是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:

 

*/ 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); } }





本文转自mfrbuaa博客园博客,原文链接:http://www.cnblogs.com/mfrbuaa/p/5222245.html,如需转载请自行联系原作者
相关文章
|
1月前
|
算法 Python
关联规则算法及其画图(python
关联规则算法及其画图(python
25 2
|
1月前
|
算法
关联规则分析(Apriori算法2
关联规则分析(Apriori算法2
34 0
|
3月前
|
算法
关联规则分析(Apriori算法2
关联规则分析(Apriori算法2
30 0
|
4月前
|
缓存 算法 Java
Linux内核新特性年终大盘点-安卓杀后台现象减少的背后功臣MGLRU算法简介
MGLRU是一种新型内存管理算法,它的出现是为了弥补传统LRU(Least Recently Used)和LFU(Least Frequently Used)算法在缓存替换选择上的不足,LRU和LFU的共同缺点就是在做内存页面替换时,只考虑内存页面在最近一段时间内被访问的次数和最后一次的访问时间,但是一个页面的最近访问次数少或者最近一次的访问时间较早,可能仅仅是因为这个内存页面新近才被创建,属于刚刚完成初始化的年代代页面,它的频繁访问往往会出现在初始化之后的一段时间里,那么这时候就把这种年轻代的页面迁移出去
|
6月前
|
机器学习/深度学习 算法 搜索推荐
关联规则挖掘:Apriori算法的深度探讨
关联规则挖掘:Apriori算法的深度探讨
319 0
|
1月前
|
算法 搜索推荐 网络架构
关联规则分析(算法+画图
关联规则分析(算法+画图
21 0
|
2月前
|
算法 Android开发
安卓逆向 -- 自吐算法(3DES和AES)
安卓逆向 -- 自吐算法(3DES和AES)
17 1
|
2月前
|
算法 Java Android开发
安卓逆向 -- NDK开发实现MD5算法
安卓逆向 -- NDK开发实现MD5算法
34 0
|
2月前
|
算法 安全 JavaScript
安卓逆向 -- 算法基础(SHA)
安卓逆向 -- 算法基础(SHA)
18 0
|
2月前
|
算法 JavaScript Java
安卓逆向 -- 算法基础(数字签名)
安卓逆向 -- 算法基础(数字签名)
20 1