Android安卓自定義圓角矩形控制元件,省去定義drawable裡面xml的麻煩,輕鬆程式設計
阿新 • • 發佈:2019-02-03
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裡定義屬性
4 定義變數<!-- 圓角矩形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>
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);
}
現在來看以上程式碼變得十分簡潔,充分說明了多讀書的重要性。寫程式碼不能閉門造車,不學習新的技術,新的方法,技術的提升將是十分緩慢。