1. 程式人生 > >Android安卓自定義圓角矩形控制元件,省去定義drawable裡面xml的麻煩,輕鬆程式設計

Android安卓自定義圓角矩形控制元件,省去定義drawable裡面xml的麻煩,輕鬆程式設計

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);
}

現在來看以上程式碼變得十分簡潔,充分說明了多讀書的重要性。寫程式碼不能閉門造車,不學習新的技術,新的方法,技術的提升將是十分緩慢。