问题的实质如下:当图像放大时,可以左右左右上下移动,直至完全消失。问题是,如何在此处设置边界,以便缩放图像,但几乎没有或根本没有压痕地粘在屏幕的角落?或至少告诉我1)哪个代码段负责此工作? 2)告诉我代码的哪一部分负责放大图像(这里的门框也是,放大时,如果是以前的两个手指,则图像会移动到从第二个屏幕释放的手指,这也很愚蠢,并且在展开时会歪曲图片,因此总是会出现抽搐))。我将不胜感激,感谢您提供任何帮助,或者至少是指导性的阅读内容。我没有找到类似的情况。我不太了解,但是没有可能雇用一名程序员。谢谢!抱歉,我正在通过口译员(
public class DrawingView extends View {
private static final String TAG = DrawingView.class.getSimpleName();
private static final float MOVE_TOLERANCE = 2.0f;
private static final float TOUCH_TOLERANCE = 4.0f;
private static final int MODE_SINGLETOUCH = 1;
private static final int MODE_MULTITOUCH = 2;
private static final int MODE_NONE = 0;
public static final float MAX_SCALING = 3.0f;
public static final float MIN_SCALING = 1.0f;
public interface DrawingViewHandler {
RectF getCropingRect();
void setCropingRectVisibility(boolean visibility);
}
public interface ScaleValueResolver {
float getValue();
}
public interface ScalingObserver {
boolean processScaleChanged(ScalingEvent event);
void processMoveEvent(MoveEvent event);
void onClearScale();
}
public static class MoveEvent {
private float dx;
private float dy;
public MoveEvent(float dx, float dy) {
this.dx = dx;
this.dy = dy;
}
public float getDx() {
return dx;
}
public float getDy() {
return dy;
}
}
public static class ScalingEvent {
private float _x;
private float _y;
private float _factor;
public ScalingEvent(float x, float y, float factor) {
this._x = x;
this._y = y;
this._factor = factor;
}
public float getX() {
return _x;
}
public ScalingEvent setX(float _x) {
this._x = _x;
return this;
}
public float getY() {
return _y;
}
public ScalingEvent setY(float _y) {
this._y = _y;
return this;
}
public float getFactor() {
return _factor;
}
public ScalingEvent setFactor(float _factor) {
this._factor = _factor;
return this;
}
}
private class DrawingViewScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener {
@Override
public boolean onScale(ScaleGestureDetector detector) {
float scaleFactor = getScaleFactor() * detector.getScaleFactor();
if (_lastScaleX < 0) {
_lastScaleX = detector.getFocusX();
}
if (_lastScaleY < 0) {
_lastScaleY = detector.getFocusY();
}
boolean observersScaled = false;
if(Math.abs(1 - detector.getScaleFactor()) > 0.01){
if (detector.getScaleFactor() < 1 && (scaleFactor - getMinScaleFactor()) < ImageUtils.SCALE_LIMIT) {
resetScaling();
observersScaled = true;
} else {
if(scaleFactor > MAX_SCALING){
scaleFactor = MAX_SCALING;
}
if(_scalingObserver != null){
ScalingEvent scaleEvent = new ScalingEvent(
_lastScaleX,
_lastScaleY,
detector.getScaleFactor());
if(BuildConfig.DEBUG) {
Log.d(TAG, String.format("posting scale = %f at (%f, %f); current drawingVIew scale = %f",
detector.getScaleFactor(), detector.getFocusX(), detector.getFocusY(), getScaleFactor()));
}
observersScaled = _scalingObserver.processScaleChanged(scaleEvent);
}
if (observersScaled) {
if(BuildConfig.DEBUG) {
Log.d(TAG, "observers scaled, updating drawing view scale to " + scaleFactor);
}
setScaleFactor(scaleFactor);
invalidate();
}
}
}
if(BuildConfig.DEBUG) {
Log.d(TAG, String.format("scaleFactor = %f (delta = %f); lastScaleX = %f, lastScaleY = %f; isScaled = %s",
scaleFactor, detector.getScaleFactor(), _lastScaleX, _lastScaleY, String.valueOf(observersScaled)));
}
return observersScaled;
}
}
private static class RecalculateCropingAreaTaskArgs {
private int bitmapHeight;
private int bitmapWidth;
private RectF cropingRect;
public int getBitmapHeight() {
return bitmapHeight;
}
public RecalculateCropingAreaTaskArgs setBitmapHeight(int bitmapHeight) {
this.bitmapHeight = bitmapHeight;
return this;
}
public int getBitmapWidth() {
return bitmapWidth;
}
public RecalculateCropingAreaTaskArgs setBitmapWidth(int bitmapWidth) {
this.bitmapWidth = bitmapWidth;
return this;
}
public RectF getCropingRect() {
return cropingRect;
}
public RecalculateCropingAreaTaskArgs setCropingRect(RectF cropingRect) {
this.cropingRect = cropingRect;
return this;
}
}
private class RecalculateCropingAreaTask extends AsyncTask<RecalculateCropingAreaTaskArgs, Void, float[]> {
private static final int CALC_STEP = 2;
@Override
protected float[] doInBackground(RecalculateCropingAreaTaskArgs... args) {
float[] coords = new float[4];
long start = System.currentTimeMillis();
doCalculation(args[0], coords);
long finished = System.currentTimeMillis();
Log.d(TAG, "recalculateLineCropingArea() finished in " + (finished - start) + " ms");
return coords;
}
private void doCalculation(RecalculateCropingAreaTaskArgs arg, float[] result) {
int height = arg.getBitmapHeight();
int width = arg.getBitmapWidth();
int pixelsCount = 0;
RectF croppingRect = arg.getCropingRect();
for (int x = 0; x < width && !isCancelled(); x += CALC_STEP) {
for (int y = 0; y < height && !isCancelled(); y += CALC_STEP) {
boolean isInCrop = croppingRect != null && croppingRect.contains(x, y);
if (!isInCrop) {
continue;
}
// todo: избавиться от обращения к bitmap
try {
if (_bitmap.getPixel(x, y) == 0) {
pixelsCount = 0;
continue;
}
pixelsCount += 1;
if (pixelsCount > 10) {
updateLineCropingArea(x, y, result);
}
} catch (Exception e){
e.printStackTrace();
}
}
}
}
private void updateLineCropingArea(float x, float y, float[] result) {
if (x < result[0] || result[0] == 0) {
result[0] = x;
} else if (x > result[2]) {
result[2] = x;
}
if (y < result[1] || result[1] == 0) {
result[1] = y;
} else if (y > result[3]) {
result[3] = y;
}
}
@Override
protected void onPostExecute(float[] result) {
_pendingRecalculateCropAreaTask = null;
if(!isCancelled()){
_minX = result[0];
_minY = result[1];
_maxX = result[2];
_maxY = result[3];
}
}
}
private RecalculateCropingAreaTask _pendingRecalculateCropAreaTask = null;
private Matrix _currentCanvasMatrix = null;
private float[] _currentCanvasMatrixValues = null;
private Paint _paint;
private Bitmap _bitmap;
private Canvas _canvas;
private Path _path;
private Paint _bitmapPaint;
private Paint _eraserPaint;
private DrawingViewHandler _drawingViewHandler;
private ScaleValueResolver _minScaleResolver;
private ScaleValueResolver _maxScaleResolver;
private final PathMeasure _pathMeasure;
private ScaleGestureDetector _scaleGestureDetector;
private ScalingObserver _scalingObserver = null;
private float _maxX = 0;
private float _maxY = 0;
private float _minX = 0;
private float _minY = 0;
private float _mX;
private float _mY;
private float _lastPathDistance = 0.0f;
private float _scaleFactor = 0;
private float _lastScaleX = -1;
private float _lastScaleY = -1;
private CoordsPoint _lastMovePoint = null;
private CoordsPoint _translate = new CoordsPoint();
private int _touchMode = MODE_NONE;
public DrawingView(Context context) {
super(context);
_path = new Path();
_pathMeasure = new PathMeasure();
_bitmapPaint = new Paint();
_bitmapPaint.setColor(Color.RED);
_eraserPaint = new Paint();
_eraserPaint.setColor(Color.TRANSPARENT);
_eraserPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
_eraserPaint.setAntiAlias(true);
_scaleGestureDetector = new ScaleGestureDetector(context, new DrawingViewScaleListener());
}
public void setHandler(DrawingViewHandler handler) {
_drawingViewHandler = handler;
}
public void setMinScaleResolver(ScaleValueResolver resolver) {
_minScaleResolver = resolver;
}
public void setMaxScaleResolver(ScaleValueResolver resolver) {
_maxScaleResolver = resolver;
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
if(w != oldw && h != oldh){
cancelPendingRecalculation();
if (_bitmap != null) {
_bitmap.recycle();
}
_bitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
_canvas = new Canvas(_bitmap);
_maxX = 0;
_maxY = 0;
_minX = 0;
_minY = 0;
_translate = new CoordsPoint();
}
}
private void checkPendingRecalculationFinished() {
if (_pendingRecalculateCropAreaTask != null) {
try {
_pendingRecalculateCropAreaTask.get();
} catch (Exception e) {
Log.w(TAG, "_pendingRecalculateCropAreaTask.get() failed!", e);
}
}
}
private void cancelPendingRecalculation() {
if (_pendingRecalculateCropAreaTask != null) {
_pendingRecalculateCropAreaTask.cancel(true);
}
}
@Override
public void draw(Canvas canvas) {
super.draw(canvas);
float scaleFactor = getScaleFactor();
if (_lastScaleX >= 0) {
canvas.scale(scaleFactor, scaleFactor, _lastScaleX, _lastScaleY);
}
canvas.translate(_translate.getX(), _translate.getY());
canvas.drawBitmap(_bitmap, 0, 0, _bitmapPaint);
canvas.drawPath(_path, getPaint());
_currentCanvasMatrix = canvas.getMatrix();
}
private Paint getPaint() {
int cropLineColor = AppPreferences.getInstance().getCropLineColor();
if (_paint == null || _paint.getColor() != cropLineColor) {
_paint = new Paint();
_paint.setAntiAlias(true);
_paint.setDither(true);
_paint.setColor(cropLineColor);
_paint.setStyle(Paint.Style.STROKE);
_paint.setStrokeJoin(Paint.Join.ROUND);
_paint.setStrokeCap(Paint.Cap.ROUND);
_paint.setStrokeWidth(AppPreferences.getInstance().getLineThickness() / getScaleFactor());
}
return _paint;
}
private void handleTouchStart(float x, float y) {
_mX = x;
_mY = y;
_lastPathDistance = 0.0f;
_path.moveTo(x, y);
if (isEraserMode()) {
eraseAtPoint((int) x, (int) y);
}
if(BuildConfig.DEBUG){
Log.d(TAG, "touch start @ x = " + _mX + "; y = " + _mY);
}
}
private void processMoveCanvasToPoint(float x, float y) {
if(_lastMovePoint == null){
_lastMovePoint = new CoordsPoint(x, y);
if(BuildConfig.DEBUG) {
Log.d(TAG, "move start @ x = " + _lastMovePoint.getX() + "; y = " + _lastMovePoint.getY());
}
} else {
final float sensitivity = 0.4f;
float dx = (x - _lastMovePoint.getX()) * sensitivity;
float dy = (y - _lastMovePoint.getY()) * sensitivity;
if (Math.abs(dx) >= MOVE_TOLERANCE || Math.abs(dy) >= MOVE_TOLERANCE){
_translate.update(dx, dy);
_lastMovePoint.setX(x);
_lastMovePoint.setY(y);
if(_scalingObserver != null){
_scalingObserver.processMoveEvent(new MoveEvent(dx * getScaleFactor(), dy * getScaleFactor()));
}
if(BuildConfig.DEBUG){
Log.d(TAG, "move canvas at dx = " + dx + "; dy = " + dy);
}
}
}
}
private void handleTouchMove(float x, float y) {
float dx = Math.abs(x - _mX);
float dy = Math.abs(y - _mY);
if (dx >= TOUCH_TOLERANCE || dy >= TOUCH_TOLERANCE) {
_path.quadTo(_mX, _mY, (x + _mX) / 2, (y + _mY) / 2);
_mX = x;
_mY = y;
if (isEraserMode()) {
_pathMeasure.setPath(_path, false);
float length = _pathMeasure.getLength();
float[] coords = {0.0f, 0.0f};
for (float distance = _lastPathDistance; distance < length; distance += 1) {
if (_pathMeasure.getPosTan(distance, coords, null)) {
eraseAtPoint((int) coords[0], (int) coords[1]);
_lastPathDistance = distance;
}
}
}
}
}
private void eraseAtPoint(int x, int y) {
_canvas.drawCircle(
x,
y,
AppPreferences.getInstance().getLineThickness() / (getScaleFactor() * 2),
_eraserPaint
);
}
private void handleTouchUp() {
_path.lineTo(_mX, _mY);
_canvas.drawPath(_path, getPaint());
_path.reset();
recalculateLineCropingArea();
if(BuildConfig.DEBUG){
Log.d(TAG, "touch up!");
}
}
private boolean isEraserMode() {
return getPaint().getColor() == Color.TRANSPARENT;
}
private boolean isSingleTouchMode() {
return _touchMode == MODE_SINGLETOUCH;
}
private boolean isMultiTouchMode() {
return _touchMode == MODE_MULTITOUCH;
}
private float getMinScaleFactor() {
if (_minScaleResolver != null) {
return _minScaleResolver.getValue();
}
return MIN_SCALING;
}
private float getMaxScaleFactor() {
if (_maxScaleResolver != null) {
return _maxScaleResolver.getValue();
}
return MAX_SCALING;
}
private float getScaleFactor() {
if (_scaleFactor == 0) {
setScaleFactor(MIN_SCALING);
if (_minScaleResolver != null) {
setScaleFactor(_minScaleResolver.getValue());
}
}
return _scaleFactor;
}
private void clearScaleFactor(){
setScaleFactor(getMinScaleFactor());
_lastScaleX = -1;
_lastScaleY = -1;
}
private void setScaleFactor(float val) {
_scaleFactor = val;
_paint = null;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
// touch coords
float x = event.getX();
float y = event.getY();
boolean isScaled = false;
// translate coordinates according to scaling
if (_currentCanvasMatrix != null) {
if (_currentCanvasMatrixValues == null) {
_currentCanvasMatrixValues = new float[9];
}
_currentCanvasMatrix.getValues(_currentCanvasMatrixValues);
if(!isMultiTouchMode()){
x = (_currentCanvasMatrixValues[Matrix.MTRANS_X] * -1 + x) / _currentCanvasMatrixValues[Matrix.MSCALE_X];
y = (_currentCanvasMatrixValues[Matrix.MTRANS_Y] * -1 + y) / _currentCanvasMatrixValues[Matrix.MSCALE_Y];
}
isScaled = _currentCanvasMatrixValues[Matrix.MSCALE_X] > (getMinScaleFactor() + 0.05);
if(_drawingViewHandler != null){
_drawingViewHandler.setCropingRectVisibility(!isScaled);
}
}
// check if touch was in croping area
boolean isInCrop = false;
if (_drawingViewHandler != null) {
RectF croppingRect = _drawingViewHandler.getCropingRect();
isInCrop = croppingRect.contains(x, y);
}
if(isInCrop){
_scaleGestureDetector.onTouchEvent(event);
}
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
_touchMode = MODE_SINGLETOUCH;
handleTouchStart(x, y);
invalidate();
break;
case MotionEvent.ACTION_POINTER_DOWN:
_touchMode = MODE_MULTITOUCH;
_path.reset();
_lastMovePoint = null;
break;
case MotionEvent.ACTION_MOVE:
if (isInCrop) {
if(!isSingleTouchMode()){
if(getScaleFactor() > getMinScaleFactor())
processMoveCanvasToPoint(x, y);
} else {
handleTouchMove(x, y);
}
invalidate();
}
break;
case MotionEvent.ACTION_UP:
if (isSingleTouchMode()) {
handleTouchUp();
}
invalidate();
_touchMode = MODE_NONE;
break;
default:
break;
}
// Log.d(TAG, "[" + _touchMode + "]" + " touch coords = [" + event.getX() + ";" + event.getY() + "]; isInCrop = " + isInCrop);
return isScaled || isInCrop;
}
private void recalculateLineCropingArea() {
RecalculateCropingAreaTaskArgs arg = new RecalculateCropingAreaTaskArgs()
.setBitmapHeight(_bitmap.getHeight())
.setBitmapWidth(_bitmap.getWidth());
if (_drawingViewHandler != null) {
arg.setCropingRect(_drawingViewHandler.getCropingRect());
}
_pendingRecalculateCropAreaTask = new RecalculateCropingAreaTask();
_pendingRecalculateCropAreaTask.execute(arg);
}
private void updateLineCropingArea(float x, float y) {
if (x < _minX || _minX == 0) {
_minX = x;
} else if (x > _maxX) {
_maxX = x;
}
if (y < _minY || _minY == 0) {
_minY = y;
} else if (y > _maxY) {
_maxY = y;
}
}
public Bitmap getBitmap() {
return _bitmap;
}
public RectF getLineRect() {
return new RectF(_minX, _minY, _maxX, _maxY);
}
public void waitForPendingCalculations(){
checkPendingRecalculationFinished();
}
public void clearPath() {
if (_bitmap == null) {
return;
}
cancelPendingRecalculation();
int width = _bitmap.getWidth();
int height = _bitmap.getHeight();
_bitmap.recycle();
_bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
_canvas = new Canvas(_bitmap);
_maxX = 0;
_maxY = 0;
_minX = 0;
_minY = 0;
_translate = new CoordsPoint();
invalidate();
}
public void setScalingObserver(ScalingObserver observer) {
_scalingObserver = observer;
}
public void resetScaling() {
resetDrawingScaling();
if(_scalingObserver != null){
_scalingObserver.onClearScale();
}
_translate = new CoordsPoint();
invalidate();
}
public void resetDrawingScaling(){
clearScaleFactor();
invalidate();
}
public void resetDrawingScalingo(){
_paint = null;
}
}