布局拼圖功能實現


一、功能簡述

布局界面圖.png

1、根據用戶選擇圖片張數和指定畫布比例,根據布局模板初始洞口數據和圖片指定繪制;
2、可選中圖片繪制邊框,可切換圖片,可置換洞口圖片,可手動縮放圖片;
3、拖拉選中狀態(tài)的邊框,用遞歸算法推算出與之聯動的所有洞口,實時刷新洞口位置和圖片填充內容;
4、拖拉未選中狀態(tài)的邊框,推算與之在同一水平或垂直線上的多有洞口,實時刷新洞口位置和圖片填充內容;
5、可切換背景、添加標簽、簽名、文字、濾鏡、美顏;
5、可切動效切換布局模板、實時調整內外邊框和圓角;
6、實現布局圖片保存和布局草稿功能。

二、布局模板

布局設計圖.png
字段名 字段類型 字段說明
PicNum 字符串 圖片張數
pic_w 字符串 素材的基準寬度
pic_h 字符串 素材的基準高度
point 字符串 每張圖片在模板中的位置
{
  "PicNum": "6",
  "pic_w":"2048",
  "pic_h":"2048",
  "point": {
        "6": [
            "0,0,1024,512",
            "0,512,1024,512",
            "0,1024,1024,1024",
            "1024,0,1024,1024",
            "1024,1024,1024,512",
            "1024,1536,1024,512"
        ]
    }
}

三、布局功能實現

構建矩形邊框模型

  • LayoutParameter統(tǒng)籌布局LayoutArea和LayoutLine,配合繪制和事件觸發(fā)。

    • 記錄布局外邊矩形,用于判斷LayoutLine是否為外線,外線不可拖動;
    • 記錄所有垂直方向和水平方向的LayoutLine集合,可用于篩選同一直線上的邊框,做未選中狀態(tài)的邊框拖動;
    • 記錄在同一水平和垂直方向上的LayoutLine集合,可用于篩選聯動矩形框,做選中狀態(tài)的邊框拖動。
    public class LayoutParameter {
        private LayoutArea mOuterArea;//外部矩形區(qū)域
        private List<LayoutArea> mLayoutAreaList = new ArrayList<>();//所有矩形區(qū)域
        private List<LayoutLine> mAllVerLineList = new ArrayList<>();//所有矩形的所有垂直方向的邊
        private List<LayoutLine> mAllHorLineList = new ArrayList<>();//所有矩形的所有水平方向的邊
        private List<LayoutLine> mLineList = new ArrayList<>();//所有矩形的所有邊
        private List<LayoutLine> mOneLineList = new ArrayList<>(); // 在同一水平或垂直線上的子線集
        //根據當前move的線段的方向,去檢索標準的坐標刻度,用來釋放線段時做吸附適配
        private ArrayList<Float> mAxisList = new ArrayList<>();
      }
    
  • LayoutArea矩形框模型

    • 記錄上下左右四條邊線LayoutLine和邊距Padding;
    public class LayoutArea {
        public LayoutLine mLineLeft;
        public LayoutLine mLineRight;
        public LayoutLine mLineTop;
        public LayoutLine mLineBottom;
    
        private float mPaddingLeft;
        private float mPaddingTop;
        private float mPaddingRight;
        private float mPaddingBottom;
    
        private float mRadianRatio;
        private Path mAreaPath = new Path();
        private RectF mAreaRect = new RectF();
        private PointF[] mHandleBarPoints = new PointF[2];
    }
    
  • LayoutLine邊框模型

    • 標記邊框的方向,水平、垂直;
    • 標記邊框屬于矩形框的那條邊,上、下、左、右;
    • 記錄邊框的首尾兩個點,用于篩選聯動邊框;
    • 記錄邊框觸發(fā)down、move時所在首尾點的坐標,根據坐標和邊框移動的offset得出新的首尾點坐標。
    public class LayoutLine {
        public enum Direction {
            HORIZONTAL, VERTICAL
        }
        public enum Towards {
            LEFT, TOP , RIGHT , BOTTOM
        }
        private LayoutLine.Towards mTowards = Towards.LEFT;
        private LayoutLine.Direction mDirection = LayoutLine.Direction.HORIZONTAL;
    
        private PointF mStartPoint;
        private PointF mEndPoint;
        private PointF mPreviousStart = new PointF();// 記錄line前一次觸發(fā)所在位置
        private PointF mPreviousEnd = new PointF();
      }
    

構建圖片縮放平移模型

  • BasePiece 圖片模型基類,用于處理圖片拖拉跩、置換等操作,布局模板切換和邊框調整的繪制;

  • LayoutPiece 繼承 BasePiece,操作LayoutArea;

  • LayoutJointPiece 繼承 BasePiece,操作RectF。

屬性 字段類型 屬性說明
mDrawable Drawable 用戶選圖
mDrawableBounds Rect 圖片自身矩形,mMatrix.mapRect()獲取圖片最終矩形
mMatrix Matrix 用于記錄模板初始指定位置和用戶縮放平移置換過的矩陣
mPreviousMatrix Matrix 事件觸發(fā)時記錄mMatrix,實現平滑的縮放平移效果
mAnimator ValueAnimator 用于做圖片的平移縮放動畫
public class BasePiece {
  private Matrix mMatrix;
  private Drawable mDrawable;
  private Rect mDrawableBounds;
  private ValueAnimator mAnimator;
  private Matrix mPreviousMatrix;
}
public class LayoutPiece extends BasePiece {
  private LayoutArea mArea;
  private float mScale = 1f; // 置換圖片時,被置換洞口的圖片需縮小繪制
}
public class LayoutJointPiece extends BasePiece {
    private RectF mRectF;
  }

布局繪制

圓角邊框效果圖.png
  • BasePiece.draw()

public void draw(Canvas canvas, RectF rectF, float radian) {
    Bitmap bitmap = ((BitmapDrawable) mDrawable).getBitmap();
    Paint paint = ((BitmapDrawable) mDrawable).getPaint();
    if (bitmap == null) {
        return;
    }

    if (radian > 0) {
        int saved = canvas.saveLayer(rectF, null, Canvas.ALL_SAVE_FLAG);
        paint.setColor(Color.WHITE);
        paint.setAlpha(255);
        canvas.drawRoundRect(rectF, radian, radian, paint);
        paint.setXfermode(SRC_IN);
        canvas.drawBitmap(bitmap, mMatrix, paint);
        paint.setXfermode(null);
        canvas.restoreToCount(saved);
    } else {
        canvas.save();
        paint.setAntiAlias(true);
        canvas.setDrawFilter(new PaintFlagsDrawFilter(0,Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG));
        if (isShowFrame){
            canvas.clipRect(getFrameRect(rectF));
        }else{
            canvas.clipRect(rectF);
        }
        paint.reset();
        paint.setAntiAlias(true); // 防止邊緣的鋸齒
        paint.setFilterBitmap(true); // 對位圖進行濾波處理
        canvas.drawBitmap(bitmap, mMatrix, paint);
        canvas.restore();
    }
  • LayoutPiece.draw()

public void draw(Canvas canvas) {
    draw(canvas, getArea().getAreaRect(mScale), getArea().getRadian());
}
public RectF getAreaRect(float scale) {
      float widthOff = (width() - width() * scale) / 2;
      float heightOff = (height() - height() * scale) / 2;
      mAreaRect.set(left() + widthOff, top() + heightOff, right() - widthOff, bottom() - heightOff);
      return mAreaRect;
  }
public float width() {
    return right() - left();
}
public float right() {
    return mLineRight.getStartPoint().x - mPaddingRight;
}
  • 繪制選中區(qū)域的邊框

private void drawSelectedArea(Canvas canvas, LayoutPiece piece) {
    final LayoutArea area = piece.getArea();
    RectF rectF = area.getAreaRect();
    float offset;
    if (SELECTED_LINE_SIZE % 2 == 0) {
        offset = SELECTED_LINE_SIZE / 2.0f - 1;
    } else {
        offset = (SELECTED_LINE_SIZE - 1.0f) / 2.0f - 1;
    }
    rectF = new RectF(rectF.left + offset, rectF.top + offset,
            rectF.right - offset, rectF.bottom - offset);
    canvas.drawRoundRect(rectF, area.getRadian(), area.getRadian(), mSelectedAreaPaint);

    // draw handle bar
    for (LayoutLine line : area.getLines()) {
        if (mLayoutParameter.getLineList().contains(line)) {
            PointF[] handleBarPoints = area.getHandleBarPoints(line, offset);
            canvas.drawLine(handleBarPoints[0].x, handleBarPoints[0].y, handleBarPoints[1].x,
                    handleBarPoints[1].y, mHandleBarPaint);
            canvas.drawCircle(handleBarPoints[0].x, handleBarPoints[0].y, HANDLEBAR_LINE_SIZE / 2,
                    mHandleBarPaint);
            canvas.drawCircle(handleBarPoints[1].x, handleBarPoints[1].y, HANDLEBAR_LINE_SIZE / 2,
                    mHandleBarPaint);
        }
    }
}

事件觸發(fā)

方法 方法實現說明
decideActionMode(event) 決定當前event會觸發(fā)哪種事件,none、drag、zoom、moveline、swap
prepareAction(event) 執(zhí)行Action前的準備工作
performAction(event) 執(zhí)行Action
finishAction(event) 執(zhí)行完Action后的收尾工作
calculateDistance(event) 計算事件的兩個手指之間的距離
calculateMidPoint(event, mMidPoint) 計算事件的兩個手指的中心坐標
private final int LINE_SENSITIVITY_SIZE = Utils.getRealPixel3(30); // LayoutLine的靈敏度
private enum ActionMode {
    NONE, DRAG, ZOOM, MOVE, SWAP;
}

@Override
public boolean onTouchEvent(MotionEvent event) {
    if (!isTouchEnable) {
        return false;
    }

    switch (event.getAction() & MotionEvent.ACTION_MASK) {
        case MotionEvent.ACTION_DOWN:
            mDownX = event.getX();
            mDownY = event.getY();
            mLastX = event.getX();
            mLastY = event.getY();
            decideActionMode(event);
            prepareAction(event);
            break;

        case MotionEvent.ACTION_POINTER_DOWN:
            mPreviousDistance = calculateDistance(event);
            calculateMidPoint(event, mMidPoint);
            decideActionMode(event);
            break;

        case MotionEvent.ACTION_MOVE:
            performAction(event);
            break;

        case MotionEvent.ACTION_CANCEL:
        case MotionEvent.ACTION_UP:
            boolean result = finishAction(event);
            mCurrentMode = ActionMode.NONE;
            invalidate();
            if (result){
                return true;
            }
            break;
    }

    if (mCurrentMode != ActionMode.NONE) {
        invalidate();
        return true;
    }
    return false;
}    
private void decideActionMode(MotionEvent event) {
    for (LayoutPiece piece : mPiecesList) {
        if (piece.isAnimateRunning()) {
            mCurrentMode = ActionMode.NONE;
            return;
        }
    }
    if (event.getPointerCount() == 1) {
        //落點在區(qū)域外
        if (mDownX < mLayoutParameter.getOuterArea().left()
                || mDownX > mLayoutParameter.getOuterArea().right()
                || mDownY < mLayoutParameter.getOuterArea().top()
                || mDownY > mLayoutParameter.getOuterArea().bottom()) {
            return;
        }

        mHandlingLine = findHandlingLine();
        if (mHandlingLine != null) {
            //當前觸發(fā)的是line的移動
            mCurrentMode = ActionMode.MOVE;
            mHandlingLineOfPiece = findHandlingPiece();
        } else {
            //當前觸發(fā)的是piece的移動
            mHandlingPiece = findHandlingPiece();

            if (mHandlingPiece != null) {
                mCurrentMode = ActionMode.DRAG;
                //計時器觸發(fā)長摁事件
                //mHandler.postDelayed(mSwitchToSwapAction, 500);
            }
        }
    } else if (event.getPointerCount() > 1) {
        //兩個手指,觸發(fā)縮放的條件
        if (mHandlingPiece != null
                && mHandlingPiece.contains(event.getX(1), event.getY(1))
                && mCurrentMode == ActionMode.DRAG) {
            mCurrentMode = ActionMode.ZOOM;
        }
    }
}
private void prepareAction(MotionEvent event) {
    switch (mCurrentMode) {
        case NONE:
            break;
        case DRAG:
            mHandlingPiece.record(); // 記錄mPreviousMatrix
            mLastReplaceIndex = -1;
            break;
        case ZOOM:
            mHandlingPiece.record();
            break;
        case MOVE:
            mNeedChangedLines.addAll(findNeedChangedLines(mHandlingLine, mHandlingLineTwo));
            mNeedChangePieces.clear();
            for (int i = 0; i < mNeedChangedLines.size(); i++) {
                mNeedChangedLines.get(i).prepareMove(); // 記錄mPreviousMatrix
                mNeedChangePieces.add(mPiecesHashMap.get(mNeedChangedLines.get(i).getParentId()));
            }
            break;
    }
}
private void performAction(MotionEvent event) {
    switch (mCurrentMode) {
        case NONE:
            break;
        case DRAG:
            dragPiece(mHandlingPiece, event);
            mReplacePiece = findReplacePiece(event);
            //圖片縮小
            if (mReplacePiece != null) {
                if (mLastReplaceIndex >= 0) {
                    mPiecesList.get(mLastReplaceIndex).totalZoom(1f);
                }
                mReplacePiece.totalZoom(0.9f);
                mLastReplaceIndex = mPiecesList.indexOf(mReplacePiece);
            } else {
                if (mLastReplaceIndex >= 0) {
                    mPiecesList.get(mLastReplaceIndex).totalZoom(1f);
                    mLastReplaceIndex = -1;
                }
            }

            break;
        case ZOOM:
            zoomPiece(mHandlingPiece, event);
            break;
        case SWAP:
            break;
        case MOVE:
            moveLine(mHandlingLine, event);
            EventBus.getDefault().post(new PuzzlesRequestMsg(PuzzlesRequestMsgName
                    .PUZZLES_LAYOUT_SHOW_BAR, event.getAction(), null));
            break;
    }
}
private boolean finishAction(MotionEvent event) {
  boolean isChangeFilterBarIndex = false;
  switch (mCurrentMode) {
      case NONE:
          break;
      case DRAG:
          if (mHandlingPiece != null && !mHandlingPiece.isFilledArea()) {
              mHandlingPiece.moveToFillArea(mCallback);
          }

          boolean isClick = false;
          if (Math.abs(mDownX - event.getX()) < CLICK_RESP_SIZE
                  && Math.abs(mDownY - event.getY()) < CLICK_RESP_SIZE) {
              isClick = true;
          }
          if (mPreviousHandlingPiece == mHandlingPiece && isClick) {
              mHandlingPiece = null;
          }

          mPreviousHandlingPiece = mHandlingPiece;
          if (mHandlingPiece != null && mReplacePiece != null) {
              swapPiece(mHandlingPiece, mReplacePiece);
              mHandlingPiece.swapFillArea(mCallback, true);
              mReplacePiece.swapFillArea(mCallback, true);

              mReplacePiece.totalZoom(1f);
              mLastReplaceIndex = -1;
              mReplacePiece = null;
          }
          if (isClick) {
              EventBus.getDefault().post(new PuzzlesRequestMsg(PuzzlesRequestMsgName
                      .PUZZLES_LAYOUT_SHOW_BAR, event.getAction(), mHandlingPiece));
          } else {
              EventBus.getDefault().post(new PuzzlesRequestMsg(PuzzlesRequestMsgName
                      .PUZZLES_LAYOUT_SHOW_BAR, event.getAction(), null));
          }
          break;
      case ZOOM:
          if (mHandlingPiece != null && !mHandlingPiece.isFilledArea()) {
              if (mHandlingPiece.canFilledArea()) {
                  mHandlingPiece.moveToFillArea(mCallback);
              } else {
                  mHandlingPiece.fillArea(mCallback, false);
              }
          }
          mPreviousHandlingPiece = mHandlingPiece;
          break;
      case MOVE:
          if (mHandlingLineOfPiece != null && Math.abs(mDownX - event.getX()) < 3
                  && Math.abs(mDownY - event.getY()) < 3) {
              mHandlingPiece = mHandlingLineOfPiece;
              if (mHandlingPiece == mPreviousHandlingPiece) {
                  EventBus.getDefault().post(new PuzzlesRequestMsg(PuzzlesRequestMsgName
                          .PUZZLES_LAYOUT_SHOW_BAR, event.getAction(), null));
                  mHandlingPiece = null;
                  mPreviousHandlingPiece = null;
              } else {
                  EventBus.getDefault().post(new PuzzlesRequestMsg(PuzzlesRequestMsgName
                          .PUZZLES_LAYOUT_SHOW_BAR, event.getAction(), mHandlingPiece));
                  mPreviousHandlingPiece = mHandlingPiece;
              }
          } else {
              mHandlingLineOfPiece = null;
          }
          if (mHandlingLine != null) {
              releaseLine(mHandlingLine, event);
          }
          break;
      case SWAP:
          break;
  }

  mHandlingLine = null;
  mNeedChangedLines.clear();
  return false;
}    

邊框拖動規(guī)則

邊框拖動規(guī)則示意圖.png
  • 若未選中圖4,拖動其左邊框,圖1、2、3、4、5、6都將跟著水平移動;

  • 若選中圖4,拖動其左邊框,只有圖1、2、4跟著水平移動;

  • 邊框拖動過程中,根據圖片比例和邊框比例,判斷圖片內容處于平移還是縮放過程。

方法 方法實現說明
Find< LayoutLine > 核心代碼塊,運用遞歸算法求出與之聯動邊框LayoutLine
findHandlingLine() 查找 down 坐標 所把持的邊框對象
findHandlingPiece() 查找 mHandlingLine 所附屬的 LayoutPiece
findNeedChangedLines() 根據 mHandlingLine,查找所有隨之移動的LayoutLine
public class Find<T> {
        private List<T> saves = new ArrayList<T>();

        public void find(T o) {
            // 遞歸終止條件
            if (saves.contains(o)) {
                return;
            } else {
                // 找到
                saves.add(o);
            }

            List<T> finds = contain(o);
            if (finds != null) {
                for (T t : finds) {
                    find(t);
                }
            }
        }

        public List<T> contain(T o) {
            List<T> finds = new ArrayList<>();

            LayoutLine mLine = (LayoutLine) o;
            float mLength = mLine.length();
            for (int i = 0; i < mLayoutParameter.getOneLineList().size(); i++) {
                LayoutLine line = mLayoutParameter.getOneLineList().get(i);
                if (line == mLine || saves.contains(line)) {
                    // 排除自身和已經包含在saves里的線段,減小循環(huán)量
                    continue;
                }
                if (line.getStartPoint().x == mLine.getStartPoint().x
                        && line.getStartPoint().y == mLine.getStartPoint().y
                        && line.getEndPoint().x == mLine.getEndPoint().x
                        && line.getEndPoint().y == mLine.getEndPoint().y) {
                    // 完全重合的兩條線段,add
                    finds.add((T) line);
                    continue;
                }
                LayoutLine line1; // 短線段
                LayoutLine line2; // 長線段
                if (mLength >= line.length()) {
                    line1 = line;
                    line2 = mLine;
                } else {
                    line1 = mLine;
                    line2 = line;
                }

                if (mLine.getDirection() == LayoutLine.Direction.HORIZONTAL) {
                    // x不同
                    if (isOnLine(line1.getStartPoint().x, line1.getEndPoint().x, line2.getStartPoint().x, line2.getEndPoint().x)) {
                        finds.add((T) line);
                    }
                } else {
                    // y不同
                    if (isOnLine(line1.getStartPoint().y, line1.getEndPoint().y, line2.getStartPoint().y, line2.getEndPoint().y)) {
                        finds.add((T) line);
                    }
                }
            }
            return finds;
        }

        private boolean isOnLine(float shortStart, float shortEnd, float longStart, float longEnd) {
            if (shortStart > longStart && shortStart < longEnd
                    || shortEnd > longStart && shortEnd < longEnd) {
                // 若短線段起點或者末點落在長線段上,則表示兩個線段有交集,會相互牽制引發(fā)聯動
                return true;
            } else {
                return false;
            }
        }
    }
private LayoutLine findHandlingLine() {
    ArrayList<LayoutLine> lines = new ArrayList<>();
    ArrayList<LayoutLine> mHandlingLineList = new ArrayList<>();
    for (LayoutLine line : mLayoutParameter.getLineList()) {
        if (line.contains(mDownX, mDownY, mPiecePaddingRatio * mLayoutParameter.getTotalPadding() + LINE_SENSITIVITY_SIZE)) {
            lines.add(line);
        }
    }

    if (lines.size() >= 2) {
        //當down坐標在公共區(qū)域時,需做區(qū)分處理
        //找到了四根線,兩根水平兩根垂直
        mHandlingLineList.add(lines.get(0));
        //保險一點,再做一次方向排查
        for (int i = 1; i < lines.size(); i++) {
            if (lines.get(i).getDirection().equals(lines.get(0).getDirection())) {
                mHandlingLineList.add(lines.get(i));
            }
        }
        for (int i = 1; i < lines.size(); i++) {
            if (!mHandlingLineList.contains(lines.get(i))) {
                mHandlingLineList.add(lines.get(i));
            }
        }
        mHandlingLineTwo = mHandlingLineList.get(1);
        return mHandlingLineList.get(0);
    } else {
        return null;
    }
}
private List<LayoutLine> findNeedChangedLines(LayoutLine handlingLine, LayoutLine handlingLineTwo) {
    if (handlingLine == null) return new ArrayList<>();

    mLayoutParameter.generateOneLineList(handlingLine); // 先查找在同一水平或垂直線上的Line集合

    if (mHandlingPiece != null && (mHandlingPiece.contains(handlingLine) || mHandlingPiece.contains(handlingLineTwo))) {
        //有選中子Item,則只改變影響到的矩形框
        Find<LayoutLine> mClass = new Find<>();
        mClass.find(handlingLine);
        return mClass.saves;
    } else {
        //無選中Item,則改變整條直線上的矩形框
        return mLayoutParameter.getOneLineList();
    }
}

圖片縮放平移規(guī)則

方法 方法實現說明
dragPiece() 拖拽 LayoutPiece
zoomPiece() 縮放的同時還可以平移 LayoutPiece
moveLine() 移動邊框,既要重算 Area,又要根據矩形比例和圖片比例平移縮放 LayoutPiece
private void dragPiece(LayoutPiece piece, MotionEvent event) {
    if (piece == null || event == null) return;
    piece.translate(event.getX() - mDownX, event.getY() - mDownY);
}

public void translate(float offsetX, float offsetY) {
    mMatrix.set(mPreviousMatrix);
    postTranslate(offsetX, offsetY);
}

public void postTranslate(float x, float y) {
    this.mMatrix.postTranslate(x, y);
}
private void zoomPiece(LayoutPiece piece, MotionEvent event) {
    if (piece == null || event == null || event.getPointerCount() < 2) return;
    float scale = calculateDistance(event) / mPreviousDistance;
    piece.zoomAndTranslate(scale, scale, mMidPoint, event.getX() - mDownX, event.getY() - mDownY);
}

public void zoomAndTranslate(RectF rectF, float scaleX, float scaleY, PointF midPoint, float offsetX, float offsetY) {
    //兩指觸發(fā)縮放
    mMatrix.set(mPreviousMatrix);

    float scale = getMatrixScale() * scaleX;
    float minMatrixScale = MatrixUtils.getMinMatrixScale(this, rectF);
    if (scale > mMaxScale * minMatrixScale) {
        scaleX = (mMaxScale * minMatrixScale) / getMatrixScale();
        scaleY = scaleX;
    }

    postScale(scaleX, scaleY, midPoint);
    postTranslate(offsetX * 1.0f / 2, offsetY * 1.0f / 2);
    isZoom = true;
}
private void moveLine(LayoutLine line, MotionEvent event) {
  if (line == null || event == null) return;
  float offset;
  if (line.getDirection() == LayoutLine.Direction.HORIZONTAL) {
      offset = event.getY() - mLastY;
  } else {
      offset = event.getX() - mLastX;
  }
  mLastX = event.getX();
  mLastY = event.getY();
  for (int i = 0; i < mNeedChangedLines.size(); i++) {
      LayoutArea area = mLayoutParameter.getAreaHashMap().get(mNeedChangedLines.get(i).getParentId());
      if (line.getDirection() == LayoutLine.Direction.HORIZONTAL) {
          if (mNeedChangedLines.get(i).getTowards() == LayoutLine.Towards.TOP && area.getOriginalRect().height() - offset < mMinItemHeight) {
              offset = area.getOriginalRect().height() - mMinItemHeight;
          }
          if (mNeedChangedLines.get(i).getTowards() == LayoutLine.Towards.BOTTOM && area.getOriginalRect().height() + offset < mMinItemHeight) {
              offset = mMinItemHeight - area.getOriginalRect().height();
          }
      } else {
          if (mNeedChangedLines.get(i).getTowards() == LayoutLine.Towards.LEFT && area.getOriginalRect().width() - offset < mMinItemHeight) {
              offset = area.getOriginalRect().width() - mMinItemHeight;
          }
          if (mNeedChangedLines.get(i).getTowards() == LayoutLine.Towards.RIGHT && area.getOriginalRect().width() + offset < mMinItemHeight) {
              offset = mMinItemHeight - area.getOriginalRect().width();
          }
      }
  }

  if (offset == 0) {
      return;
  }
  if (mNeedChangedLines != null && mNeedChangePieces != null) {
      for (int i = 0; i < mNeedChangedLines.size(); i++) {
          mNeedChangedLines.get(i).move(offset);
          if (mNeedChangePieces.get(i) != null) {
              mNeedChangePieces.get(i).updateWithLine(mNeedChangedLines.get(i), offset);
          }
      }
  }
}    

public void updateWithLine(final LayoutLine line, float offset) {
  LayoutArea mArea = getArea();
  if (isZoom()) {
      if (!canFilledArea(getArea().getAreaRect())) {
          fillRect(getArea().getAreaRect());
      } else {
          if (line.getDirection() == LayoutLine.Direction.HORIZONTAL) {
              float nowAreaHeight = mArea.height();
              float lastAreaHeight = 0;
              float nowAreaScale = mArea.width() / mArea.height();
              float pieceScale = getWidth() / getHeight();
              if (nowAreaScale < pieceScale) {
                  PointF lastCenter = new PointF(mArea.getCenterPoint().x, mArea.getCenterPoint().y - offset * 1.0f / 2);
                  if (line.getTowards() == LayoutLine.Towards.TOP) {
                      lastAreaHeight = mArea.height() + offset;
                      float scale = nowAreaHeight / lastAreaHeight;

                      postScale(scale, scale, lastCenter);
                  }
                  if (line.getTowards() == LayoutLine.Towards.BOTTOM) {
                      lastAreaHeight = mArea.height() - offset;
                      float scale = nowAreaHeight / lastAreaHeight;
                      postScale(scale, scale, lastCenter);
                  }
              }
          } else {
              float nowAreaWidth = mArea.width();
              float lastAreaWidth = 0;
              float nowAreaScale = mArea.width() / mArea.height();
              float pieceScale = getWidth() / getHeight();
              if (nowAreaScale > pieceScale) {
                  PointF lastCenter = new PointF(mArea.getCenterPoint().x - offset * 1.0f / 2, mArea.getCenterPoint().y);
                  if (line.getTowards() == LayoutLine.Towards.LEFT) {
                      lastAreaWidth = mArea.width() + offset;
                      float scale = nowAreaWidth / lastAreaWidth;
                      postScale(scale, scale, lastCenter);
                  }
                  if (line.getTowards() == LayoutLine.Towards.RIGHT) {
                      lastAreaWidth = mArea.width() - offset;
                      float scale = nowAreaWidth / lastAreaWidth;
                      postScale(scale, scale, lastCenter);
                  }
              }

          }
      }
  } else {
      fillRect(getArea().getAreaRect());
  }
  if (getMatrixScale() >= MatrixUtils.getMinMatrixScale(this , getArea().getAreaRect())) {
      if (line.getDirection() == LayoutLine.Direction.HORIZONTAL) {
          postTranslate(0, offset * 1.0f / 2);
      } else if (line.getDirection() == LayoutLine.Direction.VERTICAL) {
          postTranslate(offset * 1.0f / 2, 0);
      }
  }
  RectF rectF = getCurrentDrawableBounds();
  mArea = getArea();
  float moveY = 0f;

  if (rectF.top > mArea.top()) {
      moveY = mArea.top() - rectF.top;
  }

  if (rectF.bottom < mArea.bottom()) {
      moveY = mArea.bottom() - rectF.bottom;
  }

  float moveX = 0f;

  if (rectF.left > mArea.left()) {
      moveX = mArea.left() - rectF.left;
  }

  if (rectF.right < mArea.right()) {
      moveX = mArea.right() - rectF.right;
  }

  if (moveX != 0 || moveY != 0) {
      postTranslate(moveX, moveY);
  }
}    

生成保存效果圖

帶紋理背景、簽名、標簽、文字的保存圖.jpg
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

  • 效果圖: Github鏈接:https://github.com/boycy815/PinchImageView ...
    CQ_TYL閱讀 2,351評論 0 0
  • 手勢圖片控件 PinchImageView 點擊圖片框架 photoView packagecom.example...
    Ztufu閱讀 804評論 0 1
  • ¥開啟¥ 【iAPP實現進入界面執(zhí)行逐一顯】 〖2017-08-25 15:22:14〗 《//首先開一個線程,因...
    小菜c閱讀 7,328評論 0 17
  • 8. Setting Colors Since release v1.4.0, the ColorTemplate...
    ngugg閱讀 833評論 0 0
  • 圖表控件庫 MPAndroidChart 的使用 使用方法 項目源碼地址,包含了很多類型的圖標 https://g...
    jinchuang閱讀 888評論 0 0

友情鏈接更多精彩內容