实现雪花的效果其实也可以通过自定义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);
这行代码清理屏幕后再重新绘制;这使得我们可以在控件外添加背景图片,而不需要每次都在控件中重绘。
百度搜索“就爱阅读”,专业资料,生活学习,尽在就爱阅读网92to.com,您的在线图书馆!
来源: http://www.92to.com/bangong/2017/12-05/32128890.html