Bitmap加载和Cache
Bitmap加载
activity_main.xml中有一个Button和ImageView
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity">
<Button
android:id="@+id/getPic"
android:text="加载图片"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<ImageView
android:id="@+id/showPic"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</LinearLayout>
MainActivity通过Button加载图片
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button getPic = findViewById(R.id.getPic);
ImageView showPic = findViewById(R.id.showPic);
getPic.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
showPic.setImageResource(R.drawable.test);
}
});
}
}
测试图片为14.2M(可用Ps增加分辨率生成大图),点击加载按钮后报错
- 使用BitmapFactory.decodeResource()解析图片
- BitmapFactory.Options的公有属性inSampleSize为图片设置宽高采样率,当为2时,宽高各缩放1/2,大小变为原来的1/4
- BitmapFactory.Options的公有属性inJustDecodeBounds为true表示不将图片加载到内存(就不会报错)
- 先根据控件大小和图片大小计算图片缩放比再加载图片
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button getPic = findViewById(R.id.getPic);
getPic.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
loadBigImage();
}
});
}
public void loadBigImage() {
ImageView imageView = this.findViewById(R.id.showPic);
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(getResources(), R.drawable.test, options);
int width = options.outWidth;
int height = options.outHeight;
int measuredWidth = imageView.getMeasuredWidth();
int measuredHeight = imageView.getMeasuredHeight();
int sampleSize;
if (width < measuredWidth || height < measuredHeight) {
sampleSize = 1;
} else {
int scaleX = width / measuredWidth;
int scaleY = height / measuredHeight;
sampleSize = Math.max(scaleX, scaleY);
}
options.inSampleSize = sampleSize;
options.inJustDecodeBounds = false;
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.test, options);
imageView.setImageBitmap(bitmap);
}
}
Cache
如对于图片
- 当程序第一次从网络加载时,将其缓存到存储设备及内存
- 当再次请求时,先从内存中去获取,若无再从存储设备中获取,若无最后从网络下载
LruCache
LruCache用于实现内存缓存,采用LRU(Least Recently Used)算法,内部使用LinkedHashMap以强引用方式存储缓存对象
- sizeOf()用于计算缓存对象的大小,单位需要同缓存单位
- entryRemoved()在删除旧缓存时会调用
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
Log.d(TAG, "onCreate: ");
int cacheSize = maxMemory / 8;
LruCache<String, Bitmap> cache = new LruCache<String, Bitmap>(cacheSize) {
@Override
protected int sizeOf(String key, Bitmap value) {
return value.getRowBytes() * value.getHeight() / 1024;
}
@Override
protected void entryRemoved(boolean evicted, String key, Bitmap oldValue, Bitmap newValue) {
super.entryRemoved(evicted, key, oldValue, newValue);
Log.d(TAG, "entryRemoved: ");
}
};
Bitmap bitmap1 = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);
cache.put("1", bitmap1);
Bitmap bitmap2 = cache.get("1");
Log.d(TAG, "onCreate: " + bitmap1.equals(bitmap2));
cache.remove("1");
}
}
DiskLruCache
DiskLruCache用于实现存储设备缓存,同样采用LRU算法,将缓存对象写入文件系统以实现缓存效果
implementation 'com.jakewharton:disklrucache:2.0.2'
open()方法创建缓存
- 第一个参数为缓存路径,若想要在卸载应用后删除缓存,则应选择应用所关联的路径
- 第二个参数为版本号,当其发生变化时, 会清空此前所有缓存文件
- 第三个参数为单个节点所对应的数据个数,一般为1
- 第四个参数为缓存总大小
如下实现,从url中获取图片,并缓存到文件
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity">
<EditText
android:id="@+id/et_url"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="url" />
<Button
android:id="@+id/btn_write"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="写入缓存" />
<Button
android:id="@+id/btn_query"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="查找缓存及显示" />
<ImageView
android:id="@+id/iv_photo"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
<uses-permission android:name="android.permission.INTERNET"/>
- 缓存路径选择/storage/emulated/0/Android/data/packageName/cache/xxx,卸载时会同步删除缓存
- 将url的MD5码作为Key获取Editor,不允许同时编辑同一缓存对象,若缓存正在编辑则返回null
- 通过Editor获取OutputStream(传入0因为只设置了一个节点),将网络上获取的数据写入到OutputStream
- 若传输成功则调用Editor的commit()写入文件系统,否则调用abort()回滚
- 查找缓存通过Key获取Snapshot中的InputStream
- 未避免原图过大导致OOM,通过inSampleSize压缩图片再decodeFileDescriptor()生成图片(decodeStream是有序的,两次调用会影响文件流的位置属性,导致返回null)
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private static final String TAG = "MainActivity";
public static final long DISK_CACHE_SIZE = 1024 * 1024 * 50;
public static final int DISK_CACHE_INDEX = 0;
private DiskLruCache mDiskLruCache;
private EditText mUrlGetter;
private Button mWriteCache;
private Button mQueryCache;
private ImageView mPhoto;
private DiskLruCache.Editor mEditor;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
iniData();
}
private void initView() {
mUrlGetter = findViewById(R.id.et_url);
mWriteCache = findViewById(R.id.btn_write);
mQueryCache = findViewById(R.id.btn_query);
mPhoto = findViewById(R.id.iv_photo);
mWriteCache.setOnClickListener(this);
mQueryCache.setOnClickListener(this);
}
// /storage/emulated/0/Android/data/com.demo.demo0/cache/bitmap
public File getDiskCacheDir(Context context, String uniqueName) {
String cachePath;
if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())
|| !Environment.isExternalStorageRemovable()) {
cachePath = context.getExternalCacheDir().getPath();
} else {
cachePath = context.getCacheDir().getPath();
}
return new File(cachePath + File.separator + uniqueName);
}
private void iniData() {
File file = getDiskCacheDir(this, "bitmap");
Log.d(TAG, "onCreate: file = " + file.getAbsolutePath());
try {
mDiskLruCache = DiskLruCache.open(file, 1, 1, DISK_CACHE_SIZE);
Log.d(TAG, "iniData: ");
} catch (IOException e) {
e.printStackTrace();
}
}
private boolean downLoadUrlToStream(String urlString, OutputStream outputStream) {
URL url = null;
URLConnection urlConnection = null;
try {
url = new URL(urlString);
urlConnection = url.openConnection();
} catch (IOException e) {
e.printStackTrace();
}
if (urlConnection == null) {
return false;
}
try (
BufferedInputStream in = new BufferedInputStream(urlConnection.getInputStream());
BufferedOutputStream out = new BufferedOutputStream(outputStream, 4096)
) {
int b;
while ((b = in.read()) != -1) {
out.write(b);
}
return true;
} catch (IOException e) {
e.printStackTrace();
}
return false;
}
private String hashKeyFormUrl(String url) {
String cacheKey;
try {
MessageDigest digest = MessageDigest.getInstance("MD5");
digest.update(url.getBytes());
cacheKey = byteToHexString(digest.digest());
} catch (NoSuchAlgorithmException e) {
cacheKey = String.valueOf(url.hashCode());
}
return cacheKey;
}
private String byteToHexString(byte[] bytes) {
StringBuilder stringBuilder = new StringBuilder();
for (byte aByte : bytes) {
String hex = Integer.toHexString(0xFF & aByte);
if (hex.length() == 1) {
stringBuilder.append("0");
}
stringBuilder.append(hex);
}
return stringBuilder.toString();
}
@Override
public void onClick(View v) {
String url = String.valueOf(mUrlGetter.getText());
String key = hashKeyFormUrl(url);
switch (v.getId()) {
case R.id.btn_write:
new Thread(new Runnable() {
@Override
public void run() {
try {
mEditor = mDiskLruCache.edit(key);
if (mEditor != null) {
OutputStream outputStream = mEditor.newOutputStream(DISK_CACHE_INDEX);
if (downLoadUrlToStream(url, outputStream)) {
mEditor.commit();
} else {
mEditor.abort();
}
mDiskLruCache.flush();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
break;
case R.id.btn_query:
Bitmap bitmap = null;
DiskLruCache.Snapshot snapshot = null;
try {
snapshot = mDiskLruCache.get(key);
if (snapshot != null) {
FileInputStream in = (FileInputStream) snapshot.getInputStream(DISK_CACHE_INDEX);
FileDescriptor fd = in.getFD();
bitmap = decodeSampleBitmapFromFileDescriptor(fd, 300, 300);
mPhoto.setImageBitmap(bitmap);
}
} catch (IOException e) {
e.printStackTrace();
}
break;
}
}
public static Bitmap decodeSampleBitmapFromFileDescriptor(FileDescriptor fd, int reqWidth, int reqHeight) {
final BitmapFactory.Options options = new BitmapFactory.Options();
//设置 inJustDecodeBounds 参数为true,解析原图
options.inJustDecodeBounds = true;
//加载原图
BitmapFactory.decodeFileDescriptor(fd, new Rect(0, 0, 0, 0), options);
//获取采样率
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
//设置 inJustDecodeBounds 参数为false,不解析原图
options.inJustDecodeBounds = false;
//返回压缩后的图片
return BitmapFactory.decodeFileDescriptor(fd, new Rect(0, 0, 0, 0), options);
}
public static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
//获取图片原始的宽高
final int height = options.outHeight;
final int width = options.outWidth;
int inSampleSize = 1;
//先判断图片是否需要压缩
if (height > reqHeight || width > reqWidth) {
final int halfHeight = height / 2;
final int halfWidth = height / 2;
//直到压缩到比需要大小小
while ((halfHeight / inSampleSize) >= reqHeight
&& (halfWidth / inSampleSize) >= reqWidth) {
inSampleSize *= 2;
}
}
return inSampleSize;
}
}
二级缓存实现
ImageResizer
用于修改图片尺寸
public class ImageResizer {
private static final String TAG = "ImageResizer";
public ImageResizer() {
}
public Bitmap decodeSampledBitmapFromResource(Resources res, int resId, int reqWidth, int reqHeight) {
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(res, resId, options);
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
options.inJustDecodeBounds = false;
return BitmapFactory.decodeResource(res, resId, options);
}
public Bitmap decodeSampledBitmapFromFileDescriptor(FileDescriptor fd, int reqWidth, int reqHeight) {
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFileDescriptor(fd, null, options);
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
options.inJustDecodeBounds = false;
return BitmapFactory.decodeFileDescriptor(fd, null, options);
}
private int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
if (reqWidth == 0 || reqHeight == 0) {
return 1;
}
final int height = options.outHeight;
final int width = options.outWidth;
Log.d(TAG, "calculateInSampleSize: height = " + height + ", width = " + width);
int inSampleSize = 1;
if (height > reqHeight || width > reqWidth) {
final int halfHeight = height / 2;
final int halfWidth = width / 2;
while ((halfHeight / inSampleSize) >= reqHeight && (halfWidth / inSampleSize) >= reqWidth) {
inSampleSize *= 2;
}
}
Log.d(TAG, "calculateInSampleSize: inSampleSize = " + inSampleSize);
return inSampleSize;
}
}
ImageLoader
- 构造函数中创建LruCache和DiskLruCache,创建文件缓存时存储空间应大于50M
- loadBitmap()方法采用同步的方式获取对应url中的bitmap,先从内存缓存中加载,若无则从文件缓存加载,若无则最后从网上下载并将其加载到缓存,若缓存未创建则直接从网上加载
- bindBitmap()采用内部线程池(否则大量加载时会创建大量线程)调用loadBitmap(),通过主线程的Handle修改UI
- ImageView把Tag设置成对应url,修改时需判断tag是否相等(解决View复用导致列表错位)
public class ImageLoader {
private static final String TAG = "ImageLoader";
public static final int MESSAGE_POST_RESULT = 1;
private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
private static final int CORE_POOL_SIZE = CPU_COUNT + 1;
private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;
private static final long KEEP_ALIVE = 10L;
private static final int TAG_KEY_URI = R.id.imageloader_uri;
private static final long DISK_CACHE_SIZE = 1024 * 1024 * 50;
private static final int IO_BUFFER_SIZE = 8 * 1024;
private static final int DISK_CACHE_INDEX = 0;
private boolean mIsDiskLruCacheCreated = false;
private static final ThreadFactory sThreadFactory = new ThreadFactory() {
private final AtomicInteger mCount = new AtomicInteger(1);
public Thread newThread(Runnable r) {
return new Thread(r, "ImageLoader#" + mCount.getAndIncrement());
}
};
private static final Executor THREAD_POOL_EXECUTOR = new ThreadPoolExecutor(
CORE_POOL_SIZE, MAXIMUM_POOL_SIZE,
KEEP_ALIVE, TimeUnit.SECONDS,
new LinkedBlockingQueue<Runnable>(), sThreadFactory);
private Handler mMainHandler = new Handler(Looper.getMainLooper()) {
@Override
public void handleMessage(Message msg) {
LoaderResult result = (LoaderResult) msg.obj;
ImageView imageView = result.imageView;
imageView.setImageBitmap(result.bitmap);
String uri = (String) imageView.getTag(TAG_KEY_URI);
if (uri.equals(result.uri)) {
imageView.setImageBitmap(result.bitmap);
} else {
Log.w(TAG, "set image bitmap,but url has changed,ignored!");
}
}
};
private Context mContext;
private ImageResizer mImageResizer = new ImageResizer();
private LruCache<String, Bitmap> mMemoryCache;
private DiskLruCache mDiskLruCache;
private ImageLoader(Context context) {
mContext = context.getApplicationContext();
int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
int cacheSize = maxMemory / 8;
mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
@Override
protected int sizeOf(String key, Bitmap bitmap) {
return bitmap.getRowBytes() * bitmap.getHeight() / 1024;
}
};
File diskCacheDir = getDiskCacheDir(mContext, "bitmap");
if (!diskCacheDir.exists()) {
diskCacheDir.mkdirs();
}
if (getUsableSpace(diskCacheDir) > DISK_CACHE_SIZE) {
try {
mDiskLruCache = DiskLruCache.open(diskCacheDir, 1, 1, DISK_CACHE_SIZE);
mIsDiskLruCacheCreated = true;
} catch (IOException e) {
e.printStackTrace();
}
}
}
public static ImageLoader build(Context context) {
return new ImageLoader(context);
}
private void addBitmapToMemoryCache(String key, Bitmap bitmap) {
if (getBitmapFromMemCache(key) == null) {
mMemoryCache.put(key, bitmap);
}
}
private Bitmap getBitmapFromMemCache(String key) {
return mMemoryCache.get(key);
}
public Bitmap loadBitmap(String uri, int reqWidth, int reqHeight) {
Bitmap bitmap = loadBitmapFromMemCache(uri);
if (bitmap != null) {
Log.d(TAG, "loadBitmapFromMemCache,url:" + uri);
return bitmap;
}
try {
bitmap = loadBitmapFromDiskCache(uri, reqWidth, reqHeight);
if (bitmap != null) {
Log.d(TAG, "loadBitmapFromDisk,url:" + uri);
return bitmap;
}
bitmap = loadBitmapFromHttp(uri, reqWidth, reqHeight);
Log.d(TAG, "loadBitmapFromHttp,url:" + uri);
} catch (IOException e) {
e.printStackTrace();
}
if (bitmap == null && !mIsDiskLruCacheCreated) {
Log.w(TAG, "encounter error,DiskLruCache is not created.");
bitmap = downloadBitmapFromUrl(uri);
}
return bitmap;
}
public void bindBitmap(final String uri, final ImageView imageView) {
bindBitmap(uri, imageView, 0, 0);
}
public void bindBitmap(final String uri, final ImageView imageView, final int reqWidth, final int reqHeight) {
imageView.setTag(TAG_KEY_URI, uri);
Bitmap bitmap = loadBitmapFromMemCache(uri);
if (bitmap != null) {
imageView.setImageBitmap(bitmap);
return;
}
Runnable loadBitmapTask = new Runnable() {
@Override
public void run() {
Bitmap bitmap = loadBitmap(uri, reqWidth, reqHeight);
if (bitmap != null) {
LoaderResult result = new LoaderResult(imageView, uri, bitmap);
mMainHandler.obtainMessage(MESSAGE_POST_RESULT, result).sendToTarget();
}
}
};
THREAD_POOL_EXECUTOR.execute(loadBitmapTask);
}
private Bitmap loadBitmapFromMemCache(String url) {
final String key = hashKeyFormUrl(url);
Bitmap bitmap = getBitmapFromMemCache(key);
return bitmap;
}
private Bitmap loadBitmapFromDiskCache(String url, int reqWidth, int reqHeight) throws IOException {
if (mDiskLruCache == null) {
return null;
}
Bitmap bitmap = null;
String key = hashKeyFormUrl(url);
DiskLruCache.Snapshot snapShot = mDiskLruCache.get(key);
if (snapShot != null) {
FileInputStream fileInputStream = (FileInputStream) snapShot.getInputStream(DISK_CACHE_INDEX);
FileDescriptor fileDescriptor = fileInputStream.getFD();
bitmap = mImageResizer.decodeSampledBitmapFromFileDescriptor(fileDescriptor, reqWidth, reqHeight);
if (bitmap != null) {
addBitmapToMemoryCache(key, bitmap);
}
}
return bitmap;
}
private Bitmap loadBitmapFromHttp(String url, int reqWidth, int reqHeight) throws IOException {
if (Looper.myLooper() == Looper.getMainLooper()) {
throw new RuntimeException("can not visit network from UI Thread.");
}
if (mDiskLruCache == null) {
return null;
}
String key = hashKeyFormUrl(url);
DiskLruCache.Editor editor = mDiskLruCache.edit(key);
if (editor != null) {
OutputStream outputStream = editor.newOutputStream(DISK_CACHE_INDEX);
if (downLoadUrlToStream(url, outputStream)) {
editor.commit();
} else {
editor.abort();
}
mDiskLruCache.flush();
}
return loadBitmapFromDiskCache(url, reqWidth, reqHeight);
}
private boolean downLoadUrlToStream(String urlString, OutputStream outputStream) {
URL url = null;
HttpURLConnection urlConnection = null;
try {
url = new URL(urlString);
urlConnection = (HttpURLConnection) url.openConnection();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (urlConnection != null) {
urlConnection.disconnect();
}
}
if (urlConnection == null) {
return false;
}
try (
BufferedInputStream in = new BufferedInputStream(urlConnection.getInputStream());
BufferedOutputStream out = new BufferedOutputStream(outputStream, 4096)
) {
int b;
while ((b = in.read()) != -1) {
out.write(b);
}
return true;
} catch (IOException e) {
e.printStackTrace();
}
return false;
}
private Bitmap downloadBitmapFromUrl(String urlString) {
Bitmap bitmap = null;
URL url = null;
HttpURLConnection urlConnection = null;
try {
url = new URL(urlString);
urlConnection = (HttpURLConnection) url.openConnection();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (urlConnection != null) {
urlConnection.disconnect();
}
}
if (urlConnection == null) {
return null;
}
try (BufferedInputStream in = new BufferedInputStream(urlConnection.getInputStream(), IO_BUFFER_SIZE);) {
bitmap = BitmapFactory.decodeStream(in);
} catch (final IOException e) {
Log.e(TAG, "Error in downloadBitmap: " + e);
}
return bitmap;
}
// 在版本之上运行
@TargetApi(Build.VERSION_CODES.GINGERBREAD)
private long getUsableSpace(File path) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD) {
return path.getUsableSpace();
}
final StatFs stats = new StatFs(path.getPath());
return (long) stats.getBlockSize() * (long) stats.getAvailableBlocks();
}
private File getDiskCacheDir(Context context, String uniqueName) {
boolean externalStorageAvailable = Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED);
final String cachePath;
if (externalStorageAvailable) {
cachePath = context.getExternalCacheDir().getPath();
} else {
cachePath = context.getCacheDir().getPath();
}
return new File(cachePath + File.separator + uniqueName);
}
private String hashKeyFormUrl(String url) {
String cacheKey;
try {
final MessageDigest mDigest = MessageDigest.getInstance("MD5");
mDigest.update(url.getBytes());
cacheKey = bytesToHexString(mDigest.digest());
} catch (NoSuchAlgorithmException e) {
cacheKey = String.valueOf(url.hashCode());
}
return cacheKey;
}
private String bytesToHexString(byte[] bytes) {
StringBuilder sb = new StringBuilder();
for (byte aByte : bytes) {
String hex = Integer.toHexString(0xFF & aByte);
if (hex.length() == 1) {
sb.append('0');
}
sb.append(hex);
}
return sb.toString();
}
private static class LoaderResult {
public ImageView imageView;
public String uri;
public Bitmap bitmap;
public LoaderResult(ImageView imageView, String uri, Bitmap bitmap) {
this.imageView = imageView;
this.uri = uri;
this.bitmap = bitmap;
}
}
}
ids.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<item name="imageloader_uri" type="id"/>
</resources>
SquareImageView
让图片宽高相等
public class SquareImageView extends ImageView {
public SquareImageView(Context context) {
super(context);
}
public SquareImageView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public SquareImageView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, widthMeasureSpec);
}
}
activity_main.xml
用GridView设置照片墙
<LinearLayout 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:orientation="vertical"
android:padding="5dp">
<GridView
android:id="@+id/gridView1"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:horizontalSpacing="5dp"
android:listSelector="@android:color/transparent"
android:numColumns="3"
android:stretchMode="columnWidth"
android:verticalSpacing="5dp"/>
</LinearLayout>
image_list_item.xml
GridView的适配布局
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="vertical">
<com.demo.demo0.SquareImageView
android:id="@+id/image"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:scaleType="centerCrop"
android:src="@drawable/ic_launcher_background" />
</LinearLayout>
MyUtils
public class MyUtils {
public static DisplayMetrics getScreenMetrics(Context context) {
WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
DisplayMetrics dm = new DisplayMetrics();
wm.getDefaultDisplay().getMetrics(dm);
return dm;
}
public static float dp2px(Context context, float dp) {
return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp,
context.getResources().getDisplayMetrics());
}
public static boolean isWifi(Context context) {
ConnectivityManager connectivityManager = (ConnectivityManager) context
.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo activeNetInfo = connectivityManager.getActiveNetworkInfo();
return activeNetInfo != null
&& activeNetInfo.getType() == ConnectivityManager.TYPE_WIFI;
}
}
MainActivity
- 在首次运行时判断是否连接wifi,避免消耗流量
- 重写onScrollStateChanged(),在滑动时不加载图片,避免产生过多异步任务,导致线程池拥堵,同时大量UI更新操作导致卡顿,此外还可打开硬件加速
public class MainActivity extends Activity implements AbsListView.OnScrollListener {
private static final String TAG = "MainActivity";
private List<String> mUrList = new ArrayList<>();
ImageLoader mImageLoader;
private GridView mImageGridView;
private BaseAdapter mImageAdapter;
private boolean mIsGridViewIdle = true;
private int mImageWidth = 0;
private boolean mIsWifi = false;
private boolean mCanGetBitmapFromNetWork = false;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initData();
initView();
mImageLoader = ImageLoader.build(MainActivity.this);
}
private void initData() {
String uri = "https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fc-ssl.duitang.com%2Fuploads%2Fitem%2F201411%2F01%2F20141101051004_8W3ev.jpeg&refer=http%3A%2F%2Fc-ssl.duitang.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1696676552&t=c2c660256ac9be434e1a1fa0039144dc";
for (int i = 0; i < 30; i++) {
mUrList.add(uri);
}
int screenWidth = MyUtils.getScreenMetrics(this).widthPixels;
int space = (int) MyUtils.dp2px(this, 20f);
mImageWidth = (screenWidth - space) / 3;
mIsWifi = MyUtils.isWifi(this);
if (mIsWifi) {
mCanGetBitmapFromNetWork = true;
}
}
private void initView() {
mImageGridView = (GridView) findViewById(R.id.gridView1);
mImageAdapter = new ImageAdapter(this);
mImageGridView.setAdapter(mImageAdapter);
mImageGridView.setOnScrollListener(this);
if (!mIsWifi) {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("注意");
builder.setMessage("初次使用会从网络下载图片,这将要消耗些许流量,确认要下载吗?");
builder.setPositiveButton("是", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
mCanGetBitmapFromNetWork = true;
mImageAdapter.notifyDataSetChanged();
}
});
builder.setNegativeButton("否", null);
builder.show();
}
}
private class ImageAdapter extends BaseAdapter {
private LayoutInflater mInflater;
private Drawable mDefaultBitmapDrawable;
private ImageAdapter(Context context) {
mInflater = LayoutInflater.from(context);
mDefaultBitmapDrawable = context.getResources().getDrawable(R.drawable.ic_launcher_background);
}
@Override
public int getCount() {
return mUrList.size();
}
@Override
public String getItem(int position) {
return mUrList.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder = null;
if (convertView == null) {
convertView = mInflater.inflate(R.layout.image_list_item, parent, false);
holder = new ViewHolder();
holder.imageView = (ImageView) convertView.findViewById(R.id.image);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
ImageView imageView = holder.imageView;
final String tag = (String) imageView.getTag();
final String uri = getItem(position);
if (!uri.equals(tag)) {
imageView.setImageDrawable(mDefaultBitmapDrawable);
}
if (mIsGridViewIdle && mCanGetBitmapFromNetWork) {
imageView.setTag(uri);
mImageLoader.bindBitmap(uri, imageView, mImageWidth, mImageWidth);
}
return convertView;
}
}
private static class ViewHolder {
public ImageView imageView;
}
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
if (scrollState == AbsListView.OnScrollListener.SCROLL_STATE_IDLE) {
mIsGridViewIdle = true;
mImageAdapter.notifyDataSetChanged();
} else {
mIsGridViewIdle = false;
}
}
@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
}
}
效果图