0. 原文拜读
https://blog.csdn.net/lin20044140410/article/details/77454079
1 AlbumPage.AlbumSlotRenderer 缩略图的配置
AlbumPage数据的加载,跟AlbumSetPage的数据加载类似,不同的地方在于AlbumPage要把所有的图片都生成缩略图,而AlbumSetPage是取相册中的第一张图片作为封面。
package com.android.gallery3d.app;
import com.android.gallery3d.ui.AlbumSlotRenderer;
public class AlbumPage extends ActivityState implements GalleryActionBar.ClusterRunner,ButtonControlsHandler.Delegate,... {
// AlbumSlotRenderer负责图片缩略图的生成工作
private AlbumSlotRenderer mAlbumView;
private void initializeViews() {
...
// /相册界面的配置项,config定义了各个页面的配置,行、列、边距等。
mConfig = Config.AlbumPage.get(mActivity);
mConfigList = Config.AlbumPageList.get(mActivity);
if (mViewType) {
// 相册页
mSlotView = new SlotView(mActivity, mConfig.slotViewSpec);
//对AlbumSlotRenderer进行配置
mAlbumView = new AlbumSlotRenderer(mActivity, mSlotView,
mConfig.labelSpec, mSelectionManager, mConfig.placeholderColor, mViewType);
} else {
mSlotView = new SlotView(mActivity, mConfigList.slotViewSpec);
//对AlbumSlotRenderer进行配置
mAlbumView = new AlbumSlotRenderer(mActivity, mSlotView,
mConfigList.labelSpec, mSelectionManager,
mConfig.placeholderColor, mViewType);
}
mSlotView.setSlotRenderer(mAlbumView);
//添加相册页面到画布上
mRootPane.addComponent(mSlotView);
}
}
2. 数据源的加载 AlbumPage.initializeData
package com.android.gallery3d.app;
import com.android.gallery3d.ui.AlbumSlotRenderer;
public class AlbumPage extends ActivityState implements GalleryActionBar.ClusterRunner,ButtonControlsHandler.Delegate,... {
// 缩略图适配器
private AlbumDataLoader mAlbumDataAdapter;
// 缩略图生成
private AlbumSlotRenderer mAlbumView;
private void initializeData(Bundle data) {
...
mAlbumDataAdapter = new AlbumDataLoader(mActivity, mMediaSet);
mAlbumDataAdapter.setLoadingListener(new MyLoadingListener());
// 导入了数据源,mAlbumDataAdapter是加载数据的适配类对象
mAlbumView.setModel(mAlbumDataAdapter);
}
3. AlbumSlotRenderer(mAlbumView).setModel
package com.android.gallery3d.ui;
public class AlbumSlotRenderer extends AbstractSlotRenderer {
// 装载适配器
public void setModel(AlbumDataLoader model) {
if (mDataWindow != null) {
mDataWindow.setListener(null);
mSlotView.setSlotCount(0);
mDataWindow = null;
}
if (model != null) {
// 创建缩略图窗口
mDataWindow = new AlbumSlidingWindow(mActivity, model, CACHE_SIZE,
mLabelSpec, mIsGridViewShown);
// 缩略图窗口事件监听
mDataWindow.setListener(new MyDataModelListener());
mSlotView.setSlotCount(model.size());
}
}
private class MyDataModelListener implements AlbumSlidingWindow.Listener {
@Override
public void onContentChanged() {
mSlotView.invalidate();
}
@Override
public void onSizeChanged(int size) {
mSlotView.setSlotCount(size);
mSlotView.invalidate();
}
}
}
4. 点击某个相册事件
我们点击某个相册,窗口会被创建、状态会发生变化,然后更新窗口的可见区域,会间接调用到 AlbumSlotRenderer.onVisibleRangeChanged()。
package com.android.gallery3d.ui;
public class AlbumSlotRenderer extends AbstractSlotRenderer {
private AlbumSlidingWindow mDataWindow;
@Override
public void onVisibleRangeChanged(int visibleStart, int visibleEnd) {
if (mDataWindow != null) {
mDataWindow.setActiveWindow(visibleStart, visibleEnd);
}
}
5. AlbumSlidingWindow.setActiveWindow
package com.android.gallery3d.ui;
public class AlbumSlidingWindow implements AlbumDataLoader.DataListener {
public void setActiveWindow(int start, int end) {
if (!(start <= end && end - start <= mData.length && end <= mSize)) {
Utils.fail("%s, %s, %s, %s", start, end, mData.length, mSize);
}
AlbumEntry data[] = mData;
// 这里的start值通常是0,end值根据窗口的可见区域有个计算,竖屏最多16张,横屏12张。
mActiveStart = start;
mActiveEnd = end;
int contentStart = Utils.clamp((start + end) / 2 - data.length / 2, 0,
Math.max(0, mSize - data.length));
int contentEnd = Math.min(contentStart + data.length, mSize);
//设置窗口,每张图片都对应一个窗口
setContentWindow(contentStart, contentEnd);
updateTextureUploadQueue();
if (mIsActive)
//更新所有图片生成缩略图的请求,即启动生成缩略图的任务。
updateAllImageRequests();
}
- 5.1 查看下 AlbumSlidingWindow.setContentWindow()
private void setContentWindow(int contentStart, int contentEnd) {
if (contentStart == mContentStart && contentEnd == mContentEnd)
return;
if (!mIsActive) {
mContentStart = contentStart;
mContentEnd = contentEnd;
mSource.setActiveWindow(contentStart, contentEnd);
return;
}
if (contentStart >= mContentEnd || mContentStart >= contentEnd) {
for (int i = mContentStart, n = mContentEnd; i < n; ++i) {
freeSlotContent(i);
}
mSource.setActiveWindow(contentStart, contentEnd);
for (int i = contentStart; i < contentEnd; ++i) {
//准备每个窗口的数据
prepareSlotContent(i);
}
} else {
for (int i = mContentStart; i < contentStart; ++i) {
freeSlotContent(i);
}
for (int i = contentEnd, n = mContentEnd; i < n; ++i) {
freeSlotContent(i);
}
mSource.setActiveWindow(contentStart, contentEnd);
for (int i = contentStart, n = mContentStart; i < n; ++i) {
prepareSlotContent(i);
}
for (int i = mContentEnd; i < contentEnd; ++i) {
prepareSlotContent(i);
}
}
mContentStart = contentStart;
mContentEnd = contentEnd;
}
- 5.2 查看 AlbumSlidingWindow.prepareSlotContent()
package com.android.gallery3d.ui;
public class AlbumSlidingWindow implements AlbumDataLoader.DataListener {
private void prepareSlotContent(int slotIndex) {
AlbumEntry entry = new AlbumEntry();
//获取图片标识
MediaItem item = mSource.get(slotIndex); // item could be null;
entry.item = item;
entry.name = (item == null) ? null : item.getName();
entry.mediaType = (item == null) ? MediaItem.MEDIA_TYPE_UNKNOWN
: entry.item.getMediaType();
entry.path = (item == null) ? null : item.getPath();
entry.isFavorite = item != null && item.isFavorite; //ZDQ 2018/12/28 赋值
entry.rotation = (item == null) ? 0 : item.getRotation();
//创建生成缩略图的任务
entry.contentLoader = new ThumbnailLoader(slotIndex, entry.item);
if (!mViewType) {
if (entry.labelLoader != null) {
entry.labelLoader.recycle();
entry.labelLoader = null;
entry.labelTexture = null;
}
if (entry.name != null) {
entry.labelLoader = new AlbumLabelLoader(slotIndex, entry.name);
}
}
mData[slotIndex % mData.length] = entry;
}
- 5.3 开始生成缩略图 ThumbnailLoader(slotIndex, entry.item)
package com.android.gallery3d.ui;
public class AlbumSlidingWindow implements AlbumDataLoader.DataListener {
private class ThumbnailLoader extends BitmapLoader {
private final int mSlotIndex;
private final MediaItem mItem;
public ThumbnailLoader(int slotIndex, MediaItem item) {
mSlotIndex = slotIndex;
mItem = item;
}
@Override
protected Future<Bitmap> submitBitmapTask(FutureListener<Bitmap> l) {
//开始生成缩略图的任务
return mThreadPool.submit(
mItem.requestImage(MediaItem.TYPE_MICROTHUMBNAIL), this);
}
- 5.4 这里会为每个数据源创建生成缩略图的的请求,并提交到线程池,
根据 继承关系 LocalImage extends LocalMediaItem extends MediaItem,这里假设是LocalImage本地数据的处理
package com.android.gallery3d.data;
// LocalImage represents an image in the local storage.
public class LocalImage extends LocalMediaItem {
// LocalImageRequest 的父类 ImageCacheRequest,是一个类似于callable的异步任务
public static class LocalImageRequest extends ImageCacheRequest {
private String mLocalFilePath;
...
}
@Override
public Job<Bitmap> requestImage(int type) {
return new LocalImageRequest(mApplication, mPath, dateModifiedInSec,
type, filePath, mimeType);
}
}
查看下 ImageCacheRequest
package com.android.gallery3d.data;
abstract class ImageCacheRequest implements Job<Bitmap> {
@Override
public Bitmap run(JobContext jc) {
//先获取cache服务,从buffer中查询是否已经缓存过缩略图,因为生成过一次后会添
//加到缓存,下次不需要再生成。
ImageCacheService cacheService = mApplication.getImageCacheService();
BytesBuffer buffer = MediaItem.getBytesBufferPool().get();
try {
boolean found = cacheService.getImageData(mPath, mTimeModified, mType, buffer);
if (jc.isCancelled()) return null;
if (found) {
BitmapFactory.Options options = new BitmapFactory.Options();
options.inPreferredConfig = Bitmap.Config.ARGB_8888;
Bitmap bitmap;
//如果查到了,直接构建bitmap
if (mType == MediaItem.TYPE_MICROTHUMBNAIL) {
bitmap = DecodeUtils.decodeUsingPool(jc,
buffer.data, buffer.offset, buffer.length, options);
} else {
bitmap = DecodeUtils.decodeUsingPool(jc,
buffer.data, buffer.offset, buffer.length, options);
}
if (bitmap == null && !jc.isCancelled()) {
Log.w(TAG, "decode cached failed " + debugTag());
}
return bitmap;
}
} finally {
MediaItem.getBytesBufferPool().recycle(buffer);
}
//没有查到,会调用子类的onDecodeOriginal()方法进行解码
Bitmap bitmap = onDecodeOriginal(jc, mType);
if (jc.isCancelled()) return null;
if (bitmap == null) {
Log.w(TAG, "decode orig failed " + debugTag());
return null;
}
//把缩略图添加到缓存,把bitmap压缩成byte数组,存储到cacheService
if (mType == MediaItem.TYPE_MICROTHUMBNAIL) {
bitmap = BitmapUtils.resizeAndCropCenter(bitmap, mTargetSize, true);
} else {
bitmap = BitmapUtils.resizeDownBySideLength(bitmap, mTargetSize, true);
}
if (jc.isCancelled()) return null;
byte[] array = BitmapUtils.compressToBytes(bitmap);
if (jc.isCancelled()) return null;
cacheService.putImageData(mPath, mTimeModified, mType, array);
return bitmap;
}
}
- 5.5 调用onDecodeOriginal()解码生成缩略图
Bitmap bitmap = onDecodeOriginal(jc, mType)
package com.android.gallery3d.data;
// LocalImage represents an image in the local storage.
public class LocalImage extends LocalMediaItem {
public static class LocalImageRequest extends ImageCacheRequest {
private String mLocalFilePath;
@Override
public Bitmap onDecodeOriginal(JobContext jc, final int type) {
....
// try to decode from JPEG EXIF
if (type == MediaItem.TYPE_MICROTHUMBNAIL) {
//直接从jpeg的exif库函数里面获取缩略图
ExifInterface exif = new ExifInterface();
byte[] thumbData = null;
try {
exif.readExif(mLocalFilePath);
thumbData = exif.getThumbnail();
} catch (FileNotFoundException e) {
Log.w(TAG, "failed to find file to read thumbnail: " + mLocalFilePath);
} catch (IOException e) {
Log.w(TAG, "failed to get thumbnail from: " + mLocalFilePath);
}
if (thumbData != null) {
//获取成功,就直接生成bitmap
Bitmap bitmap = DecodeUtils.decodeIfBigEnough(
jc, thumbData, options, targetSize);
if (bitmap != null) return bitmap;
}
}
//最后拿不到现成的,才调用方法进行解码
return DecodeUtils.decodeThumbnail(jc, mLocalFilePath, options, targetSize, type);
}
...
}
- 5.6 缩略图解码方法
DecodeUtils.decodeThumbnail
package com.android.gallery3d.data;
public class DecodeUtils {
public static Bitmap decodeThumbnail(
JobContext jc, String filePath, Options options, int targetSize, int type) {
FileInputStream fis = null;
...
return decodeThumbnail(jc, fd, options, targetSize, type);
...
}
public static Bitmap decodeThumbnail(
...
Bitmap result = BitmapFactory.decodeFileDescriptor(fd, null, options);
...
BitmapUtils.resizeBitmapByScale(result, scale, true)
...
// 绘制缩略图
if (scale <= 0.5) result = BitmapUtils.resizeBitmapByScale(result, scale, true);
)
绘制缩略图
package com.android.gallery3d.common;
public class BitmapUtils {
public static Bitmap resizeBitmapByScale(
Bitmap bitmap, float scale, boolean recycle) {
int width = Math.round(bitmap.getWidth() * scale);
int height = Math.round(bitmap.getHeight() * scale);
if (width == bitmap.getWidth()
&& height == bitmap.getHeight()) return bitmap;
Bitmap target = Bitmap.createBitmap(width, height, getConfig(bitmap));
Canvas canvas = new Canvas(target);
//这里写到buffer后,就返回了,整个过程没有看到再去写文件流
canvas.scale(scale, scale);
Paint paint = new Paint(Paint.FILTER_BITMAP_FLAG | Paint.DITHER_FLAG);
canvas.drawBitmap(bitmap, 0, 0, paint);
if (recycle) bitmap.recycle();
return target;
}