您的位置:首页 > 移动开发 > Android开发

NineOldAndroidsDemos 学习(4) FlakeView

2013-11-05 19:49 197 查看
这个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();
    }
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: