如何在Java中添加EditText搜索框并实现交互?Android Studio书籍API结果展示
Hey there! Glad you're diving into Android app development with the Google Books API—this is such a fun project to learn with. Let's break down exactly how to get your search results showing up after typing in an EditText, step by step.
First, your app needs permission to access the internet. Open your AndroidManifest.xml and add this line right above the <application> tag:
<uses-permission android:name="android.permission.INTERNET" />
If you're targeting Android 9 (API 28) or higher, you'll also need to allow cleartext traffic for the Google Books API. Add this inside your <application> tag:
android:usesCleartextTraffic="true"
Next, let's add dependencies for network calls and JSON parsing. Open your module-level build.gradle (the one inside the app folder) and add these lines to the dependencies block:
// Retrofit for simplified network requests implementation 'com.squareup.retrofit2:retrofit:2.9.0' // Gson converter to map JSON responses to Kotlin objects implementation 'com.squareup.retrofit2:converter-gson:2.9.0' // RecyclerView for displaying book lists implementation 'androidx.recyclerview:recyclerview:1.3.2'
Don't forget to sync your project after adding these!
The Google Books API returns a JSON object with a list of "volumes" (books). We need data classes to map this JSON into usable Kotlin objects. Create these three Kotlin files:
First, BookResponse.kt:
data class BookResponse( val items: List<BookItem>? )
Then BookItem.kt:
data class BookItem( val volumeInfo: VolumeInfo )
And VolumeInfo.kt (holds the core book details we want to display):
data class VolumeInfo( val title: String, val authors: List<String>?, val imageLinks: ImageLinks? ) data class ImageLinks( val thumbnail: String? )
You can add more fields later (like publisher or description) if you want to expand the details shown.
Retrofit makes defining API endpoints straightforward. Create an interface called BooksApiService.kt:
import retrofit2.Call import retrofit2.http.GET import retrofit2.http.Query interface BooksApiService { // Replace YOUR_API_KEY with your actual key from Google Cloud Console @GET("volumes") fun searchBooks( @Query("q") query: String, @Query("key") apiKey: String = "YOUR_API_KEY" ): Call<BookResponse> }
Important: You'll need to get an API key from the Google Cloud Console (search for "Google Books API", enable it, and generate a key). For learning purposes, hardcoding it works, but avoid this in production apps.
Let's link your search input to the API. Suppose you have an EditText (searchEditText) and a Button (searchButton) in your layout. In your Activity:
First, initialize Retrofit and the API service:
private lateinit var booksApiService: BooksApiService override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) // Set up Retrofit val retrofit = Retrofit.Builder() .baseUrl("https://www.googleapis.com/books/v1/") .addConverterFactory(GsonConverterFactory.create()) .build() booksApiService = retrofit.create(BooksApiService::class.java) // Connect search button to input val searchButton = findViewById<Button>(R.id.searchButton) val searchEditText = findViewById<EditText>(R.id.searchEditText) searchButton.setOnClickListener { val query = searchEditText.text.toString().trim() if (query.isNotEmpty()) { fetchBooks(query) } else { Toast.makeText(this, "Please enter a search term", Toast.LENGTH_SHORT).show() } } }
Then create the fetchBooks function to make the API call. Here's the callback-based version (great for beginners):
private fun fetchBooks(query: String) { booksApiService.searchBooks(query).enqueue(object : Callback<BookResponse> { override fun onResponse(call: Call<BookResponse>, response: Response<BookResponse>) { if (response.isSuccessful) { val books = response.body()?.items // Pass results to display function books?.let { displayBooks(it) } ?: run { Toast.makeText(this@MainActivity, "No books found", Toast.LENGTH_SHORT).show() } } else { Toast.makeText(this@MainActivity, "Error fetching books", Toast.LENGTH_SHORT).show() } } override fun onFailure(call: Call<BookResponse>, t: Throwable) { Toast.makeText(this@MainActivity, "Network error: ${t.message}", Toast.LENGTH_SHORT).show() } }) }
If you prefer modern Kotlin Coroutines, replace the above with this:
private fun fetchBooks(query: String) { CoroutineScope(Dispatchers.IO).launch { try { val response = booksApiService.searchBooks(query).execute() if (response.isSuccessful) { val books = response.body()?.items runOnUiThread { books?.let { displayBooks(it) } ?: run { Toast.makeText(this@MainActivity, "No books found", Toast.LENGTH_SHORT).show() } } } else { runOnUiThread { Toast.makeText(this@MainActivity, "Error fetching books", Toast.LENGTH_SHORT).show() } } } catch (e: Exception) { runOnUiThread { Toast.makeText(this@MainActivity, "Network error: ${e.message}", Toast.LENGTH_SHORT).show() } } } }
Finally, let's set up the RecyclerView to show the books. First, create a list item layout (item_book.xml):
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" android:padding="16dp" android:orientation="horizontal"> <ImageView android:id="@+id/bookThumbnail" android:layout_width="80dp" android:layout_height="120dp" android:scaleType="centerCrop"/> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" android:layout_marginStart="16dp"> <TextView android:id="@+id/bookTitle" android:layout_width="match_parent" android:layout_height="wrap_content" android:textSize="18sp" android:textStyle="bold"/> <TextView android:id="@+id/bookAuthors" android:layout_width="match_parent" android:layout_height="wrap_content" android:textSize="14sp" android:textColor="@android:color/darker_gray"/> </LinearLayout> </LinearLayout>
Next, create the RecyclerView Adapter (BookAdapter.kt):
class BookAdapter(private val books: List<BookItem>) : RecyclerView.Adapter<BookAdapter.BookViewHolder>() { inner class BookViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { val title: TextView = itemView.findViewById(R.id.bookTitle) val authors: TextView = itemView.findViewById(R.id.bookAuthors) val thumbnail: ImageView = itemView.findViewById(R.id.bookThumbnail) } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BookViewHolder { val view = LayoutInflater.from(parent.context).inflate(R.layout.item_book, parent, false) return BookViewHolder(view) } override fun onBindViewHolder(holder: BookViewHolder, position: Int) { val book = books[position].volumeInfo holder.title.text = book.title holder.authors.text = book.authors?.joinToString(", ") ?: "Unknown Author" // Load thumbnail (use Glide/Picasso for production apps) book.imageLinks?.thumbnail?.let { url -> holder.thumbnail.setImageBitmap(getBitmapFromUrl(url)) } ?: run { holder.thumbnail.setImageResource(R.drawable.ic_book_placeholder) // Add a placeholder drawable } } override fun getItemCount(): Int = books.size // Basic image loading (replace with Glide/Picasso for better performance) private fun getBitmapFromUrl(url: String): Bitmap? { return try { val connection = URL(url).openConnection() connection.doInput = true connection.connect() val input = connection.getInputStream() BitmapFactory.decodeStream(input) } catch (e: IOException) { e.printStackTrace() null } } }
Back in your Activity, initialize the RecyclerView and create the displayBooks function:
private lateinit var recyclerView: RecyclerView override fun onCreate(savedInstanceState: Bundle?) { // ... existing code ... recyclerView = findViewById(R.id.recyclerView) recyclerView.layoutManager = LinearLayoutManager(this) } private fun displayBooks(books: List<BookItem>) { val adapter = BookAdapter(books) recyclerView.adapter = adapter }
Don't forget to add the RecyclerView to your main layout (activity_main.xml):
<androidx.recyclerview.widget.RecyclerView android:id="@+id/recyclerView" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_below="@id/searchLayout"/>
To make the app feel more polished, add a ProgressBar for loading and an empty state message:
<ProgressBar android:id="@+id/loadingProgress" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" android:visibility="gone"/> <TextView android:id="@+id/emptyState" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" android:text="No books found" android:visibility="gone"/>
Update the fetchBooks function to show/hide these views as needed.
That's it! Plug in your API key, run the app, and you'll see book results pop up after searching.
内容的提问来源于stack exchange,提问作者Kadi Simpson

