Material Design의 가이드라인을 따르는 어플을 만든다.
Mateial Design에서 제공하는 COLOR TOOL을 이용해서 색상 조합을 쉽게 선택할 수 있다.
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="green">#1B5E20</color>
<color name="green_dark">#003300</color>
<color name="green_light">#A5D6A7</color>
</resources>
<item name="colorPrimary">@color/green</item>
다크 모드의 색상 테마는 themes.xml (night)에서 수정할 수 있다.
어두운 테마는 기본 색상보다 낮은 채도를 사용한다.
런처 아이콘 애셋이 drawable
디렉터리에 있는 다른 앱 애셋과는 달리 mipmap
디렉터리에 있는 이유는 일부 런처가 기기의 기본 밀도 버킷에서 제공한 것보다 큰 크기로 앱 아이콘을 표시하는 경우가 있기 때문이다. 즉 아이콘을 확장해서 표시하는 런처가 있어 화질이 깨지는 것을 방지한다.
Android 밀도 한정자 목록
mdpi
- 중밀도 화면의 리소스(~160dpi)hdpi
- 고밀도 화면의 리소스 (~240dpi)xhdpi
- 초고밀도 화면의 리소스(~320dpi)xxhdpi
- 초초고밀도 화면의 리소스(~480dpi)xxxhdpi
- 초초초고밀도 화면의 리소스(~640dpi)nodpi
- 화면의 픽셀 밀도와 관계없이 조정할 수 없는 리소스anydpi
- 어떤 밀도로도 조정 가능한 리소스관련 내용과 시각적인 효과를 이 링크🔗 에서 확인해보자! 시각적 효과를 보면 왜 foreground와 background를 나눠서 구성하는지 알 수 있다.
Android 8.0부터 적응형 런처 아이콘을 지원해 앱 아이콘이 더 유연해지고 시각 효과를 발휘할 수 있게 되었다.
개발자는 앱 아이콘을 foreground와 background 레이어로 구성할 수 있다.
비트맵 이미지는 각 픽셀의 색상 정보만 안다. 반면 벡터 그래픽은 이미지를 정의하는 모양을 그리는 방법을 알고 있다.
즉 벡터 그래픽은 화질 저하 없이 모든 화면 밀도의 어떤 캔버스 크기로도 조정할 수 있다.
Material Components를 사용하면 다른 어플과 일관된 디자인과 방식으로 동작하기 때문에 사용자가 앱을 훨씬 더 빠르고 쉽게 사용할 수 있다. 아래 코드를 gradle에 추가해 사용할 수 있다. Component의 종류와 사용 방법은 위에있는 링크에서 확인!
dependencies {
implementation 'com.google.android.material:material:<version>'
}
일관된 스타일을 적용하려면 이렇게 values/styles.xml
에 스타일 내용을 작성해 스타일을 적용할 수 있다.
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="Widget.TipTime.TextView" parent="Widget.MaterialComponents.TextView">
<item name="android:minHeight">@dimen/min_text_height</item>
<item name="android:gravity">center_vertical</item>
<item name="android:textAppearance">?attr/textAppearanceBody1</item>
</style>
<style name="Widget.TipTime.CompoundButton.RadioButton"
parent="Widget.MaterialComponents.CompoundButton.RadioButton">
<item name="android:paddingStart">8dp</item>
<item name="android:textAppearance">?attr/textAppearanceBody1</item>
</style>
<style name="Widget.TipTime.CompoundButton.Switch" parent="Widget.MaterialComponents.CompoundButton.Switch">
<item name="android:minHeight">@dimen/min_text_height</item>
<item name="android:gravity">center_vertical</item>
<item name="android:textAppearance">?attr/textAppearanceBody1</item>
</style>
</resources>
반복적으로 사용하는 치수는 dimens에 작성해두고 불러와 사용한다.
<resources>
<dimen name="min_text_height">48dp</dimen>
</resources>
res/values/themes.xml
에 아래 코드를 추가한다. night 테마도 마찬가지로 추가!
<item name="textInputStyle">@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox</item>
<item name="radioButtonStyle">@style/Widget.TipTime.CompoundButton.RadioButton</item>
<item name="switchStyle">@style/Widget.TipTime.CompoundButton.Switch</item>
회전 시 화면이 잘려 사용자가 아래에 있는 버튼을 사용할 수 없다.
해결 방법: ContraintLayout
주위를 ScrollView
로 감싼다.
다음과 같은 코드를 Main Activity에 추가하고,
private fun handleKeyEvent(view: View, keyCode: Int): Boolean {
if (keyCode == KeyEvent.KEYCODE_ENTER) {
// Hide the keyboard
val inputMethodManager =
getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
inputMethodManager.hideSoftInputFromWindow(view.windowToken, 0)
return true
}
return false
}
onCreate안에 onKeyListener를 설정한다.
binding.costOfServiceEditText.setOnKeyListener { view, keyCode, _ -> handleKeyEvent(view, keyCode)
}
Tip time 앱의 UI 테스트를 해보자.
먼저 androidTest 디렉토리에 CalculatorTests.kt를 생성했다.
테스트 코드는 다음과 같다.
@RunWith(AndroidJUnit4::class)
class CalculatorTests {
@get:Rule()
val activity = ActivityScenarioRule(MainActivity::class.java)
@Test
fun calculate_20_percent_tip(){
onView(withId(R.id.cost_of_service_edit_text))
.perform(typeText("50.00"))
.perform(ViewActions.closeSoftKeyboard())
onView(withId(R.id.calculate_btn))
.perform(click())
onView(withId(R.id.tip_result))
.check(matches(withText(containsString("$10.00"))))
}
}
이 강의에서는 AndroidJUnit4
테스트 실행기를 사용한다.
MainActivity와 상호작용 하려면 먼저 액티비티를 실행한다.
@get:Rule()
val activity = ActivityScenarioRule(MainActivity::class.java)
그 다음 테스트 로직을 작성하는데 여기서는 Expresso
라는 라이브러리를 사용한다.
onView(withId(R.id.cost_of_service_edit_text))
.perform(typeText("50.00"))
.perform(ViewActions.closeSoftKeyboard())
onView()는 상호작용할 UI 구성요소를 찾는다. onView()는 ViewMatcher 객체를 매개변수로 하는데 withId()가 해당 Id의 ViewMatcher를 반환한다.
onView()는 ViewInteraction을 반환한다.
텍스트를 입력하고 나면 ViewInteraction에서 perform()을 호출한다.
그 다음 코드도 어떻게 동작하는지는 쉽게 이해할 수 있을 것이다.
EditText에 50.00을 입력하고,
Calculate 버튼을 클릭하고
Tip result에 해당하는 값이 $10.00인지 확인한다.
테스트를 실행하면 누군가 실제로 앱을 사용하는 것처럼 텍스트를 입력하고, 버튼을 클릭하고 결과를 얻는 화면이 뜬다.
안드로이드 스튜디오에서도 아래 처럼 확인된다.