前提:
在開(kāi)發(fā)中,我們會(huì)經(jīng)常使用到按鈕,而且大多數(shù)按鈕的背景、圓角、按壓效果等等都不一樣,通常的做法是寫(xiě)大量的shape、selector文件作為background,這樣一來(lái),就會(huì)創(chuàng)建大量的文件,而且寫(xiě)起來(lái)也比較煩,浪費(fèi)很多時(shí)間,所以我想通過(guò)自定義一個(gè)view,來(lái)解決這個(gè)問(wèn)題。
目標(biāo):
自定義一個(gè)button,所需要的圓角、背景顏色、字體顏色等屬性,都是可配置的,還需要實(shí)現(xiàn)按壓效果,不可用狀態(tài)。
動(dòng)手實(shí)現(xiàn):
擼起袖子開(kāi)始干!
第一步:自定義屬性
首先我們要先定義好這些屬性,方便在xml中傳入值,在attrs.xml下添加如下代碼
<declare-styleable name="CustomButton">
<!--默認(rèn)背景顏色-->
<attr name="bgNormalColor" format="color"/>
<!--按壓時(shí)背景顏色-->
<attr name="bgPressColor" format="color"/>
<!--默認(rèn)文字顏色-->
<attr name="textNormalColor" format="color" />
<!--按壓文字顏色-->
<attr name="textPressColor" format="color" />
<!--不可用狀態(tài)背景顏色-->
<attr name="bgUnableColor" format="color" />
<!--不可用狀態(tài)文字顏色-->
<attr name="textUnableColor" format="color" />
<!--圓角半徑-->
<attr name="radius" format="dimension"/>
</declare-styleable>
屬性的意思注釋寫(xiě)的很清楚了。
第二步:獲取屬性
定義好了這些屬性,我們要在自定義view中讀取這些屬性
public class CustomButton extends AppCompatTextView {
private Paint mPaint;
private int bgNormalColor,bgPressColor,textPressColor,textNormalColor,bgUnableColor,textUnableColor;
private float radius;
private Paint textPaint;
public CustomButton(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.CustomButton);
bgNormalColor = typedArray.getResourceId(R.styleable.CustomButton_bgNormalColor,R.color.red);
bgPressColor = typedArray.getResourceId(R.styleable.CustomButton_bgPressColor,R.color.red);
textNormalColor = typedArray.getResourceId(R.styleable.CustomButton_textNormalColor,R.color.white);
textPressColor = typedArray.getResourceId(R.styleable.CustomButton_textPressColor,R.color.white);
bgUnableColor = typedArray.getResourceId(R.styleable.CustomButton_bgUnableColor,R.color.gray);
textUnableColor = typedArray.getResourceId(R.styleable.CustomButton_textUnableColor,R.color.white);
radius = typedArray.getDimension(R.styleable.CustomButton_radius,5);
typedArray.recycle();
init();
}
private void init(){
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
textPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
textPaint.setTextAlign(Paint.Align.CENTER);
mPaint.setStyle(Paint.Style.FILL);
}
}
通過(guò)TypedArray我們讀取了這些屬性,讀取完要記得調(diào)用 recycle()方法回收。
第三步:畫(huà)背景
這里我們應(yīng)該使用帶圓角的方形作為背景,所以我們使用drawRoundRect方法,重寫(xiě)onDraw()方法
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
rect.set(0, 0, getWidth(), getHeight());
mPaint.setColor(getResources().getColor(bgNormalColor));
canvas.drawRoundRect(rect,radius,radius,mPaint);
}
這樣我們就畫(huà)出帶圓角的button背景
第四步:畫(huà)文字
畫(huà)文字我們使用的是 drawText(text, x, y, paint)方法,需要注意的是這里的y坐標(biāo)是文字的baseline坐標(biāo),具體解釋可以看下圖

top:可繪制的最高高度所在線
ascent:系統(tǒng)推薦的,在繪制單個(gè)字體時(shí),字符應(yīng)當(dāng)?shù)淖罡吒叨人诰€。
baseline:字符基線
descent:系統(tǒng)推薦的,在繪制單個(gè)字體時(shí),字符應(yīng)當(dāng)?shù)淖畹透叨人诰€。
bottom:可繪制的最低高度所在線
我們的buttong中的文字,一般都是居中的,橫向居中比較很好辦,首先x的坐標(biāo)通過(guò)getWidth()/2就能獲得,然后調(diào)用 textPaint.setTextAlign(Paint.Align.CENTER),讓文字相對(duì)于x坐標(biāo)居中就可以了。
縱向居中就比較麻煩了,我們需要讓文字的橫向中間線,也就是上圖中間的那條紅線,處于Button的橫向中間線上。通過(guò)getHeight()/2我們可以的到buttong中間線的y坐標(biāo),這也是文字的中間線y坐標(biāo),因?yàn)檫@個(gè)中間線到top和bottom的距離相等,得到這個(gè)距離,再加上中間線的y軸坐標(biāo),就能得到bottom y軸坐標(biāo),根據(jù)bottom坐標(biāo)可以得到baseline坐標(biāo),這就要使用到FontMetrics類(lèi)了,這個(gè)類(lèi)中有四個(gè)成員變量
FontMetrics:acsent = assent線的y坐標(biāo) - baseline 線坐標(biāo)
FontMetrics:descent = descent線的y坐標(biāo) - baseline 線坐標(biāo)
FontMetrics:top = top線的y坐標(biāo) - baseline 線坐標(biāo)
FontMetrics:bottom = bottom線的y坐標(biāo) - baseline 線坐標(biāo)
注意,這里的acsent 、descent、top、bottom與現(xiàn)實(shí)中的acsent 、descent、top、bottom四條線不是一個(gè)概念!FontMetrics中的屬性是用來(lái)計(jì)算這些線的。
通過(guò)(FontMetrics.bottom -FontMetrics.top)/2可以得到字體的中間線到bottom的距離,然后通過(guò)getHeight()/2 +(FontMetrics.bottom -FontMetrics.top)/2得到bottom線的y坐標(biāo),再根據(jù)上面的FontMetrics:bottom = bottom線的y坐標(biāo) - baseline 線坐標(biāo)就推算出baseline 線坐標(biāo) = getHeight()/2 +(FontMetrics.bottom -FontMetrics.top)/2 -FontMetrics.bottom,具體代碼如下
float baseLine = (float) getHeight()/2 +(fm.bottom -fm.top)/2 -fm.bottom;
textPaint.setColor(getResources().getColor(textNormalColor));
canvas.drawText(getText().toString(),(float) getWidth()/2,baseLine,textPaint);
這樣我們就畫(huà)出了normal狀態(tài)下的buttong的背景和文字。
第四部:實(shí)現(xiàn)按壓效果
按壓效果就是當(dāng)手指按下的時(shí)候,背景顏色和字體顏色發(fā)生改變,抬起手指恢復(fù)。這里的思路是,判斷手指觸摸的位置是否再按鈕范圍內(nèi),如果是,改變顏色。
首先復(fù)寫(xiě) onTouchEvent方法,記錄手指按下的坐標(biāo),當(dāng)手指抬起時(shí)將坐標(biāo)改為(-1,-1)
@Override
public boolean onTouchEvent(MotionEvent event) {
if(mEnable) {
mx = (int) event.getX();
my = (int) event.getY();
if (event.getAction() == MotionEvent.ACTION_DOWN) {
invalidate();
return true;
} else if (event.getAction() == MotionEvent.ACTION_UP ) {
performClick();
mx = -1;
my = -1;
}
invalidate();
}
return super.onTouchEvent(event);
}
然后在onDraw方法中,通過(guò)rectcontains(x,y)判斷坐標(biāo)是否再按鈕上
if(rect.contains(mx,my)) {
mPaint.setColor(getResources().getColor(bgPressColor));
canvas.drawRoundRect(rect,radius,radius,mPaint);
textPaint.setColor(getResources().getColor(textPressColor));
}else {
mPaint.setColor(getResources().getColor(bgNormalColor));
canvas.drawRoundRect(rect,radius,radius,mPaint);
textPaint.setColor(getResources().getColor(textNormalColor));
}
canvas.drawText(getText().toString(),(float) getWidth()/2,baseLine,textPaint);
這樣就實(shí)現(xiàn)了所謂的按壓效果
第五步:實(shí)現(xiàn)不可用狀態(tài)
按鈕的不可用狀態(tài),一般都是將按鈕置灰或者其他顏色,并且不可點(diǎn)擊,這里通過(guò)isEnabled()方法判斷是否可用,在onDraw方法里
if(isEnabled()){
if(rect.contains(mx,my)) {
mPaint.setColor(getResources().getColor(bgPressColor));
canvas.drawRoundRect(rect,radius,radius,mPaint);
textPaint.setColor(getResources().getColor(textPressColor));
}else {
mPaint.setColor(getResources().getColor(bgNormalColor));
canvas.drawRoundRect(rect,radius,radius,mPaint);
textPaint.setColor(getResources().getColor(textNormalColor));
}
canvas.drawText(getText().toString(),(float) getWidth()/2,baseLine,textPaint);
}else {
mPaint.setColor(getResources().getColor(bgUnableColor));
canvas.drawRoundRect(rect,radius,radius,mPaint);
textPaint.setColor(getResources().getColor(textUnableColor));
canvas.drawText(getText().toString(),(float) getWidth()/2,baseLine,textPaint);
}
這樣這個(gè)自定義按鈕就完成啦!
下面是全部代碼
public class CustomButton extends AppCompatTextView {
private Paint mPaint;
private RectF rect;
private int mx = -1,my = -1;
private int bgNormalColor,bgPressColor,textPressColor,textNormalColor,bgUnableColor,textUnableColor;
private float radius;
private Paint textPaint;
private boolean mEnable = true;
private Rect heightBounds ,widthBounds;
public CustomButton(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.CustomButton);
bgNormalColor = typedArray.getColor(R.styleable.CustomButton_bg_color_normal, getResources().getColor(R.color.colorPrimary));
bgPressColor = typedArray.getColor(R.styleable.CustomButton_bg_color_press,getResources().getColor(R.color.colorAccent));
textNormalColor = typedArray.getColor(R.styleable.CustomButton_text_color_normal,getResources().getColor(R.color.colorWhite));
textPressColor = typedArray.getColor(R.styleable.CustomButton_text_color_press,getResources().getColor(R.color.colorWhite));
bgUnableColor = typedArray.getColor(R.styleable.CustomButton_bg_color_unable,getResources().getColor(R.color.colorGray));
textUnableColor = typedArray.getColor(R.styleable.CustomButton_text_color_unable,getResources().getColor(R.color.colorWhite));
radius = typedArray.getDimension(R.styleable.CustomButton_button_radius,5);
typedArray.recycle();
init();
}
private void init(){
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
textPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
textPaint.setTextAlign(Paint.Align.CENTER);
mPaint.setStyle(Paint.Style.FILL);
rect = new RectF();
heightBounds = new Rect();
widthBounds = new Rect();
}
/**
* 適配 AT_MOST
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int heightMode = View.MeasureSpec.getMode(heightMeasureSpec);
int widthMode = View.MeasureSpec.getMode(widthMeasureSpec);
int height = View.MeasureSpec.getSize(heightMeasureSpec);
int width = View.MeasureSpec.getSize(widthMeasureSpec);
textPaint.setTextSize(getTextSize());
textPaint.setTypeface(getTypeface());
if(View.MeasureSpec.AT_MOST == heightMode){
textPaint.getTextBounds(getText().toString(),0,getText().toString().length(),heightBounds);
height = heightBounds.height() + getPaddingTop() + getPaddingBottom();
}
if(View.MeasureSpec.AT_MOST == widthMode){
textPaint.getTextBounds(getText().toString(),0,getText().toString().length(),widthBounds);
width = widthBounds.width() + getPaddingLeft() + getPaddingRight();
}
setMeasuredDimension(width,height);
}
@Override
protected void onDraw(Canvas canvas) {
rect.set(0, 0, getWidth(), getHeight());
Paint.FontMetrics fm = textPaint.getFontMetrics();
//獲取基線坐標(biāo)
float baseLine = (float) getHeight()/2 +(fm.bottom -fm.top)/2 -fm.bottom;
if(isEnabled()){
if(rect.contains(mx,my)) {
mPaint.setColor(bgPressColor);
canvas.drawRoundRect(rect,radius,radius,mPaint);
textPaint.setColor(textPressColor);
}else {
mPaint.setColor(bgNormalColor);
canvas.drawRoundRect(rect,radius,radius,mPaint);
textPaint.setColor(textNormalColor);
}
canvas.drawText(getText().toString(),(float) getWidth()/2+getPaddingLeft()-getPaddingRight(),baseLine+getPaddingTop()-getPaddingBottom(),textPaint);
}else {
mPaint.setColor(bgUnableColor);
canvas.drawRoundRect(rect,radius,radius,mPaint);
textPaint.setColor(textUnableColor);
canvas.drawText(getText().toString(),(float) getWidth()/2+getPaddingLeft()-getPaddingRight(),baseLine+getPaddingTop()-getPaddingBottom(),textPaint);
}
}
@Override
public boolean onTouchEvent(MotionEvent event) {
if(mEnable) {
mx = (int) event.getX();
my = (int) event.getY();
if (event.getAction() == MotionEvent.ACTION_DOWN) {
invalidate();
return true;
} else if (event.getAction() == MotionEvent.ACTION_UP ) {
performClick();
mx = -1;
my = -1;
}
invalidate();
}
return super.onTouchEvent(event);
}
@Override
public boolean performClick() {
return super.performClick();
}
@Override
public void setBackgroundColor(int color) {
bgNormalColor = color;
invalidate();
}
@Override
public void setTextColor(int color) {
textNormalColor = color;
invalidate();
}
}
這個(gè)自定義button可能還存在很多問(wèn)題,后續(xù)再繼續(xù)完善吧。