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

在这里插入图片描述

由于项目须要只有边框和文字渐变效果得按钮,尝试过用drawable文件设置,发现只能设置全背景渐变效果,也懒得用其余方式,就本身动手画了一个,画完以后就把全背竟的也给加上了,由于这个项目相似这种的按钮还很多,避免了挨个去建立drawable文件了。当我写完以后,微信就更新了8.0发现这个按钮跟微信8.0的状态按钮挺像,就起了这么一个标题java

自定义View相关的代码

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

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>

源码地址git

共同开发,共同进步,欢迎你们Start!!!github

相关文章
相关标签/搜索