Android自定义View进阶之高仿墨迹天气蓝天白云场景效果
Tips:
Android本月免费课程报名中,点击文末“阅读原文”快速抢!
前言
一直对墨迹天气的绚丽的场景蛮感兴趣的,趁有时间,自己就高仿了其中的一个场景,其他场景呢,也是类似的,主要是写对象的AI也就是逻辑了。
呈现效果
先看看效果吧,由于压缩较大,动态效果呈现稍模糊,注重代码本身就好。
代码分析
来看看代码结构吧,这里使用了SurfaceView而不是用的view,其实这个天气的场景绘制更像是游戏开发,使用SurfaceView会更灵活。
public SceneSurfaceView(Context context, AttributeSet attrs) { super(context, attrs); surfaceHolder = getHolder(); surfaceHolder.addCallback(this); setFocusable(true); setFocusableInTouchMode(true); this.setKeepScreenOn(true); }这就是构造方法了,实现SurfaceHolder.Callback来监听事件
@Override public void surfaceCreated(SurfaceHolder holder) { Log.d("weather", "surfaceCreated"); if (renderThread == null) { renderThread = new RenderThread(surfaceHolder, getContext()); renderThread.start(); } }在surface创建回调中, 我们生成了一个RenderThread线程来专门做逻辑与绘制。
记录下测量的宽高
@Override public void surfaceDestroyed(SurfaceHolder holder) { Log.d("weather", "surfaceDestroyed"); renderThread.getRenderHandler().sendEmptyMessage(1); }销毁的时候要发一个消息,具体做什么,下面在来说下面是RenderThread源码
public class RenderThread extends Thread { private Context context; private SurfaceHolder surfaceHolder; private RenderHandler renderHandler; private Scene scene; public RenderThread(SurfaceHolder surfaceHolder, Context context) { this.context = context; this.surfaceHolder = surfaceHolder; scene = new Scene(context); //add scene/actor scene.setBg(BitmapFactory.decodeResource(context.getResources(), R.drawable.bg0_fine_day)); scene.add(new BirdUp(context)); scene.add(new CloudLeft(context)); scene.add(new CloudRight(context)); scene.add(new BirdDown(context)); scene.add(new SunShine(context)); } @Override public void run() { Log.d("weather", "run"); //在非主线程使用消息队列 Looper.prepare(); renderHandler = new RenderHandler(); renderHandler.sendEmptyMessage(0); Looper.loop(); } public RenderHandler getRenderHandler() { return renderHandler; } public class RenderHandler extends Handler { @Override public void handleMessage(Message msg) { switch (msg.what) { case 0: if (scene.getWidth() != 0 && scene.getHeight() != 0) { draw(); } renderHandler.sendEmptyMessage(0); break; case 1: Looper.myLooper().quit(); break; } } } private void draw() { Canvas canvas = surfaceHolder.lockCanvas(); if (canvas != null) { scene.draw(canvas); surfaceHolder.unlockCanvasAndPost(canvas); } } public void setWidth(int width) { scene.setWidth(width); } public void setHeight(int height) { scene.setHeight(height); } }在构造方法中添加了场景背景,上下两个鸟,左右各一个云彩和阳光。
这里在run方法里生成了一个线程的消息队列,注意额不是主线程的,其实也可以在里面搞个while循环,就像一般的游戏处理一样, 但是如果使用消息队列,会更轻巧有效。
大家再来看看RenderHandler,情况分2种,一个是绘制的一个是退出的,基本也就这2种了。还记得在surfaceDestroyed中调用的退出吧,现在就在这了,呵呵。然后就是最重要的draw方法了,绘制是在Scene中操作的,来看看代码
public class Scene { private Context context; private int width; private int height; private Bitmap bg; private List<Actor> actors = new ArrayList<Actor>(); private Paint paint; public Scene(Context context) { this.context = context; paint = new Paint(); paint.setAntiAlias(true); } public void setBg(Bitmap bg) { this.bg = bg; } public void add(Actor actor) { actors.add(actor); } public void draw(Canvas canvas) { canvas.drawBitmap(bg, new Rect(0, 0, bg.getWidth(), bg.getHeight()), new Rect(0, 0, width, height), paint); for (Actor actor : actors) { actor.draw(canvas,width,height); } }可以在场景中绘制一个背景图和Actor列表Actor是啥呢,就是对象呗,像鸟啊,云啊,雨啊等等吧
public abstract class Actor { protected Context context; protected Matrix matrix = new Matrix(); protected Actor(Context context) { this.context = context; } public abstract void draw(Canvas canvas, int width, int height); }这是一个抽象类,Context 可以加载资源文件,Matrix 来描述对象的变换,抽象方法draw就是咱们的逻辑和绘制方法喽
来看看上边的那个小鸟的代码吧
主要逻辑就在draw了, 注释写的也比较清除了,先初始化操作,加载资源,设定起始位置,然后就是每帧的移动逻辑,和边界逻辑处理,就是跑到最右边,再把他拉到最左边,呵呵,下面是小鸟动画的处理,我这里是500毫秒更换一下图片,也就是说看到的小鸟的动画,其实是隔500毫秒更换了一次图片产生的效果,下面就只绘制了,好了so easy 吧!
这里要特别说明一个方法matrix.mapRect(targetBox, box);这个方法比较重要,大家以后肯定会经常用到,意思是啥呢,box这个参数是原始的图片大小数据,targetBox是经过Matrix矩阵变换后产生的数据。
好了,下面的鸟其实跟上面的逻辑一样的,只是起始位置不一样。云彩呢,和小鸟逻辑也差不多,但是需要注意一个地方, 我把云彩给放大了2倍
matrix.reset(); matrix.setScale(2f, 2f); matrix.mapRect(targetBox, box); matrix.postTranslate(initPositionX - targetBox.width() / 2, initPositionY - targetBox.height() / 2);这里初始位置呢, 我也根据放大后的宽和高进行了处理,大家注意啊,先放缩和先设置位置,出来的效果是不一样的。 大家可以自行试试效果。
现在来看看我们的阳光代码吧这里就贴一些关键代码了, 全部代码可以在我的github上下载
//旋转 matrix.mapRect(targetBox, box); matrix.postRotate(0.5F, targetBox.centerX(), targetBox.centerY()); //透明度变化 if (alphaUp) { alpha++; } else { alpha--; } if (alpha >= 255) { alphaUp = false; } if (alpha <= 0) { alphaUp = true; } paint.setAlpha(alpha); //绘制 canvas.drawBitmap(frame, matrix, paint);主要就是介绍一下,使用矩阵来进行旋转操作和透明度操作怎么来做。这里要注意一下的是旋转的时候,要设置中心点。
结束语
好了,代码说的也差不多啦,知识点也都过了一下。回过头来看看,要实现这么一个效果也不是那么难是吧。 呵呵。 当然了实现的还是比较仓促的,就是简单的一个架子和一个场景,如果感兴趣的话可以添加更多的对象AI逻辑,更多的场景。
其实如果做到最后优化好的话应该是这样的一个情况,每个场景一个xml或者其他脚本语言吧,然后解析这个xml来动态生成一个场景。当然也不是很难。真正做天气的话,可以这样做。
后台回复「视频」,获取Kotlin视频学习资料。由于资源容易被删,发现删了请给我直接留言,我给你直发下载链接。
更多干货
学懂技术关键点,月薪3W,你也可以!
6月安卓免费课程报名中,4天学做一个APP!
回复「姓名+微信号+城市」报名或点页面底部「阅读原文」报名。
回复「领取」免费下载安卓学习资料&工具&组件
各类干货和励志鸡血,更有各种好玩好看的资讯!
点击阅读原文报名Android免费课程