Android 自定義View -- 自定義通用Button,避免創(chuàng)建大量布局文件

前提:

在開(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),具體解釋可以看下圖


5017748-0fd8d5f2a4e531a0.png

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ù)完善吧。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容