我如何在android中正确处理触摸事件?
项目范围
当用户用两根手指触摸 Android 屏幕时,在每个触摸位置绘制一个“框架”,每个框架都有一个“光标”。每个框架都是一个自定义滑块,光标将上下移动。一路向上为 100%,中间为 0%,一路向下为-100%。这将用于控制小型电机,类似于坦克转动,每次触摸控制一个单独的电机(通过蓝牙发送信号)。两次触摸并绘制所有内容后,我希望能够松开任一手指,但将光标保持在上次所在的位置,而另一根手指可以自由移动光标。当最后一根手指松开时,所有内容都会“隐藏”并重置为 0%。
所需功能
- 在两根手指触摸时,在触摸位置下绘制单独的 .png
- 绘制框架和光标后,跟踪它们相对于框架的位置以确定百分比。
- 如果手指离开,则将该手指光标保持在最后已知的位置,但另一根手指可以移动其光标。此外,如果手指放回原处,它应该能够再次移动光标。
- 如果双手指离开屏幕,隐藏所有内容并将百分比重置为 0%
获得功能
- 我可以在多点触控上绘制框架和光标 位置
- 和百分比工作正常
- 光标可以正确移动
什么不起作用
- 我不确定我是否应该有一个自定义类来处理两个触摸事件,或者我是否应该有两个自定义类实例,每个实例都处理自己的触摸事件(我已经尝试过这两种方法,这是我得到的唯一方法)任何“真实”功能是使用 1 个自定义类处理两个触摸事件,另一种方式无法按预期工作)
- 当我只有 1 个自定义类时,它效果很好,但如果两个手指不在屏幕上,我会“隐藏”所有内容,有时android会注册我已经将手指从屏幕上抬起,当框架隐藏然后重新出现在不同的位置时,这会导致很多问题当
- 我使用2个自定义类时,我触摸每个自定义类都会有自己的触摸事件,如果我的话,我就不必担心多点触控在屏幕之间均匀分配班级。事实并非如此,仍然需要处理多点触控
有人可以向我解释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(主活动类)
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));
}
}
};
}
任何未列出的其他类我认为不需要,但如果需要,请告诉我。
非常感谢任何帮助
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%.
Functionality Wanted
- On two finger touch, draw separate .pngs under the touch location
- After the frames and cursors are drawn, keep track of where they are relative to the frame to determine the percentage.
- 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.
- If both fingers are lifted off of the screen, hide everything and reset percentages to 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
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?
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)
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
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
您需要保存每个点的pointerId,并将它们与每个MotionEvent 给出的新Id 进行比较。解释起来有点棘手,所以我会向您指出这个 ADB Post 比我解释得更好。长话短说?多点触控可能很棘手,但它并不像乍一看那么糟糕。
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 Post that 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.