博客
关于我
第14章 使用Kotlin 进行 Android 开发(2)
阅读量:251 次
发布时间:2019-03-01

本文共 14780 字,大约阅读时间需要 49 分钟。

14.2.3 实现后端 API 的接入

在本节中我们将实现后端 API 的接入及其数据展示的逻辑。

新建领域对象类 Movie

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 返回的数据结构与解析

我们调用的 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
= ArrayList()val MOVIE_MAP: MutableMap
= 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.kotlinimport android.content.Intentimport android.os.Bundleimport android.support.v7.app.AppCompatActivityimport android.support.v7.widget.RecyclerViewimport android.view.LayoutInflaterimport android.view.Viewimport android.view.ViewGroupimport android.widget.ImageViewimport android.widget.TextViewimport com.easy.kotlin.bean.MovieContentimport com.easy.kotlin.util.HttpUtilimport 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
, private val mTwoPane: Boolean) : RecyclerView.Adapter
() { 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

movie_list.xml

movie_list_content.xml

电影列表的整体布局的 UI 如下图所示

1233356-447103ad47e5abb3.png
电影列表的整体布局的 UI

视图数据适配器 ViewAdapter

我们在创建 MovieListActivity 过程中需要展示响应的数据,这些数据由 ViewAdapter 来承载,对应的代码如下

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)    }

在上面代码中,我们定义了一个继承 RecyclerView.Adapter 的 SimpleItemRecyclerViewAdapter 类来装载 View 中要显示的数据,实现数据与视图的解耦。View 要显示的数据从Adapter里面获取并展现出来。Adapter负责把真实的数据是配成一个个View,也就是说View要显示什么数据取决于Adapter里面的数据。

视图中图像的展示

其中,在函数 SimpleItemRecyclerViewAdapter.onBindViewHolder 中,我们设置 View 组件与Model 数据的绑定。其中的电影海报是图片,所以我们的布局文件中使用了 ImageView,对应的布局文件是 movie_list_content.xml ,代码如下

UI 设计效果图

1233356-aebd3c75d2813212.png
MovieListActivity 布局 UI

列表中图片的展示

关于图片的视图组件是 ImageView

我们这里是根据图片的 URL 来展示图片,ImageView 类有个setImageBitmap 方法,可以直接设置 Bitmap 图片数据

holder.mMoviePosterImageView.setImageBitmap(HttpUtil.getBitmapFromURL(item.posterPath))

而通过 url 获取Bitmap 图片数据的代码是

object HttpUtil {    fun getBitmapFromURL(src: String): Bitmap? {        try {            val url = URL(src)            val input = url.openStream()            val myBitmap = BitmapFactory.decodeStream(input)            return myBitmap        } catch (e: Exception) {            e.printStackTrace()            return null        }    }}

电影详情页面

MovieDetailActivity 是电影详情页面,代码如下

package com.easy.kotlinimport android.content.Intentimport android.os.Bundleimport android.support.design.widget.Snackbarimport android.support.v7.app.AppCompatActivityimport android.view.MenuItemimport kotlinx.android.synthetic.main.activity_movie_detail.*class MovieDetailActivity : AppCompatActivity() {    override fun onCreate(savedInstanceState: Bundle?) {        super.onCreate(savedInstanceState)        setContentView(R.layout.activity_movie_detail)        setSupportActionBar(detail_toolbar)        fab.setOnClickListener { view ->            Snackbar.make(view, "Replace with your own detail action", Snackbar.LENGTH_LONG)                    .setAction("Action", null).show()        }        supportActionBar?.setDisplayHomeAsUpEnabled(true)        if (savedInstanceState == null) {            val arguments = Bundle()            arguments.putString(MovieDetailFragment.ARG_MOVIE_ID,                    intent.getStringExtra(MovieDetailFragment.ARG_MOVIE_ID))            val fragment = MovieDetailFragment()            fragment.arguments = arguments            supportFragmentManager.beginTransaction()                    .add(R.id.movie_detail_container, fragment)                    .commit()        }    }    override fun onOptionsItemSelected(item: MenuItem) =            when (item.itemId) {                android.R.id.home -> {                    navigateUpTo(Intent(this, MovieListActivity::class.java))                    true                }                else -> super.onOptionsItemSelected(item)            }}

其中的详情页的布局 XML 文件是activity_item_detail.xml, 代码如下

我们把电影详情的 Fragment 的展示放到 NestedScrollView 中

电影详情的 Fragment 代码是 MovieDetailFragment

package com.easy.kotlinimport android.os.Bundleimport android.support.v4.app.Fragmentimport android.view.LayoutInflaterimport android.view.Viewimport android.view.ViewGroupimport com.easy.kotlin.bean.MovieContentimport com.easy.kotlin.util.HttpUtilimport kotlinx.android.synthetic.main.activity_movie_detail.*import kotlinx.android.synthetic.main.movie_detail.view.*class MovieDetailFragment : Fragment() {    private var mItem: MovieContent.Movie? = null    override fun onCreate(savedInstanceState: Bundle?) {        super.onCreate(savedInstanceState)        if (arguments.containsKey(ARG_MOVIE_ID)) {            mItem = MovieContent.MOVIE_MAP[arguments.getString(ARG_MOVIE_ID)]            mItem?.let {                activity.toolbar_layout?.title = it.title            }        }    }    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,                              savedInstanceState: Bundle?): View? {        // 绑定 movieDetailView        val movieDetailView = inflater.inflate(R.layout.movie_detail, container, false)        mItem?.let {            movieDetailView.movie_poster_image.setImageBitmap(HttpUtil.getBitmapFromURL(it.posterPath))            movieDetailView.movie_overview.text = "影片简介: ${it.overview}"            movieDetailView.movie_vote_count.text = "打分次数:${it.vote_count}"            movieDetailView.movie_vote_average.text = "评分:${it.vote_average}"            movieDetailView.movie_release_date.text = "发行日期:${it.release_date}"        }        return movieDetailView    }    companion object {        const val ARG_MOVIE_ID = "movie_id"    }}

其中的 R.layout.movie_detail 布局文件 movie_detail.xml 如下

电影源数据的获取

我们定义了一个 MovieContent 对象类来存储从 API 获取到的数据,代码如下

package com.easy.kotlin.beanimport android.os.StrictModeimport com.alibaba.fastjson.JSONimport com.alibaba.fastjson.JSONArrayimport java.net.URLimport java.nio.charset.Charsetimport java.util.*object MovieContent {    val MOVIES: MutableList
= ArrayList() val MOVIE_MAP: MutableMap
= HashMap() val VOTE_AVERAGE_API = "http://api.themoviedb.org//3/discover/movie?sort_by=popularity.desc&api_key=7e55a88ece9f03408b895a96c1487979&page=1" init { val policy = StrictMode.ThreadPolicy.Builder().permitAll().build() StrictMode.setThreadPolicy(policy) initMovieListData() } private fun initMovieListData() { val jsonstr = URL(VOTE_AVERAGE_API).readText(Charset.defaultCharset()) try { val obj = JSON.parse(jsonstr) as Map<*, *> val dataArray = obj.get("results") as JSONArray 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 val vote_count = it.get("vote_count").toString() val vote_average = it.get("vote_average").toString() val release_date = it.get("release_date").toString() addMovie(Movie(id = index.toString(), title = title, overview = overview, vote_count = vote_count, vote_average = vote_average, release_date = release_date, posterPath = getPosterUrl(poster_path))) } } catch (ex: Exception) { ex.printStackTrace() } } private fun addMovie(movie: Movie) { MOVIES.add(movie) MOVIE_MAP.put(movie.id, movie) } fun getPosterUrl(posterPath: String): String { return "https://image.tmdb.org/t/p/w185_and_h278_bestv2$posterPath" } data class Movie(val id: String, val title: String, val overview: String, val vote_count: String, val vote_average: String, val release_date: String, val posterPath: String)}

在 Android 4.0 之后默认的线程模式是不允许在主线程中访问网络。为了演示效果,我们在访问网络的代码前,把 ThreadPolicy 设置为允许运行访问网络

val policy = StrictMode.ThreadPolicy.Builder().permitAll().build()StrictMode.setThreadPolicy(policy)

我们使用了一个 data class Movie 来存储电影对象数据

data class Movie(val id: String,                     val title: String,                     val overview: String,                     val vote_count: String,                     val vote_average: String,                     val release_date: String,                     val posterPath: String)

配置 AndroidManifest.xml

最后,我们配置 AndroidManifest.xml文件内容如下

...

因为我们要访问网络,所以需要添加该行配置

再次打包安装运行,效果图如下

电影列表页面

1233356-79c3243fdc6346dc.png
电影列表页面

点击进入电影详情页

1233356-ff6464207e6db595.png
电影详情页

本章小结

Android 中经常出现的空引用、API的冗余样板式代码等都是是驱动我们转向 Kotlin 语言的动力。另外,Kotlin 的 Android 视图 DSL Anko 可以我们从繁杂的 XML 视图配置文件中解放出来。我们可以像在 Java 中一样方便的使用 Android 开发的流行的库诸如 Butter Knife、Realm、RecyclerView等。当然,我们使用 Kotlin 集成这些库来进行 Andorid 开发,既能够直接使用我们之前的开发库,又能够从 Java 语言、Android API 的限制中出来。这不得不说是一件好事。

本章工程源码:

转载地址:http://erha.baihongyu.com/

你可能感兴趣的文章
maven仓库设置,windows和mac
查看>>
Android错误收集
查看>>
浙大机器学习课程-8-支持向量机(原问题转化为对偶问题)
查看>>
css3之路- 结构性伪类
查看>>
239_自定义View画圆环环形
查看>>
四、让字符串成为回文串的最少插入次数(Weekly Contest 170)
查看>>
流媒体音视频服务云管理平台EasyNVS平台中视频播放页面出现错误码的问题解决
查看>>
AI视频结构化智能安防平台EasyCVR自定义场景下的网页标题优化
查看>>
渗透测试学习笔记之案例五
查看>>
Pentest Wiki Part4 后渗透(二)
查看>>
微信getUserInfo接口
查看>>
Shell脚本实现Linux回收站
查看>>
tomcat配置虚拟目录后出现404的解决方法
查看>>
leetcode之使数组中所有元素相等的最小操作数(C++)
查看>>
【Python】Dvwa文件上传python脚本(已实现)
查看>>
VS2012创建项目弹出 “未找到与约束”错误提示(已解决)
查看>>
asp,asp.net中关于双引号和单引号的用法!
查看>>
Cannot determine value type from string varchar问题
查看>>
Invalid header signature
查看>>
SAP-SD VF04维护应请款清单(批量立AR)币别错误
查看>>