싱글톤(Singleton) 패턴은 클래스의 인스턴스가 하나만 생성되도록 보장하는 디자인 패턴입니다. 이를 통해 전역적으로 접근 가능한 객체를 만들 수 있습니다. 안드로이드 자바에서 싱글톤 패턴을 구현하는 주요 구조를 설명하겠습니다.
1. 기본 싱글톤 구조
public class Singleton {
// 싱글톤 인스턴스를 보관하는 정적 변수
private static Singleton instance = null;
// private 생성자를 통해 외부에서 객체를 생성하지 못하도록 방지
private Singleton() {
// 초기화 코드
}
// 인스턴스를 반환하는 정적 메서드
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
2. 스레드 안전한 싱글톤 (Lazy Initialization)
멀티스레드 환경에서 안전하게 싱글톤을 구현하려면 synchronized 키워드를 사용하여 동시에 여러 스레드가 접근하는 문제를 방지해야 합니다.
public class Singleton {
private static Singleton instance = null;
private Singleton() {
// 초기화 코드
}
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
3. 더블 체크 락킹(Double-Checked Locking)
스레드 안전성을 유지하면서 성능을 개선하기 위해 더블 체크 락킹 기법을 사용할 수 있습니다.
public class Singleton {
private static volatile Singleton instance = null;
private Singleton() {
// 초기화 코드
}
public static Singleton getInstance() {
if (instance == null) { // 첫 번째 체크
synchronized (Singleton.class) {
if (instance == null) { // 두 번째 체크
instance = new Singleton();
}
}
}
return instance;
}
}
4. 정적 블록을 사용한 초기화
정적 블록을 사용하여 클래스 로딩 시점에 인스턴스를 생성할 수 있습니다. 이 방법은 클래스가 로딩될 때 미리 인스턴스를 생성하므로 멀티스레드 환경에서도 안전합니다.
public class Singleton {
private static final Singleton instance;
static {
instance = new Singleton();
}
private Singleton() {
// 초기화 코드
}
public static Singleton getInstance() {
return instance;
}
}
5. Enum을 이용한 싱글톤 구현
Enum 타입은 기본적으로 싱글톤을 보장하므로, 가장 간단하고 안전한 방식 중 하나입니다.
public enum Singleton {
INSTANCE;
public void someMethod() {
// 메서드 구현
}
}
2번과 3번의 특징과 사용에 적절한 예시입니다.
2번: 스레드 안전한 싱글톤 (Lazy Initialization with Synchronized)
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
특징:
간단한 구현: 구현이 간단하며 직관적입니다.
전체 메서드 동기화: getInstance() 메서드 전체가 synchronized로 감싸져 있어, 여러 스레드가 동시에 접근하더라도 인스턴스가 하나만 생성되도록 보장합니다.
성능 문제: synchronized로 인해 성능 저하가 발생할 수 있습니다. 인스턴스가 이미 생성된 후에도 getInstance() 호출 시마다 synchronized가 적용되어, 스레드 간의 경합으로 인해 불필요한 성능 저하가 발생할 수 있습니다.
사용 시점: 성능 저하가 큰 문제가 되지 않는 간단한 애플리케이션에서 사용하기 적합합니다.
사용에 적합한 앱 예시
설정 관리 앱 (Settings Manager App):
앱의 설정값을 전역적으로 관리하는 SettingsManager 싱글톤 클래스가 있는 경우, 앱의 초기화 단계에서 한 번만 호출되고 이후 빈번하게 접근하지 않는다면 이 방식이 적합합니다.
예를 들어, 사용자가 앱의 테마, 알림 설정 등을 변경할 때만 getInstance()를 호출합니다. 이러한 호출은 빈번하지 않으며, 설정값이 적재된 후에는 getInstance() 메서드 호출이 거의 일어나지 않습니다.
이러한 앱에서는 성능보다는 코드의 간결성과 구현의 용이성이 더 중요하기 때문에 2번 방식이 유용합니다.
단순한 로그 관리 앱 (Simple Logging App):
로그를 관리하는 Logger 클래스가 싱글톤으로 구현된 경우, 로그 파일이 생성되고 기록되는 동안에는 큰 부하가 없고, 앱이 단일 스레드 환경에서 작동하는 경우가 많습니다.
예를 들어, 비동기 작업이 거의 없는 단순한 로그 기록 앱에서 로그 파일에 기록하는 경우, Logger 싱글톤 클래스의 인스턴스 접근이 빈번하지 않고, 스레드 경합이 없으므로 이 방식이 적합합니다.
싱글톤 리소스 관리자 앱 (Singleton Resource Manager):
앱에서 특정 리소스(예: DB 연결, 파일 읽기/쓰기 등)를 관리하는 클래스가 싱글톤으로 구현된 경우, 이 리소스가 자주 접근되지 않는다면 2번 방식이 유용할 수 있습니다.
예를 들어, 간단한 오프라인 데이터베이스를 사용해 자주 변경되지 않는 설정 데이터를 저장하고 읽어오는 앱에서, DB 접속 인스턴스에 대한 접근이 빈번하지 않다면 성능 저하가 크지 않기 때문에 2번 방식이 적합합니다.
3번: 더블 체크 락킹 (Double-Checked Locking)
public static Singleton getInstance() {
if (instance == null) { // 첫 번째 체크
synchronized (Singleton.class) {
if (instance == null) { // 두 번째 체크
instance = new Singleton();
}
}
}
return instance;
}
특징:
성능 최적화: getInstance() 메서드에서 처음 체크할 때 인스턴스가 이미 생성된 경우에는 동기화를 피할 수 있어, 성능 저하를 최소화할 수 있습니다. 이는 인스턴스를 생성한 이후 대부분의 호출에서 synchronized 블록에 들어가지 않기 때문에 효율적입니다.
복잡한 구현: 코드가 비교적 복잡하며, volatile 키워드와 두 번의 체크를 통해 동기화의 오버헤드를 줄이는 메커니즘을 이해해야 합니다.
멀티스레드 안전성: volatile 키워드를 통해 JVM의 메모리 모델에서 발생할 수 있는 문제를 방지하며, 멀티스레드 환경에서 안전하게 작동합니다.
사용 시점: 성능이 중요한 멀티스레드 애플리케이션에서 사용하기 적합합니다. 특히, 인스턴스 생성 이후 getInstance() 메서드가 빈번히 호출되는 경우에 효과적입니다.
사용에 적합한 앱 예시
멀티스레드 네트워크 통신 앱 (Multithreaded Networking App):
여러 스레드가 동시에 네트워크 요청을 보내고 처리하는 앱에서는 NetworkManager 싱글톤 클래스가 필요할 수 있습니다.
예를 들어, 앱에서 다수의 비동기 네트워크 요청을 처리하고, 요청을 큐에 넣거나 취소하고 결과를 콜백으로 전달할 때 NetworkManager 싱글톤 인스턴스에 자주 접근해야 합니다.
이 경우, 성능이 중요한 이슈가 되며, 인스턴스가 한 번 생성된 이후에는 성능 오버헤드 없이 빠르게 접근할 수 있는 3번 방식이 적합합니다.
게임 상태 관리 앱 (Game State Manager):
게임에서 모든 스레드가 현재 상태를 필요로 하며, 빠른 상태 변경 및 조회가 필요할 수 있습니다. 이 때 GameStateManager가 싱글톤으로 구현될 수 있습니다.
예를 들어, 여러 스레드가 캐릭터 상태, 게임 점수 등을 관리하고 업데이트하는 경우, 상태 조회 및 업데이트가 빈번히 발생하고 성능이 중요합니다. 이때 3번 방식이 유용합니다.
멀티스레드로 동작하는 데이터 분석 앱 (Multithreaded Data Analysis App):
여러 데이터 분석 작업을 병렬로 수행하고, 이를 통합 관리하는 DataAnalyzer 싱글톤 클래스가 필요한 앱입니다.
예를 들어, 대량의 데이터를 동시에 여러 스레드에서 분석하고, 분석된 결과를 합산 및 요약하는 앱에서, DataAnalyzer 싱글톤 인스턴스는 여러 스레드에서 접근하므로 빠른 접근이 필요합니다.
이 경우, 빈번한 접근 시 동기화 오버헤드가 발생하지 않도록 하기 위해 3번 방식이 적합합니다.
2번: 스레드 안전한 싱글톤 (Lazy Initialization with Synchronized) 코드예시
이 예제에서는 SettingsManager 클래스가 싱글톤으로 구현되어 앱의 설정을 관리합니다. 각기 다른 스레드에서 동일한 인스턴스에 접근해 설정 값을 저장하고 불러오는 샘플을 포함하고 있습니다.
SettingsManager 클래스
import android.content.Context;
import android.content.SharedPreferences;
public class SettingsManager {
// 싱글톤 인스턴스를 보관하는 정적 변수
private static SettingsManager instance = null;
// SharedPreferences 키
private static final String PREF_NAME = "app_settings";
private static final String KEY_APP_THEME = "app_theme";
private static final String KEY_NOTIFICATION_ENABLED = "notification_enabled";
private static final String KEY_USER_NAME = "user_name";
// 예제 필드
private String appTheme;
private boolean notificationEnabled;
private String userName;
// SharedPreferences
private SharedPreferences sharedPreferences;
// private 생성자를 통해 외부에서 객체를 생성하지 못하도록 방지
private SettingsManager(Context context) {
sharedPreferences = context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE);
// 초기화: SharedPreferences에서 값 로드
appTheme = sharedPreferences.getString(KEY_APP_THEME, "Light");
notificationEnabled = sharedPreferences.getBoolean(KEY_NOTIFICATION_ENABLED, true);
userName = sharedPreferences.getString(KEY_USER_NAME, "Guest");
}
// 인스턴스를 반환하는 정적 메서드
public static synchronized SettingsManager getInstance(Context context) {
if (instance == null) {
instance = new SettingsManager(context);
}
return instance;
}
// 테마를 설정하는 메서드
public void setAppTheme(String theme) {
this.appTheme = theme;
sharedPreferences.edit().putString(KEY_APP_THEME, theme).apply();
}
// 현재 테마를 반환하는 메서드
public String getAppTheme() {
return appTheme;
}
// 알림 설정을 활성화/비활성화하는 메서드
public void setNotificationEnabled(boolean enabled) {
this.notificationEnabled = enabled;
sharedPreferences.edit().putBoolean(KEY_NOTIFICATION_ENABLED, enabled).apply();
}
// 알림 설정 상태를 반환하는 메서드
public boolean isNotificationEnabled() {
return notificationEnabled;
}
// 사용자 이름을 설정하는 메서드
public void setUserName(String userName) {
this.userName = userName;
sharedPreferences.edit().putString(KEY_USER_NAME, userName).apply();
}
// 사용자 이름을 반환하는 메서드
public String getUserName() {
return userName;
}
}
사용 예제
* 이 코드에서는 MainActivity의 메인 스레드와 새로운 스레드가 동일한 SettingsManager 인스턴스를 사용하여 설정 값을 공유합니다.
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// SettingsManager 인스턴스 가져오기 (ApplicationContext 사용)
SettingsManager settingsManager = SettingsManager.getInstance(getApplicationContext());
// 메인 스레드에서 테마 설정
settingsManager.setAppTheme("Dark");
Log.d("SettingsManager", "Theme set to: " + settingsManager.getAppTheme());
// 메인 스레드에서 알림 설정 비활성화
settingsManager.setNotificationEnabled(false);
Log.d("SettingsManager", "Notifications Enabled: " + settingsManager.isNotificationEnabled());
// 메인 스레드에서 사용자 이름 설정
settingsManager.setUserName("John Doe");
Log.d("SettingsManager", "User Name set to: " + settingsManager.getUserName());
// 새로운 스레드에서 설정 값 확인
new Thread(new Runnable() {
@Override
public void run() {
SettingsManager settingsManager = SettingsManager.getInstance(getApplicationContext());
Log.d("SettingsManager", "Current Theme: " + settingsManager.getAppTheme());
Log.d("SettingsManager", "Notifications Enabled: " + settingsManager.isNotificationEnabled());
Log.d("SettingsManager", "User Name: " + settingsManager.getUserName());
}
}).start();
}
}
3번: 더블 체크 락킹 (Double-Checked Locking) 코드 예시
이 예제에서는 NetworkManager 클래스가 싱글톤으로 구현되어 네트워크 요청을 관리합니다. 여러 스레드가 동시에 네트워크 요청을 보낼 때 유일한 인스턴스를 사용하도록 보장합니다.
NetworkManager 클래스
import java.util.LinkedList;
import java.util.Queue;
public class NetworkManager {
// 싱글톤 인스턴스를 보관하는 정적 변수 (volatile 키워드 사용)
private static volatile NetworkManager instance = null;
// 네트워크 요청을 저장하는 큐
private Queue<String> requestQueue;
// private 생성자를 통해 외부에서 객체를 생성하지 못하도록 방지
private NetworkManager() {
this.requestQueue = new LinkedList<>();
}
// 인스턴스를 반환하는 정적 메서드 (더블 체크 락킹 사용)
public static NetworkManager getInstance() {
if (instance == null) { // 첫 번째 체크
synchronized (NetworkManager.class) {
if (instance == null) { // 두 번째 체크
instance = new NetworkManager();
}
}
}
return instance;
}
// 네트워크 요청을 큐에 추가하는 메서드
public synchronized void addRequest(String url) {
requestQueue.add(url);
System.out.println("Request added to queue: " + url);
}
// 네트워크 요청을 순차적으로 처리하는 메서드
public synchronized void processNextRequest() {
if (!requestQueue.isEmpty()) {
String nextRequest = requestQueue.poll();
// 실제 네트워크 요청을 보내는 코드가 여기에 위치 (가상 처리)
System.out.println("Processing request: " + nextRequest);
} else {
System.out.println("No requests to process.");
}
}
}
사용 예제
*이 코드에서는 MainActivity의 메인 스레드와 새로운 스레드가 동일한 NetworkManager 인스턴스를 사용하여 각각 다른 네트워크 요청을 보냅니다.
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 메인 스레드에서 네트워크 요청 추가
NetworkManager networkManager = NetworkManager.getInstance();
networkManager.addRequest("https://example.com/request1");
// 새로운 스레드에서 네트워크 요청 추가
new Thread(new Runnable() {
@Override
public void run() {
NetworkManager networkManager = NetworkManager.getInstance();
networkManager.addRequest("https://example.com/request2");
}
}).start();
// 메인 스레드에서 네트워크 요청 처리
new Thread(new Runnable() {
@Override
public void run() {
try {
// 1초 간격으로 요청 처리
while (true) {
NetworkManager networkManager = NetworkManager.getInstance();
networkManager.processNextRequest();
Thread.sleep(1000);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
}
에러메시지 : android.view.InflateException ... Error inflating class ...
1. Custom View의 생성자 확인
MyView가 커스텀 뷰라면, XML로 인플레이트 될 때 적절한 생성자를 가지고 있어야 합니다. 보통 세 가지 생성자를 모두 구현해 주는 것이 좋습니다.
public class MyView extends View {
// 1. XML에서 사용될 때 호출되는 생성자
public CustomView(Context context, AttributeSet attrs) {
super(context, attrs);
}
// 2. 프로그래밍적으로 사용될 때 호출되는 생성자
public MyView(Context context) {
super(context);
}
// 3. XML에서 스타일 속성도 함께 사용할 때 호출되는 생성자
public MyView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
}
2. 패키지 이름 확인
XML에서 선언된 MyView의 경로와 실제 MyView 클래스의 경로가 일치해야 합니다. 패키지명을 정확히 확인하세요.
3. ProGuard 설정 확인
앱이 ProGuard를 사용하고 있다면, 커스텀 뷰의 클래스 이름이 난독화되어 오류가 발생할 수 있습니다. ProGuard 설정 파일에 다음과 같이 커스텀 뷰를 제외시키는 규칙을 추가하세요.
4. 레이아웃 XML 파일 구조 확인
XML 파일의 구조가 잘못되었을 수 있습니다. 부모 레이아웃이 잘못되었거나, 중복된 태그가 있을 수 있습니다. XML 파일 전체를 확인하고 올바른지 검토해 보세요.
5. 커스텀 뷰의 리소스 문제
MyView 내부에서 사용되는 리소스가 올바르게 로드되지 않을 경우 오류가 발생할 수 있습니다. 커스텀 뷰에서 attrs로 전달되는 값들이 정상인지, 필요한 리소스들이 존재하는지 확인해 보세요.
6. Logcat 확인
정확한 에러를 파악하기 위해 Logcat에서 발생하는 오류 메시지를 확인하고, 어떤 부분에서 문제가 발생하는지 파악하는 것이 중요합니다. 대표적인 오류 메시지는 다음과 같습니다.
InflateException
ClassNotFoundException
NullPointerException
이러한 메시지를 기반으로 문제를 해결할 수 있습니다.
7. Android Manifest 확인
MyView를 사용하는 액티비티나 프래그먼트가 Android Manifest에 제대로 등록되어 있는지 확인하세요.
onMeasure(int widthMeasureSpec, int heightMeasureSpec):
부모 뷰에서 자식 뷰에게 주어진 제약 조건(MeasureSpec)을 기반으로 자식 뷰가 자신의 크기를 결정하는 단계입니다.
widthMeasureSpec과 heightMeasureSpec은 부모 뷰로부터 전달된 제약 조건으로, 자식 뷰는 이 값을 참고하여 자신의 크기를 측정해야 합니다.
이 메서드에서 setMeasuredDimension(int width, int height)을 호출하여 뷰의 최종 크기를 결정합니다.
2. 레이아웃 (Layout)
레이아웃 단계에서는 뷰의 위치를 결정합니다. 이 단계는 측정된 크기를 바탕으로, 각 뷰가 부모 뷰 안에서 어디에 위치할지를 정의합니다.
onLayout(boolean changed, int left, int top, int right, int bottom):
이 메서드는 뷰의 자식들이 어떻게 배치될지(어떤 위치에 그릴지)를 결정합니다.
changed는 뷰의 크기나 위치가 변경되었는지를 나타내며, left, top, right, bottom은 부모 뷰 내에서 자식 뷰의 위치를 지정합니다.
자식 뷰가 있다면, 이 메서드 내에서 각 자식 뷰의 layout() 메서드를 호출하여 자식 뷰들의 위치를 지정합니다.
3. 그리기 (Draw)
그리기 단계에서는 뷰가 실제로 화면에 그려집니다.
onDraw(Canvas canvas):
이 메서드는 뷰의 콘텐츠를 그리는 작업을 합니다.
이 메서드에서 주어진 Canvas 객체를 사용하여 텍스트, 이미지, 도형 등을 그릴 수 있습니다.
일반적으로 onDraw()는 invalidate()나 requestLayout()이 호출될 때 트리거되며, 이는 뷰가 다시 그려져야 함을 나타냅니다.
뷰의 전체적인 그려지는 과정
Measure 단계: 부모 뷰가 자식 뷰의 크기를 결정하기 위해 onMeasure()를 호출합니다. 이 단계에서 뷰의 크기가 결정됩니다.
Layout 단계: 부모 뷰가 자식 뷰의 위치를 결정하기 위해 onLayout()을 호출합니다. 이 단계에서 뷰의 위치가 결정됩니다.
Draw 단계: onDraw() 메서드가 호출되어 뷰의 실제 내용이 화면에 그려집니다.
이 과정은 여러 번 반복될 수 있으며, 특히 레이아웃이나 그리기 관련 변화가 생길 때마다 적절한 단계들이 재호출됩니다. 예를 들어, 뷰의 크기가 변경되면 onMeasure()와 onLayout()이 다시 호출되고, 뷰의 내용이 변경되면 onDraw()가 다시 호출됩니다.
안드로이드에서 View가 화면에 그려지기까지는 여러 단계의 콜백 메서드가 호출됩니다. 앞서 설명한 onMeasure(), onLayout(), onDraw() 외에도 중요한 메서드들이 존재하며, 이들은 각각의 역할을 수행하면서 뷰가 화면에 적절히 표시되도록 돕습니다. 전체적인 순서와 함께 추가적인 메서드들을 설명드리겠습니다.
전체적인 뷰 렌더링 과정과 주요 메서드들
onFinishInflate()
역할: XML 레이아웃 파일에서 뷰를 생성한 후 호출됩니다. 주로 뷰가 XML에서 정의된 후에 초기화 작업을 진행할 때 사용됩니다.
타이밍: XML로부터 뷰가 생성된 후, 모든 하위 뷰들이 초기화된 후 호출됩니다.
onAttachedToWindow()
역할: 뷰가 윈도우(화면)에 부착될 때 호출됩니다. 이때부터 뷰는 윈도우에서 보여질 준비가 완료된 것입니다.
타이밍: 뷰가 윈도우에 추가될 때 호출됩니다. 이 시점부터는 화면에 뷰가 나타날 수 있는 상태입니다.
참고: 이 메서드는 보통 리소스 초기화 또는 리스너 등록 등의 작업에 사용됩니다.
onMeasure(int widthMeasureSpec, int heightMeasureSpec)
역할: 뷰의 크기를 측정하는 단계로, 부모 뷰에서 주어진 MeasureSpec을 기준으로 뷰의 넓이와 높이를 결정합니다.
타이밍: 뷰 그룹이 자식 뷰를 측정할 때, 그리고 뷰가 스스로의 크기를 결정할 때 호출됩니다.
onSizeChanged(int w, int h, int oldw, int oldh)
역할: 뷰의 크기가 변경될 때 호출됩니다. 주로 뷰의 크기가 변경될 때 필요한 작업을 처리할 때 사용됩니다.
타이밍: 뷰의 크기가 변경될 때 onMeasure() 후에 호출됩니다.
참고: 새 크기(w, h)와 이전 크기(oldw, oldh)를 비교하여 변경된 부분에 대해 추가 작업을 수행할 수 있습니다.
onLayout(boolean changed, int left, int top, int right, int bottom)
역할: 뷰의 위치를 결정하는 단계로, 뷰 그룹이 자식 뷰의 위치를 지정할 때 사용됩니다.
타이밍: onMeasure() 이후, 뷰의 크기가 결정된 후 호출됩니다.
onDraw(Canvas canvas)
역할: 뷰의 내용을 그리는 단계로, Canvas를 사용해 텍스트, 이미지, 도형 등을 그립니다.
타이밍: 뷰가 화면에 실제로 그려질 때 호출됩니다. 레이아웃이 완료된 후 실행됩니다.
dispatchDraw(Canvas canvas)
역할: 자식 뷰를 그리는 작업을 담당합니다. ViewGroup의 경우 onDraw()에서 호출되지 않은 자식 뷰의 그리기 작업이 이 메서드에서 처리됩니다.
타이밍: onDraw() 이후, 자식 뷰가 있는 뷰 그룹에서 호출됩니다.
onDetachedFromWindow()
역할: 뷰가 윈도우에서 분리될 때 호출됩니다. 이 메서드에서는 리소스 해제나 리스너 제거와 같은 작업을 처리할 수 있습니다.
타이밍: 뷰가 윈도우에서 제거될 때 호출됩니다.
뷰의 전체적인 순서 요약
onFinishInflate(): XML에서 뷰가 완전히 생성된 후 호출.
onAttachedToWindow(): 뷰가 윈도우에 부착될 때 호출.
onMeasure(): 뷰의 크기를 측정.
onSizeChanged(): 뷰의 크기가 변경될 때 호출.
onLayout(): 뷰의 위치를 결정.
onDraw(): 뷰의 내용을 그리기.
dispatchDraw(): 자식 뷰의 그리기 작업을 처리.
onDetachedFromWindow(): 뷰가 윈도우에서 분리될 때 호출.
이 모든 과정이 유기적으로 작동하여 뷰가 화면에 그려지고 사용자에게 표시됩니다. 각 단계에서 적절한 작업을 수행함으로써 효율적이고 원하는 대로 뷰를 화면에 표시할 수 있습니다.
안드로이드에서 View의 특정 메서드가 호출되면, 앞서 설명한 단계들 중 일부가 실행됩니다. invalidate(), requestLayout() 같은 메서드는 View의 다시 그리기, 크기 변경, 레이아웃 조정 등을 트리거합니다. 각 메서드가 어떤 콜백 메서드를 실행하게 되는지 설명드리겠습니다.
1. invalidate()
설명: invalidate()는 뷰가 다시 그려져야 함을 시스템에 알립니다. 이 메서드를 호출하면 뷰의 onDraw() 메서드가 호출되어 뷰가 다시 그려집니다.
트리거되는 메서드:
onDraw(): 뷰의 내용을 다시 그리기 위해 호출됩니다.
참고: 이 메서드는 뷰의 레이아웃이나 크기에는 영향을 주지 않고, 오직 그리기만 다시 수행합니다.
2. requestLayout()
설명: requestLayout()은 뷰의 레이아웃이 다시 계산되어야 함을 시스템에 요청합니다. 이 메서드를 호출하면 측정(Measure)과 레이아웃(Layout) 단계가 다시 수행됩니다.
트리거되는 메서드:
onMeasure(): 뷰의 크기를 다시 측정하기 위해 호출됩니다.
onLayout(): 뷰의 위치를 다시 결정하기 위해 호출됩니다.
참고: requestLayout()은 뷰의 레이아웃과 크기에 변화를 주고, 필요에 따라 onDraw()도 호출될 수 있습니다.
11. 초록색 쩜은 양도소득세 부당행위계산 부인이 적용된 금액입니다. 시가보다 저렴하다고 판단해서 양도소득세, 취득세를 계산할 때 양도가액에 시가를 적용하는 것입니다. 노란색 쩜은 증여세 부당행위 계산 부인이 적용된 금액입니다. 시가보다 저렴한다고 판단해서 증여세를 부과하는 것입니다. 12. 자녀 기본공제 5천만원을 적용 또는 결혼출산(1억원) 까지 합계 1억5천만원 공제 적용 13. 계산 단위를 5천만원, 2천만원, 1천만원 단위로 계산. 14. 각 항목을 선택하여 상세결과를 볼수 있습니다.