高仿墨迹天气 白天晴天

Posted 吴佳峻

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了高仿墨迹天气 白天晴天相关的知识,希望对你有一定的参考价值。

简介

一直对墨迹天气的绚丽的场景蛮感兴趣的,趁有时间,自己就高仿了其中的一个场景,其他场景呢,也是类似的,主要是写对象的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
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) 
        width = getMeasuredWidth();
        height = getMeasuredHeight();
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        Log.d("weather", "onMeasure width=" + width + ",height=" + height);
        if (renderThread != null) 
            renderThread.setWidth(width);
            renderThread.setHeight(height);
        
记录下测量的宽高

    @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 就是咱们的逻辑和绘制方法喽

来看看上边的那个小鸟的代码吧

public class BirdUp extends Actor 
    private static final int[] imgs = new int[]R.drawable.finedayup_1, R.drawable.finedayup_2, R.drawable.finedayup_3, R.drawable.finedayup_4, R.drawable.finedayup_5, R.drawable.finedayup_6, R.drawable.finedayup_7, R.drawable.finedayup_8;
 
    float initPositionX;
    float initPositionY;
    boolean isInit;
    List<Bitmap> frames;
    RectF box;
    RectF targetBox;
    int curFrameIndex;
    long lastTime;
    Paint paint = new Paint();
 
    protected BirdUp(Context context) 
        super(context);
        frames = new ArrayList<Bitmap>();
        box = new RectF();
        targetBox = new RectF();
        paint.setAntiAlias(true);
    
 
    @Override
    public void draw(Canvas canvas, int width, int height) 
        //逻辑处理
        //初始化
        if (!isInit) 
            initPositionX = width * 0.117F;
            initPositionY = height * 0.35F;
            matrix.reset();
            matrix.postTranslate(initPositionX, initPositionY);
            for (int res : imgs) 
                frames.add(BitmapFactory.decodeResource(context.getResources(), res));
            
            box.set(0, 0, frames.get(0).getWidth(), frames.get(0).getHeight());
            isInit = true;
            lastTime = System.currentTimeMillis();
            return;
        
        //移动
        matrix.postTranslate(2, 0);
        //边界处理
        matrix.mapRect(targetBox, box);
        if (targetBox.left > width) 
            matrix.postTranslate(-targetBox.right, 0);
        
        //取得帧动画图片
        long curTime = System.currentTimeMillis();
        curFrameIndex = (int) ((curTime - lastTime) / 500 % 8);
 
        Bitmap curBitmap = frames.get(curFrameIndex);
        //绘制
        canvas.save();
        canvas.drawBitmap(curBitmap, matrix, paint);
        canvas.restore();
    
主要逻辑就在 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来动态生成一个场景。当然也不是很难。真正做天气的话,可以这样做。

 

Github地址

https://github.com/wu928320442/MojiWeather

 

以上是关于高仿墨迹天气 白天晴天的主要内容,如果未能解决你的问题,请参考以下文章

Android UI开发第四十三篇——使用Property Animation实现墨迹天气3.0引导界面及动画实现

Unity Shader2D动态云彩

千亿气象服务市场,墨迹天气“吃肉”还是“喝汤”?

观察者模式

Redis命令

HMM条件下的 前向算法 和 维特比解码