欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

Android自定义View实现水面上涨效果

程序员文章站 2024-03-05 14:18:24
实现效果如下: 实现思路: 1、如何实现圆中水面上涨效果:利用paint的setxfermode属性为porterduff.mode.src_in画出进度所在的...

实现效果如下:

Android自定义View实现水面上涨效果

实现思路:

1、如何实现圆中水面上涨效果:利用paint的setxfermode属性为porterduff.mode.src_in画出进度所在的矩形与圆的交集实现

2、如何水波纹效果:利用贝塞尔曲线,动态改变波峰值,实现“随着进度的增加,水波纹逐渐变小的效果”

话不多说,看代码。

首先是自定义属性值,有哪些可自定义属性值呢?

圆的背景颜色:circle_color,进度的颜色:progress_color,进度显示文字的颜色:text_color,进度文字的大小:text_size,还有最后一个:波纹最大高度:ripple_topheight

<declare-styleable name="waterprogressview"> 
 <attr name="circle_color" format="color"/><!--圆的颜色--> 
 <attr name="progress_color" format="color"/><!--进度的颜色--> 
 <attr name="text_color" format="color"/><!--文字的颜色--> 
 <attr name="text_size" format="dimension"/><!--文字大小--> 
 <attr name="ripple_topheight" format="dimension"/><!--水页涟漪最大高度-->
</declare-styleable>

下面是自定义view:waterprogressview的部份代码:

成员变量

public class waterprogressview extends progressbar {
 //默认圆的背景色
 public static final int default_circle_color = 0xff00cccc;
 //默认进度的颜色
 public static final int default_progress_color = 0xff00cc66;
 //默认文字的颜色
 public static final int default_text_color = 0xffffffff;
 //默认文字的大小
 public static final int default_text_size = 18;
 //默认的波峰最高点
 public static final int default_ripple_topheight = 10;

 private context mcontext;
 private canvas mpaintcanvas;
 private bitmap mbitmap;

 //画圆的画笔
 private paint mcirclepaint;
 //画圆的画笔的颜色
 private int mcirclecolor;

 //画进度的画笔
 private paint mprogresspaint;
 //画进度的画笔的颜色
 private int mprogresscolor ;
 //画进度的path
 private path mprogresspath;
 //贝塞尔曲线波峰最大值
 private int mrippletop = 10;

 //进度文字的画笔
 private paint mtextpaint;
 //进度文字的颜色
 private int mtextcolor;
 private int mtextsize = 18;
 //目标进度,也就是双击时处理任务的进度,会影响曲线的振幅
 private int mtargetprogress = 50;

 //监听双击和单击事件
 private gesturedetector mgesturedetector;
}

获取自定义属性值:

private void getattrvalue(attributeset attrs) { 

 typedarray ta = mcontext.obtainstyledattributes(attrs, r.styleable.waterprogressview); 
 mcirclecolor = ta.getcolor(r.styleable.waterprogressview_circle_color,default_circle_color);    
 mprogresscolor = ta.getcolor(r.styleable.waterprogressview_progress_color,default_progress_color); 
 mtextcolor = ta.getcolor(r.styleable.waterprogressview_text_color,default_text_color);  
 mtextsize = (int) ta.getdimension(r.styleable.waterprogressview_text_size, desityutils.sp2px(mcontext,default_text_size)); 
 mrippletop = (int)ta.getdimension(r.styleable.waterprogressview_ripple_topheight,desityutils.dp2px(mcontext,default_ripple_topheight)); 
 ta.recycle();

}

定义构造函数,注意 mprogresspaint.setxfermode

//当new该类时调用此构造函数
public waterprogressview(context context) { 
 this(context,null);
}

//当xml文件中定义该自定义view时调用此构造函数
public waterprogressview(context context, attributeset attrs) { 
 this(context, attrs,0);
}

public waterprogressview(context context, attributeset attrs, int defstyleattr) { 
 super(context, attrs, defstyleattr); 
 this.mcontext = context; 
 getattrvalue(attrs); 
 //初始化画笔的相关属性 
 initpaint(); 
 mprogresspath = new path(); 
}

private void initpaint() { 
 //初始化画圆的paint
 mcirclepaint = new paint(); 
 mcirclepaint.setcolor(mcirclecolor); 
 mcirclepaint.setstyle(paint.style.fill); 
 mcirclepaint.setantialias(true); 
 mcirclepaint.setdither(true); 

 //初始化画进度的paint
 mprogresspaint = new paint(); 
 mprogresspaint.setcolor(mprogresscolor); 
 mprogresspaint.setantialias(true); 
 mprogresspaint.setdither(true); 
 mprogresspaint.setstyle(paint.style.fill); 
 //其实mprogresspaint画的也是矩形,当设置xfermode为porterduff.mode.src_in后则显示的为圆与进度矩形的交集,则为半圆
 mprogresspaint.setxfermode(new porterduffxfermode(porterduff.mode.src_in)); 

 //初始化画进度文字的画笔
 mtextpaint = new paint(); 
 mtextpaint.setcolor(mtextcolor); 
 mtextpaint.setstyle(paint.style.fill); 
 mtextpaint.setantialias(true); 
 mtextpaint.setdither(true); 
 mtextpaint.settextsize(mtextsize);

}

onmeasure()方法代码:

@override
protected synchronized void onmeasure(int widthmeasurespec, int heightmeasurespec) { 
 //使用时,需要明确定义该view的尺寸,即用测量模式为measurespec.exactly
 int width = measurespec.getsize(widthmeasurespec); 
 int height = measurespec.getsize(heightmeasurespec); 
 setmeasureddimension(width,height); 

 //初始化bitmap,让所有的drawcircle,drawpath,drawtext都draw在该bitmap所在的canvas上,然后再将该bitmap 画在ondraw方法的canvas上,
 //所以此bitmap的width,height需要减去left,top,right,bottom的padding
 mbitmap = bitmap.createbitmap(width-getpaddingleft()-getpaddingright(),height- getpaddingtop()-getpaddingbottom(), bitmap.config.argb_8888); 
 mpaintcanvas = new canvas(mbitmap);
}

接下来是核心部份,ondraw中的代码。我们先将circle,进度条,进度文字draw到自定义canvas的bitmap上,再将此bitmap draw到ondraw方法中的canvas上。drawcircle与drawtext应该没什么难度,关键点就在于画进度条,怎么画呢?既然有水波纹效果,有曲线,就用drawpath了。

drawpath的流程如下:

Android自定义View实现水面上涨效果

其中ratio的代码如下,即ratio为当前进度占总进度的百分比

float ratio = getprogress()*1.0f/getmax();

因为坐标是从b点向下和向右正向延伸的,则a点的坐标为(width,(1-ratio)*height),其中width为bitmap的宽,height为bitmap的高。我们先将mprogresspath.moveto到a点,然后从a点顺时针方向确定path的各个关键点,如图,则代码如下:

int righttop = (int) ((1-ratio)*height);
mprogresspath.moveto(width,righttop);
mprogresspath.lineto(width,height);
mprogresspath.lineto(0,height);
mprogresspath.lineto(0,righttop);

如此mprogresspath已经lineto到了c点,需要在a点与c点之间形成水波纹效果,则需要在a点与c点间画贝塞尔曲线。

Android自定义View实现水面上涨效果

我们设定波峰最高点为10,则一段波长为40,需要画width*1.0f/40段这样的曲线,则画曲线的代码如下:

int count = (int) math.ceil(width*1.0f/(10 *4));
for(int i=0; i<count; i++) { 
 mprogresspath.rquadto(10,10,2* 10,0);
 mprogresspath.rquadto(10,-10,2* 10,0);  
}

mprogresspath.close();
mpaintcanvas.drawpath(mprogresspath,mprogresspaint);


这样就能画出水面上涨且有波纹效果的进度条了。但我们还要实现随着水面上涨,越接近目标进度,水面波纹应该越来越小,则应该把10抽出为变量定义为mrippletop等初始时波峰最大值,然后定义top为随着进度不断接近目标进度时曲线的实时波峰值 ,其中mtargetprogress为目标progress,因为有一个目标进度才能实现当前进度不断接近目标进度的过程中,水面渐趋于平面的效果:

float top = (mtargetprogress-getprogress())*1.0f/mtargetprogress* mrippletop;

所以drawpath的代码更新如下:

float top = (mtargetprogress-getprogress())*1.0f/mtargetprogress* mrippletop;

for(int i=0; i<count; i++) { 
 mprogresspath.rquadto(mrippletop,top,2* mrippletop,0);
 mprogresspath.rquadto(mrippletop,-top,2* mrippletop,0); 
}

如此就能真正实现水面上涨的进度条了。

但如何实现图中双击时水面从0%上涨到目标进度,单击时水面在目标进度不断涌动的效果呢?
先说说双击效果的实现:这个简单,定义一个handler,,当双击时,handler.postdelayed(runnable,time) ,每隔一段时间progress+1,在runnable中invalidate()不断更新进度,直到当前progress到达mtargetprogress。

代码如下

/**
 * 实现双击动画
 */
private void startdoubletapanimation() { 
 setprogress(0); 
 doubletaphandler.postdelayed(doubletaprunnable,60);
}

private handler doubletaphandler = new handler(){ 
 @override 
 public void handlemessage(message msg) {  
 super.handlemessage(msg); 
 }
};

//双击处理线程,隔60ms发送一次数据
private runnable doubletaprunnable = new runnable() { 
 @override 
 public void run() {  
 if(getprogress() < mtargetprogress) {   
  invalidate();   
  setprogress(getprogress()+1);   
  doubletaphandler.postdelayed(doubletaprunnable,60);  
 } else {   
  doubletaphandler.removecallbacks(doubletaprunnable);  
 } 
 }
};

双击效果实现了,那如何实现单击效果呢?单击时要求水面不断涌动一段时间,水面波纹逐渐变小,然后水面变平。我们可以定义一个msingletapanimationcount变量为水面涌动的次数,然后像双击时的处理一样,定义一个handler隔一段时间发送一次更新界面的message,msingletapanimationcount-- ,然后我们交替地让初始时的波峰一次为正一次为负,则能实现水面涌动的效果。

核心代码如下:

private void startsingletapanimation() { 
 issingletapanimation = true; 
 singletaphandler.postdelayed(singletaprunnable,200);
}

private handler singletaphandler = new handler(){ 
 @override 
 public void handlemessage(message msg) {  
 super.handlemessage(msg); 
 }
};

//单击处理线程,隔200ms发送一次数据
private runnable singletaprunnable = new runnable() { 
 @override 
 public void run() {  
 if(msingletapanimationcount > 0) {   
  invalidate();   
  msingletapanimationcount--;   
  singletaphandler.postdelayed(singletaprunnable,200);  
 } else {   
  singletaphandler.removecallbacks(singletaprunnable);  
 //是否正在进行单击动画  
  issingletapanimation = false;   
 //重置单击动画运行次数为50次
  msingletapanimationcount = 50;  
 } 
 }
};

ondraw中的代码作相应的更改,因单击与双击时drawpath中曲线部分的绘制逻辑不一样,则我们定义一个变量issingletapanimation 区别是正在进行单击动画还是在进行双击动画。

更改后的代码如下:

//画进度
mprogresspath.reset();
//从右上边开始draw path
int righttop = (int) ((1-ratio)*height);
mprogresspath.moveto(width,righttop);
mprogresspath.lineto(width,height);
mprogresspath.lineto(0,height);
mprogresspath.lineto(0,righttop);

//画贝塞尔曲线,形成波浪线
int count = (int) math.ceil(width*1.0f/(mrippletop *4));
//不是单击animation状态
if(!issingletapanimation&&getprogress()>0) { 
 float top = (mtargetprogress-getprogress())*1.0f/mtargetprogress* mrippletop; 
 for(int i=0; i<count; i++) {  
 mprogresspath.rquadto(mrippletop,-top,2* mrippletop,0);   
 mprogresspath.rquadto(mrippletop,top,2* mrippletop,0); 
 }
} else { 
 //单击animation状态,为了将效果放大,将mrippletop放大2倍
 //同时偶数时曲线走向如图所示,奇数时则曲线刚好相反 
 float top = (msingletapanimationcount*1.0f/50)*10; 
 //奇偶数时曲线切换 
 if(msingletapanimationcount%2==0) {  
  for(int i=0; i<count; i++) {   
  mprogresspath.rquadto(mrippletop *2,top*2,2* mrippletop,0);    
  mprogresspath.rquadto(mrippletop *2,-top*2,2* mrippletop,0);   
 } 
 } else {  
 for(int i=0; i<count; i++) {   
  mprogresspath.rquadto(mrippletop *2,-top*2,2* mrippletop,0);    
  mprogresspath.rquadto(mrippletop *2,top*2,2* mrippletop,0); 
 } 
 }
}
mprogresspath.close();
mpaintcanvas.drawpath(mprogresspath,mprogresspaint);

基本上重要的代码与核心逻辑与代码就在上面了。

注意点:

1、当drawcircle时要考虑到padding,则circle的宽和高为getwidth与getheight减去padding值,代码如下:

//自定义bitmap的宽和高
int width = getwidth()-getpaddingleft()-getpaddingright();
int height = getheight()-getpaddingtop()-getpaddingbottom();

//画圆
mpaintcanvas.drawcircle(width/2,height/2,height/2,mcirclepaint);

2、当drawtext时,不是从text的height的中间开始draw的,而是从baseline开始draw的

Android自定义View实现水面上涨效果

那如何获取baseline的height坐标呢

paint.fontmetrics metrics = mtextpaint.getfontmetrics();
//因为ascent在baseline之上,所以ascent为负数。descent+ascent为负数,所以是减而不是加
float baseline = height*1.0f/2 - (metrics.descent+metrics.ascent)/2;

drawtext的全部代码如下:

//画进度文字
string text = ((int)(ratio*100))+"%";

//获得文字的宽度
float textwidth = mtextpaint.measuretext(text);

paint.fontmetrics metrics = mtextpaint.getfontmetrics();
//descent+ascent为负数,所以是减而不是加
float baseline = height*1.0f/2 - (metrics.descent+metrics.ascent)/2;
mpaintcanvas.drawtext(text,width/2-textwidth/2,baseline,mtextpaint);

3、因为要顾及到padding,记得将ondraw中的canvas translate到(getpaddingleft(),getpaddingtop())处。

canvas.translate(getpaddingleft(),getpaddingtop());
canvas.drawbitmap(mbitmap,0,0,null);

最后记得将自定义的bitmap draw到ondraw中的canvas上。到这儿自定义水面上涨效果的进度条于写完了。

总结

以上就是这篇文章的全部内容,希望本文的内容对大家的学习或者工作带来一定的帮助,如果有疑问大家可以留言交流。