Android 10.0 UI开发环境搭建与实战指南
1. Android 10.0 UI开发环境搭建
在开始Android 10.0的UI开发之前,我们需要先准备好开发环境。Android Studio是Google官方推荐的集成开发环境(IDE),它集成了代码编辑、调试、性能分析工具以及模拟器等全套开发工具。
1.1 安装Android Studio
首先访问Android开发者官网下载最新版的Android Studio。安装过程中有几个关键点需要注意:
- 确保勾选"Android Virtual Device"选项,这将安装模拟器组件
- 安装路径不要包含中文或特殊字符
- 安装完成后首次启动时,选择"Standard"安装类型会自动下载推荐的SDK组件
提示:如果网络环境不佳,可以手动配置代理或使用国内镜像源来加速SDK下载。
1.2 配置项目依赖
创建新项目时,在build.gradle文件中需要确保以下关键配置:
android { compileSdkVersion 29 // Android 10.0对应的API级别 defaultConfig { minSdkVersion 21 targetSdkVersion 29 } } dependencies { implementation 'androidx.appcompat:appcompat:1.3.0' implementation 'com.google.android.material:material:1.4.0' }这些依赖库提供了对Material Design组件和向后兼容性的支持。
1.3 模拟器配置技巧
Android 10.0引入了一些新特性,建议在创建AVD时:
- 选择x86_64系统镜像以获得最佳性能
- 分配至少2GB RAM
- 启用硬件加速(GPU)
- 使用最新的模拟器版本(30.0.5+)
2. Android UI基础架构
2.1 理解View和ViewGroup
Android UI的核心构建块是View和ViewGroup:
- View:所有UI控件的基类,如Button、TextView等
- ViewGroup:View的子类,可以作为其他View的容器,如LinearLayout、RelativeLayout等
在Android 10.0中,视图层次结构仍然采用树形结构,但Google更推荐使用ConstraintLayout作为根布局,因为它能提供更好的性能表现。
2.2 布局文件解析
典型的布局文件(res/layout/activity_main.xml)结构如下:
<?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent"> <TextView android:id="@+id/textView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Hello World!" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent" /> </androidx.constraintlayout.widget.ConstraintLayout>2.3 主题与样式
Android 10.0引入了深色主题支持,在res/values/themes.xml中可以定义:
<style name="AppTheme" parent="Theme.MaterialComponents.DayNight"> <!-- 自定义主题属性 --> <item name="colorPrimary">@color/purple_500</item> <item name="colorPrimaryVariant">@color/purple_700</item> <item name="colorOnPrimary">@color/white</item> </style>3. 常用UI控件详解
3.1 文本显示控件
TextView基础用法
<TextView android:id="@+id/sample_text" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Hello Android 10" android:textSize="18sp" android:textColor="@color/black" android:fontFamily="sans-serif-medium"/>在代码中动态修改文本属性:
TextView textView = findViewById(R.id.sample_text); textView.setText(R.string.updated_text); textView.setTextColor(ContextCompat.getColor(this, R.color.red));富文本显示
Android 10.0增强了SpannableString的功能:
SpannableString spannable = new SpannableString("Bold and Italic"); spannable.setSpan(new StyleSpan(Typeface.BOLD), 0, 4, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); spannable.setSpan(new StyleSpan(Typeface.ITALIC), 9, 15, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); textView.setText(spannable);3.2 按钮与点击交互
Button的基本使用
<Button android:id="@+id/action_button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Click Me" android:backgroundTint="@color/purple_500" android:textColor="@color/white"/>点击事件处理的几种方式:
- 匿名内部类方式:
Button button = findViewById(R.id.action_button); button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { // 处理点击逻辑 } });- Lambda表达式方式(需要Java 8支持):
button.setOnClickListener(v -> { // 处理点击逻辑 });图像按钮ImageButton
<ImageButton android:layout_width="48dp" android:layout_height="48dp" android:src="@drawable/ic_add" android:background="?attr/selectableItemBackgroundBorderless" android:contentDescription="@string/add_button_desc"/>注意:所有ImageButton都应该设置contentDescription属性以满足无障碍访问要求。
3.3 输入控件
EditText的进阶用法
<com.google.android.material.textfield.TextInputLayout android:layout_width="match_parent" android:layout_height="wrap_content" app:counterEnabled="true" app:counterMaxLength="20"> <com.google.android.material.textfield.TextInputEditText android:layout_width="match_parent" android:layout_height="wrap_content" android:hint="Username" android:inputType="textCapWords" android:maxLength="20"/> </com.google.android.material.textfield.TextInputLayout>输入验证示例:
TextInputEditText editText = findViewById(R.id.username_input); editText.addTextChangedListener(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) { if (s.length() < 4) { ((TextInputLayout)editText.getParent()).setError("Username too short"); } else { ((TextInputLayout)editText.getParent()).setError(null); } } @Override public void afterTextChanged(Editable s) {} });复选框和单选按钮
单选按钮组示例:
<RadioGroup android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="vertical"> <RadioButton android:id="@+id/option1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Option 1"/> <RadioButton android:id="@+id/option2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Option 2"/> </RadioGroup>获取选中状态:
RadioGroup radioGroup = findViewById(R.id.radio_group); radioGroup.setOnCheckedChangeListener((group, checkedId) -> { if (checkedId == R.id.option1) { // 选项1被选中 } else if (checkedId == R.id.option2) { // 选项2被选中 } });3.4 图片显示控件
ImageView的高级用法
加载网络图片的现代方式(使用Glide库):
implementation 'com.github.bumptech.glide:glide:4.12.0' annotationProcessor 'com.github.bumptech.glide:compiler:4.12.0'ImageView imageView = findViewById(R.id.image_view); Glide.with(this) .load("https://example.com/image.jpg") .placeholder(R.drawable.placeholder) .error(R.drawable.error_image) .transition(DrawableTransitionOptions.withCrossFade()) .into(imageView);圆形图片的实现
使用Material Components的ShapeableImageView:
<com.google.android.material.imageview.ShapeableImageView android:layout_width="100dp" android:layout_height="100dp" app:shapeAppearanceOverlay="@style/CircleImageView" android:src="@drawable/profile_pic"/>在res/values/styles.xml中定义:
<style name="CircleImageView" parent=""> <item name="cornerFamily">rounded</item> <item name="cornerSize">50%</item> </style>4. 高级UI组件与最佳实践
4.1 RecyclerView的现代化使用
RecyclerView是ListView的升级版,适合显示大量数据列表。
基本实现步骤
- 添加依赖:
implementation 'androidx.recyclerview:recyclerview:1.2.1'- 布局文件中添加RecyclerView:
<androidx.recyclerview.widget.RecyclerView android:id="@+id/recycler_view" android:layout_width="match_parent" android:layout_height="match_parent" app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"/>- 创建项目布局(item_layout.xml):
<androidx.constraintlayout.widget.ConstraintLayout android:layout_width="match_parent" android:layout_height="wrap_content"> <ImageView android:id="@+id/item_icon" android:layout_width="48dp" android:layout_height="48dp"/> <TextView android:id="@+id/item_title" android:layout_width="0dp" android:layout_height="wrap_content" app:layout_constraintStart_toEndOf="@id/item_icon"/> </androidx.constraintlayout.widget.ConstraintLayout>- 创建Adapter:
public class MyAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder> { private List<MyItem> items; public static class ViewHolder extends RecyclerView.ViewHolder { public ImageView icon; public TextView title; public ViewHolder(View itemView) { super(itemView); icon = itemView.findViewById(R.id.item_icon); title = itemView.findViewById(R.id.item_title); } } @Override public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { View view = LayoutInflater.from(parent.getContext()) .inflate(R.layout.item_layout, parent, false); return new ViewHolder(view); } @Override public void onBindViewHolder(ViewHolder holder, int position) { MyItem item = items.get(position); holder.title.setText(item.getTitle()); Glide.with(holder.itemView).load(item.getIconUrl()).into(holder.icon); } @Override public int getItemCount() { return items.size(); } }- 设置Adapter:
RecyclerView recyclerView = findViewById(R.id.recycler_view); MyAdapter adapter = new MyAdapter(itemList); recyclerView.setAdapter(adapter);高级功能实现
添加项目点击事件:
public class MyAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder> { // ...其他代码... private OnItemClickListener listener; public interface OnItemClickListener { void onItemClick(MyItem item); } public void setOnItemClickListener(OnItemClickListener listener) { this.listener = listener; } @Override public void onBindViewHolder(ViewHolder holder, int position) { // ...原有代码... holder.itemView.setOnClickListener(v -> { if (listener != null) { listener.onItemClick(items.get(position)); } }); } }使用DiffUtil优化列表更新:
public class MyDiffCallback extends DiffUtil.Callback { private List<MyItem> oldList; private List<MyItem> newList; // 实现必要方法... } // 更新数据时 DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(new MyDiffCallback(oldList, newList)); adapter.updateItems(newList); diffResult.dispatchUpdatesTo(adapter);4.2 ViewPager2的现代化使用
ViewPager2是ViewPager的替代品,基于RecyclerView实现,支持垂直分页和RTL布局。
基本实现
- 添加依赖:
implementation 'androidx.viewpager2:viewpager2:1.0.0'- 布局文件中添加ViewPager2:
<androidx.viewpager2.widget.ViewPager2 android:id="@+id/view_pager" android:layout_width="match_parent" android:layout_height="match_parent"/>- 创建FragmentStateAdapter:
public class MyPagerAdapter extends FragmentStateAdapter { public MyPagerAdapter(FragmentActivity fa) { super(fa); } @Override public Fragment createFragment(int position) { return MyFragment.newInstance(position); } @Override public int getItemCount() { return 3; // 页数 } }- 设置Adapter:
ViewPager2 viewPager = findViewById(R.id.view_pager); viewPager.setAdapter(new MyPagerAdapter(this));与TabLayout集成
- 添加Material Components依赖:
implementation 'com.google.android.material:material:1.4.0'- 添加TabLayout:
<com.google.android.material.tabs.TabLayout android:id="@+id/tab_layout" android:layout_width="match_parent" android:layout_height="wrap_content"/>- 关联TabLayout和ViewPager2:
TabLayout tabLayout = findViewById(R.id.tab_layout); new TabLayoutMediator(tabLayout, viewPager, (tab, position) -> { tab.setText("Tab " + (position + 1)); }).attach();4.3 动画与过渡效果
属性动画
基本属性动画示例:
View view = findViewById(R.id.animated_view); ObjectAnimator animator = ObjectAnimator.ofFloat(view, "translationX", 0f, 200f); animator.setDuration(500); animator.setInterpolator(new AccelerateDecelerateInterpolator()); animator.start();视图状态动画
使用AnimatedVectorDrawable实现图标变形:
- 定义矢量图(res/drawable/ic_animated.xml):
<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" android:drawable="@drawable/ic_play"> <target android:name="play_pause" android:animation="@anim/play_to_pause"/> </animated-vector>- 定义动画(res/anim/play_to_pause.xml):
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android" android:duration="300" android:propertyName="pathData" android:valueFrom="..." android:valueTo="..." android:valueType="pathType"/>- 在代码中启动动画:
ImageButton button = findViewById(R.id.play_button); AnimatedVectorDrawable drawable = (AnimatedVectorDrawable) button.getDrawable(); drawable.start();共享元素过渡
实现Activity间的共享元素过渡:
- 在第一个Activity中:
Intent intent = new Intent(this, DetailActivity.class); ActivityOptions options = ActivityOptions.makeSceneTransitionAnimation( this, sharedView, "shared_element_name"); startActivity(intent, options.toBundle());- 在第二个Activity的布局中:
<ImageView android:transitionName="shared_element_name" ... />- 在styles.xml中定义过渡动画:
<style name="AppTheme" parent="Theme.MaterialComponents.DayNight"> <item name="android:windowContentTransitions">true</item> <item name="android:windowSharedElementEnterTransition">@transition/shared_element_enter</item> <item name="android:windowSharedElementExitTransition">@transition/shared_element_exit</item> </style>5. 响应式UI设计与适配
5.1 约束布局的高级技巧
ConstraintLayout是Android Studio的默认布局,它通过约束关系定位视图,避免了嵌套布局带来的性能问题。
基本约束概念
<androidx.constraintlayout.widget.ConstraintLayout> <Button android:id="@+id/button1" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent"/> <Button android:id="@+id/button2" app:layout_constraintStart_toEndOf="@id/button1" app:layout_constraintTop_toTopOf="@id/button1" app:layout_constraintEnd_toEndOf="parent"/> </androidx.constraintlayout.widget.ConstraintLayout>比例尺寸控制
<ImageView android:layout_width="0dp" android:layout_height="0dp" app:layout_constraintDimensionRatio="H,16:9" app:layout_constraintWidth_percent="0.8" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toTopOf="parent"/>屏障(Barrier)的使用
屏障会根据引用的视图动态调整位置:
<androidx.constraintlayout.widget.Barrier android:id="@+id/barrier" android:layout_width="wrap_content" android:layout_height="wrap_content" app:barrierDirection="end" app:constraint_referenced_ids="text1,text2"/> <Button app:layout_constraintStart_toEndOf="@id/barrier"/>5.2 多屏幕尺寸适配策略
限定符的使用
Android提供了多种资源限定符来适配不同设备:
- 尺寸限定符:small, normal, large, xlarge
- 密度限定符:ldpi, mdpi, hdpi, xhdpi, xxhdpi, xxxhdpi
- 方向限定符:land, port
- 最小宽度限定符:sw600dp, sw720dp等
例如,为大屏幕创建特殊布局: res/layout-sw600dp/activity_main.xml
使用尺寸资源
在res/values/dimens.xml中定义:
<dimen name="text_size_small">12sp</dimen> <dimen name="text_size_medium">16sp</dimen> <dimen name="text_size_large">20sp</dimen>然后在res/values-sw600dp/dimens.xml中覆盖:
<dimen name="text_size_small">16sp</dimen> <dimen name="text_size_medium">20sp</dimen> <dimen name="text_size_large">24sp</dimen>5.3 夜间模式实现
Android 10.0引入了系统级的深色主题支持,应用可以轻松实现主题切换。
- 在res/values/colors.xml中定义颜色:
<color name="background">#FFFFFF</color> <color name="textColor">#000000</color>- 在res/values-night/colors.xml中定义夜间模式颜色:
<color name="background">#121212</color> <color name="textColor">#FFFFFF</color>- 确保应用主题继承自DayNight主题:
<style name="AppTheme" parent="Theme.MaterialComponents.DayNight"> <!-- 主题属性 --> </style>- 在代码中切换主题:
AppCompatDelegate.setDefaultNightMode( isNightMode ? AppCompatDelegate.MODE_NIGHT_YES : AppCompatDelegate.MODE_NIGHT_NO); recreate(); // 重启Activity使更改生效6. 性能优化与调试技巧
6.1 UI渲染性能优化
识别过度绘制
在开发者选项中开启"显示过度绘制",不同颜色代表不同层级的过度绘制:
- 无颜色:没有过度绘制
- 蓝色:1次过度绘制
- 绿色:2次过度绘制
- 粉色:3次过度绘制
- 红色:4次或更多过度绘制
优化建议:
- 移除不必要的背景
- 扁平化视图层次结构
- 使用merge标签减少布局嵌套
使用Layout Inspector
Android Studio的Layout Inspector可以:
- 查看视图层次结构
- 检查视图属性
- 分析布局性能问题
- 调试运行时布局
6.2 内存泄漏检测
常见内存泄漏场景:
- 静态变量持有Activity引用
- 非静态内部类(如Handler)持有外部类引用
- 未取消的注册(如广播接收器)
- 资源未及时释放(如文件流、数据库连接)
使用LeakCanary检测内存泄漏:
- 添加依赖:
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.7'- 在Application类中初始化:
public class MyApp extends Application { @Override public void onCreate() { super.onCreate(); if (LeakCanary.isInAnalyzerProcess(this)) { return; } LeakCanary.install(this); } }6.3 使用Android Profiler
Android Studio的Profiler工具可以监控:
- CPU使用情况
- 内存分配和泄漏
- 网络请求
- 能量消耗
关键使用技巧:
- 记录方法跟踪时选择"Sampled"或"Instrumented"模式
- 检查内存分配追踪以识别不必要的大对象分配
- 使用Network Profiler识别冗余网络请求
7. 实战案例:构建完整的用户界面
7.1 登录界面实现
完整登录界面示例:
<androidx.constraintlayout.widget.ConstraintLayout android:layout_width="match_parent" android:layout_height="match_parent" android:padding="24dp"> <ImageView android:layout_width="100dp" android:layout_height="100dp" app:layout_constraintTop_toTopOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent" android:src="@drawable/app_logo"/> <com.google.android.material.textfield.TextInputLayout android:id="@+id/username_layout" android:layout_width="match_parent" android:layout_height="wrap_content" app:layout_constraintTop_toBottomOf="@id/logo" android:layout_marginTop="32dp"> <com.google.android.material.textfield.TextInputEditText android:id="@+id/username" android:layout_width="match_parent" android:layout_height="wrap_content" android:hint="Username" android:inputType="textEmailAddress"/> </com.google.android.material.textfield.TextInputLayout> <com.google.android.material.textfield.TextInputLayout android:id="@+id/password_layout" android:layout_width="match_parent" android:layout_height="wrap_content" app:layout_constraintTop_toBottomOf="@id/username_layout"> <com.google.android.material.textfield.TextInputEditText android:id="@+id/password" android:layout_width="match_parent" android:layout_height="wrap_content" android:hint="Password" android:inputType="textPassword"/> </com.google.android.material.textfield.TextInputLayout> <CheckBox android:id="@+id/remember_me" android:layout_width="wrap_content" android:layout_height="wrap_content" app:layout_constraintTop_toBottomOf="@id/password_layout" android:text="Remember me"/> <Button android:id="@+id/login_button" android:layout_width="match_parent" android:layout_height="48dp" app:layout_constraintTop_toBottomOf="@id/remember_me" android:layout_marginTop="24dp" android:text="LOGIN" style="@style/Widget.MaterialComponents.Button"/> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" app:layout_constraintTop_toBottomOf="@id/login_button" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent" android:layout_marginTop="16dp" android:text="Forgot password?" android:textColor="@color/blue_500"/> </androidx.constraintlayout.widget.ConstraintLayout>7.2 主界面实现
使用Navigation Component实现底部导航的主界面:
- 添加依赖:
implementation 'androidx.navigation:navigation-fragment:2.3.5' implementation 'androidx.navigation:navigation-ui:2.3.5'- 创建导航图(res/navigation/main_nav.xml):
<navigation xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/main_nav" app:startDestination="@id/homeFragment"> <fragment android:id="@+id/homeFragment" android:name="com.example.ui.HomeFragment" android:label="Home"/> <fragment android:id="@+id/searchFragment" android:name="com.example.ui.SearchFragment" android:label="Search"/> <fragment android:id="@+id/profileFragment" android:name="com.example.ui.ProfileFragment" android:label="Profile"/> </navigation>- 主Activity布局:
<androidx.constraintlayout.widget.ConstraintLayout android:layout_width="match_parent" android:layout_height="match_parent"> <fragment android:id="@+id/nav_host_fragment" android:name="androidx.navigation.fragment.NavHostFragment" android:layout_width="0dp" android:layout_height="0dp" app:layout_constraintBottom_toTopOf="@id/bottom_nav" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" app:defaultNavHost="true" app:navGraph="@navigation/main_nav"/> <com.google.android.material.bottomnavigation.BottomNavigationView android:id="@+id/bottom_nav" android:layout_width="match_parent" android:layout_height="wrap_content" app:layout_constraintBottom_toBottomOf="parent" app:menu="@menu/bottom_nav_menu"/> </androidx.constraintlayout.widget.ConstraintLayout>- 在Activity中设置导航控制器:
BottomNavigationView bottomNav = findViewById(R.id.bottom_nav); NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment); NavigationUI.setupWithNavController(bottomNav, navController);7.3 详情页实现
详情页通常包含图片、标题、描述和操作按钮:
<androidx.coordinatorlayout.widget.CoordinatorLayout android:layout_width="match_parent" android:layout_height="match_parent"> <com.google.android.material.appbar.AppBarLayout android:layout_width="match_parent" android:layout_height="300dp"> <com.google.android.material.appbar.CollapsingToolbarLayout android:layout_width="match_parent" android:layout_height="match_parent" app:layout_scrollFlags="scroll|exitUntilCollapsed"> <ImageView android:id="@+id/detail_image" android:layout_width="match_parent" android:layout_height="match_parent" android:scaleType="centerCrop" app:layout_collapseMode="parallax"/> <androidx.appcompat.widget.Toolbar android:id="@+id/toolbar" android:layout_width="match_parent" android:layout_height="?attr/actionBarSize" app:layout_collapseMode="pin"/> </com.google.android.material.appbar.CollapsingToolbarLayout> </com.google.android.material.appbar.AppBarLayout> <androidx.core.widget.NestedScrollView android:layout_width="match_parent" android:layout_height="match_parent" app:layout_behavior="@string/appbar_scrolling_view_behavior"> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" android:padding="16dp"> <TextView android:id="@+id/detail_title" android:layout_width="match_parent" android:layout_height="wrap_content" android:textSize="24sp" android:textStyle="bold"/> <TextView android:id="@+id/detail_description" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="16dp" android:textSize="16sp"/> </LinearLayout> </androidx.core.widget.NestedScrollView> <com.google.android.material.floatingactionbutton.FloatingActionButton android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_margin="16dp" android:src="@drawable/ic_favorite" app:layout_anchor="@id/app_bar" app:layout_anchorGravity="bottom|end"/> </androidx.coordinatorlayout.widget.CoordinatorLayout>8. 测试与发布准备
8.1 UI自动化测试
使用Espresso进行UI测试:
- 添加依赖:
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' androidTestImplementation 'androidx.test:runner:1.4.0' androidTestImplementation 'androidx.test:rules:1.4.0'- 编写测试类:
@RunWith(AndroidJUnit4.class) public class LoginActivityTest { @Rule public ActivityScenarioRule<LoginActivity> activityRule = new ActivityScenarioRule<>(LoginActivity.class); @Test public void testLoginWithEmptyCredentials() { // 定位视图并执行操作 onView(withId(R.id.username)).perform(typeText("")); onView(withId(R.id.password)).perform(typeText("")); onView(withId(R.id.login_button)).perform(click()); // 验证结果 onView(withId(R.id.username_layout)) .check(matches(hasTextInputLayoutErrorText("Username is required"))); } @Test public void testSuccessfulLogin() { onView(withId(R.id.username)).perform(typeText("test@example.com")); onView(withId(R.id.password)).perform(typeText("password123")); onView(withId(R.id.login_button)).perform(click()); intended(hasComponent(HomeActivity.class.getName())); } }8.2 屏幕截图测试
使用Facebook的Screenshot Tests for Android:
- 添加依赖:
androidTestImplementation 'com.facebook.testing.screenshot:core:0.15.0'- 编写测试类:
@RunWith(AndroidJUnit4.class) public class ScreenshotTest { @Rule public ScreenshotRule rule = new ScreenshotRule(); @Test public void testLoginScreenScreenshot() { ActivityScenario<LoginActivity> scenario = ActivityScenario.launch(LoginActivity.class); scenario.onActivity(activity -> { Screenshot.snapActivity(activity).record(); }); } }8.3 发布前检查清单
在发布前,确保:
- UI适配:
- 测试不同屏幕尺寸和密度
- 验证横竖屏布局
- 检查深色主题表现
- 性能:
- 消除过度绘制
- 优化布局层次
- 减少不必要的视图刷新
- 无障碍:
- 所有图片都有contentDescription
- 有足够的颜色对比度
- 焦点顺序合理
- 本地化:
- 检查字符串资源是否全部外部化
- 验证RTL布局(如阿拉伯语)
- 测试主要语言的翻译
- 法律合规:
- 隐私政策链接
- 必要的权限说明
- 第三方库许可证