您的位置:首页 > 其它

Bitmap实现照片墙

2015-09-07 17:30 253 查看

Bitmap实现照片墙

Android开发中 位图 的展示 存储 缓存是很重要部分,现代化的用户界面有很多信息都是通过图片形式进行展现,如何更好的展示图片也是衡量一款App 性能和用户体验的重要部分。

Training–>Building Apps with Graphics & Animation–>Displaying Bitmaps Efficiently

今天就写一个简单的照片墙应用来实现Android的Bitmap图片展示功能。

目录结构:



1.GridView实现图片展示界面

activity_main.xml中设置GridView布局

<?xml version="1.0" encoding="utf-8"?>
<GridView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/gridview"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:columnWidth="120dp"
    android:numColumns="auto_fit"
    android:verticalSpacing="10dp"
    android:horizontalSpacing="10dp"
    android:stretchMode="columnWidth"
    android:gravity="center"
/>


GridView 设置列数 列的宽度 item横向 纵向间距 和 stretchMode 属性。

在MainActivity中实例化GridView,并实现ImageAdapter

setContentView(R.layout.activity_main);
        //实例化GridView,并设置adpter
    gridView = (GridView) findViewById(R.id.gridview);
    gridView.setAdapter(new ImageAdapter(this));


public class ImageAdapter extends BaseAdapter {
        private Context context;

        public ImageAdapter(Context context) {
            super();
            this.context = context;
        }

        @Override
        public int getCount() {
            return images.length;
        }

        @Override
        public Object getItem(int arg0) {
            // TODO Auto-generated method stub
            return null;
        }

        @Override
        public long getItemId(int arg0) {
            // TODO Auto-generated method stub
            return 0;
        }

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            LayoutInflater inflater = LayoutInflater.from(context);
            View v;
            if (convertView == null) {
                //若为空,则动态加载一个View
                v = inflater.inflate(R.layout.grid_item, null);
                v.setPadding(8, 8, 8, 8);

            } else {
                v = convertView;
            }
            ImageView imageView = (ImageView) v.findViewById(R.id.item_image);
            // 1.imageView.setImageResource(images[position]);

            // 2.imageView.setImageBitmap(BitmapUtils.decodeSampledBitmapFromResource(getResources(),
            // images[position], 220, 220));

            //3.loadImageView(imageView, images[position]);

            loadImageViewFromCache(imageView, images[position]);
            TextView textview = (TextView) v.findViewById(R.id.item_text);
            textview.setText(images[position] + "");
            return v;
        }

        private Integer[] images = new Integer[] { R.drawable.krystal001,
                R.drawable.krystal002, R.drawable.krystal003,
                R.drawable.krystal004, R.drawable.krystal005,
                R.drawable.krystal006, R.drawable.krystal007,
                R.drawable.krystal008, R.drawable.krystal009,
                R.drawable.krystal010, R.drawable.krystal011,
                R.drawable.krystal012, R.drawable.krystal013,
                R.drawable.krystal014 };

    }


3.设置每个Item的布局 grid_item.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="120dp"
    android:layout_height="120dp"
    android:orientation="vertical" >

    <ImageView
        android:id="@+id/item_image"
        android:layout_width="110dp"
        android:layout_height="110dp"
        android:layout_gravity="center_horizontal"
        android:padding="5dp"
        android:scaleType="centerCrop" 
        android:src="@drawable/ic_launcher">
    </ImageView>

    <TextView
        android:id="@+id/item_text"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:text="try" 
        android:textColor="#ff0000">
    </TextView>

</LinearLayout>


4.现在已经可以展示图片,不过默认是加载了源文件到内存,耗费内存资源,当图片比较多时间,可能会出现
OutOfMemory exception
这里需要运用技巧加载大位图到实际需要的尺寸。

Loading Large Bitmaps Efficiently

运用BitmapFactory.options,来计算原位图的尺寸和需要的尺寸,进行压缩加载。

BitmapUtils.java

package com.example.utils;

import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.BitmapFactory.Options;

public class BitmapUtils {
    /**
     * 从资源文件加载大位图,从字符数组 文件也一样
     * @param res 资源引用
     * @param id 资源ID
     * @param image_width 需要的图片宽度
     * @param image_height 需要的图片高度
     * @return bitmap位图对象
     */
     public static Bitmap decodeSampledBitmapFromResource(Resources res, int id, int image_width, int image_height){
         Bitmap bitmap=null;

         BitmapFactory.Options options=new BitmapFactory.Options();
         //不会分配内存,只是检查编码尺寸
         options.inJustDecodeBounds=true;
         BitmapFactory.decodeResource(res, id, options);

         //,把options传入,decode一次可以计算出原文件的尺寸信息,计算并设置insampleSize 
         options.inSampleSize = calculateInSampleSize(options, image_width, image_height);

         //设置完inSampleSize(压缩倍数),置inJustDecodeBounds为false,编码读入内存
         options.inJustDecodeBounds=false;
         bitmap=BitmapFactory.decodeResource(res, id, options);

         return bitmap;

     }
    /**
     * 计算压缩的倍数
     * @param options BitmapFactory.options 对象
     * @param image_width 需要的图片宽度
     * @param image_height 需要的图片高度
     * @return  InSampleSize 压缩倍数
     */

    private static int calculateInSampleSize(Options options, int image_width,
            int image_height) {
        //源文件的尺寸
        final int width=options.outWidth;
        final int height=options.outHeight;
        //压缩的倍数
        int inSampleSize=1;

         if (height > image_width || width > image_height) {

                final int halfHeight = height / 2;
                final int halfWidth = width / 2;

                // Calculate the largest inSampleSize value that is a power of 2 and keeps both
                // height and width larger than the requested height and width.
                while ((halfHeight / inSampleSize) > image_width
                        && (halfWidth / inSampleSize) > image_height) {
                    inSampleSize *= 2;
                }
            }

        return inSampleSize;
    }

}


5.在UI主线程加载Bitmap显然不是明智的选择,这里用AsyncTask进行异步加载实现

写成内部类应该会简单一点,不用传那么多参数了,只不过看起来乱糟糟的

BitmapWorkerTask.java

package com.example.utils;

import java.lang.ref.WeakReference;

import android.content.Context;
import android.graphics.Bitmap;
import android.os.AsyncTask;
import android.util.LruCache;
import android.widget.ImageView;

public class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {
    // 貌似是虛引用,用空在看吧
    private final WeakReference<ImageView> imageViewReference;
    private int res_id = 0;
    Context context;
    LruCache<String, Bitmap> cache;

    public BitmapWorkerTask(ImageView imageView, Context context,LruCache<String, Bitmap> cache) {
        // Use a WeakReference to ensure the ImageView can be garbage collected
        imageViewReference = new WeakReference<ImageView>(imageView);
        this.context = context;
        this.cache=cache;
    }

    @Override
    protected Bitmap doInBackground(Integer... arg0) {
        res_id = arg0[0];
        Bitmap bitmap= BitmapUtils.decodeSampledBitmapFromResource(
                context.getResources(), res_id, 220, 220);
        //因为BitmapWorkerTask不像写成activity中的内部类,看起来很冗余,所以这里要用原生put(K,V)
        //写在activity构造函数不用传来res 和 cache,可以调用封装的addBitmapToMemoryCache()
        String key=String.valueOf(res_id);
        if(cache.get(key)==null){
        cache.put(key, bitmap);
        }
        return bitmap;
    }

    @Override
    protected void onPostExecute(Bitmap result) {
        if (imageViewReference != null && result != null) {
            final ImageView imageView = imageViewReference.get();
            if (imageView != null) {
                imageView.setImageBitmap(result);
            }
        }
    }
}


ImageAdapter添加以下方法

/**
         * 异步加载位图
         * 
         * @param imageView
         *            要展示的imageView
         * @param res_id
         *            资源ID
         */
        private void loadImageView(ImageView imageView, int res_id) {
            BitmapWorkerTask task = new BitmapWorkerTask(imageView,
                    MainActivity.this, mMemoryCache);
            task.execute(res_id);

        }


6.添加缓存

在ListView 或者 GridView中图片是动态加载,但是也可能刚才销毁的,优于用户滚动屏幕,item又需要被重新生成,每次都重新生成对于资源是一种浪费,同时也会影响程序的性能表现。所以缓存就很重要一种解决问题的手段。

缓存重要分为两种:

Lrucache 内存缓存

disk cache 磁盘缓存

这里先只说内存缓存Lrucache,磁盘缓存晚上在研究。

LruCache 緩存 ,基于LRU算法,数据结构为 LinkedHashMap

// LruCache 緩存 ,基于LRU算法,数据结构为 LinkedHashMap
   private LruCache<String, Bitmap> mMemoryCache;


@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //实例化GridView,并设置adpter
        gridView = (GridView) findViewById(R.id.gridview);
        gridView.setAdapter(new ImageAdapter(this));

        // 获得虚拟环境的最大内存,也就是当前程序可以占用最大内存,一个应用对应一个davilk虚拟环境
        // Get max available VM memory, exceeding this amount will throw an
        // OutOfMemory exception. Stored in kilobytes as LruCache takes an
        // int in its constructor.
        final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);

        // 用最大内存的八分之一作为缓存的容量
        // Use 1/8th of the available memory for this memory cache.
        final int cacheSize = maxMemory / 8;

        mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
            @Override
            protected int sizeOf(String key, Bitmap bitmap) {
                // The cache size will be measured in kilobytes rather than
                // number of items.
                // 以KB为单位
                return bitmap.getByteCount() / 1024;
            }
        };
.....

}


/**
     * 添加到位图到缓存,对put(K,V)的封装
     * 
     * @param key
     * @param bitmap
     */
    public void addBitmapToMemoryCache(String key, Bitmap bitmap) {
        if (getBitmapFromMemCache(key) == null) {
            mMemoryCache.put(key, bitmap);
        }
    }

    /**
     * 从缓存得到位图,对get(K)的封装
     * 
     * @param key
     * @return
     */
    public Bitmap getBitmapFromMemCache(String key) {
        return mMemoryCache.get(key);
    }


ImageAdapter 加入一下方法

/**
         * 从缓存中加载位图
         * 
         * @param imageView
         *            目标ImageView
         * @param resId
         *            资源ID
         */
        private void loadImageViewFromCache(ImageView imageView, int resId) {
            final String imageKey = String.valueOf(resId);

            // 从缓存获取位图
            final Bitmap bitmap = getBitmapFromMemCache(imageKey);
            if (bitmap != null) {
                imageView.setImageBitmap(bitmap);
            } else {
                // 默认图片资源,当没加载出来不会是空白
                imageView.setImageResource(R.drawable.krystal_palceholder);
                BitmapWorkerTask task = new BitmapWorkerTask(imageView,
                        MainActivity.this, mMemoryCache);
                task.execute(resId);
            }

        }


7.程序运行结果



disk cache 和 Managing Bitmap Memory需要研究一下。

MainActivity.java 完整的源代码

package com.example.gridview;

import android.app.Activity;
import android.content.Context;
import android.graphics.Bitmap;
import android.os.Bundle;
import android.util.LruCache;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.AdapterView.OnItemLongClickListener;
import android.widget.BaseAdapter;
import android.widget.GridView;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;

import com.example.utils.BitmapWorkerTask;

public class MainActivity extends Activity {
private GridView gridView;
// LruCache 緩存 ,基于LRU算法,数据结构为 LinkedHashMap
private LruCache<String, Bitmap> mMemoryCache;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//实例化GridView,并设置adpter
gridView = (GridView) findViewById(R.id.gridview);
gridView.setAdapter(new ImageAdapter(this));

// 获得虚拟环境的最大内存,也就是当前程序可以占用最大内存,一个应用对应一个davilk虚拟环境
// Get max available VM memory, exceeding this amount will throw an
// OutOfMemory exception. Stored in kilobytes as LruCache takes an
// int in its constructor.
final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);

// 用最大内存的八分之一作为缓存的容量
// Use 1/8th of the available memory for this memory cache.
final int cacheSize = maxMemory / 8;

mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
@Override
protected int sizeOf(String key, Bitmap bitmap) {
// The cache size will be measured in kilobytes rather than
// number of items.
// 以KB为单位
return bitmap.getByteCount() / 1024;
}
};

gridView.setOnItemClickListener(new OnItemClickListener() {

@Override
public void onItemClick(AdapterView<?> parent, View view,
int position, long id) {
Toast.makeText(MainActivity.this, "开启另一个activity",
Toast.LENGTH_LONG).show();
}
});

gridView.setOnItemLongClickListener(new OnItemLongClickListener() {

@Override
public boolean onItemLongClick(AdapterView<?> parent, View view,
int position, long id) {
Toast.makeText(MainActivity.this, "收藏" + position,
Toast.LENGTH_LONG).show();

return false;
}
});

}

/** * 添加到位图到缓存,对put(K,V)的封装 * * @param key * @param bitmap */ public void addBitmapToMemoryCache(String key, Bitmap bitmap) { if (getBitmapFromMemCache(key) == null) { mMemoryCache.put(key, bitmap); } } /** * 从缓存得到位图,对get(K)的封装 * * @param key * @return */ public Bitmap getBitmapFromMemCache(String key) { return mMemoryCache.get(key); }
/**
* ImageAdapter
* @author QT
*
*/
public class ImageAdapter extends BaseAdapter {
private Context context;

public ImageAdapter(Context context) {
super();
this.context = context;
}

@Override
public int getCount() {
return images.length;
}

@Override
public Object getItem(int arg0) {
// TODO Auto-generated method stub
return null;
}

@Override
public long getItemId(int arg0) {
// TODO Auto-generated method stub
return 0;
}

@Override
public View getView(int position, View convertView, ViewGroup parent) {
LayoutInflater inflater = LayoutInflater.from(context);
View v;
if (convertView == null) {
//若为空,则动态加载一个View
v = inflater.inflate(R.layout.grid_item, null);
v.setPadding(8, 8, 8, 8);

} else {
v = convertView;
}
ImageView imageView = (ImageView) v.findViewById(R.id.item_image);
// 1.imageView.setImageResource(images[position]);

// 2.imageView.setImageBitmap(BitmapUtils.decodeSampledBitmapFromResource(getResources(),
// images[position], 220, 220));

//3.loadImageView(imageView, images[position]);

loadImageViewFromCache(imageView, images[position]);
TextView textview = (TextView) v.findViewById(R.id.item_text);
textview.setText(images[position] + "");
return v;
}

/** * 从缓存中加载位图 * * @param imageView * 目标ImageView * @param resId * 资源ID */ private void loadImageViewFromCache(ImageView imageView, int resId) { final String imageKey = String.valueOf(resId); // 从缓存获取位图 final Bitmap bitmap = getBitmapFromMemCache(imageKey); if (bitmap != null) { imageView.setImageBitmap(bitmap); } else { // 默认图片资源,当没加载出来不会是空白 imageView.setImageResource(R.drawable.krystal_palceholder); BitmapWorkerTask task = new BitmapWorkerTask(imageView, MainActivity.this, mMemoryCache); task.execute(resId); } }
/** * 异步加载位图 * * @param imageView * 要展示的imageView * @param res_id * 资源ID */ private void loadImageView(ImageView imageView, int res_id) { BitmapWorkerTask task = new BitmapWorkerTask(imageView, MainActivity.this, mMemoryCache); task.execute(res_id); }

private Integer[] images = new Integer[] { R.drawable.krystal001,
R.drawable.krystal002, R.drawable.krystal003,
R.drawable.krystal004, R.drawable.krystal005,
R.drawable.krystal006, R.drawable.krystal007,
R.drawable.krystal008, R.drawable.krystal009,
R.drawable.krystal010, R.drawable.krystal011,
R.drawable.krystal012, R.drawable.krystal013,
R.drawable.krystal014 };

}

@Override
public boolean onCreateOptionsMenu(Menu menu) {

// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
}

@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
if (id == R.id.action_settings) {
return true;
}
return super.onOptionsItemSelected(item);
}

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