天天看点

Android游戏开发:SurfaceView多点触控之完美钢琴游戏Demo

一、我们在使用SurfaceView开发小游戏时,如果需要在窗体上自绘按钮和可交互对象,这时需要监听屏幕的多点触控,并且每次触控的改变都需要和游戏产生交互,如何实现呢?

CSDN博客 @MXout 有一篇博文:Android自定义View的多点触控 有很详细的介绍,让我们共同学习下,其中最主要的知识点如下:

Android中多点触控技术的基础知识回顾:

对于单点触控,MotionEvent的类型常量及含义如下:

MotionEvent.ACTION_DOWN://触控点按下

MotionEvent.ACTION_MOVE://触控点移动

MotionEvent.ACTION_UP://触控点抬起

对于多点触控,MotionEvent的类型常量及含义如下:

MotionEvent.ACTION_DOWN://(主控点)第一个触控点按下

MotionEvent.ACTION_POINTER_DOWN://(辅控点)第一个之后的触控点按下

MotionEvent.ACTION_MOVE://主、辅控点移动

MotionEvent.ACTION_UP://最后一个触控点抬起

MotionEvent.ACTION_POINTER_UP://非最后一个触控点抬起

多点触控以及主/辅控点的按下或抬起的事件可以通过与运算和移位运算获得本次触控事件的触点id:

int id = (event.getAction()&MotionEvent.ACTION_POINTER_ID_MASK) >>> MotionEvent.ACTION_POINTER_ID_SHIFT;
           

二、自己经过学习,实现了一个完美钢琴游戏Demo,测试了屏幕多点触控的实现方法。如果你有更好的实现,也请多多指教。

按照惯例,先上图:

Android游戏开发:SurfaceView多点触控之完美钢琴游戏Demo

(屏幕上有7个红色的音符键,可以单独按下一个键,也可同时按下多个键,当按下某个键时,颜色变成灰色)

三、完美钢琴游戏源码

(1)OnTouchPoint  触控点类

记录按下触控点的ID,屏幕X,Y坐标

package com.hawkonline.touchscreendome;

public class OnTouchPoint {

    int id;//触控点id
    float x;//触控点x坐标
    float y;//触控点y坐标

    public OnTouchPoint(int id, float x, float y) {
        this.id = id;
        this.x = x;
        this.y = y;
    }

    public void setLocation(float x, float y) {
        this.x = x;
        this.y = y;
    }

    public float getX() {
        return x;
    }

    public float getY() {
        return y;
    }
}

           

(2)NoteKeys 音符按键

在界面上显示出按键(红色),按下键时颜色变成灰色。并且实现了onClick接口,可以在音符按键按下时响应事件。

package com.hawkonline.touchscreendome;

import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RectF;

import java.util.ArrayList;

//音符按键
public class NoteKeys{
    private UClickListener uClickListener;
    private float drawX,drawY,width,height;
    private RectF rectF;
    private Paint mPaint;// 画笔
    private boolean isOnTouch=false;

    public NoteKeys(float drawX,float drawY,float width,float height){
       this.drawX=drawX+5;
       this.drawY=drawY+5;
       this.width=width-10;
       this.height=height-10;

        rectF=new RectF(drawX,drawY,drawX+width,drawY+height);

        mPaint = new Paint();
        mPaint.setAntiAlias(true);// 消除锯齿
        mPaint.setStrokeWidth(5);//设置画笔paint1的粗细度
        mPaint.setStyle(Paint.Style.FILL);//设置画笔paint1的风格

    }

    public void drawSelf(Canvas canvas) {

        // 画按钮外部的圆
        if (isOnTouch){
            mPaint.setColor(Color.GRAY);
        }else{
            mPaint.setColor(Color.RED);
        }

        canvas.drawRect(rectF,mPaint);

    }
    public void setuClickListener(UClickListener uClickListener){
        this.uClickListener=uClickListener;
    }

    public boolean onTouchUView(ArrayList<OnTouchPoint> pointArrayList) {

        for( int i=0;i<pointArrayList.size();i++){
            if(rectF.contains(pointArrayList.get(i).getX(),pointArrayList.get(i).getY())){

               uClickListener.onClick();
                isOnTouch=true;
                return true;
            }
        }
        isOnTouch=false;
        return false;
    }

    interface UClickListener {
        void onClick();
    }
}
           

(3) MySurfaceView 重写SurfaceView实现游戏主程

package com.hawkonline.touchscreendome;

import android.graphics.Canvas;
import android.graphics.Color;
import android.util.Log;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;

import java.util.ArrayList;

public class MySurfaceView extends SurfaceView implements SurfaceHolder.Callback {

    private ArrayList<OnTouchPoint> pointArrayList  = new ArrayList<>();//触控点列表

    private NoteKeys [] noteKeys;


    public  MySurfaceView(MainActivity activity){
        super(activity);

        //this.activity = activity;
        this.getHolder().addCallback(this);
        noteKeys=new NoteKeys[7];

        for(int i=0;i<7;i++){
            noteKeys[i]=new NoteKeys(MainActivity.screenWidth/7*i,MainActivity.screenHeight/2,MainActivity.screenWidth/7,MainActivity.screenHeight/2-20);
            noteKeys[i].setuClickListener(new NoteKeys.UClickListener() {
                @Override
                public void onClick() {
                    //重写方法
                }
            });
        }


    }

    public void onDraw(Canvas canvas){
        canvas.drawColor(Color.BLACK);

        for(NoteKeys k:noteKeys){
          k.drawSelf(canvas);
        }

    }



    public void repaint(){
        SurfaceHolder holder = this.getHolder();
        Canvas canvas = holder.lockCanvas();
        try {
            synchronized (holder){
                onDraw(canvas);
            }
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            if(canvas!=null){
                holder.unlockCanvasAndPost(canvas);
            }
        }
    }

    public boolean onTouchEvent(MotionEvent event){
        int actionMasked = event.getActionMasked();//获得多点触控检测点
        int id = (event.getAction()& MotionEvent.ACTION_POINTER_ID_MASK) >>> MotionEvent.ACTION_POINTER_ID_SHIFT;//无符号右移获取触控点id

        switch (actionMasked){
            case MotionEvent.ACTION_DOWN://第一个触控点按下
                pointArrayList.add(id,new OnTouchPoint(id,event.getX(id), event.getY(id)));//以id索引大小排序插入
                //顺序对应ACTION_MOVE时event.getX(i)取出坐标的顺序
                break;
            case MotionEvent.ACTION_POINTER_DOWN://第一个之后的触控点按下
                pointArrayList.add(id,new OnTouchPoint(id,event.getX(id),event.getY(id)));//触控点按下时,将获得(0 ~ 个数-1)中
                //可用的最小的id号,即后续按下的点可以获得
                //先前已经抬起的触控点的id
                break;
            case MotionEvent.ACTION_MOVE://主、辅点移动
                for (int i = 0;i<pointArrayList.size();i++){
                    try {
                        //float x =event.getX(pointArrayList.get(i).id);//可能会下标越界,因为触控点的id不一定等于其下标
                        float x = event.getX(i);
                        float y = event.getY(i);
                        pointArrayList.get(i).setLocation(x,y);
                    }catch (Exception e){
                        Log.d("MySurfaceView", "onTouchEvent: point.id=" + pointArrayList.get(i).id);
                        e.printStackTrace();
                    }
                }
                //pointArrayList.get(id).setLocation(event.getX(id),event.getY(id));//id=0,即如果没有循环遍历,则只会更新主控点
                break;
            case MotionEvent.ACTION_UP://最后一个点抬起
                pointArrayList.clear();
                break;
            case MotionEvent.ACTION_POINTER_UP://非最后一个点抬起
                pointArrayList.remove(id);//删除一个触控点后,该触控点之后的点会向前移动,使得点的id不一定等于下标
                break;
        }
        for(NoteKeys k:noteKeys){
            k.onTouchUView(pointArrayList);
        }
        repaint();
        return true;
    }

    public void surfaceCreated(SurfaceHolder holder){

    }

    public void surfaceChanged(SurfaceHolder holder,int arg1,int arg2,int arg3){
        this.repaint();
    }

    public void surfaceDestroyed(SurfaceHolder holder){

    }
    
}
           

(4)在MainActivity 中的onCreate 事件中加入以下代码:

public class MainActivity extends AppCompatActivity {
    static float screenHeight;
    static float screenWidth;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

       
        DisplayMetrics metrics = new DisplayMetrics();
        getWindowManager().getDefaultDisplay().getMetrics(metrics);

        screenHeight = metrics.heightPixels;//屏幕大小
        screenWidth = metrics.widthPixels;

        MySurfaceView mySurfaceView = new MySurfaceView(this);
        setContentView(mySurfaceView);
    }
}
           

(5)这样我们就实现了一个完美钢琴Demo,这是我的一个解决思路。不知对否,望指教!!