MupdfViewer 这是最后apk,源码在前面的文章已经贴过了本站下载地址,只是不是最新的.可能不少是旧的内容.
subsampling-scale-image-view这是一个大图片的分块加载的实现.比较不错的.滑动方面我觉得使用flinger的效果比它要流畅,惯性要好.
也有人把这个作成pdf渲染器.但翻页就不好了.
现在的手机相册现在可能使用的是opengl实现的.
本项目中的,自定义flinger在滚动惯性上比其它要强一些.惯性太高,也会看到加载过程,反而会让人觉得慢...
多数的pdf阅读器都会用缓存.下文说到把块的缓存去了,原因之一是无法处理被回收的问题.另一个,缓存如果直接在主线程读取,因为它是同步的,会造成页面卡顿,滚动过程尤其明显.barteksc就是这样的,把它从scroller换成flinger实现滚动,就会明显感觉到卡顿,尤其在放大倍数提升到10倍后,对于barteksc,的10倍还不如我这边缩放4倍大,因为它是以最大的图片去计算的.
所以从这方面来说,我对改造后的效果还是挺满意的.
相册有很多,几乎全部是单张图片的浏览. 并不方便连续的浏览.
需求可能是这样的:
我在微博下了一些长图片,每一张在相册中查看当然可以,能放大,但是比较麻烦.
如果要生成pdf,会占了不少空间,而且照片会增加,比如拍照,会持续拍,总不能一直生成pdf,当然可以使用追加式的.
在浏览目录时,长按菜单,出现相册功能.按时间倒序排列查看.与pdf一样,所有的照片默认是同一个宽.不像barteksc它们都是保留原始宽,这样滑动后,可能容易内容滑出去了.这不是一个阅读器的好的体验方式.因为中间有一张特别宽的图片,会导致内容经常滑出去.
在pdf程序写好以后,我发现,既然生成的pdf可以,我也可以用pdf的浏览方式去查看相册.
相册图片解码速度比pdf还是要快不少的.目前没有针对三星这种拍照方向不对的照片处理.国内已经很少有三星手机了.
由于原来的pdfpage实现的接口,document等接口,添加一种实现即可.
class AlbumViewerActivity extends BaseViewerActivity {
protected ProgressDialog progressDialog;
@Override
protected DecodeService createDecodeService() {
return new DecodeServiceBase(new AlbumContext());
}
protected void loadDocument(String path) {
progressDialog=new ProgressDialog(this);
progressDialog.setMessage("Loading");
progressDialog.show();
AppExecutors.Companion.getInstance().diskIO().execute(() -> {
decodeService.open(path);
AppExecutors.Companion.getInstance().mainThread().execute(() -> {
progressDialog.dismiss();
documentView.showDocument();
});
});
}
}
对activity作一次重构,把加载文档放到异步线程中,因为这不是加载pdf文档,是加载一个目录下的所有图片,有可能比较耗时.
加一个context
class AlbumContext implements CodecContext {
public CodecDocument openDocument(String fileName) {
return AlbumDocument.openDocument(fileName);
}
public void setContentResolver(ContentResolver contentResolver) {
}
public void recycle() {
}
}
然后就是文档
class AlbumDocument implements CodecDocument {
ArrayList<File> files;
int count = 0;
private static FileFilter createFileFilter() {
return pathname -> {
if (pathname.isHidden()) {
return false;
}
if (pathname.isDirectory())
return false;
String fname = pathname.getName().toLowerCase(Locale.ROOT);
return AdapterUtils.INSTANCE.isImage(fname);
};
}
public static AlbumDocument openDocument(String fname) {
File[] fileArray = new File(fname).listFiles(createFileFilter());
ArrayList<File> files = new ArrayList<>(fileArray != null ? Arrays.asList(fileArray) : Collections.<File>emptyList());
Collections.sort(files, (o1, o2) -> {
if (o1 == null && o2 == null) {
return 0;
}
if (o1.isDirectory() && o2.isFile()) return -1;
if (o1.isFile() && o2.isDirectory()) return 1;
if (o1.lastModified() - o2.lastModified() > 0) {
return -1;
} else if (o1.lastModified() - o2.lastModified() < 0) { //jdk7以上需要对称,自反,传递性.
return 1;
} else {
return 0;
}
});
AlbumDocument document = new AlbumDocument(files);
return document;
}
public AlbumDocument(ArrayList<File> files) {
this.files = files;
if (null != files) {
count = files.size();
}
}
public CodecPage getPage(int pageNumber) {
return AlbumPage.createPage(files.get(pageNumber).getAbsolutePath(), pageNumber);
}
public int getPageCount() {
return count;
}
@Override
protected void finalize() throws Throwable {
recycle();
super.finalize();
}
public synchronized void recycle() {
}
@Override
public Outline[] loadOutline() {
return new Outline[0];
}
}
要过滤出支持解码的图片类型,svg目前不行.
最关键的还是解码部分. 这里对每一块解码都可能重新生成一个decoder,可以优化一下.
class AlbumPage implements CodecPage {
private long pageHandle = -1;
int pageWidth;
int pageHeight;
private BitmapRegionDecoder decoder;
private String path;
static AlbumPage createPage(String fname, int pageno) {
AlbumPage pdfPage = new AlbumPage(pageno, fname);
return pdfPage;
}
public AlbumPage(long pageno, String fname) {
this.pageHandle = pageno;
this.path = fname;
}
public int getWidth() {
if (pageWidth == 0) {
decodeBound();
}
return pageWidth;
}
private void decodeBound() {
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(path, options);
pageWidth = options.outWidth;
pageHeight = options.outHeight;
}
public int getHeight() {