Android 自定义View实现动态炫酷按钮

来源:互联网 发布:人工智能类似电影 编辑:程序博客网 时间:2024/06/18 18:18

普通按钮也就那么几种样式,看着都审美疲劳,先放效果图,演示Demo+源码在最后面



你会不会以为这个按钮是集结了很多动画的产物,我告诉你,并没有。所有的实现都是基于自定义View,采用最底层的onDraw一点一点的画出来的。没有采用一丁点的动画。虽然演示时间很短,但是要完成这么多变化,还是挺吃力。

首先讲解用法:

public class MainActivity extends Activity {    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        final AnimationButton button = (AnimationButton) findViewById(R.id.button);//        button.setTextSizeTouch(25);  //设置按下时字体的大小,不设置有默认值//        button.setStrokeProgress(10); //设置进度条的厚度,不设置有默认值//        button.setColorBase(Color.GREEN); //设置整体基色,不设置有默认值//        button.setColorBack(Color.GRAY); //设置进度条的背景色,不设置有默认值//        button.setStroke(3); //设置边框的厚度,不设置有默认值//        button.setStrokeText(0); //设置文本的厚度,不设置有默认值//        button.setTextSize(30); //设置文本的字体大小,不设置有默认值//        button.setRound(30); //设置圆角,不设置有默认值        button.setText("登录"); //设置文本,不设置有默认值        button.setMode(AnimationButton.Mode.Hand_Finish); //设置进度条模式,不设置有默认值Mode.Auto_Finish        button.setOnAnimationButtonClickListener(new AnimationButton.OnAnimationButtonClickListener() {            @Override            public void onClick() {                //stopProgress方法 仅仅在button.setMode(AnimationButton.Mode.Hand_Finish);之后才有效。                button.stopProgress();            }        });    }}
其实如果只需要最普通的功能,根本什么都不用做。因为几乎所有的参数都已经设置了固定内设置。在上面注释掉的函数用法也是用户唯一能用的几个函数,其他函数虽然标示为public,但是却是因为组件之内的方法传递,而不是给外界用户调用的。因此大家如果想自定义样式,可以调用注释里的方法。


下面开始源码讲解,首先分解功能,所有的变化可以分为三个状态:

1、默认状态,也就是最初的状态。主要完成的事情为:接收用户的点击,改变背景的样式从空心变为实心,动态改变文本的大小,然后就是逐渐得缩小成一个圆。

2、进度条状态。主要完成进度条的递进,演示图上只转了一圈。其实可以通过设置一个参数,转动多圈直到用户手动停止,甚至无限转动

3、结束状态。主要完成由圆的状态变回圆角矩形的状态,并呈现中间的Logo


既然分割出了状态,那么就采用状态机+代理模式来实现这个功能吧。首先是状态的枚举。

/** * Created by ccwxf on 2016/2/29. * 用于区别状态,有:默认状态、进度条状态、结束状态 */public enum Status {    Default,    Progress,    Finish}

然后是状态机的接口,也就是所有的状态需要完成的共同的事情:

/** * Created by ccwxf on 2016/2/29. */public interface ButtonStatus {    /**     * @return 对应的Status值     */    Status getStatus();    /**     * 这个状态的事件处理代理     * @param mEvent     * @return     */    boolean onTouchEvent(MotionEvent mEvent);    /**     * 这个状态的绘制代理     * @param mCanvas     * @param mPaint     */    void onDraw(Canvas mCanvas, Paint mPaint);}

然后我们实现按钮的代码,也就是自定义View:

/** * Created by ccwxf on 2016/2/29. */public class AnimationButton extends View {    private static int Color_Base = Color.rgb(24, 204, 149);    private static int Color_Back = Color.rgb(153, 153, 153);    private static int Stroke = 3;    private static int Stroke_Text = 0;    private static int Stroke_Progress = 10;    private static int Text_Size = 30;    private static int Text_Size_Touch = 25;    private static int Round = 30;    private static String Text = "提交";    private Mode mode = Mode.Auto_Finish;    private int maxWidth;    private int maxHeight;    private int colorBase = Color_Base;    private int colorBack = Color_Back;    private int stroke = Stroke;    private int strokeText = Stroke_Text;    private int strokeProgress = Stroke_Progress;    private int textSize = Text_Size;    private int textSizeTouch = Text_Size_Touch;    private int round = Round;    private String text = Text;    //是否停止进度条,由外界设置    private boolean isProgressStop = false;    private Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);    private ButtonStatus status;    private OnAnimationButtonClickListener listener;    public Mode getMode() {        return mode;    }    public void setMode(Mode mode) {        this.mode = mode;    }    public int getMaxWidth() {        return maxWidth;    }    public int getMaxHeight() {        return maxHeight;    }    public int getTextSizeTouch() {        return textSizeTouch;    }    public void setTextSizeTouch(int textSizeTouch) {        this.textSizeTouch = textSizeTouch;    }    public int getStrokeProgress() {        return strokeProgress;    }    public void setStrokeProgress(int strokeProgress) {        this.strokeProgress = strokeProgress;    }    public int getColorBase() {        return colorBase;    }    public void setColorBase(int colorBase) {        this.colorBase = colorBase;    }    public int getColorBack() {        return colorBack;    }    public void setColorBack(int colorBack) {        this.colorBack = colorBack;    }    public int getStroke() {        return stroke;    }    public void setStroke(int stroke) {        this.stroke = stroke;    }    public int getStrokeText() {        return strokeText;    }    public void setStrokeText(int strokeText) {        this.strokeText = strokeText;    }    public int getTextSize() {        return textSize;    }    public void setTextSize(int textSize) {        this.textSize = textSize;    }    public int getRound() {        return round;    }    public void setRound(int round) {        this.round = round;    }    public String getText() {        return text;    }    public void setText(String text) {        this.text = text;    }    public AnimationButton(Context context) {        super(context);    }    public AnimationButton(Context context, AttributeSet attrs) {        super(context, attrs);    }    public AnimationButton(Context context, AttributeSet attrs, int defStyleAttr) {        super(context, attrs, defStyleAttr);    }    @Override    public boolean onTouchEvent(MotionEvent event) {        if (status != null) {            return status.onTouchEvent(event);        }        return super.onTouchEvent(event);    }    @Override    protected void onDraw(Canvas canvas) {        if (status != null) {            status.onDraw(canvas, mPaint);        }    }    @Override    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {        maxWidth = MeasureSpec.getSize(widthMeasureSpec);        maxHeight = MeasureSpec.getSize(heightMeasureSpec);        if (maxWidth != 0 && maxHeight != 0) {            status = new DefaultStatus(this, maxWidth, maxHeight);        }        super.onMeasure(widthMeasureSpec, heightMeasureSpec);    }    /**     * 改变整体状态     *     * @param s      改变的状态     * @param width  目前的宽度     * @param height 目前的高度     */    public void changeStatus(Status s, int width, int height, int centerX, int centerY) {        switch (s) {            case Default:                break;            case Progress:                //改变状态,进入进度条状态                status = new ProgressStatus(this, width, height, centerX, centerY);                invalidate();                break;            case Finish:                //进入结束状态                status = new FinishStatus(this, width, height, centerX, centerY);                invalidate();                break;        }    }    /**     * 外界设置停止进度条     */    public void stopProgress(){        this.isProgressStop = true;    }    /**     * 检查是否进度条结束     * @return     */    public boolean isProgressStop(){        return isProgressStop;    }    public enum Mode{        Auto_Finish,        Hand_Finish    }    public interface OnAnimationButtonClickListener{        void onClick();    }    public void setOnAnimationButtonClickListener(OnAnimationButtonClickListener listener){        this.listener = listener;    }    public OnAnimationButtonClickListener getOnAnimationButtonClickListener(){        return listener;    }}

上面实现了一堆的变量参数供用户自定义。然后在onTouchEvent和onDraw方法中,将所有操作都代理出去。

然后我们来实现第一个状态,也就是默认状态:

/** * Created by ccwxf on 2016/2/29. */public class DefaultStatus implements ButtonStatus {    //分别表示处于默认状态内部的四个子状态    private static final int Status_Default = 0;    private static final int Status_Touch = 1;    private static final int Status_Up = 2;    private static final int Status_Next = 3;    //刷新width时的渐变量以及时间间距    private static final int Delay_Next = 500;    private static final int Delay_Frush = 10;    private static final int Pixel_Frush = 8;    //按钮对象    private AnimationButton button;    //按钮对象的长宽与中点坐标(长宽为绘制的长宽,而不是控件的长宽)    private int width;    private int height;    private int centerX;    private int centerY;    //子状态变量    private int status = Status_Default;    private Handler handler = new Handler();    public DefaultStatus(AnimationButton button, int width, int height) {        this.button = button;        this.width = width;        this.height = height;        this.centerX = width / 2;        this.centerY = height / 2;    }    @Override    public Status getStatus() {        return Status.Default;    }    @Override    public boolean onTouchEvent(MotionEvent mEvent) {        switch (mEvent.getAction()) {            case MotionEvent.ACTION_DOWN:                //按下时,切换到按下子状态                if(status == Status_Default){                    status = Status_Touch;                    button.invalidate();                }                return true;            case MotionEvent.ACTION_UP:            case MotionEvent.ACTION_CANCEL:                //抬起时,或者移除控件时,切换到抬起子状态                if(status == Status_Touch){                    status = Status_Up;                    button.invalidate();                    //过500ms延迟后开始进行伸缩变化                    handler.postDelayed(new Runnable() {                        @Override                        public void run() {                            //切换到next子状态                            if(status == Status_Up){                                status = Status_Next;                            }                            if(status == Status_Next){                                //若长宽不一致,则继续渐变,否则改变状态                                if (width >= height) {                                    width -= Pixel_Frush;                                    button.invalidate();                                    handler.postDelayed(this, Delay_Frush);                                }else{                                    button.changeStatus(Status.Progress, width, height, centerX, centerY);                                }                            }                        }                    }, Delay_Next);                    //响应监听器                    AnimationButton.OnAnimationButtonClickListener listener = button.getOnAnimationButtonClickListener();                    if(listener != null){                        listener.onClick();                    }                }                break;        }        return false;    }    @Override    public void onDraw(Canvas mCanvas, Paint mPaint) {        switch (status) {            case Status_Default:                onDrawDefault(mCanvas, mPaint);                break;            case Status_Touch:                onDrawTouch(mCanvas, mPaint);                break;            case Status_Up:                onDrawUp(mCanvas, mPaint);                break;            case Status_Next:                onDrawNext(mCanvas, mPaint);                break;        }    }    /**     * 绘制边框,分为空心和实心两种     *     * @param mCanvas 画布     * @param mPaint  画笔     * @param style   空心或者实心     * @param padding 边框补白     */    private void drawRound(Canvas mCanvas, Paint mPaint, Paint.Style style, int padding) {        mPaint.setColor(button.getColorBase());        int stroke = padding;        if (style == Paint.Style.STROKE) {            mPaint.setStyle(Paint.Style.STROKE);            mPaint.setStrokeWidth(button.getStroke());            stroke += button.getStroke() / 2;        } else {            mPaint.setStyle(Paint.Style.FILL);        }        //绘制边框        mCanvas.drawRoundRect(new RectF(stroke, stroke, width - stroke, height - stroke), button.getRound(), button.getRound(), mPaint);    }    /**     * 画文字,有字体大小和颜色的区别     *     * @param mCanvas   画布     * @param mPaint    画笔     * @param textSize  字体大小     * @param textColor 字体颜色     */    private void drawText(Canvas mCanvas, Paint mPaint, int textSize, int textColor) {        mPaint.setColor(textColor);        mPaint.setStrokeWidth(button.getStrokeText());        mPaint.setTextSize(textSize);        Paint.FontMetrics metrics = mPaint.getFontMetrics();        int textWidth = (int) mPaint.measureText(button.getText());        int baseLine = (int) (height / 2 + (metrics.bottom - metrics.top) / 2 - metrics.bottom);        mCanvas.drawText(button.getText(), (width - textWidth) / 2, baseLine, mPaint);    }    /**     * 绘制默认状态的按钮     *     * @param mCanvas     * @param mPaint     */    private void onDrawDefault(Canvas mCanvas, Paint mPaint) {        drawRound(mCanvas, mPaint, Paint.Style.STROKE, 0);        //绘制居中文字        drawText(mCanvas, mPaint, button.getTextSize(), button.getColorBase());    }    /**     * 绘制按下状态的按钮     *     * @param mCanvas     * @param mPaint     */    private void onDrawTouch(Canvas mCanvas, Paint mPaint) {        drawRound(mCanvas, mPaint, Paint.Style.FILL, button.getStroke());        //绘制文字,字体要变化        drawText(mCanvas, mPaint, button.getTextSizeTouch(), Color.WHITE);    }    /**     * 绘制抬起状态的按钮     *     * @param mCanvas     * @param mPaint     */    private void onDrawUp(Canvas mCanvas, Paint mPaint) {        drawRound(mCanvas, mPaint, Paint.Style.FILL, 0);        drawText(mCanvas, mPaint, button.getTextSize(), Color.WHITE);    }    /**     * 绘制进入下一状态的按钮     *     * @param mCanvas     * @param mPaint     */    private void onDrawNext(Canvas mCanvas, Paint mPaint) {        mPaint.setColor(button.getColorBase());        mPaint.setStyle(Paint.Style.FILL);        //绘制边框        if (width >= height) {            mCanvas.drawRoundRect(new RectF(centerX - width / 2, centerY - height / 2, centerX + width / 2, centerY + height / 2),                    button.getRound(), button.getRound(), mPaint);            //绘制文字            mPaint.setColor(Color.WHITE);            mPaint.setStrokeWidth(button.getStrokeText());            mPaint.setTextSize(button.getTextSize());            Paint.FontMetrics metrics = mPaint.getFontMetrics();            int textWidth = (int) mPaint.measureText(button.getText());            int baseLine = (int) (centerY + (metrics.bottom - metrics.top) / 2 - metrics.bottom);            mCanvas.drawText(button.getText(), centerX - textWidth / 2, baseLine, mPaint);        } else {            mCanvas.drawOval(new RectF(centerX - width / 2, centerY - height / 2, centerX + width / 2, centerY + height / 2), mPaint);        }    }}

然后是第二个状态,进度条状态:

/** * Created by ccwxf on 2016/2/29. */public class ProgressStatus implements ButtonStatus {    //转圈的子状态    private static final int Status_Once = 0;    private static final int Status_Twice = 1;    //转圈的渐变量    private static final int Delay_Progress = 10;    private static final int Angle_Progress = 5;    private static final int Angle_Default = -90;    private static final int Andle_Full = 270;    private AnimationButton button;    private int width;    private int height;    private int centerX;    private int centerY;    private int radius;    private int status = Status_Once;    //当前的进度    private float progress = Angle_Default;    private Handler handler = new Handler();    public ProgressStatus(AnimationButton button, int width, int height, int centerX, int centerY) {        this.button = button;        this.width = width;        this.height = height;        this.centerX = centerX;        this.centerY = centerY;        //绘制起点是Stroke的中点,若不减去这个值,则onDraw时,会不完整。        this.radius = (width - button.getStrokeProgress())  / 2;        startProgress();    }    /**     * 开始递归转动进度条     */    private void startProgress() {        handler.postDelayed(new Runnable() {            @Override            public void run() {                if(progress >= Andle_Full){                    //如果是手动结束模式                    if(button.getMode() == AnimationButton.Mode.Hand_Finish && button.isProgressStop()){                        //改变状态                        button.changeStatus(Status.Finish, width, height, centerX, centerY);                        return;                    }else{                        if(status == Status_Once){                            status = Status_Twice;                        }else if(status == Status_Twice){                            //如果是自动结束模式,则在第二次进度结束时改变状态                            if(button.getMode() == AnimationButton.Mode.Auto_Finish){                                //改变状态                                button.changeStatus(Status.Finish, width, height, centerX, centerY);                                return;                            }else{                                status = Status_Once;                            }                        }                        //重置进度                        progress = Angle_Default;                    }                }                progress += Angle_Progress;                button.invalidate();                handler.postDelayed(this, Delay_Progress);            }        }, Delay_Progress);    }    @Override    public Status getStatus() {        return Status.Progress;    }    @Override    public boolean onTouchEvent(MotionEvent mEvent) {        return false;    }    @Override    public void onDraw(Canvas mCanvas, Paint mPaint) {        if(status == Status_Once){            //绘制灰色背景            onDrawCircle(mCanvas, mPaint, button.getColorBack());            //绘制绿色进度            onDrawArc(mCanvas, mPaint, button.getColorBase(), Angle_Default, progress);        }else if(status == Status_Twice){            //绘制绿色背景            onDrawCircle(mCanvas, mPaint, button.getColorBase());            //绘制灰色进度            onDrawArc(mCanvas, mPaint, button.getColorBack(), Angle_Default, progress);        }    }    /**     * 画一整个圆作为背景     * @param mCanvas 画布     * @param mPaint 画笔     * @param color 颜色     */    private void onDrawCircle(Canvas mCanvas, Paint mPaint, int color){        mPaint.setColor(color);        mPaint.setStrokeWidth(button.getStrokeProgress());        mPaint.setStyle(Paint.Style.STROKE);        mCanvas.drawCircle(centerX, centerY, radius, mPaint);    }    /**     * 画一端圆弧     * @param mCanvas 画布     * @param mPaint 画笔     * @param color 颜色     * @param start 开始角度     * @param stop 结束角度     */    private void onDrawArc(Canvas mCanvas, Paint mPaint, int color, float start, float stop){        mPaint.setColor(color);        mPaint.setStrokeWidth(button.getStrokeProgress());        mPaint.setStyle(Paint.Style.STROKE);        //第三个参数是扫过的角度,起点0默认为右边        mCanvas.drawArc(new RectF(centerX - radius, centerY - radius, centerX + radius, centerY + radius),                start, stop - start, false, mPaint);    }}


最后一个状态:

/** * Created by ccwxf on 2016/2/29. */public class FinishStatus implements ButtonStatus {    private static final int Status_Stretch = 0;    private static final int Status_Finish = 1;    private static final int Stroke_Over = 10;    private static final String Text_Over = "√";    private static final int Text_Over_Size = 40;    private static final int Delay_Stretch = 10;    private static final int Pixel_Stretch = 8;    private AnimationButton button;    private int width;    private int height;    private int centerX;    private int centerY;    private int status = Status_Stretch;    private Handler handler = new Handler();    public FinishStatus(AnimationButton button, int width, int height, int centerX, int centerY) {        this.button = button;        this.width = width;        this.height = height;        this.centerX = centerX;        this.centerY = centerY;        startStretch();    }    /**     * 开始伸展背景     */    private void startStretch() {        handler.postDelayed(new Runnable() {            @Override            public void run() {                if(width < button.getMaxWidth()){                    width += Pixel_Stretch;                    button.invalidate();                    handler.postDelayed(this, Delay_Stretch);                }else{                    width = button.getMaxWidth();                    if(status == Status_Stretch){                        status = Status_Finish;                    }                    button.invalidate();                }            }        }, Delay_Stretch);    }    @Override    public Status getStatus() {        return Status.Finish;    }    @Override    public boolean onTouchEvent(MotionEvent mEvent) {        return false;    }    @Override    public void onDraw(Canvas mCanvas, Paint mPaint) {        //绘制背景        mPaint.setColor(button.getColorBase());        mPaint.setStyle(Paint.Style.FILL);        mCanvas.drawRoundRect(new RectF(centerX - width / 2, centerY - height / 2, centerX + width / 2, centerY + height / 2 ),                button.getRound(), button.getRound(), mPaint);        //绘制图片        if(status == Status_Finish){            mPaint.setColor(Color.WHITE);            mPaint.setStrokeWidth(Stroke_Over);            mPaint.setTextSize(Text_Over_Size);            Paint.FontMetrics metrics = mPaint.getFontMetrics();            int textWidth = (int) mPaint.measureText(Text_Over);            int baseLine = (int) (height / 2 + (metrics.bottom - metrics.top) / 2 - metrics.bottom);            mCanvas.drawText(Text_Over, (width - textWidth) / 2, baseLine, mPaint);        }    }}

好了上面就是所有的源码了。虽然被我概括成三个大状态,但是如果分细一点的话,大概需要9个状态。在各个大状态代码里面的子状态就是这个了。

怎么使用呢,也非常简单,因为大部分的参数都有内设值了。

/** * Created by ccwxf on 2016/2/29. */public class MainActivity extends Activity {    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        final AnimationButton button = (AnimationButton) findViewById(R.id.button);//        button.setTextSizeTouch(25);  //设置按下时字体的大小,不设置有默认值//        button.setStrokeProgress(10); //设置进度条的厚度,不设置有默认值//        button.setColorBase(Color.GREEN); //设置整体基色,不设置有默认值//        button.setColorBack(Color.GRAY); //设置进度条的背景色,不设置有默认值//        button.setStroke(3); //设置边框的厚度,不设置有默认值//        button.setStrokeText(0); //设置文本的厚度,不设置有默认值//        button.setTextSize(30); //设置文本的字体大小,不设置有默认值//        button.setRound(30); //设置圆角,不设置有默认值        button.setText("登录"); //设置文本,不设置有默认值        button.setMode(AnimationButton.Mode.Hand_Finish); //设置进度条模式,不设置有默认值Mode.Auto_Finish        button.setOnAnimationButtonClickListener(new AnimationButton.OnAnimationButtonClickListener() {            @Override            public void onClick() {                //stopProgress方法 仅仅在button.setMode(AnimationButton.Mode.Hand_Finish);之后才有效。                button.stopProgress();            }        });    }}

下面给出Demo,源码在Demo里的一个package里面。求人气啊,每次人气都那么低

点我去下载Demo

0 0
原创粉丝点击