自定义镂空蒙版控件

自定义镂空蒙版控件


蒙版控件
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.DashPathEffect;
import android.graphics.DiscretePathEffect;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PathEffect;
import android.graphics.RectF;
import android.graphics.Region;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;

import androidx.annotation.ColorInt;
import androidx.annotation.ColorRes;
import androidx.annotation.IntDef;
import androidx.annotation.Nullable;
import androidx.lifecycle.Lifecycle;
import androidx.lifecycle.LifecycleObserver;
import androidx.lifecycle.LifecycleOwner;
import androidx.lifecycle.OnLifecycleEvent;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

public class MaskView extends View implements LifecycleObserver {

    /**
     * 四角圆,矩形
     */
    public static final int TYPE_RECT = 1;
    /**
     * 椭圆形/圆
     */
    public static final int TYPE_OVAL = 2;

    public static final int TYPE_SOLID = 1;//实线
    public static final int TYPE_DASHED = 2;//虚线
    public static final int TYPE_ONLY_CORNERS = 3;//仅四角 仅矩形可用
    public static final int TYPE_LACEWORK = 4;//花边
    private PathEffect pathEffect;

    private float innerBoundSectionWidth;
    private int innerBoundStrokeType;
    private boolean isAdjust;
    private int width;
    private int height;
    private boolean isRecalculate = true;

    @IntDef({TYPE_RECT, TYPE_OVAL})
    @Retention(RetentionPolicy.SOURCE)
    public @interface BoundsType {
    }

    @IntDef({TYPE_SOLID, TYPE_DASHED, TYPE_ONLY_CORNERS, TYPE_LACEWORK})
    @Retention(RetentionPolicy.SOURCE)
    public @interface BoundsStrokeType {
    }

    private float innerWidth;
    private float innerHeight;
    private float offsetX;
    private float offsetY;
    private Float offsetPercentX = null;
    private Float offsetPercentY = null;
    private boolean isOffsetX = false;
    private boolean isOffsetY = false;

    private int mType;//默认椭圆形
    private float mRadius;
    private boolean isBorder = false;

    private boolean isClip = false;
    private Paint boundPaint;
    private RectF innerBounds;
    private Path innerPath;
    private float boundWidth;
    private int backgroundColor;
    private float offsetBound;

    @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
    public void onDestroy() {
        boundPaint = null;
        offsetPercentX = null;
        offsetPercentY = null;
        innerBounds = null;
        innerPath = null;
        if (getContext() instanceof LifecycleOwner) {
            ((LifecycleOwner) getContext()).getLifecycle().removeObserver(this);
        }
    }

    public MaskView(Context context) {
        this(context, null);
    }

    public MaskView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public MaskView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        if (context instanceof LifecycleOwner) {
            ((LifecycleOwner) context).getLifecycle().addObserver(this);
        }

        TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.MaskView);
        mType = array.getInt(R.styleable.MaskView_innerBoundType, TYPE_OVAL);

        if (array.hasValue(R.styleable.MaskView_innerBoundWidth)) {
            float boundWidth = array.getDimensionPixelSize(R.styleable.MaskView_innerBoundWidth, 0);
            setBoundPaintW(boundWidth);
        }

        if (array.hasValue(R.styleable.MaskView_innerBoundColor)) {
            int boundColor = array.getColor(R.styleable.MaskView_innerBoundColor, Color.WHITE);
            setBoundPaintC(boundColor);
        }

        innerBoundStrokeType = array.getInt(R.styleable.MaskView_innerBoundStrokeType, TYPE_SOLID);

        innerBoundSectionWidth = array.getDimensionPixelSize(R.styleable.MaskView_innerBoundSectionWidth, 0);

        try {
            innerWidth = array.getLayoutDimension(R.styleable.MaskView_inner_Width, -1);
        } catch (Exception e) {
            e.printStackTrace();
            innerWidth = -1;
        }

        try {
            innerHeight = array.getLayoutDimension(R.styleable.MaskView_inner_height, (int) innerWidth);
        } catch (Exception e) {
            e.printStackTrace();
            innerHeight = -1;
        }

        isAdjust = innerWidth == innerHeight;

        backgroundColor = array.getColor(R.styleable.MaskView_backgroundColor, Color.TRANSPARENT);

        mRadius = array.getDimensionPixelSize(R.styleable.MaskView_radius, 0);

        if (array.hasValue(R.styleable.MaskView_offsetX)) {
            initOffsetX(array.getDimensionPixelSize(R.styleable.MaskView_offsetX, 0));
        }
        if (array.hasValue(R.styleable.MaskView_offsetY)) {
            initOffsetY(array.getDimensionPixelSize(R.styleable.MaskView_offsetY, 0));
        }

        if (array.hasValue(R.styleable.MaskView_offset_percentX)) {
            offsetPercentX = array.getFloat(R.styleable.MaskView_offset_percentX, 0.0f);
            isOffsetX = true;
        }

        if (array.hasValue(R.styleable.MaskView_offset_percentY)) {
            offsetPercentY = array.getFloat(R.styleable.MaskView_offset_percentY, 0.0f);
            isOffsetY = true;
        }

        if (array.hasValue(R.styleable.MaskView_paddingX)) {
            int paddingX = array.getDimensionPixelSize(R.styleable.MaskView_paddingX, 0);
            setPadding(paddingX, getPaddingTop(), paddingX, getPaddingBottom());
        }

        if (array.hasValue(R.styleable.MaskView_paddingY)) {
            int paddingY = array.getDimensionPixelSize(R.styleable.MaskView_paddingY, 0);
            setPadding(getPaddingLeft(), paddingY, getPaddingRight(), paddingY);
        }

        array.recycle();
        setInnerBoundStrokeType();
        innerBounds = new RectF();
        innerPath = new Path();
        setLayerType(View.LAYER_TYPE_SOFTWARE, null);
    }

    public void setRadius(float mRadius) {
        this.mRadius = mRadius;
        isRecalculate = true;
    }

    public void setPadding(float dp) {
        int px = BaseUtils.dp2px(dp);
        setPadding(px, px, px, px);
        isRecalculate = true;
    }

    /**
     * @param marginDp 单位dp
     */
    public void setPaddingX(float marginDp) {
        int px = BaseUtils.dp2px(marginDp);
        setPadding(px, getPaddingTop(), px, getPaddingBottom());
        isRecalculate = true;
    }

    /**
     * @param marginDp 单位dp
     */
    public void setPaddingY(float marginDp) {
        int px = BaseUtils.dp2px(marginDp);
        setPadding(getPaddingLeft(), px, getPaddingRight(), px);
        isRecalculate = true;
    }

    public RectF getBoundsRect() {
        return innerBounds;
    }

    public void setInnerBoundsType(@BoundsType int type) {
        mType = type;
        if (type == TYPE_OVAL && innerBoundStrokeType == TYPE_ONLY_CORNERS) {
            innerBoundStrokeType = TYPE_SOLID;
        }
        isRecalculate = true;
    }

    public void setInnerWidth(float widthDp) {
        innerWidth = BaseUtils.dp2px(widthDp);
        isRecalculate = true;
    }

    public void setInnerHeight(float heightDp) {
        innerHeight = BaseUtils.dp2px(heightDp);
        isAdjust = false;
        isRecalculate = true;
    }

    public void adjustInnerBounds(boolean isAdjust) {
        this.isAdjust = isAdjust;
        isRecalculate = true;
    }

    public void setInnerWidthMatch() {
        innerWidth = ViewGroup.LayoutParams.MATCH_PARENT;
        isRecalculate = true;
    }

    public void setInnerHeightMatch() {
        innerHeight = ViewGroup.LayoutParams.MATCH_PARENT;
        isAdjust = false;
        isRecalculate = true;
    }

    public void setOffsetX(int offset) {
        initOffsetX(offset);
        isRecalculate = true;
    }

    public void setOffsetY(int offset) {
        initOffsetY(offset);
        isRecalculate = true;
    }

    public void setOffsetX(float offsetPercent) {
        offsetPercentX = offsetPercent;
        isOffsetX = true;
        isRecalculate = true;
    }

    public void setOffsetY(float offsetPercent) {
        offsetPercentY = offsetPercent;
        isOffsetY = true;
        isRecalculate = true;
    }

    public void setInnerBoundsCenter() {
        isOffsetY = false;
        isOffsetX = false;
        isRecalculate = true;
    }

    public void setBoundWidth(int widthDp) {
        if (setBoundPaintW(BaseUtils.dp2px(widthDp))) {
            isRecalculate = true;
        }
    }

    public void setBoundColorRes(@ColorRes int color) {
        setBoundColor(ResourcesUtils.getColor(color));
    }

    public void setBoundColor(@ColorInt int color) {
        if (setBoundPaintC(color)) {
            invalidate();
        }
    }

    public void setInnerBoundStrokeType(@BoundsStrokeType int type) {
        innerBoundStrokeType = type;
        setInnerBoundStrokeType();
    }

    @Override
    public void setBackgroundColor(int backgroundColor) {
        this.backgroundColor = backgroundColor;
        invalidate();
    }

    @Override
    public void setBackgroundDrawable(Drawable background) {
        super.setBackgroundDrawable(null);
    }

    @Override
    public void setForeground(Drawable foreground) {
        super.setForeground(null);
    }

    public void setInnerBoundSectionWidth(int dp) {
        innerBoundSectionWidth = dp;
        isRecalculate = true;
    }

    private void setInnerBoundStrokeType() {
        if (innerBoundStrokeType == TYPE_SOLID) {
            pathEffect = null;
        } else if (innerBoundStrokeType == TYPE_DASHED) {
            pathEffect = new DashPathEffect(new float[]{innerBoundSectionWidth, innerBoundSectionWidth}, innerBoundSectionWidth);
        } else if (innerBoundStrokeType == TYPE_LACEWORK) {
            pathEffect = new DiscretePathEffect(innerBoundSectionWidth, innerBoundSectionWidth);
        } else {
            pathEffect = null;
            if (mType == TYPE_OVAL) {
                mType = TYPE_RECT;
            }
        }
    }


    public void setPathEffect(PathEffect pathEffect) {
        this.pathEffect = pathEffect;
        postInvalidate();
    }

    private void initOffsetX(float offset) {
        offsetX = offset;
        isOffsetX = true;
    }

    private void initOffsetY(float offset) {
        offsetY = offset;
        isOffsetY = true;
    }

    private boolean setBoundPaintW(float width) {
        boolean changeW = boundPaint == null || width != boundPaint.getStrokeWidth();
        if (changeW) {
            boundWidth = width;
            initBoundPaint();
            boundPaint.setStrokeWidth(width);
        }
        isBorder();
        return changeW;
    }

    private boolean setBoundPaintC(int boundColor) {
        boolean changeC = boundPaint == null || boundColor != boundPaint.getColor();
        if (changeC) {
            initBoundPaint();
            boundPaint.setColor(boundColor);
        }
        isBorder();
        return changeC;
    }

    private void isBorder() {
        isBorder = boundPaint != null && (boundPaint.getStrokeWidth() > 0 || boundPaint.getColor() != Color.TRANSPARENT);
    }

    private void initBoundPaint() {
        if (boundPaint == null) {
            boundPaint = new Paint();
            boundPaint.setAntiAlias(true);
            boundPaint.setStyle(Paint.Style.STROKE);
        }
    }

    /**
     * 计算偏移
     */
     /**
     * 计算偏移
     */
    private float calculateOffset(int view, float inner, float offset, Float percent, boolean isOffset) {
        if (isOffset) {
            if (percent != null) {
                offset = (view - inner) * percent;
            }
            return offset;
        } else {
            return (view - offsetBound * 2 - inner) / 2f;
        }
    }

    /**
     * 计算内框宽/高
     */
    private float calculateSize(float view, float inner, float marginStart, float marginEnd) {
        if (inner == ViewGroup.LayoutParams.MATCH_PARENT) {
            inner = view - offsetBound * 2 - marginStart - marginEnd;
        }
        return inner;
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        isRecalculate = true;
        recalculate();
    }

    private void recalculate() {
        if (isRecalculate) {
            isRecalculate = false;
            width = getMeasuredWidth();
            height = getMeasuredHeight();

            offsetBound = isBorder ? boundWidth : 0;

            innerWidth = calculateSize(width, innerWidth, getPaddingLeft(), getPaddingRight());
            innerHeight = calculateSize(height, innerHeight, getPaddingTop(), getPaddingBottom());

            if (isAdjust) {
                innerWidth = Math.min(innerWidth, innerHeight);
                innerHeight = innerWidth;
            }

            offsetX = calculateOffset(width, innerWidth, offsetX, offsetPercentX, isOffsetX);
            offsetY = calculateOffset(height, innerHeight, offsetY, offsetPercentY, isOffsetY);

            float offsetStart = offsetBound / 2f;
            float offsetEnd = offsetBound * 1.5f;

            innerBounds.set(offsetX + offsetStart,
                    offsetY + offsetStart,
                    offsetX + innerWidth + offsetEnd,
                    offsetY + innerHeight + offsetEnd);
            setPath();
        }
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);
        recalculate();
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        onDrawBackground(canvas);
        onDrawBound(canvas);
    }

    private void onDrawBackground(Canvas canvas) {
        canvas.save();
        if (isClip) {
            canvas.clipPath(innerPath, Region.Op.DIFFERENCE);
        }
        canvas.drawColor(backgroundColor);
        canvas.restore();
    }

    private void onDrawBound(Canvas canvas) {
        canvas.save();
        if (isBorder && isClip) {
            if (innerBoundStrokeType == TYPE_ONLY_CORNERS) {

                float strokeWidth = boundPaint.getStrokeWidth();

                canvas.clipRect(innerBounds.left - strokeWidth,
                        innerBounds.top + innerBoundSectionWidth,
                        innerBounds.right + strokeWidth,
                        innerBounds.bottom - innerBoundSectionWidth,
                        Region.Op.DIFFERENCE);

                canvas.clipRect(innerBounds.left + innerBoundSectionWidth,
                        innerBounds.top - strokeWidth,
                        innerBounds.right - innerBoundSectionWidth,
                        innerBounds.bottom + strokeWidth,
                        Region.Op.DIFFERENCE);
            }
            if (pathEffect != null) {
                boundPaint.setPathEffect(pathEffect);
            }
            canvas.drawPath(innerPath, boundPaint);
        }
        canvas.restore();
    }

    private void setPath() {
        isClip = innerPath != null && innerBounds != null && !innerBounds.isEmpty();
        if (isClip) {
            innerPath.reset();
            if (mType == TYPE_RECT) {// 绘制圆角矩形
                innerPath.addRoundRect(innerBounds, mRadius, mRadius, Path.Direction.CW);
            } else if (mType == TYPE_OVAL) {
                // 绘制椭圆
                innerPath.addOval(innerBounds, Path.Direction.CW);
            }
        }
    }
}

attrs.xml
      <declare-styleable name="MaskView">
        <!--内边框宽度-->
        <attr name="innerBoundWidth" format="dimension" />
        <!--内边框颜色-->
        <attr name="innerBoundColor" format="color" />
        <attr name="inner_Width" format="dimension">
            <enum name="match_parent" value="-1" />
        </attr>
        <!--默认inner_height=inner_Width-->
        <attr name="inner_height" format="dimension">
            <enum name="match_parent" value="-1" />
        </attr>
        <!--inner_Width = match_parent 可用-->
        <attr name="paddingX" format="dimension" />
        <!--inner_height = match_parent 可用-->
        <attr name="paddingY" format="dimension" />
        <!--背景颜色-->
        <attr name="backgroundColor" format="color" />
        <!--type = Rect 可用-->
        <attr name="radius" format="dimension" />
        <!--镂空外左边(含边框)与View内左边偏移尺寸-->
        <attr name="offsetX" format="dimension" />
        <!--镂空外顶边(含边框)与View内顶边偏移尺寸-->
        <attr name="offsetY" format="dimension" />
        <!--镂空外左边(含边框)与View内左边偏移百分比-->
        <attr name="offset_percentX" format="float" />
        <!--镂空外顶边(含边框)与View内顶边偏移百分比-->
        <attr name="offset_percentY" format="float" />
        <attr name="innerBoundType" format="enum">
            <enum name="Rect" value="1" />
            <enum name="Oval" value="2" />
        </attr>
        <!--内边框描边类型-->
        <attr name="innerBoundStrokeType" format="enum">
            <enum name="solid" value="1" />
            <enum name="dashed" value="2" />
            <!--innerBoundType=Rect可用-->
            <enum name="onlyCorners" value="3" />
            <enum name="lacework" value="4" />
        </attr>
        <!--innerBoundRimType = dashed/onlyCorners/lacework 可用-->
        <attr name="innerBoundSectionWidth" format="dimension" />
    </declare-styleable>
使用示例
<!--正方形镂空四角描边蒙版-->
		<com.***.widget.MaskView
            android:id="@+id/mask_view"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
     		app:innerBoundType="Rect"
            app:offset_percentY="0.35"
            android:padding="35dp"
            app:innerBoundStrokeType="onlyCorners"
            app:innerBoundSectionWidth="20dp"
            app:innerBoundColor="@color/white"
            app:inner_Width="match_parent"
            app:innerBoundWidth="3dp"/>
<!--圆形镂空实线描边蒙版-->
		<com.***.widget.MaskView
            android:id="@+id/mask_view"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            app:innerBoundType="Oval"
            app:offset_percentY="0.35"
            android:padding="35dp"
            app:innerBoundSectionWidth="20dp"
            app:innerBoundColor="@color/white"
            app:inner_Width="match_parent"
            app:innerBoundWidth="5dp"/>
效果示例

1、正方形镂空四角描边蒙版
正方形镂空四角描边蒙版
圆形镂空实线描边蒙版
圆形镂空实线描边蒙版

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
UniApp是一个跨平台的开发框架,可以用于开发iOS、Android和Web应用。在UniApp中,可以使用原生的相机组件来进行拍照或录像操作,并且可以通过自定义蒙版来实现一些特殊效果。 要实现自定义相机蒙版,首先需要使用`uni.chooseImage`接口选择图片作为蒙版素材。然后,使用`uni.scanCode`接口获取相机的扫描结果,将扫描结果与蒙版进行合成。最后,使用`uni.previewImage`接口预览合成后的照片。 以下是一个简单的示例代码: ```javascript // 选择图片作为蒙版素材 uni.chooseImage({ success: function (res) { var maskImage = res.tempFilePaths[0]; // 打开相机扫描 uni.scanCode({ success: function (scanRes) { var scanResult = scanRes.result; // 合成蒙版 uni.getImageInfo({ src: maskImage, success: function (infoRes) { var context = uni.createCanvasContext('cameraCanvas'); context.drawImage(infoRes.path, 0, 0, infoRes.width, infoRes.height); // 在蒙版上绘制扫描结果 context.setFontSize(20); context.fillText(scanResult, 50, 50); // 导出合成后的照片 context.draw(false, function () { uni.canvasToTempFilePath({ canvasId: 'cameraCanvas', success: function (exportRes) { var mergedImage = exportRes.tempFilePath; // 预览合成后的照片 uni.previewImage({ urls: [mergedImage] }); } }); }); } }); } }); } }); ``` 以上代码中使用了`uni.chooseImage`选择蒙版素材,`uni.scanCode`获取扫描结果,`uni.getImageInfo`获取蒙版图片信息,以及`uni.canvasToTempFilePath`导出合成后的照片,并使用`uni.previewImage`预览合成后的照片。 请根据自己的实际需求进行相应的修改和扩展。希望对你有所帮助!
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值