高仿墨迹天气 白天晴天

来源:互联网 发布:仙界网络直播间好看吗 编辑:程序博客网 时间:2024/05/01 22:12

简介

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