高仿墨迹天气 白天晴天

标签: android移动墨迹天气代码分析动画
3842人阅读 评论(10) 收藏 举报
分类:

简介

一直对墨迹天气的绚丽的场景蛮感兴趣的,趁有时间,自己就高仿了其中的一个场景,其他场景呢,也是类似的,主要是写对象的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

 

2
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:185478次
    • 积分:2077
    • 等级:
    • 排名:第18447名
    • 原创:37篇
    • 转载:0篇
    • 译文:0篇
    • 评论:121条
    Android开发交流群
    323876830
    博客专栏