【译】使用Kotlin从零开始写一个现代Android-项目-Part1

  • finalProduction: 上传Google Play 使用
  • demoProduction:该版本使用生产环境服务器Url,并且它有着GP上的版本没有的新功能,用户可以在Google play 旁边安装,然后可以进行新功能测试和提供反馈。
  • demoTesting:和demoProduction一样,只不过它用的是测试地址
  • mock: 对于我来说,作为开发人员和设计师而言都是很有用的。有时我们已经准备好设计,而我们的API仍未准备好。等待API准备就绪后再开始开发可不是好的解决方案。此构建变体为提供有mock数据,因此设计团队可以对其进行测试并提供反馈。对于保证项目进度真的很有帮助,一旦API准备就绪,我们便将开发转移到demoTesting环境。

在此应用程序中,我们将拥有所有这些变体。它们的applicationId和名称不同。 gradle 3.0.0 flavourDimension中有一个新的api,可让您混合不同的产品风味,因此您可以混合demominApi23风味。在我们的应用程序中,我们将仅使用“默认” 的flavorDimension。早app的build.gradle中,将此代码插入android {}下:

flavorDimensions “default”

productFlavors {

finalProduction {
dimension “default”
applicationId “me.mladenrakonjac.modernandroidapp”
resValue “string”, “app_name”, “Modern App”
}

demoProduction {
dimension “default”
applicationId “me.mladenrakonjac.modernandroidapp.demoproduction”
resValue “string”, “app_name”, “Modern App Demo P”
}

demoTesting {
dimension “default”
applicationId “me.mladenrakonjac.modernandroidapp.demotesting”
resValue “string”, “app_name”, “Modern App Demo T”
}

mock {
dimension “default”
applicationId “me.mladenrakonjac.modernandroidapp.mock”
resValue “string”, “app_name”, “Modern App Mock”
}
}

打开string.xml文件,删掉app_namestring资源,因此,我们才不会发生资源冲突,然后点击Sync Now,如果转到屏幕左侧的“构建变体”,则可以看到4个不同的构建变体,其中每个都有两种构建类型:“Debug”和“Release”,切换到demoProduction构建变体并运行它。然后切换到另一个并运行它。您就可以看到两个名称不同的应用程序。

3、ConstraintLayout

如果你打开activity_main.xml ,你可以看到跟布局是ConstraintLayout,如果你开发过iOS应用程序,你可能知道AutoLayoutConstraintLayout和它非常的相似,他们甚至用了相同的 Cassowary 算法。

<?xml version="1.0" encoding="utf-8"?>

<android.support.constraint.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:layout_width=“match_parent”
android:layout_height=“match_parent”
tools:context=“me.mladenrakonjac.modernandroidapp.MainActivity”>

Constraints可以帮我们描述View之间的关系。对于每一个View来说,应该有4个约束,每一边一个约束,在这种情况下,我们的View就被约束在了父视图的每一边了。

在Design Tab中,如果你将Hello World文本稍微向上移动,则在TextTab中将增加下面这行代码:

app:layout_constraintVertical_bias=“0.28”

Design tab 和 Text tab是同步的,我们在Design中移动视图,则会影响Text中的xml,反之亦然。垂直偏差描述了视图对其约束的垂直趋势。如果要使视图垂直居中,则应使用:

app:layout_constraintVertical_bias=“0.28”

我们让Activity只显示一个仓库,它有仓库的名字,star的数量,作者,并且还会显示是否有issue

要得到上面的布局设计,代码如下所示:

<?xml version="1.0" encoding="utf-8"?>

<android.support.constraint.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:layout_width=“match_parent”
android:layout_height=“match_parent”
tools:context=“me.mladenrakonjac.modernandroidapp.MainActivity”>

</android.support.constraint.ConstraintLayout>

不要被tools:text搞迷惑了,它的作用仅仅是让我们可以预览我们的布局。

我们可以注意到,我们的布局是扁平的,没有任何嵌套,你应该尽量少的使用布局嵌套,因为它会影响我们的性能。ConstraintLayout也可以在不同的屏幕尺寸下正常工作。

我有种预感,很快就能达到我们想要的布局效果了。

上面只是一些关于ConstraintLayout的少部分介绍,你也可以看一下关于ConstraintLayout使用的google code lab: https://codelabs.developers.g…

4. Data binding library

当我听到Data binding 库的时候,我的第一反应是:Butterknife已经很好了,再加上,我现在使用一个插件来从xml中获取View,我为啥要改变,来使用Data binding呢?但当我对Data binding有了更多的了解之后,我的它的感觉就像我第一次见到Butterknife一样,无法自拔。

Butterknife能帮我们做啥?

ButterKnife帮助我们摆脱无聊的findViewById。因此,如果您有5个视图,而没有Butterknife,则你有5 + 5行代码来绑定您的视图。使用ButterKnife,您只有我行代码就搞定。就是这样。

Butterknife的缺点是什么?

Butterknife仍然没有解决代码可维护问题,使用ButterKnife时,我经常发现自己遇到运行时异常,这是因为我删除了xml中的视图,而没有删除Activity/Fragment类中的绑定代码。另外,如果要在xml中添加视图,则必须再次进行绑定。真的很不好维护。你将浪费大量时间来维护View绑定。

那与之相比,Data Binding 怎么样呢?

有很多好处,使用Data Binding,你可以只用一行代码就搞定View的绑定,让我们看看它是如何工作的,首先,先将Data Binding 添加到项目:

// at the top of file
apply plugin: ‘kotlin-kapt’

android {
//other things that we already used
dataBinding.enabled = true
}
dependencies {
//other dependencies that we used
kapt “com.android.databinding:compiler:3.0.0-beta1”
}

请注意,数据绑定编译器的版本与项目build.gradle文件中的gradle版本相同:

classpath ‘com.android.tools.build:gradle:3.0.0-beta1’

然后,点击Sync Now,打开activity_main.xml,将Constraint Layout 用layout标签包裹

<?xml version="1.0" encoding="utf-8"?>

<android.support.constraint.ConstraintLayout
android:layout_width=“match_parent”
android:layout_height=“match_parent”
tools:context=“me.mladenrakonjac.modernandroidapp.MainActivity”>

</android.support.constraint.ConstraintLayout>

注意,你需要将所有的xml移动到layout 标签下面,然后点击Build图标或者使用快捷键Cmd + F9,我们需要构建项目来使Data Binding库为我们生成ActivityMainBinding类,后面在MainActivity中将用到它。

如果没有重新编译项目,你是看不到ActivityMainBinding的,因为它在编译时生成。

我们还没有完成绑定,我们只是定义了一个非空的 ActivityMainBinding 类型的变量。你会注意到我没有把? 放在 ActivityMainBinding 的后面,而且也没有初始化它。这怎么可能呢?lateinit 关键字允许我们使用非空的延迟被初始化的变量。和 ButterKnife 类似,在我们的布局准备完成后,初始化绑定需要在 onCreate 方法中进行。此外,你不应该在 onCreate 方法中声明绑定,因为你很有可能在 onCreate 方法外使用它。我们的 binding 不能为空,所以这就是我们使用 lateinit 的原因。使用 lateinit 修饰,我们不需要在每次访问它的时候检查 binding 变量是否为空。

我们初始化binding变量,你需要替换:

setContentView(R.layout.activity_main)

为:

binding = DataBindingUtil.setContentView(this, R.layout.activity_main)

就是这样,你成功的绑定了所有View,现在你可以访问它并且做一些更改,例如,我们将仓库名字改为Modern Android Medium Article:

binding.repositoryName.text = “Modern Android Medium Article”

如你所见,现在我们可以通过bingding变量来访问main_activity.xml的所有View了(前提是它们有id),这就是Data Binding 比ButterKnife 好用的原因。

kotlin的 Getters 和 setters

大概,你已经注意到了,我们没有像Java那样使用.setText(),我想在这里暂停一下,以说明与Java相比,Kotlin中的getter和setter方法如何工作的。

首先,你需要知道,我们为什么要使用getters和setters,我们用它来隐藏类中的变量,仅允许使用方法来访问这些变量,这样我们就可以向用户隐藏类中的细节,并禁止用户直接修改我们的类。假设我们用 Java 写了一个 Square 类:

public class Square {
private int a;

Square(){
a = 1;
}

public void setA(int a){
this.a = Math.abs(a);
}

public int getA(){
return this.a;
}

}

使用setA()方法,我们禁止了用户向Square类的a变量设置一个负数,因为正方形的边长一定是正数,要使用这种方法,我们必须将其设为私有,因此不能直接设置它。这也意味着我们不能直接获得a,需要给它定一个get方法来返回a,如果有10个变量,那么我们就得定义10个相似的get方法,写这样无聊的样板代码,通常会影响我们的心情。

Kotling使我们的开发人员更轻松了。如果你调用下面的代码:

var side: Int = square.a

这并不意味着你是在直接访问a变量,它和Java中调用getA()是相同的

int side = square.getA();

因为Kotlin自动生成默认的getter和setter。在Kotlin中,只有当您有特殊的setter或getter时,才应指定它。否则,Kotlin会为您自动生成:

var a = 1
set(value) { field = Math.abs(value) }

field ? 这又是个什么东西?为了更清楚明白,请看下面代码:

var a = 1
set(value) { a = Math.abs(value) }

这表明你在调用set方法中的set(value){},因为Kotlin的世界中,没有直接访问属性,这就会造成无限递归,当你调用a = something,会自动调用set方法。使用filed就能避免无限递归,我希望这能让你明白为什么要用filed关键字,并且了解getters和setters是如何工作的。

回到代码中继续,我将向你介绍Kotlin语言的另一个重要功能:apply函数:

class MainActivity : AppCompatActivity() {

lateinit var binding: ActivityMainBinding

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
binding.apply {
repositoryName.text = “Medium Android Repository Article”
repositoryOwner.text = “Mladen Rakonjac”
numberOfStarts.text = “1000 stars”

}
}
}

apply 允许你在一个实例上调用多个方法,我们仍然还没有完成数据绑定,还有更棒的事儿,让我们为仓库定义一个UI模型(这个是github仓库的数据模型Repository,它持有要展示的数据,请不要和Repository模式的中的Repository搞混淆了哈),要创建一个Kotlin class,点击New -> Kotlin File/Class :

class Repository(var repositoryName: String?,var repositoryOwner: String?,var numberOfStars: Int? ,var hasIssues: Boolean = false)

在Kotlin中,主构造函数是类头的一部分,如果你不想定义次构造函数,那就是这样了,数据类到此就完成了,构造函数没有参数分配给字段,没有setters和getters,整个类就一行代码。

回到MainActivity.kt,为Repository创建一个实例。

var repository = Repository(“Medium Android Repository Article”,
“Mladen Rakonjac”, 1000, true)

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img

img

img

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

文末

今天关于面试的分享就到这里,还是那句话,有些东西你不仅要懂,而且要能够很好地表达出来,能够让面试官认可你的理解,例如Handler机制,这个是面试必问之题。有些晦涩的点,或许它只活在面试当中,实际工作当中你压根不会用到它,但是你要知道它是什么东西。

最后在这里小编分享一份自己收录整理上述技术体系图相关的几十套腾讯、头条、阿里、美团等公司2021年的面试题,把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节,由于篇幅有限,这里以图片的形式给大家展示一部分。

还有 高级架构技术进阶脑图、Android开发面试专题资料,高级进阶架构资料 帮助大家学习提升进阶,也节省大家在网上搜索资料的时间来学习,也可以分享给身边好友一起学习。

【Android核心高级技术PDF文档,BAT大厂面试真题解析】

【算法合集】

【延伸Android必备知识点】

【Android部分高级架构视频学习资源】

**Android精讲视频领取学习后更加是如虎添翼!**进军BATJ大厂等(备战)!现在都说互联网寒冬,其实无非就是你上错了车,且穿的少(技能),要是你上对车,自身技术能力够强,公司换掉的代价大,怎么可能会被裁掉,都是淘汰末端的业务Curd而已!现如今市场上初级程序员泛滥,这套教程针对Android开发工程师1-6年的人员、正处于瓶颈期,想要年后突破自己涨薪的,进阶Android中高级、架构师对你更是如鱼得水,赶快领取吧!

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

DQUtKt3-1713440932656)]

【延伸Android必备知识点】

[外链图片转存中…(img-vJX2E5L2-1713440932657)]

【Android部分高级架构视频学习资源】

**Android精讲视频领取学习后更加是如虎添翼!**进军BATJ大厂等(备战)!现在都说互联网寒冬,其实无非就是你上错了车,且穿的少(技能),要是你上对车,自身技术能力够强,公司换掉的代价大,怎么可能会被裁掉,都是淘汰末端的业务Curd而已!现如今市场上初级程序员泛滥,这套教程针对Android开发工程师1-6年的人员、正处于瓶颈期,想要年后突破自己涨薪的,进阶Android中高级、架构师对你更是如鱼得水,赶快领取吧!

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

  • 25
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,下面是一个简单的 Android 音乐播放器示例,使用 Kotlin 语言: 首先,需要在 AndroidManifest.xml 文件中添加以下权限: ```xml <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.WAKE_LOCK" /> ``` 然后,在 MainActivity.kt 文件中添加以下代码: ```kotlin import android.media.MediaPlayer import android.os.Bundle import android.os.Handler import android.os.Message import android.util.Log import android.view.View import android.widget.SeekBar import androidx.appcompat.app.AppCompatActivity import kotlinx.android.synthetic.main.activity_main.* import java.io.IOException class MainActivity : AppCompatActivity() { private var mediaPlayer: MediaPlayer? = null private var isPlaying = false private var isPaused = false private var currentSongIndex = 0 private var songList = mutableListOf<String>() private lateinit var seekBarHandler: Handler private lateinit var runnable: Runnable override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) // Add songs to playlist songList.add("/sdcard/Music/Song1.mp3") songList.add("/sdcard/Music/Song2.mp3") songList.add("/sdcard/Music/Song3.mp3") // Initialize media player mediaPlayer = MediaPlayer() // Set up seek bar handler to update seek bar position seekBarHandler = object : Handler() { override fun handleMessage(msg: Message) { super.handleMessage(msg) if (mediaPlayer != null) { val currentPosition = mediaPlayer!!.currentPosition seek_bar.progress = currentPosition val duration = mediaPlayer!!.duration val progress = (currentPosition.toFloat() / duration.toFloat() * 100).toInt() seek_bar_percent.text = "$progress%" if (mediaPlayer!!.isPlaying) { runnable = Runnable { sendMessageDelayed(obtainMessage(0), 1000) } postDelayed(runnable, 1000) } } } } // Set up seek bar listener to seek to specific position seek_bar.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener { override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) { if (fromUser) { mediaPlayer?.seekTo(progress) } } override fun onStartTrackingTouch(seekBar: SeekBar) { seekBarHandler.removeCallbacks(runnable) } override fun onStopTrackingTouch(seekBar: SeekBar) { seekBarHandler.removeCallbacks(runnable) mediaPlayer?.seekTo(seekBar.progress) runnable = Runnable { seekBarHandler.sendEmptyMessage(0) } seekBarHandler.postDelayed(runnable, 1000) } }) // Set up play button listener to play or pause current song btn_play.setOnClickListener { if (!isPlaying) { if (isPaused) { mediaPlayer?.start() isPaused = false } else { playSong(currentSongIndex) } isPlaying = true btn_play.setImageResource(R.drawable.ic_pause) } else { mediaPlayer?.pause() isPaused = true isPlaying = false btn_play.setImageResource(R.drawable.ic_play) } } // Set up next button listener to play next song in playlist btn_next.setOnClickListener { if (currentSongIndex < songList.size - 1) { currentSongIndex++ } else { currentSongIndex = 0 } playSong(currentSongIndex) } // Set up previous button listener to play previous song in playlist btn_prev.setOnClickListener { if (currentSongIndex > 0) { currentSongIndex-- } else { currentSongIndex = songList.size - 1 } playSong(currentSongIndex) } } override fun onDestroy() { super.onDestroy() mediaPlayer?.stop() mediaPlayer?.release() mediaPlayer = null seekBarHandler.removeCallbacks(runnable) } private fun playSong(index: Int) { try { mediaPlayer?.reset() mediaPlayer?.setDataSource(songList[index]) mediaPlayer?.prepare() mediaPlayer?.start() seek_bar.max = mediaPlayer!!.duration seekBarHandler.sendEmptyMessage(0) isPlaying = true btn_play.setImageResource(R.drawable.ic_pause) } catch (e: IOException) { Log.e("MainActivity", "Error playing song: ${e.message}") } } } ``` 这个示例实现了以下功能: - 播放/暂停当前歌曲; - 播放下一首歌曲; - 播放上一首歌曲; - 显示歌曲进度,并允许拖动进度条到指定位置。 请注意,这只是一个简单的示例,实际的音乐播放器可能需要更复杂的功能。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值