美文网首页
布局拼图功能实现

布局拼图功能实现

作者: 溫順尚早 | 来源:发表于2019-02-28 19:41 被阅读0次


    一、功能简述

    布局界面图.png
    1、根据用户选择图片张数和指定画布比例,根据布局模板初始洞口数据和图片指定绘制;
    2、可选中图片绘制边框,可切换图片,可置换洞口图片,可手动缩放图片;
    3、拖拉选中状态的边框,用递归算法推算出与之联动的所有洞口,实时刷新洞口位置和图片填充内容;
    4、拖拉未选中状态的边框,推算与之在同一水平或垂直线上的多有洞口,实时刷新洞口位置和图片填充内容;
    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统筹布局LayoutArea和LayoutLine,配合绘制和事件触发。

      • 记录布局外边矩形,用于判断LayoutLine是否为外线,外线不可拖动;
      • 记录所有垂直方向和水平方向的LayoutLine集合,可用于筛选同一直线上的边框,做未选中状态的边框拖动;
      • 记录在同一水平和垂直方向上的LayoutLine集合,可用于筛选联动矩形框,做选中状态的边框拖动。
      public class LayoutParameter {
          private LayoutArea mOuterArea;//外部矩形区域
          private List<LayoutArea> mLayoutAreaList = new ArrayList<>();//所有矩形区域
          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边框模型

      • 标记边框的方向,水平、垂直;
      • 标记边框属于矩形框的那条边,上、下、左、右;
      • 记录边框的首尾两个点,用于筛选联动边框;
      • 记录边框触发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前一次触发所在位置
          private PointF mPreviousEnd = new PointF();
        }
      

    构建图片缩放平移模型

    • BasePiece 图片模型基类,用于处理图片拖拉跩、置换等操作,布局模板切换和边框调整的绘制;

    • LayoutPiece 继承 BasePiece,操作LayoutArea;

    • LayoutJointPiece 继承 BasePiece,操作RectF。

    属性 字段类型 属性说明
    mDrawable Drawable 用户选图
    mDrawableBounds Rect 图片自身矩形,mMatrix.mapRect()获取图片最终矩形
    mMatrix Matrix 用于记录模板初始指定位置和用户缩放平移置换过的矩阵
    mPreviousMatrix Matrix 事件触发时记录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;
    }
    
    • 绘制选中区域的边框

    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);
            }
        }
    }
    

    事件触发

    方法 方法实现说明
    decideActionMode(event) 决定当前event会触发哪种事件,none、drag、zoom、moveline、swap
    prepareAction(event) 执行Action前的准备工作
    performAction(event) 执行Action
    finishAction(event) 执行完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) {
            //落点在区域外
            if (mDownX < mLayoutParameter.getOuterArea().left()
                    || mDownX > mLayoutParameter.getOuterArea().right()
                    || mDownY < mLayoutParameter.getOuterArea().top()
                    || mDownY > mLayoutParameter.getOuterArea().bottom()) {
                return;
            }
    
            mHandlingLine = findHandlingLine();
            if (mHandlingLine != null) {
                //当前触发的是line的移动
                mCurrentMode = ActionMode.MOVE;
                mHandlingLineOfPiece = findHandlingPiece();
            } else {
                //当前触发的是piece的移动
                mHandlingPiece = findHandlingPiece();
    
                if (mHandlingPiece != null) {
                    mCurrentMode = ActionMode.DRAG;
                    //计时器触发长摁事件
                    //mHandler.postDelayed(mSwitchToSwapAction, 500);
                }
            }
        } else if (event.getPointerCount() > 1) {
            //两个手指,触发缩放的条件
            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;
    }    
    

    边框拖动规则

    边框拖动规则示意图.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里的线段,减小循环量
                        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) {
                    // 若短线段起点或者末点落在长线段上,则表示两个线段有交集,会相互牵制引发联动
                    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坐标在公共区域时,需做区分处理
            //找到了四根线,两根水平两根垂直
            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();
        }
    }
    

    图片缩放平移规则

    方法 方法实现说明
    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) {
        //两指触发缩放
        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

    相关文章

      网友评论

          本文标题:布局拼图功能实现

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