1、背景
我们的项目的设计师喜欢用圆角矩形背景作为设计元素,而且颜色、样式各不一样导致项目工程里面定义了大量的xml文件,为了消除这一现象,我想到自定义控件解决这个问题。
图1、项目中使用大量的xml定义圆角矩形
2、看看效果
先看效果
图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);
}
现在来看以上代码变得十分简洁,充分说明了多读书的重要性。写代码不能闭门造车,不学习新的技术,新的方法,技术的提升将是十分缓慢。