安卓圖片瀏覽(支援超大圖,附原始碼)

Jiantao發表於2017-11-23

大圖瀏覽可以說是所有App必備功能,可見其重要性,所以有必要將其獨立,便於維護和複用。本文程式碼基於SubsamplingScaleImageView開源庫實現,增加單手拖拽返回,透明度變化等效果。

瀏覽效果

photoview001
photoview001

photoview002
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 非剛需,暫未實現。

原始碼地址

以上提及程式碼均在AndroidUiKit專案(安卓常用UI元件庫。 總結、沉澱、封裝優化;為避免重複造輪子,此專案會收集優秀的三方庫,或直接引用,或修改原始碼;目標很明確:快速整合開發,提高效率。)

程式碼位置:uikit module中photoviewer包下

App圖片處理庫推薦

  • 圖片載入神器Glide
  • Multi-media selector(本地圖片視訊選擇器) AndroidUiKit專案中有推薦。

本文屬原始碼內容,轉載請說明出處,首發部落格

相關文章