【Android开源项目解析】背景有波浪效果的TextView——从Titanic项目学习BitmapShader的使用
2015-09-07 21:22
761 查看
Hello,好久没写文章了,有木有想我呀~
正式工作已经过去一个月了,发现在青岛实习和在北京工作,感觉完全不一样呢~
现在每天晚上回到住的地方,都累的想睡觉…所以也没心情写太多文章和大家分享了,不过我会尽快调整状态,重振雄风的!(哪里起来怪怪的…)
项目介绍
我的想法
实现思路
下面是项目地址
https://github.com/RomainPiel/Titanic
要实现的效果是下面这样滴
我不知道你们会有什么样的思路,我第一眼看到的时候,我想起了PorterDuffXfermode,前面是一张文字的图片,后面一张波浪的图片,然后设置相交模式,从而出现这种文字和图片的交叉效果,不断的改变后面图片位置,实现动态的效果。
当然,我只是想了一想,然后就抱着这个想法看源码去了,看完源码才发现,作者的实现思路更加的NB~通过BitmapShader实现了这种效果,下面,我会参照源码,给你介绍一下实现思路,非常简单,不要眨眼哦~
如果你不想设置特殊字体,那么中间的代码删除也可以,两行代码搞定,是不是非常easy~
那么Titanic这个类是干嘛的呢?
为了方便理解和观看,我将不重要代码省略了,你可以对照源码观看
我们调用了Titanic的start()之后,就执行了下面的代码了:
这段代码是不是非常简单?但是有点奇怪啊,里面为什么声明Runnable对象呢?而且这里没开线程啊,而是直接调用的run(),好诡异的代码!
其实这个不需要多想,作者只不过是为了代码复用,所用把需要的代码放在run()方法,方便调用,但是这个代码风格我并不喜欢,因为这会让其他阅读代码的人产生误会,比如我就为了这一块代码,想了半个多小时他为什么这样写…
我们看不惯,就可以改成下面的这种方式:
都是为了代码复用,这种代码风格我更喜欢一些。
ok,不扯淡,我们看看在start()里面到底做了些什么!
这段代码也很好理解,设置了个标志位,表示我们要开始下沉了哈!然后使用两个ObjectAnimator来进行动画,这个动画是控制干嘛的呢?
看里面的参数,maskX和maskY,作者的注释写的很清楚,maskX的范围是0-200,200是图片的高度,什么图片?当然是后面波浪效果的图片啦,见下图(啥?没有图片!?白色的看不出来…右键保存看吧):
注意哦,这张图片的上半部分是透明的,下面是白色的,宽高为200*300。
maskY的变化范围是h/2到-h/2,h则是TextView的高度,也就是上下浮动高度为h,那么这个maskX和maskY到底是干嘛的,这两个属性控制的是什么呢?
首先,我们知道,如果要使用ObjectAnimator实现这种效果,那么我们的控件里面必须有这个属性的getter和setter才行,所以这两个属性肯定是自定义的,我们去看TitanicTextView的代码验证一下:
果然,我们看到,作者自己定义了这两个成员变量,那么改变这两个值到底有什么作用呢?我们找一下代码里面有什么地方用到了这两个值:
耶,在onDraw()里面,在super.onDraw()之前,用到了这两个参数,这里面有几个变量,我们还没有介绍
sinking 用于判断是否开始浮动动画
shader 这是一个BitmapShader,这是今天我们的男主角
shaderMatrix 这是一个变换矩阵,在这里主要进行了setTranslate操作,参数正是我们的maskX和maskY还有offsetY
这个offsetY是啥子?代码里找找
在最后计算了offsetY,TextView的高度减去waveH,然后除以二,这得到的其实就是wave或者说b的y坐标,在这个基础上,再进行以下操作,就可以实现上下左右移动的动画效果
那么这个shader到底是干嘛的?为啥是我们的男主角?
我们在上面的代码里面,可以看到不仅对offsetY进行了初始化,而且也对shader进行了初始化,里面传入了三个参数
new BitmapShader(b, Shader.TileMode.REPEAT, Shader.TileMode.CLAMP);
第一个参数b是设置Shader要使用的Bitmap对象,第二个参数设置x轴上图像的扩展方式,第三个参数则是y轴上的扩展方式,这个参数共有以下三种
CLAMP :如果渲染器超出原始边界范围,会复制范围内边缘染色
REPEAT :横向和纵向的重复渲染器图片,平铺
MIRROR:镜像方式平铺。
那么上面的写法我们就很明白了,作者在x轴上复制重复,在y轴上渲染边缘颜色,那么,wave得到的Shader就是下面这个样子()
画工咋样…^_^
还要注意一点,就是创建BitmapShader时,第一个参数的得来
首先根据wave的Drawable对象的长宽,构建一个一样大的Bitmap对象,然后去创建了个Canvas对象,然后画了一个和当前文字颜色的背景,这时候,我们Shader上面的透明颜色,就变成了我们的文字颜色,然后给Drawable设置Bounds之后,draw了一下,这样,我们的Bitmap对象,就是一个和wave的Drawable大小相同,下半部分是白色波浪,上半部分是文字颜色背景的一张图片啦,这个时候在创建一个BitmapShader,就可以得到我们想要的了,然后
设置给paint,这样用这支笔写出来的字,就是以我们创建好的BitmapShader为背景的字了~
波浪效果呢?
开启动画之后,改变maskX和maskY,然后用矩阵移动BitmapShader的位置,字体的背景颜色就变化咯,而我们看到的效果就是波浪效果~
还记得maskY的变化范围吗?
h/2 到 -h/2
这样就能够从最下面没有波浪,一直增长到白色波浪沾满整个高度,然后就出现这个效果啦~
如果这篇文章你看明白了,那么另外一个类似的项目,你应该就很容易知道是怎么实现的了吧~
Shimmer-android项目地址: https://github.com/RomainPiel/Shimmer-android
ok,这篇文章就写到这里,下期我们再见,回家看西游记去了
转载请注明出处:http://blog.csdn.net/zhaokaiqiang1992
正式工作已经过去一个月了,发现在青岛实习和在北京工作,感觉完全不一样呢~
现在每天晚上回到住的地方,都累的想睡觉…所以也没心情写太多文章和大家分享了,不过我会尽快调整状态,重振雄风的!(哪里起来怪怪的…)
项目介绍
我的想法
实现思路
项目介绍
这篇文章,会介绍一个开源项目,叫做Titanic,是的,中文名就叫“泰坦尼克”…下面是项目地址
https://github.com/RomainPiel/Titanic
要实现的效果是下面这样滴
我的想法
如果你是一个程序员,那么在你第一眼看到这个效果的时候,你可能会想,如果我要实现类似的效果,应该怎么做呢?我不知道你们会有什么样的思路,我第一眼看到的时候,我想起了PorterDuffXfermode,前面是一张文字的图片,后面一张波浪的图片,然后设置相交模式,从而出现这种文字和图片的交叉效果,不断的改变后面图片位置,实现动态的效果。
当然,我只是想了一想,然后就抱着这个想法看源码去了,看完源码才发现,作者的实现思路更加的NB~通过BitmapShader实现了这种效果,下面,我会参照源码,给你介绍一下实现思路,非常简单,不要眨眼哦~
实现思路
Titanic这个项目非常的简单,只有两个类,分别是Titanic和TitanicTextView,那么如何使用呢?非常简单:TitanicTextView tv = (TitanicTextView) findViewById(R.id.my_text_view); tv.setTypeface(Typefaces.get(this, "Satisfy-Regular.ttf")); new Titanic().start(tv);
如果你不想设置特殊字体,那么中间的代码删除也可以,两行代码搞定,是不是非常easy~
那么Titanic这个类是干嘛的呢?
为了方便理解和观看,我将不重要代码省略了,你可以对照源码观看
我们调用了Titanic的start()之后,就执行了下面的代码了:
public void start(final TitanicTextView textView) { final Runnable animate = new Runnable() { @Override public void run() { textView.setSinking(true); // horizontal animation. 200 = wave.png width ObjectAnimator maskXAnimator = ObjectAnimator.ofFloat(textView, "maskX", 0, 200); maskXAnimator.setRepeatCount(ValueAnimator.INFINITE); maskXAnimator.setDuration(1000); maskXAnimator.setStartDelay(0); int h = textView.getHeight(); // vertical animation // maskY = 0 -> wave vertically centered // repeat mode REVERSE to go back and forth ObjectAnimator maskYAnimator = ObjectAnimator.ofFloat(textView, "maskY", h/2, - h/2); maskYAnimator.setRepeatCount(ValueAnimator.INFINITE); maskYAnimator.setRepeatMode(ValueAnimator.REVERSE); maskYAnimator.setDuration(10000); maskYAnimator.setStartDelay(0); // now play both animations together animatorSet = new AnimatorSet(); animatorSet.playTogether(maskXAnimator, maskYAnimator); animatorSet.setInterpolator(new LinearInterpolator()); animatorSet.addListener(new Animator.AnimatorListener() { @Override public void onAnimationStart(Animator animation) { } @Override public void onAnimationEnd(Animator animation) { textView.setSinking(false); if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) { textView.postInvalidate(); } else { textView.postInvalidateOnAnimation(); } animatorSet = null; } 省略... }); if (animatorListener != null) { animatorSet.addListener(animatorListener); } animatorSet.start(); } if (!textView.isSetUp()) { textView.setAnimationSetupCallback(new TitanicTextView.AnimationSetupCallback() { @Override public void onSetupAnimation(final TitanicTextView target) { animate.run(); } }); } else { animate.run(); } };
这段代码是不是非常简单?但是有点奇怪啊,里面为什么声明Runnable对象呢?而且这里没开线程啊,而是直接调用的run(),好诡异的代码!
其实这个不需要多想,作者只不过是为了代码复用,所用把需要的代码放在run()方法,方便调用,但是这个代码风格我并不喜欢,因为这会让其他阅读代码的人产生误会,比如我就为了这一块代码,想了半个多小时他为什么这样写…
我们看不惯,就可以改成下面的这种方式:
public void start(final TitanicTextView textView) { if (!textView.isSetUp()) { textView.setAnimationSetupCallback(new TitanicTextView.AnimationSetupCallback() { @Override public void onSetupAnimation(final TitanicTextView target) { run(textView); } }); } else { run(textView); } } private void run(final TitanicTextView textView) { textView.setSinking(true); ObjectAnimator maskXAnimator = ObjectAnimator.ofFloat(textView, "maskX", 0, 200); maskXAnimator.setRepeatCount(ValueAnimator.INFINITE); maskXAnimator.setDuration(1000); maskXAnimator.setStartDelay(0); int h = textView.getHeight(); ObjectAnimator maskYAnimator = ObjectAnimator.ofFloat(textView, "maskY", h / 2, -h / 2); maskYAnimator.setRepeatCount(ValueAnimator.INFINITE); maskYAnimator.setRepeatMode(ValueAnimator.REVERSE); maskYAnimator.setDuration(10000); maskYAnimator.setStartDelay(0); // now play both animations together animatorSet = new AnimatorSet(); animatorSet.playTogether(maskXAnimator, maskYAnimator); animatorSet.setInterpolator(new LinearInterpolator()); animatorSet.addListener(new Animator.AnimatorListener() { @Override public void onAnimationStart(Animator animation) { } @Override public void onAnimationEnd(Animator animation) { textView.setSinking(false); if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) { textView.postInvalidate(); } else { textView.postInvalidateOnAnimation(); } animatorSet = null; } @Override public void onAnimationCancel(Animator animation) { } @Override public void onAnimationRepeat(Animator animation) { } }); animatorSet.start(); }
都是为了代码复用,这种代码风格我更喜欢一些。
ok,不扯淡,我们看看在start()里面到底做了些什么!
textView.setSinking(true); ObjectAnimator maskXAnimator = ObjectAnimator.ofFloat(textView, "maskX", 0, 200); maskXAnimator.setRepeatCount(ValueAnimator.INFINITE); maskXAnimator.setDuration(1000); maskXAnimator.setStartDelay(0); int h = textView.getHeight(); ObjectAnimator maskYAnimator = ObjectAnimator.ofFloat(textView, "maskY", h / 2, -h / 2); maskYAnimator.setRepeatCount(ValueAnimator.INFINITE); maskYAnimator.setRepeatMode(ValueAnimator.REVERSE); maskYAnimator.setDuration(10000); maskYAnimator.setStartDelay(0);
这段代码也很好理解,设置了个标志位,表示我们要开始下沉了哈!然后使用两个ObjectAnimator来进行动画,这个动画是控制干嘛的呢?
看里面的参数,maskX和maskY,作者的注释写的很清楚,maskX的范围是0-200,200是图片的高度,什么图片?当然是后面波浪效果的图片啦,见下图(啥?没有图片!?白色的看不出来…右键保存看吧):
注意哦,这张图片的上半部分是透明的,下面是白色的,宽高为200*300。
maskY的变化范围是h/2到-h/2,h则是TextView的高度,也就是上下浮动高度为h,那么这个maskX和maskY到底是干嘛的,这两个属性控制的是什么呢?
首先,我们知道,如果要使用ObjectAnimator实现这种效果,那么我们的控件里面必须有这个属性的getter和setter才行,所以这两个属性肯定是自定义的,我们去看TitanicTextView的代码验证一下:
public float getMaskX() { return maskX; } public void setMaskX(float maskX) { this.maskX = maskX; invalidate(); } public float getMaskY() { return maskY; } public void setMaskY(float maskY) { this.maskY = maskY; invalidate(); }
果然,我们看到,作者自己定义了这两个成员变量,那么改变这两个值到底有什么作用呢?我们找一下代码里面有什么地方用到了这两个值:
@Override protected void onDraw(Canvas canvas) { if (sinking && shader != null) { if (getPaint().getShader() == null) { getPaint().setShader(shader); } shaderMatrix.setTranslate(maskX, maskY + offsetY); shader.setLocalMatrix(shaderMatrix); } else { getPaint().setShader(null); } super.onDraw(canvas); }
耶,在onDraw()里面,在super.onDraw()之前,用到了这两个参数,这里面有几个变量,我们还没有介绍
sinking 用于判断是否开始浮动动画
shader 这是一个BitmapShader,这是今天我们的男主角
shaderMatrix 这是一个变换矩阵,在这里主要进行了setTranslate操作,参数正是我们的maskX和maskY还有offsetY
这个offsetY是啥子?代码里找找
private void createShader() { if (wave == null) { wave = getResources().getDrawable(R.drawable.wave); } int waveW = wave.getIntrinsicWidth(); int waveH = wave.getIntrinsicHeight(); Bitmap b = Bitmap.createBitmap(waveW, waveH, Bitmap.Config.ARGB_8888); Canvas c = new Canvas(b); c.drawColor(getCurrentTextColor()); wave.setBounds(0, 0, waveW, waveH); wave.draw(c); shader = new BitmapShader(b, Shader.TileMode.REPEAT, Shader.TileMode.CLAMP); getPaint().setShader(shader); offsetY = (getHeight() - waveH) / 2; }
在最后计算了offsetY,TextView的高度减去waveH,然后除以二,这得到的其实就是wave或者说b的y坐标,在这个基础上,再进行以下操作,就可以实现上下左右移动的动画效果
shaderMatrix.setTranslate(maskX, maskY + offsetY); shader.setLocalMatrix(shaderMatrix);
那么这个shader到底是干嘛的?为啥是我们的男主角?
我们在上面的代码里面,可以看到不仅对offsetY进行了初始化,而且也对shader进行了初始化,里面传入了三个参数
new BitmapShader(b, Shader.TileMode.REPEAT, Shader.TileMode.CLAMP);
第一个参数b是设置Shader要使用的Bitmap对象,第二个参数设置x轴上图像的扩展方式,第三个参数则是y轴上的扩展方式,这个参数共有以下三种
CLAMP :如果渲染器超出原始边界范围,会复制范围内边缘染色
REPEAT :横向和纵向的重复渲染器图片,平铺
MIRROR:镜像方式平铺。
那么上面的写法我们就很明白了,作者在x轴上复制重复,在y轴上渲染边缘颜色,那么,wave得到的Shader就是下面这个样子()
画工咋样…^_^
还要注意一点,就是创建BitmapShader时,第一个参数的得来
int waveW = wave.getIntrinsicWidth(); int waveH = wave.getIntrinsicHeight(); Bitmap b = Bitmap.createBitmap(waveW, waveH, Bitmap.Config.ARGB_8888); Canvas c = new Canvas(b); c.drawColor(getCurrentTextColor()); wave.setBounds(0, 0, waveW, waveH); wave.draw(c); shader = new BitmapShader(b, Shader.TileMode.REPEAT, Shader.TileMode.CLAMP);
首先根据wave的Drawable对象的长宽,构建一个一样大的Bitmap对象,然后去创建了个Canvas对象,然后画了一个和当前文字颜色的背景,这时候,我们Shader上面的透明颜色,就变成了我们的文字颜色,然后给Drawable设置Bounds之后,draw了一下,这样,我们的Bitmap对象,就是一个和wave的Drawable大小相同,下半部分是白色波浪,上半部分是文字颜色背景的一张图片啦,这个时候在创建一个BitmapShader,就可以得到我们想要的了,然后
getPaint().setShader(shader);
设置给paint,这样用这支笔写出来的字,就是以我们创建好的BitmapShader为背景的字了~
波浪效果呢?
开启动画之后,改变maskX和maskY,然后用矩阵移动BitmapShader的位置,字体的背景颜色就变化咯,而我们看到的效果就是波浪效果~
还记得maskY的变化范围吗?
h/2 到 -h/2
这样就能够从最下面没有波浪,一直增长到白色波浪沾满整个高度,然后就出现这个效果啦~
如果这篇文章你看明白了,那么另外一个类似的项目,你应该就很容易知道是怎么实现的了吧~
Shimmer-android项目地址: https://github.com/RomainPiel/Shimmer-android
ok,这篇文章就写到这里,下期我们再见,回家看西游记去了
转载请注明出处:http://blog.csdn.net/zhaokaiqiang1992
相关文章推荐
- 使用C++实现JNI接口需要注意的事项
- Android IPC进程间通讯机制
- Android Manifest 用法
- [转载]Activity中ConfigChanges属性的用法
- Android之获取手机上的图片和视频缩略图thumbnails
- Android之使用Http协议实现文件上传功能
- Android学习笔记(二九):嵌入浏览器
- android string.xml文件中的整型和string型代替
- i-jetty环境搭配与编译
- android之定时器AlarmManager
- android wifi 无线调试
- Android Native 绘图方法
- Android java 与 javascript互访(相互调用)的方法例子
- android 代码实现控件之间的间距
- android FragmentPagerAdapter的“标准”配置
- Android"解决"onTouch和onClick的冲突问题
- android:installLocation简析
- android searchView的关闭事件
- SourceProvider.getJniDirectories