实现效果
能够实现触摸球随手指移动而移动,点击正向回放按钮和反向回放按钮可以实现上一段轨迹的重放,输入毫秒,可以控制轨迹回放的速度(通过延迟时长实现)
设计思路
界面设计
比较简单,用到Button、TextView、EditText组件,用线性布局LinearLayout,把几个组件位置固定。设定一个FrameLayout,来表示触摸有效区域,通过设置它的height、width来划出区域
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<FrameLayout
android:id="@+id/frameLayout"
android:layout_width="match_parent"
android:layout_height="300dip">
<TextView
android:id="@+id/touch_area"
android:layout_width="fill_parent"
android:layout_height="300dip"
android:background="#0FF"
android:text="触摸事件测试区域"
android:textColor="#FFFFFF"></TextView>
<TextView
android:id="@+id/trackballTextView"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_marginTop="16dp"
android:gravity="center"
android:text="触摸球"
android:textColor="#000"
android:textSize="10sp" />
</FrameLayout>
<EditText
android:id="@+id/speedEditText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_gravity="center"
android:hint="输入回放速度(毫秒)"
android:inputType="number"
android:minHeight="48dp" />
<Button
android:id="@+id/replayButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/speedEditText"
android:layout_gravity="center"
android:layout_marginTop="10dp"
android:text="正向回放" />
<Button
android:id="@+id/replayReverseButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/replayButton"
android:layout_centerHorizontal="true"
android:layout_gravity="center"
android:layout_marginTop="16dp"
android:text="反向回放" />
</LinearLayout>
业务逻辑
回放轨迹记录:
使用触摸监听器,监听当前触摸点的坐标(x,y),每一次按压(DOWN)清空以前的列表,记录该点,存入列表中,在手指触摸移动(MOVE)过程中,不断向列表中add坐标点,在手指松开(UP)后,本次轨迹记录结束。
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
// 显示轨迹球并记录起点
trackPointsX.clear();
trackPointsY.clear();
trackPointsX.add(x);
trackPointsY.add(y);
trackballTextView.setX(x);
trackballTextView.setY(y);
trackballTextView.setVisibility(View.VISIBLE);
return true;
case MotionEvent.ACTION_MOVE:
// 记录移动过程中的点
trackPointsX.add(x);
trackPointsY.add(y);
// 实时更新轨迹
trackballTextView.setX(x);
trackballTextView.setY(y);
return true;
case MotionEvent.ACTION_UP:
// 隐藏轨迹球
trackballTextView.setVisibility(View.INVISIBLE);
// 开始回放
if (!trackPointsX.isEmpty() && !trackPointsY.isEmpty()) {
replayTrajectory();
}
return true;
}
回放轨迹实现:
利用Handler来实现延迟一段时间后执行指定的任务,利用Runnable()来创建一个匿名内部类对象,该匿名内部类实现了Runnable接口,因此必须实现run()方法,run方法是该线程操作的主方法。
在run方法中实现方法用于控制轨迹球的移动,并在一定条件下递归调用handler方法,实现了轨迹球的连续移动效果。也就是handler每隔设定的时间间隔,在列表中查询是否有未取的点,从而一直回放轨迹,当没有点位可取时,就结束本次任务。
handler.postDelayed(new Runnable() {
@Override
public void run() {
if (currentIndex[0] < totalFrames) {
float x = trackPointsX.get(indices[currentIndex[0]]);
float y = trackPointsY.get(indices[currentIndex[0]]);
trackballTextView.setX(x);
trackballTextView.setY(y);
trackballTextView.setVisibility(View.VISIBLE); // 设置轨迹球可见
currentIndex[0]++;
handler.postDelayed(this, replaySpeed);
} else {
// 回放结束,重置状态
isReplaying = false;
}
}
}, replaySpeed);
主体代码
public class MainActivity extends Activity {
private FrameLayout frameLayout;
private TextView trackballTextView;
private Button replayButton;
private TextView speedEditText;
private List<Float> trackPointsX = new ArrayList<>();
private List<Float> trackPointsY = new ArrayList<>();
private boolean isReplaying = false;
private int replaySpeed = 50; // 默认回放速度为每50毫秒移动一次
private Handler handler = new Handler();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
frameLayout = findViewById(R.id.frameLayout);
trackballTextView = findViewById(R.id.trackballTextView);
replayButton = findViewById(R.id.replayButton);
speedEditText = findViewById(R.id.speedEditText);
// 隐藏轨迹球
trackballTextView.setVisibility(View.INVISIBLE);
// 设置轨迹球的触摸监听器
frameLayout.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
float x = event.getX();
float y = event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
// 显示轨迹球并记录起点
trackPointsX.clear();
trackPointsY.clear();
trackPointsX.add(x);
trackPointsY.add(y);
trackballTextView.setX(x);
trackballTextView.setY(y);
trackballTextView.setVisibility(View.VISIBLE);
return true;
case MotionEvent.ACTION_MOVE:
// 记录移动过程中的点
trackPointsX.add(x);
trackPointsY.add(y);
// 实时更新轨迹
trackballTextView.setX(x);
trackballTextView.setY(y);
return true;
case MotionEvent.ACTION_UP:
// 隐藏轨迹球
trackballTextView.setVisibility(View.INVISIBLE);
// 开始回放
if (!trackPointsX.isEmpty() && !trackPointsY.isEmpty()) {
replayTrajectory();
}
return true;
}
return false;
}
});
// 设置回放按钮的点击监听器
replayButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (!isReplaying) {
// 获取用户输入的回放速度
String speedText = speedEditText.getText().toString();
if (!speedText.isEmpty()) {
replaySpeed = Integer.parseInt(speedText);
}
replayTrajectory();
}
}
});
}
// 回放轨迹方法
private void replayTrajectory() {
if (trackPointsX.isEmpty() || trackPointsY.isEmpty()) return;
// 设置回放状态为true
isReplaying = true;
// 重置回放索引
int totalFrames = trackPointsX.size();
final int[] reversedIndices = new int[totalFrames];
for (int i = 0; i < totalFrames; i++) {
reversedIndices[i] = totalFrames - 1 - i;
}
// 开始逐帧移动
final int[] currentIndex = {0};
handler.postDelayed(new Runnable() {
@Override
public void run() {
if (currentIndex[0] < totalFrames) {
float x = trackPointsX.get(reversedIndices[currentIndex[0]]);
float y = trackPointsY.get(reversedIndices[currentIndex[0]]);
trackballTextView.setX(x);
trackballTextView.setY(y);
trackballTextView.setVisibility(View.VISIBLE); // 设置轨迹球可见
currentIndex[0]++;
handler.postDelayed(this, replaySpeed);
} else {
// 回放结束,重置状态
isReplaying = false;
}
}
}, replaySpeed); // 延迟开始回放
}
}