
반응형 UI를 ConstraintLayout으로 만드는것을 강사님은 비추하심
공식문서에도 반응형UI에 ConstraintLayout으로 작성하고 있는데 어지러워보인다.
액정사이즈만한 이미지를 준비하기 어려움 단말기마다 다르기 때문
배경에 패턴을 이용하여 이미지를 까는 어플이 있음
배경을 설정하는 background 속성

스포이드 버튼을 클릭해 색상을 넣거나

맨 오른쪽 아이콘을 클릭해 준비한 이미지를 넣을 수 있다.

아래의 타일 이미지를 준비해서

바로 백그라운드 이미지로 적용하면 화면에 꽉차게 적용이 되지만

drawable폴더에 새로운 리소스 파일을 만들어서


이미지 src를 설정하고
tileMode를 설정한다.

<?xml version="1.0" encoding="utf-8"?>
<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
android:src="@drawable/p"
android:tileMode="repeat">
</bitmap>
다시 activity_main.xml에서 백그라운드 설정을 새로만든 타일 리소스로 설정하면

아래와 같이 패턴이 적용되어 나온다.

Root element가 layer-list인 리소스파일을 만든다.

준비한 이미지 3개를 layer-list의 item으로 설정한다.
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<bitmap android:src="@drawable/img1" android:gravity="center"/>
</item>
<item android:top="10dp" android:left="10dp">
<bitmap android:src="@drawable/img2" android:gravity="center"/>
</item>
<item android:top="20dp" android:left="20dp">
<bitmap android:src="@drawable/img3" android:gravity="center"/>
</item>
</layer-list>
gravity="center"를 설정하면 원본 이미지 비율을 유지할 수 있다.

activity_main.xml에 이미지 뷰를 넣고 만든 리소스파일을 이미지로 설정해준다.



준비한 버튼 이미지 2개를 넣고
버튼의 누름 여부에 따라 이미지를 바꿀 수 있게 셋팅해준다.
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/btn1" android:state_pressed="true"/>
<item android:drawable="@drawable/btn2"/>
</selector>
activity_main.xml에 버튼을 넣고 만든 리소스 파일을 설정해준다.

그냥 background로 리소스 파일을 설정해주면 버튼의 기본 스타일이 m3이기때문에 적용되지 않는다.

따라서 리소스파일이 적용되게 하려면 themes.xml에서 m3테마를 없애야 적용이 가능하다.

구글 정책 상 통일성을 위해 커스텀을 막고 m3를 사용하도록 유도한 것 같다.
gif파일을 사용해도 되지만 애니메이션 리소스를 직접 만드는 방법이 있다.
이 경우 이미 만들어진 gif와 달리 애니메이션 동작을 조정할 수 있다.

<?xml version="1.0" encoding="utf-8"?>
<!-- oneshot : true를 넣어주면 한번만 동작하고 멈춘다. -->
<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
android:oneshot="true">
<!--
drawable : 보여줄 이미지
duration : 이미지를 보여준 후 몇 ms 후에 다음 이미지를 보여줄 것인가
-->
<item android:drawable="@drawable/ani_img1" android:duration="100"/>
<item android:drawable="@drawable/ani_img2" android:duration="100"/>
<item android:drawable="@drawable/ani_img3" android:duration="100"/>
<item android:drawable="@drawable/ani_img4" android:duration="100"/>
<item android:drawable="@drawable/ani_img5" android:duration="100"/>
<item android:drawable="@drawable/ani_img6" android:duration="100"/>
<item android:drawable="@drawable/ani_img7" android:duration="100"/>
<item android:drawable="@drawable/ani_img8" android:duration="100"/>
</animation-list>
duration값이 작을수록 빠르게 동작한다.

activity_main.xml에 이미지뷰 추가하고 만든 애니메이션 리소스를 설정한다.

애니메이션 동작을 제어할 버튼을 추가하고 코드를 작성한다.
activityMainBinding.apply {
button.setOnClickListener {
// 이미지 뷰에 설정한 애니메이션 이미지를 추출한다.
val animationDrawable = imageView2.drawable as AnimationDrawable
// 애니메이션 가동
animationDrawable.start()
}
button2.setOnClickListener {
// 이미지 뷰에 설정한 애니메이션 이미지를 추출한다.
val animationDrawable = imageView2.drawable as AnimationDrawable
// 애니메이션 정지
animationDrawable.stop()
}
}


텍스트뷰의 백그라운드에 이미지를 넣어주고 텍스트값을 길게 넣어준다.


배경으로 설정한 이미지를 우클릭하여 Create 9-patch file...을 눌러주면


원래 이름이 중복되면 안되기 때문에 bubble2라고 설정해준다.

9 patch 이미지는 만들어질 때 원본 이미지에 비해 가로 세로 길이가 1씩 늘어난다.

이미지 크기가 변경될때의 모습을 미리보기로 확인할 수 있다.


확대할 영역만 맞춰준다.

9 patch 이미지로 배경을 설정한 텍스트뷰를 보면 설정한 영역만 확대되는 것을 확인할 수 있다.


내용이 말풍선 안에 모두 맞추어 들어간 것을 확인할 수 있다.
values폴더의 strings.xml에 greeting 추가
<resources>
<string name="app_name">Android45_Localization</string>
<string name="greeting">Hello</string>
</resources>
텍스트뷰의 text속성을 작성한 greeting으로 변경


국가별로 나누는 건 리소스 파일을 만들 때 설정할 수 있다.



// values/strings.xml
<resources>
<string name="app_name">Android45_Localization</string>
<string name="greeting">Hello</string>
</resources>
// ko/strings.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="greeting">안녕하세요</string>
</resources>
단말기의 시스템 언어가 한국어로 설정되어있다면

영어였던 텍스트가 한국어로 바뀌어있다.

대부분 위의 방법으로 하지만 strings.xml의 경우에는 우클릭 후 Open Translations Editor 클릭해서 설정할 수도 있다.


drawable 폴더도 국가별로 구성할 수 있음



폴더상으로는 변경점이 없어보인다.
하지만 이미지를 폴더에 추가하려고 하면 추가한 국가별 폴더가 나온다.



추가한 후에는 국가별로 이미지가 구분된다.
결과물도 미국의 성조기에서 한국의 태극기 이미지로 변경되어 보인다.

코드로 두번째 텍스트와 두번째 이미지를 셋팅해줘도 동일하게 한국어와 태극기가 표시된다.
activityMainBinding.apply {
textView2.setText(R.string.greeting)
imageView2.setImageResource(R.drawable.flag1)
}

언어를 영어로 다시 바꾸면

앱 내용도 언어에 맞추어 변경된다.

https://developer.android.com/guide/practices/compatibility?hl=ko
ldpi : ~120dpi
mdpi : ~160dpi
hdpi : ~240dpi
xhdpi : ~320dpi
xxhdpi : ~480dpi
xxxhdpi : ~640dpi
이미지 준비는 디자이너가 할 일
해상도가 높을 수록 1픽셀의 크기가 줄어들기 때문에 원래 이미지에 확대해서 그리게 된다.
mdpi 이미지가 있고 xhdpi이미지가 있을 때 hdpi인 기기는 mdpi이미지를 사용해서 이미지를 확대해서 그린다.
고해상도 그림을 축소하는게 연산이 더 많이 들기 때문에 저해상도 그림을 확대한다.
안드로이드 스튜디오에서 에뮬레이터를 동시에 여러개 띄우려할 때 설정에서 Launch in the Running Devices tool window 체크를 해제해준다.

DPI별로 가상기기를 준비한다.
mdpi drawable 폴더를 만든다.



hdpi drawable 폴더를 만든다.

xxhdpi drawable 폴더를 만든다.

해상도별로 준비한 이미지를 각각 만들어둔 drawable 폴더에 넣어준다.


이미지뷰를 추가하고 해상도별로 추가한 이미지를 세팅한다.

두번째 이미지의 경우,

mdpi용 이미지는 따로 없기 때문에 mdpi기기에서는 hdpi용 이미지를 가져와서 hdpi기기에 보여지는 크기가 비슷하지만 xxhdpi기기는 해당 이미지를 그대로 출력하면 이미지가 작게 보인다.
세번째 이미지의 경우,

xxhdpi용 이미지가 hdpi용 이미지의 2배 크기(가로 2배, 세로 2배)이기때문에 xxhdpi기기에서도 hdpi기기와 비슷한 크기로 이미지가 출력된다.
image Asset Studio에서는 아이콘을 만들 때 해상도별로 만들어준다.


마찬가지로 vector Asset Studio도 있다.
그 외에도 포토샵으로 아이콘을 준비하거나 전용으로 만들어주는 웹사이트도 찾아볼 수 있다.
아이폰은 십여년 전에 나온 폰부터 400ppi가 넘었기 때문에 지금까지 dpi 변동이 크지 않다.
textview에 텍스트값을 수정한 후에
화면을 회전시키면


텍스트값이 초기화된다.
이러한 이유는 onCreate가 화면 회전이 발생했을 때도 호출되면서 일부 UI가 초기화되기 때문이다.

Activity가 처음 생성될 때는 매개변수에 null 값이 들어오고,
화면 회전이 발생했을 때는 Bundle타입의 객체가 들어온다.
class MainActivity : AppCompatActivity() {
lateinit var activityMainBinding: ActivityMainBinding
// onCreate는 화면 회전이 발생했을 때도 호출된다.
// Activity가 처음 생성될 때는 매개변수에 null 값이 들어오고
// 화면 회전이 발생했을 때는 Bundle타입의 객체가 들어온다.
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
activityMainBinding = ActivityMainBinding.inflate(layoutInflater)
setContentView(activityMainBinding.root)
if(savedInstanceState == null){
Log.d("test1234","Activity 실행")
}else{
Log.d("test1234","화면 회전 발생")
}
}
}

따라서 매개변수로 들어오는 Bundle객체에 데이터를 담아놓으면 회전된 화면이 만들어 졌을 때 필요한 데이터를 추출하여 사용할 수 있다.
즉, 회전이 발생하면 일부 UI 요소들은 초기화 되는데 이를 복구하기 위한 데이터를 담아서 사용하면 된다.
화면 회전 사건이 발생했을 때,
class MainActivity : AppCompatActivity() {
lateinit var activityMainBinding: ActivityMainBinding
// onCreate는 화면 회전이 발생했을 때도 호출된다.
// Activity가 처음 생성될 때는 매개변수에 null 값이 들어오고
// 화면 회전이 발생했을 때는 Bundle타입의 객체가 들어온다.
// 화면 회전이 발생하게 되면 이 메서드가 호출된다.
// 이 때 매개변수로 들어오는 Bundle객체에 데이터를 담아놓으면
// 회전된 화면이 만들어 졌을 때 필요한 데이터를 추출하여 사용할 수 있다.
// 즉, 회전이 발생하면 일부 UI 요소들은 초기화 되는데 이를 복구하기 위한 데이터를 담아서 사용하면 된다.
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
activityMainBinding = ActivityMainBinding.inflate(layoutInflater)
setContentView(activityMainBinding.root)
activityMainBinding.apply {
// 화면 회전이 발생했다면 초기화된 뷰를 복원해준다.
if(savedInstanceState == null){
textView.text = savedInstanceState?.getString("data1")
}
button.setOnClickListener {
textView.text = "안녕하세요"
}
button2.setOnClickListener {
textView.text = editTextText.text
}
}
}
// 화면 회전이 이벤트가 발생했을 때 회전되기 전에 호출되는 메서드
// 이 메서드가 호출된 후 화면 회전이 발생하고 onCreate가 그 다음에 호출된다.
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
// 화면을 복원하기 위한 데이터를 Bundle객체에 담아준다.
// 여기서 데이터를 담은 Bundle 객체는 화면 회전이 발생한 후
// onCreate가 호출될 때 전달된다.
outState.putString("data1", activityMainBinding.textView.text.toString())
}
}


화면 회전이 발생해도 텍스트뷰의 텍스트값이 초기화되지 않는다.
EditText의 입력값은 화면회전이 발생해도 그대로 유지된다. 일부는 유지되고 일부는 유지가 안되고 있어서 이런 작업을 해줘야 한다.







모든 뷰를 세로버전의 뷰와 동일한 id로 셋팅한다.

중복 경고창이 떠도 그대로 진행하면 된다.
앱을 실행하고 가로모드로 회전하면 추가한 가로모드 화면 그대로 나온다.

만약 세로모드만 지원하겠다면
land 폴더에 layout 파일을 만들지 않는 것으로 끝나면 안된다.
land 폴더에 layout 파일을 만들지 않았다는 것은 단말기 회전이 발생함에 대응을 하지 않은 것에 해당하는 것이다.
AndroidManifest.xml에서 Activity에 screenOrientation 속성의 방향을 지정해줘야 한다.
세로 portrait, 가로 landscape
화면 회전이 발생하지 않았기 때문에 onCreate 메서드도 다시 호출되지 않는다.

※ 출처 : 멋쟁이사자 앱스쿨 2기, 소프트캠퍼스