chore: initialize repository with gitignore

This commit is contained in:
2026-04-13 07:16:39 +08:00
commit d3c18e8912
81 changed files with 4429 additions and 0 deletions
@@ -0,0 +1,49 @@
apply plugin: 'com.android.application'
configurations.configureEach {
exclude group: 'org.jetbrains.kotlin', module: 'kotlin-stdlib-jdk7'
exclude group: 'org.jetbrains.kotlin', module: 'kotlin-stdlib-jdk8'
}
android {
namespace 'com.example.task2complexcontrols'
compileSdk 34
defaultConfig {
applicationId 'com.example.task2complexcontrols'
minSdk 26
targetSdk 34
versionCode 1
versionName '1.0'
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
}
buildTypes {
release {
minifyEnabled false
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_17
targetCompatibility JavaVersion.VERSION_17
}
testOptions {
animationsDisabled true
}
}
dependencies {
implementation 'androidx.appcompat:appcompat:1.7.0'
implementation 'androidx.core:core:1.13.1'
androidTestImplementation 'androidx.test:core:1.5.0'
androidTestImplementation 'androidx.test:runner:1.5.2'
androidTestImplementation 'androidx.test:rules:1.5.0'
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
androidTestImplementation 'androidx.test.espresso:espresso-intents:3.5.1'
androidTestImplementation 'androidx.test.espresso:espresso-contrib:3.5.1'
androidTestImplementation 'junit:junit:4.13.2'
}
@@ -0,0 +1,134 @@
package com.example.task2complexcontrols;
import static androidx.test.espresso.Espresso.onData;
import static androidx.test.espresso.Espresso.onView;
import static androidx.test.espresso.action.ViewActions.click;
import static androidx.test.espresso.action.ViewActions.closeSoftKeyboard;
import static androidx.test.espresso.action.ViewActions.replaceText;
import static androidx.test.espresso.action.ViewActions.scrollTo;
import static androidx.test.espresso.assertion.ViewAssertions.matches;
import static androidx.test.espresso.matcher.ViewMatchers.isChecked;
import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
import static androidx.test.espresso.matcher.ViewMatchers.isEnabled;
import static androidx.test.espresso.matcher.ViewMatchers.withId;
import static androidx.test.espresso.matcher.ViewMatchers.withSpinnerText;
import static androidx.test.espresso.matcher.ViewMatchers.withText;
import static org.hamcrest.Matchers.allOf;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.fail;
import android.app.UiAutomation;
import android.view.accessibility.AccessibilityEvent;
import androidx.test.core.app.ActivityScenario;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.platform.app.InstrumentationRegistry;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.concurrent.TimeoutException;
@RunWith(AndroidJUnit4.class)
public class MainActivityComplexUiTest {
private ActivityScenario<MainActivity> activityScenario;
@Before
public void setUp() {
activityScenario = ActivityScenario.launch(MainActivity.class);
}
@After
public void tearDown() {
activityScenario.close();
}
/**
* 协议复选框应直接控制提交按钮是否可点击。
*/
@Test
public void testAgreementToggle_ChangesButtonState() {
onView(withId(R.id.btn_submit)).check(matches(not(isEnabled())));
onView(withId(R.id.cb_agreement)).perform(scrollTo(), click());
onView(withId(R.id.cb_agreement)).check(matches(isChecked()));
onView(withId(R.id.btn_submit)).check(matches(isEnabled()));
}
/**
* 单选按钮、复选框和 Spinner 都应支持正确选择并反映当前状态。
*/
@Test
public void testSelectionControls_UpdateUiStateCorrectly() {
onView(withId(R.id.et_name)).perform(replaceText("赵六"), closeSoftKeyboard());
onView(withId(R.id.rb_female)).perform(click());
onView(withId(R.id.cb_sport)).perform(scrollTo(), click());
onView(withId(R.id.cb_read)).perform(scrollTo(), click());
selectCity("上海");
onView(withId(R.id.et_name)).check(matches(withText("赵六")));
onView(withId(R.id.rb_female)).check(matches(isChecked()));
onView(withId(R.id.cb_sport)).check(matches(isChecked()));
onView(withId(R.id.cb_read)).check(matches(isChecked()));
onView(withId(R.id.spinner_city)).check(matches(withSpinnerText(containsString("上海"))));
}
/**
* 完整提交流程应输出与 Spinner 当前选择一致的 Toast 提示。
*/
@Test
public void testSubmitFlow_ShowsToastWithSelectedCity() {
onView(withId(R.id.et_name)).perform(replaceText("孙七"), closeSoftKeyboard());
onView(withId(R.id.rb_male)).perform(click());
onView(withId(R.id.cb_sport)).perform(scrollTo(), click());
onView(withId(R.id.cb_music)).perform(scrollTo(), click());
selectCity("深圳");
onView(withId(R.id.cb_agreement)).perform(scrollTo(), click());
onView(withId(R.id.rb_male)).check(matches(isChecked()));
onView(withId(R.id.cb_sport)).check(matches(isChecked()));
onView(withId(R.id.cb_music)).check(matches(isChecked()));
onView(withId(R.id.spinner_city)).check(matches(withSpinnerText(containsString("深圳"))));
onView(withId(R.id.btn_submit)).check(matches(isEnabled()));
clickAndAssertToast("提交成功!您选择的城市是:深圳");
}
private void selectCity(String city) {
onView(withId(R.id.spinner_city)).perform(scrollTo(), click());
onData(allOf(is(instanceOf(String.class)), is(city))).perform(click());
}
private void clickAndAssertToast(String message) {
UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
try {
AccessibilityEvent event = uiAutomation.executeAndWaitForEvent(
() -> onView(withId(R.id.btn_submit)).perform(scrollTo(), click()),
candidate -> candidate.getEventType() == AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED
&& containsText(candidate, message),
5000
);
assertNotNull(event);
} catch (TimeoutException exception) {
fail("Toast not shown: " + message);
}
}
private boolean containsText(AccessibilityEvent event, String message) {
for (CharSequence text : event.getText()) {
if (message.contentEquals(text)) {
return true;
}
}
return false;
}
}
@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application
android:label="@string/app_name"
android:theme="@style/Theme.Task2ComplexControls">
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
@@ -0,0 +1,58 @@
package com.example.task2complexcontrols;
import android.os.Bundle;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.EditText;
import android.widget.Spinner;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
public class MainActivity extends AppCompatActivity {
private EditText etName;
private Spinner spinnerCity;
private CheckBox cbAgreement;
private Button btnSubmit;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
etName = findViewById(R.id.et_name);
spinnerCity = findViewById(R.id.spinner_city);
cbAgreement = findViewById(R.id.cb_agreement);
btnSubmit = findViewById(R.id.btn_submit);
setupCitySpinner();
setupAgreementRule();
setupSubmitAction();
}
private void setupCitySpinner() {
ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(
this,
R.array.city_options,
android.R.layout.simple_spinner_item
);
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
spinnerCity.setAdapter(adapter);
}
private void setupAgreementRule() {
// 未勾选协议时禁止提交,勾选后恢复可点击。
btnSubmit.setEnabled(false);
cbAgreement.setOnCheckedChangeListener((buttonView, isChecked) -> btnSubmit.setEnabled(isChecked));
}
private void setupSubmitAction() {
btnSubmit.setOnClickListener(view -> {
String city = String.valueOf(spinnerCity.getSelectedItem());
String message = getString(R.string.toast_submit_message, city);
Toast.makeText(MainActivity.this, message, Toast.LENGTH_SHORT).show();
});
}
}
@@ -0,0 +1,107 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="20dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="20dp"
android:text="@string/title_form"
android:textSize="22sp"
android:textStyle="bold" />
<EditText
android:id="@+id/et_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="20dp"
android:hint="@string/hint_name"
android:inputType="textPersonName" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/label_gender"
android:textStyle="bold" />
<RadioGroup
android:id="@+id/rg_gender"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="20dp"
android:orientation="horizontal">
<RadioButton
android:id="@+id/rb_male"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/option_male" />
<RadioButton
android:id="@+id/rb_female"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
android:text="@string/option_female" />
</RadioGroup>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/label_hobbies"
android:textStyle="bold" />
<CheckBox
android:id="@+id/cb_sport"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/option_sport" />
<CheckBox
android:id="@+id/cb_read"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/option_read" />
<CheckBox
android:id="@+id/cb_music"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="20dp"
android:text="@string/option_music" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/label_city"
android:textStyle="bold" />
<Spinner
android:id="@+id/spinner_city"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="20dp" />
<CheckBox
android:id="@+id/cb_agreement"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="20dp"
android:text="@string/label_agreement" />
<Button
android:id="@+id/btn_submit"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:enabled="false"
android:text="@string/action_submit" />
</LinearLayout>
</ScrollView>
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string-array name="city_options">
<item>北京</item>
<item>上海</item>
<item>广州</item>
<item>深圳</item>
</string-array>
</resources>
@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">Task 2 Complex Controls</string>
<string name="title_form">用户信息提交</string>
<string name="hint_name">请输入姓名</string>
<string name="label_gender">请选择性别</string>
<string name="label_hobbies">请选择兴趣爱好</string>
<string name="label_city">请选择城市</string>
<string name="label_agreement">我已阅读并同意用户协议</string>
<string name="action_submit">提交信息</string>
<string name="option_male">男</string>
<string name="option_female">女</string>
<string name="option_sport">运动</string>
<string name="option_read">阅读</string>
<string name="option_music">音乐</string>
<string name="toast_submit_message">提交成功!您选择的城市是:%1$s</string>
</resources>
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="Theme.Task2ComplexControls" parent="Theme.AppCompat.Light.NoActionBar" />
</resources>