本篇文章和大家聊聊我认为的ampampquotKotlini
前言
Kotlinic一词属于捏造的,参考的是著名的Pythonic,后者可以译为很Python,意思是写的代码一看就很有Python味。照这个意思,Kotlinic就是很Kotlin,很有Kotlin味。
Kotlin程序员们不少是从Java转过来的,包括我;大部分时候,大家也都把它当大号的Java语法糖在用。但Kotlin总归是一门新语言,而且,在我眼里还是门挺优雅的语言。所以,或许我们可以把Kotlin写得更Kotlin些。我想简单粗浅的聊聊。善用with、apply、also、letwith和apply
with和apply,除了能帮忙少打一些代码外,重要的是能让代码区分更明确。比如valtextViewTextView(context)textView。textfishtextView。setTextColor(Color。BLUE)textView。setOnClickListener{}valimageViewImageView(context)。。。
这就是典型的Java写法,自然,没什么问题。但要是类似的代码多起来,总感觉不知道哪里是哪里。如果换用apply呢?valtextViewTextView(context)。apply{textfishsetTextColor(Color。BLUE)setOnClickListener{}}valimageViewImageView(context)。apply{}
apply的另一个常见场景是用于那些返回自己的函数,比如常见的Builder类的方法funsetName(name:String):Builder{this。namenamereturnthis}
改成apply就简洁得多funsetName(name:String)apply{this。namename}also
also的常见场景有很多,它的语义就是干完上一件事后附带干点什么事。举个例子,给个函数:funsomeFunc():Model{。。。returnModel(namemodel,valuevalue)}
如果我们突然想加个Log,打印一下返回值,按Java的写法,要这么干:funsomeFunc():Model{。。。valtempModelModel(namemodel,valuevalue)print(tempModel)returntempModel}
改的不少。但是按Kotlin的写法呢?funsomeFunc():Model{returnModel(namemodel,valuevalue)。also{print(it)}}
不需要额外整个变量出来。类似的,比如上面apply的例子,在没有声明变量的情况下,也可以这样用这个值findViewByIdImageView(R。id。someid)。apply{。。。}。also{println(it)}整在一起
这几个函数结合起来,在针对一些比较复杂的场景时,对提高代码的可读性还是挺有帮助的。如【唐子玄】在这篇文章里所举的例子:
假设需求如下:缩放textView的同时平移button,然后拉长imageView,动画结束后toast提示。
Java式写法PropertyValuesHolderscaleXPropertyValuesHolder。ofFloat(scaleX,1。0f,1。3f);PropertyValuesHolderscaleYPropertyValuesHolder。ofFloat(scaleY,1。0f,1。3f);ObjectAnimatortvAnimatorObjectAnimator。ofPropertyValuesHolder(textView,scaleX,scaleY);tvAnimator。setDuration(300);tvAnimator。setInterpolator(newLinearInterpolator());PropertyValuesHoldertranslationXPropertyValuesHolder。ofFloat(translationX,0f,100f);ObjectAnimatorbtnAnimatorObjectAnimator。ofPropertyValuesHolder(button,translationX);btnAnimator。setDuration(300);btnAnimator。setInterpolator(newLinearInterpolator());ValueAnimatorrightAnimatorValueAnimator。ofInt(ivRight,screenWidth);rightAnimator。addUpdateListener(newValueAnimator。AnimatorUpdateListener(){OverridepublicvoidonAnimationUpdate(ValueAnimatoranimation){intright((int)animation。getAnimatedValue());imageView。setRight(right);}});rightAnimator。setDuration(400);rightAnimator。setInterpolator(newLinearInterpolator());AnimatorSetanimatorSetnewAnimatorSet();animatorSet。play(tvAnimator)。with(btnAnimator);animatorSet。play(tvAnimator)。before(rightAnimator);animatorSet。addListener(newAnimator。AnimatorListener(){OverridepublicvoidonAnimationStart(Animatoranimation){}OverridepublicvoidonAnimationEnd(Animatoranimation){Toast。makeText(activity,animationend,Toast。LENGTHSHORT)。show();}OverridepublicvoidonAnimationCancel(Animatoranimation){}OverridepublicvoidonAnimationRepeat(Animatoranimation){}});animatorSet。start();
乱糟糟的。改成Kotlin式写法呢?AnimatorSet()。apply{ObjectAnimator。ofPropertyValuesHolder(textView,PropertyValuesHolder。ofFloat(scaleX,1。0f,1。3f),PropertyValuesHolder。ofFloat(scaleY,1。0f,1。3f))。apply{duration300LinterpolatorLinearInterpolator()}。let{play(it)。with(ObjectAnimator。ofPropertyValuesHolder(button,PropertyValuesHolder。ofFloat(translationX,0f,100f))。apply{duration300LinterpolatorLinearInterpolator()})play(it)。before(ValueAnimator。ofInt(ivRight,screenWidth)。apply{addUpdateListener{animationimageView。rightanimation。animatedValueasInt}duration400LinterpolatorLinearInterpolator()})}addListener(object:Animator。AnimatorListener{overridefunonAnimationRepeat(animation:Animator?){}overridefunonAnimationEnd(animation:Animator?){Toast。makeText(activity,animationend,Toast。LENGTHSHORT)。show()}overridefunonAnimationCancel(animation:Animator?){}overridefunonAnimationStart(animation:Animator?){}})start()}
从上往下读,层次分明。读起来可以感觉到:构建动画集,它包含{动画1将动画1和动画2一起播放将动画3在动画1之后播放}
(上面的代码均来自所引文章)用好拓展函数
继续上面动画的例子接着说,可以看到,最后的Listener实际上我们只用了onAnimationEnd这一部分,但却写出了一大堆。这时候,拓展函数就起作用了。
幸运的是,Google官方的androidx。core:corektx已经有了对应的拓展函数:publicinlinefunAnimator。doOnEnd(crossinlineaction:(animator:Animator)Unit):Animator。AnimatorListeneraddListener(onEndaction)publicinlinefunAnimator。addListener(crossinlineonEnd:(animator:Animator)Unit{},crossinlineonStart:(animator:Animator)Unit{},crossinlineonCancel:(animator:Animator)Unit{},crossinlineonRepeat:(animator:Animator)Unit{}):Animator。AnimatorListener{vallistenerobject:Animator。AnimatorListener{overridefunonAnimationRepeat(animator:Animator)onRepeat(animator)overridefunonAnimationEnd(animator:Animator)onEnd(animator)overridefunonAnimationCancel(animator:Animator)onCancel(animator)overridefunonAnimationStart(animator:Animator)onStart(animator)}addListener(listener)returnlistener}
所以上面的最后几行addListener可以改成doOnEnd{Toast。makeText(activity,animationend,Toast。LENGTHSHORT)。show()}
是不是简单得多?当然,弹出Toast似乎也很常用,所以再搞个拓展函数inlinefunActivity。toast(text:String,duration:IntToast。LENGTHSHORT)Toast。makeText(this,text,duration)。show()
上面的代码又可以改成这样(animation。)doOnEnd{activity。toast(animationend)}
再比较下原来的(animation。)addListener(object:Animator。AnimatorListener{overridefunonAnimationRepeat(animation:Animator?){}overridefunonAnimationEnd(animation:Animator?){Toast。makeText(activity,animationend,Toast。LENGTHSHORT)。show()}overridefunonAnimationCancel(animation:Animator?){}overridefunonAnimationStart(animation:Animator?){}})
是不是简洁得多?
上面提到androidx。core:corektx,其实它包含了大量有用的拓展函数。如果花点时间了解了解,或许能优化不少地方。用好运算符重载
Kotlin的运算符重载其实很有用,举个栗子给List添加值
我见过这种代码vallistlistOf(1)valnewListlistOf(1,2,3)valmutableListlist。toMutableList()转成可变的mutableList。addAll(newList)添加新的returnmutableList。toList()返回,改成不可变的
但是换成运算符重载呢?vallistlistOf(1)valnewListlistOf(1,2,3)returnlistnewList
一个号,简明扼要。
又比如,想判断某个View是否在ViewGroup中,最简单的看看索引呗valgroupLinearLayout(this)valisContaingroup。indexOfChild(view)!1
不过,借助corektx提供的运算符,我们可以写出这样的代码valgroupLinearLayout(this)valisContainviewingroup
语义上更直接。
想添加(删除)一个View?除了addView(removeView),也可以直接()valgroupLinearLayout(activity)groupview添加子Viewgroupview移除子View
想遍历?重载下iterator()运算符(corektx也写好了),就可以直接for了valgroupLinearLayout(this)for(childingroup){执行操作}
(这几个View的例子基本也来自上面的文章)
此外,良好设计的拓展属性和拓展函数也能帮助写出更符合语意的代码,形如设置view的大小view。setSize(width50。dp,height100。dp)设置文字大小textView。setFontSize(18。sp)获取三天后的时间valdueTimetoday3。days获取文本的md5编码valmd5Shen。md5
上面的代码很容易能看出是要干嘛,而且也非常容易实现,此处就不再赘述了。DSL
关于DSL,大家可能都知道有这么个东西,但可能用的都不多。但DSL若用得好,确实能达到化繁为简的功效。关于DSL的基本原理和实现,fundroid大佬在KotlinDSL实战:像Compose一样写代码中已经写得非常清晰了,本人就不再画蛇添足,接下来仅谈谈可能的使用吧。构建UI
DSL的一个广泛应用应该就是构建UI了。Anko(已过时)
较早的时候,一个比较广泛的应用可能就是之前的anko库了。JetBrains推出的这个库允许我们能够不用xml写布局。放一个来自博客Kotlin之小试Anko(Anko库的导入及使用)SoClear博客园的例子privatefunshowCustomerLayout(){verticalLayout{paddingdip(30)editText{hintNametextSize24f}。textChangedListener{onTextChanged{str,,,println(str)}}editText{hintPasswordtextSize24f}。textChangedListener{onTextChanged{str,,,println(str)}}button(跳转到其它界面){textSize26fidBTNIDonClick{界面跳转并携带参数startActivityIntentActivity(nameto小明,ageto12)}}button(显示对话框){onClick{makeAndShowDialog()}}button(列表selector){onClick{makeAndShowListSelector()}}}}privatefunmakeAndShowListSelector(){valcountrieslistOf(Russia,USA,England,Australia)selector(Whereareyoufrom,countries){ds,itoast(Soyourelivingin{countries〔i〕},right?)}}privatefunmakeAndShowDialog(){alert(thisisthemsg){customTitle{verticalLayout{imageView(R。mipmap。iclauncher)editText{hinthinttitle}}}okButton{toast(buttonok)会自行关闭不需要我们手动调用}cancelButton{toast(buttoncancel)}}。show()}
简洁优雅,而且由于是Kotlin代码生成的,还省去了解析xml的消耗。不过,由于现在有更好的选择,Anko官方已经停止维护此库;而被推荐的、用于取而代之的两个库分别是:ViewsDSL和JetpackComposeViewsDSL
关于这个库,Anko官方在推荐时说,它是AnextensibleViewDSLwhichresemblesAnko。。二者也确实很相像,但ViewsDSL在Anko之上提供了更高的拓展性、对AppCompat的支持、对Material的支持,甚至提供了直接预览kt布局的能力!
基本的使用可以看看上图,额外的感兴趣的大家可以去官网查看,此处就不多赘述。JetpackCompose
作为一个用Compose超过一年的萌新,我自己是十分喜欢这个框架的。但同时,目前(20220725)Compose的基建确实还尚不完善,所以对企业项目来说还,是应该充分评估后再考虑。但我仍然推荐你尝试一下,因为它简单、易用。即使是在现有的View项目中,也能无缝嵌入部分Compose代码;反之亦然。
Talkischeap,showmeyourcode。比如要实现一个列表,View项目(使用RecyclerView)需要xmlAdapterViewHolder。而Compose就简洁得多:LazyColumn(Modifier。fillMaxSize()){items(10){iText(textItemi,modifierModifier。fillMaxWidth()。clickable{context。toast(点击事件)}。padding(8。dp),styleMaterialTheme。typography。h4)}}
上面的代码创造了一个全屏的列表,并且添加了10个子项。每个item是一个文本,并且简单设置了其样式和点击事件。即使是完全不懂Compose,阅读代码也不难猜到各项的含义。运行起来,效果如下:
构建复杂的字符串
拼接字符串是一项常见的工作,不过,当它复杂起来但又有一定结构时,简单的或者模板字符串看起来就有些杂乱了。这时,DSL就能很优雅的解决这个任务。
举几个常见的例子吧:Html
使用DSL,能够写出类似这样的代码valhtmlTextbuildHtml{html{body{p(idtowrapper){p{这是一个段落}repeat(3){ili{Item{i1}}}img(srctoimg02。bs178。combijpda6fb6595926da01。jpg,widthto100px)}}}}
上述代码会生成类似这样的html!DOCTYPEhtmlhtmllangzhCNbodyp这是一个段落ulItem1ululItem2ululItem3ulimgsrca2020imgdataimg。jpgdatasrcimg02。bs178。combijpda6fb6595926da01。jpgwidth100pxbodyhtml
简洁直接,而且不容易出错。你可能比较疑惑上面的xxx是个啥,其实这是用了运算符重载把String转成了纯文本Tag。代码可能类似于openclassTag()openclassTextTag(valvalue:String):Tag()operatorfunString。unaryPlus()TextTag(this)Markdown
类似的,也可以用这种方式生成markdown。代码可能类似于valmarkDownTextbuildMarkdown{text(我是)link(FunnyFaltyFish,https:github。comFunnySaltyFish)newline()bold(很高兴见到你)}
生成的文本类似于我是〔FunnySaltyFish〕(https:github。comFunnySaltyFish)很高兴见到你SpannableString
对Android开发者来说,这个东西估计更常见。但传统的构造方式可以说够复杂的,所以DSL也能用。好的是,Google已经在corektx里写好了更简便的方法
使用例子如下:valbuildbuildSpannedString{backgroundColor(Color。YELLOW){append(我叫)bold{append(FunnySaltyFish)}append(,是一名学生)}}
渲染出的效果如下我叫FunnySaltyFish,是一名学生善用号
这里的意思其实包含了两个方面:用好Kotlin表达式的返回值,以及号做返回值的函数Kotlin表达式返回值
学过Kt的大家都知道,不同于Java,它的if,else,try这些是带返回值的。在很多地方,使用加上一个表达式的返回值看着可能会更清晰一些。
也就是,把类似于下面这种的Java式写法valweekday6一周第几天,〔1,7〕vardayDescriptionwhen(weekday){in1。。5dayDescription工作日in6。。7dayDescription周末elsedayDescription世界末日}
改成下面的Kotlin写法valweekday6一周第几天,〔1,7〕valdayDescriptionwhen(weekday){in1。。5工作日in6。。7周末else世界末日}
在try。。。catch的场合,这样的差异会更明显。比如一个非常简易的读文件例子(下面的代码仅可以读取小文件,请谨慎地实际使用)valfilePathd:学习资料日语资料。txtvarresultvarinputStream:FileInputStream?nulltry{inputStreamFileInputStream(File(filePath))resultinputStream。readBytes()。decodeToString()}catch(e:Exception){result读取失败!e。printStackTrace()}finally{try{inputStream?。close()}catch(e:IOException){e。printStackTrace()}}
这是个典型的Java写法,用Kotlin的写法会简洁些valresulttry{File(filePath)。inputStream()。use{it。readBytes()。decodeToString()}}catch(e:Exception){e。printStackTrace()读取失败}
这两处代码间主要有下面几处变化:result的赋值直接由try。。。catch语句的返回值提供文件流的关闭由Closeable。use拓展函数内部处理,外部调用更简洁使用其他拓展函数避免了嵌套的对象创建
如果你对中间读取的错误不关心,可以使用下面的形式valresultrunCatching{File(filePath)。inputStream()。use{it。readBytes()。decodeToString()}}。getOrDefault(读取失败)
runCatching函数返回一个Result对象,其getOrDefault方法可以在出错时使用给定的默认值(其他几个get方法包括getOrNull、getOrThrow、getOrElse)。这样写起来更简洁
不过,上面的写法还是不够Kotlin。借助Kt提供的琳琅满目的拓展函数,其实上面的代码可以写成这样valresultrunCatching{File(filePath)。readText()}。getOrDefault(读取失败)
在不需要考虑buffer的情况下,流都不需要管啦:)函数返回
用写函数其实在官方的各种拓展函数里非常常见。有一点比较有趣的是,因为Unit在Kotlin里面也是一种普通的类型,所以即使函数什么也不返回(也就是返回Unit),也可以拿写。不过这一点就因人而异了,得看实际情况。
对于一些非unit返回值的简单函数,用显得清晰明了
比如上面的获取dayDescriptionfungetDayDescripton(weekday:Int)when(weekday){in1。。5工作日in6。。7周末else世界末日}
比如打log时可能要输个分割线重复字符串inlineoperatorfunString。times(n:Int)this。repeat(n)valpider20
比如上面的读短文本funFile。readText(default:String)runCatching{this。readText()}。getOrDefault(default)
类似的例子很多很多,就不赘述了。杂项Collection
kt的集合可以说是很强大了,该有的不该有的它都给了。随便举几个例子吧创建
我经常看到类似这样的代码vallistarrayListOfint()list。add(1)list。add(2)list。add(3)
嗯,很Java。实际上创建一个列表,Kt有更好的方法。
带初始参数的arrayListOfvallistarrayListOf(1,2,3)
要是复杂一点呢?比如值为index的平方?vallistList(3){iii}
基于其他对象创建?valnamesstudents。map{it。name}
基于另一个列表中某些符合要求的创建?及格的同学们valpassedStudentsstudents。filter{it。grade60}转字符串
比如:〔a,b,c〕a,b,cvalstringlistOf(a,b,c)。joinToString{it}
也可以设置前后缀、分隔符等valstringlistOf(a,b,c)。joinToString(prefix〔,postfix〕){it}println(string)〔a,b,c〕代理
Kotlin的by应该说用的不少,它对应的概念Delegate也是语法上相较Java特别的地方。常见的用处呢,最简单的就是bylazy延迟初始化;除此之外,利用它也能快速实现一个懒汉式的单例(饿汉式的就object)classDataManager{companionobject{valIMPLbylazy{DataManager()}}}
放Java的话,上述代码语义上类似于if(IMPL!null)returnIMPL;synchronized(lock){if(IMPLnull){IMPLnewDataManager();}returnIMPL;}
如果不需要锁,还可以加上参数lazy(LazyThreadSafetyMode。NONE)配合协程
如果懒加载的内容是耗时操作,还可以配合上协程,实现异步的懒加载异步懒加载,byFunnySaltyFishparamT要加载的数据类型paramscope加载时的协程作用域paramblock加载代码跨returnLazyDeferredTfunTlazyPromise(scope:CoroutineScopeMainScope(),block:suspendCoroutineScope。()T)lazy{scope。async(startCoroutineStart。LAZY){block。invoke(this)}}
使用的时候才去加载数据,而且可以异步加载。
比如privatesuspendfunfetchData():String{println(开始加载数据)delay(1000)println(加载完毕)return成功}valusernamebylazyPromise(viewModelScope){fetchData()}valpasswordbylazyPromise(viewModelScope){fetchData()}
而具体使用这两个变量的方法为suspendfunlogin()withContext(Dispatchers。IO){username。start()password。start()println({username。await()}{password。await()}登陆成功!)}
最后在调用这个函数(比如点击事件)时onClick{scope。launch{viewModel。login()}}
没有值时会去异步加载这个值,输出如下:开始加载数据开始加载数据加载完毕加载完毕成功成功登陆成功!
后面再调用就直接使用已经初始化好的值,输出如下成功成功登陆成功!代理属性
代理的一个常见用法估计就是代理各种东东了
MapclassPeople(valmap:MapString,Any?){valname:Stringbymapvalage:Intbymap}valpeoplePeople(mapOf(nametoFunnySaltyFish,ageto20))println({people。name}:{people。age})FunnySaltyFish:20
Intent接收另一个activity传来的数据valfromEntrancebyintentDataString(entrance)
数据库User是某个数据库的表,nameage是两列valnamebyUser。namevalagebyUser。age
至此本文差不多结束了,零零凑凑写完,感觉也是一个挺奇妙的过程。最后,如果你觉得我的内容还不错的话,欢迎点个赞,这对我帮助很大!
美景惊艳时光!晋祠网红银杏进入最佳观赏期晋祠的网红银杏树。本报讯(记者贾尚志文摄)霜降过后,晋祠景区的银杏进入最佳观赏期,在绚烂阳光下,叶子金黄的银杏林成为秋日里绝美景色,其中,王琼祠前500多年树龄的连理银杏……
充电桩进小区不应卡在物业关本报记者周宵鹏本报通讯员赵贺既能降低日常用车成本,又能响应节能减排号召,新能源汽车近年来成为不少人的选择。买了新能源汽车就要考虑充电问题,按照相关要求,业主想在自己……
苦难是杯垫底的酒我一向反感对苦难的美化、诗化,尤其是成功人士说什么它是大学校等等,更是矫情,无非在标榜自己的意志坚强;这种说法也是对一般人智商的嘲弄,所有有价值的人类活动不正是为了使自己摆脱苦……
羊了个羊创始人称自己一季度赚了20万,网友们却只关心他的发型有时候大火靠的不是实力,而是运气。这个不只表现在明星效应身上,也会发生在其他的领域。今天我们要说的这一件事情,完全就是占了天时地利人和。它就是羊了个羊,你要说它有实力,它……
连续霸榜8天,这台国产旗舰双11人气火爆,150瓦闪充比苹果现在的国产手机品牌,为了解决续航问题,真的是越来越疯狂了,在不断提升电池容量的同时,快充功率也是不断加码。很多品牌都已经开始普及百瓦快充,要知道,iPhone14系列售价过万还……
10月换机优先考虑大内存,这三款12GB256GB手机,最低10月换机优先考虑大内存,这三款12GB256GB手机,价格一个比一个优秀。iQOOZ5参考价格:1799元这款12GB256GB仅售1799,转转价格更低,甚至不……
4人全部换掉!国乒参赛名单遭质疑,男女单打冠军恐皆丢掉2022年团体世乒赛没有出现太大意外,国乒如愿包揽男团和女团冠军,依旧延续着强大而且无可撼动的统治力。在结束世乒赛的征程之后,国际乒联也随即公布亚洲杯的具体参赛名单,国乒男女单……
填补高铁留白,西部城市格局要变了?每经记者:淡忠奎每经编辑:刘艳美图片来源:新华社2022年是实施十四五规划、加快建设交通强国的重要一年。尤其在当前形势下,铁路等基础设施建设在稳住经济大盘中担负着重……
酷比魔方iPlay50将于10月19日发布号称一个平板三套系IT之家10月12日消息,酷比魔方官方宣布,酷比魔方年度新品iPlay50将于10月19日发布,slogan为一个平板、三个系统。IT之家了解到,酷比魔方在2020年11……
2022年秋季护理自己小霞日常健康护理篇2022年广州秋天也不一样啦。【秋意渐至广州最低气温将跌破20】10月8日是寒露节气,广州的天气变得凉快了,有了点秋天的味道〔惊喜〕。据悉,9日夜间起有新一股冷空气……
一袭旗袍,彰显东方女性风韵之美彰显东方女性韵味之美,无论是社交场合,还是街头漫步,女性一袭旗袍,就是一道靓丽风景线。据专家考证,1636年4月,清朝颁布了宗室王公与福晋、诸臣顶戴品级服色等制度。从此,……
A股石油板块,能否搞出一波大行情?这两天,很多人在为外围市场的大涨而欢呼雀跃,都在对节后的A股描绘暴涨的前景,我就不去白日做梦了,我还是踏踏实实的做基础的分析工作吧。有个粉丝朋友提议我分析一下石油板块的节后走势……