天气渐热,来片雪花降降温——Android自定义SurfaceView实现雪花效果
实现雪花的效果其实也可以通过自定义View的方式来实现的(SurfaceView也是继承自View的),而且操作上也相对简单一些,当然也有一些不足啦...
相对于View,SurfaceView有如下特点:
(1)SurfaceView可以直接获取Canvas对象,在非UI线程里也可以进行绘制;
(2)SurfaceView支持双缓冲技术,具有更高的绘图效率;
(3)Surface系列产品也火了一阵子了,用Surface准没错.....(好吧,我承认微软给了我很大一笔广告费....想象ing...)
先上图:
(1)原图:
a.雪花(snow_flake.png),由于是白色的所以看不见(虚线之间)
------
------
b.背景(snow_bg0.png)

(2) 效果截图(雪花的大小、数量、下落速度等都是可通过xml属性调节的):

下面开始实现自定义SurfaceView...
1. 首先确定一片雪花所需要的参数:长、宽、在屏幕上的坐标、下落的水平/垂直速度....恩先这些吧,把它们封装到一个类里面:
public class SnowFlake {
private int mWidth;
private int mHeight;
private int mX;
private int mY;
private int mSpeedX;
private int mSpeedY;
public int getHeight() {
return mHeight;
}
public void setHeight(int height) {
this.mHeight = height;
}
public int getSpeedX() {
return mSpeedX;
}
public void setSpeedX(int speedX) {
this.mSpeedX = mSpeedX;
}
public int getSpeedY() {
return mSpeedY;
}
public void setSpeedY(int speedY) {
this.mSpeedY = speedY;
}
public int getWidth() {
return mWidth;
}
public void setWidth(int width) {
this.mWidth = width;
}
public int getX() {
return mX;
}
public void setX(int x) {
this.mX = x;
}
public int getY() {
return mY;
}
public void setY(int y) {
this.mY = y;
}
}
2. 在res/values下新建 attrs.xml 文件,自定义几个属性值:雪花的数量、最大/ 小尺寸、下落速度、资源图片等,更改如下:
<?xml version="1.0" encoding="utf-8"?> <resources>
<attr name="flakeCount" format="integer"/>
<attr name="minSize" format="integer"/>
<attr name="maxSize" format="integer"/>
<attr name="flakeSrc" format="reference|integer"/>
<attr name="speedX" format="integer"/>
<attr name="speedY" format="integer"/> <declare-styleable name="Snow">
<attr name="flakeCount"/>
<attr name="minSize"/>
<attr name="maxSize"/>
<attr name="flakeSrc"/>
<attr name="speedX"/>
<attr name="speedY"/>
</declare-styleable>
</resources>
3. 下面轮到SurfaceView出场...啊不...是SurfaceView的son出场了........
(1)定义名称为Snow的类,扩展SurfaceView,并实现接口 SurfaceHolder.Callback,代码如下:
public class Snow extends SurfaceView implements SurfaceHolder.Callback {
public Snow(Context context) {
this(context, null);
}
public Snow(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public Snow(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
public void surfaceCreated(SurfaceHolder holder) {
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
}
}
(2)添加以下变量,初始化默认值:
private SurfaceHolder mHolder;
private SnowFlake[] mFlakes;
private int mViewWidth = 200;
private int mViewHeight = 100;
private int mFlakeCount = 20;
private int mMinSize = 50;
private int mMaxSize = 70;
private int mSpeedX = 10;
private int mSpeedY = 20;
private Bitmap mSnowBitmap = null;
private boolean mStart = false;
(3)在构造函数中获取控件属性值,并初始化 SurfaceHolder (注意我们只需在最后一个构造函数实现即可,前面的两个通过this来调用此构造函数):
public Snow(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initHolder();
setZOrderOnTop(true);
TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.Snow, defStyleAttr, 0);
int cnt = array.getIndexCount();
for (int i = 0; i < cnt; i++) {
int attr = array.getIndex(i);
switch (attr) {
case R.styleable.Snow_flakeCount:
mFlakeCount = array.getInteger(attr, 0);
break;
case R.styleable.Snow_minSize:
mMinSize = array.getInteger(attr, 50);
break;
case R.styleable.Snow_maxSize:
mMaxSize = array.getInteger(attr, 70);
break;
case R.styleable.Snow_flakeSrc:
Integer srcId = array.getResourceId(attr, R.drawable.snow_flake);
mSnowBitmap = BitmapFactory.decodeResource(getResources(), srcId);
break;
case R.styleable.Snow_speedX:
mSpeedX = array.getInteger(attr, 10);
break;
case R.styleable.Snow_speedY:
mSpeedY = array.getInteger(attr, 10);
break;
default:
break;
}
}
if (mMinSize > mMaxSize) {
mMaxSize = mMinSize;
}
array.recycle();
}
初始化 SurfaceHolder 部分:
private void initHolder() {
mHolder = this.getHolder();
mHolder.setFormat(PixelFormat.TRANSLUCENT);
mHolder.addCallback(this);
}
(4)在Snow类中添加如下变量,并重写 onMeasure() 函数,测量SurfaceView的大小:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//--- measure the view's width
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
if (widthMode == MeasureSpec.EXACTLY) {
mViewWidth = MeasureSpec.getSize(widthMeasureSpec);
} else {
mViewWidth = (getPaddingStart() + mSnowBitmap.getWidth() + getPaddingEnd());
} //--- measure the view's height
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
if (heightMode == MeasureSpec.EXACTLY) {
mViewHeight = MeasureSpec.getSize(heightMeasureSpec);
} else {
mViewHeight = (getPaddingTop() + mSnowBitmap.getHeight() + getPaddingBottom());
} setMeasuredDimension(mViewWidth, mViewHeight);
}
(5)初始化snow flakes的函数:通过随机数生成一定范围内的坐标值和snow flake 的大小值,一开始时雪花是在屏幕上方的:
private void initSnowFlakes() {
mFlakes = new SnowFlake[mFlakeCount];
boolean isRightDir = new Random().nextBoolean();
for (int i = 0; i < mFlakes.length; i++) {
mFlakes[i] = new SnowFlake();
mFlakes[i].setWidth(new Random().nextInt(mMaxSize-mMinSize) + mMinSize);
mFlakes[i].setHeight(mFlakes[i].getWidth());
mFlakes[i].setX(new Random().nextInt(mViewWidth));
mFlakes[i].setY(-(new Random().nextInt(mViewHeight)));
mFlakes[i].setSpeedY(new Random().nextInt(4) + mSpeedY);
if (isRightDir) {
mFlakes[i].setSpeedX(new Random().nextInt(4) + mSpeedX);
}
else {
mFlakes[i].setSpeedX(-(new Random().nextInt(4) + mSpeedX));
}
}
}
(6)绘制snow flakes 的函数:通过SurfaceHolder 的lockCanvas()函数获取到画布,绘制完后再调用 unlockCanvasAndPost() 函数释放canvas并将缓冲区绘制的内容一次性绘制到canvas上:
private void drawView() {
if (mHolder == null) {
return;
}
Canvas canvas = mHolder.lockCanvas();
if (canvas == null) {
return;
}
canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
drawSnow(canvas);
mHolder.unlockCanvasAndPost(canvas);
}
private void drawSnow(Canvas canvas) {
Rect rect = new Rect();
Paint paint = new Paint();
for (SnowFlake flake : mFlakes) {
rect.left = flake.getX();
rect.top = flake.getY();
rect.right = rect.left + flake.getWidth();
rect.bottom = rect.top + flake.getHeight();
canvas.drawBitmap(mSnowBitmap, null, rect, paint);
}
}
(7)更新snow flakes的参数的函数:
private void updatePara() {
int x;
int y;
for (SnowFlake flake : mFlakes) {
if (flake == null) {
break;
}
x = flake.getX() + flake.getSpeedX();
y = flake.getY() + flake.getSpeedY();
if ((x > mViewWidth + 20 || x < 0)
|| (y > mViewHeight + 20)) {
x = new Random().nextInt(mViewWidth);
y = 0;
}
flake.setX(x);
flake.setY(y);
}
}
(8)开启绘画线程的 start 函数:
public void start() {
new Thread(){
@Override
public void run() {
while (true) {
try {
if (mStart) {
updatePara();
drawView();
}
Thread.sleep(20);
}
catch (Exception ex) {
ex.printStackTrace();
}
}
}
}.start();
}
(9)修改 surfaceCreated(SurfaceHolder holder) 函数,即在SurfaceView创建完成后初始化snow flakes,并开启动画线程:
@Override
public void surfaceCreated(SurfaceHolder holder) {
initSnowFlakes();
start();
}
(10)重写 onVisibilityChanged() 函数,在控件不可见时停止更新和绘制控件,避免CPU资源浪费:
@Override
protected void onVisibilityChanged(View changedView, int visibility) {
super.onVisibilityChanged(changedView, visibility);
mStart = (visibility == VISIBLE);
}
4. 控件的使用:
由于我们做了很多封装工作,所以控件使用是很简单的, 在布局文件中添加并设置对应属性即可:
<com.haoye.snow.Snow
android:layout_width="match_parent"
android:layout_height="match_parent"
myview:flakeCount="30"
myview:minSize="30"
myview:maxSize="70"
myview:speedX="5"
myview:speedY="10"
myview:flakeSrc="@drawable/snow_flake"/>
--------------------------
至此,自定义SurfaceView控件就完成了,当然我们还可以添加一些其他的效果,比如让随机生成的雪花小片的多一些,适当调节雪花的亮度等,这样可以更好地模拟远处下雪的情景,使景色具有深度。
另外在上面的代码实现中,其实通过
mHolder.setFormat(PixelFormat.TRANSLUCENT);
setZOrderOnTop(true);
这两行代码,我们已经将SurfaceView设置为背景透明的模式,在每次绘制的时候,通过
canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
这行代码清理屏幕后再重新绘制;这使得我们可以在控件外添加背景图片,而不需要每次都在控件中重绘。
源码下载:https://github.com/laishenghao/Snow/
转载请注明:http://www.cnblogs.com/laishenghao/p/5396185.html
天气渐热,来片雪花降降温——Android自定义SurfaceView实现雪花效果的更多相关文章
- 《IT蓝豹》吹雪花demo,学习android传感器
吹雪花demo,学习android传感器 吹雪花demo,学习android传感器,嘴巴对着手机底部吹一下就会出现飘着雪花效果. 算是学习android传感器效果.本例子主要是通过android.me ...
- Android使用SurfaceView实现墨迹天气的风车效果
SurfaceView也是继承自View,它和我们以前接触到的View(Button.TextView等)最大的不同是,SurfaceView可以有一个单独的线程进行绘制,这个线程区别于UI线程(主线 ...
- 类似UC天气下拉和微信下拉眼睛头部弹入淡出UI交互效果(开源项目)。
Android-PullLayout是github上的一个第三方开源项目,该项目主页是:https://github.com/BlueMor/Android-PullLayout 原作者项目意图实现 ...
- ios 基于CAEmitterLayer的雪花,烟花,火焰,爱心等效果demo(转)
转载自:http://blog.csdn.net/mad2man/article/details/16898369 分类: cocoa SDK2013-11-23 11:52 388人阅读 评论(0) ...
- ios 基于CAEmitterLayer的雪花,烟花,火焰,爱心等效果demo
demo功能:基于CAEmitterLayer的雪花,烟花,火焰,爱心等效果. demo说明:基于Core Animation的粒子发射系统,粒子用CAEmitterCell来初始化. 粒子画在背景层 ...
- android自定义View绘制天气温度曲线
原文:android自定义View绘制天气温度曲线 版权声明:本文为博主原创文章,未经博主允许不得转载. https://blog.csdn.net/u012942410/article/detail ...
- Mybatis框架(9)---Mybatis自定义插件生成雪花ID做为表主键项目
Mybatis自定义插件生成雪花ID做为主键项目 先附上项目项目GitHub地址 spring-boot-mybatis-interceptor 有关Mybatis雪花ID主键插件前面写了两篇博客作为 ...
- Android 热补丁动态修复框架小结
一.概述 最新github上开源了很多热补丁动态修复框架,大致有: https://github.com/dodola/HotFix https://github.com/jasonross/Nuwa ...
- [转载]中国天气网API
最近在做个网站要用到天气网的api,在网上找了些参考资料,这篇文章对天气网api的介绍比较详细,所以转载之,谢谢原作者的辛勤劳动和奉献精神. 原文地址:http://g.kehou.com/t1033 ...
随机推荐
- WPF移动Window窗体(鼠标点击左键移动窗体自定义行为)
XAML代码部分:1.引用System.Windows.Interactivity 2.为指定的控件添加一个拖动的行为 3.很简单的了解行为的作用和用法 <Window xmlns=" ...
- JS中的类,类的继承方法
大牛请无视此篇! 首先我们定义一个类,方法很简单,就像我们定义函数一样,只不过我们为了与函数区分,名称首字母要大写,看代码: function Person (){ } 这就是一个很简单的Poson类 ...
- LINUX RHEL6.5字符界面安装图形化桌面
安装RHEL 6.5 系统,也是一波三折.好不容易把系统装上去了,发现没装图形化界面.重装倒是学会了,不过觉得太麻烦,于是有了今天. 查了很多帖子,然后自己一一尝试,发现都是说简单,只要 yum gr ...
- 【《Effective C#》提炼总结】提高Unity中C#代码质量的21条准则
作者:Williammao, 腾讯移动客户端开发工程师 商业转载请联系腾讯WeTest获得授权,非商业转载请注明出处. 原文链接:http://wetest.qq.com/lab/view/290.h ...
- 提高C++编译速度-------pimpl 模式& 桥接模式(转)
pimpl 模式(Private Implementation),我们常常听到诸如“不要改动你的公有接口”这样的建议,所以我们一般都会修改私有接口,但是这会导致包含该头文件的所有源文件都要重新编译,这 ...
- SQL server 数据库——数学函数、字符串函数、转换函数、时间日期函数
数学函数.字符串函数.转换函数.时间日期函数 1.数学函数 ceiling()--取上限 select ceiling(oil) as 油耗上限 from car floor()--取下限 sele ...
- 1819: [JSOI]Word Query电子字典
1819: [JSOI]Word Query电子字典 Time Limit: 10 Sec Memory Limit: 64 MBSubmit: 729 Solved: 238[Submit][S ...
- Octave Tutorial(《Machine Learning》)之第一课《数据表示和存储》
Octave Tutorial 第一课 Computation&Operation 数据表示和存储 1.简单的四则运算,布尔运算,赋值运算(a && b,a || b,xor( ...
- 介绍Office 365 中文用户社区 4.0
本文于2017年3月18日首发于LinkedIn,原文链接在这里 为了给广大用户提供一个可以自由交流.切磋技术的平台,微软和其他一些国际知名的大型软件公司一样,都有创建用户社区(Community,或 ...
- yii中调整ActiveForm表单样式
Yii2中对于表单和字段的支持组件为ActiveForm和ActiveField, <?php $form = ActiveForm::begin([ 'id' => 'login-for ...