xml地图|网站地图|网站标签 [设为首页] [加入收藏]

您的位置:亚洲必赢 > 计算机尝试 > 打造一个简单通用的Material加载LoadingView,图片加

打造一个简单通用的Material加载LoadingView,图片加

发布时间:2020-02-15 07:21编辑:计算机尝试浏览(141)

    大家开垦App时,都不免要向服务器乞请数据,在数额重返在此以前平常都急需有个进度提示器来报告客户,程序正在着力帮你加载,当数码再次回到后展现符合规律数据,那是个很简短也很常用的作用,然而大概每八个页面都亟待为那么些大致敬义浪费精力体力,所以大家须要二个简单易行通用的加载LoadingView。

    貌似项目中,用得最多的相应有:UniversalImageLoader、Picasso、Fresco、Glide那三种。

    所谓三级缓存指的是内部存储器、磁盘缓存、互连网加载 。bitmap获取存取逻辑大致如下:
    1.得到 url ,央浼网络加载。
    2.把加载成功的流存入到磁盘(DiskLruCache)
    3.流对象转成bitmap对象,并收缩存入到内部存款和储蓄器中(LruCache)
    4.安装压缩后的bitmap对象给ImageView
    那时候图片已成功体现并达成成储,再一次加载时可依次从内部存款和储蓄器、磁盘中抽取bitmap,当都为空去乞请网络并储存,以此重复。

    在编写Android程序的时候日常要用到广大图纸,分裂图片总是会有例外的形象、差异的大小,但在大许多情状下,这个图片都会胜出大家前后相继所急需的大大小小。例如说系统图片Curry彰显的图形大都以用手提式有线电话机拍片头拍出来的,这几个图片的分辨率会比大家手提式无线电话机显示器的分辨率高得多。我们应该知道,我们编辑的应用程序都以有自然内部存储器限定的,程序占用了过高的内部存款和储蓄器就便于现身OOM(OutOfMemory卡塔尔(قطر‎非常。图片的优化,能够给客户能够的体会,美化程序界面以致制止程序现身OOM。下边那篇作品告诉大家得以经过代码看出每一种应用程序最高可用内部存款和储蓄器是多少,当然大家得以因而第三方工具来对应用程式质量进行检查实验:打造一个简单通用的Material加载LoadingView,图片加载框架对比。http://www.ineice.com/

    实现Material Progressbar

    因为网络供给的时辰日常是不解的,所以我们常常都以用二个循环的圆形提示器来提示客户,如下图。

    必赢彩票网下载 1Material-Progressbar

    其生龙活虎View,细心察看,能够按上边包车型客车步子做最佳循环来展现:

    1.基于初阶弧度startArc和要画的弧度arc,画八个半圆,弧度arc逐步加大。2.判读弧度arc是不是高于maxArc,假使为真,最初弧度startArc开端加多,弧度arc逐渐降低。3.当弧度arc小于minArc时,回到第1步。同有的时候候,整个画布canvas在依据二个角速度做旋转。除外还应该有大器晚成件职业要做,须要在弧形中间画一个圆形,来擦除中间有些的水彩,大家能够用Xfermode来完毕,Xfermode能够对四个图层按准则进行混合,具体能够自行谷歌哦。

    大家最初入手实现,篇幅关系,只贴一些首要代码片段(项目现已分享到Github,结尾会给出链接)。

    public class MaterialCircleView extends View {/** * 是否需要对画笔颜色进行渐变处理 */private boolean bGradient;/** * 画笔颜色 */private int circleColor;/** * 画圆圈宽度 */private int circleWidth;/** * 圆圈半径 */private int radius;public MaterialCircleView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); TypedArray t = null; try { t = context.obtainStyledAttributes(attrs, R.styleable.MaterialCircleView, 0, defStyleAttr); setbGradient(t.getBoolean(R.styleable.MaterialCircleView_bGradient, true)); circleColor = t.getColor(R.styleable.MaterialCircleView_circleColor, getResources().getColor(android.R.color.holo_blue_light)); circleWidth = t.getDimensionPixelSize(R.styleable.MaterialCircleView_circleWidth, 10); radius = t.getDimensionPixelSize(R.styleable.MaterialCircleView_radius, 50); } finally { if (t != null) { t.recycle(); } } mPaint = new Paint(); if (isbGradient { mPaint.setColor(Color.rgb(red, green, blue)); }else { mPaint.setColor(circleColor); } mPaint.setAntiAlias; setBackgroundColor(getResources().getColor(android.R.color.transparent));}@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); sWidth = this.getMeasuredWidth(); sHeight = this.getMeasuredHeight(); halfWidth = sWidth / 2; halfHeight = sHeight / 2;}@Overrideprotected void onDraw(Canvas canvas) { super.onDraw; //计算startAngle和endAngle, //保证它们在maxAngle和minAngle之间循环递增递减 if (startAngle == minAngle) { endAngle  = 6; } if (endAngle >= 280 || startAngle > minAngle) { startAngle  = 6; if(endAngle > 20) { endAngle -= 6; } } if (startAngle > minAngle   280) { minAngle = startAngle; startAngle = minAngle; endAngle = 20; } checkPaint(); //旋转canvas canvas.rotate(curAngle  = rotateDelta, halfWidth, halfHeight); //将弧度和擦除圆形绘制在bitmap上 Bitmap bitmap = Bitmap.createBitmap(sWidth, sHeight, Bitmap.Config.ARGB_8888); Canvas bmpCanvas = new Canvas; bmpCanvas.drawArc(new RectF(0, 0, sWidth, sHeight), startAngle, endAngle, true, mPaint); Paint transparentPaint = new Paint(); transparentPaint.setAntiAlias; transparentPaint.setColor(getResources().getColor(android.R.color.transparent)); transparentPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR)); bmpCanvas.drawCircle(halfWidth, halfHeight, halfWidth - circleWidth, transparentPaint); canvas.drawBitmap(bitmap, 0, 0, new Paint; //保证绘制动画延续 invalidate();}
    

    蓬蓬勃勃体完成进程正是那般,代码量比少之甚少,这里顺带提一下,我们十一分达成了一个颜色渐变的长河,福睿斯.styleable.MaterialCircleView_必赢彩票网下载,bGradient属性是true时启用,其实就一贯退换mPaint的颜料。

    private int colorDelta = 2;private void checkPaint() { if (isbGradient { switch (phase % 5) { case 0: green  = colorDelta; if (green > 255) { green = 255; phase   ; } break; case 1: red  = colorDelta; green -= colorDelta; if (red > 255) { red = 255; green = 0; phase   ; } break; case 2: blue -= colorDelta; if (blue < 0) { blue = 0; phase   ; } break; case 3: red -= colorDelta; green  = colorDelta; if (red < 0) { red = 0; green = 255; phase   ; } break; case 4: green -= colorDelta; blue  = colorDelta; if (green < 0) { green = 0; blue = 255; phase   ; } break; } mPaint.setColor(Color.rgb(red, green, blue)); }}
    

    简单的说地解析下风流倜傥后生可畏框架的情事:

    当中涉嫌到LruCache和DiskLruCache(LRU算法),线程池使用,图片压缩,Handler消亡异步难题,通过setTag来标志ImageView。
    关于:

    int maxMemory = (int)(Runtime.getRuntime().maxMemory() / 1024);

    实现UniversalLoadingView

    现行反革命早原来就有了圆形提示器,还亟需叁个textView来呈现文字,所以大家再封装二个ViewGroup,来管理加载的二种情况,包蕴提醒器的藏匿和切实,textView文本的修改等。相通只贴关键代码片段。

    public class UniversalLoadingView extends ViewGroup{public enum State{ GONE, LOADING, LOADING_FALIED, LOADING_EMPTY}public UniversalLoadingView(Context context) { this(context, null);}public UniversalLoadingView(Context context, AttributeSet attrs) { this(context, attrs, 0);}public UniversalLoadingView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); TypedArray t = null; try { t = context.obtainStyledAttributes(attrs, R.styleable.MaterialCircleView, 0, defStyleAttr); bGradient = t.getBoolean(R.styleable.MaterialCircleView_bGradient, true); circleColor = t.getColor(R.styleable.MaterialCircleView_circleColor, getResources().getColor(android.R.color.holo_blue_light)); circleWidth = t.getDimensionPixelSize(R.styleable.MaterialCircleView_circleWidth, 10); radius = t.getDimensionPixelSize(R.styleable.MaterialCircleView_radius, MaterialCircleView.dpToPx(50, getResources; } finally { if (t != null) { t.recycle(); } } try { t = context.obtainStyledAttributes(attrs, R.styleable.UniversalLoadingView, 0, defStyleAttr); setbTransparent(t.getBoolean(R.styleable.UniversalLoadingView_bg_transparent, false)); alpha = t.getDimensionPixelSize(R.styleable.UniversalLoadingView_bg_alpha, 255); } finally { if (t != null) { t.recycle(); } } materialCircleView = new MaterialCircleView(context, attrs, defStyleAttr); //add circle view addView(materialCircleView); mTipTextView = new TextView; mTipTextView.setText(LOADING_TIP); mTipTextView.setTextSize; mTipTextView.setGravity(Gravity.CENTER); mTipTextView.setSingleLine; mTipTextView.setMaxLines; mTipTextView.setTextColor(getResources().getColo r(android.R.color.darker_gray)); addView(mTipTextView); this.setOnClickListener(new OnClickListener() { @Override public void onClick { if (mLoadState == State.LOADING_EMPTY || mLoadState == State.LOADING_FALIED) { if (mReloadListener != null) { mReloadListener.reload(); } } } }); mHandler = new Handler(); if (isbTransparent { setBackgroundColor(getResources().getColor(android.R.color.transparent)); }}@Overridepublic boolean onInterceptTouchEvent(MotionEvent ev) {// return super.onInterceptTouchEvent; return true;}@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { measureChildren(widthMeasureSpec, heightMeasureSpec); LayoutParams params = (LayoutParams) materialCircleView.getLayoutParams(); sWidth = MeasureSpec.getSize(widthMeasureSpec); sHeight = MeasureSpec.getSize(heightMeasureSpec); params.left = (sWidth - radius) / 2; params.top = (sHeight - radius) / 2 - radius; params.width = radius; params.height = radius; LayoutParams tipParams = (LayoutParams) mTipTextView.getLayoutParams(); int tipWidth = MaterialCircleView.dpToPx(100, getResources; int tipHeight = MaterialCircleView.dpToPx(50, getResources; tipParams.left = (sWidth - tipWidth) / 2; tipParams.top = (sHeight - radius) / 2 ; tipParams.width = tipWidth; tipParams.height = tipHeight; setMeasuredDimension(sWidth, sHeight);}@Overrideprotected void onLayout(boolean changed, int l, int t, int r, int b) { LayoutParams params = (LayoutParams) materialCircleView.getLayoutParams(); materialCircleView.layout(params.left, params.top, params.left   params.width , params.top   params.height); LayoutParams tipParams = (LayoutParams) mTipTextView.getLayoutParams(); mTipTextView.layout(tipParams.left, tipParams.top, tipParams.left   tipParams.width, tipParams.top   tipParams.height);}
    

    大家还索要多个爆出叁个重试加载数据的接口,因为总有网络倒霉的时候。

    public void setOnReloadListener(ReloadListner listener) { this.mReloadListener = listener;}/** * reload interface */public interface ReloadListner { public void reload();}
    

    在Activity的Xml布局文件中,我们能够直接助长

     <com.sw.library.widget.library.UniversalLoadingView android: app:bGradient="false" app:radius="50dp" app:bg_transparent="false" app:circleColor="@android:color/holo_green_dark" android:background="@android:color/white" android:layout_width="match_parent" android:layout_height="match_parent"></com.sw.library.widget.library.UniversalLoadingView>
    

    也能够平素new UniversalLoadingView来成立,然后addView到构造根容器中。这一个类型小编早已分享到Github了 request,协同进步.最终是运作效果图,有图有真相。

    必赢彩票网下载 2demo.gif

    ① UniversalImageLoader

    LruCache

    LRU:(Least Recently Used卡塔尔(قطر‎是多年来起码使用算法,内部使用三个LinkedHashMap以强援引的艺术存款和储蓄外部的缓存对象,大旨情想是当缓存满时优先淘汰近日起码使用的缓存对象(JakeWharton/DiskLruCache* 下载*)。

            int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);                                
            int cacheSize = maxMemory / 8;
            mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
                @Override
                protected int sizeOf(String key, Bitmap value) {
                    return value.getRowBytes() * value.getWidth() / 1024;
                }
            };
    

    只必要提供缓存的总容积大小同样爱惜写sizeOf方法,sizeOf方法的效果是计量缓存对象的深浅。特殊景况还需重写entryRemoved方法,移除旧缓存时会调用此格局,因而能够当中做些能源回笼职业(如要求)。

    Log.d("TAG", "Max memory is" maxMemory "KB");

    GitHub - nostra13/Android-Universal-Image-Loader: Powerful and flexible library for loading, caching and displaying images on Android.

    ThreadPoolExecutor

    线程池:席卷 corePoolSize 宗旨线程数、maximumPoolSize 最大线程数、keepAliveTime 非大旨线闲置时的超时时间长度 、unit 时间长度单位、workQueue 职责队列、threadFactory 线程工厂,此中线程工厂为线程池提供创造新线程的功能,独有 Thread new Thread(Runnabler卡塔尔(قطر‎方法。
    施行职分时大概信守如下准则:
    1)固然线程中的线程数量 未完成 主旨线程的数据 ,那么会直接开发银行三个主干线程来试行职分。
    2)假若 线程中的线程数量 已经高达也许超过基本线程的数据 ,那么任务会被插入到职责队列中驱除等待实行。
    3)若是 在步骤第22中学不也许将职责插入到任务队列,那频仍然为天职队列 已满,这时候 要是线程数量未有达标线程池规定的最大值 ,那么会应声运营四个非宗旨线程来实施职分。
    4)假诺手续3中线程数量风流倜傥度达到 线程池规定的最大值 ,那么就谢绝实践此职责,
    ThreadPoolExecutor 会调用 RejectedExecutionHandler 的 rejectedExecution 方法来公告调用者。
    线程池的帮助和益处:
    1)重用线程池中的线程,幸免因为线程的始建和销毁所推动的属性 花销。
    2)能有效调节 线程池的最大产出数据,制止多量的线程此前因互相抢占系统财富而招致短路的光景。
    3)能够对线程实行轻松的保管,并提供准期实行以至内定间距循环推行等成效。
    实则Android封装了四类线程池,在那之中FixedThreadPoo唯有中央线程何况大旨线程不会被回笼,未有过期机制,其余队列也远非大小限定,所以能越发火速地响应外界的央浼;CachedThreadPool相符奉行大气的耗费时间超级少的天职;ScheduledThreadPool符合管理依期和持有原则性重复职务;SingleThreadExcuor内部只有三个基本线程,它确定保证全体的天职都 在同一个线程中按顺序推行,所以这么些任务之间无需管理线程同步的主题素材。能够依照要求选取适用的线程池,原理其实就在创设线程池时设置差异的参数,此处不做安详严整。

    所以在突显高分辨率图片的时候,最佳先将图片打开裁减。压缩后的图片大小应该和用来展现它的控件大小左近,在一个相当小的ImageView上出示一李京大的图纸不会推动其余视觉上的好处,但却会据有我们一定多难得的内部存款和储蓄器,况兼在性质上还有大概会拉动消极的一面影响。上面我们就来看意气风发看,如何对一张大图片实行适度的减弱,让它亦能够最棒大小展现的还要,还是能够堤防OOM的面世。

    相比较老的图形加载库,今后早就结束维护了。不提出接纳。就算从前相当的火。

    图形压缩

    想要压缩,大家先是步应该是获取imageview想要展现的分寸,没大小显明不可能压缩?
    那么怎样拿到imageview想要呈现的大小呢?(该模块转自HongYang)

    /**
     * http://blog.csdn.net/lmj623565791/article/details/41874561
     *
     * @author zhy
     */
    public class ImageSizeUtil {
        /**
         * 根据需求的宽和高以及图片实际的宽和高计算SampleSize
         *
         * @param options
         * @return
         */
        public static int caculateInSampleSize(Options options, int reqWidth,
                                               int reqHeight) {
            int width = options.outWidth;
            int height = options.outHeight;
    
            int inSampleSize = 1;
    
            while (width > reqWidth || height > reqHeight) {
                width /= 2;
                height /= 2;
                inSampleSize  ;
            }
            return inSampleSize;
        }
    
        /**
         * 根据ImageView获适当的压缩的宽和高
         *
         * @param imageView
         * @return
         */
        public static ImageSize getImageViewSize(ImageView imageView) {
    
            ImageSize imageSize = new ImageSize();
            DisplayMetrics displayMetrics = imageView.getContext().getResources()
                    .getDisplayMetrics();
    
    
            LayoutParams lp = imageView.getLayoutParams();
    
            int width = imageView.getWidth();// 获取imageview的实际宽度
            if (width <= 0) {
                width = lp.width;// 获取imageview在layout中声明的宽度
            }
            if (width <= 0) {
                //width = imageView.getMaxWidth();// 检查最大值
                width = getImageViewFieldValue(imageView, "mMaxWidth");
            }
            if (width <= 0) {
                width = displayMetrics.widthPixels;
            }
    
            int height = imageView.getHeight();// 获取imageview的实际高度
            if (height <= 0) {
                height = lp.height;// 获取imageview在layout中声明的宽度
            }
            if (height <= 0) {
                height = getImageViewFieldValue(imageView, "mMaxHeight");// 检查最大值
            }
            if (height <= 0) {
                height = displayMetrics.heightPixels;
            }
            imageSize.width = width;
            imageSize.height = height;
    
            return imageSize;
        }
    
        public static class ImageSize {
            int width;
            int height;
        }
    
        /**
         * 通过反射获取imageview的某个属性值
         *
         * @param object
         * @param fieldName
         * @return
         */
        public static int getImageViewFieldValue(Object object, String fieldName) {
            int value = 0;
            try {
                Field field = ImageView.class.getDeclaredField(fieldName);
                field.setAccessible(true);
                int fieldValue = field.getInt(object);
                if (fieldValue > 0 && fieldValue < Integer.MAX_VALUE) {
                    value = fieldValue;
                }
            } catch (Exception e) {
            }
            return value;
        }
    }
    

    能够看出,大家获得imageview以往:
    首先盘算通过getWidth获取呈现的宽;某个时候,这么些getWidth重回的是0;
    那么大家再去拜望它有未有在构造文件中书写宽;
    万黄金时代结构文件中也未有精确值,那么大家再去造访它有未有设置最大值;
    若是最大值也没安装,那么我们只有拿出大家的终极方案,使用大家的显示屏宽度;
    总的说来,不能够让它放肆,大家明显要获得叁个适逢其会的呈现值。
    能够见到这里还是最大开间,大家用的反光,并不是getMaxWidth(卡塔尔;因为getMaxWidth竟然要API 16;为了宽容性,大家应用反射的方案。
    收获尺寸大小后可由此caculateIn萨姆pleSize(卡塔尔(قطر‎方法设置合适的in萨姆pleSize。

    好啊,代码给了很详细的注释,具体得以达成可根据代码看特别清楚。
    完整代码如下 :

    /**
     * Created by zhanFeng on 2017/3/22.
     */
    
    public class ImageLoader {
    
        private static ImageLoader sImageLoader;
    
        public static ImageLoader getInstance(Context context) {
            if (sImageLoader == null) {
                synchronized (ImageLoader.class) {
                    if (sImageLoader == null) {
                        sImageLoader = new ImageLoader(context);
                    }
                }
            }
            return sImageLoader;
        }
    
        private static final String TAG = "ImageLoader";
        private Context mContext;
        private LruCache<String, Bitmap> mMemoryCache;
        private DiskLruCache mDiskLruCache;
        //磁盘缓存空间 20M
        private static final long DISK_CACHE_SIZE = 1024 * 1024 * 20;
        private boolean mIsDiskLruCacheCreated = false;
    
        private final static int TAG_KEY_URI = R.id.imageLoader_uri;
        private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
        private static final int CORE_POOL_SIZE = CPU_COUNT   1;
        private static final int MAXMUN_POOL_SIZE = CPU_COUNT * 2   1;
        private static final long KEEP_ALIVE = 10L;
    
        private static final ThreadFactory sThreadFactor = new ThreadFactory() {
            private final AtomicInteger mCount = new AtomicInteger(1);
    
            @Override
            public Thread newThread(Runnable r) {
                return new Thread(r, "ImageLoader#"   mCount.getAndIncrement());
            }
        };
    
        //创建线程池
        private static final Executor THREAD_POOL_EXECUTOR = new ThreadPoolExecutor(CORE_POOL_SIZE, MAXMUN_POOL_SIZE,
                KEEP_ALIVE, TimeUnit.SECONDS, new LinkedBlockingDeque<Runnable>(), sThreadFactor);
    
        private Handler mMainHandler = new Handler(Looper.getMainLooper()) {
            @Override
            public void handleMessage(Message msg) {
                LoaderResult loaderResult = (LoaderResult) msg.obj;
                ImageView imageView = loaderResult.mImageView;
                String uri = (String) imageView.getTag(TAG_KEY_URI);
                if (uri.equals(loaderResult.uri)) {
                    System.out.println("Handler:"   uri);
                    imageView.setImageBitmap(loaderResult.bitmap);
                } else {
                    Log.e(TAG, "set image bitmap,but url has changed,ignored!");
                }
            }
        };
    
        private ImageLoader(Context context) {
            //单例中防止上下文引起的内存泄漏
            mContext = context.getApplicationContext();
            //获取应用内存
            int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
            //内存缓存空间为应用内存的1/8
            int cacheSize = maxMemory / 8;
            mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
                @Override
                protected int sizeOf(String key, Bitmap value) {
                    return value.getRowBytes() * value.getWidth() / 1024;
                }
            };
    
            //创建保存bitmap的文件
            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();
                }
            }
    
        }
    
        /**
         * 获取手机存储空间大小
         *
         * @param path
         * @return
         */
        private long getUsableSpace(File path) {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD) {
                return path.getUsableSpace();
            }
            final StatFs statFs = new StatFs(path.getPath());
            return statFs.getBlockSizeLong() * statFs.getAvailableBlocksLong();
        }
    
        /**
         * 创建保存bitmap的文件
         * true 有SD卡且未移除时创建在SD卡创建      false 创建在内置存储卡上
         *
         * @param context
         * @param bitmapName
         * @return
         */
        private File getDiskCacheDir(Context context, String bitmapName) {
            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   bitmapName);
        }
    
        /**
         * 从内存中获取bitmap
         *
         * @param url
         * @return
         */
        private Bitmap loadBitmapFromMemCache(String url) {
            String key = hashKeyFormUrl(url);
            return mMemoryCache.get(key);
        }
    
        /**
         * 添加bitmap到内存中
         *
         * @param url
         * @param bitmap
         */
        private void addBitmapToMemoryCache(String url, Bitmap bitmap) {
            if (loadBitmapFromMemCache(url) == null) {
                if (bitmap != null) {
                    mMemoryCache.put(url, bitmap);
                }
            }
        }
    
        public void displayImage(@Nullable final String url, final ImageView imageView) {
            //setTag 与 Handler中getTag可对imageView进行识别,避免异步造成图片显示错位
            imageView.setTag(TAG_KEY_URI, url);
    
            Bitmap bitmap = loadBitmapFromMemCache(url);
    
            if (bitmap != null) {
                imageView.setImageBitmap(bitmap);
                return;
            }
    
            //创建任务
            Runnable runnableTask = new Runnable() {
                @Override
                public void run() {
                    Bitmap bitmap = loadBitmap(url, imageView);
                    if (bitmap != null) {
                        LoaderResult loaderResult = new LoaderResult(imageView, url, bitmap);
                        //Handler发送消息
                        mMainHandler.obtainMessage(0, loaderResult).sendToTarget();
    
                    }
                }
            };
    
            //将任务放入线程池执行
            THREAD_POOL_EXECUTOR.execute(runnableTask);
        }
    
        /**
         * @param url
         * @param imageView
         * @return
         */
        private Bitmap loadBitmap(String url, ImageView imageView) {
            Bitmap bitmap = null;
    
            bitmap = loadBitmapFromMemCache(url);
            if (bitmap != null) {
                return bitmap;
            }
            try {
                bitmap = loadBitmapFromDiskCache(url, imageView);
                if (bitmap != null) {
                    return bitmap;
                }
    
                bitmap = loadBitmapFromHttp(url, imageView);
            } catch (IOException e) {
                e.printStackTrace();
            }
    
            //存储空间不够大时未创建磁盘缓存,直接从网络加载
            if (bitmap == null && !mIsDiskLruCacheCreated) {
                bitmap = downloadBitmapFromUrl(url);
            }
    
            return bitmap;
        }
    
        /**
         * 从磁盘中加载bitmap
         *
         * @param url
         * @param imageView
         * @return bitmap
         * @throws IOException
         */
        private Bitmap loadBitmapFromDiskCache(String url, ImageView imageView) throws IOException {
            if (Looper.myLooper() == Looper.getMainLooper()) {
                Log.w(TAG, "load bitmap from UI Thread,it's not recommend!");
            }
            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(0);
                FileDescriptor fileDescriptor = fileInputStream.getFD();
                ImageSizeUtil.ImageSize imageViewSize = ImageSizeUtil.getImageViewSize(imageView);
                //将文件描述符压缩并转换成bitmap
                bitmap = decodeSampledBitmapFromPath(fileDescriptor, imageViewSize.width, imageViewSize.height);
                if (null != bitmap) {
                    //保存到内存中
                    addBitmapToMemoryCache(key, bitmap);
                }
            }
            return bitmap;
        }
    
        private static class LoaderResult {
            private ImageView mImageView;
            private String uri;
            private Bitmap bitmap;
    
            public LoaderResult(ImageView imageView, String url, Bitmap bitmap) {
                this.mImageView = imageView;
                this.uri = url;
                this.bitmap = bitmap;
            }
    
        }
    
    
        public static final int IO_BUFFER_SIZE = 1024 * 8;
    
        private Bitmap downloadBitmapFromUrl(String uri) {
    
            Bitmap bitmap = null;
            HttpURLConnection httpURLConnection = null;
            BufferedInputStream is = null;
    
            try {
                URL url = new URL(uri);
                try {
                    httpURLConnection = (HttpURLConnection) url.openConnection();
                    is = new BufferedInputStream(httpURLConnection.getInputStream(), IO_BUFFER_SIZE);
                    //此处可把bitmap对象进行压缩,可自行实现,很简单的
                    bitmap = BitmapFactory.decodeStream(is);
                } catch (IOException e) {
                    Log.e(TAG, "Error in downloadBitmap:"   e);
                }
            } catch (MalformedURLException e) {
                e.printStackTrace();
            } finally {
                if (null != is) {
                    try {
                        is.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                if (null != httpURLConnection) {
                    try {
                        httpURLConnection.disconnect();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }
            return bitmap;
        }
    
        /**
         * 从网络下载图片
         *
         * @param url
         * @param imageView
         * @return
         */
        private Bitmap loadBitmapFromHttp(String url, ImageView imageView) {
            if (Looper.myLooper() == Looper.getMainLooper()) {
                throw new RuntimeException("can not visit network form UI Thread");
            }
            if (mDiskLruCache == null) {
                return null;
            }
            String key = hashKeyFormUrl(url);
            try {
                DiskLruCache.Editor edit = mDiskLruCache.edit(key);
                if (null != edit) {
                    OutputStream outputStream = edit.newOutputStream(0);
                    if (downloadUrlToStream(url, outputStream)) {
                        edit.commit();
                    } else {
                        edit.abort();
                    }
                    mDiskLruCache.flush();
                }
                return loadBitmapFromDiskCache(url, imageView);
            } catch (IOException e) {
                return null;
            }
        }
    
        /**
         * 从网络加载图片并保存到磁盘中
         *
         * @param urlString
         * @param outputStream
         * @return
         */
        private boolean downloadUrlToStream(String urlString, OutputStream outputStream) {
            HttpURLConnection urlConnection = null;
            BufferedOutputStream out = null;
            BufferedInputStream in = null;
            try {
                final URL url = new URL(urlString);
                urlConnection = (HttpURLConnection) url.openConnection();
                in = new BufferedInputStream(urlConnection.getInputStream(), 8 * 1024);
                out = new BufferedOutputStream(outputStream, 8 * 1024);
                int b;
                while ((b = in.read()) != -1) {
                    out.write(b);
                }
                return true;
            } catch (final IOException e) {
                e.printStackTrace();
            } finally {
                if (urlConnection != null) {
                    urlConnection.disconnect();
                }
                try {
                    if (out != null) {
                        out.close();
                    }
                    if (in != null) {
                        in.close();
                    }
                } catch (final IOException e) {
                    e.printStackTrace();
                }
            }
            return false;
        }
    
        /**
         * md5加密,避免url存在特殊字符
         *
         * @param url
         * @return
         */
        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[] digest) {
            StringBuilder stringBuilder = new StringBuilder();
            for (int i = 0; i < digest.length; i  ) {
                String hex = Integer.toHexString(0xFF & digest[i]);
                if (hex.length() == 1) {
                    stringBuilder.append('0');
                }
                stringBuilder.append(hex);
            }
            return stringBuilder.toString();
        }
    
        /**
         * 根据图片需要显示的宽和高对图片进行压缩
         *
         * @param fileDescriptor
         * @param width
         * @param height
         * @return
         */
        protected Bitmap decodeSampledBitmapFromPath(FileDescriptor fileDescriptor, int width, int height) {
            // 获得图片的宽和高,并不把图片加载到内存中
            BitmapFactory.Options options = new BitmapFactory.Options();
            options.inJustDecodeBounds = true;
            BitmapFactory.decodeFileDescriptor(fileDescriptor, null, options);
    
            //根据需求的宽和高以及图片实际的宽和高计算SampleSize
            options.inSampleSize = ImageSizeUtil.caculateInSampleSize(options,
                    width, height);
    
            // 使用获得到的InSampleSize再次解析图片
            options.inJustDecodeBounds = false;
            Bitmap bitmap = BitmapFactory.decodeFileDescriptor(fileDescriptor, null, options);
            return bitmap;
    
        }
    }
    

    好啊,终于本身实现了图片加载,设置属性可通过builder格局来实行打包。当然,当中蒙受叁个坑正是MD5加密时,相应地点替换来mDigest.digest(卡塔尔.length和mDigest.digest(卡塔尔.[i]会冷俊不禁差异的url转变的key雷同的标题,此处还得对mDigest.digest(卡塔尔转产生一个数组对象再举行调用!


    参考:
    《Abdroid开采格局探求》
    Android 框架练成教你制作高效的图纸加载框架
    AndroidInstagram完整版,完美结合LruCache和DiskLruCache

    BitmapFactory这些类提供了八个深入分析方法(decodeByteArray,

    ② Picasso

    decodeFile, decodeResource等卡塔尔用于创制Bitmap对象,大家应当依赖图片的根源选取适宜的点子。举个例子SD卡中的图片可以应用decodeFile方法,网络上的图样能够利用decodeStream方法,能源文件中的图片能够接收decodeResource方法。那些方法会尝试为早就创设的bitmap分配内部存款和储蓄器,这个时候就能够相当轻巧引致OOM现身。为此每后生可畏种解析方法都提供了贰个可选的BitmapFactory.Options参数,将以此参数的inJustDecodeBounds属性设置为true就足以让剖析方法禁绝为bitmap分配内部存款和储蓄器,重返值也不再是三个Bitmap对象,而是null。纵然Bitmap是null了,不过BitmapFactory.Options的outWidth、outHeight和outMimeType属性都会被赋值。那么些技艺让我们得以在加载图片以前就获得到图片的长度宽度值和MIME类型,进而依照情形对图纸进行压缩。如下代码所示:

    GitHub - square/picasso: A powerful image downloading and caching library for Android

    [java] view plaincopy

    Picasso 是 Square 公司出的,功用强盛况兼调用简洁,如下所示:

    BitmapFactory.Options options = newBitmapFactory.Options();

    Picasso.with(context).load(");

    options.inJustDecodeBounds = true;

    上述代码正是给一个 ImageView 加载远程图片的三个示例.

    BitmapFactory.decodeResource(getResources(),R.id.myimage, options);

    ③ Fresco

    int imageHeight = options.outHeight;

    GitHub - facebook/fresco: An Android library for managing images and the memory they use.

    int imageWidth = options.outWidth;

    Fresco 是 推杰出的,优点在于Fresco是在Native层优化内部存款和储蓄器,制止OOM现身。

    String imageType =options.outMimeType;

    瑕玷是应用绝对麻烦点,何况导入包体量超大。

    为了幸免OOM至极,最佳在剖判每张图纸的时候都先检查一下图片的朗朗上口,除非你极度相信图片的来源,保证这么些图片都不会超越你程序的可用内存。

    ④ Glide

    现行反革命图片的大小已经知晓了,大家就可以决定是把整张图片加载到内部存款和储蓄器中依然加载一个压缩版的图纸到内部存款和储蓄器中。以下多少个成分是我们要求思索的:

    GitHub - bumptech/glide: An image loading and caching library for Android focused on smooth scrolling

    预估一下加载整张图片所需占用的内部存款和储蓄器。

    Glide 是 Google出品,基于Picasso和Picasso相近使用轻便。

    为了加载这一张图纸你所乐意提供多少内部存款和储蓄器。

    Glide 的 Bitmap 格式是 RGB_565 格式,而 Picasso 是 ARGB_8888 格式,代表着内部存款和储蓄器消耗少。

    用来体现那张图纸的控件的实际尺寸。

    Glide的囤积是动态的,是依照你的显示屏控件大小,来缓存对应尺寸的图纸到地面内部存款和储蓄器,好处是省去存款和储蓄空间、加载速度也变快。

    当前设施的显示器尺寸和分辨率。

    还会有便是Glide扶植GIF。

    比如,你的ImageView只有128*96像素的尺寸,只是为着呈现一张缩略图,那时把一张1024*768像素的图样完全加载到内部存款和储蓄器中显著是不值得的。

    SO!日常景观下,用Glide就足足了。Fresco其实也十一分好,借使您的app是多图、社交类的app的话。

    那我们怎么着本领对图片进行压缩呢?通过设置BitmapFactory.Options中inSampleSize的值就足以兑现。举个例子大家有一张2048*1536像素的图样,将in萨姆pleSize的值设置为4,就能够把那张图纸压缩成512*384像素。原来加载那张图片供给占用13M的内部存款和储蓄器,压缩后就只必要占用0.75M了(要是图片是A奥迪Q3GB_8888体系,即每一个像素点占用4个字节卡塔尔(قطر‎。下边包车型客车章程能够依赖传入的宽和高,计算出合适的in萨姆pleSize值:

    [java] view plaincopy

    public static intcalculateInSampleSize(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 heightRatio = Math.round((float) height / (float)reqHeight);

    本文由亚洲必赢发布于计算机尝试,转载请注明出处:打造一个简单通用的Material加载LoadingView,图片加

    关键词: 日记本 Android 简单 加载