
컨셉 유지하기 위해 중간중간 얼렁뚱땅 넘어가는 부분이 있어요. 절ㄷ ㅐ 귀찮아서 그런거아니고 몰라서 그런건 맞음
처음부터 끝까지 과정을 무에서 유로 흘러가는 과정을 담아볼까 한다. 하나하나 세세하게 한달 뒤 또 까먹고 전전긍긍 하고있을게 분명하기 때문에ㅎㅎ
gradle에 추가
//viewPager2
implementation("androidx.viewpager2:viewpager2:1.0.0")
레트로핏 때 필요한거 미리 넣어놓기
//retrofit
implementation("com.google.code.gson:gson:2.10.1")
implementation("com.squareup.retrofit2:retrofit:2.9.0")
implementation("com.squareup.retrofit2:converter-gson:2.9.0")
implementation("com.squareup.okhttp3:okhttp:4.10.0")
implementation("com.squareup.okhttp3:logging-interceptor:4.10.0")
인터넷 권한 받아오기 그냥 한번에 넣어놓을게요 manifest에 넣으면 됨.
<uses-permission android:name="android.permission.INTERNET"/>
오키 여기까지 하고 까먹은거 있으면 추가로 넣어야지
fragment필요한거 생성해주시고 나는 이미지검색프래크먼트랑 저장소 프래그먼트 2개 만듬.
package com.android.searchproject
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import androidx.viewpager2.adapter.FragmentStateAdapter
class ViewPagerAdapter(fragmentActivity: FragmentActivity) : FragmentStateAdapter(fragmentActivity) {
var fragments : ArrayList<Fragment> = ArrayList()
override fun getItemCount(): Int {
return fragments.size
}
override fun createFragment(position: Int): Fragment {
return fragments[position]
}
fun addFragment(fragment: Fragment){
fragments.add(fragment)
notifyItemInserted(fragments.size - 1)
}
fun removeFragment(){
fragments.removeLast()
notifyItemRemoved(fragments.size)
}
}
package com.android.searchproject
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.viewpager2.widget.ViewPager2
import com.android.searchproject.databinding.ActivityMainBinding
import com.google.android.material.tabs.TabLayoutMediator
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
initViewPager()
}
private fun initViewPager() {
var viewPager2Adapter = ViewPagerAdapter(this)
viewPager2Adapter.addFragment(SearchFragment())
viewPager2Adapter.addFragment(StorageFragment())
binding.viewPager.apply {
adapter = viewPager2Adapter
registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
override fun onPageSelected(position: Int) {
super.onPageSelected(position)
}
})
}
TabLayoutMediator(binding.tapLayout, binding.viewPager) { tab, position ->
when (position) {
0 -> {
tab.text = "이미지 검색"
}
1 -> {
tab.text = "좋아요 보관함"
}
}
}.attach()
}
}
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/black"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" >
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="HaruSearch"
android:textColor="@color/white"
android:textSize="30sp"
android:textStyle="bold" />
</androidx.appcompat.widget.Toolbar>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/constraintLayout2"
android:layout_width="match_parent"
android:layout_height="50dp"
android:background="#C5C5C5"
android:paddingHorizontal="10dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/toolbar">
<EditText
android:id="@+id/et_main_search"
android:layout_width="0dp"
android:layout_height="40dp"
android:paddingStart="5dp"
android:hint=" 키워드"
android:layout_marginEnd="10dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/btn_main_search"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/btn_main_search"
android:layout_width="100dp"
android:layout_height="wrap_content"
android:text="검색"
android:backgroundTint="#505050"
android:textSize="16sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/viewPager"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@+id/constraintLayout2"
app:layout_constraintBottom_toTopOf="@+id/tapLayout"/>
<com.google.android.material.tabs.TabLayout
android:id="@+id/tapLayout"
android:layout_width="409dp"
android:layout_height="wrap_content"
tools:layout_editor_absoluteX="1dp"
tools:layout_editor_absoluteY="682dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
Search_recyclerview.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="15dp">
<ImageView
android:id="@+id/search_Image"
android:layout_width="match_parent"
android:layout_height="180dp"
android:scaleType="centerCrop"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ImageView
android:id="@+id/search_favorite"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_marginTop="5dp"
android:layout_marginEnd="5dp"
android:visibility="gone"
android:src="@drawable/favorite"
app:layout_constraintEnd_toEndOf="@+id/search_Image"
app:layout_constraintTop_toTopOf="@+id/search_Image" />
<TextView
android:id="@+id/search_name"
tools:text="name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="1dp"
android:layout_marginTop="2dp"
android:textSize="20sp"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/search_Image"/>
<TextView
android:id="@+id/search_datetime"
tools:text="dateandTime"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="1dp"
android:textSize="15sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/search_name" />
</androidx.constraintlayout.widget.ConstraintLayout>
이렇게 일단 대충 UI를 구성해주었다.

나의 어플리케이션을 생성해서 받아와준다.


데이터 클래스를 작성할 때는 카카오에서 제공해주는 문서에 들어가면 친절하게 이름과 타입을 표로 만들어 놓았다. 우리는 이걸 보고 옮겨 적어주기만 하면된다.
package com.android.searchproject
import android.os.Parcelable
import android.provider.DocumentsContract
import com.google.gson.annotations.SerializedName
data class SearchResponse(
@SerializedName("documents")
val documents: MutableList<DocumentsContract.Document>?,
@SerializedName("meta")
val metaData: MetaData?
)
data class MetaData(
@SerializedName("total_count")
val totalCount : Int,
@SerializedName("pageable_count")
val pageableCount : Int,
@SerializedName("is_end")
val isEnd : Boolean
)
@Parcelize
data class Document(
@SerializedName("collection")
val collection: String,
@SerializedName("datetime")
val dateTime: String,
@SerializedName("display_sitename")
val displaySiteName: String,
@SerializedName("doc_url")
val docUrl: String,
@SerializedName("height")
val height: Int,
@SerializedName("image_url")
val imageUrl: String,
@SerializedName("thumbnail_url")
val thumbnailUrl: String,
@SerializedName("width")
val width: Int,
): Parcelable
Parcelable이 정의가 되지 않았다는 오류가 생겼다.
plugins {
id("com.android.application")
id("org.jetbrains.kotlin.android")
id ("kotlin-parcelize")
id ("kotlin-kapt")
}
플러그인에 밑에 2줄을 추가해주니 해결 됨.
아 찾아봤는데 정리하기 귀찮다. 나중에 생각나면 다시 정리해야지
레트로핏에 대한 자세한 내용은 이전에 작성한 자료를 보시면 됩니다.
(아직 작성 안했고 이 자리에 작성하는대로 링크 넣어야징~~)
object NetWorkClient {
private const val BASE_URL = "https://dapi.kakao.com/"
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 searchRetrofit = Retrofit.Builder()
.baseUrl(BASE_URL)//
.addConverterFactory(GsonConverterFactory.create()).client(
createOkHttpClient()
).build()
val searchNetwork: NetworkInterface = searchRetrofit.create(NetworkInterface::class.java)
}
Base_URL의 코드는 기본정보의 URL에서 가지고 오면 된다. (위에 사진 있음.)
interface NetworkInterface {
@Headers("Authorization: API")
@GET("v2/search/image")
suspend fun searchImage(@QueryMap param: HashMap<String, String>) : SearchResponse
}
API에는 어플리케이션 생성하고 받은 REST_API 넣으면 됨.
package com.android.searchproject
import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.view.isVisible
import androidx.recyclerview.widget.RecyclerView
import com.android.searchproject.databinding.SearchRecyclerviewBinding
import com.bumptech.glide.Glide
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
class SearchAdapter(private val context: Context, private val results: MutableList<Document>, private val favoriteItems: MutableList<Document>) :
RecyclerView.Adapter<SearchAdapter.SearchViewHolder>() {
interface SearchThumbnailClickListener {
fun onClick(view: View, position: Int)
}
var searchThumbnailClickListener: SearchThumbnailClickListener? = null
private val inputFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSXXX", Locale.getDefault())
private val outputFormat = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault())
inner class SearchViewHolder(private val binding: SearchRecyclerviewBinding) :
RecyclerView.ViewHolder(binding.root) {
val image = binding.searchImage
val name = binding.searchName
val dateTime = binding.searchDatetime
val favorite = binding.searchFavorite
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SearchViewHolder {
return SearchViewHolder(
SearchRecyclerviewBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
)
}
override fun getItemCount(): Int {
return results.size
}
override fun onBindViewHolder(holder: SearchViewHolder, position: Int) {
holder.image.setOnClickListener {
searchThumbnailClickListener?.onClick(holder.favorite, position)
}
Glide.with(context)
.load(results[position].thumbnailUrl)
.into(holder.image)
holder.name.text = results[position].displaySiteName
holder.dateTime.text =
outputFormat.format(inputFormat.parse(results[position].dateTime) as Date)
holder.favorite.isVisible = (favoriteItems.find { it == results[position] } != null)
}
}
아 몰라

우선 어제처럼 TIL을 작성하면 너무 비효율적인 것 같음.
오늘은 주말이지만 어제 해결하지 못한 오류가 계속 생각나서 코드 쳐다 봄.

처음엔 setFragment를 viewPager에 진행했었음.
애뮬레이터 실행은 되는데 검색어에 입력하고 검색버튼을 누르면 앱이 꺼지는 오류가 발생함

이렇게 사이트 검색은 가능하지만 ViewPager2 does not support direct child views와 같은 오류가 발생함. ViewPager2위에 직접적으로 자식뷰를 생성하는건 안된다고 한다
main_activity_xml. 그래서 뷰페이저를 프레임 레이아웃으로 감싸고
<FrameLayout
android:id="@+id/frameLayout"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@+id/constraintLayout2"
app:layout_constraintBottom_toTopOf="@+id/tapLayout">
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/viewPager"
android:layout_width="match_parent"
android:layout_height="0dp" />
</FrameLayout>
frameLayout위에 리사이클러뷰가 올라가질 수 있도록 함.

검색이 가능해졌다!!
일단 오늘은 여기까지 하고 3일차 목표
이미지 클릭시 좋아요 보관함으로 이동
키보드 숨김처리
엔터 클릭 시 검색
리스트에서 특정 이미지를 선택하면 특별한 표시를 보여주도록 구현합니다