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

Android自定义View实现水面上涨效果,第1张

概述实现效果如下:实现思路:1、如何实现圆中水面上涨效果:利用Paint的setXfermode属性为PorterDuff.Mode.SRC_IN画出进度所在的矩形与圆的交集实现

实现效果如下:

实现思路:

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,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()方法代码:

@OverrIDeprotected 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的流程如下:


其中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,righttop);

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


我们设定波峰最高点为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,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,2* mRippletop,0); mProgresspath.rQuadTo(mRippletop,-top,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 pathint righttop = (int) ((1-ratio)*height);mProgresspath.moveto(wIDth,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,0);    mProgresspath.rQuadTo(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,0);      mProgresspath.rQuadTo(mRippletop *2,-top*2,0);    }  } else {   for(int i=0; i<count; i++) {     mProgresspath.rQuadTo(mRippletop *2,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,mCirclePaint);

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

那如何获取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,null);

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

总结

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

总结

以上是内存溢出为你收集整理的Android自定义View实现水面上涨效果全部内容,希望文章能够帮你解决Android自定义View实现水面上涨效果所遇到的程序开发问题。

如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。

欢迎分享,转载请注明来源:内存溢出

原文地址: https://www.outofmemory.cn/web/1148098.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-31
下一篇 2022-05-31

发表评论

登录后才能评论

评论列表(0条)

保存