美文网首页Android开发Android技术知识Android知识
安卓图片浏览(支持超大图,附源码)

安卓图片浏览(支持超大图,附源码)

作者: jiantaocd | 来源:发表于2017-11-22 10:25 被阅读104次

    大图浏览可以说是所有App必备功能,可见其重要性,所以有必要将其独立,便于维护和复用。本文代码基于SubsamplingScaleImageView开源库实现,增加单手拖拽返回,透明度变化等效果。

    浏览效果

    photoview001
    photoview002
    由于简书有图片大小限制,压缩后很模糊,
    请移步github查看。

    功能实现

    • 超大图浏览

      SubsamplingScaleImageView基于BitmapRegionDecoder实现,避免OOM情况下,轻松浏览超大图,支持各种手势操作。感兴趣的请查阅其源码。

    • 浏览Activity背景透明

          
          //manifest 设置Activity theme属性
          android:theme="@style/photoviewer_theme"
          
          //对应style定义
          <style name="photoviewer_theme" parent="Theme.AppCompat.NoActionBar">
                <item name="android:windowBackground">@android:color/transparent</item>
                <item name="android:windowIsTranslucent">true</item>
         </style> 
           
      
    • 沉浸式效果

          private void hideSystemUI() {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
                View decorView = getWindow().getDecorView();
                decorView.setSystemUiVisibility(
                        View.SYSTEM_UI_FLAG_LAYOUT_STABLE
                                | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
                                | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
                                | View.SYSTEM_UI_FLAG_FULLSCREEN // hide status bar
                                | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
            } else {
                WindowManager.LayoutParams attrs = getWindow().getAttributes();
                attrs.flags |= WindowManager.LayoutParams.FLAG_FULLSCREEN;
                getWindow().setAttributes(attrs);
                getWindow().addFlags(
                        WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS);
            }
            //change navigationbar bg color
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
                getWindow().addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
                getWindow().setNavigationBarColor(Color.TRANSPARENT);
            }
        }
        
      
    • 自定义DragPhotoView拦截touch事件

           setOnTouchListener(new View.OnTouchListener() {
                @Override
                public boolean onTouch(View v, MotionEvent event) {
                    //如果正在动画,拦截不处理。
                    if (isAnimating()) {
                        return true;
                    }
                    //判断图片是否为初始状态(第一次进入后显示的大小)
                    if (isReady() && (firstDisplayScale == getScale())) {
                        final int action = event.getAction();
                        switch (action & MotionEvent.ACTION_MASK) {
                            case MotionEvent.ACTION_DOWN:
                                if (DEBUG) {
                                    Log.d(TAG, "action_down  = " + firstDisplayScale + ", getScale = " + getScale() + " isFirstEnterState = " + (firstDisplayScale == getScale()));
                                }
                                downX = event.getX();
                                downY = event.getY();
                                canDrag = true;
                                break;
      
                            case MotionEvent.ACTION_MOVE:
      
                                if (canDrag) {
      
                                    final float dy = Math.abs(downY - event.getY());
                                    if (firstDisplayScale == getScale() && dy > touchSlop) {
                                        isDragging = true;
                                        translateX = event.getX() - downX;
                                        translateY = event.getY() - downY;
      
                                        float percent = dy / maxTranslateY;
                                        if (percent > 1.0f) {
                                            percent = 1.0f;
                                        }
                                        bgAlpha = (int) (255 * (1 - percent));
                                        bgAlpha = bgAlpha < minBgAlpha ? minBgAlpha : bgAlpha;
                                        //尽量将图片缩放比例设置小点
                                        final float p = dy / getHeight();
                                        bitmapScale = (1 - p);
                                        bitmapScale = bitmapScale < minBitmapScale ? minBitmapScale : bitmapScale;
                                        if(DEBUG) {
                                            Log.d(TAG, "action_move translateX = " + translateX + "; translateY = " + translateY + "; pointerCount = " + event.getPointerCount());
                                        }
                                        invalidate();
                                    }
                                }
                                break;
      
                            case MotionEvent.ACTION_CANCEL:
                            case MotionEvent.ACTION_UP:
                                if(DEBUG) {
                                    Log.d(TAG, "action = " + action + "; pointerCount = " + event.getPointerCount());
                                }
                                if (isDragging) {
                                    //最后一个手指离开屏幕时,检查y轴方向拖拽距离是否超过阀值,若超过则回调dismiss接口
                                    if (Math.abs(translateY) >= maxTranslateY && dismissListener != null) {
                                        dismissListener.onDismiss();
                                    }else{
                                        //若为超过阀值,则指定动画回到初始位置。
                                        restoreFirstEnterState();
                                    }
                                    isDragging = false;
                                }
                                break;
                            case MotionEvent.ACTION_POINTER_UP:
                            case MotionEvent.ACTION_POINTER_DOWN:
                                if(DEBUG){
                                    Log.d(TAG, "action pointer down or up= " + action + "; pointerCount = " + event.getPointerCount());
                                }
                                //防止拖拽过程中多点触摸导致事件错乱。
                                canDrag = isDragging;
                                break;
      
                            default:
      
                                break;
                        }
                    }
                    return isDragging;
                }
            });
            
            @Override
        protected void onDraw(Canvas canvas) {
      
                //肯定touch move事件,不断更新以下几个变量的值来达到动画效果。
                createPaint();
                bgPaint.setAlpha(bgAlpha);
                canvas.drawRect(0, 0, getWidth(), getHeight(), bgPaint);
                canvas.translate(translateX, translateY);
                canvas.scale(bitmapScale, bitmapScale, getWidth() / 2, getHeight() / 2);
                super.onDraw(canvas);
        }
        
      
    • 点击位置(进入和退出动画)

      // TODO 非刚需,暂未实现。

    兼容处理(18.03.03更新)

    • Image failed to decode using JPEG decoder

      由于使用了SubsamplingScaleImageView库显示大图,内部使用的是系统BitmapRegionDecoder解码,其只支持JPG和PNG格式,而且在不同的设备和版本上表现有差异,一句话:不稳定,还出问题。

      我的解决思路:监听SubsamplingScaleImageView解码错误回调,转为使用PhotoView显示图片。

    源码地址

    以上提及代码均在AndroidUiKit项目(安卓常用UI组件库。 总结、沉淀、封装优化;为避免重复造轮子,此项目会收集优秀的三方库,或直接引用,或修改源码;目标很明确:快速集成开发,提高效率。)

    代码位置:uikit module中photoviewer包下

    App图片处理库推荐

    • 图片加载神器Glide
    • Multi-media selector(本地图片视频选择器) AndroidUiKit项目中有推荐。

    本文为原创内容,转载请说明出处,首发博客

    相关文章

      网友评论

        本文标题:安卓图片浏览(支持超大图,附源码)

        本文链接:https://www.haomeiwen.com/subject/xtlnvxtx.html