Android新手上路——自定义SurfaceView,重力感应小球

本文介绍了一个基于Android的重力感应小球跳动应用的实现方式,使用SurfaceView和加速度传感器来控制小球的移动。文章详细介绍了如何在非UI线程中更新SurfaceView的内容,并利用加速度传感器数据调整小球的位置。
摘要由CSDN通过智能技术生成

如有错误,还请指正
- SurfaceView的使用
- 加速度传感器的使用


  • 重力小球View的代码
package com.example.doge_gravityjumpingaty.view;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Paint.Style;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.util.AttributeSet;
import android.util.Log;
import android.view.SurfaceHolder;
import android.view.SurfaceHolder.Callback;
import android.view.SurfaceView;

/**
 * @author fhbianling--- A little learning is a dangerous thing.
 * @mail fhbianling@163.com
 * @time 2016-6-26下午4:04:32
 */
public class GravityJumpingView extends SurfaceView implements Runnable, Callback {
    /**
     * SurfaceView可以在非UI线程中绘制界面,因此在重绘频繁的情况下,可以考虑使用SurfaceView 
     * 它的回调中两个主要的方法:
     * 通过getHolder().lockCanvas()获得一个Canvas对象,在这个Canvas上作画,
     * 然后getHolder().unlockCanvasAndPost(Canvas canvas)提交画布,并显示。 注意这三步操作要加同步锁
     **/
    private Paint mPaint;
    private Sensor sensorG;
    private SensorManager manager;
    private int viewWidth;
    private int viewHeight;
    private int radiusBall;
    private boolean isRunning;
    private int centerXmin, centerXmax, centerYmin, centerYmax;
    private int centerX, centerY;
    private float aX, aY;
    private float vX, vY;
    private Canvas mCanvas;
    private SurfaceHolder mHolder;
    /**
     * 传感器的监听器
     */
    private SensorEventListener mListener = new SensorEventListener() {

        @Override
        public void onSensorChanged(SensorEvent event) {

            /** 将当前加速度赋值给全局变量用以在线程中更新圆球圆心位置 **/
            /** 对于加速度传感器,其event.values[]的0,1,2下标的三个值分别对应当前手机x,y,z方向的加速度 **/
            /** 这个坐标系,以手机屏幕左至右为x方向正方向,下至上为y方向正方向,垂直屏幕指向屏幕上方为z正方向 **/
            /** 和2d时的相关x,y方向(如onTouchEvent()中的event.getY())不同 **/
            aX = event.values[0];
            aY = event.values[1];
        }

        @Override
        public void onAccuracyChanged(Sensor sensor, int accuracy) {
            /** 这个回调方法在传感器精度改变时被触发,在这个demo中并不需要做任何操作 **/
        }
    };

    /**
     * 初始化传感器和画笔
     * 
     * @param context
     */
    private void init(Context context) {
        mPaint = new Paint();
        mPaint.setAntiAlias(true);
        // 通过context获得系统服务-->传感器管理器
        manager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE);
        // 通过传感器管理器获得加速度传感器,获取其他传感器的方法只需传入不同的Sensor.Type...常量值即可
        // 不同的手机拥有的传感器不一定一样,不常用的传感器,一般的手机生产厂商不会提供
        // 通过SensorManager对象的
        // .getSensorList(Sensor.TYPE_ALL)方法可以返回一个手机拥有的传感器的List<Sensor>集合
        sensorG = manager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
        // 为SurfaceView的SurfaceHolder添加回调接口,这个接口的三个方法分别在Surface创建,销毁和改变时触发
        // 一个surface可以理解为对应的SurfaceView的一个可见区域,并且它是直接对应到内存中的,因此它有自己的生命周期
        // 分别就对应了surfaceCreate,surfaceChange,surfaceDestroy三个方法
        mHolder = this.getHolder();
        mHolder.addCallback(this);
    }

    public GravityJumpingView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context);
    }

    public GravityJumpingView(Context context) {
        super(context);
        init(context);
    }

    public GravityJumpingView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        init(context);
    }

    /**
     * 通过使这个View本身实现Runnable接口来绘制界面, 并通过Runnable的线程休眠达到控制每秒帧数的效果,
     * 因为传感器的数据变化较快,控制每秒重绘次数避免绘制太多次。
     */
    @Override
    public void run() {
        // 注册监听器的第三个参数用于控制传感器获取数值的频率
        manager.registerListener(mListener, sensorG, SensorManager.SENSOR_DELAY_NORMAL);
        Log.d("Listener", "register");
        while (isRunning) {
            vX -= aX;
            // 这里的反向是由于坐标系转换
            vY += aY;
            centerX += vX;
            centerY += vY;
            // V1=V0+a*t;sX1=sX0+vX*t;物理公式的简单运用
            // 以下两个if...else...结构用于进行边界检测
            if (centerX <= centerXmin) {
                centerX = centerXmin;
                // 使反向后的vX的数值变为之前的4/5是为了模拟碰撞过程中弹性形变造成的动能损失,vY同
                vX = -vX * 4 / 5;
            } else if (centerX >= centerXmax) {
                centerX = centerXmax;
                vX = -vX * 4 / 5;
            }
            if (centerY <= centerYmin) {
                centerY = centerYmin;
                vY = -vY * 4 / 5;
            } else if (centerY >= centerYmax) {
                centerY = centerYmax;
                vY = -vY * 4 / 5;
            }
            try {
                // 在数据刷新后的绘画过程
                synchronized (mHolder) {
                    // mCanvas=mHolder.lockCanvas(dirty)方法,dirty是一个Rect类的实例,
                    // 通过这个方法可以只在dirty这个矩形的区域内更新画面,以进一步优化绘制的内存消耗,
                    //在这里使用的.lockCanvas()方法会绘制整个Canvas区域
                    mCanvas = mHolder.lockCanvas();
                    draw();
                    mHolder.unlockCanvasAndPost(mCanvas);
                }
                // 使线程睡眠50ms达到近似每秒20帧的帧数
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (IllegalArgumentException e) {
                e.printStackTrace();
            }
        }
        // 在循环结束后注销传感器
        if (manager != null && mListener != null) {
            manager.unregisterListener(mListener);
            Log.d("Listener", "unregister");
        }
    }

    /**
     * 绘制数据更新后的小球
     */
    private void draw() {
        if (mPaint == null || mCanvas == null) {
            return;
        }
        // 画白色背景
        mPaint.setStyle(Style.FILL_AND_STROKE);
        mPaint.setColor(Color.WHITE);
        mCanvas.drawRect(0, 0, viewWidth, viewHeight, mPaint);
        // 画移动范围边框
        mPaint.setColor(Color.BLACK);
        mPaint.setStyle(Style.STROKE);
        mCanvas.drawRect(0, 0, viewWidth, viewHeight, mPaint);
        // 画小球
        mPaint.setStyle(Style.FILL_AND_STROKE);
        mCanvas.drawCircle(centerX, centerY, radiusBall, mPaint);
    }

    /**
     * 在surface创建时注册监听,初始化数据,并启动绘制线程, 这个方法在onAttachedToWindow()后被调用
     */
    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        start();
        Log.d("surfaceCreated", "1");
    }

    /**
     * start方法用于初始化数据,并启动绘制线程
     */
    private void start() {
        isRunning = true;
        viewWidth = getWidth();
        viewHeight = getHeight();
        radiusBall = Math.min(viewWidth, viewHeight) / 20;
        centerXmin = radiusBall;
        centerXmax = viewWidth - radiusBall;
        centerYmin = radiusBall;
        centerYmax = viewHeight - radiusBall;
        centerX = viewWidth / 2;
        centerY = viewHeight / 2;
        // centerX,centerY的Min,Max分别对应圆心的最小范围和最大范围
        // centerX,centerY代表绘制圆形的实时圆心位置
        new Thread(this).start();
    }

    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
        Log.d("surfaceChanged", "2");
    }

    /**
     * 在surfaceDestroyed()方法中停止线程,注销传感器注册在run()中执行,
     * 这个方法会在onDetachedToWindow()后被调用
     */
    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        isRunning = false;
        Log.d("surfaceDestroyed", "3");
    }

}

布局参数和Acitivity很简单就不贴了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值