IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> 自定义镂空蒙版控件 -> 正文阅读

[移动开发]自定义镂空蒙版控件

自定义镂空蒙版控件


蒙版控件

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、正方形镂空四角描边蒙版
正方形镂空四角描边蒙版
圆形镂空实线描边蒙版
圆形镂空实线描边蒙版

  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2021-07-09 17:36:08  更:2021-07-09 17:36:20 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/20 10:12:30-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码