自定义裁剪框

这是一个自定义的裁剪框View,支持触摸移动和缩放功能。通过监听MotionEvent,实现裁剪框在屏幕内的平移,并保持其在允许的边界范围内。同时,代码中包含了防止裁剪框尺寸小于预设最小值的判断。裁剪框的移动和大小调整由ACTION_DOWN、ACTION_MOVE和ACTION_UP事件驱动,确保裁剪框始终在有效区域内。
摘要由CSDN通过智能技术生成
自定义View ---- 裁剪框

使用:xml或创建就行。

缩放功能未做,思路可如下:
定义个 CallBack,缩放后重置裁剪框的大小,定义两个变量保存裁剪框绝对中心点,以及绝对宽高。四个值按照变化除以缩放比例即可, CallBack 回调完成图片对应部分的缩放。

问题: 需要隐藏 ActionBar,不隐藏就设置一下高度差,为ActionBar的高度,ActionBar高度值需要等onResume() 执行完之后才能获取。 不支持横竖屏切换。
//隐藏 ActionBar
context.getSupportActionBar().hide();

裁剪框:


import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;

import androidx.annotation.Nullable;

import java.util.Stack;

public class TailoringView extends View {

    public TailoringView(Context context) {
        super(context, null);
        init(context);
    }

    public TailoringView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        init(context);
    }

    public TailoringView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context);
    }

    public TailoringView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        init(context);
    }

    private void init(Context context) {

        direction = context.getResources().getConfiguration().getLayoutDirection();

        displayWidth = context.getResources().getDisplayMetrics().widthPixels;
        displayHeight = context.getResources().getDisplayMetrics().heightPixels;

        x = displayWidth >> 1;
        y = displayHeight >> 1;

        width = displayWidth >> 1;
        height = displayHeight >> 1;

        mRectPaint.setColor(context.getColor(R.color.black));
        mRectPaint.setAlpha(0);

        greyPaint.setColor(context.getColor(R.color.black));
        greyPaint.setAlpha(100);

        mRectPaint.setStrokeWidth(1);
        mFramePaint.setColor(context.getColor(R.color.white));


    }

    /**
     * 横竖屏方向
     */
    private int direction;
    /**
     * 裁剪框中心坐标
     */
    private float x, y;
    /**
     * 屏幕宽高,默认获取的屏幕宽高
     */
    private int displayWidth, displayHeight;
    /**
     * 裁剪框宽高
     */
    private float width, height;

    /**
     * 上一次的中心值,宽高值
     */
    private final Stack<Float> lastXStack = new Stack<>();
    private final Stack<Float> lastYStack = new Stack<>();
    private final Stack<Float> lastWStack = new Stack<>();
    private final Stack<Float> lastHStack = new Stack<>();

    /**
     * 点击的X坐标
     */
    private float clickX;
    /**
     * 点击的Y坐标
     */
    private float clickY;

    /**
     * 裁剪框最小宽度,默认200
     */
    private float minWidth = 200;
    /**
     * 裁剪框最小高度,默认200
     */
    private float minHeight = 200;
    /**
     * 最大的边界距离,默认100
     */
    private float padding = 100;

    /**
     * 获取的高度会有问题,该值可以调整
     */
    private float heightOffset = 210;

    /**
     * 0,1,2,3:0左,1上,2右,3下
     */
    private int all = -1;

    private final Paint mRectPaint = new Paint();
    private final Paint mFramePaint = new Paint();
    private final Paint greyPaint = new Paint();

    @SuppressLint("ResourceAsColor")
    @Override
    protected void onDraw(Canvas canvas) {

        synchronized (this) {
            final float diff = Math.max(width / 160, height / 160);

            mFramePaint.setStrokeWidth(diff * 2);

            float x1 = x - (width / 2);
            float x2 = x + (width / 2);
            float y1 = y - (height / 2);
            float y2 = y + (height / 2);

            float diffFrame = Math.min(height / 5, width / 5);

            //绘制矩形框
            canvas.drawRect(x1, y1, x2, y2, mRectPaint);
            //周围置灰
            canvas.drawRect(0, 0, x1 + diff, displayHeight, greyPaint);
            canvas.drawRect(x1 + diff, 0, displayWidth, y1 + diff, greyPaint);
            canvas.drawRect(x2 - diff, y1 + diff, displayWidth, y2 - diff, greyPaint);
            canvas.drawRect(x1 + diff, y2 - diff, displayWidth, displayHeight, greyPaint);

            //左上角
            canvas.drawLine(x1 - diff, y1, x1 + diffFrame, y1, mFramePaint);
            canvas.drawLine(x1, y1 - diff, x1, y1 + diffFrame, mFramePaint);
            //左下角
            canvas.drawLine(x1, y2 + diff, x1, y2 - diffFrame, mFramePaint);
            canvas.drawLine(x1 - diff, y2, x1 + diffFrame, y2, mFramePaint);
            //右上角
            canvas.drawLine(x2 + diff, y1, x2 - diffFrame, y1, mFramePaint);
            canvas.drawLine(x2, y1 - diff, x2, y1 + diffFrame, mFramePaint);
            //右下角
            canvas.drawLine(x2 + diff, y2, x2 - diffFrame, y2, mFramePaint);
            canvas.drawLine(x2, y2 + diff, x2, y2 - diffFrame, mFramePaint);
        }
    }

    @SuppressLint("ClickableViewAccessibility")
    @Override
    public boolean onTouchEvent(MotionEvent event) {

        float diffAll = Math.min(height / 5, width / 5);
        switch (event.getAction()) {

            case MotionEvent.ACTION_MOVE:
                if(all != -1) {
                    //防止速度过快,未产生事件
                    lastXStack.add(x);
                    lastYStack.add(y);
                    lastWStack.add(width);
                    lastHStack.add(height);
                }

                final float offX = event.getRawX();
                final float offY = event.getRawY();

                final float dXValue = offX - clickX;
                final float dYValue = offY - clickY;

                clickX = offX;
                clickY = offY;

                //X超边界不可移动
                if (x - (width / 2) <= padding && (all == 0 && dXValue < 0)) {
                    return true;
                }
                if (x + (width / 2) >= displayWidth - padding && (all == 2 && dXValue > 0)) {
                    return true;
                }

                //Y超边界不可移动
                if (y - (height / 2) < padding && (all == 1 && dYValue < 0)) {
                    return true;
                }
                if (y + (height / 2) >= displayHeight - padding - heightOffset && (all == 3 && dYValue > 0)) {
                    return true;
                }

                //宽是否为最小值
                if (width <= minWidth) {
                    if (all == 0 && dXValue > 0) {
                        return true;
                    }
                    if (all == 2 && dXValue < 0) {
                        return true;
                    }
                }
                //高是否为最小值
                if (height <= minHeight) {
                    if (all == 1 && dYValue > 0) {
                        return true;
                    }
                    if (all == 3 && dYValue < 0) {
                        return true;
                    }
                }
                //根据点击点进行值变化
                switch (all) {
                    case 0:
                        x += (dXValue / 2);
                        width -= dXValue;
                        break;
                    case 1:
                        y += (dYValue / 2);
                        height -= dYValue;
                        break;
                    case 2:
                        x += (dXValue / 2);
                        width += dXValue;
                        break;
                    case 3:
                        y += (dYValue / 2);
                        height += dYValue;
                        break;
                    default:
                        break;
                }
                if (all != -1) {
                    invalidate();
                }
                break;
            case MotionEvent.ACTION_DOWN:
                float x1 = x - (width / 2);
                float x2 = x + (width / 2);
                float y1 = y - (height / 2);
                float y2 = y + (height / 2);

                //需要移动的边
                clickX = event.getRawX();
                clickY = event.getRawY() - heightOffset;
                if (Math.abs(clickX - x1) <= diffAll && clickY >= y1 && clickY <= y2) {
                    //点击左边界
                    all = 0;
                } else if (Math.abs(clickY - y1) <= diffAll && clickX >= x1 && clickX <= x2) {
                    //点击上边界
                    all = 1;
                } else if (Math.abs(clickX - x2) <= diffAll && clickY >= y1 && clickY <= y2) {
                    //点击右边界
                    all = 2;
                } else if (Math.abs(clickY - y2) <= diffAll && clickX >= x1 && clickX <= x2) {
                    //点击下边界
                    all = 3;
                } else {
                    all = -1;
                }
                if(all != -1) {
                    lastXStack.add(x);
                    lastYStack.add(y);
                    lastWStack.add(width);
                    lastHStack.add(height);
                }
                break;
                case MotionEvent.ACTION_UP:
                    if(x - (width / 2) <= padding) {
                        popValue();
                        while (x - (width / 2) <= padding) {
                            popValue();
                        }
                    }
                    if(x + (width / 2) >= displayWidth - padding) {
                        popValue();
                        while (x + (width / 2) >= displayWidth - padding) {
                            popValue();
                        }
                    }
                    if(y - (height / 2) < padding) {
                        popValue();
                        while (y - (height / 2) < padding ) {
                            popValue();
                        }
                    }
                    if(y + (height / 2) >= displayHeight - padding - heightOffset) {
                        popValue();
                        while (y + (height / 2) >= displayHeight - padding - heightOffset) {
                            popValue();
                        }
                    }
                    if(width <= minWidth) {
                        popValue();
                        while (width <= minWidth) {
                            popValue();
                        }
                    }
                    if(height <= minHeight) {
                        popValue();
                        while (height <= minHeight) {
                            popValue();
                        }
                    }

                    invalidate();
                    break;
            default:
                break;
        }

        return true;
    }

    private void reset() {
        lastXStack.clear();
        lastYStack.clear();
        lastWStack.clear();
        lastHStack.clear();

        x = displayWidth >> 1;
        y = displayHeight >> 1;

        width = displayWidth >> 1;
        height = displayHeight >> 1;

        lastXStack.add(x);
        lastYStack.add(y);
        lastWStack.add(width);
        lastHStack.add(height);
    }

    /**
     * MOVE 事件产生慢,导致UI异常,进行值回溯防止异常
     */
    private void popValue() {
        if(lastHStack.size() == 0 || lastWStack.size() == 0 || lastXStack.size() == 0 || lastYStack.size() == 0) {
            reset();
            return;
        }
        x = lastXStack.pop();
        y = lastYStack.pop();
        height = lastHStack.pop();
        width = lastWStack.pop();
        invalidate();
    }

    /**
     * 获取X值
     * @return 裁剪框中心点的 x 坐标
     */
    public float getMX() {
        return x;
    }

    /**
     * 获取Y值
     * @return 裁剪框中心点的 Y 坐标
     */
    public float getMY() {
        return y;
    }

    /**
     * 获取裁剪框宽
     * @return 裁剪框的宽值
     */
    public float getMWidth() {
        return width;
    }

    /**
     * 获取裁剪框高
     * @return 裁剪框的高值
     */
    public float getMHeight() {
        return height;
    }

    /**
     * 设置 View 视图最大宽度
     * @param displayWidth 最大宽度
     */
    public void setDisplayWidth(int displayWidth) {
        this.displayWidth = displayWidth;
        reset();
    }

    /**
     * 设置 View 视图最大高度
     * @param displayHeight 最大高度
     */
    public void setDisplayHeight(int displayHeight) {
        this.displayHeight = displayHeight;
        reset();
    }

    /**
     * 设置裁剪框最小宽度
     * @param minWidth 裁剪框最小宽度
     */
    public void setMinWidth(float minWidth) {
        this.minWidth = minWidth;
        reset();
    }

    /**
     * 设置裁剪框最小高度
     * @param minHeight 裁剪框最小高度
     */
    public void setMinHeight(float minHeight) {
        this.minHeight = minHeight;
        reset();
    }

    /**
     * 设置裁剪框距离边界线最小差值,超过该值不可再往外移动
     * @param padding 最小差距值
     */
    public void setPadding(float padding) {
        this.padding = padding;
        reset();
    }

    /**
     * 设置高度差,获取的高度与真实高度不一致,用该值进行高度调整
     * @param heightOffset 高度差
     */
    public void setHeightOffset(float heightOffset) {
        this.heightOffset = heightOffset;
        reset();
    }
}


快速复制粘贴:

import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;

import androidx.annotation.Nullable;

import java.util.Stack;

public class TailoringView extends View {

public TailoringView(Context context) {
    super(context, null);
    init(context);
}

public TailoringView(Context context, @Nullable AttributeSet attrs) {
    super(context, attrs);
    init(context);
}

public TailoringView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
    init(context);
}

public TailoringView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
    super(context, attrs, defStyleAttr, defStyleRes);
    init(context);
}

private void init(Context context) {

    direction = context.getResources().getConfiguration().getLayoutDirection();

    displayWidth = context.getResources().getDisplayMetrics().widthPixels;
    displayHeight = context.getResources().getDisplayMetrics().heightPixels;

    x = displayWidth >> 1;
    y = displayHeight >> 1;

    width = displayWidth >> 1;
    height = displayHeight >> 1;

    mRectPaint.setColor(context.getColor(R.color.black));
    mRectPaint.setAlpha(0);

    greyPaint.setColor(context.getColor(R.color.black));
    greyPaint.setAlpha(100);

    mRectPaint.setStrokeWidth(1);
    mFramePaint.setColor(context.getColor(R.color.white));


}

/**
 * 横竖屏方向
 */
private int direction;
/**
 * 裁剪框中心坐标
 */
private float x, y;
/**
 * 屏幕宽高,默认获取的屏幕宽高
 */
private int displayWidth, displayHeight;
/**
 * 裁剪框宽高
 */
private float width, height;

/**
 * 上一次的中心值,宽高值
 */
private final Stack<Float> lastXStack = new Stack<>();
private final Stack<Float> lastYStack = new Stack<>();
private final Stack<Float> lastWStack = new Stack<>();
private final Stack<Float> lastHStack = new Stack<>();

/**
 * 点击的X坐标
 */
private float clickX;
/**
 * 点击的Y坐标
 */
private float clickY;

/**
 * 裁剪框最小宽度,默认200
 */
private float minWidth = 200;
/**
 * 裁剪框最小高度,默认200
 */
private float minHeight = 200;
/**
 * 最大的边界距离,默认100
 */
private float padding = 100;

/**
 * 获取的高度会有问题,该值可以调整
 */
private float heightOffset = 210;

/**
 * 0,1,2,3:0左,1上,2右,3下
 */
private int all = -1;

private final Paint mRectPaint = new Paint();
private final Paint mFramePaint = new Paint();
private final Paint greyPaint = new Paint();

@SuppressLint("ResourceAsColor")
@Override
protected void onDraw(Canvas canvas) {

    synchronized (this) {
        final float diff = Math.max(width / 160, height / 160);

        mFramePaint.setStrokeWidth(diff * 2);

        float x1 = x - (width / 2);
        float x2 = x + (width / 2);
        float y1 = y - (height / 2);
        float y2 = y + (height / 2);

        float diffFrame = Math.min(height / 5, width / 5);

        //绘制矩形框
        canvas.drawRect(x1, y1, x2, y2, mRectPaint);
        //周围置灰
        canvas.drawRect(0, 0, x1 + diff, displayHeight, greyPaint);
        canvas.drawRect(x1 + diff, 0, displayWidth, y1 + diff, greyPaint);
        canvas.drawRect(x2 - diff, y1 + diff, displayWidth, y2 - diff, greyPaint);
        canvas.drawRect(x1 + diff, y2 - diff, displayWidth, displayHeight, greyPaint);

        //左上角
        canvas.drawLine(x1 - diff, y1, x1 + diffFrame, y1, mFramePaint);
        canvas.drawLine(x1, y1 - diff, x1, y1 + diffFrame, mFramePaint);
        //左下角
        canvas.drawLine(x1, y2 + diff, x1, y2 - diffFrame, mFramePaint);
        canvas.drawLine(x1 - diff, y2, x1 + diffFrame, y2, mFramePaint);
        //右上角
        canvas.drawLine(x2 + diff, y1, x2 - diffFrame, y1, mFramePaint);
        canvas.drawLine(x2, y1 - diff, x2, y1 + diffFrame, mFramePaint);
        //右下角
        canvas.drawLine(x2 + diff, y2, x2 - diffFrame, y2, mFramePaint);
        canvas.drawLine(x2, y2 + diff, x2, y2 - diffFrame, mFramePaint);
    }
}

@SuppressLint("ClickableViewAccessibility")
@Override
public boolean onTouchEvent(MotionEvent event) {

    float diffAll = Math.min(height / 5, width / 5);
    switch (event.getAction()) {

        case MotionEvent.ACTION_MOVE:
            if(all != -1) {
                //防止速度过快,未产生事件
                lastXStack.add(x);
                lastYStack.add(y);
                lastWStack.add(width);
                lastHStack.add(height);
            }

            final float offX = event.getRawX();
            final float offY = event.getRawY();

            final float dXValue = offX - clickX;
            final float dYValue = offY - clickY;

            clickX = offX;
            clickY = offY;

            //X超边界不可移动
            if (x - (width / 2) <= padding && (all == 0 && dXValue < 0)) {
                return true;
            }
            if (x + (width / 2) >= displayWidth - padding && (all == 2 && dXValue > 0)) {
                return true;
            }

            //Y超边界不可移动
            if (y - (height / 2) < padding && (all == 1 && dYValue < 0)) {
                return true;
            }
            if (y + (height / 2) >= displayHeight - padding - heightOffset && (all == 3 && dYValue > 0)) {
                return true;
            }

            //宽是否为最小值
            if (width <= minWidth) {
                if (all == 0 && dXValue > 0) {
                    return true;
                }
                if (all == 2 && dXValue < 0) {
                    return true;
                }
            }
            //高是否为最小值
            if (height <= minHeight) {
                if (all == 1 && dYValue > 0) {
                    return true;
                }
                if (all == 3 && dYValue < 0) {
                    return true;
                }
            }
            //根据点击点进行值变化
            switch (all) {
                case 0:
                    x += (dXValue / 2);
                    width -= dXValue;
                    break;
                case 1:
                    y += (dYValue / 2);
                    height -= dYValue;
                    break;
                case 2:
                    x += (dXValue / 2);
                    width += dXValue;
                    break;
                case 3:
                    y += (dYValue / 2);
                    height += dYValue;
                    break;
                default:
                    break;
            }
            if (all != -1) {
                invalidate();
            }
            break;
        case MotionEvent.ACTION_DOWN:
            float x1 = x - (width / 2);
            float x2 = x + (width / 2);
            float y1 = y - (height / 2);
            float y2 = y + (height / 2);

            //需要移动的边
            clickX = event.getRawX();
            clickY = event.getRawY() - heightOffset;
            if (Math.abs(clickX - x1) <= diffAll && clickY >= y1 && clickY <= y2) {
                //点击左边界
                all = 0;
            } else if (Math.abs(clickY - y1) <= diffAll && clickX >= x1 && clickX <= x2) {
                //点击上边界
                all = 1;
            } else if (Math.abs(clickX - x2) <= diffAll && clickY >= y1 && clickY <= y2) {
                //点击右边界
                all = 2;
            } else if (Math.abs(clickY - y2) <= diffAll && clickX >= x1 && clickX <= x2) {
                //点击下边界
                all = 3;
            } else {
                all = -1;
            }
            if(all != -1) {
                lastXStack.add(x);
                lastYStack.add(y);
                lastWStack.add(width);
                lastHStack.add(height);
            }
            break;
            case MotionEvent.ACTION_UP:
                if(x - (width / 2) <= padding) {
                    popValue();
                    while (x - (width / 2) <= padding) {
                        popValue();
                    }
                }
                if(x + (width / 2) >= displayWidth - padding) {
                    popValue();
                    while (x + (width / 2) >= displayWidth - padding) {
                        popValue();
                    }
                }
                if(y - (height / 2) < padding) {
                    popValue();
                    while (y - (height / 2) < padding ) {
                        popValue();
                    }
                }
                if(y + (height / 2) >= displayHeight - padding - heightOffset) {
                    popValue();
                    while (y + (height / 2) >= displayHeight - padding - heightOffset) {
                        popValue();
                    }
                }
                if(width <= minWidth) {
                    popValue();
                    while (width <= minWidth) {
                        popValue();
                    }
                }
                if(height <= minHeight) {
                    popValue();
                    while (height <= minHeight) {
                        popValue();
                    }
                }

                invalidate();
                break;
        default:
            break;
    }

    return true;
}

private void reset() {
    lastXStack.clear();
    lastYStack.clear();
    lastWStack.clear();
    lastHStack.clear();

    x = displayWidth >> 1;
    y = displayHeight >> 1;

    width = displayWidth >> 1;
    height = displayHeight >> 1;

    lastXStack.add(x);
    lastYStack.add(y);
    lastWStack.add(width);
    lastHStack.add(height);
}

/**
 * MOVE 事件产生慢,导致UI异常,进行值回溯防止异常
 */
private void popValue() {
    if(lastHStack.size() == 0 || lastWStack.size() == 0 || lastXStack.size() == 0 || lastYStack.size() == 0) {
        reset();
        return;
    }
    x = lastXStack.pop();
    y = lastYStack.pop();
    height = lastHStack.pop();
    width = lastWStack.pop();
    invalidate();
}

/**
 * 获取X值
 * @return 裁剪框中心点的 x 坐标
 */
public float getMX() {
    return x;
}

/**
 * 获取Y值
 * @return 裁剪框中心点的 Y 坐标
 */
public float getMY() {
    return y;
}

/**
 * 获取裁剪框宽
 * @return 裁剪框的宽值
 */
public float getMWidth() {
    return width;
}

/**
 * 获取裁剪框高
 * @return 裁剪框的高值
 */
public float getMHeight() {
    return height;
}

/**
 * 设置 View 视图最大宽度
 * @param displayWidth 最大宽度
 */
public void setDisplayWidth(int displayWidth) {
    this.displayWidth = displayWidth;
    reset();
}

/**
 * 设置 View 视图最大高度
 * @param displayHeight 最大高度
 */
public void setDisplayHeight(int displayHeight) {
    this.displayHeight = displayHeight;
    reset();
}

/**
 * 设置裁剪框最小宽度
 * @param minWidth 裁剪框最小宽度
 */
public void setMinWidth(float minWidth) {
    this.minWidth = minWidth;
    reset();
}

/**
 * 设置裁剪框最小高度
 * @param minHeight 裁剪框最小高度
 */
public void setMinHeight(float minHeight) {
    this.minHeight = minHeight;
    reset();
}

/**
 * 设置裁剪框距离边界线最小差值,超过该值不可再往外移动
 * @param padding 最小差距值
 */
public void setPadding(float padding) {
    this.padding = padding;
    reset();
}

/**
 * 设置高度差,获取的高度与真实高度不一致,用该值进行高度调整
 * @param heightOffset 高度差
 */
public void setHeightOffset(float heightOffset) {
    this.heightOffset = heightOffset;
    reset();
}

}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值