《Kotlin从零到精通Android开发》欧阳燊(二)


androidExtensions {

    experimental = true

}



上面采用了新的适配器插件,似乎已经大功告成,可是依然要书写单独的适配器代码,仔细研究发现这个RecyclerStaggeredAdapter还有三个要素是随着具体业务而变化的,包括:

1、列表项的布局文件资源编码,如R.layout.item_recycler_staggered;

2、列表项信息的数据结构名称,如RecyclerInfo;

3、对各种控件对象的设置操作,如ItemHolder类的bind方法;

除了以上三个要素,RecyclerStaggeredAdapter内部的其余代码都是允许复用的,因此,接下来的工作就是想办法把这三个要素抽象为公共类的某种变量。对于第一个的布局编码,可以考虑将其作为一个整型的输入参数;对于第二个的数据结构,可以考虑定义一个模板类,在外部调用时再指定具体的数据类;对于第三个的bind方法,若是Java编码早已束手无策,现用Kotlin编码正好将该方法作为一个函数参数传入。依照三个要素的三种处理对策,进而提炼出来了循环适配器的通用类RecyclerCommonAdapter,详细的Kotlin代码示例如下:


//循环视图通用适配器

//将具体业务中会变化的三类要素抽取出来,作为外部传进来的变量。这三类要素包括:

//布局文件对应的资源编号、列表项的数据结构、各个控件对象的初始化操作

class RecyclerCommonAdapter<T>(context: Context, private val layoutId: Int, private val items: List<T>, val init: (View, T) -> Unit): RecyclerBaseAdapter<RecyclerCommonAdapter.ItemHolder<T>>(context) {

 

    override fun getItemCount(): Int = items.size

 

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {

        val view: View = inflater.inflate(layoutId, parent, false)

        return ItemHolder<T>(view, init)

    }

 

    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {

        val vh: ItemHolder<T> = holder as ItemHolder<T>

        vh.bind(items.get(position))

    }

 

    //注意init是个函数形式的输入参数

    class ItemHolder<in T>(val view: View, val init: (View, T) -> Unit) : RecyclerView.ViewHolder(view) {

        fun bind(item: T) {

            init(view, item)

        }

    }

}



有了这个通用适配器,外部使用适配器只需像函数调用那样传入这三种变量就好了,具体调用的Kotlin代码如下所示:


//第二种方式:使用把三类可变要素抽象出来的通用适配器

val adapter = RecyclerCommonAdapter(this, R.layout.item_recycler_staggered, RecyclerInfo.defaultStag,

        {view, item ->

            val iv_pic = view.findViewById(R.id.iv_pic) as ImageView

            val tv_title = view.findViewById(R.id.tv_title) as TextView

            iv_pic.setImageResource(item.pic_id)

            tv_title.text = item.title

        })

rv_staggered.adapter = adapter



最终出炉的适配器仅有十行代码不到,其中的关键技术——函数参数真是不鸣则已、一鸣惊人。至此本节的适配器实现过程终于落下帷幕,一路上可谓是过五关斩六将,硬生生把数十行的Java代码压缩到不到十行的Kotlin代码,经过不断迭代优化方取得如此彪炳战绩。尤其是最后的两种实现方式,分别运用了Kotlin的多项综合技术,才能集Kotlin精妙语法之大成。

7.2使用材质设计


MaterialDesign库提供了协调布局CoordinatorLayout,AppBarLayout,CollapsingToolbarLayout

协调布局CoordinatorLayout

继承自ViewGroup

对齐方式:layout_gravity,

子视图位置:app:layout_anchor?app:layout_anchorGravity

行为:app:layout_behavior

FloatingActionButton 悬浮按钮

悬浮按钮会悬浮在其他视图之上

隐藏和显示悬浮按钮时会播放切换动画,hide和show方法

悬浮按钮默认会随着便签条Snackbar的出现或消失动态调整位置

###工具栏Toolbar

Android 5.0之后使用Toolbar代替ActionBar

不过为了兼容老版本,ActionBar仍然保留,可是ActionBar和Toolbar都占着顶部导航栏的位置,所以想引入Toolbar就得关闭ActionBar,具体步骤如下:

1.定义一个style


<style name="AppCompatTheme" parent= "Theme.AppCompat.Light.NoActionBar" />



2.清单文件中的application标签的


android:theme="@style/AppCompatTheme"



3.创建一个布局文件


<android.support.v7.widget.Toolbar

android:id="@+id/tl_head"

android:layout_width="match_parent"

android:layout_height="wrap_content" />




extends AppCompatActivity



5.onCreate中setSupportActionBar

Toolbar的常用方法

Toolbar比ActionBar灵活,主要便是它提供了多个方法来修改控件风格,下面是Toolbar的常用方法:

setLogo : 设置工具栏图标。

setTitle : 设置标题文字。

setTitleTextAppearance : 设置标题的文字风格。

setTitleTextColor : 设置标题的文字颜色。

setSubtitle : 设置副标题文字。副标题在标题下方。

setSubtitleTextAppearance : 设置副标题的文字风格。

setSubtitleTextColor : 设置副标题的文字颜色。

setNavigationIcon : 设置导航图标。导航图标在工具栏图标左边。

setNavigationOnClickListener : 设置导航图标的点击监听器。

setOverflowIcon : 设置溢出菜单的按钮图标。

showOverflowMenu : 显示溢出菜单图标。

hideOverflowMenu : 隐藏溢出菜单图标。

dismissPopupMenus : 关闭已弹出的菜单。

应用栏布局AppBarLayout

AppBarLayout 继承自LinearLayout,所以它具备LinearLayout的所有属性方法

可折叠工具栏布局CollapsingToolbarLayout

image.png

7.3实现页面切换


翻页视图ViewPager

image.png

Fragment

Tablayout

image.png

广播Broadcast


###收发临时广播

1.一人发送广播,多人接收处理

2.对于发送者来说,不需要考虑接收者

3.对于接收者来说,要自行过滤符合条件的广播

sendBroadcast,registerReceiver,unregisterReceiver

静态属性如果是个常量,就还要添加修饰符const


companion object {

//静态属性如果是个常量,就还要添加修饰符const

const val EVENT = "com.example.complex.fragment.BroadcastFragment"

}



编译时常量(const):真正意义的常量

运行时常量(val):可以在声明的时候赋值,运行后被赋值

系统广播

静态注册和动态注册

总结


视图排列:下拉框,列表视图,网格视图,循环视图,适配器的延迟初始化属性

材质设计:状态栏,工具栏

页面切换:ViewPager,fragment,tablayout

广播,两种常量

第八章 Kotlin进行数据存储

==============================================================================

8.1使用共享参数SharedPreferences


共享参数读写模板Preference


class Perference<T>(val context: Context, val name: String, val default: T)

    : ReadWriteProperty<Any?, T> {



    /***

     * 通过属性代理初始化共享参数对象

     * lazy:第一次使用时执行初始化

     */

    val prefs: SharedPreferences by lazy { context.getSharedPreferences("default", Context.MODE_PRIVATE) }



    /***

     * 接管属性值的获取行为

     * *:表示一个不确定的类型

     */

    override fun getValue(thisRef: Any?, property: KProperty<*>): T {

        return findPreference(name, default)

    }



    /***

     * 接管属性值的修改行为

     */

    override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {

        putPreference(name, value)

    }



    //利用with函数定义临时的命名空间

    private fun <T> findPreference(name: String, default: T): T = with(prefs) {

        val res: Any = when (default) {

            is Long -> getLong(name, default)

            is String -> getString(name, default)

            is Int -> getInt(name, default)

            is Boolean -> getBoolean(name, default)

            is Float -> getFloat(name, default)

            else -> throw IllegalArgumentException("This type can be saved into Preferences")

        }

        return res as T

    }



    private fun <T> putPreference(name: String, value: T) = with(prefs.edit()) {

        //putInt、putString等方法返回Editor对象

        when (value) {

            is Long -> putLong(name, value)

            is String -> putString(name, value)

            is Int -> putInt(name, value)

            is Boolean -> putBoolean(name, value)

            is Float -> putFloat(name, value)

            else -> throw IllegalArgumentException("This type can be saved into Preferences")

        }.apply() //commit方法和apply方法都表示提交修改

    }



}





调用:


private var name: String by Perference(this, "name", "")

private var age: String by Perference(this, "age", "")



后续代码若给委托属性赋值,则立即触发写入动作

属性代理等黑科技

这个高大上的Preference用了哪些黑科技?

用到了模板类,委托属性,lazy修饰符,with函数

模板类:

因为共享参数允许保存的类型包括,整型,浮点型,字符串等,所以要定义成模板类,参数类型在调用时再指定

除了T,还有Any和*

1、T是抽象的泛型,在模板类中用来占位子,外部调用模板类时才能确定T的具体类型;

2、Any是Kotlin的基本类型,所有Kotlin类都从Any派生而来,故而它相当于Java里面的Object;

3、_星号表示一个不确定的类型,同样也是在外部调用时才能确定,这点跟T比较像,但T出现在模板类的定义中,而_与模板类无关,它出现在单个函数定义的参数列表中,因此星号相当于Java里面的问号?;

委托属性/属性代理

by表示代理动作,第五章的例子是接口代理或称类代理,而这里则为属性代理,所谓属性代理,是说该属性的类型不变,但是属性的读写行为被后面的类接管了。类似银行推出了“委托代扣”的业务,只要用户跟银行签约并指定委托扣费的电力账户,那么在每个月指定时间,银行会自动从用户银行卡中扣费并缴纳给指定的电力账户,如此省却了用户的人工操作。

现实生活中的委托扣费场景,对应到共享参数这里,开发者的人工操作指的是手工编码从SharedPreferences类读取数据和保存数据,而自动操作指的是约定代理的属性自动通过模板类Preference完成数据的读取和保存,也就是说,Preference接管了这些属性的读写行为,接管后的操作则是模板类的getValue和setValue方法。属性被接管的行为叫做属性代理,而被代理的属性称作委托属性。

lazy修饰符

模板类Preference声明了一个共享参数的prefs对象,其中用到了关键字lazy,lazy的意思是懒惰,表示只在该属性第一次使用时执行初始化。联想到Kotlin还有类似的关键字名叫lateinit,意思是延迟初始化,加上lazy可以归纳出Kotlin变量的三种初始化操作,具体说明如下:

1、声明时赋值:这是最常见的变量初始化,在声明某个变量时,立即在后面通过等号“=”给它赋予具体的值。

2、lateinit延迟初始化:变量声明时没有马上赋值,但该变量仍是个非空变量,何时初始化由开发者编码决定。

3、lazy首次使用时初始化:声明变量时指定初始化动作,但该动作要等到变量第一次使用时才进行初始化。

此处的prefs对象使用lazy规定了属性值在首次使用时初始化,且初始化动作通过by后面的表达式来指定,即“{ context.getSharedPreferences(“default”, Context.MODE_PRIVATE) }”。连同大括号在内的这个表达式,其实是个匿名实例,它内部定义了prefs对象的初始化语句,并返回SharedPreferences类型的变量值。

with函数

with函数的书写格式形如“with(函数头语句) { 函数体语句 }”,看这架势,with方法的函数语句分为两部分,详述如下:

1、函数头语句:头部语句位于紧跟with的圆括号内部。它先于函数体语句执行,并且头部语句返回一个对象,函数体语句在该对象的命名空间中运行;即体语句可以直接调用该对象的方法,而无需显式指定该对象的实例名称。

2、函数体语句:体语句位于常规的大括号内部。它要等头部语句执行完毕才会执行,同时体语句在头部语句返回对象的命名空间中运行;即体语句允许直接调用头部对象的方法,而无需显式指定该对象的实例名称。

综上所述,在模板类Preference的编码过程中,联合运用了Kotlin的多项黑科技,方才实现了优于Java的共享参数操作方式

实现记住密码的功能


private var phone: String by Preference(this, "phone", "")

private var password: String by Preference(this, "password", "")



由于上面的语句已经自动从共享参数获取属性值,接下来若要往共享参数保存新的属性值,只需修改委托属性的变量值即可。

8.2使用数据库SQLite


数据库帮助器SQLiteOpenHelper

SQLiteDatabase(数据库管理类):获取数据库实例

SQLiteOpenHelper:操作数据表的API

更安全的ManagedSQLiteOpenHelper

SQLiteOpenHelper开发者需要在操作表之前打开数据库连接,结束后关闭数据库连接

于是Kotlin结合anko库推出了改良版的SQLite管理工具:ManagedSQLiteOpenHelper


use {

        //1、插入记录

        //insert(...)

        //2、更新记录

        //update(...)

        //3、删除记录

        //delete(...)

        //4、查询记录

        //query(...)或者rawQuery(...)

    }




class UserDBHelper(var context: Context, private var DB_VERSION: Int=CURRENT_VERSION) : ManagedSQLiteOpenHelper(context, DB_NAME, null, DB_VERSION) {

    companion object {

        private val TAG = "UserDBHelper"

        var DB_NAME = "user.db" //数据库名称

        var TABLE_NAME = "user_info" //表名称

        var CURRENT_VERSION = 1 //当前的最新版本,如有表结构变更,该版本号要加一

        private var instance: UserDBHelper? = null

        @Synchronized

        fun getInstance(ctx: Context, version: Int=0): UserDBHelper {

            if (instance == null) {

                //如果调用时没传版本号,就使用默认的最新版本号

                instance = if (version>0) UserDBHelper(ctx.applicationContext, version)

                            else UserDBHelper(ctx.applicationContext)

            }

            return instance!!

        }

    }

 

    override fun onCreate(db: SQLiteDatabase) {

        Log.d(TAG, "onCreate")

        val drop_sql = "DROP TABLE IF EXISTS $TABLE_NAME;"

        Log.d(TAG, "drop_sql:" + drop_sql)

        db.execSQL(drop_sql)

        val create_sql = "CREATE TABLE IF NOT EXISTS $TABLE_NAME (" +

            "_id INTEGER PRIMARY KEY  AUTOINCREMENT NOT NULL," +

            "name VARCHAR NOT NULL," + "age INTEGER NOT NULL," +

            "height LONG NOT NULL," + "weight FLOAT NOT NULL," +

            "married INTEGER NOT NULL," + "update_time VARCHAR NOT NULL" +

            //演示数据库升级时要先把下面这行注释

            ",phone VARCHAR" + ",password VARCHAR" + ");"

        Log.d(TAG, "create_sql:" + create_sql)

        db.execSQL(create_sql)

    }

 

    override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {

        Log.d(TAG, "onUpgrade oldVersion=$oldVersion, newVersion=$newVersion")

        if (newVersion > 1) {

            //Android的ALTER命令不支持一次添加多列,只能分多次添加

            var alter_sql = "ALTER TABLE $TABLE_NAME ADD COLUMN phone VARCHAR;"

            Log.d(TAG, "alter_sql:" + alter_sql)

            db.execSQL(alter_sql)

            alter_sql = "ALTER TABLE $TABLE_NAME ADD COLUMN password VARCHAR;"

            Log.d(TAG, "alter_sql:" + alter_sql)

            db.execSQL(alter_sql)

        }

    }

 

    fun delete(condition: String): Int {

        var count = 0

        use {

            count = delete(TABLE_NAME, condition, null)

        }

        return count

    }

 

    fun insert(info: UserInfo): Long {

        val infoArray = mutableListOf(info)

        return insert(infoArray)

    }

 

    fun insert(infoArray: MutableList<UserInfo>): Long {

        var result: Long = -1

        for (i in infoArray.indices) {

            val info = infoArray[i]

            var tempArray: List<UserInfo>

            // 如果存在同名记录,则更新记录

            // 注意条件语句的等号后面要用单引号括起来

            if (info.name.isNotEmpty()) {

                val condition = "name='${info.name}'"

                tempArray = query(condition)

                if (tempArray.size > 0) {

                    update(info, condition)

                    result = tempArray[0].rowid

                    continue

                }

            }

            // 如果存在同样的手机号码,则更新记录

            if (info.phone.isNotEmpty()) {

                val condition = "phone='${info.phone}'"

                tempArray = query(condition)

                if (tempArray.size > 0) {

                    update(info, condition)

                    result = tempArray[0].rowid

                    continue

                }

            }

            // 不存在唯一性重复的记录,则插入新记录

            val cv = ContentValues()

            cv.put("name", info.name)

            cv.put("age", info.age)

            cv.put("height", info.height)

            cv.put("weight", info.weight)

            cv.put("married", info.married)

            cv.put("update_time", info.update_time)

            cv.put("phone", info.phone)

            cv.put("password", info.password)

            use {

                result = insert(TABLE_NAME, "", cv)

            }

            // 添加成功后返回行号,失败后返回-1

            if (result == -1L) {

                return result

            }

        }

        return result

    }

 

    @JvmOverloads

    fun update(info: UserInfo, condition: String = "rowid=${info.rowid}"): Int {

        val cv = ContentValues()

        cv.put("name", info.name)

        cv.put("age", info.age)

        cv.put("height", info.height)

        cv.put("weight", info.weight)

        cv.put("married", info.married)

        cv.put("update_time", info.update_time)

        cv.put("phone", info.phone)

        cv.put("password", info.password)

        var count = 0

        use {

            count = update(TABLE_NAME, cv, condition, null)

        }

        return count

    }

 

    fun query(condition: String): List<UserInfo> {

        val sql = "select rowid,_id,name,age,height,weight,married,update_time,phone,password from $TABLE_NAME where $condition;"

        Log.d(TAG, "query sql: " + sql)

        var infoArray = mutableListOf<UserInfo>()

        use {

            val cursor = rawQuery(sql, null)

            if (cursor.moveToFirst()) {

                while (true) {

                    val info = UserInfo()

                    info.rowid = cursor.getLong(0)

                    info.xuhao = cursor.getInt(1)

                    info.name = cursor.getString(2)

                    info.age = cursor.getInt(3)

                    info.height = cursor.getLong(4)

                    info.weight = cursor.getFloat(5)

                    //SQLite没有布尔型,用0表示false,用1表示true

                    info.married = if (cursor.getInt(6) == 0) false else true

                    info.update_time = cursor.getString(7)

                    info.phone = cursor.getString(8)

                    info.password = cursor.getString(9)

                    infoArray.add(info)

                    if (cursor.isLast) {

                        break

                    }

                    cursor.moveToNext()

                }

            }

            cursor.close()

        }

        return infoArray

    }

 

    fun queryByPhone(phone: String): UserInfo {

        val infoArray = query("phone='$phone'")

        val info: UserInfo = if (infoArray.size>0) infoArray[0] else UserInfo()

        return info

    }

 

    fun deleteAll(): Int = delete("1=1")

 

    fun queryAll(): List<UserInfo> = query("1=1")

 

}





记得加:


compile "org.jetbrains.anko:anko-sqlite:$anko_version"



调用:


var helper: UserDBHelper = UserDBHelper.getInstance(this)

val userArray = helper.queryAll()



优化记住密码功能

共享参数实现了记住密码的功能,但是只能记住一个账号的。可以采用数据库

8.3 文件I/O操作


###文件保存空间

手机的存储空间分为内部存储和外部存储

内部存储放的是手机系统以及各应用的安装目录

外部存储放的是公共文件,如图片,视频,文档等

为了不影响系统的流畅运行,App运行过程中要处理的文件都保存在外部存储空间

为保证App正常读写外部存储,需要在AndroidManifest中增加权限


<!-- SD读写权限 -->

<uses-

permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

<uses-

permission android:name="android.permission.READ_EXTERNAL_STORAG" />

<uses-

permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"

/>



Android7.0加强了SD卡的权限管理,系统默认关闭了外部存储的公共空间,外部存储的私有空间依然可以正常读写

因为Android把外部存储分成了2块区域,一块是所有应用都能访问的公共空间,一块是只有应用自己才可以访问的专享空间。当然,您的应用被卸载,那么这块专享空间文件目录也就没有了

获取公共空间的存储路径:Environment.getExternalStoragePublicDirectory

获取应用私有空间的存储路径:getExternalFilesDir


//公共路径

val publicPath = Environment.getExternalStoragePublicDirectory

(Environment.DIRECTORY_DOWNLOADS).toString();

//私有路径

val privatePath = getExternalFilesDir(Environment. DIRECTORY_DO

WNLOADS).toString();

tv_file_path.text = "{publicPath}" ,${privatePath}" 

"Android7.0之后默认禁止访问公共存储目录



image.png

读写文本文件

Kotlin利用拓展函数功能添加了一些常用的文件内容读写方法,一行代码搞定问题


//比如文本写入文件

File(file_path).writeText(content)



若要往源文件追加文本,则可调用appendText方法


readText:读取文本形式的文件内容

readLines:按行读取文件内容

val content = File(file_path).readText()



读写图片文件

写入


fun saveImage(path: String, bitmap: Bitmap) {

try {

val file = File(path)

val fos: OutputStream = file.outputStream()

//压缩格式为JPEG,压缩质量为80%

bitmap.compress(Bitmap.CompressFormat.JPEG, 80, fos)

fos.flush()

fos.close()

} catch (e: Exception) {

e.printStackTrace()

}

}



读取:


//方式一

val bytes = File(file_path).readBytes()

val bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.size)



//方式二

val fis = File(file_path).inputStream()

val bitmap = BitmapFactory.decodeStream(fis)

fis.close()



//方式三

val bitmap = BitmapFactory.decodeFile(file_path)



遍历文件目录

Kotlin新概念:文件树


var fileNames: MutableList<String> = mutableListOf()

//在该目录下走一圈,得到文件目录树结构

val fileTree: FileTreeWalk = File(mPath).walk()

fileTree.maxDepth(1) //需遍历的目录层级为1,即无需检查子目录

.filter { it.isFile } //只挑选文件,不处理文件夹

.filter { it.extension == "txt" } //选择拓展名为txt的文本文件

.forEach { fileNames.add(it.name) } //循环处理符合条件的文件



改进后:


var fileNames: MutableList<String> = mutableListOf()

//在该目录下走一圈,得到文件目录树结构

val fileTree: FileTreeWalk = File(mPath).walk()

fileTree.maxDepth(1) //需遍历的目录层级为1,即无需检查子目录

.filter { it.isFile } //只挑选文件,不处理文件夹

.filter { it.extension in listOf("png","jpg") } //选择拓展名为png和jpg的图片文件

.forEach { fileNames.add(it.name) } //循环处理符合条件的文件



8.4 Application全局变量

方式一:


class MainApplication : Application() {

override fun onCreate() {

super.onCreate()

instance = this

}

//单例化的第一种方式:声明一个简单的Application属性

companion object {

//声明可空属性

private var instance: MainApplication? = null

fun instance() = instance!!

//声明延迟初始化属性

//private lateinit var instance: MainApplication

//fun instance() = instance

}

}





方式二


class MainApplication : Application() {

override fun onCreate() {

super.onCreate()

instance = this

}

//单例化的第二种方式:利用系统自带的Delegates生成委托属性

companion object {

private var instance: MainApplication by Delegates.notNull()

fun instance() = instance

}

}





前两种单例都只完成了非空校验,还不是严格意义上的单例化,真正的单例化有且仅有一次的赋值操作,尽管前两种单例化并未实现唯一赋值功能,不多大多数场合已经够用了。

那么怎实现唯一赋值的的单例化?需要开发者自己写一个校验赋值次数的行为类


class MainApplication : Application() {

override fun onCreate() {

super.onCreate()

instance = this

}

//单例化的第三种方式:自定义一个非空且只能一次性赋值的委托属性

companion object {

private var instance: MainApplication by NotNullSingleValueVar(

)

fun instance() = instance

}

//定义一个属性管理类,进行非空和重复赋值的判断

private class NotNullSingleValueVar<T>

() : ReadWriteProperty<Any?, T> {

private var value: T? = null

override fun getValue(thisRef: Any?, property: KProperty<*>): T

{

return value ?: throw IllegalStateException("application no

t initialized")

}

override fun setValue(thisRef: Any?, property: KProperty<*>, va

lue: T) {

this.value = if (this.value == null) value

else throw IllegalStateException("application already initi

alized")

}

}

}



上述代码,自定义的代理行为在getValue方法中进行非空校验,在setValue方法中进行重复赋值的校验,按照要求接管了委托属性的读写行为。

利用Application实现全局变量

适合在Application中保持的全局变量主要有一下几类数据

1.频繁读取的信息,如用户名,手机号等

2.网络上获取的临时数据,节约流量也为了减少用户的等待时间,暂时放在内存中供下次使用,例如logo,图片

3.容易因频繁分配内存而导致内存泄漏的对象,例如Handle线程池ThreadPool等

总结

1.利用工具类Preference进行共享参数的键值对管理工作,并掌握委托属性,lazy修饰符,with函数的基本用法

2.使用Kotlin的ManagedSQLiteOpenHelper管理数据库

3.使用Kotlin的文件I/O函数进行文件处理,包括文本文件读写,图片文件读写,文件目录遍历等

4.Kotlin实现Application单例化,通过Application操作全局变量

第九章 Kotlin自定义控件

=============================================================================

9.1自定义普通视图


构造对象

自定义属性的步骤:

  1. 在res\values目录下创建attrs.xml,文件内容如下所示,其中declare-styleable的name属性值表示新视图的名称,两个attr节点表示新增的两个属性分别是textColor和textSize:

<resources>

    <declare-styleable name="CustomPagerTab">

        <attr name="textColor" format="color" />

        <attr name="textSize" format="dimension" />

    </declare-styleable>

</resources>



  1. 在模块的widget目录下创建CustomPagerTab.java,填入以下自定义视图的代码:

public class CustomPagerTab extends PagerTabStrip {

    private int textColor = Color.BLACK;

    private int textSize = 15;

 

    public CustomPagerTab(Context context) {

        super(context);

    }

    

    public CustomPagerTab(Context context, AttributeSet attrs) {

        super(context, attrs);

        //构造函数从attrs.xml读取CustomPagerTab的自定义属性

        if (attrs != null) {

            TypedArray attrArray=getContext().obtainStyledAttributes(attrs, R.styleable.CustomPagerTab);

            textColor = attrArray.getColor(R.styleable.CustomPagerTab_textColor, textColor);

            textSize = attrArray.getDimensionPixelSize(R.styleable.CustomPagerTab_textSize, textSize);

            attrArray.recycle();

        }

        setTextColor(textColor);

        setTextSize(TypedValue.COMPLEX_UNIT_SP, textSize);

    }

    

//    //PagerTabStrip没有三个参数的构造函数

//    public PagerTab(Context context, AttributeSet attrs, int defStyleAttr) {

//    }

}







  1. 布局文件的根节点增加自定义的命名空间声明,如“xmlns:app=“http://schemas.android.com/apk/res-auto””;并把android.support.v4.view.PagerTabStrip的节点名称改为自定义视图的全路径名称如“com.example.custom.widget.PagerTab”,同时在该节点下指定新增的两个属性即app:textColor与app:textSize。修改之后的布局文件代码如下:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"

    xmlns:app="http://schemas.android.com/apk/res-auto"

    android:layout_width="match_parent"

    android:layout_height="match_parent"

    android:orientation="vertical"

    android:padding="10dp" >

 

    <android.support.v4.view.ViewPager

        android:id="@+id/vp_content"

        android:layout_width="match_parent"

        android:layout_height="400dp" >

 

        <com.example.custom.widget.CustomPagerTab

            android:id="@+id/pts_tab"

            android:layout_width="wrap_content"

            android:layout_height="wrap_content"

            app:textColor="@color/red"

            app:textSize="17sp" />

    </android.support.v4.view.ViewPager>

</LinearLayout>



上述自定义属性的三个步骤,其中第二步骤涉及到Java代码,接下来用Kotlin改写CustomPagerTab类的代码,主要改动有以下两点:

1、原来的两个构造函数,合并为带默认参数的一个主构造函数,并且直接跟在类名后面;

2、类名后面要加上注解“@JvmOverloads constructor”,表示该类支持被Java代码调用。因为布局文件中引用了自定义视图的节点,系统是通过SDK里的Java代码找到自定义视图类,所以凡是自定义视图都要加上该注解,否则App运行时会抛出异常。

下面是CustomPagerTab类改写之后的Kotlin代码:


//自定义视图务必要在类名后面增加“@JvmOverloads constructor”,因为布局文件中的自定义视图必须兼容Java

class CustomPagerTab @JvmOverloads constructor(context: Context, attrs: AttributeSet?=null) : PagerTabStrip(context, attrs) {

    private var txtColor = Color.BLACK

    private var textSize = 15

    

    init {

        txtColor = Color.BLACK

        textSize = 15

        //初始化时从attrs.xml读取CustomPagerTab的自定义属性

        if (attrs != null) {

            val attrArray = getContext().obtainStyledAttributes(attrs, R.styleable.CustomPagerTab)

            txtColor = attrArray.getColor(R.styleable.CustomPagerTab_textColor, txtColor)

            textSize = attrArray.getDimensionPixelSize(R.styleable.CustomPagerTab_textSize, textSize)

            attrArray.recycle()

        }

        setTextColor(txtColor)

        setTextSize(TypedValue.COMPLEX_UNIT_SP, textSize.toFloat())

    }

}





测量尺寸

绘制

完整的自定义视图有3部分组成:

1.定义构造函数,读取自定义属性值并初始化

2.重写测量函数onMesure,计算该视图的宽高尺寸

3.重写绘图函数onDraw(控件+布局)或者dispatchDraw(布局),在当前视图内部绘制指定形状


public class RoundTextView extends TextView {

public RoundTextView(Context context) {

super(context);

}

public RoundTextView(Context context, AttributeSet attrs) {

super(context, attrs);

}

public RoundTextView(Context context, AttributeSet attrs, int defSt

yle) {

super(context, attrs, defStyle);

}

//控件只能重写onDraw方法

@Override

protected void onDraw(Canvas canvas) {

super.onDraw(canvas);

Paint paint = new Paint();

paint.setColor(Color.RED); 

paint.setStrokeWidth(2); 

paint.setStyle(Style.STROKE); 

paint.setAntiAlias(true);

RectF rectF = new RectF(1, 1, this.getWidth()-1, this.getHeight()-1);



canvas.drawRoundRect(rectF, 10, 10, paint);

}

}



//“@JvmOverloads constructor”

class RoundTextView @JvmOverloads constructor(context: Context, attrs:

AttributeSet?

=null, defStyle: Int=0) : TextView(context, attrs, defStyle) {



override fun onDraw(canvas: Canvas) {

super.onDraw(canvas)



val paint = Paint()

paint.color = Color.RED 

paint.strokeWidth = 2f 

paint.style = Style.STROKE 

paint.isAntiAlias = true 

val rectF = RectF(1f, 1f, (this.width - 1).toFloat(), (this.hei

ght - 1). toFloat())

canvas.drawRoundRect(rectF, 10f, 10f, paint)

}

}






public class RoundLayout extends LinearLayout {

public RoundLayout(Context context) {

super(context);

}

public RoundLayout(Context context, AttributeSet attrs) {

super(context, attrs);

}

public RoundLayout(Context context, AttributeSet attrs, int defStyl

e) {

super(context, attrs, defStyle);

}

//布局一般重写dispatchDraw方法,防止绘图效果 被上面的控件覆盖

@Override

protected void dispatchDraw(Canvas canvas) {

super.dispatchDraw(canvas);

Paint paint = new Paint();

paint.setColor(Color.BLUE);

paint.setStrokeWidth(2);

paint.setStyle(Style.STROKE);

paint.setAntiAlias(true);

RectF rectF = new RectF(1, 1, this.getWidth()-1, this.getHeight

()-1);

canvas.drawRoundRect(rectF, 10, 10, paint);

}

}



用Kotlin改写后:


//自定义视图要在类名后增加“@JvmOverloads constructor”

class RoundLayout @JvmOverloads constructor(context: Context, attrs: At

tributeSet?

=null, defStyle: Int=0) : LinearLayout(context, attrs, defStyle) {

override fun dispatchDraw(canvas: Canvas) {

super.dispatchDraw(canvas)

val paint = Paint()

paint.color = Color.BLUE

paint.strokeWidth = 2f 

paint.style = Style.STROKE

paint.isAntiAlias = true 

val rectF = RectF(1f, 1f, (this.width - 1).toFloat(), (this.height - 1).toFloat())

canvas.drawRoundRect(rectF, 10f, 10f, paint)

}

}





9.3自定义通知栏


Notification

image.png

image.png

三种特殊的通知类型:

1.进度通知 setProgress

2.浮动通知 setFullScreenIntent

3.锁屏通知setVisibility

image.png

远程视图RemoteView

消息通知自定义布局,需要借助RemoteView实现

Notification.Builder的setContent

image.png

9.4 Service服务


普通启动服务


startService<NormalService>()

//带参数

startService<NormalService>

("request_content" to et_request.text.toString())



val intent = intentFor<NormalService>

("request_content" to et_request.text.toString())

startService(intent)



val intent = intentFor<NormalService>

(Pair("request_content", et_request.text.toString()))

startService(intent)



绑定方式启动服务


val bindFlag = bindService(intentBind, mFirstConn, Context.BIND_AUTO_CREATE)

//解除绑定

if (mBindService != null) {

unbindService(mFirstConn)

mBindService = null

}





推送服务到前台

不要让服务依附于任何页面,而Android允许服务以某种形式出现在屏幕上,那就是通知栏。

startForeground:服务切到前台

stopForeground:停止前台运行(true表示清除通知,false不清除)


<!-- 震动权限-->

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




val vibrator = getSystemService(Context.VIBRATOR_SERVICE) as Vibrator

vibrator.vibrate(3000)



其他服务管理器:


//下载管理器

val Context.downloader: DownloadManager

get() = getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager

//定位管理器

val Context.locator: LocationManager

get() = getSystemService(Context.LOCATION_SERVICE) as LocationManager

//连接管理器

val Context.connector: ConnectivityManager

get() = getSystemService(Context.CONNECTIVITY_SERVICE) as Connectiv

ityManager

//电话管理器

val Context.telephone: TelephonyManager

get() = getSystemService(Context.TELEPHONY_SERVICE) as TelephonyMan

ager

//无线管理器

val Context.wifi: WifiManager

get() = getSystemService(Context.WIFI_SERVICE) as WifiManager

//闹钟管理器

val Context.alarm: AlarmManager

get() = getSystemService(Context.ALARM_SERVICE) as AlarmManager

//音频管理器

val Context.audio: AudioManager

get() = getSystemService(Context.AUDIO_SERVICE) as AudioManager



总结

1.自定义视图

2.消息通知

3.Service

4.各种系统管理

第十章 Kotlin实现网络通信

==============================================================================

10.1多线程



Thread {

//具体业务

}.start()



水平进度对话框


val dialog = progressDialog("正在加载中", "请稍后")

dialog.show()





圆圈进度对话框


val dialog = indeterminateProgressDialog("正在加载中", "请稍后")

dialog.show()



异步任务

最后说一下我的学习路线

其实很简单就下面这张图,含概了Android所有需要学的知识点,一共8大板块:

  1. 架构师筑基必备技能
  2. Android框架体系架构(高级UI+FrameWork源码)
  3. 360°Androidapp全方位性能调优
  4. 设计思想解读开源框架
  5. NDK模块开发
  6. 移动架构师专题项目实战环节
  7. 移动架构师不可不学习微信小程序
  8. 混合开发的flutter

Android学习的资料

我呢,把上面八大板块的分支都系统的做了一份学习系统的资料和视频,大概就下面这些,我就不全部写出来了,不然太长了影响大家的阅读。

330页PDF Android学习核心笔记(内含上面8大板块)

Android学习的系统对应视频

总结

我希望通过我自己的学习方法来帮助大家去提升技术:

  • 1、多看书、看源码和做项目,平时多种总结

  • 2、不能停留在一些基本api的使用上,应该往更深层次的方向去研究,比如activity、view的内部运行机制,比如Android内存优化,比如aidl,比如JNI等,并不仅仅停留在会用,而要通过阅读源码,理解其实现原理

  • 3、同时对架构是有一定要求的,架构是抽象的,但是设计模式是具体的,所以一定要加强下设计模式的学习

  • 4、android的方向也很多,高级UI,移动架构师,数据结构与算法和音视频FFMpeg解码,如果你对其中一项比较感兴趣,就大胆的进阶吧!

希望大家多多点赞,转发,评论加关注,你们的支持就是我继续下去的动力!加油!

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

get() = getSystemService(Context.CONNECTIVITY_SERVICE) as Connectiv

ityManager

//电话管理器

val Context.telephone: TelephonyManager

get() = getSystemService(Context.TELEPHONY_SERVICE) as TelephonyMan

ager

//无线管理器

val Context.wifi: WifiManager

get() = getSystemService(Context.WIFI_SERVICE) as WifiManager

//闹钟管理器

val Context.alarm: AlarmManager

get() = getSystemService(Context.ALARM_SERVICE) as AlarmManager

//音频管理器

val Context.audio: AudioManager

get() = getSystemService(Context.AUDIO_SERVICE) as AudioManager




### []( )总结



1.自定义视图  

2.消息通知  

3.Service  

4.各种系统管理



[]( )第十章 Kotlin实现网络通信

==============================================================================



[]( )10.1多线程

---------------------------------------------------------------------



Thread {

//具体业务

}.start()




水平进度对话框



val dialog = progressDialog(“正在加载中”, “请稍后”)

dialog.show()




圆圈进度对话框



val dialog = indeterminateProgressDialog(“正在加载中”, “请稍后”)

dialog.show()




异步任务



### 最后说一下我的学习路线

**其实很简单就下面这张图,含概了Android所有需要学的知识点,一共8大板块:**

1.  **架构师筑基必备技能**
2.  **Android框架体系架构(高级UI+FrameWork源码)**
3.  **360°Androidapp全方位性能调优**
4.  **设计思想解读开源框架**
5.  **NDK模块开发**
6.  **移动架构师专题项目实战环节**
7.  **移动架构师不可不学习微信小程序**
8.  **混合开发的flutter**

[外链图片转存中...(img-jsryVg4J-1714526343540)]

**Android学习的资料**

我呢,把上面八大板块的分支都系统的做了一份学习系统的资料和视频,大概就下面这些,我就不全部写出来了,不然太长了影响大家的阅读。

**330页PDF Android学习核心笔记(内含上面8大板块)**

[外链图片转存中...(img-l0MtpxIM-1714526343540)]

**Android学习的系统对应视频**

# 总结

我希望通过我自己的学习方法来帮助大家去提升技术:

* 1、多看书、看源码和做项目,平时多种总结

* 2、不能停留在一些基本api的使用上,应该往更深层次的方向去研究,比如activity、view的内部运行机制,比如Android内存优化,比如aidl,比如JNI等,并不仅仅停留在会用,而要通过阅读源码,理解其实现原理

* 3、同时对架构是有一定要求的,架构是抽象的,但是设计模式是具体的,所以一定要加强下设计模式的学习

* 4、android的方向也很多,高级UI,移动架构师,数据结构与算法和音视频FFMpeg解码,如果你对其中一项比较感兴趣,就大胆的进阶吧!



> 希望大家多多点赞,转发,评论加关注,你们的支持就是我继续下去的动力!加油!



**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**

**[需要这份系统化学习资料的朋友,可以戳这里获取](https://bbs.csdn.net/topics/618156601)**

**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**

  • 10
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值