使用Kotlin 进行 Android 开发(2)

使用Kotlin开发现代Android项目的入门指南 在"Dependencies"选项卡中,点击加号按钮,选择"Module Dependency"。在弹出的对话框中,选择"Kotlin/JVM"并点击"OK"。然后点击"Apply"和"OK"保存更改。在项目的"java"目录下,找到"com.example.yourprojectname"文件夹,并右键点击它。在"Kind"下拉菜单中选择"Activity",然后点击"OK"。在项目的"res"目录下,找到"layout"文件夹,并右键点击它。在项目模板中选择"空活动",然后点击"下一步"。 阅读详情

在这里插入图片描述

使用Kotlin 进行 Android 开发(2)

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<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 如下图所示

电影列表的整体布局的 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 ,代码如下

<?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 设计效果图

MovieListActivity 布局 UI

列表中图片的展示

关于图片的视图组件是 ImageView

    <ImageView
        android:id="@+id/movie_poster_image"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:scaleType="centerCrop" />

复制

我们这里是根据图片的 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.kotlin

import android.content.Intent
import android.os.Bundle
import android.support.design.widget.Snackbar
import android.support.v7.app.AppCompatActivity
import android.view.MenuItem
import 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, 代码如下

<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.ItemDetailActivity"
    tools:ignore="MergeRootFrame">

    <android.support.design.widget.AppBarLayout
        android:id="@+id/app_bar"
        android:layout_width="match_parent"
        android:layout_height="@dimen/app_bar_height"
        android:fitsSystemWindows="true"
        android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">

        <android.support.design.widget.CollapsingToolbarLayout
            android:id="@+id/toolbar_layout"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:fitsSystemWindows="true"
            app:contentScrim="?attr/colorPrimary"
            app:layout_scrollFlags="scroll|exitUntilCollapsed"
            app:toolbarId="@+id/toolbar">

            <android.support.v7.widget.Toolbar
                android:id="@+id/detail_toolbar"
                android:layout_width="match_parent"
                android:layout_height="?attr/actionBarSize"
                app:layout_collapseMode="pin"
                app:popupTheme="@style/ThemeOverlay.AppCompat.Light" />

        </android.support.design.widget.CollapsingToolbarLayout>

    </android.support.design.widget.AppBarLayout>

    <android.support.v4.widget.NestedScrollView
        android:id="@+id/item_detail_container"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_behavior="@string/appbar_scrolling_view_behavior" />

    <android.support.design.widget.FloatingActionButton
        android:id="@+id/fab"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_vertical|start"
        android:layout_margin="@dimen/fab_margin"
        app:layout_anchor="@+id/item_detail_container"
        app:layout_anchorGravity="top|end"
        app:srcCompat="@android:drawable/stat_notify_chat" />

</android.support.design.widget.CoordinatorLayout>

复制

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

    <android.support.v4.widget.NestedScrollView
        android:id="@+id/movie_detail_container"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:fitsSystemWindows="true"
        app:layout_behavior="@string/appbar_scrolling_view_behavior" />

复制

电影详情的 Fragment 代码是 MovieDetailFragment

package com.easy.kotlin

import android.os.Bundle
import android.support.v4.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
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.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 如下

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_gravity="center"
    android:layout_margin="0dp"
    android:clickable="true"
    android:foreground="?attr/selectableItemBackground"
    android:orientation="vertical">



    <TextView
        android:id="@+id/movie_release_date"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:padding="16dp"
        android:textIsSelectable="true"
        android:textSize="18sp"
        tools:context="com.easy.kotlin.MovieDetailFragment" />

    <ImageView
        android:id="@+id/movie_poster_image"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_centerVertical="true"
        android:fitsSystemWindows="true"
        android:scaleType="fitCenter" />



    <TextView
        android:id="@+id/movie_overview"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:padding="16dp"
        android:textIsSelectable="true"
        android:textSize="18sp"
        tools:context="com.easy.kotlin.MovieDetailFragment" />


    <TextView
        android:id="@+id/movie_vote_average"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:padding="16dp"
        android:textIsSelectable="true"
        android:textSize="18sp"
        tools:context="com.easy.kotlin.MovieDetailFragment" />

    <TextView
        android:id="@+id/movie_vote_count"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:padding="16dp"
        android:textIsSelectable="true"
        android:textSize="18sp"
        tools:context="com.easy.kotlin.MovieDetailFragment" />


</LinearLayout>

复制

电影源数据的获取

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

package com.easy.kotlin.bean

import android.os.StrictMode
import com.alibaba.fastjson.JSON
import com.alibaba.fastjson.JSONArray
import java.net.URL
import java.nio.charset.Charset
import java.util.*


object MovieContent {

    val MOVIES: MutableList<Movie> = ArrayList()
    val MOVIE_MAP: MutableMap<String, Movie> = 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文件内容如下

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.easy.kotlin">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        ...
        <activity
            android:name=".MovieListActivity"
            android:label="@string/app_name"
            android:theme="@style/AppTheme.NoActionBar">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity
            android:name=".MovieDetailActivity"
            android:label="@string/title_movie_detail"
            android:parentActivityName=".MovieListActivity"
            android:theme="@style/AppTheme.NoActionBar">
            <meta-data
                android:name="android.support.PARENT_ACTIVITY"
                android:value="com.easy.kotlin.MovieListActivity" />
        </activity>
    </application>

    <uses-permission android:name="android.permission.INTERNET" />

</manifest>

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

<uses-permission android:name="android.permission.INTERNET" />

复制

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

电影列表页面
在这里插入图片描述

电影列表页面

点击进入电影详情页
在这里插入图片描述

电影详情页

本章小结

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

最后

更多Kotlin的学习资料可以扫描免费领取!

《高级Kotlin强化实战 》

第一章 Kotlin 入门教程

​ ● Kotlin 概述

​ ● Kotlin 与 Java 比较

​ ● 巧用 Android Studio

​ ● 认识 Kotlin 基本类型

​ ● 走进 Kotlin 的数组

​ ● 走进 Kotlin 的集合

​ ● 完整代码

​ ● 基础语法

img

第二章 Kotlin 实战避坑指南

​ ● 方法入参是常量,不可修改

​ ● 不要 Companion、INSTANCE?

​ ● Java 重载,在 Kotlin 中怎么巧妙过渡一下?

​ ● Kotlin 中的判空姿势

​ ● Kotlin 复写 Java 父类中的方法

​ ● Kotlin “狠”起来,连TODO都不放过!

​ ● is、as` 中的坑

​ ● Kotlin 中的 Property 的理解

​ ● also 关键字

​ ● takeIf 关键字

​ ● 单例模式的写法

img

第三章 项目实战《Kotlin Jetpack 实战》

​ ● 从一个膜拜大神的 Demo 开始

​ ● Kotlin 写 Gradle 脚本是一种什么体验?

​ ● Kotlin 编程的三重境界

​ ● Kotlin 高阶函数

​ ● Kotlin 泛型

​ ● Kotlin 扩展

​ ● Kotlin 委托

​ ● 协程“不为人知”的调试技巧

​ ● 图解协程:suspend

img

《史上最详Android版kotlin协程入门进阶实战 》

第一章 Kotlin协程的基础介绍

​ ● 协程是什么

​ ● 什么是Job 、Deferred 、协程作用域

​ ● Kotlin协程的基础用法

img

第二章 kotlin协程的关键知识点初步讲解

​ ● 协程调度器

​ ● 协程上下文

​ ● 协程启动模式

​ ● 协程作用域

​ ● 挂起函数

img

第三章 kotlin协程的异常处理

​ ● 协程异常的产生流程

​ ● 协程的异常处理

img

第四章 kotlin协程在Android中的基础应用

​ ● Android使用kotlin协程

​ ● 在Activity与Framgent中使用协程

​ ● ViewModel中使用协程

​ ● 其他环境下使用协程

Kotlin在Android开发中的应用与实践 Kotlin语言作为一种现代化的编程语言,已经在Android开发中变得越来越流行。本部分将简述Kotlin语言的背景、特点和优势,解释为何Kotlin在Android开发中得以广泛应用。背景:Kotlin语言由JetBrains公司于2011年推出,是一种基于Java虚拟机(JVM)的静态类型编程语言。Kotlin起初被设计为解决Java语言在开发效率和代码质量方面存在的一些问题,旨在提供一种更现代、更简洁、更安全的编程语言。 阅读详情

相关推荐

使用Kotlin编写Android应用程序

编写Android应用程序是一项令人兴奋的任务,而Kotlin则是一种流行的编程语言,被广泛用于开发Android应用程序。在本文中,我们将介绍如何使用Kotlin编写Android应用程序,并提供相应的源代码示例。在项目设置中,填写应用程序的名称、包名等信息。方法获取到布局文件中的TextView和Button,并为Button设置点击事件。在res目录下的layout文件夹中,我们可以创建XML布局文件来定义应用程序的用户界面。您可以根据自己的需求进行进一步的开发和定制,使用更多的布局组件和功能。

JaDentenet的博客 335

使用Kotlin 进行 Android 开发(1)

Google 在 2017-10-26 发布了 Android 8.1 Oreo 开发者预览版的同时还正式发布了 Android Studio 3.0 ,为其 IDE 引入了一系列新功能。Android Studio 3.0 专注于加速 Android 应用开发,包含大量更新内容,其中最主要的功能之一就包括对 Kotlin 的支持。正如谷歌在 Google I / O 2017.5 所宣布的那样,Kotlin 已被官方支持用于 Android 开发。

Misdirection_XG的博客 2610

Android Studio Kotlin插件开发实战(Kotlin-Plugin-1.1.2)

Kotlin 最初由 JetBrains 公司于 2010 年开始开发,目标是提供一种比 Java 更简洁、更安全、更富有表现力的语言。它于 2016 年正式发布 1.0 版本,标志着其进入稳定阶段。随着 Android 开发者对代码简洁性和开发效率的不断追求,Kotlin 在 2017 年被 Google 正式宣布为 Android 平台的一级语言,标志着其成为 Android 开发的重要支柱。Kotlin 的发展路径如下图所示:graph TD。

weixin_42504649的博客 1374

用kotlin语言开发安卓app流程

然后,我们定义一个接口,其中包含了访问API所需的方法。使用Retrofit的注解来描述这些方法和API的终点。是Android提供的一个轻量级的存储类,适用于保存少量数据,比如用户设置。它允许你保存和读取键值对数据。

qq_73990157的博客 3495

UFS逻辑单元管理

介绍UFS 逻辑单元管理和配置

m0_51720747的博客 1947

告别零散脚本:用Playwright+Pytest+Yaml+Allure搭建可维护的UI自动化项目

UI自动化测试是软件质量保障的关键环节,其核心在于模拟用户操作,对Web应用界面进行功能验证。传统的脚本堆砌方式常因代码耦合度高、维护成本大而难以为继。工程化解决方案通过分离关注点,将测试逻辑、数据和配置解耦,从而显著提升项目的可读性、可维护性和可扩展性。其技术价值在于构建可持续运行的自动化资产,而非一次性脚本。典型的应用场景包括Web应用的回归测试、兼容性测试及CI/CD流水线中的质量门禁。本文以Playwright、Pytest、Yaml和Allure为核心技术栈,详细阐述了如何从项目架构、页面对象模型

weixin_42529328的博客 299

2024年安卓最新Kotlin将继续助力Android开发,作为正在开发的你了解过哪些(2),2024年最新安卓大厂面试

各行各样都会淘汰一些能力差的,不仅仅是IT这个行业,所以,不要被程序猿是吃青春饭等等这类话题所吓倒,也不要觉得,找到一份工作,就享受安逸的生活,你在安逸的同时,别人正在奋力的向前跑,这样与别人的差距也就会越来越遥远,加油,希望,我们每一个人,成为更好的自己。BAT大厂面试题、独家面试工具包,资料包括 数据结构、Kotlin、计算机网络、Framework源码、数据结构与算法、小程序、NDK、Flutter,

2401_84545213的博客 958

注册“Oracle Provider for OLE DB”和创建链接服务器

注册“Oracle Provider for OLE DB”和创建链接服务器 在sql server 数据库上创建链接服务器,连接oracle数据库,访问接口需要设置为:“Oracle Provider for OLE DB”。 如果电脑上没有这个驱动,安装一个完整的Oracle11gR2程序是一个有效的方法,但这个64位版本的安装包有2个多G, 相当庞大,如果仅仅...

weixin_30760895的博客 2384

kotlin安卓开发(2):HelloWorld

Kotlin 的智能类型推断是其类型系统中的一个强大特性,它允许编译器自动推断变量的类型,从而减少显式类型声明的需要,使代码更加简洁和易读。存储值范围:-9,223,372,036,854,775,808 到 9,223,372,036,854,775,807。在 Kotlin 中,var 和 val 是用来声明变量的关键字,它们的主要区别在于变量的可变性。在左侧的项目类型列表中,选择了“Kotlin”,创建一个使用 Kotlin 编程语言的项目。val 声明的变量是可变的,可以在赋值后被重新赋值。

qq_57404999的博客 2160

双斜率积分ADC:高精度测量中的经典选择与TC530/TC534实战指南

模数转换器(ADC)是连接模拟世界与数字系统的关键桥梁,其核心任务是将连续的模拟信号转换为离散的数字量。在众多ADC类型中,双斜率积分ADC以其独特的原理脱颖而出,它通过积分与反积分的线性过程,用时间换取精度,实现了对噪声的卓越抑制和极高的线性度。这种技术价值使其在对抗工频干扰、追求长期稳定性的场景中无可替代,尤其适用于精密测量仪表和工业传感器信号采集。本文聚焦于双斜率积分ADC的经典代表TC530与TC534,深入剖析其工作原理、关键参数如积分电阻、积分电容的选择,并提供从硬件电路设计到软件驱动的完整实战

weixin_30847063的博客 359

【深度学习】肺结节分割项目实战二:分割肺实质

在后续训练模型的时候如果直接使用原始的肺部CT图像,其中的非肺区域会对训练造成干扰,因此我们需要提取分割肺实质。这一部分参考了kaggle上的教程。 从这张CT图像中可以看出扫描器以外的部分为黑色,背景和肺内部颜色较深,非肺组织和骨头颜色较浅。我们的目的是将肺部分割出来。由于肺部区域和其他部分对比明显,我们可以设置一个合适的阈值对图像中的区域进行隔离。 import argparse import os import numpy as np from medpy.filter.smoothing imp

weixin_42182906的博客 7241

探索现代Android开发的精华:Kotlin结合MVP与Dagger2的典范应用

探索现代Android开发的精华:Kotlin结合MVP与Dagger2的典范应用 随着Kotlin的日益崛起,越来越多的技术爱好者和开发者投身于这一高效、现代的语言。对于那些寻求融合最新设计模式和技术栈以构建健壮、可维护Android应用的人们来说,Sample App: Kotlin-MVP-Dagger2无疑是一个值得深入研究的宝藏项目。 项目简介 本项目展示了如何在Android开发中巧...

gitblog_00085的博客 395

【入坑存内计算一键指南】存内计算运算原理:算法优化、存储介质探究

本文探讨了存内计算及其面向的架构优化,尤其侧重于图算法优化和存内计算存储介质。文章介绍了存内计算的概念和意义,着重强调了存内计算解决了内存墙问题,提高了计算效率。针对图算法的优化,提出了基于位运算的创新方法,重塑了单源最短路径算法,并介绍了基于局部重叠度的链路预测算法优化设计。接着,文章详细讨论了存内计算存储介质,特别是非易失性存储器,比较了不同类型存储器的性能,并重点介绍了基于ReRAM的存内计算技术,包括其物理模型和交叉阵列结构。

一键难忘的博客 7067

Kotlin-Android开发之MVP模式+Retrofit2.0+RxJava1.0+Dagger2框架封装

前言:上一篇文章讲了《Kotlin&Java-Android开发之MVP模式+Retrofit2.0框架封装》相信你看了之后,收货不少,所以这次在MVP+Retrofit的基础上增加RxJava和Dagger,都是现在比较流行的框架,如果你没有看上篇博客,从这篇开始也是一样,每个框架都会大致介绍一下,非常适合小白。注意:此篇博客使用的代码是Kotlin代码,如果你对Kotlin不熟悉,请移...

等待着冬天的风的博客 2242

3D高斯压缩黑科技开源!1GB场景压到42MB,Web3D迎来“WebP时刻”——SOG格式深度解析

Web3D迎来革命性突破!PlayCanvas开源全新3D高斯泼溅压缩格式SOG,将1GB场景压缩至仅42MB,体积缩小95%,加载速度提升10倍+。SOG通过将3D数据编码为WebP纹理+元数据,实现GPU零拷贝实时渲染,完美兼容WebGL/WebGPU。实测显示,4百万高斯点的场景在手机上1.5秒内完成加载,内存占用降低83%。这一技术突破为电商展示、虚拟旅游、工业设计等领域带来新机遇,开发者可通过开源工具链快速集成。SOG被誉为"3D界的WebP",有望成为下一代3D互联网的基础设

这里汇聚了前沿的技术分享与实用的开发技巧,带你探索从创意到企业的技术创业之路。 865

YOLOv13优化Faster_Block:卷积(Partial Convolution)、多层感知机(MLP)结构和残差连接

Faster_Block 代表了现代深度学习中效率与性能平衡的一个典型案例。它通过部分卷积技术巧妙地减少了计算开销,同时通过精心设计的架构保持了强大的特征学习能力。这种设计思想对于移动端部署、实时应用和大规模模型训练都具有重要意义。 未来的发展方向可能包括更智能的动态分割策略、与新兴硬件的深度适配,以及与其他先进技术(如自注意力机制、神经架构搜索等)的结合。Faster_Block 的成功展示了在深度学习领域,创新往往来自于对问题本质的深刻理解和巧妙的工程实现。

FJN110的博客 427

安卓开发之Kotlin(2)

学习新技术

Gordon的博客 761

锂电池充电器电路设计 里面有电路

锂电池充电器电路设计 里面有电路 3.7V升压到5V电路设计。

使用Kotlin高效地开发Android App(一)(2)

都说三年是程序员的一个坎,能否晋升或者提高自己的核心竞争力,这几年就十分关键。技术发展的这么快,从哪些方面开始学习,才能达到高级工程师水平,最后进阶到Android架构师/技术专家?我总结了这 5大块;我搜集整理过这几年阿里,以及腾讯,字节跳动,华为,小米等公司的面试题,把面试的要求和技术点梳理成一份大而全的“ Android架构师”面试 Xmind(实际上比预期多花了不少精力),包含知识脉络 + 分支细节。

2401_84520401的博客 710

Probability and Statistics - 4th edition

Probability and Statistics - 4th edition

蓝牙模块HM-10手册

蓝牙模块HM-10手册,该手册对蓝牙模块HM-10进行了详细的介绍和使用方法以及一些AT指令。

Android开发之Kotlin基础(零散版2)

by lazy要求属性声明为val,即不可变变量,在java中相当于被final修饰,这意味着该变量一旦初始化后就不允许再被修改值了,基本类型是值不能被修改,对象类型是引用不能被修改。可是有的时候,我并不想声明一个类型可空的对象,而且我也没办法在对象一声明的时候就为它初始化,那么这时就需要用到。var 很容易理解,JavaScript 等其他语言也通过该关键字来声明变量,它对应的就是 Java 中的变量。在很多 Kotlin 的学习资料中,都会传递一个原则:优先使用 val 来声明变量。

wj3034547573的博客 423

复分析 阿尔福斯(Ahlfors) 第三版答案 英文

答案分为十章,有部分习题没有答案,全英文复分析阿尔福斯(Ahlfors) 第三版答案

第14章 使用Kotlin 进行 Android 开发2

下面我们来介绍 ItemDetailFragment 。 ItemDetailFragment 这个 ItemDetailFragment 表示单个 Item 详细信息。此片段在双窗格模式 (在平板电脑上) 包含在 ItemListActivity 中,在手机上则是包含在ItemDetailActivity中。其 Kotlin 代码如下 ...

weixin_33695082的博客 472

10 个对 Android 开发者有用的 Kotlin 扩展函数 #2

通过出色的 Kotlin 扩展提高您的工作效率。

虎哥LoveDroid 909

Kotlin Android开发·继承、构造和接口(2),字节Android高工面试

今天关于面试的分享就到这里,还是那句话,有些东西你不仅要懂,而且要能够很好地表达出来,能够让面试官认可你的理解,例如Handler机制,这个是面试必问之题。有些晦涩的点,或许它只活在面试当中,实际工作当中你压根不会用到它,但是你要知道它是什么东西。最后在这里小编分享一份自己收录整理上述技术体系图相关的几十套腾讯、头条、阿里、美团等公司2021年的面试题,把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节,由于篇幅有限,这里以图片的形式给大家展示一部分。还有。

2401_83692985的博客 953

第14章 使用Kotlin 进行 Android 开发(2)

14.2.3 实现后端 API 的接入 在本节中我们将实现后端 API 的接入及其数据展示的逻辑。 新建领域对象类 Movie data class Movie(val id: String, val title: String, val overview: String, val posterPath: String) { override f...

AI天才研究院 8822

探索Kotlin:从K1到K2

而K2 Kotlin就不一样了!不止于此,Kotlin还能在多平台上飞哦,可以一套代码适配多个平台,比如Android、iOS,甚至是Web。K1 Kotlin就像是Kotlin的早期版本,它跟传统的Java应用比较类似。如果你见过老一辈的安卓应用,它们很可能是用K1 Kotlin写的,比较稳定实用,不过现代化程度可能不够哦。但如果你追求创新,想要更高效的开发体验,还想在多平台上发力,K2 Kotlin是不二之选啦!总之,随着技术的发展,Kotlin在安卓开发中已经成为了一匹黑马,带来了无限的可能性。

double222222的博客 2132

安卓上的Kotlin:安卓 KTX、Kotlin Bootcamp Udacity等等

最近,谷歌展示了一系列的努力来改善Kotlin开发人员在安卓平台上的体验,其中包括安卓KTX、Kotlin Bootcamp Udacity课程、安卓Studio 3.2中的Lint支持等等。\\谷歌产品管理总监Stephanie Cuthbertson说, 自谷歌去年宣布支持Kotlin以来,开发者社区已经接纳了该语言,95%的开发人员说他们非常乐意使用Kotlin来进行安卓开发。使用Kotli...

cpongo5 143

Android studio 2 手把手教你使用kotlin插件

Kotlin for Android最近谷歌IO大会, 把kotlin纳入了Android开发首选语言, 估计这与谷歌和oracle一直在打官司的缘故分不开吧, 而且kotlin本身就很好用,不只是增加语法糖,而且kotlin-native是基于自己的runtime, 跨平台的.好了, 废话不多说,自己体会kotlin的好. 之前讲了 [android studio3.0预览版使用kotlin],

爱去玩的专栏 3803
上一篇: Android程序员的副业如何选择
下一篇: 【Android面试】一个offer暖你一整天
网络安全-XG
博客等级 码龄5年 1万+粉丝 2752原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值