我的编程空间,编程开发者的网络收藏夹
学习永远不晚

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

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

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

实现效果如下:

实现思路:

1、如何实现圆中水面上涨效果:利用Paint的setXfermode属性为PorterDuff.Mode.class="lazy" data-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.class="lazy" data-src_IN后则显示的为圆与进度矩形的交集,则为半圆
 mProgressPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.class="lazy" data-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的流程如下:


其中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点间画贝塞尔曲线。


我们设定波峰最高点为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的

那如何获取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上。到这儿自定义水面上涨效果的进度条于写完了。

总结

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

您可能感兴趣的文章:Android 自定义View的使用介绍Android自定义View设定到FrameLayout布局中实现多组件显示的方法 分享Android自定义View实现广告信息上下滚动效果Android自定义View实现带数字的进度条实例代码Android自定义View之酷炫圆环(二)Android自定义view制作绚丽的验证码Android自定义View之酷炫数字圆环Android自定义View过程解析Android自定义View软键盘实现搜索实例讲解Android中的View类以及自定义View控件的方法


免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

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

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

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

实现效果如下:实现思路:1、如何实现圆中水面上涨效果:利用Paint的setXfermode属性为PorterDuff.Mode.SRC_IN画出进度所在的矩形与圆的交集实现 2、如何水波纹效果:利用贝塞尔曲线,动态改变波峰值,实现“随着进
2022-06-06

Android自定义View实现水波纹效果

介绍:水波纹散开效果的控件在 App 里面还是比较常见的,例如 网易云音乐歌曲识别,附近搜索场景。看下实现的效果:实现思路: 先将最大圆半径与最小圆半径间距分成几等份,从内到外,Paint 透明度依次递减,绘制出同心圆,然后不断的改变这些同
2023-05-30

Android 自定义view实现水波纹动画效果

在实际的开发中,很多时候还会遇到相对比较复杂的需求,比如产品妹纸或UI妹纸在哪看了个让人兴奋的效果,兴致高昂的来找你,看了之后目的很明确,当然就是希望你能给她;在这样的关键时候,身子板就一定得硬了,可千万别说不行,爷们儿怎么能说不行呢;好了
2023-05-31

Android自定义view实现水波纹进度球效果

今天我们要实现的这个view没有太多交互性的view,所以就继承view。 自定义view的套路,套路很深 1、获取我们自定义属性attrs(可省略) 2、重写onMeasure方法,计算控件的宽和高 3、重
2022-06-06

Android自定义View 实现水波纹动画引导效果

一、实现效果图二、实现代码 1.自定义viewpackage com.czhappy.showintroduce.view; import android.content.Context; import android.graphics.B
2022-06-06

android自定义view实现钟表效果

本文实例为大家分享了android view实现钟表的具体代码,供大家参考,具体内容如下 先看效果图:自定义view大家肯定已经不陌生了,所以直接今天直接步入正题:如何利用canvas去绘制出一个钟表 当然绘制之前我们必须进行测量(重写on
2022-06-06

Android自定义View实现折线图效果

下面就是结果图(每种状态用一个表情图片表示):一、主页面的布局文件如下:
2022-06-06

Android自定义Animation实现View摇摆效果

使用自定义Animation,实现View的左右摇摆效果,如图所示:代码很简单,直接上源码 activity_maini.xml布局文件:
2022-06-06

Android自定义View实现打字机效果

一、先来看看效果演示二、实现原理:这个其实不难实现,通过一个定时器不断调用TextView的setText就行了,在setText的时候播放打字的音效。 具体代码如下:import java.util.Timer; import java.
2022-06-06

Android中自定义view实现侧滑效果

效果图:看网上的都是两个view拼接,默认右侧的不显示,水平移动的时候把右侧的view显示出来。但是看最新版QQ上的效果不是这样的,但给人的感觉却很好,所以献丑来一发比较高仿的。 知识点: 1、ViewDragHelper 的用法; 2、
2022-06-06

Android自定义View实现圆环交替效果

下面请先看效果图:看上去是不很炫的样子,它的实现上也不是很复杂,重点在与onDraw()方法的绘制。 首先是我们的attrs文件:
2022-06-06

Android自定义View实现闪耀字体效果

本文实例为大家分享了闪耀字体效果的具体代码,供大家参考,具体内容如下import android.content.Context; import android.graphics.Canvas; import android.graphic
2022-06-06

编程热搜

  • Android:VolumeShaper
    VolumeShaper(支持版本改一下,minsdkversion:26,android8.0(api26)进一步学习对声音的编辑,可以让音频的声音有变化的播放 VolumeShaper.Configuration的三个参数 durati
    Android:VolumeShaper
  • Android崩溃异常捕获方法
    开发中最让人头疼的是应用突然爆炸,然后跳回到桌面。而且我们常常不知道这种状况会何时出现,在应用调试阶段还好,还可以通过调试工具的日志查看错误出现在哪里。但平时使用的时候给你闹崩溃,那你就欲哭无泪了。 那么今天主要讲一下如何去捕捉系统出现的U
    Android崩溃异常捕获方法
  • android开发教程之获取power_profile.xml文件的方法(android运行时能耗值)
    系统的设置–>电池–>使用情况中,统计的能耗的使用情况也是以power_profile.xml的value作为基础参数的1、我的手机中power_profile.xml的内容: HTC t328w代码如下:
    android开发教程之获取power_profile.xml文件的方法(android运行时能耗值)
  • Android SQLite数据库基本操作方法
    程序的最主要的功能在于对数据进行操作,通过对数据进行操作来实现某个功能。而数据库就是很重要的一个方面的,Android中内置了小巧轻便,功能却很强的一个数据库–SQLite数据库。那么就来看一下在Android程序中怎么去操作SQLite数
    Android SQLite数据库基本操作方法
  • ubuntu21.04怎么创建桌面快捷图标?ubuntu软件放到桌面的技巧
    工作的时候为了方便直接打开编辑文件,一些常用的软件或者文件我们会放在桌面,但是在ubuntu20.04下直接直接拖拽文件到桌面根本没有效果,在进入桌面后发现软件列表中的软件只能收藏到面板,无法复制到桌面使用,不知道为什么会这样,似乎并不是很
    ubuntu21.04怎么创建桌面快捷图标?ubuntu软件放到桌面的技巧
  • android获取当前手机号示例程序
    代码如下: public String getLocalNumber() { TelephonyManager tManager =
    android获取当前手机号示例程序
  • Android音视频开发(三)TextureView
    简介 TextureView与SurfaceView类似,可用于显示视频或OpenGL场景。 与SurfaceView的区别 SurfaceView不能使用变换和缩放等操作,不能叠加(Overlay)两个SurfaceView。 Textu
    Android音视频开发(三)TextureView
  • android获取屏幕高度和宽度的实现方法
    本文实例讲述了android获取屏幕高度和宽度的实现方法。分享给大家供大家参考。具体分析如下: 我们需要获取Android手机或Pad的屏幕的物理尺寸,以便于界面的设计或是其他功能的实现。下面就介绍讲一讲如何获取屏幕的物理尺寸 下面的代码即
    android获取屏幕高度和宽度的实现方法
  • Android自定义popupwindow实例代码
    先来看看效果图:一、布局
  • Android第一次实验
    一、实验原理 1.1实验目标 编程实现用户名与密码的存储与调用。 1.2实验要求 设计用户登录界面、登录成功界面、用户注册界面,用户注册时,将其用户名、密码保存到SharedPreference中,登录时输入用户名、密码,读取SharedP
    Android第一次实验

目录