一、功能簡述

布局界面圖.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