Android自定义控件 -- 带边框的TextView

1. 前言

原来使用带边框的 TextView 时一般都是用XML定义来完成,在drawable目录中定义如下所示的xml文件:

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <!-- 点击状态下按钮背景样式 -->
    <item android:state_pressed="true">
        <shape android:shape="rectangle">
            <corners android:radius="2dp"/>
            <solid android:color="@android:color/transparent"/>
            <stroke android:width="1dp"
                    android:color="#ff48beab"/>
        </shape>
    </item>
    <!-- 正常点击状态下按钮背景样式 -->
    <item android:state_pressed="false">
        <shape android:shape="rectangle">
            <corners android:radius="2dp"/>
            <solid android:color="@android:color/transparent"/>
            <stroke android:width="1dp"
                    android:color="#ff48baab"/>
        </shape>
    </item>
</selector>

这样可以实现圆角边框,但颜色是固定的,如果在不同的位置放置不同的TextView(比如多种颜色的按钮),那么就要定义多个颜色不同的XML文件。

2. 自定义带边框的TextView

最近在做项目的时候遇到多种颜色标签需求,如果还是按照上面的做法,那么需要多套XML文件配合,这样很麻烦。所以自定义了一个控件,让边框颜色在使用时指定。如图:
在这里插入图片描述
这个控件是继承自TextView的,只是在onDraw方法中画了一个边框,并设计了几个自定义属性用来更灵活地控制控件。

  • 步骤1:自定义属性
    在values中的attr.xml文件中,自定义属性如下:
<!--带边框的TextView-->
<declare-styleable name="BorderLabelTextView">
       <!--边框的宽度,默认为1dp-->
       <attr name="strokeWidth" format="dimension" />
       <!--圆角半径,默认为2dp-->
       <attr name="cornerRadius" format="dimension" />
       <!--边框颜色,默认是没有边框即颜色为Color.TRANSPARENT-->
       <attr name="strokeColor" format="color" />
       <!--边框是否跟随文字颜色,默认是true-->
       <attr name="followTextColor" format="boolean" />
       <!--实心或空心,默认空心-->
       <attr name="isSolid" format="boolean" />
</declare-styleable>
  • 步骤2: 自定义控件(BorderLabelTextView)
    继承自TextView,并在onDraw方法中画一个边框。
package com.tcmain.djim.view;

import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RectF;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.TypedValue;
import android.widget.TextView;

import com.risen.tclibrary.R;

/**
 * Created by sgll on 2018/7/27.
 * 用于作为标签显示的TextView
 * 边框默认与文字颜色一致
 */
@SuppressLint("AppCompatCustomView")
public class BorderLabelTextView extends TextView{
    // 默认边框宽度, 1dp
    public static final float DEFAULT_STROKE_WIDTH = 1.0f;
    // 默认圆角半径, 2dp
    public static final float DEFAULT_CORNER_RADIUS = 2.0f;
    // 默认左右内边距
    public static final float DEFAULT_LR_PADDING = 6f;
    // 默认上下内边距
    public static final float DEFAULT_TB_PADDING = 2f;

    // 边框线宽
    private int strokeWidth;
    // 边框颜色
    private int strokeColor;
    // 圆角半径
    private int cornerRadius;
    // 边框颜色是否跟随文字颜色
    private boolean mFollowTextColor;
    //实心或空心,默认空心
    private boolean isSolid;

    // 画边框所使用画笔对象
    private Paint mPaint = new Paint();
    // 画边框要使用的矩形
    private RectF mRectF;
    private DisplayMetrics displayMetrics;

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

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

    public BorderLabelTextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);

        // 将DIP单位默认值转为PX
        displayMetrics = context.getResources().getDisplayMetrics();
        strokeWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, DEFAULT_STROKE_WIDTH, displayMetrics);
        cornerRadius = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, DEFAULT_CORNER_RADIUS, displayMetrics);

        // 读取属性值
        TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.BorderLabelTextView);
        strokeWidth = ta.getDimensionPixelSize(R.styleable.BorderLabelTextView_strokeWidth, strokeWidth);
        cornerRadius = ta.getDimensionPixelSize(R.styleable.BorderLabelTextView_cornerRadius, cornerRadius);
        strokeColor = ta.getColor(R.styleable.BorderLabelTextView_strokeColor, Color.TRANSPARENT);
        mFollowTextColor = ta.getBoolean(R.styleable.BorderLabelTextView_followTextColor, false);
        isSolid = ta.getBoolean(R.styleable.BorderLabelTextView_isSolid, false);
        ta.recycle();

        mRectF = new RectF();

        // 边框默认颜色与文字颜色一致
//        if(strokeColor == Color.TRANSPARENT){
//            strokeColor = getCurrentTextColor();
//        }

        // 如果使用时没有设置内边距, 设置默认边距
        int paddingLeft = getPaddingLeft() == 0 ? (int) TypedValue.applyDimension(
                TypedValue.COMPLEX_UNIT_DIP, DEFAULT_LR_PADDING, displayMetrics) : getPaddingLeft();
        int paddingRight = getPaddingRight() == 0 ? (int) TypedValue.applyDimension(
                TypedValue.COMPLEX_UNIT_DIP, DEFAULT_LR_PADDING, displayMetrics) : getPaddingRight();
        int paddingTop = getPaddingTop() == 0 ? (int) TypedValue.applyDimension(
                TypedValue.COMPLEX_UNIT_DIP, DEFAULT_TB_PADDING, displayMetrics) : getPaddingTop();
        int paddingBottom = getPaddingBottom() == 0 ? (int) TypedValue.applyDimension(
                TypedValue.COMPLEX_UNIT_DIP, DEFAULT_TB_PADDING, displayMetrics) : getPaddingBottom();
        setPadding(paddingLeft, paddingTop, paddingRight, paddingBottom);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        if(isSolid){
            // 实心效果
            mPaint.setStyle(Paint.Style.FILL);
        }else{
            // 空心效果
            mPaint.setStyle(Paint.Style.STROKE);
        }

        // 设置画笔为无锯齿
        mPaint.setAntiAlias(true);
        // 线宽
        mPaint.setStrokeWidth(strokeWidth);

        // 设置边框线的颜色, 如果声明为边框跟随文字颜色且当前边框颜色与文字颜色不同时重新设置边框颜色
        if(mFollowTextColor && strokeColor != getCurrentTextColor()){
            strokeColor = getCurrentTextColor();
        }
        mPaint.setColor(strokeColor);

        // 画空心圆角矩形
        mRectF.left = mRectF.top = 0.5f * strokeWidth;
        mRectF.right = getMeasuredWidth() - strokeWidth;
        mRectF.bottom = getMeasuredHeight() - strokeWidth;
        canvas.drawRoundRect(mRectF, cornerRadius, cornerRadius, mPaint);

        super.onDraw(canvas);
        //drawRoundRect放在 super.onDraw(canvas)前面
        //如果drawRoundRect放在 super.onDraw(canvas)后面,父类先画TextView,然后再画子类矩形,
        //如果画的是实心矩形,则会遮盖父类的text字体
    }

    /**
     * 设置边框线宽度
     * @param size
     */
    public void setStrokeWidth(int size){
        strokeWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, size, displayMetrics);
        invalidate();
    }

    /**
     * 设置边框颜色
     * @param color
     */
    public void setStrokeColor(int color){
        strokeColor = color;
        invalidate();
    }

    /**
     * 设置圆角半径
     * @param radius
     */
    public void setCornerRadius(int radius){
        cornerRadius = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, radius, displayMetrics);
        invalidate();
    }

    /**
     * 设置边框颜色是否跟随文字颜色
     * @param isFollow
     */
    public void setFollowTextColor(boolean isFollow){
        mFollowTextColor = isFollow;
        invalidate();
    }

    /**
     * 设置实心或空心,默认空心
     * @param solid
     */
    public void setSolid(boolean solid){
        isSolid = solid;
        invalidate();
    }

}

注意:

  • 画边框时使用的RectF尺寸时,如果边框宽度较宽,由于 Paint 笔触是在边框中线为准,因此如果左上角指定为(0,0)话,会有一半边框宽度的线是画在不见区域的。这里指定左上角坐标为 0.5f *
    strokeWidth(即半个边框宽度)即可,右下角也需要作同样的考虑。
  • 在 onDraw 方法中 ,drawRoundRect 放在 super.onDraw(canvas)前面。如果drawRoundRect放在
    super.onDraw(canvas)后面,父类先画TextView,然后再画子类矩形,此时如果是画实心矩形,则会遮盖父类的text字体。
  • 步骤3: 在布局中使用如下:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:brlabel="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="@drawable/selector_gray_list"
    android:gravity="center_vertical"
    android:orientation="horizontal"
    android:padding="@dimen/margin_15">

    <com.tcmain.djim.view.BorderLabelTextView
        android:id="@+id/textview_1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:paddingLeft="10dp"
        android:paddingRight="10dp"
        android:paddingTop="1dp"
        android:textColor="@color/white"
        android:textSize="14sp"
        brlabel:strokeColor="@color/red01"
       android:text="学军" />

    <com.tcmain.djim.view.BorderLabelTextView
        android:id="@+id/textview_2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:paddingLeft="10dp"
        android:paddingRight="10dp"
        android:paddingTop="1dp"
        android:textColor="@color/white"
        android:textSize="14sp"
        brlabel:isSolid="true"
        brlabel:strokeColor="@color/red01"
        android:text="14" />

</LinearLayout>
3. 作为按钮使用

在某些时候,我们可能会需要一些中间镂空的按钮, BorderLabelTextView 也可以用在这种情况下。

  • 步骤1:在 res 目录中新建一个 colors 文件夹,在其中创建一个xml文件(button_text_color.xml),内容如下:
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:color="@color/secondary_text" android:state_pressed="true"/>
    <item android:color="@color/hint_text" android:state_enabled="false"/>
    <item android:color="@color/primary_text"/>
</selector>
  • 步骤2:在布局中使用
    其实是为按钮在不同状态下指定不同的颜色,以响应点击或禁用操作,增加用户体验。
<com.witmoon.eab.widget.BorderTextView
    android:id="@+id/retrieve_check_code_again"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:paddingBottom="6dp"
    android:enabled="false"
    android:textColor="@color/button_text_color"
    android:paddingTop="6dp"
    android:text="重新发送"/>

如果设置了边框是跟随文字颜色而改变的,那么在被点击或者禁用时TextView会重新绘制,边框也随之改变颜色。

相关文章
Android自定义控件 – 带有边框的RelativeLayout、LinearLayout

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值