Activity初学乍练
感谢你提到 Activity 初学乍练!Activity
是 Android 开发的核心组件之一,是用户与应用交互的主要界面。作为初学者,理解 Activity
的基本概念、生命周期、使用场景以及如何与其他组件结合(如你之前提到的 AlertDialog
、PopupWindow
、Menu
、ViewPager2
、DrawerLayout
、TextWatcher
、Configuration
、AsyncTask
、Gestures
)是至关重要的。本回答将为初学者提供 Activity 的全面入门指南,结合你提到的组件和相关话题(如 Handler
、OnTouchListener
、onTouchEvent
、多点触碰
),通过一个综合示例帮助你快速上手,包括代码、优化建议及常见问题处理。如果你的需求涉及特定功能(如复杂 UI、数据传递、生命周期管理)或有其他疑问,请提供更多细节,我可以进一步定制答案。
1. Activity 简介
定义:Activity
是 Android 应用的四大组件之一,代表一个用户界面,通常对应一个屏幕(如主界面、设置页面)。每个 Activity
负责显示 UI 并处理用户交互(如点击、输入、手势)。
核心概念:
- 生命周期:
Activity
有明确的生命周期,管理其创建、运行、暂停、恢复和销毁。 - 布局:通过 XML(
res/layout
)或代码动态定义 UI。 - 事件处理:通过监听器(如
OnClickListener
、TextWatcher
)或手势(如GestureDetector
)响应用户操作。 - 组件集成:与
Fragment
、ViewPager2
、DrawerLayout
等结合实现复杂 UI。 - 数据传递:通过
Intent
在Activity
间传递数据。 - 配置变化:处理屏幕旋转、语言切换等(
Configuration
)。
用途:
- 显示 UI(如按钮、输入框、图像)。
- 处理用户交互(如点击、滑动)。
- 管理应用导航(如跳转到其他
Activity
或打开DrawerLayout
)。 - 执行异步任务(如网络请求)。
局限性:
- 生命周期管理复杂,需小心处理(如保存状态)。
- 配置变化可能导致重启,需正确配置。
- 性能敏感,复杂 UI 或频繁操作可能卡顿。
2. Activity 生命周期
Activity
的生命周期包含以下关键方法,初学者需要熟悉:
onCreate(Bundle savedInstanceState)
:
- 触发:
Activity
创建时调用。 - 用途:初始化 UI、绑定数据、设置监听器。
- 注意:仅调用一次,需检查
savedInstanceState
恢复状态。
onStart()
:
- 触发:
Activity
变为可见。 - 用途:准备显示界面。
onResume()
:
- 触发:
Activity
获得焦点,可交互。 - 用途:启动动画、刷新数据。
onPause()
:
- 触发:
Activity
失去焦点(如被覆盖)。 - 用途:暂停动画、保存临时数据。
onStop()
:
- 触发:
Activity
完全不可见。 - 用途:释放资源、保存状态。
onDestroy()
:
- 触发:
Activity
销毁。 - 用途:清理资源(如取消
AsyncTask
、移除监听器)。
onRestart()
:
- 触发:从停止状态重新启动。
- 用途:恢复操作。
生命周期图:
onCreate() → onStart() → onResume()
↳ onPause() → onStop() → onRestart() → onStart() → onResume()
↳ onDestroy()
常见问题:
- 配置变化(如屏幕旋转)默认导致
Activity
重启,需处理状态保存。 - 未正确清理资源可能导致内存泄漏。
3. 初学者综合示例
以下是一个综合示例,展示 Activity
的基本使用,结合你提到的组件(DrawerLayout
、ViewPager2
、TextWatcher
、Configuration
、AsyncTask
、Gestures
)以及 Handler
和多点触碰。示例功能包括:
- 主界面:包含
EditText
(输入监听)、ImageView
(手势处理)、ViewPager2
(页面切换)。 - 侧滑菜单:通过
DrawerLayout
切换页面或触发异步任务。 - 手势:支持单击、双击、缩放。
- 异步任务:模拟网络请求,更新 UI。
- 配置变化:响应屏幕方向变化。
- 数据保存:处理生命周期状态。
3.1 布局文件(res/layout/activity_main.xml
)
包含 DrawerLayout
、Toolbar
、EditText
、ImageView
和 ViewPager2
。
3.2 菜单资源(res/menu/nav_menu.xml
)
侧滑菜单。
3.3 字符串资源(res/values/strings.xml
)
支持多语言。
Activity 示例 Open navigation drawer Close navigation drawer Home Profile Settings Enter text
中文资源(res/values-zh/strings.xml
):
Activity 示例 打开导航抽屉 关闭导航抽屉 首页 个人中心 设置 请输入文本
3.4 Fragment 页面(PageFragment.java
)
用于 ViewPager2
的页面。
package com.example.myapp;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
public class PageFragment extends Fragment {
private static final String ARG_PAGE = “page”;
private static final String ARG_RESULT = “result”;
private TextView textView;
public static PageFragment newInstance(int page, String result) {
PageFragment fragment = new PageFragment();
Bundle args = new Bundle();
args.putInt(ARG_PAGE, page);
args.putString(ARG_RESULT, result);
fragment.setArguments(args);
return fragment;
}
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(android.R.layout.simple_list_item_1, container, false);
textView = view.findViewById(android.R.id.text1);
updateContent();
return view;
}
public void updateResult(String result) {
if (getArguments() != null) {
getArguments().putString(ARG_RESULT, result);
updateContent();
}
}
private void updateContent() {
int page = getArguments() != null ? getArguments().getInt(ARG_PAGE) : 0;
String result = getArguments() != null ? getArguments().getString(ARG_RESULT, "") : "";
textView.setText("页面 " + (page + 1) + "\n结果: " + (result.isEmpty() ? "无" : result));
}
}
3.5 AsyncTask 子类(NetworkTask.java
)
模拟网络请求。
package com.example.myapp;
import android.os.AsyncTask;
import android.widget.ProgressBar;
import android.widget.TextView;
import androidx.appcompat.app.AlertDialog;
import java.lang.ref.WeakReference;
public class NetworkTask extends AsyncTask {
private final WeakReference activityRef;
private final WeakReference progressBarRef;
private final WeakReference resultTextRef;
public NetworkTask(MainActivity activity, ProgressBar progressBar, TextView resultText) {
activityRef = new WeakReference<>(activity);
progressBarRef = new WeakReference<>(progressBar);
resultTextRef = new WeakReference<>(resultText);
}
@Override
protected void onPreExecute() {
ProgressBar progressBar = progressBarRef.get();
if (progressBar != null) {
progressBar.setVisibility(View.VISIBLE);
}
}
@Override
protected String doInBackground(String... params) {
try {
String input = params[0];
for (int i = 1; i <= 100; i += 10) {
Thread.sleep(500);
publishProgress(i);
if (isCancelled()) {
return null;
}
}
return "任务完成: " + input;
} catch (InterruptedException e) {
return "错误: " + e.getMessage();
}
}
@Override
protected void onProgressUpdate(Integer... values) {
ProgressBar progressBar = progressBarRef.get();
if (progressBar != null) {
progressBar.setProgress(values[0]);
}
}
@Override
protected void onPostExecute(String result) {
ProgressBar progressBar = progressBarRef.get();
TextView resultText = resultTextRef.get();
MainActivity activity = activityRef.get();
if (progressBar != null && resultText != null && activity != null) {
progressBar.setVisibility(View.GONE);
resultText.setText(result != null ? result : "任务取消");
if (result != null) {
new AlertDialog.Builder(activity)
.setTitle("结果")
.setMessage(result)
.setPositiveButton("确定", null)
.show();
activity.updateViewPagerResult(result);
}
}
}
@Override
protected void onCancelled() {
ProgressBar progressBar = progressBarRef.get();
TextView resultText = resultTextRef.get();
MainActivity activity = activityRef.get();
if (progressBar != null && resultText != null && activity != null) {
progressBar.setVisibility(View.GONE);
resultText.setText("任务取消");
new AlertDialog.Builder(activity)
.setTitle("取消")
.setMessage("任务被取消")
.setPositiveButton("确定", null)
.show();
}
}
}
3.6 AndroidManifest.xml
声明支持配置变化。
3.7 Activity 代码(MainActivity.java
)
综合示例,包含生命周期管理、手势处理、异步任务等。
package com.example.myapp;
import android.content.res.Configuration;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.view.View;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.appcompat.app.ActionBarDrawerToggle;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import androidx.core.view.GestureDetectorCompat;
import androidx.core.view.GravityCompat;
import androidx.drawerlayout.widget.DrawerLayout;
import androidx.fragment.app.Fragment;
import androidx.viewpager2.adapter.FragmentStateAdapter;
import androidx.viewpager2.widget.ViewPager2;
import com.google.android.material.navigation.NavigationView;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
public class MainActivity extends AppCompatActivity {
private DrawerLayout drawerLayout;
private ViewPager2 viewPager;
private EditText inputEditText;
private TextView resultText;
private ProgressBar progressBar;
private ImageView gestureImage;
private TextWatcher textWatcher;
private ViewPagerAdapter viewPagerAdapter;
private GestureDetectorCompat gestureDetector;
private ScaleGestureDetector scaleDetector;
private NetworkTask asyncTask;
private float scale = 1.0f;
private Handler handler;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Toast.makeText(this, "onCreate", Toast.LENGTH_SHORT).show();
// 初始化 Toolbar
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
// 初始化 DrawerLayout
drawerLayout = findViewById(R.id.drawerLayout);
ActionBarDrawerToggle toggle = new ActionBarDrawerToggle(
this, drawerLayout, toolbar,
R.string.navigation_drawer_open, R.string.navigation_drawer_close);
drawerLayout.addDrawerListener(toggle);
toggle.syncState();
// 侧滑菜单处理
NavigationView navigationView = findViewById(R.id.navigationView);
navigationView.setNavigationItemSelectedListener(item -> {
int itemId = item.getItemId();
if (itemId == R.id.nav_home) {
Toast.makeText(this, R.string.menu_home, Toast.LENGTH_SHORT).show();
viewPager.setCurrentItem(0);
} else if (itemId == R.id.nav_profile) {
Toast.makeText(this, R.string.menu_profile, Toast.LENGTH_SHORT).show();
viewPager.setCurrentItem(1);
} else if (itemId == R.id.nav_settings) {
String input = inputEditText.getText().toString();
if (asyncTask != null && asyncTask.getStatus() == AsyncTask.Status.RUNNING) {
asyncTask.cancel(true);
} else {
asyncTask = new NetworkTask(this, progressBar, resultText);
asyncTask.execute(input);
}
}
drawerLayout.closeDrawers();
return true;
});
// 初始化 ViewPager2
viewPager = findViewById(R.id.viewPager);
viewPagerAdapter = new ViewPagerAdapter(this);
viewPager.setAdapter(viewPagerAdapter);
// 初始化 UI 组件
inputEditText = findViewById(R.id.inputEditText);
resultText = findViewById(R.id.resultText);
progressBar = findViewById(R.id.progressBar);
gestureImage = findViewById(R.id.gestureImage);
handler = new Handler(Looper.getMainLooper());
// 初始化 TextWatcher
textWatcher = new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {}
@Override
public void afterTextChanged(Editable s) {
String input = s.toString();
if (input.equalsIgnoreCase("english")) {
setLocale(new Locale("en"));
} else if (input.equalsIgnoreCase("chinese")) {
setLocale(new Locale("zh"));
} else if (input.equalsIgnoreCase("reset")) {
scale = 1.0f;
gestureImage.setScaleX(scale);
gestureImage.setScaleY(scale);
resultText.setText("结果: 重置");
viewPagerAdapter.updateResult("重置");
}
}
};
inputEditText.addTextChangedListener(textWatcher);
// 初始化手势检测
gestureDetector = new GestureDetectorCompat(this, new GestureDetector.SimpleOnGestureListener() {
@Override
public boolean onSingleTapUp(MotionEvent e) {
resultText.setText("手势: 单击");
viewPagerAdapter.updateResult("单击");
return true;
}
@Override
public boolean onDoubleTap(MotionEvent e) {
resultText.setText("手势: 双击");
viewPagerAdapter.updateResult("双击");
handler.post(() -> new AlertDialog.Builder(MainActivity.this)
.setTitle("双击")
.setMessage("触发双击手势")
.setPositiveButton("确定", null)
.show());
return true;
}
});
scaleDetector = new ScaleGestureDetector(this, new ScaleGestureDetector.SimpleOnScaleGestureListener() {
@Override
public boolean onScale(ScaleGestureDetector detector) {
scale *= detector.getScaleFactor();
scale = Math.max(0.5f, Math.min(scale, 2.0f));
gestureImage.setScaleX(scale);
gestureImage.setScaleY(scale);
resultText.setText("手势: 缩放 (比例: " + String.format("%.2f", scale) + ")");
viewPagerAdapter.updateResult("缩放 (比例: " + String.format("%.2f", scale) + ")");
return true;
}
});
gestureImage.setOnTouchListener((v, event) -> {
viewPager.requestDisallowInterceptTouchEvent(true);
gestureDetector.onTouchEvent(event);
scaleDetector.onTouchEvent(event);
return true;
});
// 恢复状态
if (savedInstanceState != null) {
scale = savedInstanceState.getFloat("scale", 1.0f);
gestureImage.setScaleX(scale);
gestureImage.setScaleY(scale);
String savedInput = savedInstanceState.getString("input");
inputEditText.setText(savedInput);
resultText.setText(savedInstanceState.getString("result", "结果"));
viewPagerAdapter.updateResult(resultText.getText().toString());
}
}
@Override
protected void onStart() {
super.onStart();
Toast.makeText(this, "onStart", Toast.LENGTH_SHORT).show();
}
@Override
protected void onResume() {
super.onResume();
Toast.makeText(this, "onResume", Toast.LENGTH_SHORT).show();
}
@Override
protected void onPause() {
super.onPause();
Toast.makeText(this, "onPause", Toast.LENGTH_SHORT).show();
}
@Override
protected void onStop() {
super.onStop();
Toast.makeText(this, "onStop", Toast.LENGTH_SHORT).show();
}
@Override
protected void onDestroy() {
super.onDestroy();
Toast.makeText(this, "onDestroy", Toast.LENGTH_SHORT).show();
inputEditText.removeTextChangedListener(textWatcher);
if (asyncTask != null) {
asyncTask.cancel(true);
}
handler.removeCallbacksAndMessages(null);
}
@Override
protected void onRestart() {
super.onRestart();
Toast.makeText(this, "onRestart", Toast.LENGTH_SHORT).show();
}
@Override
public void onConfigurationChanged(@NonNull Configuration newConfig) {
super.onConfigurationChanged(newConfig);
resultText.setText("配置变化: " + (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT ? "Portrait" : "Landscape"));
viewPagerAdapter.updateResult(resultText.getText().toString());
Toast.makeText(this, "Configuration changed", Toast.LENGTH_SHORT).show();
}
@Override
protected void onSaveInstanceState(@NonNull Bundle outState) {
super.onSaveInstanceState(outState);
outState.putFloat("scale", scale);
outState.putString("input", inputEditText.getText().toString());
outState.putString("result", resultText.getText().toString());
}
@Override
public void onBackPressed() {
if (drawerLayout.isDrawerOpen(GravityCompat.START)) {
drawerLayout.closeDrawer(GravityCompat.START);
} else {
super.onBackPressed();
}
}
public void updateViewPagerResult(String result) {
viewPagerAdapter.updateResult(result);
}
private void setLocale(Locale locale) {
Locale.setDefault(locale);
Configuration config = new Configuration();
config.setLocale(locale);
getResources().updateConfiguration(config, getResources().getDisplayMetrics());
recreate();
}
private static class ViewPagerAdapter extends FragmentStateAdapter {
private final List<PageFragment> fragments = new ArrayList<>();
private String result = "";
public ViewPagerAdapter(AppCompatActivity activity) {
super(activity);
for (int i = 0; i < 3; i++) {
fragments.add(PageFragment.newInstance(i, result));
}
}
public void updateResult(String result) {
this.result = result;
for (PageFragment fragment : fragments) {
fragment.updateResult(result);
}
notifyDataSetChanged();
}
@NonNull
@Override
public Fragment createFragment(int position) {
return fragments.get(position);
}
@Override
public int getItemCount() {
return 3;
}
}
}
3.8 运行效果
- 生命周期:每个生命周期阶段(
onCreate
、onStart
等)显示 Toast 提示,方便初学者观察。 - 界面:包含
Toolbar
、EditText
(输入监听)、ImageView
(手势处理)、ProgressBar
(异步任务进度)、TextView
(结果)、ViewPager2
(3 个页面)。 - 手势:
- 单击:更新
TextView
和ViewPager2
显示“单击”。 - 双击:显示
AlertDialog
,更新TextView
和ViewPager2
显示“双击”。 - 缩放:调整
ImageView
大小,更新TextView
和ViewPager2
显示缩放比例。 - TextWatcher:
- 输入“english”或“chinese”切换语言,刷新 UI。
- 输入“reset”重置
ImageView
缩放比例。 - AsyncTask:
- 点击侧滑菜单“Settings”,执行或取消异步任务(模拟网络请求)。
- 显示
ProgressBar
,任务完成后更新TextView
和ViewPager2
,并显示AlertDialog
。 - Configuration:
- 屏幕旋转时,更新
TextView
和ViewPager2
显示方向。 - DrawerLayout:
- 菜单切换
ViewPager2
页面或触发/取消异步任务。 - 状态保存:
- 保存
EditText
输入、缩放比例和结果,屏幕旋转后恢复。
说明:
- 生命周期:通过 Toast 展示每个阶段,适合初学者理解。
- 手势:使用
GestureDetectorCompat
和ScaleGestureDetector
处理单击、双击和缩放。 - TextWatcher:支持语言切换和重置。
- AsyncTask:模拟网络请求,结合
WeakReference
防止泄漏。 - Configuration:通过
android:configChanges
处理屏幕旋转。 - Handler:延迟显示
AlertDialog
,优化用户体验。 - 状态保存:通过
onSaveInstanceState
和onCreate
恢复数据。
4. 初学者常见问题及解决
- 生命周期混乱:
- 问题:不清楚何时初始化或清理资源。
- 解决:在
onCreate
初始化 UI 和监听器,在onDestroy
清理:java @Override protected void onDestroy() { inputEditText.removeTextChangedListener(textWatcher); if (asyncTask != null) { asyncTask.cancel(true); } super.onDestroy(); }
- 屏幕旋转丢失数据:
- 问题:旋转后 UI 重置。
- 解决:保存状态:
java @Override protected void onSaveInstanceState(@NonNull Bundle outState) { outState.putString("input", inputEditText.getText().toString()); super.onSaveInstanceState(outState); }
- 滑动冲突:
- 问题:
ViewPager2
和DrawerLayout
冲突。 - 解决:禁用父 View 拦截:
java viewPager.requestDisallowInterceptTouchEvent(true);
- 内存泄漏:
- 问题:
AsyncTask
或监听器未清理。 - 解决:使用
WeakReference
和onDestroy
清理:java asyncTask = new NetworkTask(this, progressBar, resultText);
- UI 未响应:
- 问题:耗时操作阻塞主线程。
- 解决:使用
AsyncTask
或Handler
:java handler.postDelayed(() -> resultText.setText("延迟更新"), 1000);
5. 初学者进阶功能
以下是结合 Activity
的进阶功能,适合初学者进一步学习。
5.1 跳转到另一个 Activity
创建第二个 Activity
并传递数据。
package com.example.myapp;
import android.os.Bundle;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;
public class SecondActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(android.R.layout.simple_list_item_1);
TextView textView = findViewById(android.R.id.text1);
String data = getIntent().getStringExtra(“data”);
textView.setText(“收到数据: ” + (data != null ? data : “无”));
}
}
在 MainActivity 中启动:
navigationView.setNavigationItemSelectedListener(item -> {
if (item.getItemId() == R.id.nav_profile) {
Intent intent = new Intent(this, SecondActivity.class);
intent.putExtra("data", inputEditText.getText().toString());
startActivity(intent);
}
return true;
});
AndroidManifest.xml:
<activity android:name=".SecondActivity" />
5.2 PopupWindow
双击 ImageView
显示 PopupWindow
。
@Override
public boolean onDoubleTap(MotionEvent e) {
resultText.setText("手势: 双击");
viewPagerAdapter.updateResult("双击");
PopupWindow popupWindow = new PopupWindow(new TextView(this), 200, 100, true);
((TextView) popupWindow.getContentView()).setText("双击触发");
popupWindow.showAsDropDown(gestureImage);
return true;
}
5.3 结合 Handler
延迟处理手势,避免频繁更新。
Handler handler = new Handler(Looper.getMainLooper());
@Override
public boolean onSingleTapUp(MotionEvent e) {
handler.postDelayed(() -> {
resultText.setText("手势: 单击");
viewPagerAdapter.updateResult("单击");
}, 200);
return true;
}
6. 初学者优化建议
- 生命周期管理:
- 初始化:在
onCreate
初始化 UI 和资源。 - 清理:在
onDestroy
释放资源:java @Override protected void onDestroy() { handler.removeCallbacksAndMessages(null); super.onDestroy(); }
- 状态保存:
- 保存关键数据以应对配置变化:
java @Override protected void onSaveInstanceState(@NonNull Bundle outState) { outState.putFloat("scale", scale); super.onSaveInstanceState(outState); }
- 性能优化:
- 避免在主线程执行耗时操作:
java new NetworkTask(this, progressBar, resultText).execute(input);
- 用户体验:
- 添加反馈(如 Toast、动画):
java resultText.animate().alpha(0).setDuration(200).withEndAction(() -> { resultText.setText("新结果"); resultText.animate().alpha(1).setDuration(200).start(); }).start();
- 调试技巧:
- 使用 Log 或 Toast 跟踪生命周期:
java Log.d("MainActivity", "onCreate called");
7. 结合之前组件的 Activity 示例
- AlertDialog:双击
ImageView
显示对话框。 - PopupWindow:长按
ImageView
显示弹出窗口。 - Menu:侧滑菜单触发异步任务或页面切换。
- ViewPager2:显示手势或任务结果。
- DrawerLayout:快速右滑打开侧滑菜单。
- TextWatcher:监听输入,触发语言切换或重置。
- Configuration:响应屏幕旋转。
- AsyncTask:模拟网络请求,更新 UI。
- Gestures:处理单击、双击、缩放。
- Handler:延迟处理手势。
- OnTouchListener:捕获触摸事件。
8. 可能的其他意图
- 复杂 UI:如果需要更复杂的布局(如
RecyclerView
、BottomSheetDialog
),请提供细节。 - 数据传递:如果需要多个
Activity
间交互,请说明。 - 生命周期调试:如果对生命周期不熟悉,请描述具体问题。
- 跨平台需求:如果需要 iOS 或 Web 的界面方案(如 React Native),请说明。
- 问题调试:如果有具体问题(如 UI 未更新、崩溃),请描述。
下一步
请提供更多细节,例如:
- 你想实现的
Activity
功能(复杂 UI、导航、数据传递等)? - 是否需要特定组件的集成(
RecyclerView
、Fragment
等)? - 是否需要适配特定 Android 版本或设备?
- 是否有生命周期、性能或其他问题?
如果没有进一步信息,我可以提供更复杂的 Activity
示例(如多 Activity
导航、复杂手势交互)或更详细的初学者教程。