Android自定義控制元件 -- 帶邊框的TextView
使用xml實現邊框
原來使用帶邊框的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檔案。
自定義帶邊框的TextView
最近在做專案時遇到多種顏色的標籤需求,如果還是按照上面的做法,那麼需要多套XML檔案配合,於是我想了一下,能不能自定義一個控制元件,讓邊框顏色在使用時指定。
在專案的設計圖中主要是用於一些標籤,如下圖所示:
接下來我就嘗試了一下,發現是可行的,於是就有了下面這個自定義,在個人專案中基本夠用。
這個控制元件是繼承自TextView的,只是在onDraw方法中畫了一個邊框,並設計了幾個自定義屬性用來更靈活地控制控制元件。
自定義屬性如下:
<declare-styleable name="BorderTextView">
<attr name="strokeWidth" format="dimension"/>
<attr name="cornerRadius" format="dimension"/>
<attr name="strokeColor" format="color"/>
<attr name="followTextColor" format="boolean"/>
</declare-styleable>
這幾個屬性簡要解釋如下:
- strokeWidth
邊框的寬度,預設為1dp - cornerRadius
圓角半徑,預設為2dp - strokeColor
邊框顏色,預設是沒有邊框即顏色為Color.TRANSPARENT - followTextColor
邊框是否跟隨文字顏色,預設是true
自定義控制元件程式碼(BorderTextView ):
package com.witmoon.eab.widget;
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.NonNull;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.TypedValue;
import android.widget.TextView;
import com.witmoon.eab.R;
/**
* 用於作為標籤顯示的TextView
* 邊框預設與文字顏色一致
* Created by chuxin on 2015/9/11.
*/
public class BorderTextView extends TextView {
public static final float DEFAULT_STROKE_WIDTH = 1.0f; // 預設邊框寬度, 1dp
public static final float DEFAULT_CORNER_RADIUS = 2.0f; // 預設圓角半徑, 2dp
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 Paint mPaint = new Paint(); // 畫邊框所使用畫筆物件
private RectF mRectF; // 畫邊框要使用的矩形
public BorderTextView(Context context) {
this(context, null);
}
public BorderTextView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public BorderTextView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
// 將DIP單位預設值轉為PX
DisplayMetrics 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.BorderTextView);
strokeWidth = ta.getDimensionPixelSize(R.styleable.BorderTextView_strokeWidth, strokeWidth);
cornerRadius = ta.getDimensionPixelSize(R.styleable.BorderTextView_cornerRadius, cornerRadius);
strokeColor = ta.getColor(R.styleable.BorderTextView_strokeColor, Color.TRANSPARENT);
mFollowTextColor = ta.getBoolean(R.styleable.BorderTextView_followTextColor, true);
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(@NonNull Canvas canvas) {
super.onDraw(canvas);
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);
}
}
程式碼中的註釋也比較詳細了,而且也非常簡單,因此這裡應該不需要贅述。唯一需要注意的是在畫邊框時使用的RectF尺寸,如果邊框寬度較寬,由於Paint筆觸是在邊框中線為準,因此如果左上角指定為(0,0)話,會有一半邊框寬度的線是畫在不見區域的;這裡指定左上角座標為 0.5f * strokeWidth(即半個邊框寬度)即可,右下角也需要作同樣的考慮。
使用
自定義控制元件的使用就更簡單了,這裡我沒有設定自定義屬性,一切採用預設值:
<com.witmoon.eab.widget.BorderTextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="身份驗證"
android:textColor="@color/tag_text_blue"/>
展示效果如下圖所示:
基本上滿足要求。
作為按鈕使用
在某些時候,我們可能會需要一些這種中間鏤空的按鈕,BorderTextView也可以用在這種情況下,下面是個例子。
首先在values目錄中新建一個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>
其實是為按鈕在不同狀態下指定不同的顏色,以響應點選或禁用操作,增加使用者體驗。
<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會重新繪製,邊框也隨之改變顏色。