LargeImage

Introduction: Android 加载大图 可以高清显示 10000*10000 像素的图片,轻松实现微博长图功能
More: Author   ReportBugs   
Tags:
加载大图-长图-

Android 加载大图 可以高清显示 10000*10000 像素的图片
可以滑动,放大缩小具有 PhotoView 的效果
普通图片也可以用它展示

Gradle

  compile 'com.shizhefei:LargeImageView:1.1.0'

Download Demo apk

效果

image

使用方法

<com.shizhefei.view.largeimage.LargeImageView
    android:id="@+id/imageView"
    android:scrollbars="vertical|horizontal"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

代码

largeImageView = (LargeImageView) findViewById(R.id.imageView);

//通过文件的方式加载 sd 卡中的大图
largeImageView.setImage(new FileBitmapDecoderFactory(file));

//通过流的方式加载 assets 文件夹里面的大图
largeImageView.setImage(new InputStreamBitmapDecoderFactory(getAssets().open(ss[position])))

//加载普通大小图片
largeImageView.setImage(R.drawable.cat);
largeImageView.setImage(drawable);
largeImageView.setImage(bitmap);

支持的事件

    largeImageView.setOnClickListener(onClickListener);
    largeImageView.setOnLongClickListener(onLongClickListener);

设置是否可以缩放

 largeImageView.setEnabled(true);

Hook 临界值(不设置的话会使用默认的计算缩放最小倍数和最大倍数)

/**
 * Hook 临界值
 */
public interface CriticalScaleValueHook {

    /**
     * 返回最小的缩放倍数
     * scale 为 1 的话表示,显示的图片和 View 一样宽
     *
     * @param largeImageView
     * @param imageWidth
     * @param imageHeight
     * @param suggestMinScale 默认建议的最小的缩放倍数
     * @return
     */
    float getMinScale(LargeImageView largeImageView, int imageWidth, int imageHeight, float suggestMinScale);

    /**
     * 返回最大的缩放倍数
     * scale 为 1 的话表示,显示的图片和 View 一样宽
     *
     * @param largeImageView
     * @param imageWidth
     * @param imageHeight
     * @param suggestMaxScale 默认建议的最大的缩放倍数
     * @return
     */
    float getMaxScale(LargeImageView largeImageView, int imageWidth, int imageHeight, float suggestMaxScale);

}

例如

   largeImageView.setCriticalScaleValueHook(new LargeImageView.CriticalScaleValueHook() {
        @Override
        public float getMinScale(LargeImageView largeImageView, int imageWidth, int imageHeight, float suggestMinScale) {
            return 1;
        }

        @Override
        public float getMaxScale(LargeImageView largeImageView, int imageWidth, int imageHeight, float suggestMaxScale) {
            return 4;
        }
    });

加载网络的图片,先下载本地,再通过加载图片的文件
比如 glide 加载图片,具体代码查看 demo

    String url = "http://img.tuku.cn/file_big/201502/3d101a2e6cbd43bc8f395750052c8785.jpg";
    Glide.with(this).load(url).downloadOnly(new ProgressTarget<String, File>(url, null) {
        @Override
        public void onLoadStarted(Drawable placeholder) {
            super.onLoadStarted(placeholder);
            ringProgressBar.setVisibility(View.VISIBLE);
            ringProgressBar.setProgress(0);
        }

        @Override
        public void onProgress(long bytesRead, long expectedLength) {
            int p = 0;
            if (expectedLength >= 0) {
                p = (int) (100 * bytesRead / expectedLength);
            }
            ringProgressBar.setProgress(p);
        }

        @Override
        public void onResourceReady(File resource, GlideAnimation<? super File> animation) {
            super.onResourceReady(resource, animation);
            ringProgressBar.setVisibility(View.GONE);
            largeImageView.setImage(new FileBitmapDecoderFactory(resource));
        }

        @Override
        public void getSize(SizeReadyCallback cb) {
            cb.onSizeReady(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL);
        }
    });

实现原理

只加载显示的区域的图片,切成小块拼接.

LargeImageView

根据滚动和缩放事件 scrollTo 对应的位置,计算当前显示区域的图片绘制出来

UpdateImageView

监听 View 的显示区域的变化,然后加载显示区域内应该显示的图片区域,然后绘制到 View 上
UpdateView 负责监听显示区域的变化的 View,子类通过重写 onUpdateWindow(Rect visiableRect)监听显示区域,大部分代码源于 SurfaceView 监听代码

BlockImageLoader 负责加载显示区域的图片块。

实现细节:

每次 LargeImageView 的 onDraw 方法都会调用 ImageManagerd 的 getDrawData(float imageScale, Rect imageRect)方法,imageRect 为在 View 上图片显示的区域(需要加载的图片区域),imageScale 假设等于 4 的话,就是 View 上显示 1 像素,image 要加载 4 个像素的区域(缩小 4 倍的图片)
getDrawData(float imageScale, Rect imageRect)实现细节:
手势移动,图片显示区域会变化。比如显示区域是 800800,向右移动 2 像素,难道要重新加载 800800 的图片区域? 所以我采用了图片切块的操作,分块的优化

  1. 比如图片显示比例是 1,那么要横向分多少份才,纵向分多少分,才合理?图片显示比例是 4,横向分多少份才,纵向分多少分,才合理。

所以我采用了基准块(图片比例是 1,一个图片块的宽高的合理 sise) BASE_BLOCKSIZE = context.getResources().getDisplayMetrics().heightPixels / 2+1;
图片缩放比例为 1 的话,图片块宽高是 BASE_BLOCKSIZE
图片缩放比例为 4 的话,图片块宽高是 4*BASE_BLOCKSIZE
图片没被位移,那么屏幕上显示横向 2 列,纵向 getDisplayMetrics().heightPixels/BASE_BLOCKSIZE 行

2.因为手势放大缩小操作要加载不同清晰度的图片区域,比如之前的图片缩放是 4,现在缩放是 4.2,难道要重新加载?

  • 通过 public int getNearScale(float imageScale)方法计算趋于 2 的指数次方的值(1,2,4,8,16)
    比如 3.9 和 4.2 和 4 比较接近,就直接加载图片显示比例为 4 的图片块

3.之前没加载的区域,难道要空白显示么?

  • 为了避免加载出现白色块,我会缓存当前比例的加载的图片块,以及 2 倍比例的图片块(之前加载过,并且当前还属于当前显示区域的,如果不是的话也不缓存它) 所以发现没有的话去拿其他比例的图片区去显示

4.难道只加载显示区域?

  • 当然,还会去加载旁边一部分没显示的区域的图片块

5.onDraw 方法是 UI 线程,调用 getDrawData(float imageScale, Rect imageRect)加载图片块的方法怎么不卡住

getDrawData 只返回之前加载过的图片块,而没有加载的是通过 LoadHandler.sendMessage 去加载 LoadHandler 的 Loop 是通过 HandlerThread 线程创建的 Loop,也就是开个线程加载.

每加载一个图片块通过 onImageLoadListenner.onBlockImageLoadFinished();onDraw 重绘
onDraw 又调用 getDrawData 加载,直至需要显示的图片块加载完成

主力类库

1.https://github.com/LuckyJayce/ViewPagerIndicator
Indicator 取代 tabhost,实现网易顶部 tab,新浪微博主页底部 tab,引导页,无限轮播 banner 等效果,高度自定义 tab 和特效

2.https://github.com/LuckyJayce/MVCHelper
实现下拉刷新,滚动底部自动加载更多,分页加载,自动切换显示网络失败布局,暂无数据布局,支持任意 view,支持切换主流下拉刷新框架。

3.https://github.com/LuckyJayce/MultiTypeView
简化 RecyclerView 的多种 type 的 adapter,Fragment 可以动态添加到 RecyclerView 上,实现复杂的界面分多个模块开发

4.https://github.com/LuckyJayce/EventBus
事件总线,通过动态代理接口的形式发布,接收事件。定义一个接口把事件发给注册并实现接口的类

5.https://github.com/LuckyJayce/LargeImage
大图加载,可供学习

6.https://github.com/LuckyJayce/GuideHelper
新手引导页,轻松的实现对应的 view 上面的显示提示信息和展示功能给用户

7.https://github.com/LuckyJayce/HVScrollView
可以双向滚动的 ScrollView,支持嵌套 ScrollView 联级滑动,支持设置支持的滚动方向

8.https://github.com/LuckyJayce/CoolRefreshView
下拉刷新 RefreshView,支持任意 View 的刷新 ,支持自定义 Header,支持 NestedScrollingParent,NestedScrollingChild 的事件分发,嵌套 ViewPager 不会有事件冲突

有了这些类库,让你 6 的飞起

说明

其中 android-gesture-detectors-lib 手势类库
源地址https://github.com/Almeros/android-gesture-detectors

联系方式和问题建议

License

Copyright 2015 shizhefei(LuckyJayce)

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

   http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Apps
About Me
GitHub: Trinea
Facebook: Dev Tools