java 我如何在 android 中正确处理触摸事件?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/6182039/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-30 14:43:49  来源:igfitidea点击:

How do i handle touch events properly in android?

javaandroiduser-interfacemulti-touch

提问by heater

Scope of the project

项目范围

When a user touches the Android screen with two fingers, draw a "Frame" at each touch location with a "cursor" for each frame. Each frame is a custom slider that the cursor will move up and down. All the way up will be 100%, middle will be 0% and all the way down will be -100%. This will be used to control small motors, similar to tank turning, each touch controls a separate motor (sending signals over bluetooth). After a two touch and everything is drawn, I want to be able to lift off either finger, BUT keep the cursor at what ever location it was last at, while the other finger is free to move its cursor. When the last finger is lifted off, everything "hides" and resets to 0%.

当用户用两根手指触摸 Android 屏幕时,在每个触摸位置绘制一个“框架”,每个框架都有一个“光标”。每帧都是一个自定义滑块,光标会上下移动。一路向上将是 100%,中间将是 0%,一路向下将是 -100%。这将用于控制小型电机,类似于坦克转动,每次触摸控制一个单独的电机(通过蓝牙发送信号)。两次触摸并绘制完所有内容后,我希望能够抬起任一手指,但将光标保持在最后一次所在的位置,而另一根手指可以自由移动其光标。当最后一个手指被抬起时,一切都“隐藏”并重置为 0%。

Functionality Wanted

功能需求

  1. On two finger touch, draw separate .pngs under the touch location
  2. After the frames and cursors are drawn, keep track of where they are relative to the frame to determine the percentage.
  3. If a finger is lifted off, keep that fingers cursor at last known location, but the other finger can move it's cursor. Also if the finger is put back down it should be able to move its cursor again.
  4. If both fingers are lifted off of the screen, hide everything and reset percentages to 0%
  1. 在两指触摸时,在触摸位置下绘制单独的 .png
  2. 绘制框架和光标后,跟踪它们相对于框架的位置以确定百分比。
  3. 如果手指被抬起,请将该手指光标保持在最后一个已知位置,但另一个手指可以移动它的光标。此外,如果手指放回原处,它应该能够再次移动光标。
  4. 如果两个手指都离开屏幕,隐藏所有内容并将百分比重置为 0%

Functionality Obtained

获得的功能

  • I can draw the frames and cursors on multitouch
  • Positions and percentages work fine
  • Cursors do move properly
  • 我可以在多点触控上绘制框架和光标
  • 位置和百分比工作正常
  • 光标确实移动正确

What doesn't work

什么不起作用

  • I am unsure if I should have one custom class that handles both touch event or if i should have 2 instances of the custom class each handling their own touch events (I have tried both, the only way i get any "real" functionality is with 1 custom class handling both touch events, the other way doesn't work as intended)
  • When I only have 1 custom class, It works great, but I have it "hide" everything if both fingers are not on the screen, and sometimes android registers that I have lifted a finger off the screen and this causes me a lot of issues when the frames hide then re appear in a different location
  • When I use 2 custom classes I touch each custom class would have its own touch event, and i wouldn't have to worry about multitouch if i split the classes evenly between the screen. This was not the case, still need to deal with multitouch
  • 我不确定我是否应该有一个自定义类来处理这两个触摸事件,或者我是否应该有 2 个自定义类的实例,每个实例都处理自己的触摸事件(我都尝试过,我获得任何“真实”功能的唯一方法是使用1 个自定义类处理两个触摸事件,另一种方式无法按预期工作)
  • 当我只有 1 个自定义类时,它工作得很好,但是如果两个手指都没有在屏幕上,我让它“隐藏”所有内容,有时 android 注册我已经将手指从屏幕上抬起,这给我带来了很多问题当框架隐藏然后重新出现在不同的位置
  • 当我使用 2 个自定义类时,我触摸每个自定义类都会有自己的触摸事件,如果我在屏幕之间均匀地拆分这些类,我就不必担心多点触控。事实并非如此,仍然需要处理多点触控

Can someone explain to me how android handles their touch events. from what I have done, it seems if i lay down finger 1, the finger 2, the first finger will register a "ACTION_DOWN" and the second will register a "ACTION_POINTER_2_DOWN", BUT if i life off my first finger, my second finger is "demoted" and now all of the events my second finger registers does not related to "ACTION_POINTER_2" and instead will be "ACTION_DOWN, ACTION_UP, etc". Is this correct?

有人可以向我解释 android 如何处理他们的触摸事件。从我所做的看来,如果我放下手指 1,手指 2,第一根手指将注册一个“ACTION_DOWN”,第二个将注册一个“ACTION_POINTER_2_DOWN”,但是如果我用我的第一根手指,我的第二根手指被“降级”,现在我的第二根手指注册的所有事件都与“ACTION_POINTER_2”无关,而是“ACTION_DOWN、ACTION_UP 等”。这个对吗?

TouchUI.java

触摸界面

    package com.robota.android;

    import android.content.Context;
    import android.graphics.Bitmap;
    import android.graphics.BitmapFactory;
    import android.graphics.Canvas;
    import android.util.AttributeSet;
    import android.util.Log;
    import android.view.MotionEvent;
    import android.widget.ImageView;

public class TouchUI extends ImageView {

public static final String LEFT_TOUCHUI = "com.robota.android:id/leftTouchUI";
public static final String RIGHT_TOUCHUI = "com.robota.android:id/rightTouchUI";
private String whoAmI = new String();
private MyPoints framePts = new MyPoints();
private MyPoints cursorPts = new MyPoints();
private Bitmap frame;
private Bitmap cursor;
private int frameWidth;
private int frameHeight;
private int cursorHeight;
private boolean pointerDown = false;
private int dy;

public TouchUI(final Context context, final AttributeSet as){
    super(context, as);
    Log.d("TouchUI", getResources().getResourceName(this.getId()));
    whoAmI = new String(getResources().getResourceName(this.getId()));
    if(whoAmI.equals(LEFT_TOUCHUI)){
        frame = BitmapFactory.decodeResource(getResources(), R.drawable.tank_left);
    }else if(whoAmI.equals(RIGHT_TOUCHUI)){
        frame = BitmapFactory.decodeResource(getResources(), R.drawable.tank_right);
    }
    cursor = BitmapFactory.decodeResource(getResources(), R.drawable.cursor);
    frameWidth = frame.getWidth();
    frameHeight = frame.getHeight();
    cursorHeight = cursor.getHeight();
}

public void determinePointers(int x, int y){
        framePts.setOrigin(x-frameWidth/2, y-frameHeight/2);
        cursorPts.setOrigin(x-frameWidth/2, y-frameHeight/2);
}

@Override
public boolean onTouchEvent(MotionEvent e){
    int x = 0;
    int y = 0;
    Log.d("TouchUI", ">>>>> " + whoAmI);
    if(e.getAction() == MotionEvent.ACTION_DOWN){
        determinePointers(x,y);
        pointerDown = true;
    }else if(e.getAction() == MotionEvent.ACTION_UP){
        pointerDown = false;
    }else if(e.getAction() == MotionEvent.ACTION_MOVE){
        dy = (int)e.getY()-framePts.getY();
        if(dy <= 0){
            dy=0;
        }else if(dy+cursorHeight/2 >= frameHeight){
            dy=frameHeight;
        }
        sendMotorSpeed(dy);
    }
    return true;
}

public void sendMotorSpeed(int dy){
    float motor = dy;
    motor-=frameHeight;
    motor*=-1;

    motor = (motor/frameHeight)*255;

    PacketController.updateMotorSpeeds(whoAmI, (int)motor);
}

public void onDraw(Canvas canvas){
    if(pointerDown){//twoDown){
        canvas.drawBitmap(frame, framePts.getX(), framePts.getY(), null);
        canvas.drawBitmap(cursor, cursorPts.getX(), (cursorPts.getY()+dy), null);
    }
    invalidate();
}

private class MyPoints{

    private int x = -100;
    private int y = -100;
    private int deltaY = 0;;

    public MyPoints(){
        this.x = 0;
        this.y = 0;
    }

    public int getX(){
        return this.x;
    }

    public int getY(){
        return this.y;
    }

    public void setOrigin(int x, int y){
        this.x = x;
        this.y = y;
    }

    public int getDeltaY(){
        return deltaY;
    }

    public void setDeltaY(int newY){
        deltaY = (newY-y);
        Log.d("TouchUI", "DY: " + deltaY);
    }
}
}

Main.xml

主文件

    <?xml version="1.0" encoding="utf-8"?>
    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/parentLayout"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical">
<LinearLayout android:orientation="horizontal"
              android:layout_width="match_parent"
              android:layout_height="match_parent">
    <com.robota.android.TouchUI xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@+id/leftTouchUI"
        android:background="#0000"
        android:layout_height="match_parent"
        android:layout_width="wrap_content"
        android:layout_weight="1">
    </com.robota.android.TouchUI>
    <com.robota.android.TouchUI xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@+id/rightTouchUI"
        android:background="#0000"
        android:layout_height="match_parent"
        android:layout_width="wrap_content"
        android:layout_weight="1">
    </com.robota.android.TouchUI>
</LinearLayout>

RobotController.java (Main Activity Class)

RobotController.java(主活动类)

    package com.robota.android;

    import android.app.Activity;
    import android.bluetooth.BluetoothAdapter;
    import android.bluetooth.BluetoothDevice;
    import android.content.ActivityNotFoundException;
    import android.content.Intent;
    import android.os.Bundle;
    import android.os.Handler;
    import android.os.Message;
    import android.util.Log;
    import android.view.Menu;
    import android.view.MenuInflater;
    import android.view.MenuItem;
    import android.view.View;
    import android.view.Window;
    import android.widget.ScrollView;
    import android.widget.TextView;
    import android.widget.Toast;

public class RobotController extends Activity {
// Tag used to keep track of class in the Log
private static final String TAG = "robotController_new";
// Boolean to debugging
private static final boolean D = true;

// Intent request codes
private static final int DISCONNECT_DEVICE = 1;
private static final int CONNECT_DEVICE = 2;
private static final int REQUEST_ENABLE_BT = 3;

// Handler Codes
public static final int MESSAGE_READ = 1;
public static final int MESSAGE_WRITE = 2;

// Local Bluetooth Adapter
private BluetoothAdapter bluetoothAdapter = null;
// Bluetooth Discovery and Datahandler
private BluetoothComm btComm = null;

// Debug's TextView, this is where strings will be written to display
private TextView tv;
private ScrollView sv;

/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
    if(D) Log.d(TAG, "++ON CREATE++");
    super.onCreate(savedInstanceState);
    requestWindowFeature(Window.FEATURE_NO_TITLE);
    setContentView(R.layout.main);

    bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();

    if(bluetoothAdapter == null){
        if(D) Log.d(TAG, "NO BLUETOOTH DEVICE");
        Toast.makeText(this, "Bluetooth is not available", Toast.LENGTH_SHORT).show();
        finish();
        return;
    }

    PacketController.controller = this;
}


public void onStart(){
    super.onStart();
    if(D) Log.d(TAG, "++ON START++");

    if(!bluetoothAdapter.isEnabled()){
        Intent enableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
        startActivityForResult(enableIntent, REQUEST_ENABLE_BT);
    }else{
        // Start BluetoothComm
        if(btComm == null){
            setupComm();
        }
    }
}

/**
 * Creates new Bluetooth Communication
 */
private void setupComm(){
    if(D) Log.d(TAG, "+++setupComm+++");
    btComm = new BluetoothComm(this, handler);
}

private void connectDevice(Intent data){
    if(D) Log.d(TAG, "+++connectDevice+++");
    String addr = data.getExtras()
        .getString(DeviceListActivity.EXTRA_DEVICE_ADDRESS);
    BluetoothDevice device = bluetoothAdapter.getRemoteDevice(addr);
    if(D) Log.d(TAG,"REMOTE ADDR: "+ addr);
    btComm.connect(device);
}

private void disconnectDevice(){
    if(D) Log.d(TAG, "---disconnectDevice---");
    if(btComm.getState() == btComm.STATE_CONNECTED){
        btComm.disconnect();
    }
}

@Override
public boolean onCreateOptionsMenu(Menu menu)
{
    //super.onCreateOptionsMenu(menu);
    MenuInflater inflater = getMenuInflater();
    inflater.inflate(R.menu.menu, menu);
    return true;
}

@Override
public boolean onOptionsItemSelected(MenuItem item) {
    Intent serverIntent = null;
    switch(item.getItemId()){       
    case R.id.insecure_connect_scan:
        // Launch the DeviceListActivity to see devices and do scan
        serverIntent = new Intent(this, DeviceListActivity.class);
        try{
            startActivityForResult(serverIntent, CONNECT_DEVICE);
        }catch(ActivityNotFoundException activityNotFound){
            Log.e(TAG, "Could not start DeviceListActivity(Insecure)");
        }
        return true;
    }
    return false;
}

@Override
public void onActivityResult(int requestCode, int resultCode, Intent data){
    switch(requestCode){
    case CONNECT_DEVICE:
        if(resultCode == Activity.RESULT_OK){
            connectDevice(data);
        }
        break;
    case DISCONNECT_DEVICE:
        if(resultCode == Activity.RESULT_OK){
            disconnectDevice();
        }
        break;
    }
}

public Handler getHandler(){
    return this.handler;
}

public BluetoothComm getBtComm(){
    return this.btComm;
}

// The Handler that gets information back from the BluetoothChatService
private final Handler handler = new Handler() {
    @Override
    public void handleMessage(Message msg) {
        if(D) Log.d(TAG, "check message");
        switch (msg.what) {
        case MESSAGE_READ:
            if(D) Log.d(TAG, "trying to read message");
            byte[] readBuf = (byte[]) msg.obj;
            // construct a string from the valid bytes in the buffer
            String readMessage = new String(readBuf, 0, msg.arg1);
            if(D) Log.d(TAG, "bytes: " + readBuf + " arg1: " + msg.arg1 + " Message: " + readMessage);
            tv.append(readMessage);
            break;
        case MESSAGE_WRITE:
            if(D) Log.d(TAG, "trying to send message");
            String sendMessage = new String(String.valueOf(msg.obj));
        }
    }
};
}

Any other classes not listed I didn't believe needed to be, but if they are needed please let me know.

我认为没有必要列出任何其他未列出的课程,但如果需要,请告诉我。

Any help is much appreciated

任何帮助深表感谢

采纳答案by Geobits

You're going to need to save the pointerId's of each point and compare them to the new Id's given with each MotionEvent. It's slightly tricky to explain, so I'll point you to this ADB Postthat explains it much better than I could. Long story short? Multitouch can be tricksy, but it's not as bad as it looks at first glance.

您将需要保存每个点的指针 ID,并将它们与每个 MotionEvent 给出的新 ID 进行比较。解释起来有点棘手,所以我会向你指出这篇亚行帖子,它比我能更好地解释它。长话短说?多点触控可能很棘手,但并不像乍一看那么糟糕。