安卓自定义View——实现类似微信状态按钮的文字与边框渐变

在这里插入图片描述

因为项目需要只有边框和文字渐变效果得按钮,尝试过用drawable文件设置,发现只能设置全背景渐变效果,也懒得用其他方式,就自己动手画了一个,画完之后就把全背竟的也给加上了,因为这个项目类似这种的按钮还不少,避免了挨个去创建drawable文件了。当我写完之后,微信就更新了8.0发现这个按钮跟微信8.0的状态按钮挺像,就起了这么一个标题

自定义View相关的代码

最开始画这个控件的时候,继承的是View,自己用画笔去绘制文字,发现居中一直有问题,算了一下午的文字居中发现没有任何屌用,后来小伙伴跟我说,那你就继承TextView,然后直接设置文字居中不就好了,当时感觉自己的智商被人摁在地上摩擦。。。。。

package com.ltb.myroundtextlibrary.widget;

import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.LinearGradient;
import android.graphics.Paint;
import android.graphics.RectF;
import android.graphics.Shader;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.Gravity;

import androidx.annotation.Nullable;

import com.ltb.myroundtextlibrary.R;
import com.ltb.myroundtextlibrary.utils.SizeUtils;

public class MyRoundTextView extends androidx.appcompat.widget.AppCompatTextView {

    //边框画笔
    private Paint mPaint;

    private Context mContext;

    private int mHeight;
    private int mWidth;
    //文字宽度
    private int txtWidth;
    //文字长度
    private int txtLength;

    private int strokeWidth = SizeUtils.dp2px(1);

    //起始颜色
    private int startColor;
    //结束颜色
    private int endColor;
    //文字
    private String txt = "";

    private String content1;
    private String content2;

    //判断是否是全渐变背景
    private boolean isFull = false;

    public MyRoundTextView(Context context) {
        super(context);
    }

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

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

    private void init(AttributeSet attrs) {
        mContext = getContext();

        startColor = mContext.getResources().getColor(R.color.colorForgetPwd);
        endColor = mContext.getResources().getColor(R.color.colorForgetPwd);

        //获取自定义参数
        TypedArray typedArray = mContext.obtainStyledAttributes(attrs, R.styleable.MyRoundTextView, 0, 0);
        //获取起始颜色
        startColor = typedArray.getColor(R.styleable.MyRoundTextView_m_gradient_color_start, startColor);
        //获取结束颜色
        endColor = typedArray.getColor(R.styleable.MyRoundTextView_m_gradient_color_end, endColor);
        //获取边框粗细
        strokeWidth = (int) typedArray.getDimension(R.styleable.MyRoundTextView_m_stroke_width, strokeWidth);
        //是否填满
        isFull = typedArray.getBoolean(R.styleable.MyRoundTextView_m_is_full, isFull);

        txt = getText().toString();
        typedArray.recycle();
        //配置边框画笔
        mPaint = new Paint();
        mPaint.setAntiAlias(true);
        mPaint.setStrokeWidth(strokeWidth);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        setMeasuredDimension(measureWidth(widthMeasureSpec), measureHeight(heightMeasureSpec));
        mWidth = measureWidth(widthMeasureSpec);
        mHeight = measureHeight(heightMeasureSpec);
    }

    @SuppressLint("DrawAllocation")
    @Override
    protected void onDraw(Canvas canvas) {
        //判断是否渐变铺满
        mPaint.setStyle(!isFull ? Paint.Style.STROKE : Paint.Style.FILL);

        //设置渐变参数
        LinearGradient linearGradient = new LinearGradient(
                strokeWidth,
                strokeWidth,
                mWidth,
                mHeight,
                endColor,
                startColor,
                Shader.TileMode.CLAMP);

        mPaint.setShader(linearGradient);

        RectF r2 = new RectF();
        r2.set(strokeWidth * 2,
                strokeWidth * 2,
                mWidth - strokeWidth * 4,
                mHeight - strokeWidth * 4);

        //绘制圆角
        canvas.drawRoundRect(r2, SizeUtils.dp2px(20), SizeUtils.dp2px(20), mPaint);

        super.onDraw(canvas);

        if (txtLength == 0) {
            if (!txtIsEmpty(txt)) {
                setTextViewStyles();
            }
        }

    }

    private boolean txtIsEmpty(String txt) {
        return TextUtils.isEmpty(txt);
    }

    /**
     * 设置文字渐变
     */
    private void setTextViewStyles() {
        txtWidth = (int) getPaint().measureText(txt);
        txtLength = txt.length();

        if (getGravity() != Gravity.CENTER) {
            setGravity(Gravity.CENTER);
        }

        //当全渐变背景时候 默认设置文字颜色为白色 不设置成为渐变
        if (!isFull) {

            int x0 = mWidth / 2 - txtWidth / 2;
            int y0 = getBottom();

            int x1 = mWidth / 2 + txtWidth / 2;

            LinearGradient txtLinearGradient = new LinearGradient(
                    x0,
                    y0,
                    x1,
                    y0,
                    endColor,
                    startColor,
                    Shader.TileMode.CLAMP
            );
            getPaint().setShader(txtLinearGradient);
        } else {
            getPaint().setShader(null);
            setTextColor(Color.WHITE);
            getPaint().setColor(Color.WHITE);
        }
        if (!TextUtils.isEmpty(content1) || !TextUtils.isEmpty(content2)) {
            txt = isFull ? content1 : content2;
        }
        setText(txt);
        invalidate();
    }

    /**
     * 设置状态改变前后的文字信息
     *
     * @param content1 点击前展示的文字
     * @param content2 点击后展示的文字
     */
    public void setContent(String content1, String content2) {
        this.content1 = content1;
        this.content2 = content2;
    }

    /**
     * 设置是点击后的状态还是点击前的状态
     *
     * @param full 是否充满背景
     */
    public void setFull(boolean full) {
        if (TextUtils.isEmpty(content1) || TextUtils.isEmpty(content2)) {
            throw new NullPointerException("content1 and content2 must not be null!!");
        }
        isFull = full;
        txtLength = 0;
        invalidate();
    }

    public boolean isFull() {
        return isFull;
    }

    /**
     * 根据模式计算高度
     *
     * @param heightMeasureSpec
     */
    private int measureHeight(int heightMeasureSpec) {
        //获取模式
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        //获取高度
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
        int height = 0;
        switch (heightMode) {
            case MeasureSpec.EXACTLY://固定值或者match_content
                height = heightSize + strokeWidth * 2;
                break;
        }
        return height;
    }

    /**
     * 根据模式计算宽度
     *
     * @param widthMeasureSpec
     */
    private int measureWidth(int widthMeasureSpec) {
        //获取模式
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        //获取宽度
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int width = 0;
        switch (widthMode) {
            case MeasureSpec.EXACTLY://固定值或者match_content
                width = widthSize + strokeWidth * 2;
                break;
        }
        return width;
    }

}

相关辅助类代码——SizeUtils

package com.ltb.myroundtextlibrary.utils;

import android.content.res.Resources;
import android.util.DisplayMetrics;
import android.util.TypedValue;
import android.view.View;
import android.view.ViewGroup;

public class SizeUtils {
    private SizeUtils() {
        throw new UnsupportedOperationException("u can't instantiate me...");
    }

    /**
     * Value of dp to value of px.
     *
     * @param dpValue The value of dp.
     * @return value of px
     */
    public static int dp2px(final float dpValue) {
        final float scale = Resources.getSystem().getDisplayMetrics().density;
        return (int) (dpValue * scale + 0.5f);
    }

    /**
     * Value of px to value of dp.
     *
     * @param pxValue The value of px.
     * @return value of dp
     */
    public static int px2dp(final float pxValue) {
        final float scale = Resources.getSystem().getDisplayMetrics().density;
        return (int) (pxValue / scale + 0.5f);
    }

    /**
     * Value of sp to value of px.
     *
     * @param spValue The value of sp.
     * @return value of px
     */
    public static int sp2px(final float spValue) {
        final float fontScale = Resources.getSystem().getDisplayMetrics().scaledDensity;
        return (int) (spValue * fontScale + 0.5f);
    }

    /**
     * Value of px to value of sp.
     *
     * @param pxValue The value of px.
     * @return value of sp
     */
    public static int px2sp(final float pxValue) {
        final float fontScale = Resources.getSystem().getDisplayMetrics().scaledDensity;
        return (int) (pxValue / fontScale + 0.5f);
    }

    /**
     * Converts an unpacked complex data value holding a dimension to its final floating
     * point value. The two parameters <var>unit</var> and <var>value</var>
     * are as in {@link TypedValue#TYPE_DIMENSION}.
     *
     * @param value The value to apply the unit to.
     * @param unit  The unit to convert from.
     * @return The complex floating point value multiplied by the appropriate
     * metrics depending on its unit.
     */
    public static float applyDimension(final float value, final int unit) {
        DisplayMetrics metrics = Resources.getSystem().getDisplayMetrics();
        switch (unit) {
            case TypedValue.COMPLEX_UNIT_PX:
                return value;
            case TypedValue.COMPLEX_UNIT_DIP:
                return value * metrics.density;
            case TypedValue.COMPLEX_UNIT_SP:
                return value * metrics.scaledDensity;
            case TypedValue.COMPLEX_UNIT_PT:
                return value * metrics.xdpi * (1.0f / 72);
            case TypedValue.COMPLEX_UNIT_IN:
                return value * metrics.xdpi;
            case TypedValue.COMPLEX_UNIT_MM:
                return value * metrics.xdpi * (1.0f / 25.4f);
        }
        return 0;
    }

    /**
     * Force get the size of view.
     * <p>e.g.</p>
     * <pre>
     * SizeUtils.forceGetViewSize(view, new SizeUtils.OnGetSizeListener() {
     *     Override
     *     public void onGetSize(final View view) {
     *         view.getWidth();
     *     }
     * });
     * </pre>
     *
     * @param view     The view.
     * @param listener The get size listener.
     */
    public static void forceGetViewSize(final View view, final OnGetSizeListener listener) {
        view.post(new Runnable() {
            @Override
            public void run() {
                if (listener != null) {
                    listener.onGetSize(view);
                }
            }
        });
    }

    /**
     * Return the width of view.
     *
     * @param view The view.
     * @return the width of view
     */
    public static int getMeasuredWidth(final View view) {
        return measureView(view)[0];
    }

    /**
     * Return the height of view.
     *
     * @param view The view.
     * @return the height of view
     */
    public static int getMeasuredHeight(final View view) {
        return measureView(view)[1];
    }

    /**
     * Measure the view.
     *
     * @param view The view.
     * @return arr[0]: view's width, arr[1]: view's height
     */
    public static int[] measureView(final View view) {
        ViewGroup.LayoutParams lp = view.getLayoutParams();
        if (lp == null) {
            lp = new ViewGroup.LayoutParams(
                    ViewGroup.LayoutParams.MATCH_PARENT,
                    ViewGroup.LayoutParams.WRAP_CONTENT
            );
        }
        int widthSpec = ViewGroup.getChildMeasureSpec(0, 0, lp.width);
        int lpHeight = lp.height;
        int heightSpec;
        if (lpHeight > 0) {
            heightSpec = View.MeasureSpec.makeMeasureSpec(lpHeight, View.MeasureSpec.EXACTLY);
        } else {
            heightSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
        }
        view.measure(widthSpec, heightSpec);
        return new int[]{view.getMeasuredWidth(), view.getMeasuredHeight()};
    }

    ///
    // interface
    ///

    public interface OnGetSizeListener {
        void onGetSize(View view);
    }
}

自定义属性代码

<declare-styleable name="MyRoundTextView">
        <!--起始颜色-->
        <attr name="m_gradient_color_start" format="color|reference" />
        <!--结束颜色-->
        <attr name="m_gradient_color_end" format="color|reference" />
        <!--线条宽度-->
        <attr name="m_stroke_width" format="integer|dimension" />
        <!--是否渐变背景充满-->
        <attr name="m_is_full" format="boolean" />
    </declare-styleable>

Demo的布局代码

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <com.ltb.myroundtextlibrary.widget.MyRoundTextView
        android:id="@+id/btn1"
        android:layout_width="300dp"
        android:layout_height="40dp"
        android:layout_centerInParent="true"
        android:layout_gravity="center"
        android:layout_marginLeft="30dp"
        android:layout_marginTop="30dp"
        android:layout_marginRight="30dp"
        android:text="注册"
        app:m_gradient_color_end="@color/colorEnd"
        app:m_gradient_color_start="@color/colorStart" />

    <com.ltb.myroundtextlibrary.widget.MyRoundTextView
        android:id="@+id/btn2"
        android:layout_width="300dp"
        android:layout_height="40dp"
        android:layout_centerInParent="true"
        android:layout_gravity="center"
        android:layout_marginLeft="30dp"
        android:layout_marginTop="20dp"
        android:layout_marginRight="30dp"
        android:gravity="center"
        android:text="确认"
        app:m_gradient_color_end="@color/colorEnd"
        app:m_gradient_color_start="@color/colorStart"
        app:m_is_full="true" />

</LinearLayout>

源码地址

共同开发,共同进步,欢迎大家Start!!!

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值