<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>jaewon_p.log</title>
        <link>https://velog.io/</link>
        <description></description>
        <lastBuildDate>Tue, 16 Jul 2024 04:18:42 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>jaewon_p.log</title>
            <url>https://velog.velcdn.com/images/jaewon_p/profile/5d6be904-b19e-465c-ba45-91677726044b/image.jpeg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. jaewon_p.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/jaewon_p" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[Android 64bit 대응(갤럭시 24)]]></title>
            <link>https://velog.io/@jaewon_p/Android-64bit-%EB%8C%80%EC%9D%91%EA%B0%A4%EB%9F%AD%EC%8B%9C-24</link>
            <guid>https://velog.io/@jaewon_p/Android-64bit-%EB%8C%80%EC%9D%91%EA%B0%A4%EB%9F%AD%EC%8B%9C-24</guid>
            <pubDate>Tue, 16 Jul 2024 04:18:42 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>인수인계를 받고 처음으로 안드로이드 스튜디오를 켜서 레거시 코드를 읽고 있었다. 인수인계를 하시면서 처음으로 나한테 물어본 질문은 64비트 호환에 대해 알고 있으시냐는 질문이었다. 64비트에 관한 이슈가 있어 해결을 해야 했다.</p>
</blockquote>
<p>64비트에 관한 내용을 찾아보니 갤럭시 s24부터 64bit앱만 지원한다고 한다. 회사 앱이 예전부터 만든 앱이다보니 32bit 앱이라고 생각한다.</p>
<p>그래서 64bit로 컨버전 작업이 필요했다.</p>
<ol>
<li>SDK버전 확인
 64bit를 호환하려면 minSDK버전을 21로 올려야 호환이 가능했다.</li>
<li>라이브러리 확인
 앱에서 사용하는 라이브러리 중 오래된 버전은 32bit만 지원할 수 있다.<ul>
<li>라이브러리를 확인하는 방법
<img src="https://velog.velcdn.com/images/jaewon_p/post/9cf1e7d3-b8f1-44aa-8e32-58792207603d/image.png" alt="">
설치된 라이브러리 중 32bit만 호환 되는지 확인하고 버전 업그레이드를 해주면 된다.</li>
</ul>
</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[Java→Kotlin 전환해야 하는 이유]]></title>
            <link>https://velog.io/@jaewon_p/JavaKotlin-%EC%A0%84%ED%99%98%ED%95%B4%EC%95%BC-%ED%95%98%EB%8A%94-%EC%9D%B4%EC%9C%A0</link>
            <guid>https://velog.io/@jaewon_p/JavaKotlin-%EC%A0%84%ED%99%98%ED%95%B4%EC%95%BC-%ED%95%98%EB%8A%94-%EC%9D%B4%EC%9C%A0</guid>
            <pubDate>Thu, 02 May 2024 07:20:46 GMT</pubDate>
            <description><![CDATA[<h3 id="java와-상호-운용-가능">Java와 상호 운용 가능</h3>
<h3 id="정적-타입의-언어">정적 타입의 언어</h3>
<ul>
<li>컴파일 시점에 객체의 타입이 결정되며 런타임 시점에 문제가 발생할 확률이 훨씬 더 적어 상대적으로 안전한 언어라고 할 수 있다</li>
<li>타입 추론<ul>
<li>Kotlin은 기본적으로 변수가 선언될 때 변수의 타입을 명시하지 않더라도 할당된 값을 보고 어떤 자료형을 가지는지 추론해 줍니다. 타입 추론 덕분에 반복적인 코드량을 줄일 수 있다</li>
</ul>
</li>
<li>간결한 언어<ul>
<li>data class를 사용하여 필드, Getter/Setter, 생성자를 자동 생성함으로써 보일러플레이트 코드가 개선된다</li>
</ul>
</li>
<li>Null safe 언어<ul>
<li>null 체크를 좀 더 간결하게 nullable변수에서 null safe (?.)로 호출 하거나 엘비스 연산자를 통해 null일 때에 대한 처리를 직접적으로 바로 처리할 수 있습니다.</li>
</ul>
</li>
<li>코루틴<ul>
<li>스레드는 아니지만 일종의 가벼운 스레드로 동시성 작업을 간편하게 처리할수 있다</li>
</ul>
</li>
<li>확장함수<ul>
<li>기존 클래스를 상속하지 않고도 새 함수로 확장할 수 있으므로 코드 재사용 및 기능 추가가 더 쉬워진다</li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA['오늘여행' 유저테스트 피드백 분석]]></title>
            <link>https://velog.io/@jaewon_p/%EC%98%A4%EB%8A%98%EC%97%AC%ED%96%89-%EC%9C%A0%EC%A0%80%ED%85%8C%EC%8A%A4%ED%8A%B8-%ED%94%BC%EB%93%9C%EB%B0%B1-%EB%B6%84%EC%84%9D</link>
            <guid>https://velog.io/@jaewon_p/%EC%98%A4%EB%8A%98%EC%97%AC%ED%96%89-%EC%9C%A0%EC%A0%80%ED%85%8C%EC%8A%A4%ED%8A%B8-%ED%94%BC%EB%93%9C%EB%B0%B1-%EB%B6%84%EC%84%9D</guid>
            <pubDate>Mon, 08 Apr 2024 09:47:42 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>현재 운영중인 &#39;오늘여행&#39; 앱을 배포한 후 유저테스트를 진행하였다. 앱을 사용하면서 편리하고 좋은 점과 불편한점을 피드백으로 받게되어 수정할 내용을 더 명확하게 알 수 있었다. 피드백 받은 내용을 정리해 보려고 한다.</p>
</blockquote>
<h3 id="설문내용">설문내용</h3>
<table>
<thead>
<tr>
<th><img src="https://velog.velcdn.com/images/jaewon_p/post/c4615e7f-c4a7-4617-88e2-473154251306/image.png" ></th>
<th><img src="https://velog.velcdn.com/images/jaewon_p/post/f08c883a-6a73-4bbf-979a-3a8d500cbc02/image.png"></th>
</tr>
</thead>
</table>
<table>
<thead>
<tr>
<th><img src="https://velog.velcdn.com/images/jaewon_p/post/894c3c5b-bb7a-4ac0-81ec-f5c22509750a/image.png"></th>
<th><img src="https://velog.velcdn.com/images/jaewon_p/post/d6ef3b62-8469-452f-a16b-ba10f48af897/image.png"></th>
</tr>
</thead>
</table>
<h3 id="긍정적인-답변">긍정적인 답변</h3>
<ul>
<li>UI와 UX가 사용자 친화적이라 좋았습니다. </li>
<li>설명을 듣지 않아도 바로 사용이 가능하다는 게 좋았습니다. </li>
<li>가고 싶은 지역의 관광지나 카페, 음식점을 경로에 지정해두고 사진을 저장하여 추억을 꺼내 볼 수 있다는 점이 좋습니다👍👍</li>
<li>UI, UX에 신경을 많이 썼다는 느낌이 있어요, 부드러운 ui와 직관성 있는 접근성이 마음에 들었습니다</li>
</ul>
<h3 id="부정적인-답변">부정적인 답변</h3>
<ul>
<li>행사날짜(기간, 시작일, 종료일)의 가독성이 떨어집니다.</li>
<li>단순히 사진 저장용인거 같아서 조금 아쉬웠습니다.</li>
<li>목적지를 봤을 때 좀 더 자세한 정보가 있으면 좋겠어요.</li>
</ul>
<h3 id="추가하면-좋을-기능">추가하면 좋을 기능</h3>
<ul>
<li>여행 후기나 느낀점 같은걸 개인적으로 작성할 수 있는 란이 있으면 더 좋을 수도 있겠다는 생각이 들었어요!</li>
<li>검색할 수 있는 기능이 있었으면 좋겠습니다.</li>
<li>여행 기록 부분에서 메모를 할 수 있으면 좋을 거 같습니다.</li>
<li>해당 경로를 지정했을 때 거리 측정. </li>
</ul>
<h3 id="마무리-느낀점">마무리 느낀점</h3>
<blockquote>
<p>앱을 기획 단계부터 개발을 거쳐 배포까지 모든 과정에 참여하면서 앱을 만드는데 조금 더 알아가는 경험이 된 것 같다. 처음 만드는 과정이지만 내가 개발하기 편하게 하기 위해서가 아닌 사용자가 사용했을 때 조금이라도 더 편하고 유용하게 쓰였으면 하는 마음에 UX 적으로 많이 고민을 한 것 같다.
유저 테스트에 피드백을 받고 나서 그동안 고민했던 노력이 인정받는 느낌이 들었다. 아직 많이 실력도 부족하고 고쳐야 할 것도 많지만 포기하지 않고 끝까지 해보려고 해야겠다.</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[[kotlin] 화면 크기의 맞는 Gridlayout 열 구하기]]></title>
            <link>https://velog.io/@jaewon_p/kotlin-%ED%99%94%EB%A9%B4-%ED%81%AC%EA%B8%B0%EC%9D%98-%EB%A7%9E%EB%8A%94-Gridlayout-%EC%97%B4-%EA%B5%AC%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@jaewon_p/kotlin-%ED%99%94%EB%A9%B4-%ED%81%AC%EA%B8%B0%EC%9D%98-%EB%A7%9E%EB%8A%94-Gridlayout-%EC%97%B4-%EA%B5%AC%ED%95%98%EA%B8%B0</guid>
            <pubDate>Tue, 19 Mar 2024 10:18:13 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>내배캠 최종 프로젝트를 진행하면서 여행을 다녀온 곳에 저장된 사진을 gridlayout으로 출력하는 것을 구현하게 되었다.</p>
</blockquote>
<pre><code class="language-kotlin">val layoutManager = GridLayoutManager(requireContext(), 2)
    recyclerView.layoutManager = layoutManager</code></pre>
<blockquote>
<p>처음에는 이런식으로 열을 2로 고정을 하여 gridlayout을 구현하고 있었다. 이러한 방식으로 구현을 하다보니 다른 애뮬레이터에서 테스트를 해봤을 때 간혹 좌우 폭이 넓어 빈 공간이 생겨 보기 좋지 않은 결과물이 되었다. 그래서 애뮬레이터 마다 좌우 넓이 값을 찾고 그에 맞는 grid item 수를 구현해 보려고 한다.</p>
</blockquote>
<h3 id="넓이-구하기">넓이 구하기</h3>
<pre><code class="language-kotlin">private fun getDisplayWidth(): Int {
    val displayMetrics = DisplayMetrics()
    requireActivity().windowManager.defaultDisplay.getMetrics(displayMetrics)
    return displayMetrics.widthPixels
}</code></pre>
<ul>
<li>애뮬레이터의 좌우 넓이를 구하기 위해 변수 displayMetrics에 DisplayMetrics()함수를 사용했다.</li>
<li>return 값으로 단말의 좌우 폭의 값으로 주었다.</li>
</ul>
<h3 id="개수-계산하기">개수 계산하기</h3>
<pre><code class="language-kotlin">private fun calColumns(columnWidthDp: Int): Int {
        val displayWidth = getDisplayWidth()
        val numColumns = displayWidth / columnWidthDp
        return if (numColumns &lt;= 1) 2 else numColumns
    }</code></pre>
<p>변수 displayWidth에 구한 넓이를 넣어주고 (단말의 넓이 / 지정한 넓이)를 해주어 알맞는 gridlayout의 수를 구해 return을 해주었다.</p>
<h3 id="gridlayout-적용하기">Gridlayout 적용하기</h3>
<pre><code class="language-kotlin">private fun setupRecyclerView(recyclerView: RecyclerView) {
        val columnWidthDp = 400
        val layoutManager = GridLayoutManager(requireContext(), calColumns(columnWidthDp))
        recyclerView.layoutManager = layoutManager
    }</code></pre>
<p>변수 columnWidthDp에 원하는 넓이의 값을 주고 계산해서 단말의 알맞는 열의 개수를 구하는 것을 구현하였다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Android] viewPager 좌우 스와이프 비활성화]]></title>
            <link>https://velog.io/@jaewon_p/Android-viewPager-%EC%A2%8C%EC%9A%B0-%EC%8A%A4%EC%99%80%EC%9D%B4%ED%94%84-%EB%A7%89%EA%B8%B0</link>
            <guid>https://velog.io/@jaewon_p/Android-viewPager-%EC%A2%8C%EC%9A%B0-%EC%8A%A4%EC%99%80%EC%9D%B4%ED%94%84-%EB%A7%89%EA%B8%B0</guid>
            <pubDate>Mon, 18 Mar 2024 06:23:41 GMT</pubDate>
            <description><![CDATA[<pre><code class="language-kotlin">binding.vpViewpagerMain.isUserInputEnabled = false
</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[기상청 단기 날씨 API(kotlin)]]></title>
            <link>https://velog.io/@jaewon_p/%EA%B8%B0%EC%83%81%EC%B2%AD-%EB%8B%A8%EA%B8%B0-%EB%82%A0%EC%94%A8-APIkotlin</link>
            <guid>https://velog.io/@jaewon_p/%EA%B8%B0%EC%83%81%EC%B2%AD-%EB%8B%A8%EA%B8%B0-%EB%82%A0%EC%94%A8-APIkotlin</guid>
            <pubDate>Mon, 11 Mar 2024 11:52:34 GMT</pubDate>
            <description><![CDATA[<h3 id="weatherinterface">WeatherInterface</h3>
<pre><code class="language-kotlin">import retrofit2.Call
import retrofit2.http.GET
import retrofit2.http.Query


interface WeatherInterface {
    // getVilageFcst : 동네 예보 조회
    @GET(&quot;getVilageFcst?serviceKey=API키&quot;)

    fun getWeather(@Query(&quot;dataType&quot;) dataType : String,
                   @Query(&quot;numOfRows&quot;) numOfRows : Int,
                   @Query(&quot;pageNo&quot;) pageNo : Int,
                   @Query(&quot;base_date&quot;) baseDate : String,
                   @Query(&quot;base_time&quot;) baseTime : String,
                   @Query(&quot;nx&quot;) nx : String,
                   @Query(&quot;ny&quot;) ny : String)
            : Call&lt;weather&gt;
}
</code></pre>
<h3 id="weatherclient">WeatherClient</h3>
<pre><code class="language-kotlin">import com.google.gson.GsonBuilder
import com.twoday.todaytrip.BuildConfig
import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import java.util.concurrent.TimeUnit

object WeatherClient {
    private const val WEATHER_BASE_URL = &quot;http://apis.data.go.kr/1360000/VilageFcstInfoService_2.0/&quot;
    private val gson = GsonBuilder().setLenient().create()
    private fun createOkHttpClient(): OkHttpClient {
        val interceptor = HttpLoggingInterceptor()

        if (BuildConfig.DEBUG)
            interceptor.level = HttpLoggingInterceptor.Level.BODY
        else
            interceptor.level = HttpLoggingInterceptor.Level.NONE

        return OkHttpClient.Builder()
            .connectTimeout(20, TimeUnit.SECONDS)
            .readTimeout(20, TimeUnit.SECONDS)
            .writeTimeout(20, TimeUnit.SECONDS)
            .addNetworkInterceptor(interceptor)
            .build()
    }

    private val weatherRetrofit = Retrofit.Builder()
        .baseUrl(WEATHER_BASE_URL).addConverterFactory(GsonConverterFactory.create())
        .client(createOkHttpClient())
        .build()

    val weatherNetWork: WeatherInterface = weatherRetrofit.create(WeatherInterface::class.java)
}</code></pre>
<h3 id="weatherdto">WeatherDTO</h3>
<pre><code class="language-kotlin">package com.twoday.todaytrip.weather

import com.google.gson.annotations.SerializedName

data class weather(
    @SerializedName(&quot;response&quot;)
    val response: Response
)
data class Response(
    @SerializedName(&quot;body&quot;)
    val body: Body,
    @SerializedName(&quot;header&quot;)
    val header: Header
)
data class Header(
    @SerializedName(&quot;resultCode&quot;)
    val resultCode: String,
    @SerializedName(&quot;resultMsg&quot;)
    val resultMsg: String
)
data class Body(
    @SerializedName(&quot;dataType&quot;)
    val dataType: String,
    @SerializedName(&quot;items&quot;)
    val items: Items,
    @SerializedName(&quot;numOfRows&quot;)
    val numOfRows: Int,
    @SerializedName(&quot;pageNo&quot;)
    val pageNo: Int,
    @SerializedName(&quot;totalCount&quot;)
    val totalCount: Int
)
data class Items(
    @SerializedName(&quot;item&quot;)
    val item: List&lt;Item&gt;
)
data class Item(
    @SerializedName(&quot;baseDate&quot;)
    val baseDate: String,
    @SerializedName(&quot;baseTime&quot;)
    val baseTime: String,
    @SerializedName(&quot;category&quot;)
    val category: String,
    @SerializedName(&quot;fcstDate&quot;)
    val fcstDate: String,
    @SerializedName(&quot;fcstTime&quot;)
    val fcstTime: String,
    @SerializedName(&quot;fcstValue&quot;)
    val fcstValue: String,
    @SerializedName(&quot;nx&quot;)
    val nx: Int,
    @SerializedName(&quot;ny&quot;)
    val ny: Int
)</code></pre>
<h3 id="weatherinfo">weatherInfo()</h3>
<pre><code class="language-kotlin">WeatherClient.weatherNetWork.getWeather(
            dataType = &quot;JSON&quot;,
            numOfRows = 12,
            pageNo = 1,
            baseDate = baseDate,
            baseTime = baseTime,
            nx = latitude,
            ny = longitude
        ).enqueue(object : retrofit2.Callback&lt;weather&gt; {
            override fun onResponse(call: Call&lt;weather&gt;, response: Response&lt;weather&gt;) {
                if (response.isSuccessful) {
                    val it: List&lt;Item&gt; = response.body()?.response?.body?.items?.item ?: return
                    var temp = &quot;&quot;
                    var sky = &quot;&quot;
                    var rainType = &quot;&quot;

                    it.forEach { item -&gt;
                        when (item.category) {
                            &quot;SKY&quot; -&gt; sky = item.fcstValue
                            &quot;TMP&quot; -&gt; temp = item.fcstValue
                            &quot;PTY&quot; -&gt; rainType = item.fcstValue
                        }
                    }
                    Log.d(&quot;iop&quot;, sky)
                    setWeather(rainType, sky, temp)
                }
            }

            override fun onFailure(call: Call&lt;weather&gt;, t: Throwable) {
                Log.d(&quot;api fail&quot;, t.message.toString())
            }
        })</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[Velog 동영상 첨부 방법(github ReadMe 가능)]]></title>
            <link>https://velog.io/@jaewon_p/Velog-%EB%8F%99%EC%98%81%EC%83%81-%EC%B2%A8%EB%B6%80-%EB%B0%A9%EB%B2%95github-ReadMe-%EA%B0%80%EB%8A%A5</link>
            <guid>https://velog.io/@jaewon_p/Velog-%EB%8F%99%EC%98%81%EC%83%81-%EC%B2%A8%EB%B6%80-%EB%B0%A9%EB%B2%95github-ReadMe-%EA%B0%80%EB%8A%A5</guid>
            <pubDate>Fri, 08 Mar 2024 10:44:52 GMT</pubDate>
            <description><![CDATA[<h3 id="동영상-넣는-방법">동영상 넣는 방법</h3>
<ol>
<li>영상찍기<pre><code>&#39;command&#39; + &#39;shift&#39; + &#39;5&#39; = 동영상 캡처</code></pre></li>
<li>.mov파일 .gif 변환<blockquote>
<p><a href="https://convertio.co/kr/mov-gif/">https://convertio.co/kr/mov-gif/</a></p>
</blockquote>
</li>
</ol>
<p>이 사이트에서 영상 변환</p>
<p>3.변환된 .gif파일 복붙</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Velog 이미지 크기, 정렬]]></title>
            <link>https://velog.io/@jaewon_p/Velog-%EC%9D%B4%EB%AF%B8%EC%A7%80-%ED%81%AC%EA%B8%B0-%EC%A0%95%EB%A0%AC</link>
            <guid>https://velog.io/@jaewon_p/Velog-%EC%9D%B4%EB%AF%B8%EC%A7%80-%ED%81%AC%EA%B8%B0-%EC%A0%95%EB%A0%AC</guid>
            <pubDate>Fri, 08 Mar 2024 10:35:55 GMT</pubDate>
            <description><![CDATA[<h3 id="이미지-크기-조절">이미지 크기 조절</h3>
<pre><code>&lt;img src=&quot;이미지 링크&quot; height=&quot;100px&quot; width=&quot;300px&quot;&gt;</code></pre><h3 id="이미지-정렬">이미지 정렬</h3>
<h4 id="가운데-정렬">가운데 정렬</h4>
<pre><code>&lt;p align=&quot;center&quot;&gt;&lt;img src=&quot;이미지 링크&quot;&gt;&lt;/p&gt;</code></pre><h4 id="왼쪽-오른쪽-정렬">왼쪽, 오른쪽 정렬</h4>
<pre><code>&lt;img src=&quot;이미지 링크&quot; align=&quot;right or left&quot;&gt;</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[CollapsingToolbarLayout]]></title>
            <link>https://velog.io/@jaewon_p/CollapsingToolbarLayout</link>
            <guid>https://velog.io/@jaewon_p/CollapsingToolbarLayout</guid>
            <pubDate>Fri, 08 Mar 2024 10:32:21 GMT</pubDate>
            <description><![CDATA[<h3 id="완성화면">완성화면</h3>
<p align="center"><img src="https://velog.velcdn.com/images/jaewon_p/post/7b47c084-1d4f-4341-ac2f-988e1813e93e/image.gif" width="100"></p>

<h3 id="xml구조">xml구조</h3>
<pre><code class="language-xml">&lt;CoordinatorLayout&gt;   &lt;&lt; CollapsingToolbarLayout을 사용하기 위해서는 꼭 CoordinatorLayout을 사용해야함


    &lt;AppBarLayout&gt;   &lt;&lt; appBarLayout 안에 CollapsingToolbarLayout을 넣음

        &lt;CollapsingToolbarLayout&gt;   &lt;&lt; 스크롤시 접히거나 나타날 부분을 넣는 layout

            &lt;ImageView/&gt;            &lt;&lt; 접히거나 나타날 이미지
            ...
            &lt;Toolbar/&gt;              &lt;&lt; 접혔을 때에도 남아있을 툴바

        &lt;/CollapsingToolbarLayout&gt;

    &lt;/AppBarLayout&gt;

    &lt;ViewPager2/&gt;   &lt;&lt; 스크롤할 뷰 (NestedScrollView나, RecyclerView도 가능)

&lt;/CoordinatorLayout&gt;</code></pre>
<h3 id="상세설명">상세설명</h3>
<p>CoordinatorLayout : FrameLayout의 특징을 갖는 레이아웃
behavior는 스크롤, 드래그, 스와이드, 플링 등, 뷰의 다양한 움직임이나 애니메이션에 따른 상호작용을 구현하기 위해 사용된다.</p>
<h4 id="layout_scrollflags">layout_scrollFlags</h4>
<ul>
<li>scroll : 사용자의 스크롤에 따라, 이 뷰가 화면에서 사라질 수 있음을 나타낸다. 이 값이 설정되지 않으면 이 뷰는 화면 위쪽에 항상 남아 있다.</li>
<li>exitUntilCollapsed : 이 값이 설정되면 위쪽으로 스크롤하는 동안 minHeight에 도달할 때까지만 이 뷰가 사라진다. 그리고 스크롤 방향이 변결될 때까지는 minHeight 지점에 남아 있는다.</li>
<li>enterAlwaysCollapsed : enterAlways와 유사하지만 아래쪽으로 스크롤하는 경우만 다르다. 즉, 아래쪽으로 스크롤할 때 스크롤되는 리스트의 끝에 도달했을 때만 이 뷰가 다시 나타난다. <h3 id="전체-코드">전체 코드</h3>
</li>
</ul>
<pre><code class="language-xml">&lt;androidx.coordinatorlayout.widget.CoordinatorLayout 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;&gt;

    &lt;com.google.android.material.appbar.AppBarLayout
        android:layout_width=&quot;match_parent&quot;
        android:layout_height=&quot;wrap_content&quot;&gt;

        &lt;com.google.android.material.appbar.CollapsingToolbarLayout
            android:layout_width=&quot;match_parent&quot;
            android:layout_height=&quot;wrap_content&quot;
            app:contentScrim=&quot;@color/main_blue&quot;
            app:layout_scrollFlags=&quot;scroll|exitUntilCollapsed&quot;&gt;

            &lt;!-- parallax: 스크롤 시 접힘 --&gt;
            &lt;androidx.constraintlayout.widget.ConstraintLayout
                android:layout_width=&quot;match_parent&quot;
                android:layout_height=&quot;match_parent&quot;&gt;

                &lt;!-- pin: 스크롤 시 고정 --&gt;
                &lt;androidx.appcompat.widget.AppCompatImageView
                    android:id=&quot;@+id/iv_local&quot;
                    android:layout_width=&quot;match_parent&quot;
                    android:layout_height=&quot;250dp&quot;
                    android:scaleType=&quot;centerCrop&quot;
                    android:src=&quot;@drawable/img_busan1&quot;
                    app:layout_collapseMode=&quot;parallax&quot;
                    app:layout_constraintBottom_toBottomOf=&quot;parent&quot;
                    app:layout_constraintEnd_toEndOf=&quot;parent&quot;
                    app:layout_constraintStart_toStartOf=&quot;parent&quot;
                    app:layout_constraintTop_toTopOf=&quot;parent&quot; /&gt;

                &lt;androidx.appcompat.widget.AppCompatImageView
                    android:id=&quot;@+id/iv_place_list_fragment_blur&quot;
                    android:layout_width=&quot;match_parent&quot;
                    android:layout_height=&quot;wrap_content&quot;
                    android:scaleType=&quot;fitXY&quot;
                    android:src=&quot;@drawable/img_place_detail_blur&quot;
                    app:layout_constraintEnd_toEndOf=&quot;parent&quot;
                    app:layout_constraintStart_toStartOf=&quot;parent&quot;
                    app:layout_constraintTop_toTopOf=&quot;parent&quot; /&gt;

                &lt;androidx.constraintlayout.widget.ConstraintLayout
                    android:layout_width=&quot;wrap_content&quot;
                    android:layout_height=&quot;wrap_content&quot;
                    android:layout_marginTop=&quot;8dp&quot;
                    android:layout_marginEnd=&quot;8dp&quot;
                    app:layout_constraintEnd_toEndOf=&quot;parent&quot;
                    app:layout_constraintTop_toTopOf=&quot;parent&quot;&gt;

                    &lt;ImageView
                        android:id=&quot;@+id/img_weather&quot;
                        android:layout_width=&quot;33dp&quot;
                        android:layout_height=&quot;37dp&quot;
                        android:layout_marginStart=&quot;12dp&quot;
                        android:src=&quot;@drawable/ic_place_list_weather_sun&quot;
                        app:layout_constraintBottom_toBottomOf=&quot;parent&quot;
                        app:layout_constraintStart_toStartOf=&quot;parent&quot;
                        app:layout_constraintTop_toTopOf=&quot;parent&quot; /&gt;

                    &lt;TextView
                        android:id=&quot;@+id/tv_weather_info&quot;
                        android:layout_width=&quot;wrap_content&quot;
                        android:layout_height=&quot;wrap_content&quot;
                        android:layout_marginStart=&quot;12dp&quot;
                        android:text=&quot;온도º&quot;
                        android:textColor=&quot;@color/white&quot;
                        android:textSize=&quot;16sp&quot;
                        app:layout_constraintBottom_toBottomOf=&quot;@+id/img_weather&quot;
                        app:layout_constraintStart_toEndOf=&quot;@+id/img_weather&quot;
                        app:layout_constraintTop_toTopOf=&quot;@+id/img_weather&quot; /&gt;

                    &lt;TextView
                        android:id=&quot;@+id/tv_weather_info2&quot;
                        android:layout_width=&quot;wrap_content&quot;
                        android:layout_height=&quot;wrap_content&quot;
                        android:layout_marginStart=&quot;12dp&quot;
                        android:text=&quot;날씨&quot;
                        android:textColor=&quot;@color/white&quot;
                        android:textSize=&quot;16sp&quot;
                        app:layout_constraintBottom_toBottomOf=&quot;@+id/img_weather&quot;
                        app:layout_constraintStart_toEndOf=&quot;@+id/tv_weather_info&quot;
                        app:layout_constraintTop_toTopOf=&quot;@+id/img_weather&quot; /&gt;
                &lt;/androidx.constraintlayout.widget.ConstraintLayout&gt;
            &lt;/androidx.constraintlayout.widget.ConstraintLayout&gt;

            &lt;androidx.appcompat.widget.Toolbar
                android:id=&quot;@+id/toolbar&quot;
                android:layout_width=&quot;match_parent&quot;
                android:layout_height=&quot;?attr/actionBarSize&quot;
                app:layout_collapseMode=&quot;pin&quot;
                app:layout_constraintEnd_toEndOf=&quot;parent&quot;
                app:layout_constraintStart_toStartOf=&quot;parent&quot;
                app:layout_constraintTop_toTopOf=&quot;parent&quot;&gt;

                &lt;TextView
                    android:id=&quot;@+id/tv_travel_address&quot;
                    android:layout_width=&quot;wrap_content&quot;
                    android:layout_height=&quot;wrap_content&quot;
                    android:layout_marginStart=&quot;16dp&quot;
                    android:text=&quot;경상남도 거제시&quot;
                    android:textAppearance=&quot;@style/address&quot; /&gt;
            &lt;/androidx.appcompat.widget.Toolbar&gt;

        &lt;/com.google.android.material.appbar.CollapsingToolbarLayout&gt;

        &lt;!-- 뷰페이저와 연결할 탭 --&gt;
        &lt;androidx.constraintlayout.widget.ConstraintLayout
            android:id=&quot;@+id/layout_place_list_fragment&quot;
            android:layout_width=&quot;match_parent&quot;
            android:layout_height=&quot;match_parent&quot;
            app:layout_constraintBottom_toBottomOf=&quot;parent&quot;
            app:layout_constraintEnd_toEndOf=&quot;parent&quot;
            app:layout_constraintStart_toStartOf=&quot;parent&quot;&gt;


            &lt;com.google.android.material.tabs.TabLayout
                android:id=&quot;@+id/tl_tab_layout&quot;
                android:layout_width=&quot;match_parent&quot;
                android:layout_height=&quot;55dp&quot;
                app:layout_constraintEnd_toEndOf=&quot;parent&quot;
                app:layout_constraintStart_toStartOf=&quot;parent&quot;
                app:layout_constraintTop_toTopOf=&quot;parent&quot;
                app:tabBackground=&quot;@color/white&quot;
                app:tabGravity=&quot;fill&quot;
                app:tabIndicatorColor=&quot;@color/main_blue&quot;
                app:tabIndicatorFullWidth=&quot;false&quot;
                app:tabIndicatorGravity=&quot;bottom&quot;
                app:tabMode=&quot;fixed&quot;
                app:tabSelectedTextColor=&quot;@color/black&quot;
                app:tabTextAppearance=&quot;@style/tab_text&quot;
                app:tabTextColor=&quot;@color/middle_gray&quot;&gt;

                &lt;com.google.android.material.tabs.TabItem
                    android:layout_width=&quot;wrap_content&quot;
                    android:layout_height=&quot;wrap_content&quot;
                    android:text=&quot;@string/place_list_tourist_attraction&quot; /&gt;

                &lt;com.google.android.material.tabs.TabItem
                    android:layout_width=&quot;wrap_content&quot;
                    android:layout_height=&quot;wrap_content&quot;
                    android:text=&quot;@string/place_list_restaurant&quot; /&gt;

                &lt;com.google.android.material.tabs.TabItem
                    android:layout_width=&quot;wrap_content&quot;
                    android:layout_height=&quot;wrap_content&quot;
                    android:text=&quot;@string/place_list_cafe&quot; /&gt;

                &lt;com.google.android.material.tabs.TabItem
                    android:layout_width=&quot;wrap_content&quot;
                    android:layout_height=&quot;wrap_content&quot;
                    android:text=&quot;@string/place_list_event&quot; /&gt;
            &lt;/com.google.android.material.tabs.TabLayout&gt;
        &lt;/androidx.constraintlayout.widget.ConstraintLayout&gt;

    &lt;/com.google.android.material.appbar.AppBarLayout&gt;

    &lt;androidx.viewpager2.widget.ViewPager2
        android:id=&quot;@+id/vp_viewpager_main&quot;
        android:layout_width=&quot;match_parent&quot;
        android:layout_height=&quot;match_parent&quot;
        android:background=&quot;@color/white&quot;
        app:layout_behavior=&quot;@string/appbar_scrolling_view_behavior&quot; /&gt;

&lt;/androidx.coordinatorlayout.widget.CoordinatorLayout&gt;</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[Button 보라색 색상고정 현상]]></title>
            <link>https://velog.io/@jaewon_p/Button-%EB%B3%B4%EB%9D%BC%EC%83%89-%EC%83%89%EC%83%81%EA%B3%A0%EC%A0%95-%ED%98%84%EC%83%81</link>
            <guid>https://velog.io/@jaewon_p/Button-%EB%B3%B4%EB%9D%BC%EC%83%89-%EC%83%89%EC%83%81%EA%B3%A0%EC%A0%95-%ED%98%84%EC%83%81</guid>
            <pubDate>Wed, 28 Feb 2024 10:50:06 GMT</pubDate>
            <description><![CDATA[<p>안드로이드를 시작하고 많은 사람들이 겪는 상황일 것이라 생각한다. 버튼을 만든 후 배경색을 바꾸려 할 때 drawable이 적용되지 않는 현상이다.
이를 해결하기 위해 2가지 방법이 있다.</p>
<h3 id="appcompatbutton-사용">AppCompatButton 사용</h3>
<pre><code class="language-xml">&lt;androidx.appcompat.widget.AppCompatButton
        android:id=&quot;@+id/button&quot;
        android:layout_width=&quot;44dp&quot;
        android:layout_height=&quot;28dp&quot;
        android:layout_marginBottom=&quot;4dp&quot;
        android:background=&quot;@drawable/button_round&quot;
        android:text=&quot;@string/contain&quot;
        android:textColor=&quot;@color/white&quot;
        android:textSize=&quot;12sp&quot;
        android:textStyle=&quot;bold&quot;
        app:layout_constraintBottom_toBottomOf=&quot;parent&quot;
        app:layout_constraintEnd_toEndOf=&quot;parent&quot; /&gt;</code></pre>
<p>그냥 <code>&lt;Button&gt;</code>을 만들지 말고 androidx.appcompat.widget.AppCompatButton 으로 지정해주면 background 속성이 잘 적용된다.</p>
<h3 id="theme-변경">Theme 변경</h3>
<pre><code class="language-xml">&lt;style name=&quot;Base.Theme.TodayTrip&quot; parent=&quot;Theme.MaterialComponents.DayNight.NoActionBar&quot;&gt;</code></pre>
<p>⬇︎</p>
<pre><code class="language-xml">&lt;style name=&quot;Base.Theme.TodayTrip&quot; parent=&quot;Theme.AppCompat.DayNight.NoActionBar&quot;&gt;</code></pre>
<p>변경해주면 속성변경이 가능하다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[TabLayout] tab indicator 길이]]></title>
            <link>https://velog.io/@jaewon_p/TabLayout-tab-indicator-%EA%B8%B8%EC%9D%B4</link>
            <guid>https://velog.io/@jaewon_p/TabLayout-tab-indicator-%EA%B8%B8%EC%9D%B4</guid>
            <pubDate>Tue, 27 Feb 2024 11:34:48 GMT</pubDate>
            <description><![CDATA[<p>TabLayout - Tab Indicator 길이 조절</p>
<h3 id="tab의-indicator길이를-text에-맞추기">Tab의 Indicator길이를 Text에 맞추기</h3>
<pre><code>app:tabIndicatorFullWidth=&quot;false&quot;</code></pre><h3 id="tab의-indicator길이를-tab전체-길이에-맞추기">Tab의 Indicator길이를 Tab전체 길이에 맞추기</h3>
<pre><code>app:tabIndicatorFullWidth=&quot;true&quot;</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[Loading Dialog]]></title>
            <link>https://velog.io/@jaewon_p/Loading-Dialog</link>
            <guid>https://velog.io/@jaewon_p/Loading-Dialog</guid>
            <pubDate>Fri, 02 Feb 2024 11:57:04 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>무언가 작업을 할 때 로딩이 되는 화면을 구현해 보도록 하겠다.</p>
</blockquote>
<h3 id="circleprogressdialog">CircleProgressDialog</h3>
<pre><code class="language-kotlin">package com.example.searchapi.fragment

import android.graphics.Color
import android.graphics.drawable.ColorDrawable
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.DialogFragment
import com.example.searchapi.databinding.FragmentDialogBinding

class CircleProgressDialog : DialogFragment() {
    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?,
    ): View {
        val binding = FragmentDialogBinding.inflate(inflater, container, false)
        dialog?.window?.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))

        return binding.root
    }
}</code></pre>
<h3 id="fragment_dialogxml">fragment_dialog.xml</h3>
<pre><code class="language-xml">&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;
&lt;FrameLayout 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;.fragment.CircleProgressDialog&quot;&gt;

    &lt;ProgressBar
        android:id=&quot;@+id/circle_progress&quot;
        android:layout_width=&quot;300dp&quot;
        android:layout_height=&quot;300dp&quot;
        android:indeterminateDrawable=&quot;@drawable/circle_progress&quot;
        android:indeterminateDuration=&quot;1000&quot;
        android:padding=&quot;10dp&quot;
        app:layout_constraintBottom_toBottomOf=&quot;parent&quot;
        app:layout_constraintEnd_toEndOf=&quot;parent&quot;
        app:layout_constraintStart_toStartOf=&quot;parent&quot;
        app:layout_constraintTop_toTopOf=&quot;parent&quot; /&gt;
&lt;/FrameLayout&gt;</code></pre>
<h3 id="drawable-circle_progressxml">Drawable circle_progress.xml</h3>
<pre><code class="language-xml">&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;
&lt;rotate xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;
    android:fromDegrees=&quot;0&quot;
    android:pivotX=&quot;50%&quot;
    android:pivotY=&quot;50%&quot;
    android:toDegrees=&quot;360&quot; &gt;
    &lt;shape
        android:innerRadiusRatio=&quot;3&quot;
        android:shape=&quot;ring&quot;
        android:thicknessRatio=&quot;8&quot;
        android:useLevel=&quot;false&quot;&gt;

        &lt;size
            android:width=&quot;10dp&quot;
            android:height=&quot;60dp&quot;/&gt;
        &lt;gradient
            android:angle=&quot;0&quot;
            android:endColor=&quot;#FF9800&quot;
            android:startColor=&quot;@color/white&quot;
            android:type=&quot;sweep&quot;
            android:useLevel=&quot;false&quot;
            /&gt;
    &lt;/shape&gt;
&lt;/rotate&gt;</code></pre>
<h3 id="code">code</h3>
<pre><code class="language-kotlin">binding.rvSearch.addOnScrollListener(object : RecyclerView.OnScrollListener() {
    override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
        super.onScrolled(recyclerView, dx, dy)
        val query = binding.etSearch.text.toString()
        val lastVisibleItemPosition =
                    (recyclerView.layoutManager as GridLayoutManagerWrapper).findLastCompletelyVisibleItemPosition()
        val itemTotalCount = recyclerView.adapter!!.itemCount - 1
        if (lastVisibleItemPosition == itemTotalCount) {
            showLoading() //로딩
            page += 1 //스크롤이 최하단이면 다음페이지 출력
            CoroutineScope(Dispatchers.Main).launch {
                val response = Retrofit.api.searchImage(query, &quot;accuracy&quot;, page, 80)
                imageAdapter.imageList.addAll(response.documents)
                imageAdapter.notifyDataSetChanged()
            }
        }
    }
})</code></pre>
<pre><code class="language-kotlin">private fun showLoading() {
    CoroutineScope(Dispatchers.Main).launch {
        loadingDialog.show(parentFragmentManager, loadingDialog.tag)
        withContext(Dispatchers.Default) { delay(1500) }
        loadingDialog.dismiss()
    }
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[Fragment 교체 에러]]></title>
            <link>https://velog.io/@jaewon_p/Fragment-%EA%B5%90%EC%B2%B4-%EC%97%90%EB%9F%AC</link>
            <guid>https://velog.io/@jaewon_p/Fragment-%EA%B5%90%EC%B2%B4-%EC%97%90%EB%9F%AC</guid>
            <pubDate>Fri, 02 Feb 2024 11:43:10 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>API로 80개의 이미지를 불러온 후 스크롤이 끝까지 내려가면 새로운 아이템들을 불러오는 기능을 구현하고 있었고, 새로 불러오는 과정에서 CircleProgressDialog로 로딩 pogressbar를 띄우는 기능을 구현하려고 했다. 그 과정에서 에러가 발생했다.</p>
</blockquote>
<h3 id="에러내용">에러내용</h3>
<pre><code class="language-kotlin">private fun showLoading() {
    CoroutineScope(Dispatchers.Main).launch {
        loadingDialog.show(supportFragmentManager, loadingDialog.tag)
        withContext(Dispatchers.Default) { delay(1500) }
        loadingDialog.dismiss()
    }
}</code></pre>
<pre><code class="language-kotlin">Unresolved reference : supportFragmentManager</code></pre>
<ul>
<li>Host Activity인 MainActivity에서 프래그먼트를 교체하고 싶은데 Host Fragment인 Fragment에서 supportFragmentManager를 호출하였기 때문이다.<h3 id="에러해결">에러해결</h3>
<pre><code class="language-kotlin">private fun showLoading() {
  CoroutineScope(Dispatchers.Main).launch {
      loadingDialog.show(parentFragmentManager, loadingDialog.tag)
      withContext(Dispatchers.Default) { delay(1500) }
      loadingDialog.dismiss()
  }
}</code></pre>
</li>
<li>MainActivity에서의 동작으로 프래그먼트를 교체하고싶다면 supportFragmentManager를 사용해야한다.</li>
<li>MainActivity 위에 올라가 있는 프래그먼트에서의 동작으로 MainActivity 위의 프래그먼트를 교체하고 싶다면 parentFragmentManager를 사용해야 한다.</li>
<li>childFragmentManager의 경우 프래그먼트 위에 또 프래그먼트가 올라가는 상황에서 사용하게 되는데 다중프래그먼트의 경우 구현이 복잡해지기 때문에 가능하다면 custom view를 사용하는것을 권장한다.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/jaewon_p/post/49b1fc3b-b20a-41af-aeed-581fe51ca099/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[RecyclerView 업데이트 오류]]></title>
            <link>https://velog.io/@jaewon_p/RecyclerView-%EC%97%85%EB%8D%B0%EC%9D%B4%ED%8A%B8-%EC%98%A4%EB%A5%98</link>
            <guid>https://velog.io/@jaewon_p/RecyclerView-%EC%97%85%EB%8D%B0%EC%9D%B4%ED%8A%B8-%EC%98%A4%EB%A5%98</guid>
            <pubDate>Thu, 01 Feb 2024 11:21:26 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>API 통신을 통해 받아온 데이터들을 리사이클러뷰에 출력을 한 후 아이템 뷰를 클릭하면 보관함에 아이템을 보관하는 기능을 구현하고 있었다. 여러 개를 클릭 한 후 보관함 프레그먼트안에서 뿌려진 아이템을 다시 클릭하게 되면 삭제기능을 구현하려고 했는데 클릭한 순서대로 삭제를 하면 문제가 없는데 중간이나 처음에 추가한 아이템을 삭제하게 되면 에러가 발생했다.</p>
</blockquote>
<table>
<thead>
<tr>
<th><img src="https://velog.velcdn.com/images/jaewon_p/post/83a81045-152b-4d1e-bf49-b850fa884c24/image.png" alt=""></th>
<th><img src="https://velog.velcdn.com/images/jaewon_p/post/cbf34438-b9c2-4936-9f89-d14443cc23c1/image.png" alt=""></th>
</tr>
</thead>
<tbody><tr>
<td>### 오류내용</td>
<td></td>
</tr>
</tbody></table>
<ul>
<li><p><img src="https://velog.velcdn.com/images/jaewon_p/post/0a70d535-0c27-4451-b1ff-31105f9ed8f8/image.png" alt=""></p>
</li>
<li><p>데이터 변경에 대한 범위를 정산적으로 지정해주지 않아 생기는 것 같기도 하다.</p>
</li>
<li><p>또한 업데이트 함수 에러일 수도 있다고 생각했다.</p>
</li>
<li><p>더 찾아보니 삼성 안드로이드 기기에서 발생하는 RecyclerView Scroll 에러라고 한다.</p>
<h3 id="해결방법">해결방법</h3>
<pre><code class="language-kotlin">class GridLayoutManagerWrapper: GridLayoutManager {
  constructor(context: Context, spanCount: Int) : super(context, spanCount) {}
  constructor(context: Context, spanCount: Int, orientation: Int, reverseLayout: Boolean) : super(context, spanCount, orientation, reverseLayout) {}
  constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int, defStyleRes: Int) : super(context, attrs, defStyleAttr, defStyleRes) {}

  override fun supportsPredictiveItemAnimations(): Boolean {
      return false
  }
}</code></pre>
<pre><code class="language-kotlin">with(binding) {
  rvSearch.adapter = imageAdapter
  rvSearch.layoutManager = GridLayoutManagerWrapper(context, 2)
  }</code></pre>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Android - Floating Icon Color]]></title>
            <link>https://velog.io/@jaewon_p/Android-Floating-Icon-Color</link>
            <guid>https://velog.io/@jaewon_p/Android-Floating-Icon-Color</guid>
            <pubDate>Wed, 31 Jan 2024 11:42:42 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>나는 플로팅 버튼의 아이콘 색상을 검은색에서 하얀색으로 바꾸려고 한다.</p>
</blockquote>
<p>많은 구글링 결과 이 코드를 찾아냈다.</p>
<pre><code class="language-kotlin">app:tint=&quot;@color/white&quot;</code></pre>
<p>간단하지만 찾지 못하는 이유는 속성이 자동완성이 안돼서 직접 tint=&quot;&quot;까지 작성해야한다.</p>
<table>
<thead>
<tr>
<th><img src="https://velog.velcdn.com/images/jaewon_p/post/0d0b1b86-9593-44b0-8add-9b95be3f5633/image.png" alt=""></th>
<th><img src="https://velog.velcdn.com/images/jaewon_p/post/78f4f152-cba9-4783-a6dc-50797a7728f9/image.png" alt=""></th>
</tr>
</thead>
</table>
]]></description>
        </item>
        <item>
            <title><![CDATA[Android - Glide]]></title>
            <link>https://velog.io/@jaewon_p/Android-Glide</link>
            <guid>https://velog.io/@jaewon_p/Android-Glide</guid>
            <pubDate>Wed, 31 Jan 2024 11:28:02 GMT</pubDate>
            <description><![CDATA[<h3 id="glide">Glide</h3>
<ul>
<li>Glide는 구글에서 만든 이미지 로더 라이브러리다. </li>
<li>Glide의 with()는 Picasso와는 다르게 Context뿐 아니라 Activity와 Fragment도 인자로 사용할 수 있다.</li>
<li>Glide는 Picasso보다 메모리 용량을 적게 차지한다.(그 이유는 이미지를 다운로드 할 때 작은 이미지 사이즈로 변환해주기 때문이다.)</li>
<li>원본 이미지를 그대로 가져와야 한다면 Picasso를, 그렇지 않다면 Glide를 사용하는 것이 좋다.</li>
<li>이미지, gif, 비디오 등 다양한 API를 사용할 수 있다.</li>
</ul>
<h3 id="glide-사용방법">Glide 사용방법</h3>
<h4 id="dependency-추가">dependency 추가</h4>
<pre><code class="language-kotlin">implementation (&quot;com.github.bumptech.glide:glide:4.16.0&quot;)</code></pre>
<p>외부 통신을 통해 이미지를 가져와야 할 때, 인터넷 권한을 AndroidManifest.xml 파일에 추가해준다.</p>
<pre><code class="language-kotlin">&lt;uses-permission android:name=&quot;android.permission.INTERNET&quot;/&gt;</code></pre>
<ul>
<li><p>Activity</p>
<pre><code class="language-kotlin">Glide.with(this)
  .load(이미지 경로)
  .into(imageView)</code></pre>
<p><img src="https://velog.velcdn.com/images/jaewon_p/post/97171ff4-fe46-4b5b-a5be-0fcc7e6bfbc9/image.png" alt=""></p>
</li>
<li><p>ViewHolder</p>
<pre><code class="language-kotlin">Glide.with(itemView)
  .load(이미지 경로)
  .into(itemView.imageView)</code></pre>
<p><img src="https://velog.velcdn.com/images/jaewon_p/post/b89b3cb5-0e6e-457c-85ef-356d20f2a905/image.png" alt=""></p>
</li>
<li><p>Gif 이미지 로드</p>
<pre><code class="language-kotlin">Glide.with(this)
  .load(R.drawable.gif_file)
  .asGif()
  .into(imageView)</code></pre>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Android - Room]]></title>
            <link>https://velog.io/@jaewon_p/Android-Room</link>
            <guid>https://velog.io/@jaewon_p/Android-Room</guid>
            <pubDate>Wed, 24 Jan 2024 11:45:24 GMT</pubDate>
            <description><![CDATA[<h3 id="room">Room</h3>
<ul>
<li>SQLite를 쉽게 사용할 수 있는 데이터베이스 객체 매핑 라이브러리</li>
<li>쉽게 Query를 사용할 수 있는 API를 제공</li>
<li>Query를 컴파일 시간에 검증함</li>
<li>Query결과를 LiveData로하여 데이터베이스가 변경될 때 마다 쉽게 UI를 변경할 수 있음</li>
<li>SQLite 보다 Room을 사용할 것을 권장함<h3 id="장점">장점</h3>
</li>
<li>SQL 쿼리의 컴파일 시간 확인</li>
<li>반복적이고 오류가 발생하기 쉬운 상용구 코드를 최소화하는 편의 주석</li>
<li>간소화된 데이터베이스 이전 경로<h3 id="설정방법">설정방법</h3>
<pre><code class="language-kotlin">plugins {
  id &#39;kotlin-kapt&#39;
}
</code></pre>
</li>
</ul>
<p>dependencies {
    def room_version = &quot;2.5.1&quot;
    implementation &quot;androidx.room:room-runtime:$room_version&quot;
    annotationProcessor &quot;androidx.room:room-compiler:$room_version&quot;
    kapt &quot;androidx.room:room-compiler:$room_version&quot;
    // optional - Kotlin Extensions and Coroutines support for Room
    implementation &quot;androidx.room:room-ktx:$room_version&quot;
    // optional - Test helpers
    testImplementation &quot;androidx.room:room-testing:$room_version&quot;
}</p>
<pre><code>### Entity 정의
- 데이터 베이스의 테이블 행 역할을 한다.
- 반드시 PrimaryKey가 있어야 한다. (자동 생성은 autoGenerate = true 속성 추가)
- 기본적으로 클래스 이름이 테이블명이지만 따로 지정은 @Entity(tableName = &quot; &quot;)을 사용한다.
#### e.g. CREATE TABLE student_table (student_id INTEGER PRIMARY KEY, name TEXT NOT NULL);
```kotlin
@Entity(tableName = &quot;student_table&quot;)    // 테이블 이름을 student_table로 지정함
data class Student (
    @PrimaryKey 
        @ColumnInfo(name = &quot;student_id&quot;) 
    val id: Int,
    val name: String
)</code></pre><h3 id="dao-정의">DAO 정의</h3>
<ul>
<li>인터페이스나 추상 클래스로 정의 가능.</li>
<li>일반적으로 인터페이스로 구현. @Dao 어노테이션 필수이다.</li>
<li>앱 데이터베이스의 데이터와 상호작용하는 메소드를 하나 이상 정의한다.</li>
<li>가능한 annotation으로 @Insert, @Update, @Delete, @Query가 있다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Android - SharedPreferences]]></title>
            <link>https://velog.io/@jaewon_p/Android-SharedPreferences</link>
            <guid>https://velog.io/@jaewon_p/Android-SharedPreferences</guid>
            <pubDate>Wed, 24 Jan 2024 11:34:40 GMT</pubDate>
            <description><![CDATA[<h3 id="sharedpreferences">SharedPreferences</h3>
<ul>
<li>간단한 데이터를 저장하고 불러올 수 있다.</li>
<li>앱을 꺼도 데이터가 유지된다는 점에서 간편한 데이터베이스 역할을 할 수 있다.<ul>
<li>초기 설정 값이나 자동 로그인 여부 등 간단한 값을 저장할 때 적합하다.</li>
</ul>
</li>
<li>ShardPreferences는 어플리케이션에서 파일 형태로 데이터를 저장한다.</li>
<li>데이터는 (key, value) 형태로 shared_prefs 폴더 안에 xml 파일로 저장된다. 해당 파일은 어플리케이션이 삭제되기 전까지 보존된다.</li>
</ul>
<h3 id="공유-환경설정의-핸들-가져오기">공유 환경설정의 핸들 가져오기</h3>
<ul>
<li><code>getSharedPreferences</code> (name, mode)<ul>
<li>여러개의 Shared Preference파일들을 사용하는 경우</li>
<li>name : 프레퍼런스 데이터를 저장할 XML 파일의 이름이다.</li>
<li>mode : 파일의 공유 모드<ul>
<li>MODE_PRIVATE: 생성된 XML 파일은 호출한 애플리케이션 내에서만 읽기 쓰기가 가능<pre><code class="language-kotlin">val sharedPref = activity?.getSharedPreferences(
getString(R.string.preference_file_key), Context.MODE_PRIVATE)</code></pre>
<img src="https://velog.velcdn.com/images/jaewon_p/post/97012229-58d8-402f-962f-d2facfa6e6e8/image.png" alt=""></li>
</ul>
</li>
</ul>
</li>
</ul>
<h3 id="getpreferences">getPreferences</h3>
<ul>
<li>한개의 Shared Preference 파일을 사용하는 경우 사용한다.</li>
<li>Activity 클래스에 정의된 메소드 이므로, Activity 인스턴스를 통해 접근이 가능하다.</li>
<li>생성한 액티비티 전용이므로 같은 패키지의 다른 액티비티는 읽을 수 없다.</li>
<li>액티비티와 동일한 이름의 XML 파일 생성<pre><code class="language-kotlin">val sharedPref = activity?.getPreferences(Context.MODE_PRIVATE)</code></pre>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Team KPT]]></title>
            <link>https://velog.io/@jaewon_p/Team-KPT</link>
            <guid>https://velog.io/@jaewon_p/Team-KPT</guid>
            <pubDate>Mon, 22 Jan 2024 11:50:28 GMT</pubDate>
            <description><![CDATA[<h2 id="팀-kpt">팀 KPT</h2>
<h3 id="keep--다음-프로젝트에서도-유지했으면-하는-부분">KEEP : 다음 프로젝트에서도 유지했으면 하는 부분</h3>
<ul>
<li><ol>
<li>프로젝트 만들고 각자 clone을 하기 전 gitignore 파일을 먼저 작성하여 미연에 혼선을 방지할 수 있었다.</li>
</ol>
</li>
<li><ol start="2">
<li>이전 프로젝트 경험을 회고하여 프로젝트 시작전 개선점을 찾아 반영한 점</li>
</ol>
</li>
<li><ol start="3">
<li>이슈가 있을 때마다 노션에 기록해 문제점을 잘 파악할 수 있었다.</li>
</ol>
</li>
<li><ol start="4">
<li>이슈가 생겼을 때 혼자 해결하기 보단, 다같이 해결하는 분위기가 좋았다.</li>
</ol>
</li>
<li><ol start="5">
<li>팀 룰과 코드 컨벤션 등을 디테일하게 노션에 정리해두어 협업에 더욱 도움이 되었다.</li>
</ol>
</li>
<li><ol start="6">
<li>발표 전에 회의가 진행될 툴로 카메라, 마이크, 화면공유 등 진행에 문제가 없는지 확인하여 원활한 발표를 할 수 있었다.</li>
</ol>
</li>
</ul>
<h3 id="problem--문제점을-객관적으로-판단">PROBLEM : 문제점을 객관적으로 판단</h3>
<ul>
<li><ol>
<li>일정 스코프를 명확히 하지 않은 것</li>
</ol>
</li>
<li><ol start="2">
<li>디자인과 프로젝트 초기 세팅이 너무 많은 시간을 소모한 것</li>
</ol>
</li>
<li><ol start="3">
<li>github의 사용법을 잘 숙지하지 못한 점</li>
</ol>
</li>
<li><ol start="4">
<li>코드 구현을 할 물리적인 시간이 부족하여 끝까지 마음이 조급했던 것 같다.</li>
</ol>
</li>
<li><ol start="5">
<li>디테일하게 ui와 코드를 수정하지 못했다.</li>
</ol>
</li>
<li><ol start="6">
<li>마무리(read.me 등)에 더 신경을 썼다면 좋았을 것 같다.</li>
</ol>
</li>
</ul>
<h3 id="try--다음-프로젝트를-위해-해야-할-노력--해결-할-수-있는-현실적인-방안-제시">TRY : 다음 프로젝트를 위해 해야 할 노력 / 해결 할 수 있는 현실적인 방안 제시</h3>
<ul>
<li><ol>
<li>전체 인원의 역량을 잘 파악하여 구체적 일정 스코프를 짠 뒤 진행한다</li>
</ol>
</li>
<li><ol start="2">
<li>이전 프로젝트의 코드 스니펫들을 모아 개발 시간을 단축한다</li>
</ol>
</li>
<li><ol start="3">
<li>사용자 경험 측면에서 사용하기 편한 디자인이 될 수 있도록 조금 더 고민해야겠다.</li>
</ol>
</li>
<li><ol start="4">
<li>시간 분배를 잘해서 급해지지 않도록 해야 한다.</li>
</ol>
</li>
<li><ol start="5">
<li>기능 구현이 어렵고 시간이 걸리더라도 디자인에 조금 더 신경을 써야 할 것 같다.</li>
</ol>
</li>
</ul>
<h3 id="feel-or-action--이번-프로젝트를-통해-느낀-점">Feel or Action : 이번 프로젝트를 통해 느낀 점</h3>
<ul>
<li><ol>
<li>알면 알수록 더 복잡해지고 어려워지는 것 같습니다. 반복하면서 더 몸에 익혀야겠다고 생각했습니다.</li>
</ol>
</li>
<li><ol start="2">
<li>경험을 기반으로 개발과 계획에 참고해야겠다.</li>
</ol>
</li>
<li><ol start="3">
<li>실력 차이가 나는 것이 느껴져도 위축되지 않고 더 열심히 해야겠다는 마음가짐을 가져야겠다.</li>
</ol>
</li>
<li><ol start="4">
<li>구현하는 데 어려움이 느껴진다면 고민하지 않고 팀원과 튜터님께 달려가야겠다.</li>
</ol>
</li>
<li><ol start="5">
<li>한정된 시간에서 원활한 진행을 위해 소통을 더 적극적으로 해야겠다.</li>
</ol>
</li>
</ul>
<h2 id="상호-조언">상호 조언</h2>
<h4 id="송근영">송근영</h4>
<ul>
<li>초기 설정 시기에 팀원들의 개인 역량을 고려해서 코드 구현할 시간을 충분히 확보해 주셨다면 더 좋았을 것 같습니다.</li>
<li>초기 계획을 디테일하게 설정했고, 문서화 작업에 신경을 많이 써주셔서 같은 문제가 반복되지 않았던 것 같습니다.</li>
<li>팀 노션을 디테일하게 계획해주셔서 다음 팀 프로젝트 때는 어떤 식으로 노션을 작성해야 할지 감이 잡힌 것 같습니다.</li>
<li>진행 중인 기능 구현이 있음에도, 트러블 이슈에 대해서 친절하게 설명해 주셨습니다.</li>
<li>깃 협업에 어려움이 있어 당황스러웠는데 직접 시나리오 별로 정리해주셔서 많은 도움이 되었습니다.</li>
</ul>
<h4 id="박재원">박재원</h4>
<ul>
<li>코드 구현에 어려운 부분을 적극적으로 도와주셔서 든든했습니다.</li>
<li>빡빡한 일정 속에서도 다른 팀원을 도와주시는 게 대단하다고 생각했습니다</li>
<li>정규 시간 외에도 본인 파트 외 기능 구현을 묵묵히 도와주셔서 감사했습니다.</li>
</ul>
<h4 id="이가현">이가현</h4>
<ul>
<li>어려운 점이 분명 있으셨을텐데 모르는 부분을 잘 물어봐 주시고 맡은 역할을 충분히 수행하셨습니다.</li>
<li>감기에 걸리시고도 맡은 바 역할을 전부 해내시는 모습을 보며 감동 받았습니다.</li>
<li>기능구현에 어려움이 있어도 포기하지 않으시고 잘 마무리 하신 것 같습니다.</li>
</ul>
<h4 id="유영국">유영국</h4>
<ul>
<li>코딩에 아직 익숙하지 않지만 어려운 부분을 담당해주시면서 책임감 있게 해결하려는 모습 감동 받았습니다.</li>
<li>어려운 부분을 맡으셔서 힘든 부분이 많으셨을텐데 어려운 점을 함께 얘기하면서 조급함을 덜어낼 수 있었던 것 같습니다.</li>
<li>처음이라곤 믿을 수 없는 이해 능력과 개발 속도가 신기했습니다. 코딩 처음 아니시죠?</li>
<li>말씀을 너무 예쁘게 하시고 배려심 깊으신게 느껴져서 따수웠습니다..</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[ActivityResultLauncher]]></title>
            <link>https://velog.io/@jaewon_p/ActivityResultLauncher</link>
            <guid>https://velog.io/@jaewon_p/ActivityResultLauncher</guid>
            <pubDate>Mon, 22 Jan 2024 11:45:30 GMT</pubDate>
            <description><![CDATA[<h3 id="activityresultlauncher">ActivityResultLauncher</h3>
<ul>
<li>액티비티에서 데이터를 받아오기 위해 사용된다.</li>
<li>콜백을 분리하여 항상 콜백을 받을 수 있도록 한다.</li>
<li>e.g. 현재 실행중인 앱의 activity A에서 갤러리 앱의 activity B로부터 사진을 갖고올 때<h3 id="activityforresult가-deperated된-이유">ActivityForResult가 Deperated된 이유</h3>
</li>
<li>AndroidX Activity와 Fragment에 도입된 ActivityResult API 를 안드로이드 공식문서에서 적극 권장함</li>
<li>결과를 얻기위해 액티비티를 시작할 때 메모리 부족으로 프로세스와 액티비티가 소멸될 수 있다.</li>
<li>두 메서드가 같은 곳에서 구현을 해야하는데 메모리 부족으로 제대로 동작하지 않을 수 있다.</li>
</ul>
<h3 id="activityresultlauncher가-activityforresult와-다른점">&#39;ActivityResultLauncher&#39;가 &#39;ActivityForResult&#39;와 다른점</h3>
<ul>
<li><p>Activity ResulAPI는 다른 activity를 실행하는 코드에서 결과 콜백을 분리한다.</p>
</li>
<li><p>RequestCode가 사라졌다.</p>
</li>
<li><p>Activity가 종료되었다가 다시 만들어져도 Result값을 기다리게 할 수 있다.</p>
<h3 id="activityresultlauncher-eg">activityResultLauncher e.g.</h3>
<pre><code class="language-kotlin">private val activityResultLauncher : ActivityResultLauncher&lt;Intent&gt; = registerForActivityResult(
      ActivityResultContracts.StartActivityForResult()
  ){

      // SubOne에서 결과를 받아옴
      if(it.resultCode == ONE){
          val intent = it.data
          val returnValue = intent!!.getStringExtra(&quot;one&quot;)
          Toast.makeText(this, returnValue.toString(), Toast.LENGTH_SHORT).show()
      }

      // SubTwo에서 결과를 받아옴
      else if(it.resultCode == TWO){
          val intent = it.data
          val returnValue = intent!!.getStringExtra(&quot;two&quot;)
          Toast.makeText(this, returnValue.toString(), Toast.LENGTH_SHORT).show()
      }
  }
</code></pre>
</li>
</ul>
<pre><code>### Permission e.g.
```kotlin
// 카메라 권한 요청 - 1
perButton.setOnClickListener {
    if(ContextCompat.checkSelfPermission(
            this@MainActivity,
            Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED){
        Toast.makeText(this@MainActivity, &quot;이미 권한이 있습니다.&quot;, Toast.LENGTH_SHORT).show()
    }
    else{
        permissionLauncher.launch(Manifest.permission.CAMERA)
    }
}
// 카메라 권한 요청 - 2
perButton.setOnClickListener {
    permissionLauncher.launch(Manifest.permission.CAMERA)
}</code></pre>]]></description>
        </item>
    </channel>
</rss>