普通按钮也就那么几种样式,看着都审美疲劳,先放效果图:
你会不会以为这个按钮是集结了很多动画的产物,我告诉你,并没有。所有的实现都是基于自定义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(); } }); } }
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持小牛知识库。
本文向大家介绍Android自定义View之酷炫圆环(二),包括了Android自定义View之酷炫圆环(二)的使用技巧和注意事项,需要的朋友参考一下 先看下最终的效果 静态: 动态: 一、开始实现 新建一个DoughnutProgress继承View 先给出一些常量、变量以及公共方法的代码,方便理解后面的代码 重写onMeasure方法,为什么要重写onMeasure方法可以看我的上一篇
本文向大家介绍Android自定义View之酷炫数字圆环,包括了Android自定义View之酷炫数字圆环的使用技巧和注意事项,需要的朋友参考一下 先看下最终的效果 一、开始实现 新建一个DoughnutView继承View 先重写onMeasure方法。 下面就是最重要的重写onDraw方法,大致流程如下 1、画白色圆环(背景),记得改下Activity背景色不然白色圆环看不出来。 2、画彩
本文向大家介绍Android自定义View实现拖动选择按钮,包括了Android自定义View实现拖动选择按钮的使用技巧和注意事项,需要的朋友参考一下 本文为大家分享了Android实现拖动选择按钮的具体代码,供大家参考,具体内容如下 效果图 View代码 第一步:自定义属性 第二步:自定义圆形 第三步:自定义拖动按钮 使用方法 布局文件: 页面代码: 以上就是本文的全部内容,希望对大家的学习有所
本文向大家介绍Android 自定义返回按钮的实例详解,包括了Android 自定义返回按钮的实例详解的使用技巧和注意事项,需要的朋友参考一下 Android 自定义返回按钮的实例详解 程序中我们有时候想让放回按钮按照自己的需求调整页面而不是单纯的按照系统返回上一级,这个问题很简单,重写 onKeyDown 方法即可。 下面方法,包含了 webview 中的返回上一页和普通 activity 的单
本文向大家介绍Android自定义view实现拖拽选择按钮,包括了Android自定义view实现拖拽选择按钮的使用技巧和注意事项,需要的朋友参考一下 本文实例为大家分享了Android实现拖拽选择按钮的具体代码,供大家参考,具体内容如下 github地址:https://github.com/xuezj/DragChooseDemo DragChooseDemo 效果图 Attributes属性
本文向大家介绍Android view自定义实现动态进度条,包括了Android view自定义实现动态进度条的使用技巧和注意事项,需要的朋友参考一下 Android 自定义view实现动态进度条 效果图: 这个是看了梁肖的demo,根据他的思路自己写了一个,但是我写的这个貌似计算还是有些问题,从上面的图就可以看出来,左侧、顶部、右侧的线会有被截掉的部分,有懂得希望能给说一下,改进一下,这个