php怎么实现拼图功能,布局拼图功能实现

一、功能简述

625be3b0bffe

布局界面图.png

1、根据用户选择图片张数和指定画布比例,根据布局模板初始洞口数据和图片指定绘制;

2、可选中图片绘制边框,可切换图片,可置换洞口图片,可手动缩放图片;

3、拖拉选中状态的边框,用递归算法推算出与之联动的所有洞口,实时刷新洞口位置和图片填充内容;

4、拖拉未选中状态的边框,推算与之在同一水平或垂直线上的多有洞口,实时刷新洞口位置和图片填充内容;

5、可切换背景、添加标签、签名、文字、滤镜、美颜;

5、可切动效切换布局模板、实时调整内外边框和圆角;

6、实现布局图片保存和布局草稿功能。

二、布局模板

625be3b0bffe

布局设计图.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 mLayoutAreaList = new ArrayList<>();//所有矩形区域

private List mAllVerLineList = new ArrayList<>();//所有矩形的所有垂直方向的边

private List mAllHorLineList = new ArrayList<>();//所有矩形的所有水平方向的边

private List mLineList = new ArrayList<>();//所有矩形的所有边

private List mOneLineList = new ArrayList<>(); // 在同一水平或垂直线上的子线集

//根据当前move的线段的方向,去检索标准的坐标刻度,用来释放线段时做吸附适配

private ArrayList 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;

}

布局绘制

625be3b0bffe

圆角边框效果图.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;

}

边框拖动规则

625be3b0bffe

边框拖动规则示意图.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 {

private List saves = new ArrayList();

public void find(T o) {

// 递归终止条件

if (saves.contains(o)) {

return;

} else {

// 找到

saves.add(o);

}

List finds = contain(o);

if (finds != null) {

for (T t : finds) {

find(t);

}

}

}

public List contain(T o) {

List 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 lines = new ArrayList<>();

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

}

}

生成保存效果图

625be3b0bffe

带纹理背景、签名、标签、文字的保存图.jpg

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
滑动拼图验证功能是一种常见的网站人机验证方法,旨在防止恶意机器人或自动化程序对网站进行恶意攻击。下面是一个使用JavaScript实现滑动拼图验证码的完整示例: ``` <!DOCTYPE html> <html> <head> <title>滑动拼图验证</title> <style> /* 拼图滑块样式 */ #slider { position: relative; width: 400px; height: 200px; background-color: #f2f2f2; margin: 100px auto; } #slider .slider-btn { position: absolute; width: 50px; height: 50px; background-color: #3399ff; top: 75px; cursor: pointer; } </style> </head> <body> <div id="slider"> <div class="slider-btn"></div> </div> <script> document.onreadystatechange = function () { if (document.readyState == "complete") { var sliderBtn = document.querySelector('.slider-btn'); var slider = document.getElementById('slider'); var startX, moveX, endX; sliderBtn.onmousedown = function (e) { startX = e.clientX - sliderBtn.offsetLeft; document.onmousemove = function (e) { moveX = e.clientX - startX; // 控制滑块的移动范围 if (moveX >= 0 && moveX <= slider.offsetWidth - sliderBtn.offsetWidth) { sliderBtn.style.left = moveX + 'px'; } } document.onmouseup = function (e) { endX = e.clientX - startX; // 判断是否滑动到最右边 if (endX >= slider.offsetWidth - sliderBtn.offsetWidth) { alert('验证通过!'); // 在这里可以写验证通过后的逻辑代码 } document.onmousemove = null; document.onmouseup = null; } } } } </script> </body> </html> ``` 该示例中,通过CSS设置了拼图滑块的样式和初始位置。在JavaScript部分,监听鼠标按下、移动和松开事件,通过计算鼠标的位置和滑块的位置,实现滑块的拖动功能。并且设置当滑块拖动到最右边时,触发验证通过的逻辑代码。用户可以根据实际需求进行修改和优化。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值