水印绘制控件

水印绘制控件
/**
 * 创建者:Tomcat0916
 * 创建时间:2020/11/25
 * 功能描述:水印控件
 * 自动行列
 * 示例:
 * <pre>
 *      <com.*.WatermarkView
 *         android:id="@+id/tv_text_hint"
 *         android:layout_width="match_parent"
 *         android:layout_height="match_parent"
 *         android:alpha="0.5"
 *         android:rotation="-30"
 *         android:textColor="@color/color_gray_4"
 *         android:textSize="12sp"
 *         android:textStyle="bold"/>
 * </pre>
 *
 * @see R.styleable#WatermarkView_column  列  默认: 自动计算 = 宽/字符长度
 * @see R.styleable#WatermarkView_row   行  默认 : 自动计算 = 高/字符串高度
 * @see R.styleable#WatermarkView_decorationX  最小列间距 固定列 默认1dp 否则 默认 50dp
 * @see R.styleable#WatermarkView_decorationY   最小行间距  固定行 默认1dp 否则 默认 50dp
 */
public class WatermarkView extends AppCompatTextView {

    private static final int DEF_DECORATION_Y = 50, DEF_DECORATION_X = 50;

    private StaticLayout textLayout;
    private CharSequence lastText = "";
    private int row = -1, column = -1, realRow = -1, realColumn = -1, columnWith, rowHeight, width, height;
    private boolean isCreated, isFixRows, isFixColumns, isVertical, isTextSizeChanged = false;
    private float decorationX = -1,//横向间离
            decorationY = -1, //纵向间离,
            spacingX = -1,//横向间离
            spacingY = -1, //纵向间离
            offsetX, //偶数行横向偏移量
            correctY = 0, correctX = 0,//因canvas默认旋转原点为左上角,所以旋转后需矫正偏移
            rotation, //旋转角度
            maxTextWidth,
            textWidth;

    @Override
    protected void onDetachedFromWindow() {
        textLayout = null;
        super.onDetachedFromWindow();
    }

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

    public WatermarkView(Context context, AttributeSet attrs) {
        this(context, attrs, android.R.attr.textViewStyle);
    }

    public WatermarkView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.WatermarkView);
        setColumn(array.getInt(R.styleable.WatermarkView_column, column));
        setRow(array.getInt(R.styleable.WatermarkView_row, row));
        decorationX = getDecorationFromArray(array, R.styleable.WatermarkView_decorationX, isFixColumns, DEF_DECORATION_X);
        decorationY = getDecorationFromArray(array, R.styleable.WatermarkView_decorationY, isFixRows, DEF_DECORATION_Y);
        offsetX = array.getDimension(R.styleable.WatermarkView_offsetX, -1);
        array.recycle();
        isCreated = true;
    }

    private float getDecorationFromArray(TypedArray array, @StyleableRes int id, boolean isFix, int def) {
        return array.hasValue(id) ? array.getDimension(id, 0) : BaseUtils.dp2px(isFix ? 1 : def);
    }

    @Override
    public void setRotation(float rotation) {
        this.rotation = rotation % 360;//-359~359
        if (rotation > 0) {
            this.rotation -= 360;//-359~0
        }
        float abs = Math.abs(rotation % 180);
        boolean b = (abs > 85 && abs < 95);
        if (isVertical != b) {
            isVertical = b;
            isTextSizeChanged = true;
        }
        postInvalidateImpl();
    }

    public void setDecoration(@FloatRange(from = 0) float decorationX, @FloatRange(from = 0) float decorationY) {
        setDecorationX(decorationX);
        setDecorationY(decorationY);
    }

    private void postInvalidateImpl() {
        if (isCreated) {
            postInvalidate();
        }
    }

    public float getDecorationX() {
        return decorationX;
    }

    public void setDecorationX(@FloatRange(from = 0) float decorationX) {
        this.decorationX = decorationX;
        postInvalidateImpl();
    }

    public float getDecorationY() {
        return decorationY;
    }

    public void setDecorationY(@FloatRange(from = 0) float decorationY) {
        this.decorationY = decorationY;
        postInvalidateImpl();
    }

    public void setMatrix(@IntRange(from = 0) int row, @IntRange(from = 0) int column) {
        setRow(row);
        setColumn(column);
    }

    public int getRow() {
        return row;
    }

    public void setRow(@IntRange(from = 0) int row) {
        this.row = row;
        isFixRows = row > 0;
        postInvalidateImpl();
    }

    public int getColumn() {
        return column;
    }

    public void setColumn(@IntRange(from = 0) int column) {
        this.column = column;
        isFixColumns = column > 0;
        postInvalidateImpl();
    }

    public float getMaxTextWidth() {
        return maxTextWidth;
    }

    public void setMaxTextWidth(float maxTextWidth) {
        if (this.maxTextWidth != maxTextWidth) {
            this.maxTextWidth = maxTextWidth;
            isTextSizeChanged = true;
        }
        postInvalidateImpl();
    }

    @Override
    protected void onDraw(Canvas canvas) {
        if (!TextUtils.isEmpty(getText())) {
            initTextLayout();
            if (textLayout != null) {
                float realColumnWith = columnWith + spacingX;//真实列宽
                float realRowHeight = rowHeight + spacingY;//真实行高
                canvas.translate((width - realColumnWith * realColumn) / 2f + correctX + spacingX / 2, (height - realRowHeight * realRow) / 2f + correctY + spacingY / 2);//居中绘制
                float offset = offsetX > -1 ? offsetX : realColumnWith / 2f;//计算偶数行所需偏移量
                int column = isFixColumns ? this.realColumn : this.realColumn + 1;
                int row = isFixRows ? this.realRow : this.realRow + 1;
                float addColumn = column + (offset != 0 ? (offset / realColumnWith + 1) : 0);
                for (int i = 0; i < row; i++) {//行
                    canvas.save();
                    boolean isEvenNum = i % 2 == 1;//是否偶数行
                    canvas.translate(isEvenNum ? -offset : 0f, realRowHeight * i);//平移至每行起点
                    for (int j = 0; j < (isEvenNum ? addColumn : column); j++) {//列 偶数行因偏移为美观需额外添加列
                        canvas.save();
                        canvas.translate(realColumnWith * j, 0);//平移至每列起点
                        canvas.rotate(rotation);//旋转坐标系
                        textLayout.draw(canvas);//绘制文字
                        canvas.restore();
                    }
                    canvas.restore();
                }
            }
        }
    }

    private void initTextLayout() {
        CharSequence source = getText();
        if (!TextUtils.isEmpty(source)) {
            if (!source.equals(lastText)) {
                lastText = source;
                isTextSizeChanged = true;
            }
            TextPaint paint = getPaint();

            if (isTextSizeChanged && paint != null) {
                paint.setColor(getCurrentTextColor());
                width = getWidth();
                height = getHeight();
                textWidth = paint.measureText(source.toString());
                if (maxTextWidth > 0 && textWidth > maxTextWidth) {
                    textWidth = maxTextWidth;
                }
                if (isVertical && textWidth > height) {
                    textWidth = height;
                } else if (!isVertical && textWidth > width) {
                    textWidth = width;
                }
                isTextSizeChanged = false;
                //1.需要分行的字符串
                //4.画笔对象
                //5.layout的宽度,字符串超出宽度时自动换行。
                //6.layout的对其方式,有ALIGN_CENTER, ALIGN_NORMAL, ALIGN_OPPOSITE 三种。
                //7.相对行间距,相对字体大小,1.5f表示行间距为1.5倍的字体高度。
                //8.在基础行距上添加多少 实际行间距等于这两者的和。
                //9.设置是否在字体的上升和下降之外包括额外的空间(避免在某些语言(例如阿拉伯语和卡纳达语)中进行剪切是必需的*)。 *默认值为{@code true}
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                    textLayout = StaticLayout.Builder
                            .obtain(source, 0, source.length(), paint, (int) textWidth)
                            .setAlignment(Layout.Alignment.ALIGN_CENTER)
                            .setLineSpacing(0f, 1f)
                            .build();
                } else {
                    textLayout = new StaticLayout(
                            source,
                            paint,
                            (int) textWidth,
                            Layout.Alignment.ALIGN_CENTER,
                            1f,
                            0f,
                            true
                    );
                }
            }
            if (textLayout != null) {
                double rotation = Math.abs(this.rotation * (Math.PI / 180));
                int textHeight = textLayout.getHeight();

                columnWith = (int) (Math.abs(textWidth * Math.cos(rotation)) + Math.abs(textHeight * Math.cos(Math.PI / 2 - rotation)));
                spacingX = decorationX;
                spacingY = decorationY;
                realRow = row;
                realColumn = column;
                if (isFixColumns) {
                    int w = width / column;
                    if (w < columnWith) {//固定列数,平均列宽小于文字绘制所需宽度,重新计算列数
                        realColumn = (int) (width / (columnWith + decorationX)) + 1;
                    } else {
                        spacingX = w - columnWith + decorationX;
                    }
                } else {
                    realColumn = (int) (width / (columnWith + decorationX)) + 1;
                }

                float offsetY = (float) Math.abs(textWidth * Math.sin(rotation));

                rowHeight = (int) (offsetY + Math.abs(textHeight * Math.cos(rotation)));

                if (isFixRows) {
                    int h = height / row;
                    if (h < rowHeight) {//固定列数,平均列宽小于文字绘制所需宽度,重新计算列数
                        realRow = (int) (height / (rowHeight + decorationY)) + 1;
                    } else {
                        spacingY = h - rowHeight + decorationY;
                    }
                } else {
                    realRow = (int) (height / (rowHeight + decorationY)) + 1;
                }

                boolean firstQuadrant = this.rotation < 0 && this.rotation >= -90;
                boolean deltaQuadrant = this.rotation == 0 || (this.rotation >= -360 && this.rotation < -270);

                int x = 0;
                //根据canvas旋转后文字所在象限计算矫正偏移量
                if (firstQuadrant) {
                    x = 1;
                    correctX = 0;
                    correctY = offsetY;
                } else if (deltaQuadrant) {
                    correctX = (float) (Math.abs(Math.sin(rotation) * textHeight));
                    correctY = 0;
                    x = 4;
                } else if (this.rotation < -90 && this.rotation >= -180) {//2
                    x = 2;
                    correctX = (float) Math.abs(textWidth * Math.sin(rotation - Math.PI / 2f));
                    correctY = rowHeight;
                } else {//3
                    x = 3;
                    correctX = columnWith;
                    correctY = rowHeight - offsetY;
                }
            }
        }
    }
}
arrts资源
    <declare-styleable name="WatermarkView">
        <attr name="column" format="integer" />
        <attr name="row" format="integer" />
        <attr name="decorationX" format="dimension" />
        <attr name="decorationY" format="dimension" />
    </declare-styleable>

请添加图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值