第14章使用Kotlin进行Android开发(2)
在本节中我们将实现后端 API 的接入及其数据展示的逻辑。
data class Movie(val id: String, val title: String, val overview: String, val posterPath: String) { override fun toString(): String { return "Movie(id='$id', title='$title', overview='$overview', posterPath='$posterPath')" } }
我们调用的 API 是
val VOTE_AVERAGE_API = "http://api.themoviedb.org//3/discover/movie?certification_country=US&certification=R&sort_by=vote_average.desc&api_key=7e55a88ece9f03408b895a96c1487979"
它的数据返回是
{ "page": 1, "total_results": 10350, "total_pages": 518, "results": [ { "vote_count": 28, "id": 138878, "video": false, "vote_average": 10, "title": "Fatal Mission", "popularity": 3.721883, "poster_path": "/u351Rsqu5nd36ZpbWxIpd3CpbJW.jpg", "original_language": "en", "original_title": "Fatal Mission", "genre_ids": [ 10752, 28, 12 ], "backdrop_path": "/wNq5uqVDT7a5G1b97ffYf4hxzYz.jpg", "adult": false, "overview": "A CIA Agent must rely on reluctant help from a female spy in the North Vietnam jungle in order to pass through enemy lines.", "release_date": "1990-07-25" }, ... ] }
我们使用 fastjson 来解析这个数据。在 app 下面的 build.gradle中添加依赖
dependencies { ... // https://mvnrepository.com/artifact/com.alibaba/fastjson compile group: 'com.alibaba', name: 'fastjson', version: '1.2.39' }
解析代码如下
val jsonstr = URL(VOTE_AVERAGE_API).readText(Charset.defaultCharset()) try { val obj = JSON.parse(jsonstr) as Map<*, *> val dataArray = obj.get("results") as JSONArray } } catch (ex: Exception) { }
然后我们把这个 dataArray 放到我们的 MovieContent 对象中
dataArray.forEachIndexed { index, it -> val title = (it as Map<*, *>).get("title") as String val overview = it.get("overview") as String val poster_path = it.get("poster_path") as String addMovie(Movie(index.toString(), title, overview, getPosterUrl(poster_path))) }
其中,addMovie 的代码是
object MovieContent { val MOVIES: MutableList<Movie> = ArrayList() val MOVIE_MAP: MutableMap<String, Movie> = HashMap() ... private fun addMovie(movie: Movie) { MOVIES.add(movie) MOVIE_MAP.put(movie.id, movie) } }
然后,我们再新建 MovieDetailActivity、MovieDetailFragment、MovieListActivity 以及 activity_movie_list.xml、activity_movie_detail.xml 、 movie_detail.xml、movie_list.xml、movie_list_content.xml ,它们的代码分别介绍如下。
MovieListActivity 是电影列表页面的 Activity,代码如下
package com.easy.kotlin import android.content.Intent import android.os.Bundle import android.support.v7.app.AppCompatActivity import android.support.v7.widget.RecyclerView import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.ImageView import android.widget.TextView import com.easy.kotlin.bean.MovieContent import com.easy.kotlin.util.HttpUtil import kotlinx.android.synthetic.main.activity_movie_detail.* import kotlinx.android.synthetic.main.activity_movie_list.* import kotlinx.android.synthetic.main.movie_list.* import kotlinx.android.synthetic.main.movie_list_content.view.* class MovieListActivity : AppCompatActivity() { private var mTwoPane: Boolean = false override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_movie_list) setSupportActionBar(toolbar) toolbar.title = title if (movie_detail_container != null) { mTwoPane = true } setupRecyclerView(movie_list) } private fun setupRecyclerView(recyclerView: RecyclerView) { recyclerView.adapter = SimpleItemRecyclerViewAdapter(this, MovieContent.MOVIES, mTwoPane) } class SimpleItemRecyclerViewAdapter(private val mParentActivity: MovieListActivity, private val mValues: List<MovieContent.Movie>, private val mTwoPane: Boolean) : RecyclerView.Adapter<SimpleItemRecyclerViewAdapter.ViewHolder>() { private val mOnClickListener: View.OnClickListener init { mOnClickListener = View.OnClickListener { v -> val item = v.tag as MovieContent.Movie if (mTwoPane) { val fragment = MovieDetailFragment().apply { arguments = Bundle() arguments.putString(MovieDetailFragment.ARG_MOVIE_ID, item.id) } mParentActivity.supportFragmentManager .beginTransaction() .replace(R.id.movie_detail_container, fragment) .commit() } else { val intent = Intent(v.context, MovieDetailActivity::class.java).apply { putExtra(MovieDetailFragment.ARG_MOVIE_ID, item.id) } v.context.startActivity(intent) } } } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { val view = LayoutInflater .from(parent.context) .inflate(R.layout.movie_list_content, parent, false) return ViewHolder(view) } override fun onBindViewHolder(holder: ViewHolder, position: Int) { val item = mValues[position] holder.mIdView.text = item.id holder.mTitle.text = item.title holder.mMoviePosterImageView.setImageBitmap(HttpUtil.getBitmapFromURL(item.posterPath)) with(holder.itemView) { tag = item setOnClickListener(mOnClickListener) } } override fun getItemCount(): Int { return mValues.size } inner class ViewHolder(mView: View) : RecyclerView.ViewHolder(mView) { val mIdView: TextView = mView.id_text val mTitle: TextView = mView.title val mMoviePosterImageView: ImageView = mView.movie_poster_image } } }
对应的布局文件如下
activity_movie_list.xml
<?xml version="1.0" encoding="utf-8"?> <android.support.design.widget.CoordinatorLayout 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" android:fitsSystemWindows="true" tools:context="com.easy.kotlin.MovieListActivity"> <android.support.design.widget.AppBarLayout android:id="@+id/app_bar" android:layout_width="match_parent" android:layout_height="wrap_content" android:theme="@style/AppTheme.AppBarOverlay"> <android.support.v7.widget.Toolbar android:id="@+id/toolbar" android:layout_width="match_parent" android:layout_height="?attr/actionBarSize" app:popupTheme="@style/AppTheme.PopupOverlay" /> </android.support.design.widget.AppBarLayout> <FrameLayout android:id="@+id/frameLayout" android:layout_width="match_parent" android:layout_height="match_parent" app:layout_behavior="@string/appbar_scrolling_view_behavior"> <include layout="@layout/movie_list" /> </FrameLayout> </android.support.design.widget.CoordinatorLayout>
movie_list.xml
<?xml version="1.0" encoding="utf-8"?> <android.support.v7.widget.RecyclerView 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/movie_list" android:name="com.easy.kotlin.MovieListFragment" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_marginLeft="16dp" android:layout_marginRight="16dp" app:layoutManager="LinearLayoutManager" tools:context="com.easy.kotlin.MovieListActivity" tools:listitem="@layout/movie_list_content" />
movie_list_content.xml
<?xml version="1.0" encoding="utf-8"?> <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="320dp" android:layout_gravity="center" android:layout_margin="0dp" android:clickable="true" android:foreground="?attr/selectableItemBackground" android:orientation="horizontal"> <TextView android:id="@+id/id_text" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_margin="@dimen/text_margin" android:textAppearance="?attr/textAppearanceListItem" /> <ImageView android:id="@+id/movie_poster_image" android:layout_width="match_parent" android:layout_height="match_parent" android:scaleType="centerCrop" /> <View android:id="@+id/title_background" android:layout_width="match_parent" android:layout_height="48dp" android:layout_gravity="bottom" android:alpha="0.8" android:background="@color/colorPrimaryDark" android:gravity="center" /> <TextView android:id="@+id/title" android:layout_width="match_parent" android:layout_height="48dp" android:layout_gravity="bottom" android:gravity="center" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:textColor="@android:color/white" android:textSize="12sp" /> </FrameLayout>
电影列表的整体布局的 UI 如下图所示