热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

水波纹效果实现

先来贴一下要实现的最终效果:由于截图软件的原因所以上图看着有些卡,在实际中是比较流畅滴,下面开始来一步步实现它。特殊字体处理:

先来贴一下要实现的最终效果:

由于截图软件的原因所以上图看着有些卡,在实际中是比较流畅滴,下面开始来一步步实现它。

特殊字体处理:

新建工程,然后先来显示一下特殊字体,对于android中如何使用自定义字体这里不多赘述了,比较简单,先将字体文件放至到asserts目录当中:

然后新建一个自定义View,在新建之前需要针对这个效果进行一个思考:是直接继承View开搞么?当然是毫无问题滴,但是!!从效果来看其实也就是对文字进行了相应的显示处理,而对于文字的显示Android提供的TextView已经完全满足要求,那从便捷性来讲继承TextView来进行相应的拓展应该是最好的方式,所以:

然后接着做一些初始化,咱们首先先来显示一个静态的文字"Loading",如下:

然后在xml中进行声明:

编译运行:

准备一个着色器:

接下来就是要给咱们的静态文字进行“喷漆”,而对应的在Android中有一个着色器类似的概念,而要实现水波纹着色的效果,这里需要用到一张图片: 

其实现原理就是通过着色器将这个图片按一定的移动来覆盖到咱们的文字上来,注意:并不是普通上的覆盖,因为只是文字区域,其它背景是不会被这个着色器给影响到的,关于着色器的使用可能是比较陌生,下面首先先创建一个着色器为之后的效果做准备,如下:

而对应的类就是"BitmapShader",如下:

下面来创建它:

要构造它需要传三个参数,第一个为bitmap,看一下它的参数描述:

而着色器要用到的图片就是前面说的那种水波效果的,那直接获取它的bitmap么?我们知道对于res中的图片可能直接获取它的Bitmap对象,如下:

但是此的图片非透明区域是白色的,而我们想给文字着色用红色,所以咱们应该基于原图要对属性进行修改,这时就得用动态创建Bitmap的方法了,如下:

看一下Bitmap.createBitmap这个静态方法的官方说明:

好,接着还差前两个参数目前还没有准备好,得获取原图的宽高信息,这时就得通过加载原图来获取啦,如下:

ok,目前咱们创建的bitmap对象是一个里面木有图片内容的,所以接下来就是将原始Drawable中的信息写到咱们这个可变的bitmap对象中,这里得借助于Canvas了,如下:

然后这个canvas当然得对着咱们创建的bitmap对象啦,如下:

好,着色器的第一个bitmap参数搞定,接下来再来准备它另外两个参数:

tileX是x方向绘制图片的平铺模式,而tileY是y方向绘制图片的平铺模式,其中TileMode是一个枚举值,如下:

对于这三个模式有啥区别,可以参考如下两个博客:

1、https://blog.csdn.net/lmj623565791/article/details/41967509 其如下说明:

2、https://blog.csdn.net/chen19960724/article/details/52799593 其有三个直观的效果图,如下:

清楚了三个枚举值的含义之后,回到咱们的例子,根据咱们的这个水波纹图片,x方向应该是不断的重复,使用REPEAT,而y方向应该是使用拉伸,所以:

貌似着色器创建成功了,但是还差一个细节:

其实对于Drawable.draw()方法在官方有说明:

最后还需要将着色器设置到paint上,如下:

/*** 实现字体水波纹的效果--准备一个着色器对文字进行着色*/
@SuppressLint("AppCompatCustomView")
public class WaterView extends TextView {/* 着色器 */private BitmapShader bitmapShader;public WaterView(Context context) {this(context, null);}public WaterView(Context context, @Nullable AttributeSet attrs) {this(context, attrs, -1);}public WaterView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);init();}private void init() {this.setTextColor(Color.RED);//自定义字体this.setTypeface(Typeface.createFromAsset(getResources().getAssets(), "Satisfy-Regular.ttf"));}@Overrideprotected void onSizeChanged(int w, int h, int oldw, int oldh) {super.onSizeChanged(w, h, oldw, oldh);//创建一个着色器createShader();}private void createShader() {//注意:通过getResources()方法获取的Drawable是没有边界的,需要手动设置边界。Drawable waveDrawable = getResources().getDrawable(R.drawable.wave);//获得水波图片的原始宽高,也就是类似于图片属性里面的宽高信息int waveWidth = waveDrawable.getIntrinsicWidth();int waveHeight = waveDrawable.getIntrinsicHeight();Bitmap bitmap = Bitmap.createBitmap(waveWidth, waveHeight, Bitmap.Config.ARGB_8888);//创建一个画布,为了将wave图片颜色数据写入到Bitmap中Canvas canvas = new Canvas(bitmap);//画布必须有一个颜色,否则无法将图片数据写入canvas.drawColor(getCurrentTextColor());//如果wave沒有边界,则canvas是无法绘制的,所以设置一下边界:waveDrawable.setBounds(0, 0, waveWidth, waveHeight);waveDrawable.draw(canvas);/*** TileMode.CLAMP:使用原来的那张图片整体。* TileMode.REPEAT:将原来的图片复数无数份。* TileMode.MIRROR:镜像,将原来的图片镜像后,写入,再镜像再写入,以此类推*/bitmapShader = new BitmapShader(bitmap, Shader.TileMode.REPEAT, Shader.TileMode.CLAMP);//利用着色器进行着色getPaint().setShader(bitmapShader);}
}

至此着色器就创建完成。

实现着色器的平移:

原理:

接下来就是要利用着色器进行相应的平移操作了,其绘制工作肯定是在它的onDraw()方法里面啦,所以先重写它:

那如何实现,先来说明一下原理:

用图来表示一下:

所以此时画布就变成这样了:

当然其wave图片的画布目前是写成了红色了,如下:

所以准备来说图应该为:

但是目前有个问题~~就是由于水波纹的图片上半部份是空白区域,如果以图的左上角进行画布移动那会存在一段时间看不到水波纹覆盖文字的效果了,所以此时应该将基准点上移一下,如下:

而要想有水波纹效果,则需要水平方向和垂直方向不断移动这个着色器来达到目的,这里以垂直方向为例来看一下它的移动过程:

也就是移动到文字底部之后需要将位置进行还原,所以这里涉及到着色器x方向和y方向的平移,而x的初值为图片的0,而y的初值为-waveHeight[图片高度] / 2。

具体实现:

明白了整个实现的原理之后,下面首先来定义一下着色器平移x和y的初始值:

/*** 实现字体水波纹的效果--实现着色器的平移*/
@SuppressLint("AppCompatCustomView")
public class WaterView extends TextView {/* 着色器 */private BitmapShader bitmapShader;private float shaderX;private float shaderY;public WaterView(Context context) {this(context, null);}public WaterView(Context context, @Nullable AttributeSet attrs) {this(context, attrs, -1);}public WaterView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);init();}private void init() {this.setTextColor(Color.RED);//自定义字体this.setTypeface(Typeface.createFromAsset(getResources().getAssets(), "Satisfy-Regular.ttf"));}@Overrideprotected void onSizeChanged(int w, int h, int oldw, int oldh) {super.onSizeChanged(w, h, oldw, oldh);//创建一个着色器createShader();}private void createShader() {//注意:通过getResources()方法获取的Drawable是没有边界的,需要手动设置边界。Drawable waveDrawable = getResources().getDrawable(R.drawable.wave);//获得水波图片的原始宽高,也就是类似于图片属性里面的宽高信息int waveWidth = waveDrawable.getIntrinsicWidth();int waveHeight = waveDrawable.getIntrinsicHeight();Bitmap bitmap = Bitmap.createBitmap(waveWidth, waveHeight, Bitmap.Config.ARGB_8888);//创建一个画布,为了将wave图片颜色数据写入到Bitmap中Canvas canvas = new Canvas(bitmap);//画布必须有一个颜色,否则无法将图片数据写入canvas.drawColor(getCurrentTextColor());//如果wave沒有边界,则canvas是无法绘制的,所以设置一下边界:waveDrawable.setBounds(0, 0, waveWidth, waveHeight);waveDrawable.draw(canvas);/*** TileMode.CLAMP:使用原来的那张图片整体。* TileMode.REPEAT:将原来的图片复数无数份。* TileMode.MIRROR:镜像,将原来的图片镜像后,写入,再镜像再写入,以此类推*/bitmapShader = new BitmapShader(bitmap, Shader.TileMode.REPEAT, Shader.TileMode.CLAMP);//利用着色器进行着色getPaint().setShader(bitmapShader);shaderX = 0;shaderY = -waveHeight / 2;}@Overrideprotected void onDraw(Canvas canvas) {super.onDraw(canvas);}
}

接着就是需要让咱们的着色器bitmapShader进行水平和垂直方向的移动了,这里就需要用到一个矩阵类,如下:

然后用它来进行着色器的平移,具体写法如下:

而要不断的重绘需要在onDraw()一次之后执行一个invalidate,如下:

而每次刷新时其shaderX和shaderY的值肯定是需要不断变化的,所以这里先初始这样更新值:

编译运行看效果:

呃,貌似木有水波的效果,这是因为这个每次改变垂直与水平的加值调得不对,所以接下来调整一下,貌似垂直方向走得过快,所以先将它的加值调小:

再看:

 

嗯~~貌似差不多,接着水平方向的平移幅度过于小以至于看不到水波滚动的效果了,所以加大加值:

编译运行:

嗯~~效果出来了,但是!!当整个文字被着色满之后应该又回到顶点重新开始着色,也就是当垂直方向着色器移动一定之后得将它的x值恢复成默认初值,那移动到多少的位置其y值得还原呢?应该是移动的高度大于其Loading文本的高度,所以还需要加一个极限值判断条件,如下:

/*** 实现字体水波纹的效果--实现着色器的平移*/
@SuppressLint("AppCompatCustomView")
public class WaterView extends TextView {/* 着色器 */private BitmapShader bitmapShader;private float shaderX;private float shaderY;private Matrix matrix;private int waveHeight;/* 文本loading的高度 */private int textHeight;public WaterView(Context context) {this(context, null);}public WaterView(Context context, @Nullable AttributeSet attrs) {this(context, attrs, -1);}public WaterView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);init();}private void init() {this.setTextColor(Color.RED);//自定义字体this.setTypeface(Typeface.createFromAsset(getResources().getAssets(), "Satisfy-Regular.ttf"));//matrix:矩阵,可以实现视图的平移旋转等效果matrix = new Matrix();}@Overrideprotected void onSizeChanged(int w, int h, int oldw, int oldh) {super.onSizeChanged(w, h, oldw, oldh);//创建一个着色器createShader();textHeight = h;}private void createShader() {//注意:通过getResources()方法获取的Drawable是没有边界的,需要手动设置边界。Drawable waveDrawable = getResources().getDrawable(R.drawable.wave);//获得水波图片的原始宽高,也就是类似于图片属性里面的宽高信息int waveWidth = waveDrawable.getIntrinsicWidth();waveHeight = waveDrawable.getIntrinsicHeight();Bitmap bitmap = Bitmap.createBitmap(waveWidth, waveHeight, Bitmap.Config.ARGB_8888);//创建一个画布,为了将wave图片颜色数据写入到Bitmap中Canvas canvas = new Canvas(bitmap);//画布必须有一个颜色,否则无法将图片数据写入canvas.drawColor(getCurrentTextColor());//如果wave沒有边界,则canvas是无法绘制的,所以设置一下边界:waveDrawable.setBounds(0, 0, waveWidth, waveHeight);waveDrawable.draw(canvas);/*** TileMode.CLAMP:使用原来的那张图片整体。* TileMode.REPEAT:将原来的图片复数无数份。* TileMode.MIRROR:镜像,将原来的图片镜像后,写入,再镜像再写入,以此类推*/bitmapShader = new BitmapShader(bitmap, Shader.TileMode.REPEAT, Shader.TileMode.CLAMP);//利用着色器进行着色getPaint().setShader(bitmapShader);shaderX = 0;shaderY = -waveHeight / 2;}@Overrideprotected void onDraw(Canvas canvas) {super.onDraw(canvas);shaderX += 5;shaderY += 0.4;if (shaderY > -waveHeight / 2 + textHeight) {shaderY = -waveHeight / 2;shaderX = 0;}//让BitmapShader不断向下和向右移动matrix.setTranslate(shaderX, shaderY);//为着色器设置matrix,就可以实现着色器的移动bitmapShader.setLocalMatrix(matrix);invalidate();}
}

最终编译运行:

可以看到为啥创建着色器BitmapShader对象给水平方向的模式是重复,因为x方向是需要不断进行水平移动,而又要有水波纹的效果,不重复去将图拼接就达不到这种水波纹的效果嘛,通过这个例子可以好好体会一下着色器的应用~



推荐阅读
  • android listview OnItemClickListener失效原因
    最近在做listview时发现OnItemClickListener失效的问题,经过查找发现是因为button的原因。不仅listitem中存在button会影响OnItemClickListener事件的失效,还会导致单击后listview每个item的背景改变,使得item中的所有有关焦点的事件都失效。本文给出了一个范例来说明这种情况,并提供了解决方法。 ... [详细]
  • 本文介绍了Android 7的学习笔记总结,包括最新的移动架构视频、大厂安卓面试真题和项目实战源码讲义。同时还分享了开源的完整内容,并提醒读者在使用FileProvider适配时要注意不同模块的AndroidManfiest.xml中配置的xml文件名必须不同,否则会出现问题。 ... [详细]
  • Nginx使用AWStats日志分析的步骤及注意事项
    本文介绍了在Centos7操作系统上使用Nginx和AWStats进行日志分析的步骤和注意事项。通过AWStats可以统计网站的访问量、IP地址、操作系统、浏览器等信息,并提供精确到每月、每日、每小时的数据。在部署AWStats之前需要确认服务器上已经安装了Perl环境,并进行DNS解析。 ... [详细]
  • 在Android开发中,使用Picasso库可以实现对网络图片的等比例缩放。本文介绍了使用Picasso库进行图片缩放的方法,并提供了具体的代码实现。通过获取图片的宽高,计算目标宽度和高度,并创建新图实现等比例缩放。 ... [详细]
  • 在说Hibernate映射前,我们先来了解下对象关系映射ORM。ORM的实现思想就是将关系数据库中表的数据映射成对象,以对象的形式展现。这样开发人员就可以把对数据库的操作转化为对 ... [详细]
  • XML介绍与使用的概述及标签规则
    本文介绍了XML的基本概念和用途,包括XML的可扩展性和标签的自定义特性。同时还详细解释了XML标签的规则,包括标签的尖括号和合法标识符的组成,标签必须成对出现的原则以及特殊标签的使用方法。通过本文的阅读,读者可以对XML的基本知识有一个全面的了解。 ... [详细]
  • imx6ull开发板驱动MT7601U无线网卡的方法和步骤详解
    本文详细介绍了在imx6ull开发板上驱动MT7601U无线网卡的方法和步骤。首先介绍了开发环境和硬件平台,然后说明了MT7601U驱动已经集成在linux内核的linux-4.x.x/drivers/net/wireless/mediatek/mt7601u文件中。接着介绍了移植mt7601u驱动的过程,包括编译内核和配置设备驱动。最后,列举了关键词和相关信息供读者参考。 ... [详细]
  • 本文介绍了如何使用C#制作Java+Mysql+Tomcat环境安装程序,实现一键式安装。通过将JDK、Mysql、Tomcat三者制作成一个安装包,解决了客户在安装软件时的复杂配置和繁琐问题,便于管理软件版本和系统集成。具体步骤包括配置JDK环境变量和安装Mysql服务,其中使用了MySQL Server 5.5社区版和my.ini文件。安装方法为通过命令行将目录转到mysql的bin目录下,执行mysqld --install MySQL5命令。 ... [详细]
  • 本文介绍了Java工具类库Hutool,该工具包封装了对文件、流、加密解密、转码、正则、线程、XML等JDK方法的封装,并提供了各种Util工具类。同时,还介绍了Hutool的组件,包括动态代理、布隆过滤、缓存、定时任务等功能。该工具包可以简化Java代码,提高开发效率。 ... [详细]
  • eclipse学习(第三章:ssh中的Hibernate)——11.Hibernate的缓存(2级缓存,get和load)
    本文介绍了eclipse学习中的第三章内容,主要讲解了ssh中的Hibernate的缓存,包括2级缓存和get方法、load方法的区别。文章还涉及了项目实践和相关知识点的讲解。 ... [详细]
  • 本文介绍了在Vue项目中如何结合Element UI解决连续上传多张图片及图片编辑的问题。作者强调了在编码前要明确需求和所需要的结果,并详细描述了自己的代码实现过程。 ... [详细]
  • HTML学习02 图像标签的使用和属性
    本文介绍了HTML中图像标签的使用和属性,包括定义图像、定义图像地图、使用源属性和替换文本属性。同时提供了相关实例和注意事项,帮助读者更好地理解和应用图像标签。 ... [详细]
  • 图片复制到服务器 方向变了_双服务器热备更新配置文件步骤问题及解决方法
    本文介绍了在将图片复制到服务器并进行方向变换的过程中,双服务器热备更新配置文件所出现的问题及解决方法。通过停止所有服务、更新配置、重启服务等操作,可以避免数据中断和操作不规范导致的问题。同时还提到了注意事项,如Avimet版本的差异以及配置文件和批处理文件的存放路径等。通过严格执行切换步骤,可以成功进行更新操作。 ... [详细]
  • Html5-Canvas实现简易的抽奖转盘效果
    本文介绍了如何使用Html5和Canvas标签来实现简易的抽奖转盘效果,同时使用了jQueryRotate.js旋转插件。文章中给出了主要的html和css代码,并展示了实现的基本效果。 ... [详细]
  • 本文介绍了在Linux下安装和配置Kafka的方法,包括安装JDK、下载和解压Kafka、配置Kafka的参数,以及配置Kafka的日志目录、服务器IP和日志存放路径等。同时还提供了单机配置部署的方法和zookeeper地址和端口的配置。通过实操成功的案例,帮助读者快速完成Kafka的安装和配置。 ... [详细]
author-avatar
shaihaiyou
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有