1、背景

我们的项目的设计师喜欢用圆角矩形背景作为设计元素,而且颜色、样式各不一样导致项目工程里面定义了大量的xml文件,为了消除这一现象,我想到自定义控件解决这个问题。

Android圆角矩形图标 安卓圆角矩形_圆角矩形

图1、项目中使用大量的xml定义圆角矩形

2、看看效果

先看效果 

Android圆角矩形图标 安卓圆角矩形_android_02

       图2自定义圆角矩形控件的效果图

看看xml都怎么写的

<?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"
    android:id="@+id/llfffff"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@drawable/ic_bg"
    android:orientation="vertical" >

    <com.example.tttttttt.ShapeCornerBgView
        android:id="@+id/ShapeCornerBgView"
        android:layout_width="100dp"
        android:layout_height="30dp"
        android:layout_marginLeft="20dp"
        android:text="案例1111"
        android:textColor="#ffffff"
        app:appRadius="10dp" />

    <com.example.tttttttt.ShapeCornerBgView
        android:layout_width="100dp"
        android:layout_height="30dp"
        android:layout_marginLeft="20dp"
        android:layout_marginTop="10dp"
        android:text="案例2222"
        android:textColor="#ffffff"
        app:appBgColor="#dd4a4a"
        app:appBorder="true"
        app:appBorderColor="#00ffff"
        app:appBorderWidth="3dp"
        app:appRadius="10dp" />

    <com.example.tttttttt.ShapeCornerBgView
        android:layout_width="100dp"
        android:layout_height="30dp"
        android:layout_marginLeft="20dp"
        android:layout_marginTop="10dp"
        android:text="案例3333"
        android:textColor="#dd4a4a"
        app:appBorder="true"
        app:appBorderColor="#00ffff"
        app:appBorderWidth="3dp"
        app:appRadius="15dp"
        app:appTopLeftCorner="false"
        app:appTopRightCorner="false" />

    <com.example.tttttttt.ShapeCornerBgView
        android:layout_width="100dp"
        android:layout_height="30dp"
        android:layout_marginLeft="20dp"
        android:layout_marginTop="10dp"
        android:text="案例3333"
        android:textColor="#dd4a4a"
        app:appBorder="true"
        app:appBorderColor="#ff00ff"
        app:appBorderWidth="3dp"
        app:appRadius="15dp" />

</LinearLayout>




怎么样简单吧,使用的时候只需要定义几个属性就行了,好了下面讲解怎样定义这样的控件

3 确定需要的属性

首先确定需要的变量,是否有边框,线条粗细,背景颜色,圆角度数,左上角是否有圆角...

我们在attr里定义属性

<!-- 圆角矩形TextView -->
    <declare-styleable name="ShapeCornerBgView">
        <attr name="appBorder" format="boolean" />
        <!-- 是否有边框 -->
        <attr name="appBorderWidth" format="dimension" />
        <!-- 边框线条的粗细 -->
        <attr name="appBorderColor" format="color" />
        <!-- 边框线条的颜色 -->
        <attr name="appBgColor" format="color" />
        <!-- 背景的颜色 -->
        <attr name="appRadius" format="dimension" />
        <!-- 圆角度数 -->
        <attr name="appTopLeftCorner" format="boolean" />
        <!-- 是否有圆角,默认,真 -->
        <attr name="appBottomLeftCorner" format="boolean" />
        <!-- 是否有圆角,默认,真 -->
        <attr name="appTopRightCorner" format="boolean" />
        <!-- 是否有圆角,默认,真 -->
        <attr name="appBottomRightCorner" format="boolean" />
        <!-- 是否有圆角,默认,真 -->
    </declare-styleable>

4 定义变量

int borderWidth = 1;// 默认1dimpx
	boolean isHasBorder = false;

	int borderColor;// 线条的颜色,默认与字的颜色相同
	int bgColor;// 背景的颜色,默认是透明的
	int mRadius = 3;// 默认3

	private RectF rectf = new RectF();// 方角

	// 四个角落是否是全是圆角
	boolean isTopLeftCorner = true;
	boolean isBottomLeftCorner = true;
	boolean isTopRightCorner = true;
	boolean isBottomRightCorner = true;


5 让变量同属性对应起来

TypedArray mTypedArray = context.obtainStyledAttributes(attrs, R.styleable.ShapeCornerBgView);
		isHasBorder = mTypedArray.getBoolean(R.styleable.ShapeCornerBgView_appBorder, isHasBorder);// 默认无边框
		borderWidth = mTypedArray.getDimensionPixelSize(R.styleable.ShapeCornerBgView_appBorderWidth, borderWidth);
		mRadius = mTypedArray.getDimensionPixelSize(R.styleable.ShapeCornerBgView_appRadius, mRadius);

		borderColor = mTypedArray.getColor(R.styleable.ShapeCornerBgView_appBorderColor, borderColor);

		bgColor = isHasBorder ? Color.TRANSPARENT : Color.RED;

		bgColor = mTypedArray.getColor(R.styleable.ShapeCornerBgView_appBgColor, bgColor);
		// 四个角落是否全是圆角,默认全是真的
		isTopLeftCorner = mTypedArray.getBoolean(R.styleable.ShapeCornerBgView_appTopLeftCorner, isTopLeftCorner);
		isBottomLeftCorner = mTypedArray.getBoolean(R.styleable.ShapeCornerBgView_appBottomLeftCorner, isBottomLeftCorner);
		isTopRightCorner = mTypedArray.getBoolean(R.styleable.ShapeCornerBgView_appTopRightCorner, isTopRightCorner);
		isBottomRightCorner = mTypedArray.getBoolean(R.styleable.ShapeCornerBgView_appBottomRightCorner, isBottomRightCorner);
		mTypedArray.recycle();

6 绘制 

在控件的onDraw里面绘制,用的canvas.drawRoundRect 方法,canvas.drawRect方法(主要是填充有些地方不需要圆角的地方)

A 绘制背景

if (bgColor != Color.TRANSPARENT) {// 透明就不用画了
			paint.setColor(bgColor); // 设置画笔颜色
			paint.setStyle(Paint.Style.FILL);
			rectf.left = 0; // 左边
			rectf.top = 0; // 上边
			rectf.right = width; // 右边
			rectf.bottom = height; // 下边
			//有边框的时候较正一下
			if(isHasBorder)
				changeRectF(rectf);			
			canvas.drawRoundRect(rectf, mRadius, mRadius, paint); // 绘制圆角矩形
 fillCorner(canvas);}






B 绘制背景

// 有边框
		if (isHasBorder) {
			paint.setColor(borderColor); // 设置画笔颜色
			paint.setStyle(Paint.Style.STROKE);
			paint.setStrokeWidth(borderWidth);
			// 画圆角
			if (isTopLeftCorner && isTopRightCorner && isBottomLeftCorner && isBottomRightCorner) {
				rectf.left = 0; // 左边
				rectf.top = 0; // 上边
				rectf.right = width; // 右边
				rectf.bottom = height; // 下边
				changeRectF(rectf);
				canvas.drawRoundRect(rectf, mRadius, mRadius, paint); // 绘制圆角矩形
			} else {// 画路径
				Path path = getWantPath();
				canvas.drawPath(path, paint);
			}
		}



7 其它细节详情全部代码,以下是全部代码


package com.example.tttttttt;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.view.Gravity;
import android.widget.TextView;

/**
 * 圆角背景控件
 */
public class ShapeCornerBgView extends TextView {

	public int getDimen720Px(Context context, int dimen) {
		float dp = dimen * 1080f / 720 / 3;
		return dip2px(context, dp);
	}

	/**
	 * 根据手机的分辨率从 dp 的单位 转成为 px(像素)
	 */
	public static int dip2px(Context context, float dpValue) {
		final float scale = context.getResources().getDisplayMetrics().density;
		return (int) (dpValue * scale + 0.5f);
	}

	Paint paint;
	int borderWidth = 1;// 默认1dimpx
	boolean isHasBorder = false;

	int borderColor;// 线条的颜色,默认与字的颜色相同
	int bgColor;// 背景的颜色,默认是透明的
	int mRadius = 3;// 默认3

	private RectF rectf = new RectF();// 方角

	// 四个角落是否是全是圆角
	boolean isTopLeftCorner = true;
	boolean isBottomLeftCorner = true;
	boolean isTopRightCorner = true;
	boolean isBottomRightCorner = true;

	public ShapeCornerBgView(Context context, AttributeSet attrs) {
		super(context, attrs);

		borderWidth = dip2px(context, borderWidth);
		mRadius = dip2px(context, mRadius);
		borderColor = getCurrentTextColor();

		TypedArray mTypedArray = context.obtainStyledAttributes(attrs, R.styleable.ShapeCornerBgView);
		isHasBorder = mTypedArray.getBoolean(R.styleable.ShapeCornerBgView_appBorder, isHasBorder);// 默认无边框
		borderWidth = mTypedArray.getDimensionPixelSize(R.styleable.ShapeCornerBgView_appBorderWidth, borderWidth);
		mRadius = mTypedArray.getDimensionPixelSize(R.styleable.ShapeCornerBgView_appRadius, mRadius);

		borderColor = mTypedArray.getColor(R.styleable.ShapeCornerBgView_appBorderColor, borderColor);

		bgColor = isHasBorder ? Color.TRANSPARENT : Color.RED;

		bgColor = mTypedArray.getColor(R.styleable.ShapeCornerBgView_appBgColor, bgColor);
		// 四个角落是否全是圆角,默认全是真的
		isTopLeftCorner = mTypedArray.getBoolean(R.styleable.ShapeCornerBgView_appTopLeftCorner, isTopLeftCorner);
		isBottomLeftCorner = mTypedArray.getBoolean(R.styleable.ShapeCornerBgView_appBottomLeftCorner, isBottomLeftCorner);
		isTopRightCorner = mTypedArray.getBoolean(R.styleable.ShapeCornerBgView_appTopRightCorner, isTopRightCorner);
		isBottomRightCorner = mTypedArray.getBoolean(R.styleable.ShapeCornerBgView_appBottomRightCorner, isBottomRightCorner);
		mTypedArray.recycle();

		paint = new Paint();
		paint.setAntiAlias(true); // 设置画笔为无锯齿
		this.setGravity(Gravity.CENTER);// 全部居中显示
	}

	// 修正画圆角矩形的位置
	private void changeRectF(RectF rectF) {
		int half = borderWidth / 2;
		rectF.top += half;
		rectF.left += half;
		rectF.bottom -= half;
		rectF.right -= half;
	}

	protected void onDraw(Canvas canvas) {
		if (width == 0) // 没初始化完成不需要绘制
			return;
		// 先画背景
		if (bgColor != Color.TRANSPARENT) {// 透明就不用画了
			paint.setColor(bgColor); // 设置画笔颜色
			paint.setStyle(Paint.Style.FILL);
			rectf.left = 0; // 左边
			rectf.top = 0; // 上边
			rectf.right = width; // 右边
			rectf.bottom = height; // 下边
			//有边框的时候较正一下
			if(isHasBorder)
				changeRectF(rectf);
			canvas.drawRoundRect(rectf, mRadius, mRadius, paint); // 绘制圆角矩形
			fillCorner(canvas);
		}
		// 有边框
		if (isHasBorder) {
			paint.setColor(borderColor); // 设置画笔颜色
			paint.setStyle(Paint.Style.STROKE);
			paint.setStrokeWidth(borderWidth);
			// 画圆角
			if (isTopLeftCorner && isTopRightCorner && isBottomLeftCorner && isBottomRightCorner) {
				rectf.left = 0; // 左边
				rectf.top = 0; // 上边
				rectf.right = width; // 右边
				rectf.bottom = height; // 下边
				changeRectF(rectf);
				canvas.drawRoundRect(rectf, mRadius, mRadius, paint); // 绘制圆角矩形
			} else {// 画路径
				Path path = getWantPath();
				canvas.drawPath(path, paint);
			}
		}
		super.onDraw(canvas);
	}

	@Override
	protected void onSizeChanged(int w, int h, int oldw, int oldh) {
		super.onSizeChanged(w, h, oldw, oldh);
		width = w;
		height = h;
	}

	private void fillCorner(Canvas canvas) {
		int half = 0;
		// 拿背景去填充,不需要圆角的地方
		// 左上角
		if (isTopLeftCorner == false) {
			rectf.left = half; // 左边
			rectf.top = half; // 上边
			rectf.right = mRadius;
			rectf.bottom = mRadius;
			canvas.drawRect(rectf, paint);
		}
		// 左下角
		if (isBottomLeftCorner == false) {
			rectf.right = mRadius;
			rectf.top = height - mRadius;
			rectf.left = half;
			rectf.bottom = height - half;
			canvas.drawRect(rectf, paint);
		}
		// 右上角
		if (isTopRightCorner == false) {
			rectf.right = width - half;
			rectf.left = width - mRadius; // 左边
			rectf.top = half; // 上边
			rectf.bottom = mRadius; // 下边
			canvas.drawRect(rectf, paint);
		}
		// 右下角
		if (isBottomRightCorner == false) {
			rectf.left = width - mRadius; // 左边
			rectf.top = height - mRadius; // 上边
			rectf.right = width - half; // 右边
			rectf.bottom = height - half; // 下边
			canvas.drawRect(rectf, paint);
		}
	}

	private Path getWantPath() {
		Path path = new Path();
		float half = borderWidth / 2;
		path.moveTo(half, mRadius + half);

		if (isTopLeftCorner) {// 左上角是圆角
			path.arcTo(new RectF(half, half, 2 * mRadius + half, 2 * mRadius + half), 180, 90);
		} else {
			path.lineTo(half, half);
			path.lineTo(mRadius, half);
		}
		path.lineTo(width - mRadius - half, half);
		if (isTopRightCorner) {
			path.arcTo(new RectF(width - 2 * mRadius - half, half, width - half, 2 * mRadius + half), -90, 90);
		} else {
			path.lineTo(width - half, half);
			path.lineTo(width - half, mRadius + half);
		}
		path.lineTo(width - half, height - mRadius - half);
		if (isBottomRightCorner) {
			path.arcTo(new RectF(width - 2 * mRadius - half, height - 2 * mRadius - half, width - half, height - half), 0, 90);
		} else {
			path.lineTo(width - half, height - half);
			path.lineTo(width - mRadius - half, height - half);
		}
		path.lineTo(mRadius + half, height - half);
		if (isBottomLeftCorner) {
			path.arcTo(new RectF(half, height - 2 * mRadius - half, 2 * mRadius + half, height - half), 90, 90);
		} else {
			path.lineTo(half, height - half);
			path.lineTo(half, height - mRadius - half);
		}
		path.close();
		return path;
	}

	public void setBgColor(int bgColor) {
		this.bgColor = bgColor;
		invalidate();
	}

	int width, height;
}



8 后记

程序开发就是一个不断提高效率的过程,有时一些不合理的东西用法在使用过程中逐渐暴露出问题来,就需要用新的方法去改进,提高生产效率,让开发变得十分easy,而不是一直重复的体力劳动,做一个不是码农的工程师。^_+     =.=

2016年12月22日17时于深圳



9 后来更新

A 添加禁用背景色,禁用文本颜色

在后来的需求中又有禁用按钮的不同样式的需求,那么就需要对这个控件进行改造

在attr文件里面添加两个属性

<attr name="appEnableFalseBgColor" format="color"/>
        <!-- 禁用时的背景颜色 -->
        <attr name="appEnableFalseTextColor" format="color"/>


在代码里面定义两个变量

int mColorBgEnableFalse;
    int mColorTextEnableFalse;



关联起来后onDraw里面更改颜色

// 先画背景
        if (bgColor != Color.TRANSPARENT) {// 透明就不用画了
            int color = bgColor;
            if (isEnabled() == false) {
                color = mColorBgEnableFalse;
            }


重写setEnable方法

// 设置是否可用更改颜色
    @Override
    public void setEnabled(boolean enabled) {
        super.setEnabled(enabled);
        setTextColor(enabled ? mColorText : mColorTextEnableFalse);
        invalidate();
    }



B 大的改造

后来通过阅读安卓开发书籍了解到ShapeDrawable 可以绘制圆角矩形,而且十分方便,示例如下


ShapeDrawable shapeDrawable = new ShapeDrawable(new RoundRectShape(ffVar, null, null));
            Paint paint = shapeDrawable.getPaint();
            paint.setColor(color);
            shapeDrawable.setBounds(rect);
            shapeDrawable.draw(canvas);



attr.xml

<!-- 背景颜色 -->
<attr name="appBgColor" format="color"/>
<!-- 边角幅度 -->
<attr name="appRadius" format="dimension"/>

<!--  圆角矩形TextView-->
<declare-styleable name="ShapeCornerBgView">
    <attr name="appBorder" format="boolean"/>
    <!-- 是否有边框 -->
    <attr name="appBorderWidth" format="dimension"/>
    <!-- 边框线条的粗细 -->
    <attr name="appBorderColor" format="color"/>
    <!-- 边框线条的颜色 -->
    <attr name="appBgColor"/>
    <!-- 背景的颜色 -->
    <attr name="appRadius"/>
    <!-- 圆角度数 -->
    <attr name="appTopLeftCorner" format="boolean"/>
    <!-- 是否有圆角,默认,真 -->
    <attr name="appBottomLeftCorner" format="boolean"/>
    <!-- 是否有圆角,默认,真 -->
    <attr name="appTopRightCorner" format="boolean"/>
    <!-- 是否有圆角,默认,真 -->
    <attr name="appBottomRightCorner" format="boolean"/>
    <!-- 是否有圆角,默认,真 -->
    <attr name="appEnableFalseBgColor" format="color"/>
    <!-- 禁用时的背景颜色 -->
    <attr name="appEnableFalseTextColor" format="color"/>
    <!-- 禁用时的文字颜色 -->
</declare-styleable>


ShapeCornerBgView.java

public class ShapeCornerBgView extends TextView {
    int borderWidth = 1;// 默认1dimpx
    boolean isHasBorder = false;

    int borderColor;// 线条的颜色,默认与字的颜色相同
    int bgColor;// 背景的颜色,默认是透明的
    int mRadius = 3;// 默认3

    int mColorText;

    private Rect rect = new Rect();// 方角

    // 四个角落是否是全是圆角
    boolean isTopLeftCorner = true;
    boolean isBottomLeftCorner = true;
    boolean isTopRightCorner = true;
    boolean isBottomRightCorner = true;

    int mColorBgEnableFalse;
    int mColorTextEnableFalse;

    public ShapeCornerBgView(Context context, AttributeSet attrs) {
        super(context, attrs);

        borderWidth = Tools.getDimen720Px(context, borderWidth);
        mRadius = Tools.getDimen720Px(context, mRadius);
        mColorText=mColorTextEnableFalse=borderColor = getCurrentTextColor();

        TypedArray mTypedArray = context.obtainStyledAttributes(attrs, R.styleable.ShapeCornerBgView);
        isHasBorder = mTypedArray.getBoolean(R.styleable.ShapeCornerBgView_appBorder, isHasBorder);// 默认无边框
        borderWidth = mTypedArray.getDimensionPixelSize(R.styleable.ShapeCornerBgView_appBorderWidth, borderWidth);
        mRadius = mTypedArray.getDimensionPixelSize(R.styleable.ShapeCornerBgView_appRadius, mRadius);

        borderColor = mTypedArray.getColor(R.styleable.ShapeCornerBgView_appBorderColor, borderColor);

        bgColor = isHasBorder ? Color.TRANSPARENT : Color.RED;

        bgColor = mTypedArray.getColor(R.styleable.ShapeCornerBgView_appBgColor, bgColor);
        // 四个角落是否全是圆角,默认全是真的
        isTopLeftCorner = mTypedArray.getBoolean(R.styleable.ShapeCornerBgView_appTopLeftCorner, isTopLeftCorner);
        isBottomLeftCorner = mTypedArray.getBoolean(R.styleable.ShapeCornerBgView_appBottomLeftCorner, isBottomLeftCorner);
        isTopRightCorner = mTypedArray.getBoolean(R.styleable.ShapeCornerBgView_appTopRightCorner, isTopRightCorner);
        isBottomRightCorner = mTypedArray.getBoolean(R.styleable.ShapeCornerBgView_appBottomRightCorner, isBottomRightCorner);

        mColorBgEnableFalse=mTypedArray.getColor(R.styleable.ShapeCornerBgView_appEnableFalseBgColor, bgColor);
        mColorTextEnableFalse=mTypedArray.getColor(R.styleable.ShapeCornerBgView_appEnableFalseTextColor, mColorTextEnableFalse);
        mTypedArray.recycle();
        this.setGravity(Gravity.CENTER);// 全部居中显示
        this.setEnabled(isEnabled());
    }

    @SuppressLint("DrawAllocation")
    @Override
    protected void onDraw(Canvas canvas) {
        if (getWidth() == 0) // 没初始化完成不需要绘制
            return;
        float ffVar[] = getOutterRadii();
        // 先画背景
        if (bgColor != Color.TRANSPARENT) {// 透明就不用画了
            int color = bgColor;
            if (isEnabled() == false) {
                color = mColorBgEnableFalse;
            }
            ShapeDrawable shapeDrawable = new ShapeDrawable(new RoundRectShape(ffVar, null, null));
            Paint paint = shapeDrawable.getPaint();
            paint.setColor(color);
            shapeDrawable.setBounds(rect);
            shapeDrawable.draw(canvas);
        }
        // 有边框
        if (isHasBorder) {
            RectF rectF = new RectF(borderWidth, borderWidth, borderWidth, borderWidth);
            ShapeDrawable shapeDrawable = new ShapeDrawable(new RoundRectShape(ffVar, rectF, ffVar));
            Paint paint = shapeDrawable.getPaint();
            paint.setColor(borderColor);
            shapeDrawable.setBounds(rect);
            shapeDrawable.draw(canvas);
        }
        super.onDraw(canvas);
    }

    //获得圆角的度数
    private float[] getOutterRadii() {
        float fRandis[] = { mRadius, mRadius, mRadius, mRadius, mRadius, mRadius, mRadius, mRadius };

        if (isTopLeftCorner == false) {
            fRandis[0] = 0;
            fRandis[1] = 0;
        }
        if (isTopRightCorner == false) {
            fRandis[2] = 0;
            fRandis[3] = 0;
        }
        if (isBottomLeftCorner == false) {
            fRandis[4] = 0;
            fRandis[5] = 0;
        }
        if (isBottomRightCorner == false) {
            fRandis[6] = 0;
            fRandis[7] = 0;
        }
        return fRandis;
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);

        rect.left = 0;
        rect.top = 0;
        rect.bottom = h;
        rect.right = w;
    }

    // 设置是否可用更改颜色
    @Override
    public void setEnabled(boolean enabled) {
        super.setEnabled(enabled);
        setTextColor(enabled ? mColorText : mColorTextEnableFalse);
        invalidate();
    }

    public void setBgColor(int bgColor) {
        this.bgColor = bgColor;
        invalidate();
    }
}


Tool.java 转换单位的工具

/**
 * 得到demo的px大小
 *
 * @param dimen
 * @return
 */
public static int getDimen720Px(Context context, int dimen) {
    float dp = dimen * 1080f / 750 / 3;
    return dip2px(context, dp);
}


现在来看以上代码变得十分简洁,充分说明了多读书的重要性。写代码不能闭门造车,不学习新的技术,新的方法,技术的提升将是十分缓慢。