高仿墨迹天气 白天晴天

简介

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

时间: 2024-08-27 10:43:34

高仿墨迹天气 白天晴天的相关文章

高仿墨迹天气黄历

本项目是高仿墨迹天气的黄历功能模块,可插件化安装

高仿墨迹天气下拉拉伸图片

简介 最近比较闲,就多学习了下,关键是不看点东西,就犯困啊.墨迹天气这个应用有不少地方需要学习的,这篇文章呢, 说一下他的"我"Tab页下拉拉伸图片展示效果,如果留意的话, 像QQ的好友动态也有差不多的效果. 代码分析 代码比较简单了,就重写了一个ScrollView类,先说说他的原理吧,我是先根据id拿到这个ImageView,然后获得他的TopMargin也就是遮掩后的偏移值,在触摸的时候,对ImageView的TopMargin进行改变产生效果,松手的时候搞个属性动画让他还原到以

Android应用源码之仿墨迹天气插件

Android应用源码之仿墨迹天气插件 仿照墨迹天气的桌面小插件例子源码. 下载地址:http://www.dwz.cn/Cox82 运行截图:

Android 打造自己的个性化应用(四):仿墨迹天气实现--&gt;自定义扩展名的zip格式的皮肤

在这里谈一下墨迹天气的换肤实现方式,不过首先声明我只是通过反编译以及参考了一些网上其他资料的方式推测出的换肤原理, 在这里只供参考. 若大家有更好的方式, 欢迎交流. 墨迹天气下载的皮肤就是一个zip格式的压缩包,在应用的时候把皮肤资源释放到墨迹天气应用的目录下,更换皮肤时新的皮肤资源会替换掉老的皮肤资源每次加载的时候就是从手机硬盘上读取图片,这些图片资源的命名和程序中的资源的命名保持一致,一旦找不到这些资源,可以选择到系统默认中查找.这种实现是直接读取了外部资源文件,在程序运行时通过代码显示的

Android之高仿雅虎天气(一)

引言: 记得去年下半年有上传一份代码(超逼真仿雅虎天气界面):http://download.csdn.net/detail/weidi1989/6312271 但那仅仅只是一个界面,而且还有一些比较严重的bug,记得其中有一个是:那个可以换位置的ListView无法缓存item,导致上下滚动时相当卡,性能可想而知了.以至于我就放弃继续开发,今年这段时间忙里偷闲,刚好有网友说要我继续把功能做完,那个换位置的ListView可以去掉,于是乎拿出来又折腾了一下,虽然还是没有解决这个问题(已经干掉此功

Android之高仿雅虎天气(二)---代码结构解析

版本已升级至1.0.1 源码地址: GitHub:https://github.com/way1989/WayHoo OsChina:http://git.oschina.net/way/WayHoo 本例使用了6个库代码和1个主工程代码. 一.6个库代码如下图所示: 其中 ①.MenuDrawer.ViewPagerIndicator.ShowcaseView.SwipeBack都是Github上有名的侧边栏菜单开源库,我这里未做修改,直接引用,感谢开源的力量. ②.PullToRefresh

Android高仿雅虎天气(两)---代码结构分析

版本已经升级到1.0.1 源码地址: GitHub:https://github.com/way1989/WayHoo OsChina:http://git.oschina.net/way/WayHoo 本例使用了6个库代码和1个主工程代码. 一.6个库代码如下图所示: watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvd2F5X3BpbmdfbGk=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolv

Android 打造自己的个性化应用(五):仿墨迹天气实现续--&gt; 使用Ant实现zip/tar的压缩与解压

上一篇中提到对于Zip包的解压和压缩需要借助Ant 实现,我经过参考了其他的资料,整理后并加上了一些自己的看法: 这里就具体地讲下如何使用Ant进行解压缩及其原因: java中实际是提供了对  zip等压缩格式的支持,但是为什么这里会用到ant呢?   原因主要有两个: 1. java提供的类对于包括有中文字符的路径,文件名支持不够好,你用其它第三方软件解压的时候就会存在乱码.而ant.jar就支持文件名或者路径包括中文字符. 2. ant.jar提供了强大的工具类,更加方便于我们对压缩与解压的

另辟蹊径 直取通州的“墨迹天气”APP应用的成功故事

一个天气应用,曾被认为是要挑战国家气象局,网站也莫名其妙地被封,两个合伙人先后离开.创始人金犁是如何把这么一款工具类应用做到人所共知的? 采访 | 郑江波 翟文婷 文 | 翟文婷 出生时间:1982年12月30日 籍贯:沈阳 教育背景:大连交通大学毕业 创业城市:北京 创业次数:1次 首次创业年龄:26岁 前不久,“墨迹天气”的商务部接到个陌生来电,对方自称是湖南电视台<天天向上>节目组,邀请墨迹天气的创始人金犁上节目.接电话的人第一反应是,“又是骗子吧?”出于礼貌,此人象征性地请对方先发封信