Android自定义View制作动态炫酷按钮实例解析
普通按钮也就那么几种样式,看着都审美疲劳,先放效果图:
你会不会以为这个按钮是集结了很多动画的产物,我告诉你,并没有。所有的实现都是基于自定义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(); } }); } }
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。
上一篇: Java程序员应该遵守的10条纪律