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()方法中,我们实现了左移和右移的推断。

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

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" >

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

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;
}

以上就是小游戏的所有代码实现,做的比較粗糙,希望后面有机会了能够优化下。

时间: 2024-10-07 05:29:50

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

IOS学习之道:使用UIButton纯手工打造的黑白快小游戏.

由于代码量比较多,有兴趣的同学可以去我的资源页进行下载. http://download.csdn.net/detail/tx874828503/8637445 使用UIButton纯手工打造的黑白快小游戏.请勿商业使用,转载请注明出处,谢谢

最近微信上很火的小游戏【壹秒】android版——开发分享

最近在朋友圈,朋友转了一个html小游戏[壹秒],游戏的规则是:用户按住按钮然后释放,看谁能精准地保持一秒的时间.^_^刚好刚才在linux服务器上调试程序的时候服务器挂了,腾出点时间分享下前天自己做的这个小游戏. 话不多说,直接上图:     呐,作为一个对android无比热爱的小伙伴,腾出一个小小时间做一个小小的小游戏,也不失为一种享受哈.整个游戏比较简单,主要分为UI设计与开发和时间计算逻辑.当然,这个小游戏界面要做得漂亮点,所以我侧重讲下UI设计与开发. UI设计与开发 采用Relat

android 绘图之Canvas,Paint类

Canvas,Paint 1.在android 绘图但中经常要用到Canvas和Paint类,Canvas好比是一张画布,上面已经有你想绘制图画的轮廓了,而Paint就好比是画笔,就要给Canvas进行添色等操作. 这两个类通常都是在onDraw(Canvas canvas)方法中用的. 2.Bitmap:代表一张位图,BitmapDrawable里封装的突变就是一个Bitmao对象 3.Canvas里面有一些例如: drawArc(参数) 绘制弧 drawBitmao(Bitmap bitma

Android 2D Graphics学习 Region和Canvas裁剪

1.首先介绍Region类 Region,中文意思即区域的意思,它表示的是canvas图层上的某一块封闭的区域. [java] view plaincopyprint? /**构造方法*/ public Region()  //创建一个空的区域 public Region(Region region) //拷贝一个region的范围 public Region(Rect r)  //创建一个矩形的区域 public Region(int left, int top, int right, int

Android 2D绘图解析之 Canvas,Paint

原创文章,转载请注明 ( 来自:http://blog.csdn.net/leejizhou/article/details/51524948 李济洲的博客 ) [Android 2D绘图解析]系列文章将全面介绍Android绘图相关,这篇简单介绍下如何利用Android API进行一些简单图形的绘制,绘图的前提是需要继承自View,然后重写它的onDraw(Canvas canvas) 方法即可. 首先我们新建一个类继承自View,并重写onDraw方法. package com.leejz.

Android中2D绘图工具类(Canvas,Paint和Color)简介

通常的Android应用开发中都要图片,找美工设计些图片,放到项目中就可以了,不过除了使用现有的图片外,我们也可以用2D绘图,就是在View画一些图案或文字.其中通常要用到的类有Canvas(画布),Paint(画笔)及Color(用来设置画笔颜色)类,当然还可能用到其它很多类,都是根据自己项目中的需要来定的. Canvas:画布,用来直接在View上绘制诸如矩形,圆形,文字,位图等图形.官方api网址:http://developer.android.com/reference/android

疯狂Android讲义 - 学习笔记(六)

第7章 7.1 使用简单图片 7.1.1 使用Drawable对象 7.1.2 Bitmap和BitmapFactory 7.2 绘图 7.2.1 Android绘图基础:Canvas.Paint等 Android的绘图应该继承View组件,并重写onDraw(Canvas canvas) 方法即可,Canvas代表了“依附”于指定View的画布.表7.1 Canvas的绘制方法 Paint代表了Canvas上的画笔,Paint类主要用于设置绘制风格,包括画笔颜色.画笔粗细.填充风格等. 7.2

图形绘制 Canvas Paint Path 详解

图形绘制简介        Android中使用图形处理引擎,2D部分是android SDK内部自己提供,3D部分是用Open GL ES 1.0.大部分2D使用的api都在android.graphics和android.graphics.drawable包中.他们提供了图形处理相关的Canvas.ColorFilter.Point.RetcF等类,还有一些动画相关的AnimationDrawable.BitmapDrawable.TransitionDrawable等.       以图形

Android Animation学习(二) ApiDemos解析:基本Animatiors使用

Animator类提供了创建动画的基本结构,但是一般使用的是它的子类: ValueAnimator.ObjectAnimator.AnimatorSet ApiDemos中Animation部分是单独的一个包. 下面代码来自ApiDemos中的AnimationCloning类,加了一个使用ValueAnimator的动画,还有一些注释. 完整的项目见:URL:https://github.com/mengdd/AnimationApiDemos.git package com.example.