• Android使用学习之画图(Canvas,Paint)与手势感应及其应用(乒乓球小游戏)


    作为一个没有学习Android的菜鸟,近期一直在工作之外努力地学习的Android的使用。

    这周看了下Android的画图。主要是Canvas,Paint等,感觉须要实践下。下午正好有空,就想整一个乒乓球的游戏,算是巩固学的知识。

    首先,须要了解下Android的画图须要掌握的经常使用类。包含Canvas,就像一个画板一样,全部的东西都是在其上画的。Paint就是画笔。用其能够画各种基本图形和文字。       Canvas和Paint经常使用的方法就不列举了,这种东西网上到处是。有了这两个东西。想实现游戏,还差个手势感应,对这个游戏来说。仅仅须要识别出左移和右移就可以。

    为此,我们须要使用Android的OnGestureListener接口和GestureDetector类。

            重要的方法例如以下所看到的:

        GestureDetector detector;
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_start);
            detector = new GestureDetector(this, this);
        }
        @Override
        public boolean onTouchEvent(MotionEvent event) {
            // TODO Auto-generated method stub
            return detector.onTouchEvent(event);
        }
        @Override
        public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
            // 最小移动标准
            float minMove = 30;
            if ((e1.getX() - e2.getX()) > minMove && Math.abs(velocityX) > Math.abs(velocityY)) {
                Toast.makeText(this, "Move to Left", Toast.LENGTH_LONG).show();
            } else if ((e2.getX() - e1.getX()) > minMove && Math.abs(velocityX) > Math.abs(velocityY)) {
                Toast.makeText(this, "Move to Right", Toast.LENGTH_LONG).show();
            }
            return false;
        }
    我们须要在onTouchEvent中使用调用GestureDetector的onTouchEvent()方法,仅仅有这样手势感应的接口才干够被回调。当中。onFling()方法中,我们实现了左移和右移的推断。

    游戏的截图例如以下所看到的:


    实现该游戏,主要须要考虑的地方有下面几个地方:

    1.游戏的状态怎样描写叙述。详细来说,就是小球和球拍的状态

    2.视图怎样动态刷新

    对问题1来说,球拍的状态比較简单。仅仅有左移和右移两种,设置球拍的x,y坐标,利用步长就可非常好改变,小球的话。略微麻烦一些。我们须要用其x,y坐标。x移动速度和y移动速度来控制其状态。碰到左右强。x速度反转,碰到球拍或者上边y速度反转,碰到下边(未碰到球拍)则比赛结束。

    对问题2来说,我们须要启动定时刷新任务,定时运行推断当前状态,然后依据数据来刷新视图。

    以下就主要介绍下自己的小游戏实现了。代母包括以下几个类,TablePlayActivity。TableView,MsgDefine,TablePlayModel。TablePlayController。

    TablePlayActivity例如以下:

    package com.example.tableplay;
    
    import android.support.v7.app.ActionBarActivity;
    import android.app.Service;
    import android.os.Bundle;
    import android.os.Handler;
    import android.os.Message;
    import android.os.Vibrator;
    import android.util.DisplayMetrics;
    import android.util.Log;
    import android.view.GestureDetector;
    import android.view.GestureDetector.OnGestureListener;
    import android.view.Menu;
    import android.view.MenuItem;
    import android.view.MotionEvent;
    import android.view.Window;
    import android.view.WindowManager;
    import android.widget.Toast;
    
    public class TablePlayActivity extends ActionBarActivity implements OnGestureListener{
        private GestureDetector dector;
        private TableView myView;
        private Vibrator vibrator;
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            requestWindowFeature(Window.FEATURE_NO_TITLE);
            super.onCreate(savedInstanceState);
            init();
            dector = new GestureDetector(this, this); 
            vibrator = (Vibrator) getSystemService(Service.VIBRATOR_SERVICE);
        }
    
        private void init() {
            myView = new TableView(this);
            setContentView(myView);
            getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
                    WindowManager.LayoutParams.FLAG_FULLSCREEN);
            WindowManager wm = getWindowManager();
            DisplayMetrics metrics = new DisplayMetrics();
            wm.getDefaultDisplay().getMetrics(metrics);
            Log.i("dingbin", "width " + metrics.widthPixels + "height " + metrics.heightPixels);
            TablePlayModel.getInstance().mScreenX = metrics.widthPixels;
            TablePlayModel.getInstance().mScreenY = metrics.heightPixels;
            TablePlayModel.getInstance().mRacketY = metrics.heightPixels - TablePlayModel.getInstance().SHIFT_DISTANCE;
            createHandler();
            TablePlayController.getInstance().startGame(mHandler);
        }
        private Handler mHandler;
        public void createHandler() {
            mHandler = new Handler() {
                @Override
                public void handleMessage(Message msg) {
                    super.handleMessage(msg);
                    switch(msg.what) {
                        case MsgDefine.MSG_TYPE_REFRESH:{
                                myView.invalidate();
                                break;
                            }
                        case MsgDefine.MSG_TYPE_VIBROTOR:{
                            vibrator.vibrate(1000);
                            Toast.makeText(TablePlayActivity.this, "Nice", Toast.LENGTH_SHORT).show();
                            break;
                        }
                        default :break;
                        }
                       
                }
            };
        }
        
        @Override
        public boolean onCreateOptionsMenu(Menu menu) {
            // Inflate the menu; this adds items to the action bar if it is present.
            getMenuInflater().inflate(R.menu.table_play, menu);
            return true;
        }
    
        @Override
        public boolean onOptionsItemSelected(MenuItem item) {
            // Handle action bar item clicks here. The action bar will
            // automatically handle clicks on the Home/Up button, so long
            // as you specify a parent activity in AndroidManifest.xml.
            int id = item.getItemId();
            if (id == R.id.action_settings) {
                return true;
            }
            return super.onOptionsItemSelected(item);
        }
    
        @Override
        public boolean onTouchEvent(MotionEvent event) {
            return dector.onTouchEvent(event);
        }
        
        @Override
        public boolean onDown(MotionEvent e) {
            // TODO Auto-generated method stub
            return false;
        }
    
        @Override
        public void onShowPress(MotionEvent e) {
            // TODO Auto-generated method stub
            
        }
    
        @Override
        public boolean onSingleTapUp(MotionEvent e) {
            // TODO Auto-generated method stub
            return false;
        }
    
        @Override
        public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
            // TODO Auto-generated method stub
            return false;
        }
    
        @Override
        public void onLongPress(MotionEvent e) {
            // TODO Auto-generated method stub
            
        }
    
        @Override
        public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
            // 最小移动标准
            float minMove = 30;
            if ((e1.getX() - e2.getX()) > minMove && Math.abs(velocityX) > Math.abs(velocityY)) {
               // 左移
                TablePlayController.getInstance().racketMoveLeft();
            } else if ((e2.getX() - e1.getX()) > minMove && Math.abs(velocityX) > Math.abs(velocityY)) {
               // 右移
                TablePlayController.getInstance().racketMoveRight();
            }
            // 重绘View
            myView.invalidate();
            return false;
        }
    }
    
    在TablePlayActivity类中。我们主要实现了手势感应的推断和Handler消息的处理,另外初始化中获取了屏幕的宽和高。
    TableView是我们基本的绘图类,我们在其的onDraw方法中,实现了小球和球拍的重绘,代码例如以下:

    package com.example.tableplay;
    
    import android.content.Context;
    import android.graphics.Canvas;
    import android.graphics.Color;
    import android.graphics.Paint;
    import android.graphics.Paint.Style;
    import android.util.Log;
    import android.view.View;
    import android.widget.Toast;
    
    public class TableView extends View{
    
        private Paint paint;
        private Context mContext;
        public TableView(Context context) {
            super(context);
            mContext = context;
            // 创建画笔
            paint = new Paint();
            paint.setAntiAlias(true);
            paint.setStyle(Style.FILL);
        }
    
        @Override
        protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);
            TablePlayModel data = TablePlayModel.getInstance();
            if (data.isGameOver) {
                paint.setTextSize(80);
                paint.setColor(Color.BLUE);
                setBackgroundResource(R.drawable.game_over);
                canvas.drawText("Game is over!", 270, data.mScreenY / 2, paint);
            } else {
                // 画球拍
                paint.setColor(Color.GREEN);
                canvas.drawRect(data.mRacketX, data.mRacketY, data.mRacketX + data.RACKET_WIDTH,
                        data.mRacketY + data.RACKET_HEIGHT, paint);
                // 画球
                paint.setColor(Color.GRAY);
                canvas.drawCircle(data.mBallX, data.mBallY, data.mBallRadius, paint);
            }
        }
    }
    
    接下来是数据类TablePlayModel,该类主要缓存我们须要使用的数据,包含我们全部须要使用的游戏状态和常量。代码例如以下:

    package com.example.tableplay;
    
    import java.util.Random;
    
    import android.util.Log;
    
    public class TablePlayModel {
        private static TablePlayModel mInstance = null;
        // 屏幕的宽和高
        public int mScreenX;
        public int mScreenY;
        // 球拍的宽度和高度
        public final int RACKET_WIDTH = 180;
        public final int RACKET_HEIGHT = 25;
        // 球拍移动的步长
        public int mRacketStep = 150;
        // 球拍的水平和垂直位置
        public int mRacketX = 100;
        public int mRacketY;
        private Random rand = new Random();
        // 小球的半径
        public int mBallRadius = 35;
        // 小球的x,y坐标
        public int mBallX = rand.nextInt(200) + 20;
        public int mBallY = rand.nextInt(500) + 20;
        // 小球x,y移动速度
        public int mBallYSpeed = 28;
        private double mXRate = rand.nextDouble() - 0.5;
        public int mBallXSpeed = (int) (mBallYSpeed * mXRate * 2);
        
        // 刷新间隔 ms
        public int mTimeInterval = 100;
        // 刷新延时 ms
        public int mTimeDelay = 0;
        
        public final int SHIFT_DISTANCE = 200;
        
        public boolean isGameOver = false;
        
        public static TablePlayModel getInstance() {
            if (mInstance == null) {
                synchronized (TablePlayModel.class) {
                    if (mInstance == null) {
                        Log.i("dingbin", "TablePlayModel.getInstance()");
                        mInstance =  new TablePlayModel();
                    }
                }
            }
            return mInstance;
        }
    }
    
    接下来是最重要的控制类TablePlayController,我们的核心逻辑都在该类中。详细来说。就是启动定时器运行任务,依据当前的状态运行小球的运动位置,更新小球的坐标。并通知视图刷新界面。代码例如以下:

    package com.example.tableplay;
    
    import java.util.Timer;
    import java.util.TimerTask;
    
    import android.os.Handler;
    import android.os.Message;
    import android.util.Log;
    import android.widget.Toast;
    
    public class TablePlayController {
        private static TablePlayController mInstance = null;
        private TablePlayModel data = TablePlayModel.getInstance();
        private TablePlayController() {
        }
        public static TablePlayController getInstance() {
            if (mInstance == null) {
                synchronized (TablePlayController.class) {
                    if (mInstance == null) {
                        return new TablePlayController();
                    }
                }
            }
            return mInstance;
        }
        public void startGame(final Handler handler) {
            Log.i("dingbin", "startGame");
            data = TablePlayModel.getInstance();
            Log.i("dingbin", data.toString());
            final Timer timer = new Timer();
            timer.schedule(new TimerTask() {
                
                @Override
                public void run() {
                    // 假设碰到左右边界
                    if (data.mBallX <= data.mBallRadius || data.mBallX >= data.mScreenX - data.mBallRadius) {
                        data.mBallXSpeed = -data.mBallXSpeed;
                    }
                    // 假设位置低于球拍的高度可是不在球拍的范围内,则比赛结束
                    if (data.mBallY > data.mRacketY && (data.mBallX < data.mRacketX ||
                            data.mBallX > data.mRacketX + data.RACKET_WIDTH)) {
                        timer.cancel();
                        data.isGameOver = true;
                    } else if(data.mBallY <= data.mBallRadius ) {
                        data.mBallYSpeed = - data.mBallYSpeed;
                    } else if ( (data.mBallX >= data.mRacketX &&
                            data.mBallX <= data.mRacketX + data.RACKET_WIDTH && 
                                    (data.mBallY + data.mBallRadius) >= data.mRacketY)) {
                        data.mBallYSpeed = - data.mBallYSpeed;
                        Message msg = Message.obtain(handler);
                        msg.what = MsgDefine.MSG_TYPE_VIBROTOR;
                        msg.sendToTarget();
                    }
                    // 小球坐标改变
                    data.mBallX += data.mBallXSpeed;
                    data.mBallY += data.mBallYSpeed;
                    // 发送消息
                    Message msg = Message.obtain(handler);
                    msg.what = MsgDefine.MSG_TYPE_REFRESH;
                    msg.sendToTarget();
                }
            }, data.mTimeDelay, data.mTimeInterval);
        }
        
        /*
         * 球拍左移
         */
        public void racketMoveLeft() {
            if (data.mRacketX >= data.mRacketStep) {
                data.mRacketX += -data.mRacketStep;
            } else {
                data.mRacketX = 0;
            }
        }
        
        /*
         * 球拍右移
         */
        public void racketMoveRight() {
            if ((data.mRacketX + data.RACKET_WIDTH) < data.mScreenX) {
                data.mRacketX += data.mRacketStep;
            } else {
                data.mRacketX = data.mScreenX - data.RACKET_WIDTH;
            }
        }
    }
    
    为了实现较好的效果,我们在成功打击球时,会震动一下(须要在AndroidManifest.xml中添加<uses-permission android:name="android.permission.VIBRATE"/>),并弹toast祝贺。

    最后剩下的是信息数据类MsgDefine。存放着数据的类型。详细来说,包含刷新消息和震动消息类型,代码例如以下:

    package com.example.tableplay;
    
    public class MsgDefine {
        public static final int MSG_TYPE_REFRESH = 1;
        public static final int MSG_TYPE_VIBROTOR = 2;
    }
    
    以上就是小游戏的所有代码实现,做的比較粗糙,希望后面有机会了能够优化下。





  • 相关阅读:
    2015 Multi-University Training Contest 5 1007
    2015 Multi-University Training Contest 5 1002
    ZOJ 3261 Connections in Galaxy War (并查集)
    POJ 2492 A Bug's Life (并查集)
    POJ 1733 Parity game (离散化+带权并查集)
    HDU 3172 Virtual Friends (并查集节点统计)
    HDU 2473 Junk-Mail Filter (并查集节点删除)
    hdu3047 Zjnu Stadium && HDU 3038 How Many Answers Are Wrong (带权并查集)
    HDU 1272 小希的迷宫
    poj 3620 Avoid The Lakes(dfs)
  • 原文地址:https://www.cnblogs.com/lcchuguo/p/5158617.html
Copyright © 2020-2023  润新知