<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>Foggy day</title>
    <link>https://jinhan38.tistory.com/</link>
    <description></description>
    <language>ko</language>
    <pubDate>Wed, 8 Apr 2026 19:42:26 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>jinhan38</managingEditor>
    <item>
      <title>[Android] SplashScreen 완벽 적용</title>
      <link>https://jinhan38.tistory.com/185</link>
      <description>&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;이번 포스팅에서는 Android에서 SplashScreen을 적용하는 방법을 알아보겠습니다. 좀 더 정확하게는 앱 실행 후 launch activity가 나오기 전에 잠시 동안 나오는 window 화면의 UI를 수정하는 내용입니다. 아무 설정을 하지 않는다면 일반적으로 빈 화면이 노출됩니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;여기서 말하는 SplashScreen은 SplashActivity를 말하는 것이 아닙니다. Android 12부터 사용 가능한 Splash API를 의미합니다. &lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: left;&quot;&gt;SplashActivity를 사용하지 않는 방법도 있지만, 예제에서는 &lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: left;&quot;&gt;SplashActivity와 SplashScreen을 둘 다 사용하겠습니다. &lt;/span&gt;일반적으로 &lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: left;&quot;&gt;SplashActivity에서 처리해야 하는 작업들이 있기 때문입니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;background-color: #ffffff; color: #333333; text-align: left;&quot; data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 style=&quot;background-color: #ffffff; color: #333333; text-align: left;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1. Dependency&lt;/b&gt;&lt;/h4&gt;
&lt;h4 style=&quot;background-color: #ffffff; color: #333333; text-align: left;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2. Resource&lt;/b&gt;&lt;/h4&gt;
&lt;h4 style=&quot;background-color: #ffffff; color: #333333; text-align: left;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3. Theme&lt;/b&gt;&lt;/h4&gt;
&lt;h4 style=&quot;background-color: #ffffff; color: #333333; text-align: left;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;4. SplashActivity&amp;nbsp;&lt;/b&gt;&lt;/h4&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;1. Dependency&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;App gradle에서 디펜던시를 추가해 주세요&lt;/p&gt;
&lt;pre id=&quot;code_1722654129269&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;implementation(&quot;androidx.core:core-splashscreen:1.0.1&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;br /&gt;2. Resource&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;window화면에서 보여줄 Resource를 추가해 주세요&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 아이콘을 안 보여줄 것이기 때문에 빈 화면의 xml 추가했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;res/drawable/transparent_layer.xml&lt;/p&gt;
&lt;pre id=&quot;code_1722655592167&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&amp;gt;
&amp;lt;layer-list xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;&amp;gt;

    &amp;lt;item android:drawable=&quot;@android:color/transparent&quot; /&amp;gt;

&amp;lt;/layer-list&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 window 배경에 적용할 이미지를 drawable에 추가해 주세요&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;res/drawable/splash_bg.xml&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;br /&gt;&lt;br /&gt;3. &lt;b&gt;&lt;b&gt;Theme&lt;/b&gt;&lt;/b&gt; &lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;themes.xml 파일에서 style을 추가하겠습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1722655700717&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;style name=&quot;Theme.CustomSplash&quot; parent=&quot;Theme.SplashScreen&quot;&amp;gt;
    &amp;lt;item name=&quot;windowSplashScreenBackground&quot;&amp;gt;@drawable/splash_bg&amp;lt;/item&amp;gt;
    &amp;lt;item name=&quot;windowSplashScreenAnimatedIcon&quot;&amp;gt;@drawable/transparent_layer&amp;lt;/item&amp;gt;
    &amp;lt;item name=&quot;postSplashScreenTheme&quot;&amp;gt;@style/Theme.Probing&amp;lt;/item&amp;gt;
&amp;lt;/style&amp;gt;

&amp;lt;style name=&quot;Theme.MyApp&quot; parent=&quot;Theme.AppCompat.NoActionBar&quot;&amp;gt;
    &amp;lt;item name=&quot;backgroundColor&quot;&amp;gt;@color/white&amp;lt;/item&amp;gt;
    &amp;lt;item name=&quot;windowActionBar&quot;&amp;gt;false&amp;lt;/item&amp;gt;
    &amp;lt;item name=&quot;windowNoTitle&quot;&amp;gt;true&amp;lt;/item&amp;gt;
    &amp;lt;item name=&quot;android:statusBarColor&quot;&amp;gt;@color/white&amp;lt;/item&amp;gt;
    &amp;lt;item name=&quot;android:windowLightStatusBar&quot;&amp;gt;true&amp;lt;/item&amp;gt;
&amp;lt;/style&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;windowSplashScreenBackground &lt;br /&gt;앱 시작 시 나타나는 window화면의 배경입니다. drawable의 resource를 입력해도 되고, 컬러를 입력해도 됩니다.&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;windowSplashScreenAnimatedIcon&lt;/span&gt; &lt;br /&gt;화면 가운데에 나올 Icon입니다. drawable에 있는 파일을 추가하면 됩니다. drawable을 설정하기에 따라서 애니메이션 기능도 구현할 수 있습니다. 저는 아이콘을 안 보여줄 것이기 때문에 transparent_layer 파일을 입력했습니다.&lt;/li&gt;
&lt;li&gt;postSplashScreenTheme&lt;br /&gt;Theme.CustomSplash를 적용시킨 후에 적용할 Theme를 입력하면 됩니다. 예제에서는 Theme.MyApp이라는 style을 추가 사용했습니다.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;br /&gt;4. &lt;b&gt;&lt;b&gt;SplashActivity&lt;/b&gt;&lt;/b&gt; &lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앱 실행 후 최초 진입할 Activity입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SplashActivity를 추가해 주시고,&amp;nbsp;manifest에서 앞서 만든 theme을 적용하겠습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1722656030682&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;activity
    android:name=&quot;.activity.SplashActivity&quot;
    android:theme=&quot;@style/Theme.CustomSplash&quot;
    android:exported=&quot;true&quot;&amp;gt;
    &amp;lt;intent-filter&amp;gt;
        &amp;lt;action android:name=&quot;android.intent.action.MAIN&quot; /&amp;gt;

        &amp;lt;category android:name=&quot;android.intent.category.LAUNCHER&quot; /&amp;gt;
    &amp;lt;/intent-filter&amp;gt;
&amp;lt;/activity&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 SplashActivity.kt에서 SplashScreen을 적용시키겠습니다.&lt;br /&gt;installSplashScreen() 코드를 넣어야 theme들이 window화면에 적용됩니다.&lt;/p&gt;
&lt;pre id=&quot;code_1722656158865&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@SuppressLint(&quot;CustomSplashScreen&quot;)
class SplashActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        installSplashScreen()
        setContentView(R.layout.activity_splash)
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기까지 작업이 완료됐다면 앱을 실행할 때 추가한 drawable들이 노출되는 것을 확인할 수 있습니다.&lt;/p&gt;</description>
      <category>Android</category>
      <category>Android</category>
      <category>Android Splash</category>
      <category>android splashscreen</category>
      <category>splashscreen</category>
      <author>jinhan38</author>
      <guid isPermaLink="true">https://jinhan38.tistory.com/185</guid>
      <comments>https://jinhan38.tistory.com/185#entry185comment</comments>
      <pubDate>Sat, 3 Aug 2024 12:49:15 +0900</pubDate>
    </item>
    <item>
      <title>Flutter로 웹 서비스 개발하기 (1) - Flutter Responsive Web</title>
      <link>https://jinhan38.tistory.com/184</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;안녕하세요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;인프런에서 Flutter Web 강의를 개설하게 됐습니다. Flutter에 관심있는 분들에게 추천드립니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;background-color: #ffffff; color: #24292f; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Flutter SEO에 대하여&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;많은 Flutter 개발자들이 Flutter Web은 구글에서 검색이 안 되는 것으로 알고 있습니다. 저 또한 예전에는 그렇게 알고 있었습니다. 하지만 이는 잘못된 지식입니다. Flutter로 만든 Web도 구글에 검색이 되고, SEO 최적화 작업도 할 수 있습니다. &lt;br /&gt;구글에 '샐링잇' or 'sailing-it'을 검색해보세요. Flutter로 만든 제 회사의 홈페이지가 검색되는 것을 볼 수 있습니다.(&lt;a href=&quot;https://sailing-it.com/)&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://sailing-it.com/)&lt;/a&gt;&lt;br /&gt;물론 이를 위해서는 추가적인 작업들이 필요합니다. 그래서 직접 SEO 패키지(&lt;a href=&quot;https://pub.dev/packages/flutter_seo&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://pub.dev/packages/flutter_seo&lt;/a&gt;)를&amp;nbsp;만들어서&amp;nbsp;pub.dev에&amp;nbsp;업로드&amp;nbsp;했습니다.&amp;nbsp;현재&amp;nbsp;강의인&amp;nbsp;Flutter&amp;nbsp;Responsive&amp;nbsp;Web&amp;nbsp;이후에&amp;nbsp;제가&amp;nbsp;만든&amp;nbsp;패키지를&amp;nbsp;활용해서&amp;nbsp;구글에서&amp;nbsp;검색이&amp;nbsp;될&amp;nbsp;수&amp;nbsp;있도록&amp;nbsp;SEO를&amp;nbsp;적용한&amp;nbsp;Flutter&amp;nbsp;Web&amp;nbsp;SEO&amp;nbsp;강의를&amp;nbsp;진행할&amp;nbsp;예정입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;background-color: #ffffff; color: #24292f; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;강의에서 배우는 것들은 아래와 같습니다.&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Responsive&amp;nbsp;Web &lt;br /&gt;반응형 웹을 개발하는 방법에 대해 배울 것입니다. 웹에서 발생하는 다양한 화면 사이즈에 어떻게 대응하면 좋을지 배울 수 있습니다.&lt;/li&gt;
&lt;li&gt;위젯에&amp;nbsp;대한&amp;nbsp;이해 &lt;br /&gt;커스텀 위젯을 많이 사용하기 때문에 위젯들에 대한 이해도를 높일 수 있습니다.&lt;/li&gt;
&lt;li&gt;구조&amp;nbsp;설계 &lt;br /&gt;Web을 개발할 때 필요한 구조를 설계하는 방법을 배울 수 있습니다.&lt;/li&gt;
&lt;li&gt;AWS&amp;nbsp;S3&amp;nbsp;배포 &lt;br /&gt;AWS의 S3에 Flutter로 만든 웹을 배포할 수 있습니다.&lt;/li&gt;
&lt;li&gt;Style&amp;nbsp;설정 &lt;br /&gt;적절한&amp;nbsp;스타일&amp;nbsp;설정&amp;nbsp;방법을&amp;nbsp;배울&amp;nbsp;수&amp;nbsp;있습니다.&amp;nbsp;FrontEnd에서는&amp;nbsp;스타일들을&amp;nbsp;어떤&amp;nbsp;방식으로&amp;nbsp;설정할지가&amp;nbsp;항상&amp;nbsp;고민입니다.&amp;nbsp;많은&amp;nbsp;방법&amp;nbsp;중&amp;nbsp;하나가&amp;nbsp;될&amp;nbsp;수&amp;nbsp;있는&amp;nbsp;Style&amp;nbsp;설정&amp;nbsp;방법을&amp;nbsp;배울&amp;nbsp;수&amp;nbsp;있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot;&gt;
&lt;div style=&quot;background-color: #ffffff;&quot;&gt;
&lt;div style=&quot;background-color: #ffffff; color: #24292f;&quot; data-testid=&quot;editor-content-container&quot;&gt;
&lt;div style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot;&gt;
&lt;div style=&quot;background-color: #ffffff;&quot;&gt;
&lt;div style=&quot;background-color: #ffffff; color: #24292f;&quot; data-testid=&quot;editor-content-container&quot;&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;이 강의의 특징&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;인식의 변화&lt;br /&gt;Flutter로도 충분히 Web을 개발할 수 있다는 인식 변화가 우선입니다. 하나의 프레임워크로 App과 Web을 개발할 수 있다는 것은 개발자로서의 자신감 향상과 더불어 할 수 있는 영역이 대폭 넓어질 수 있습니다.&lt;/li&gt;
&lt;li&gt;Web 중심의 Flutter 강의&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #24292f; text-align: start;&quot;&gt;기존의 강의들은 대부분 App 중심의 강의였습니다. 이 강의는 Web 개발에 집중한 Flutter 강의입니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #ffffff; color: #24292f; text-align: start;&quot;&gt;낮은 라이브러리 의존성&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;대부분의 것들을 직접 코드로 작성합니다. 때문에 라이브러리의 의존성이 낮아서 버전 변경에 유연합니다. 또한 직접 코드를 작성하기 때문에 Framework에 대한 이해도가 높아집니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot;&gt;
&lt;div style=&quot;background-color: #ffffff;&quot;&gt;
&lt;div style=&quot;background-color: #ffffff; color: #24292f;&quot; data-testid=&quot;editor-content-container&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://inf.run/k7Lk2&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://inf.run/k7Lk2&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;figure id=&quot;og_1720016214933&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Flutter로 웹 서비스 개발하기 (1) - Responsive Web 강의 | 김진한 - 인프런&quot; data-og-description=&quot;김진한 | 최고의 크로스플랫폼 Flutter, Flutter로 Web을 개발해보세요., Flutter로 Web을 개발해보세요최고의 크로스 플랫폼 Flutter로 App 뿐만 아니라 Web도 개발이 가능합니다.하나의 프레임워크와 언어&quot; data-og-host=&quot;www.inflearn.com&quot; data-og-source-url=&quot;https://inf.run/k7Lk2&quot; data-og-url=&quot;https://www.inflearn.com/course/flutter-웹서비스-responsive-web-part1&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/dbdF5F/hyWvWMLxbv/CkdW2dFRajOzRC1bBf4fw1/img.png?width=1200&amp;amp;height=781&amp;amp;face=0_0_1200_781,https://scrap.kakaocdn.net/dn/czUAPi/hyWrMSAlIf/OufWnbqiKcsevbdTK420x0/img.png?width=1200&amp;amp;height=781&amp;amp;face=0_0_1200_781,https://scrap.kakaocdn.net/dn/dEyR5y/hyWvXZcgPN/aELZOVygT9gN9gllMvhVsK/img.png?width=1182&amp;amp;height=1182&amp;amp;face=0_0_1182_1182&quot;&gt;&lt;a href=&quot;https://inf.run/k7Lk2&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://inf.run/k7Lk2&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/dbdF5F/hyWvWMLxbv/CkdW2dFRajOzRC1bBf4fw1/img.png?width=1200&amp;amp;height=781&amp;amp;face=0_0_1200_781,https://scrap.kakaocdn.net/dn/czUAPi/hyWrMSAlIf/OufWnbqiKcsevbdTK420x0/img.png?width=1200&amp;amp;height=781&amp;amp;face=0_0_1200_781,https://scrap.kakaocdn.net/dn/dEyR5y/hyWvXZcgPN/aELZOVygT9gN9gllMvhVsK/img.png?width=1182&amp;amp;height=1182&amp;amp;face=0_0_1182_1182');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Flutter로 웹 서비스 개발하기 (1) - Responsive Web 강의 | 김진한 - 인프런&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;김진한 | 최고의 크로스플랫폼 Flutter, Flutter로 Web을 개발해보세요., Flutter로 Web을 개발해보세요최고의 크로스 플랫폼 Flutter로 App 뿐만 아니라 Web도 개발이 가능합니다.하나의 프레임워크와 언어&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.inflearn.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;</description>
      <category>Flutter/Flutter 강의</category>
      <category>FLUTTER</category>
      <category>flutter web</category>
      <category>flutter web 강의</category>
      <category>Flutter 강의</category>
      <author>jinhan38</author>
      <guid isPermaLink="true">https://jinhan38.tistory.com/184</guid>
      <comments>https://jinhan38.tistory.com/184#entry184comment</comments>
      <pubDate>Wed, 3 Jul 2024 23:18:20 +0900</pubDate>
    </item>
    <item>
      <title>[Flutter] Flutter web에서 마우스 드래그 허용, MaterialScrollBehavior/ScrollBehavior, ScrollPhysics</title>
      <link>https://jinhan38.tistory.com/182</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;이번 포스팅에서는 Flutter web에서 마우스 드래그를 허용하는 방법과 MaterialScrollBehavior, ScrollBehavior에 대해 알아보겠습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;br /&gt;1. CustomScrollBehavior&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;2. MaterialScrollBehavior &lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;3. ScrollPhysics &lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. CustomScrollBehavior &lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Flutter웹에서 마우스 드래그가 안 되는 이유는 기본 세팅값에 mouse 드래그 허용 타입이 빠져있기 때문입니다. 때문에 MaterialScrollBehavior(extends ScrollBehavior)를 커스텀해서 PointerDeviceKind 타입에 mouse를 추가해주면 드래그가 가능합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PointerDeviceKind에 대해 좀 더 자세하게 들여보겠습니다. enum 클래스인 PointerDeviceKind에는 6개의 타입이 있습니다(touch, mouse, stylus, invertedStylus, trackpad, unknown). 그리고 ScrollBehavior에서 PointerDeviceKind 값을 가지고 있는 변수는 dragDevices입니다. dragDevices의 초기 값인 _kTouchLikeDeviceTypes( scroll_configuration.dart) 변수에는 6개의 타입 중 mouse만 빠져 있습니다. 그래서 dragDevices getter를 override해서 mouse 타입을 추가하는 것입니다.&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Full code&lt;/b&gt;&lt;/h2&gt;
&lt;pre id=&quot;code_1716482753286&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'CustomScrollBehavior',
      scrollBehavior: CustomScrollBehavior(),
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
      ),
      home: Scaffold(
        body: SingleChildScrollView(
          scrollDirection: Axis.horizontal,
          child: Row(
            children: [
              Container(width: 200, height: 300, color: Colors.red),
              Container(
                  width: 200, height: 300, color: Colors.blue),
              Container(
                  width: 200, height: 300, color: Colors.green),
              Container(
                  width: 200, height: 300, color: Colors.purple),
              Container(
                  width: 200, height: 300, color: Colors.orange),
              Container(
                  width: 200, height: 300, color: Colors.yellow),
              Container(
                  width: 200, height: 300, color: Colors.teal),
              Container(
                  width: 200, height: 300, color: Colors.grey),
              Container(
                  width: 200, height: 300, color: Colors.brown),
              Container(
                  width: 200, height: 300, color: Colors.amber),
            ],
          ),
        ),
      ),
    );
  }
}

/// 마우스로 드래그 안 될 때
class CustomScrollBehavior extends MaterialScrollBehavior {
  @override
  Set&amp;lt;PointerDeviceKind&amp;gt; get dragDevices =&amp;gt; {
        PointerDeviceKind.touch,
        PointerDeviceKind.mouse,
      };
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. MaterialScrollBehavior&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MaterialScrollBehavior는 ScrollBehavior의 자식 클래스로 스크롤이 가능한 위젯의 동작 방식을 결정합니다. MaterialScrollBehavior를 처음 접하는 분들이 많을 것 같습니다. 하지만 Flutter를 사용하는 대부분의 개발자들은 인지하지 못한 채 MaterialScrollBehavior를 사용하고 있었습니다. MaterialApp 위젯의 build 함수에서 MaterialScrollBehavior를 사용하고 있었습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MaterialScrollBehavior에서는&amp;nbsp;3개의&amp;nbsp;함수를&amp;nbsp;override하고&amp;nbsp;있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;getPlatform&lt;br /&gt;&lt;/span&gt;TargetPlatform enum class 를 return하며 현재 Flutter application이 어떤 플랫폼에서 동작하는 알 수 있습니다. android, iOS, fuchsia, linux, macOS, windows 6개의 타입이 존재합니다. 만약에 Flutter web이 윈도우에서 돌아갈 경우 windows, mac에서 돌아갈 경우 macOS값을 반환합니다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;buildScrollbar&lt;br /&gt;&lt;/span&gt;Scrollable한 위젯에 Scrollbar를 입혀주는 함수입니다. linux, macOS, windows의 platform에서는 Scrollbar를 보여주고 있습니다. android, fuchsia, iOS에서는 Scrollbar를 추가하지 않았습니다. 그래서 우리가 android나 iOS 앱을 개발할 때 스크롤바를 보여주기 위해서는 Scrollable한 위젯을 따로 Scrollbar 위젯으로 감싸줘야 했던 것입니다.&lt;/li&gt;
&lt;li&gt;buildOverscrollIndicator&lt;br /&gt;여기서는 android인 경우에만 다른 조건을 설정하고 있습니다. android 플랫폼이고&amp;nbsp;&amp;nbsp;useMaterial3를 true로 한 경우 StretchingOverscrollIndicator를 반환합니다. 그렇지 않은 경우에는 GlowingOverscrollIndicator를 사용합니다. 그 외의 플랫폼들에서는 child위젯을 그대로 반환합니다.&amp;nbsp;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;StretchingOverscrollIndicator은&amp;nbsp;끝에&amp;nbsp;도달&amp;nbsp;했을&amp;nbsp;때&amp;nbsp;화면의&amp;nbsp;위젯들이&amp;nbsp;고무줄처럼&amp;nbsp;약간&amp;nbsp;늘어났다가&amp;nbsp;돌아오는,&amp;nbsp;stretching하는&amp;nbsp;효과를&amp;nbsp;줍니다.&amp;nbsp;GlowingOverscrollIndicator는&amp;nbsp;반투명한&amp;nbsp;물결을&amp;nbsp;보여줍니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;br /&gt;3. ScrollPhysics&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스크롤링에&amp;nbsp;대한&amp;nbsp;이벤트를&amp;nbsp;정의하는&amp;nbsp;또&amp;nbsp;다른&amp;nbsp;특성은&amp;nbsp;ScrollPhysics클래스입니다. ScrollPhysics는 ios와 macos에는 BouncingScrollPhysics를, 그 외의 플랫폼에는 ClampingScrollPhysics를 사용합니다.&lt;br /&gt;GlowingOverscrollIndicator는 android에서 ClampingScrollPhysics을 사용할 때 동작을 설정하는 역할을 합니다. showLeading의 값을 false로 하면 상단에서는 이펙트가 동작 안하고, showTrailing을 false로 하면 하단에서 이펙트가 동작 안 합니다. color값을 수정하면 물결의 컬러가 변경됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Flutter/Flutter 기타</category>
      <category>FLUTTER</category>
      <category>flutter scroll</category>
      <category>flutter scrollbehavior</category>
      <category>flutter scrollphysics</category>
      <category>flutter web drag</category>
      <category>GlowingOverscrollIndicator</category>
      <category>materialscrollbehavior</category>
      <category>ScrollBehavior</category>
      <category>ScrollPhysics</category>
      <category>stretchingoverscrollindicator</category>
      <author>jinhan38</author>
      <guid isPermaLink="true">https://jinhan38.tistory.com/182</guid>
      <comments>https://jinhan38.tistory.com/182#entry182comment</comments>
      <pubDate>Fri, 24 May 2024 02:05:33 +0900</pubDate>
    </item>
    <item>
      <title>[Flutter] Image cache(precacheImage)와 WidgetsFlutterBinding, PaintingBinding</title>
      <link>https://jinhan38.tistory.com/181</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 포스팅에서는 Flutter에서 image를 사전 cache 하는 법과 cache 된 이미지들이 어디에 저장되는지 알아보겠습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Asset 이미지들의 경우 실제로 사용할 때 초반에 잠시 깜빡이는 동작이 발생합니다. 이것은 아직 메모리에 로드되지 않았기 때문입니다. 사전캐싱 처리를 한다면 깜빡이는 동작 없이 바로 이미지를 보여줄 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;1. Image Widget&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;2. PaintingBinding, WidgetsFlutterBinding&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;3. precacheImage&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. Image Widget&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적으로 flutter에서 이미지 위젯을 구현할 때 image 관련 패키지를 사용하거나 flutter 자체에서 제공하는 Image 위젯을 사용합니다. Image 위젯에서는 한 번 이미지를 생성하면 cache를 하도록 돼 있습니다. Image 클래스의 _resolveImage 함수가 그 역할을 합니다. _resolveImage 함수에서 ImageProvider 클래스의 resolve함수를 호출하고, resolve함수에서는 resolveStreamForKey 함수를 호출해서 PaintingBinding -&amp;gt; imageCache 클래스의 _cache 변수에 이미지를 저장합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. PaintingBinding, WidgetsFlutterBinding&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 이미지 데이터를 저장하는 PaintingBinding 클래스는 어디에 있는걸까요? PaintingBinding은 mixin클래스로 WidgetsFlutterBinding 클래스에서 사용하고 있습니다. WidgetsFlutterBinding 클래스에서는 PaintingBinding 외에도 GestureBinding, SchedulerBinding, ServicesBinding, SemanticsBinding, RendererBinding, WidgetsBinding 등의 mixin클래스들을 사용하고 있습니다. WidgetsFlutterBinding 클래스는 일반적으로 main 함수에서 WidgetsFlutterBinding.ensureInitialized(); 코드로 자주 사용됩니다. ensureInitialized 함수를 호출해서 static으로 여러 mixin 클래스들을 초기화시켜 줍니다. 다시 말해 관습적으로 main에서 호출하던 ensureInitialized 함수는 Flutter 엔진에서 사용하는 여러 클래스들을 초기화(인스턴스화) 시켜주는 역할을 합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PaintingBinding의 경우에는 initInstances 함수에서 _imageCache 변수를 생성해주고 있습니다. 이미지 캐시 데이터들을 가지고 있는 ImageCache클래스는 독자적으로 static하게 존재하는 것이 아니라 WidgetsFlutterBinding -&amp;gt;&amp;nbsp; PaintingBinding 클래스의 내부에 존재하고 있었습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;br /&gt;&lt;b&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;3.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;precacheImage&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면 이론적인 설명은 차치하고, 이미지를 사전캐싱하는 방법을 알아보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사전 캐싱을 하기 위해서는 precacheImage 함수를 호출해야 합니다. precacheImage 함수는 image.dart파일에서 전역 함수로 존재합니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1715849423432&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;precacheImage(const AssetImage(&quot;여기에 assets 파일의 경로를 넣으세요&quot;), context);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 호출하면 이미지가 캐시되고, 해당 이미지를 사용할 때 바로 딜레이 없이 로드되는 것을 확인할 수 있습니다.&amp;nbsp;&lt;/p&gt;</description>
      <category>Flutter/Flutter 기타</category>
      <category>FLUTTER</category>
      <category>flutter image</category>
      <category>flutter image cache</category>
      <category>flutter precacheimage</category>
      <category>image cache</category>
      <category>paintingbinding</category>
      <category>WidgetsFlutterBinding</category>
      <author>jinhan38</author>
      <guid isPermaLink="true">https://jinhan38.tistory.com/181</guid>
      <comments>https://jinhan38.tistory.com/181#entry181comment</comments>
      <pubDate>Thu, 16 May 2024 17:55:50 +0900</pubDate>
    </item>
    <item>
      <title>[Dart] Isolate 사용법(Thread 작업)</title>
      <link>https://jinhan38.tistory.com/180</link>
      <description>&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;이번 포스팅에서는 Dart의 Isolate에 대해 알아보겠습니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt; Isoate는 별도의 Thread에서 작업을 할 수 있도록 도와주는 클래스입니다. 시간이 오래 걸리는 작업을 하면서 화면은 계속해서 업데이트 하고 싶은 경우&amp;nbsp;사용하면 좋습니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;1. 메인 스레드에서 작업&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;2. Isolate.run&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;3. Isolate.spawn&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;background-color: #ffffff; color: #000000; text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1.&amp;nbsp; 메인 스레드에서 작업&lt;/b&gt;&lt;/h2&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;무거운 작업을 메인스레드에서 하게 되면 화면이 멈추는 현상이 발생합니다. 왜냐하면 Flutter는 기본적으로 단일 스레드를 사용하기 때문입니다. UI를 그리는 메인 스레드에서 무거운 연산 작업을 하게 될 경우 화면을 계속해서 업데이트할 수 없기 때문에 멈추는 것입니다. 이러한 것을 정크(jank)라고 합니다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;아래 메인 스레드에서 while문을 돌리는 예제입니다. sleep을 추가해 놨기 때문에 잠시동안 화면이 멈춰버립니다. 이러한 문제를 해결하기 위해서 Isolate를 사용할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1715245947064&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;int count = 0;

/// 메인 스레드에서 작업 실행
ElevatedButton(
  onPressed: () {
    setState(() {
      count = 0;
    });
    while (count &amp;lt; 300) {
      count++;
      sleep(const Duration(milliseconds: 10));
      setState(() {});
    }
  },
  child: const Text(&quot;Main Thread&quot;),
)&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;영상을 보면 작업이 완료 될 때까지 프로그레스바가 멈춰 있는 것을 볼 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignLeft&quot; data-video-host=&quot;kakaotv&quot; data-video-url=&quot;https://tv.kakao.com/v/446592685&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/4r2WY/hyV2v92Asi/Y6xi4MiB518wlWfkSld4F0/img.jpg?width=886&amp;amp;height=1920&amp;amp;face=0_0_886_1920,https://scrap.kakaocdn.net/dn/fouap/hyVZtTwPTS/0l6l1b7hyvewOurGb5KfcK/img.jpg?width=886&amp;amp;height=1920&amp;amp;face=0_0_886_1920&quot; data-video-width=&quot;300&quot; data-video-height=&quot;650&quot; data-video-origin-width=&quot;860&quot; data-video-origin-height=&quot;1864&quot; data-ke-mobilestyle=&quot;widthContent&quot; data-video-play-service=&quot;daum_tistory&quot; data-original-url=&quot;&quot; data-video-title=&quot;&quot;&gt;&lt;iframe src=&quot;https://play-tv.kakao.com/embed/player/cliplink/446592685?service=daum_tistory&quot; width=&quot;300&quot; height=&quot;650&quot; frameborder=&quot;0&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption style=&quot;display: none;&quot;&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;background-color: #ffffff; color: #000000; text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2.&amp;nbsp; Isolate.run&lt;/b&gt;&lt;/h2&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;Isolate.run을 사용하면 다른 스레드에서 작업을 요청한 후 결과 값을 돌려받을 수 있습니다. 마치 Future 함수의 형태와 유사합니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;이때 주의할 것은 Isolate.run의 구현부에서 메인스레드에 있는 변수를 참조하거나 setState를 호출할 경우 오류가 발생합니다. Isolate는 다른 메모리이기 때문에 메인스레드의 메모리에 있는 것들을 공유할 수가 없기 때문입니다. 이러한 문제를 해결하기 위한 것이 Isolate.spawn입니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1715246254251&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  /// isolate.run 을 사용해서 작업 실행
  ElevatedButton(
    onPressed: () =&amp;gt; isolateRun(),
    child: const Text(&quot;Isolate run&quot;),
  )
  
  ....

  /// Isolate.run 을 사용해서 다른 스레드 사용
  /// setState 나 다른 변수들을 사용하면 오류 발생 -&amp;gt; 메모리를 공유하지 않기 때문에 변수도 공유할 수 없음
  /// 메인 스레드와 Isolate 스레드 간에 통신을 하기 위해서는 Isolate.spawn 사용 필요.
  void isolateRun() async {
    setState(() {
      count = 0;
    });
    var result = await Isolate.run(() {
      int isolateCount = 0;
      while (isolateCount &amp;lt; 300) {
        isolateCount++;
        sleep(const Duration(milliseconds: 10));
      }
      return isolateCount.toString();
    });
    setState(() {
      count = int.parse(result);
    });
  }&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: left;&quot;&gt;영상을 보면 작업이 완료 될 때까지 프로그레스바가 계속 돌아가고 있는 것을 볼 수 있습니다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignLeft&quot; data-video-host=&quot;kakaotv&quot; data-video-url=&quot;https://tv.kakao.com/v/446592716&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/sehCw/hyV2tRUgeu/jGUoTZqNJ5A0jh6URX0QkK/img.jpg?width=886&amp;amp;height=1920&amp;amp;face=0_0_886_1920,https://scrap.kakaocdn.net/dn/dzoj83/hyV2vWt9tZ/4hqtTVTGvGwybrbq96LFJk/img.jpg?width=886&amp;amp;height=1920&amp;amp;face=0_0_886_1920&quot; data-video-width=&quot;300&quot; data-video-height=&quot;650&quot; data-video-origin-width=&quot;860&quot; data-video-origin-height=&quot;1864&quot; data-ke-mobilestyle=&quot;widthContent&quot; data-video-play-service=&quot;daum_tistory&quot; data-original-url=&quot;&quot; data-video-title=&quot;&quot;&gt;&lt;iframe src=&quot;https://play-tv.kakao.com/embed/player/cliplink/446592716?service=daum_tistory&quot; width=&quot;300&quot; height=&quot;650&quot; frameborder=&quot;0&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption style=&quot;display: none;&quot;&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;background-color: #ffffff; color: #000000; text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;3.&amp;nbsp; Isolate.spawn&lt;/b&gt;&lt;/h2&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: left;&quot;&gt;Isolate.spawn을 사용하면 Isolate 스레드와 메인 스레드가 통신할 수 있습니다. 마치 Stream 함수의 형태와 유사합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: left;&quot;&gt;spawn에서는 run과 마찬가지로 구현부가 필요하고, 두 스레드를 연결시켜 줄 도구가 필요합니다. 도구의 역할을 하는 것이 ReceivePort입니다. ReceivePort는 Stream 클래스를 구현한(implement)한 클래스입니다. 때문에 StreamSubscription를 return하는 listen 함수를 사용할 수 있습니다. spawn의 구현부에서 sendPort.send() 함수로 데이터를 보내면 listen 함수에서 메인스레드로 데이터를 받을 수가 있습니다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1715246546417&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  /// isolate spawn 을 사용해서 작업 실행
  ElevatedButton(
    onPressed: () =&amp;gt; isolateSpawn(),
    child: const Text(&quot;Isolate spawn&quot;),
  ),
  
  ....

  void isolateSpawn() {
    setState(() {
      count = 0;
    });

    /// 메인 스레드와 isolate 스레드를 연결 시켜줄 ReceivePort 생성
    ReceivePort receivePort = ReceivePort();

    /// receivePort.listen을 사용해서 Isolate가 전달하는 데이터를 메인 스레드에서 수신
    receivePort.listen((data) {
      count = int.parse(data);
      setState(() {});
    });

    /// Isolate 생성
    Isolate.spawn(
      /// 다른 스레드에서 작업할 내용
      /// sendPort.send() 함수를 사용해서 메인 스레드에게 데이터를 전달할 수 있음
      (sendPort) async {
        int isolateCount = 0;
        while (isolateCount &amp;lt; 300) {
          isolateCount++;
          sendPort.send(isolateCount.toString());
          sleep(const Duration(milliseconds: 10));
        }
      },

      /// 앞서 생성한 sendPort 전달
      receivePort.sendPort,
    );
  }&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: left;&quot;&gt;영상을 보면 실시간으로 count 값이 변경되는 것을 볼 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignLeft&quot; data-video-host=&quot;kakaotv&quot; data-video-url=&quot;https://tv.kakao.com/v/446592727&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/bfeXe5/hyVZlHXtoJ/dzkPkTawFadYrfwGRxNmSk/img.jpg?width=886&amp;amp;height=1920&amp;amp;face=0_0_886_1920,https://scrap.kakaocdn.net/dn/b9kp45/hyV2vhTQV9/vat6zHVh9MtKVwRMnzhbG0/img.jpg?width=886&amp;amp;height=1920&amp;amp;face=0_0_886_1920&quot; data-video-width=&quot;300&quot; data-video-height=&quot;650&quot; data-video-origin-width=&quot;860&quot; data-video-origin-height=&quot;1864&quot; data-ke-mobilestyle=&quot;widthContent&quot; data-video-play-service=&quot;daum_tistory&quot; data-original-url=&quot;&quot; data-video-title=&quot;&quot;&gt;&lt;iframe src=&quot;https://play-tv.kakao.com/embed/player/cliplink/446592727?service=daum_tistory&quot; width=&quot;300&quot; height=&quot;650&quot; frameborder=&quot;0&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption style=&quot;display: none;&quot;&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: left;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: left;&quot;&gt;*만약에&lt;/span&gt; Isolate.spawn에 메소드를 사용할 경우 때 첫 번째 인자에 함수를 넣고 싶다면 함수는 반드시 static이어야 합니다. Isolate는 다른 메모리에서 실행되기 때문에 해당 함수가 어떤 객체의 상태에도 의존하지 않아야 합니다. 다시 말해 독립적인 함수의 형태가 필요합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;background-color: #ffffff; color: #000000; text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Full code&lt;/b&gt;&lt;/h2&gt;
&lt;pre id=&quot;code_1715247579018&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import 'dart:io';
import 'dart:isolate';

import 'package:flutter/material.dart';

class Home extends StatefulWidget {
  const Home({super.key});

  @override
  State&amp;lt;Home&amp;gt; createState() =&amp;gt; _HomeState();
}

class _HomeState extends State&amp;lt;Home&amp;gt; {
  int count = 0;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: SafeArea(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            /// 메인 스레드에서 작업 실행
            ElevatedButton(
              onPressed: () {
                setState(() {
                  count = 0;
                });
                while (count &amp;lt; 300) {
                  count++;
                  sleep(const Duration(milliseconds: 10));
                  setState(() {});
                }
              },
              child: const Text(&quot;Main Thread&quot;),
            ),

            /// isolate.run 을 사용해서 작업 실행
            ElevatedButton(
              onPressed: () =&amp;gt; isolateRun(),
              child: const Text(&quot;Isolate run&quot;),
            ),

            /// isolate spawn 을 사용해서 작업 실행
            ElevatedButton(
              onPressed: () =&amp;gt; isolateSpawn(),
              child: const Text(&quot;Isolate spawn&quot;),
            ),

            Text(
              &quot;count : $count&quot;,
              style: const TextStyle(fontSize: 25),
            ),
            const Center(child: CircularProgressIndicator()),
          ],
        ),
      ),
    );
  }

  /// Isolate.run 을 사용해서 다른 스레드 사용
  /// setState 나 다른 변수들을 사용하면 오류 발생 -&amp;gt; 메모리를 공유하지 않기 때문에 변수도 공유할 수 없음
  /// 메인 스레드와 Isolate 스레드 간에 통신을 하기 위해서는 Isolate.spawn 사용 필요.
  void isolateRun() async {
    setState(() {
      count = 0;
    });
    var result = await Isolate.run(() {
      int isolateCount = 0;
      while (isolateCount &amp;lt; 300) {
        isolateCount++;
        sleep(const Duration(milliseconds: 10));
      }
      return isolateCount.toString();
    });
    setState(() {
      count = int.parse(result);
    });
  }

  void isolateSpawn() {
    setState(() {
      count = 0;
    });

    /// 메인 스레드와 isolate 스레드를 연결 시켜줄 ReceivePort 생성
    ReceivePort receivePort = ReceivePort();

    /// receivePort.listen을 사용해서 Isolate가 전달하는 데이터를 메인 스레드에서 수신
    receivePort.listen((data) {
      count = int.parse(data);
      setState(() {});
    });

    /// Isolate 생성
    Isolate.spawn(
      /// 다른 스레드에서 작업할 내용
      /// sendPort.send() 함수를 사용해서 메인 스레드에게 데이터를 전달할 수 있음
      (sendPort) async {
        int isolateCount = 0;
        while (isolateCount &amp;lt; 300) {
          isolateCount++;
          sendPort.send(isolateCount.toString());
          sleep(const Duration(milliseconds: 10));
        }
      },

      /// 앞서 생성한 sendPort 전달
      receivePort.sendPort,
    );
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: left;&quot;&gt;참조 문서 &lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;a href=&quot;https://dart.dev/language/isolates&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://dart.dev/language/isolates&lt;/a&gt;&lt;/p&gt;</description>
      <category>Flutter/Dart 문법</category>
      <category>dart isolate</category>
      <category>FLUTTER</category>
      <category>flutter isolate</category>
      <category>flutter multiple thread</category>
      <category>flutter thread</category>
      <category>isolate run</category>
      <category>isolate spawn</category>
      <author>jinhan38</author>
      <guid isPermaLink="true">https://jinhan38.tistory.com/180</guid>
      <comments>https://jinhan38.tistory.com/180#entry180comment</comments>
      <pubDate>Thu, 9 May 2024 18:40:13 +0900</pubDate>
    </item>
    <item>
      <title>[Flutter] AnimatedCrossFade widget</title>
      <link>https://jinhan38.tistory.com/179</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 포스팅에서는 AnimatedCrossFade 위젯에 대해 알아보겠습니다. AnimatedCrossFade는 두 위젯을 애니메이션 사용해서 바꿀 수 있도록 도와줍니다. 컬러나 텍스트 뿐만 아니라 사이즈 변경까지도 자동으로 애니메이션을 적용시킬 수 있기 때문에 매우 유용한 위젯입니다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;최종 동영상&lt;/b&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignLeft&quot; data-video-host=&quot;kakaotv&quot; data-video-url=&quot;https://tv.kakao.com/v/446559872&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/eNng4/hyVZj4fZKs/Tf8k7CAcx0S7kRN2kNSR4K/img.jpg?width=886&amp;amp;height=1920&amp;amp;face=0_0_886_1920,https://scrap.kakaocdn.net/dn/LygZA/hyVZodq54I/Q8zCPIhsOA94eUJlrSDEeK/img.jpg?width=886&amp;amp;height=1920&amp;amp;face=0_0_886_1920&quot; data-video-width=&quot;400&quot; data-video-height=&quot;867&quot; data-video-origin-width=&quot;860&quot; data-video-origin-height=&quot;1864&quot; data-ke-mobilestyle=&quot;widthContent&quot; data-video-play-service=&quot;daum_tistory&quot; data-original-url=&quot;&quot; data-video-title=&quot;&quot;&gt;&lt;iframe src=&quot;https://play-tv.kakao.com/embed/player/cliplink/446559872?service=daum_tistory&quot; width=&quot;400&quot; height=&quot;867&quot; frameborder=&quot;0&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption style=&quot;display: none;&quot;&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;first와 second 위젯을 지정해준 후에 crossFadeState 값에 어떤 위젯을 보여줄지 결정해주면 됩니다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;아래 예제에서는 first라는 변수로 구분해주고 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;간단하게 사용할 수 있으니 부가적인 설명은 생략했습니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1715144580200&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class Home extends StatefulWidget {
  const Home({super.key});

  @override
  State&amp;lt;Home&amp;gt; createState() =&amp;gt; _HomeState();
}

class _HomeState extends State&amp;lt;Home&amp;gt; {
  bool first = true;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: SafeArea(
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.center,
          children: [
            ElevatedButton(
                onPressed: () {
                  setState(() {
                    first = !first;
                  });
                },
                child: const Text(&quot;변경&quot;)),
            Expanded(
              child: AnimatedCrossFade(
                firstChild: Container(
                  width: 300,
                  height: 300,
                  color: Colors.red,
                  child: const Center(
                    child: Text(
                      &quot;첫 번째 widget&quot;,
                      style: TextStyle(fontSize: 20, color: Colors.white),
                    ),
                  ),
                ),
                secondChild: Container(
                  width: 400,
                  height: 400,
                  color: Colors.blue,
                  child: const Center(
                    child: Text(
                      &quot;두 번째 widget&quot;,
                      style: TextStyle(fontSize: 20, color: Colors.white),
                    ),
                  ),
                ),
                crossFadeState: first
                    ? CrossFadeState.showFirst
                    : CrossFadeState.showSecond,
                duration: const Duration(milliseconds: 1500),
                // reverseDuration: const Duration(milliseconds: 3000),

                // firstCurve: Curves.fastOutSlowIn,

                // secondCurve: Curves.elasticInOut,

                // sizeCurve: Curves.slowMiddle,
              ),
            ),
          ],
        ),
      ),
    );
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Flutter/Flutter widget</category>
      <category>animatedcrossfade</category>
      <category>FLUTTER</category>
      <category>flutter animatedcrossfade</category>
      <category>flutter widget</category>
      <author>jinhan38</author>
      <guid isPermaLink="true">https://jinhan38.tistory.com/179</guid>
      <comments>https://jinhan38.tistory.com/179#entry179comment</comments>
      <pubDate>Wed, 8 May 2024 14:05:29 +0900</pubDate>
    </item>
    <item>
      <title>[Android] Android에서 SharedPreference 사용하면서 발생할 수 있는 오류</title>
      <link>https://jinhan38.tistory.com/178</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최근 Kotlin으로 만든 Android Native앱을 업데이트 한 후 기존에는 없던 오류가 발생하는 일이 생겼습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원인은 SharedPreference에 저장한 데이터의 형식이 변경됐기 때문입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존에 apple이라는 key로 int의 값을 저장했습니다. 업데이트 후에는 apple이라는 key의 타입을 Long으로 변경해서 사용했습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발을 할 땐 앱을 삭제 후 새로 설치하기 때문에 문제가 되는 것은 없었습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 일반 사용자들이 앱을 삭제하거나 데이터를 삭제하지 않고, 바로 업데이트해서 사용하는 경우 SharedPreference에 저장한 apple이라는 값의 type이 달라져서 오류가 발생했습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;운영하면서 경험해볼 수 있는 오류였습니다.&lt;/p&gt;</description>
      <category>Android</category>
      <category>Android</category>
      <category>android error</category>
      <category>sharedpreference error</category>
      <author>jinhan38</author>
      <guid isPermaLink="true">https://jinhan38.tistory.com/178</guid>
      <comments>https://jinhan38.tistory.com/178#entry178comment</comments>
      <pubDate>Wed, 3 Apr 2024 13:58:39 +0900</pubDate>
    </item>
    <item>
      <title>[Android] 접근성 서비스(AccessibilityService) - Click Event</title>
      <link>https://jinhan38.tistory.com/177</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;이번 포스팅에서는 Android의 접근성 서비스(AccessibilityService)에 대해 알아보겠습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;접근성 서비스는 기본적으로 장애가 있는 사용자를 위한 서비스입니다. 하지만 디바이스의 화면을 컨트롤 하기 위해 많이 사용되곤 합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Accessibility&amp;nbsp;services&amp;nbsp;should&amp;nbsp;only&amp;nbsp;be&amp;nbsp;used&amp;nbsp;to&amp;nbsp;assist&amp;nbsp;users&amp;nbsp;with&amp;nbsp;disabilities&amp;nbsp;in&amp;nbsp;using&amp;nbsp;Android&amp;nbsp;devices&amp;nbsp;and&amp;nbsp;apps&lt;br /&gt;&lt;a href=&quot;https://developer.android.com/reference/android/accessibilityservice/AccessibilityService&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://developer.android.com/reference/android/accessibilityservice/AccessibilityService&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1711257960836&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;AccessibilityService &amp;nbsp;|&amp;nbsp; Android Developers&quot; data-og-description=&quot;&quot; data-og-host=&quot;developer.android.com&quot; data-og-source-url=&quot;https://developer.android.com/reference/android/accessibilityservice/AccessibilityService&quot; data-og-url=&quot;https://developer.android.com/reference/android/accessibilityservice/AccessibilityService&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bBKlmZ/hyVDBDmAVL/8u7zHTu9I9CZv5TBPeEpuk/img.png?width=1201&amp;amp;height=676&amp;amp;face=0_0_1201_676&quot;&gt;&lt;a href=&quot;https://developer.android.com/reference/android/accessibilityservice/AccessibilityService&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://developer.android.com/reference/android/accessibilityservice/AccessibilityService&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bBKlmZ/hyVDBDmAVL/8u7zHTu9I9CZv5TBPeEpuk/img.png?width=1201&amp;amp;height=676&amp;amp;face=0_0_1201_676');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;AccessibilityService &amp;nbsp;|&amp;nbsp; Android Developers&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;developer.android.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. AccessibilityService 클래스 생성&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. Manifest 추가&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. accessibility_service_config 작성&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. runtime 권한 획득&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5. Click Gesture Event 발생&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;1.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;AccessibilityService 클래스 생성&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;AccessibilityService 클래스를 상속받은 클래스를 생성해주세요.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드에서는 3개의 함수를 override 했습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;onAccessibilityEvent 함수는 사용자가 화면을 터치 했을 때 진입하는 함수입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;onStartCommand 함수는 MyAccessibilityService 클래스로 intent를 사용해서 데이터를 전달할 때 호출됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;onInterrupt 함수는 해당 서비스가 종료되면 호출됩니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1711258098169&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import android.accessibilityservice.AccessibilityService
import android.accessibilityservice.GestureDescription
import android.content.Intent
import android.graphics.Path
import android.view.accessibility.AccessibilityEvent

class MyAccessibilityService : AccessibilityService() {

    // 화면 터치 이벤트 발생 시 호출됨
    override fun onAccessibilityEvent(event: AccessibilityEvent?) {
    }

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
    // 뒤에서 구현 예정
        return super.onStartCommand(intent, flags, startId)
    }

    // 서비스 중단 시 호출
    override fun onInterrupt() {
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. Manifest 추가&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Manifest에 service 태그를 추가해주세요.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따로 추가할 uses-permission은 없습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;meta-data의 resource에 accessibility_service_config를 사용해야 합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1711258235626&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;service
    android:name=&quot;.MyAccessibilityService&quot;
    android:exported=&quot;true&quot;
    android:permission=&quot;android.permission.BIND_ACCESSIBILITY_SERVICE&quot;&amp;gt;
    &amp;lt;intent-filter&amp;gt;
        &amp;lt;action android:name=&quot;android.accessibilityservice.AccessibilityService&quot; /&amp;gt;
    &amp;lt;/intent-filter&amp;gt;
    &amp;lt;meta-data
        android:name=&quot;android.accessibilityservice&quot;
        android:resource=&quot;@xml/accessibility_service_config&quot; /&amp;gt;
&amp;lt;/service&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. accessibility_service_config 작성&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;res/xml/ 경로에&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;accessibility_service_config.xml 파일을 만들겠습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 특성 중 canPerformGestures 값을 true로 설정해야 Gesture 이벤트를 발생시킬 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1711258355705&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&amp;gt;
&amp;lt;accessibility-service xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;
    android:accessibilityEventTypes=&quot;typeAllMask&quot;
    android:accessibilityFeedbackType=&quot;feedbackAllMask&quot;
    android:canPerformGestures=&quot;true&quot;
    android:canRetrieveWindowContent=&quot;true&quot;
    android:description=&quot;@string/accessibility_service_description&quot; /&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. runtime 권한 획득&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;runtime에서 접근성 권한을 획득해야 합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 접근성 권한이 있는지 체크해줍니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1711258865380&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/**
 * 접근성 권한 있는지 체크 
 */
private fun isAccessibilityServiceEnabled(context: Context, service: Class&amp;lt;out AccessibilityService&amp;gt;): Boolean {
    val expectedComponentName = ComponentName(context, service)

    val enabledServicesSetting = Settings.Secure.getString(
        context.contentResolver,
        Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES
    ) ?: return false

    val colonSplitter = TextUtils.SimpleStringSplitter(':')
    colonSplitter.setString(enabledServicesSetting)

    while (colonSplitter.hasNext()) {
        val componentNameString = colonSplitter.next()
        val enabledComponentName = ComponentName.unflattenFromString(componentNameString)
        if (enabledComponentName != null &amp;amp;&amp;amp; enabledComponentName == expectedComponentName)
            return true
    }

    return false
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;권한이 없을 경우 접근성 권한을 활성화 시킬 수 있는 설정 화면으로 이동시켜줘야 합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1711258903178&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt; startActivity(Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS))&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5. Click Gesture Event 발생(접근성 권한 승인 이후 진행)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드로 Click 이벤트를 발생시켜보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 MyAccessibilityService를 수정하겠습니다. 앞선 코드와는 다르게 onStartCommand에서 intent의 데이터를 받고 clickScreen 함수를 호출하고 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1711258596753&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import android.accessibilityservice.AccessibilityService
import android.accessibilityservice.GestureDescription
import android.content.Intent
import android.graphics.Path
import android.view.accessibility.AccessibilityEvent

class MyAccessibilityService : AccessibilityService() {

    // 화면 터치 이벤트 발생 시 호출됨
    override fun onAccessibilityEvent(event: AccessibilityEvent?) {
    }

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        if (intent?.action == &quot;touch&quot;) {
            val x = intent.getIntExtra(&quot;x&quot;, 0)
            val y = intent.getIntExtra(&quot;y&quot;, 0)
            clickScreen(x.toFloat(), y.toFloat())
        }
        return super.onStartCommand(intent, flags, startId)
    }

    /**
     * 화면 클릭
     */
    private fun clickScreen(x: Float, y: Float, startTime: Long = 100, duration: Long = 100) {
        val gestureBuilder = GestureDescription.Builder()
        val path = Path()
        path.moveTo(x, y)
        gestureBuilder.addStroke(GestureDescription.StrokeDescription(path, startTime, duration))
        val gesture = gestureBuilder.build()
        dispatchGesture(gesture, object : GestureResultCallback() {
            override fun onCompleted(gestureDescription: GestureDescription?) {
                super.onCompleted(gestureDescription)
                // 화면 터치 제스처 실행 완료
            }

            override fun onCancelled(gestureDescription: GestureDescription?) {
                super.onCancelled(gestureDescription)
                // 화면 터치 제스처 실행 취소
            }
        }, null)
    }

    // 서비스 중단 시 호출
    override fun onInterrupt() {
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;onStartCommand 함수에 데이터를 전달하기 위해서는 intent를 만들어서 startService를 호출해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;action을 touch로 세팅한 후 x, y 값을 추가해줬습니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1711258707425&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;val intentTouch = Intent(this, MyAccessibilityService::class.java)
intentTouch.action = &quot;touch&quot;
intentTouch.putExtra(&quot;x&quot;, 100)
intentTouch.putExtra(&quot;y&quot;, 100)
startService(intentTouch)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Android</category>
      <category>AccessibilityService</category>
      <category>Android</category>
      <category>안드로이드 접근성 서비스</category>
      <author>jinhan38</author>
      <guid isPermaLink="true">https://jinhan38.tistory.com/177</guid>
      <comments>https://jinhan38.tistory.com/177#entry177comment</comments>
      <pubDate>Sun, 24 Mar 2024 14:42:34 +0900</pubDate>
    </item>
    <item>
      <title>[Android] Hex 투명도 표</title>
      <link>https://jinhan38.tistory.com/176</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 99.8837%; height: 578px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 20px;&quot;&gt;&lt;b&gt;HEX&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 20px;&quot;&gt;&lt;b&gt;투명도&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 20px;&quot;&gt;&lt;b&gt;HEX&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 20px;&quot;&gt;&lt;b&gt;투명도&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 20px;&quot;&gt;&lt;b&gt;HEX&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 20px;&quot;&gt;&lt;b&gt;투명도&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 20px;&quot;&gt;&lt;b&gt;HEX&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 20px;&quot;&gt;&lt;b&gt;투명도&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 362px;&quot; colspan=&quot;2&quot; rowspan=&quot;20&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;00&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;0%&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;4F&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;31%&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;92&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;62%&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;ED&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;93%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;03&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;1%&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;52&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;32%&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;A1&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;63%&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;F0&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;94%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;05&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;2%&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;54&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;33%&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;A3&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;64%&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;F2&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;95%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;08&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;3%&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;57&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;34%&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;A6&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;65%&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;F5&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;96%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;0A&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;4%&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;59&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;35%&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;A8&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;66%&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;F7&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;97%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;0D&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;5%&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;5C&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;36%&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;AB&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;67%&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;FA&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;98%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;0F&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;6%&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;5E&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;37%&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;AD&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;68%&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;FC&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;99%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;12&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;7%&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;61&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;38%&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;B0&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;69%&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;FF&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;100%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;14&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;8%&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;63&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;39%&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;B3&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;70%&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;17&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;9%&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;66&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;40%&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;B5&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;71%&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;1A&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;10%&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;69&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;41%&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;B8&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;72%&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;1C&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;11%&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;6B&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;42%&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;BA&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;73%&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;1F&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;12%&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;6E&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;43%&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;BD&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;74%&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;21&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;13%&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;70&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;44%&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;BF&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;75%&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;24&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;14%&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;73&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;45%&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;C2&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;76%&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;26&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;15%&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;75&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;46%&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;C4&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;77%&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;29&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;16%&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;78&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;47%&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;C7&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;78%&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;2B&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;17%&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;7A&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;48%&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;C9&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;79%&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;2E&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;18%&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;7D&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;49%&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;CC&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;80%&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;30&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;19%&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;80&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;50%&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;CF&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;81%&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;height: 18px;&quot; colspan=&quot;2&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;33&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;20%&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;82&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;51%&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;D1&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;82%&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;height: 18px;&quot; colspan=&quot;2&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;36&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;21%&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;85&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;52%&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;D4&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;83%&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;height: 18px;&quot; colspan=&quot;2&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;28&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;22%&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;87&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;53%&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;D6&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;84%&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;height: 18px;&quot; colspan=&quot;2&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;3B&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;23%&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;8A&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;54%&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;D9&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;85%&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;height: 18px;&quot; colspan=&quot;2&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;3D&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;24%&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;8C&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;55%&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;DB&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;86%&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;height: 18px;&quot; colspan=&quot;2&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;40&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;25%&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;8F&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;56%&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;DE&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;87%&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;height: 18px;&quot; colspan=&quot;2&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;42&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;26%&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;91&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;57%&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;E0&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;88%&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;height: 18px;&quot; colspan=&quot;2&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;45&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;27%&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;94&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;58%&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;E3&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;89%&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;height: 18px;&quot; colspan=&quot;2&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;47&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;28%&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;96&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;59%&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;E6&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;90%&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;height: 18px;&quot; colspan=&quot;2&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;4A&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;29%&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;99&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;60%&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;E8&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;91%&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;height: 18px;&quot; colspan=&quot;2&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;4D&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;30%&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;9C&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;61%&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;EB&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;92%&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 10%; text-align: center; height: 18px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 1.16279%; height: 18px;&quot; colspan=&quot;2&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;</description>
      <category>Android</category>
      <category>android hex</category>
      <category>HEX</category>
      <category>hex transparent</category>
      <category>hex 투명도</category>
      <category>hex 투명도 표</category>
      <author>jinhan38</author>
      <guid isPermaLink="true">https://jinhan38.tistory.com/176</guid>
      <comments>https://jinhan38.tistory.com/176#entry176comment</comments>
      <pubDate>Fri, 15 Mar 2024 20:08:08 +0900</pubDate>
    </item>
    <item>
      <title>[Android] Overlay View 그리기</title>
      <link>https://jinhan38.tistory.com/175</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Android에서 Overlay 기능이 필요할 때가 있습니다. 예를 들어 네비게이션 앱처럼 앱을 백그라운드 상태로 두더라도 화면에 특정 View를 보여주는 것입니다. 이럴 때 사룔하는 것이 Overlay 기능입니다. 다른 앱 위에 우리가 설정한 View를 보여주고, 특정 로직을 수행할 수 있습니다. 이를 위해서는 권한 요청과 Service 클래스를 구현해야 합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Overlay를 사용할 때 주의할 것은 앱을 종료시키더라도 Overlay가 같이 종료되는 것은 아니라는 점입니다. 때문에 비즈니스 로직에 따라서 어떠한 경우에 overlay를 종료시킬지, 계속 살려둘지를 잘 설정해야 합니다. 또한 Service만 종료시키는 것이 아니라 View들도 직접 제거해줘야 합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예제에서 구현한 기능&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. Overlay 권한 획득&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. Overlay Service 호출&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. OverlayService에서 View 추가 및 사이즈 변경&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. Overlay 종료 프로세스&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;kakaotv&quot; data-video-url=&quot;https://tv.kakao.com/v/445230124&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/c2RG4Z/hyVxq3kioz/Mpg08S3CcwBmKBdRvRWke0/img.jpg?width=886&amp;amp;height=1920&amp;amp;face=0_0_886_1920,https://scrap.kakaocdn.net/dn/bYeO1C/hyVxCQdGaQ/zDcKzk0VeKJikmyPk3wJa0/img.jpg?width=886&amp;amp;height=1920&amp;amp;face=0_0_886_1920&quot; data-video-width=&quot;860&quot; data-video-height=&quot;1864&quot; data-video-origin-width=&quot;860&quot; data-video-origin-height=&quot;1864&quot; data-ke-mobilestyle=&quot;widthContent&quot; data-video-play-service=&quot;daum_tistory&quot; data-original-url=&quot;&quot; data-video-title=&quot;&quot;&gt;&lt;iframe src=&quot;https://play-tv.kakao.com/embed/player/cliplink/445230124?service=daum_tistory&quot; width=&quot;860&quot; height=&quot;1864&quot; frameborder=&quot;0&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption style=&quot;display: none;&quot;&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자세한 내용은 코드에 주석으로 추가했습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1710211758999&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&amp;gt;
&amp;lt;manifest xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;
    xmlns:tools=&quot;http://schemas.android.com/tools&quot;&amp;gt;

    &amp;lt;uses-permission android:name=&quot;android.permission.SYSTEM_ALERT_WINDOW&quot; /&amp;gt;

    &amp;lt;application
       
       ... 
       
        &amp;lt;activity
            android:name=&quot;.OverlayActivity&quot;
            android:exported=&quot;true&quot; &amp;gt;
            &amp;lt;intent-filter&amp;gt;
                &amp;lt;action android:name=&quot;android.intent.action.MAIN&quot; /&amp;gt;

                &amp;lt;category android:name=&quot;android.intent.category.LAUNCHER&quot; /&amp;gt;
            &amp;lt;/intent-filter&amp;gt;
        &amp;lt;/activity&amp;gt;

        &amp;lt;service
            android:name=&quot;.OverlayBoxService&quot;
            android:enabled=&quot;true&quot;
            android:exported=&quot;true&quot;
            android:permission=&quot;android.permission.SYSTEM_ALERT_WINDOW&quot; /&amp;gt;
            
    &amp;lt;/application&amp;gt;

&amp;lt;/manifest&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OverlayActivity&lt;/p&gt;
&lt;pre id=&quot;code_1710211603926&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class OverlayActivity : ComponentActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_overlay)
        findViewById&amp;lt;Button&amp;gt;(R.id.bt_show_overlay).setOnClickListener {
            checkOverlayPermission()
        }
    }

    /**
     * 권한 요청
     */
    private fun checkOverlayPermission() {
        if (!Settings.canDrawOverlays(this)) {
            val intent = Intent(
                Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
                Uri.parse(&quot;package:$packageName&quot;)
            )
            activityResultLauncher.launch(intent)
        } else {
            showOverlay()
        }
    }

    /**
     * 권한 요청 리스너
     */
    private val activityResultLauncher: ActivityResultLauncher&amp;lt;Intent&amp;gt; = registerForActivityResult(
        ActivityResultContracts.StartActivityForResult()
    ) {
        if (Settings.canDrawOverlays(this)) {
            showOverlay()
        }
    }

    /**
     * Overlay 서비스 호출
     */
    private fun showOverlay() {
        val serviceIntent = Intent(this, OverlayBoxService::class.java)
        startService(serviceIntent)
    }

    /**
     * 앱을 종료시킬 때 Service도 같이 종료시키는 로직
     */
    override fun onDestroy() {
        val serviceIntent = Intent(this, OverlayBoxService::class.java)
        stopService(serviceIntent)
        super.onDestroy()
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;activity_overlay&lt;/p&gt;
&lt;pre id=&quot;code_1710211630467&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&amp;gt;
&amp;lt;androidx.constraintlayout.widget.ConstraintLayout xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;
    xmlns:app=&quot;http://schemas.android.com/apk/res-auto&quot;
    xmlns:tools=&quot;http://schemas.android.com/tools&quot;
    android:layout_width=&quot;match_parent&quot;
    android:layout_height=&quot;match_parent&quot;
    tools:context=&quot;.OverlayActivity&quot;&amp;gt;

    &amp;lt;Button
        android:id=&quot;@+id/bt_show_overlay&quot;
        android:layout_width=&quot;match_parent&quot;
        android:layout_height=&quot;60dp&quot;
        android:text=&quot;Show Overlay&quot;
        android:layout_marginBottom=&quot;15dp&quot;
        app:layout_constraintBottom_toBottomOf=&quot;parent&quot;
        app:layout_constraintEnd_toEndOf=&quot;parent&quot;
        app:layout_constraintStart_toStartOf=&quot;parent&quot; /&amp;gt;

&amp;lt;/androidx.constraintlayout.widget.ConstraintLayout&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OverlayBoxService&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;api레벨에 따른 분기처리도 추가했습니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1710211815818&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import android.app.Service
import android.content.Context
import android.content.Intent
import android.graphics.Color
import android.graphics.PixelFormat
import android.os.Build
import android.os.IBinder
import android.util.DisplayMetrics
import android.view.Gravity
import android.view.View
import android.view.WindowManager
import android.widget.Button


class OverlayBoxService : Service() {
    companion object {
        private const val TAG = &quot;OverlayBoxService&quot;
    }

    override fun onBind(p0: Intent?): IBinder? {
        return null
    }


    private lateinit var windowManager: WindowManager

    private var screenWidth = 0
    private var screenHeight = 0
    private var flag: Int = 0

    private lateinit var boxView: View
    private lateinit var changeButton: Button
    private lateinit var closeButton: Button

    override fun onCreate() {
        super.onCreate()
        windowManager = getSystemService(WINDOW_SERVICE) as WindowManager
        getScreenSize()
        setParamFlag()
        setBoxView()
        setSizeChangeButton()
        setCloseOverlayButton()
    }

    /**
     * api 레벨에 따라서 화면 사이즈 불러오기
     */
    private fun getScreenSize() {
        if (Build.VERSION.SDK_INT &amp;gt;= Build.VERSION_CODES.R) {
            val screenRect = windowManager.currentWindowMetrics.bounds
            screenWidth = screenRect.right
            screenHeight = screenRect.bottom
        } else {
            val metrics = DisplayMetrics()
            windowManager.defaultDisplay.getMetrics(metrics)
            screenWidth = metrics.widthPixels
            screenHeight = metrics.heightPixels
        }
    }

    /**
     * api 레벨에 따라서 flag 설정
     */
    private fun setParamFlag() {
        flag = if (Build.VERSION.SDK_INT &amp;gt;= Build.VERSION_CODES.O) {
            WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
        } else {
            WindowManager.LayoutParams.TYPE_SYSTEM_ALERT
        }
    }

    /**
     * BoxView 세팅
     */
    private fun setBoxView() {
        boxView = View(this)
        boxView.setBackgroundColor(Color.BLUE)
        val params = WindowManager.LayoutParams(
            screenWidth / 2,
            screenHeight / 3,
            flag,
            WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
            PixelFormat.TRANSLUCENT
        )
        params.gravity = Gravity.CENTER
        windowManager.addView(boxView, params)

    }

    /**
     * BoxView 사이즈 변경하는 버튼 세팅
     */
    private fun setSizeChangeButton() {
        changeButton = Button(this)
        changeButton.text = &quot;사이즈 변경&quot;
        changeButton.setOnClickListener {
            changeSize(screenWidth / 5, screenHeight / 5)
        }
        val params = WindowManager.LayoutParams(
            screenWidth,
            dpToPx(60, this),
            flag,
            WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
            PixelFormat.TRANSLUCENT
        )
        params.y = dpToPx(75, this)
        params.gravity = Gravity.BOTTOM or Gravity.START
        windowManager.addView(changeButton, params)
    }

    /**
     * Overlay 종료 버튼
     */
    private fun setCloseOverlayButton() {
        closeButton = Button(this)
        closeButton.text = &quot;종료&quot;
        closeButton.setOnClickListener {
            removeViews()
            stopSelf()
        }
        val params = WindowManager.LayoutParams(
            screenWidth,
            dpToPx(60, this),
            flag,
            WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
            PixelFormat.TRANSLUCENT
        )
        params.y = dpToPx(135, this)
        params.gravity = Gravity.BOTTOM or Gravity.START
        windowManager.addView(closeButton, params)

    }

    /**
     * dp를 px 사이즈로 변경
     */
    private fun dpToPx(dp: Int, context: Context): Int {
        val density = context.resources.displayMetrics.density
        return (dp * density + 0.5f).toInt()
    }

    /**
     * BoxView 사이즈 변경
     */
    private fun changeSize(width: Int, height: Int) {
        val params = WindowManager.LayoutParams(
            width,
            height,
            flag,
            WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
            PixelFormat.TRANSLUCENT
        )
        windowManager.updateViewLayout(boxView, params)

    }

    /**
     * OverlayActivity에서 onDestroy 가 호출 될 때 Service를 종료시키는 로직을 추가했습니다.
     * Service를 종료시킬 때 view들을 제거해줘야 완전히 화면에서 사라집니다.
     */
    override fun onDestroy() {
        removeViews()
        super.onDestroy()
    }

    private fun removeViews() {
        if (boxView.parent != null) windowManager.removeView(boxView)
        if (changeButton.parent != null) windowManager.removeView(changeButton)
        if (closeButton.parent != null) windowManager.removeView(closeButton)
    }

}&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Android</category>
      <category>Android</category>
      <category>Android Overlay</category>
      <category>overlay</category>
      <author>jinhan38</author>
      <guid isPermaLink="true">https://jinhan38.tistory.com/175</guid>
      <comments>https://jinhan38.tistory.com/175#entry175comment</comments>
      <pubDate>Tue, 12 Mar 2024 11:59:16 +0900</pubDate>
    </item>
  </channel>
</rss>