NineOldAndroidsDemos 学习(4) FlakeView

本文介绍了如何使用自定义视图实现动态生成小机器人动画效果,包括精灵类Flake的属性设定、视图类FlakeView的动画逻辑及UI渲染过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

这个Flake实现的效果还是很酷的,


实现起来也不是很难,首先介绍大体的原理:

1.每个小机器人 (Flake )  可以理解为一个 "精灵",他有自己的速度,位置,大小,形状,旋转等.

2.而FlakeView就是一个舞台了,在ValueAnimator的驱动下,每一个动画帧 调用invalidate();刷新这个view,在他的onDraw中就会遍历每个flake,根据他们的位置 形状等

将其画出来.


下面从代码角度来分析一下

首先是Flake.java

/**
 * This class represents a single Droidflake, with properties representing its
 * size, rotation, location, and speed.
 *可以理解小机器人的精灵类 他有自己的位置 速度等
 */
public class Flake {

    // These are the unique properties of any flake: its size, rotation, speed,
    // location, and its underlying Bitmap object
    float x, y;
    float rotation;
    float speed;
    float rotationSpeed;
    int width, height;
    Bitmap bitmap;

    // This map stores pre-scaled bitmaps according to the width. No reason to create
    // new bitmaps for sizes we've already seen.
    //缓存他的bitmap
    static HashMap<Integer, Bitmap> bitmapMap = new HashMap<Integer, Bitmap>();

    /**
     * Creates a new droidflake in the given xRange and with the given bitmap. Parameters of
     * location, size, rotation, and speed are randomly determined.
     * 给定x的边界,然后随机创建小机器人
     */
    static Flake createFlake(float xRange, Bitmap originalBitmap) {
        Flake flake = new Flake();
        // Size each flake with a width between 5 and 55 and a proportional height
        flake.width = (int)(5 + (float)Math.random() * 50);
        float hwRatio = originalBitmap.getHeight() / originalBitmap.getWidth();
        flake.height = (int)(flake.width * hwRatio);

        // Position the flake horizontally between the left and right of the range
        //让小机器人在x方向上处在屏幕直接,不要跑出屏幕
        flake.x = (float)Math.random() * (xRange - flake.width);
        // Position the flake vertically slightly off the top of the display
        flake.y = 0 - (flake.height + (float)Math.random() * flake.height);

        // Each flake travels at 50-200 pixels per second
        flake.speed = 50 + (float) Math.random() * 150;

        // Flakes start at -90 to 90 degrees rotation, and rotate between -45 and 45
        // degrees per second
        flake.rotation = (float) Math.random() * 180 - 90;
        flake.rotationSpeed = (float) Math.random() * 90 - 45;

        // Get the cached bitmap for this size if it exists, otherwise create and cache one
        flake.bitmap = bitmapMap.get(flake.width);
        if (flake.bitmap == null) {
            flake.bitmap = Bitmap.createScaledBitmap(originalBitmap,
                    (int)flake.width, (int)flake.height, true);
            bitmapMap.put(flake.width, flake.bitmap);
        }
        return flake;
    }
}


然后是FlakeView.java

**
 * This class is the custom view where all of the Droidflakes are drawn. This class has
 * all of the logic for adding, subtracting, and rendering Droidflakes.
 *
 * 这个view用来承载那些"机器人元素" , 包含添加 ,减少 , 和渲染 小机器人 的功能
 */
public class FlakeView extends View {

    Bitmap droid;       //  从R中获得的那个绿色机器人图片
    int numFlakes = 0;  // 小机器人图片的数目
    ArrayList<Flake> flakes = new ArrayList<Flake>(); // 承载 机器人的 list

    // Animator used to drive all separate flake animations. Rather than have potentially
    // hundreds of separate animators, we just use one and then update all flakes for each
    // frame of that single animation.
    //使用同一个animator来驱动所有的 小机器人 的运动,这个值从0变到1不断重复,其实这个值在这个案例中并没有用到他的值
    //可以理解为一个handler +thread 
    ValueAnimator animator = ValueAnimator.ofFloat(0, 1);
    long startTime, prevTime; // 用来计算 时间间隔和 fps
    int frames = 0;     // 用来计算一秒刷新几帧
    Paint textPaint;    // 文字
    float fps = 0;      // frames per second
    Matrix m = new Matrix(); // 用来移动 旋转 小机器人 
    String fpsString = "";
    String numFlakesString = "";

    /**
     * 初始化一些全局变量
     */
    public FlakeView(Context context) {
        super(context);
        droid = BitmapFactory.decodeResource(getResources(), R.drawable.droid);
        textPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        textPaint.setColor(Color.WHITE);
        textPaint.setTextSize(24);

        // This listener is where the action is for the flak animations. Every frame of the
        // animation, we calculate the elapsed time and update every flake's position and rotation
        // according to its speed.
        //在动画的每一帧(动画帧的间隔经过测试是 10ms 小于 人眼可以分别的 1/60 s )都会回调到这个方法,这个方法用来调整 小机器人的 位置 并 通知刷新UI.
        animator.addUpdateListener(new AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator arg0) {
                long nowTime = System.currentTimeMillis();
                float secs = (float)(nowTime - prevTime) / 1000f;
                prevTime = nowTime;
                for (int i = 0; i < numFlakes; ++i) {
                    Flake flake = flakes.get(i);
                    flake.y += (flake.speed * secs);
                    if (flake.y > getHeight()) {
                        // If a flake falls off the bottom, send it back to the top
                        flake.y = 0 - flake.height;
                    }
                    flake.rotation = flake.rotation + (flake.rotationSpeed * secs);
                }
                // Force a redraw to see the flakes in their new positions and orientations
                invalidate();
            }
        });
        animator.setRepeatCount(ValueAnimator.INFINITE);//无限重复播放动画
        animator.setDuration(3000);
    }

    int getNumFlakes() {
        return numFlakes;
    }

    private void setNumFlakes(int quantity) {
        numFlakes = quantity;
        numFlakesString = "numFlakes: " + numFlakes;
    }

    /**
     * Add the specified number of droidflakes.
     */
    void addFlakes(int quantity) {
        for (int i = 0; i < quantity; ++i) {
            flakes.add(Flake.createFlake(getWidth(), droid));
        }
        setNumFlakes(numFlakes + quantity);
    }

    /**
     * Subtract the specified number of droidflakes. We just take them off the end of the
     * list, leaving the others unchanged.
     */
    void subtractFlakes(int quantity) {
        for (int i = 0; i < quantity; ++i) {
            int index = numFlakes - i - 1;
            flakes.remove(index);
        }
        setNumFlakes(numFlakes - quantity);
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        // Reset list of droidflakes, then restart it with 8 flakes
        flakes.clear();
        numFlakes = 0;
        addFlakes(8);
        // Cancel animator in case it was already running
        animator.cancel();
        // Set up fps tracking and start the animation
        startTime = System.currentTimeMillis();
        prevTime = startTime;
        frames = 0;
        animator.start();
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        // For each flake: back-translate by half its size (this allows it to rotate around its center),
        // rotate by its current rotation, translate by its location, then draw its bitmap
        //遍历每一个 小机器人 
        for (int i = 0; i < numFlakes; ++i) {
            Flake flake = flakes.get(i);
            m.setTranslate(-flake.width/2, -flake.height/2);//移动小机器人的中心到(0,0)
            m.postRotate(flake.rotation);//旋转小机器人
            m.postTranslate(flake.width/2 + flake.x, flake.height/2 + flake.y);//移动小机器人到(x,y)
            canvas.drawBitmap(flake.bitmap, m, null);//把小机器人画出来
        }
        // fps counter: count how many frames we draw and once a second calculate the
        // frames per second
        //计算一秒我们画了多少帧
        ++frames;
        long nowTime = System.currentTimeMillis();
        long deltaTime = nowTime - startTime;
        if (deltaTime > 1000) {
            float secs = (float) deltaTime / 1000f;
            fps = (float) frames / secs;
            fpsString = "fps: " + fps;
            startTime = nowTime;
            frames = 0;
        }
        canvas.drawText(numFlakesString, getWidth() - 200, getHeight() - 50, textPaint);
        canvas.drawText(fpsString, getWidth() - 200, getHeight() - 80, textPaint);
    }

    public void pause() {
        // Make sure the animator's not spinning in the background when the activity is paused.
        animator.cancel();
    }

    public void resume() {
        animator.start();
    }

}


在activity中的操作就是



public class Droidflakes extends Activity {

    FlakeView flakeView;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.droidflakes);
        LinearLayout container = (LinearLayout) findViewById(R.id.container);
        flakeView = new FlakeView(this);
        container.addView(flakeView);
        getWindow().setBackgroundDrawable(new ColorDrawable(Color.BLACK));

        Button more = (Button) findViewById(R.id.more);
        more.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                flakeView.addFlakes(flakeView.getNumFlakes());
            }
        });
        Button less = (Button) findViewById(R.id.less);
        less.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                flakeView.subtractFlakes(flakeView.getNumFlakes() / 2);
            }
        });
        if (Integer.parseInt(Build.VERSION.SDK) >= Build.VERSION_CODES.HONEYCOMB) {
            HoneycombHelper.setup(this);
        }
    }

    private static final class HoneycombHelper {
        static void setup(final Droidflakes activity) {
            CheckBox accelerated = (CheckBox) activity.findViewById(R.id.accelerated);
            accelerated.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
                @Override
                public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                    activity.flakeView.setLayerType(//开启硬件加速的方法
                            isChecked ? View.LAYER_TYPE_NONE : View.LAYER_TYPE_SOFTWARE, null);
                }
            });
        }
    }

    @Override
    protected void onPause() {//在这时候 停止动画防止不必要的资源浪费
        super.onPause();
        flakeView.pause();
    }

    @Override
    protected void onResume() {
        super.onResume();
        flakeView.resume();
    }
}





















内容概要:本文档详细介绍了一个基于多任务学习(MTL)结合Transformer编码器的多变量时间序列预测项目。该项目旨在解决多变量时间序列预测中的复杂性、长序列依赖、任务间干扰等问题,通过融合多任务学习与Transformer架构,构建了一套高效且可扩展的预测系统。项目覆盖了从环境准备、数据预处理、模型构建与训练、性能评估到GUI界面设计的完整流程,并提供了详细的代码实现。模型采用多头自注意力机制有效捕捉变量间及时间维度上的复杂依赖,通过多任务共享层和任务特异性层实现任务间知识共享与个性化特征提取。此外,项目还引入了动态任务权重调整、轻量化设计、数据预处理优化等关键技术,确保模型在实际应用中的高效性和准确性。 适合人群:具备一定编程基础,特别是对深度学习和时间序列分析有一定了解的研发人员和技术爱好者。 使用场景及目标:① 提升多变量时间序列预测的准确率;② 实现高效的时间序列特征提取;③ 促进多任务学习在时间序列领域的应用推广;④ 提供可扩展的模型架构设计,适应不同应用场景的需求;⑤ 降低模型训练与推断的计算资源需求;⑥ 促进复杂系统的智能决策能力,如智能制造、能源管理、金融分析、气象预报等领域的精准预测支持。 其他说明:项目不仅提供了完整的代码实现和详细的文档说明,还设计了用户友好的GUI界面,方便用户进行数据加载、参数设置、模型训练及结果可视化等操作。未来改进方向
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值