-
Notifications
You must be signed in to change notification settings - Fork 1
[정현수_Android] 9-2주차 과제 제출 #11
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,20 +1,110 @@ | ||
| package com.example.android_2026_1 | ||
|
|
||
| import android.annotation.SuppressLint | ||
| import android.content.Intent | ||
| import android.os.Bundle | ||
| import androidx.activity.enableEdgeToEdge | ||
| import android.text.Editable | ||
| import android.text.TextWatcher | ||
| import android.widget.Button | ||
| import android.widget.EditText | ||
| import androidx.appcompat.app.AppCompatActivity | ||
| import androidx.core.view.ViewCompat | ||
| import androidx.core.view.WindowInsetsCompat | ||
| import androidx.core.net.toUri | ||
| import androidx.lifecycle.lifecycleScope | ||
| import androidx.recyclerview.widget.LinearLayoutManager | ||
| import androidx.recyclerview.widget.RecyclerView | ||
| import com.google.gson.annotations.SerializedName | ||
| import kotlinx.coroutines.launch | ||
| import retrofit2.Retrofit | ||
| import retrofit2.converter.gson.GsonConverterFactory | ||
| import retrofit2.http.GET | ||
| import retrofit2.http.Query | ||
|
|
||
| object RetrofitClient { | ||
| val retrofit: Retrofit = Retrofit.Builder() | ||
| .baseUrl("https://api.github.com/") | ||
| .addConverterFactory(GsonConverterFactory.create()) | ||
| .build() | ||
|
|
||
| val api: GithubApi = retrofit.create(GithubApi::class.java) | ||
| } | ||
|
|
||
| class MainActivity : AppCompatActivity() { | ||
|
|
||
| private lateinit var etSearch: EditText | ||
| private lateinit var btnSearch: Button | ||
| private lateinit var rvUsers: RecyclerView | ||
| private val userList = ArrayList<String>() | ||
| private lateinit var nameAdapter: NameAdapter | ||
|
|
||
| @SuppressLint("NotifyDataSetChanged") | ||
| override fun onCreate(savedInstanceState: Bundle?) { | ||
| super.onCreate(savedInstanceState) | ||
| enableEdgeToEdge() | ||
| setContentView(R.layout.activity_main) | ||
| ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets -> | ||
| val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars()) | ||
| v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom) | ||
| insets | ||
|
|
||
| etSearch = findViewById(R.id.et_search) | ||
| btnSearch = findViewById(R.id.btn_search) | ||
| rvUsers = findViewById(R.id.rv_users) | ||
|
|
||
| rvUsers.layoutManager = LinearLayoutManager(this) | ||
| nameAdapter = NameAdapter(userList) { selectedName: String -> | ||
| etSearch.setText(selectedName) | ||
| etSearch.setSelection(selectedName.length) | ||
| } | ||
| rvUsers.adapter = nameAdapter | ||
|
|
||
| etSearch.addTextChangedListener(object : TextWatcher { | ||
| override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {} | ||
| override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) { | ||
| val query: String = s?.toString() ?: "" | ||
| if (query.isNotEmpty()) { | ||
| lifecycleScope.launch { | ||
| try { | ||
| val response: UserSearchResponse = RetrofitClient.api.searchUsers(query) | ||
| val items: List<UserItem>? = response.items | ||
| if (items != null) { | ||
| userList.clear() | ||
| for (item in items) { | ||
| userList.add(item.login) | ||
| } | ||
| nameAdapter.notifyDataSetChanged() | ||
| } | ||
| } catch (e: Exception) { | ||
| e.printStackTrace() | ||
| } | ||
| } | ||
| } else { | ||
| userList.clear() | ||
| nameAdapter.notifyDataSetChanged() | ||
| } | ||
| } | ||
| override fun afterTextChanged(s: Editable?) {} | ||
| }) | ||
|
|
||
| btnSearch.setOnClickListener { | ||
| val name: String = etSearch.text.toString() | ||
| if (name.isNotEmpty()) { | ||
| val intent = Intent( | ||
| Intent.ACTION_VIEW, | ||
| "https://github.com/$name?tab=repositories".toUri() | ||
| ) | ||
| startActivity(intent) | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| interface GithubApi { | ||
| @GET("search/users") | ||
| suspend fun searchUsers( | ||
| @Query("q") query: String, | ||
| @Query("per_page") perPage: Int = 20 | ||
| ): UserSearchResponse | ||
| } | ||
|
|
||
| data class UserSearchResponse( | ||
| @SerializedName("items") val items: List<UserItem>? | ||
| ) | ||
|
|
||
| data class UserItem( | ||
| @SerializedName("login") val login: String | ||
| ) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,35 @@ | ||
| package com.example.android_2026_1 | ||
|
|
||
| import android.view.ViewGroup | ||
| import android.widget.TextView | ||
| import androidx.recyclerview.widget.RecyclerView | ||
|
|
||
| class NameAdapter( | ||
| private val userList: List<String>, | ||
| private val onItemClick: (String) -> Unit | ||
| ) : RecyclerView.Adapter<NameAdapter.NameViewHolder>() { | ||
|
|
||
| class NameViewHolder(val textView: TextView) : RecyclerView.ViewHolder(textView) | ||
|
|
||
| override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): NameViewHolder { | ||
| val textView = TextView(parent.context).apply { | ||
| layoutParams = ViewGroup.LayoutParams( | ||
| ViewGroup.LayoutParams.MATCH_PARENT, | ||
| ViewGroup.LayoutParams.WRAP_CONTENT | ||
| ) | ||
| setPadding(50, 40, 50, 40) | ||
| textSize = 16f | ||
| } | ||
| return NameViewHolder(textView) | ||
| } | ||
|
|
||
| override fun onBindViewHolder(holder: NameViewHolder, position: Int) { | ||
| val name = userList[position] | ||
| holder.textView.text = name | ||
| holder.textView.setOnClickListener { | ||
| onItemClick(name) | ||
| } | ||
| } | ||
|
|
||
| override fun getItemCount(): Int = userList.size | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,10 +1,35 @@ | ||
| <?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:id="@+id/main" | ||
| <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" | ||
| android:layout_width="match_parent" | ||
| android:layout_height="match_parent" | ||
| tools:context=".MainActivity"> | ||
| android:orientation="vertical" | ||
| android:padding="16dp"> | ||
|
|
||
| </androidx.constraintlayout.widget.ConstraintLayout> | ||
| <LinearLayout | ||
| android:layout_width="match_parent" | ||
| android:layout_height="wrap_content" | ||
| android:orientation="horizontal"> | ||
|
|
||
| <EditText | ||
| android:id="@+id/et_search" | ||
| android:layout_width="0dp" | ||
| android:layout_height="wrap_content" | ||
| android:layout_weight="1" | ||
| android:minHeight="48dp" | ||
| android:inputType="text" | ||
| android:maxLines="1" | ||
| android:maxLength="39" | ||
| android:hint="이름 입력" /> | ||
|
|
||
| <Button | ||
| android:id="@+id/btn_search" | ||
| android:layout_width="wrap_content" | ||
| android:layout_height="wrap_content" | ||
| android:text="검색" /> | ||
| </LinearLayout> | ||
|
|
||
| <androidx.recyclerview.widget.RecyclerView | ||
| android:id="@+id/rv_users" | ||
| android:layout_width="match_parent" | ||
| android:layout_height="match_parent" /> | ||
| </LinearLayout> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
검색 기능이나 많은 양의 리스트를 불러올 때 디바운싱 처리를 하는 것도 좋아보입니다.