版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請(qǐng)進(jìn)行舉報(bào)或認(rèn)領(lǐng)
文檔簡介
kotlinandroid開發(fā)入門目錄AndroidTheRecycler使Forecastlist可點(diǎn)擊KotlinAndroidFlowcontrol和ranges
Unittesting什么是\h一個(gè)強(qiáng)大的Java開發(fā)IDE被大家所熟知。AndroidStudio,官方的AndroidIDE,就對(duì)Java開發(fā)者來說,Kotlin是非常直覺化的,并且非常容易學(xué)習(xí)。語言的大部它與我們?nèi)粘I钍褂玫腎DE無需配置就能完全整合。AndroidStudio能夠非常它更加安全:Kotlin是空安全的,也就是說在我們編譯時(shí)期就處理了各種null的它是函數(shù)式的:Kotlin是基于面向?qū)ο蟮恼Z言。但是就如其他很多現(xiàn)代的語言它可以擴(kuò)展函數(shù):這意味著我們可以擴(kuò)展類的更多的特性,甚至我們沒有權(quán)限它是高度互操作性的:你可以繼續(xù)使用所有的你用Java寫的代碼和庫,因?yàn)閮晌覀兺ㄟ^KotlinpublicclasspublicclassArtist{privatelongid;privateStringname;privateStringurl;privateStringmbid;publiclonggetId(){returnid;publicvoidsetId(longid){this.id=id;publicStringgetName(){returnname;publicvoidsetName(Stringname){=name;publicStringgetUrl(){returnurl;publicpublicvoidsetUrl(Stringurl){this.url=url;publicStringgetMbid(){returnmbid;publicvoidsetMbid(Stringmbid){this.mbid=mbid;@OverridepublicStringtoString(){return"Artist{"+"id="+id",name='"+name+'\''",url='"+url+'\''",mbid='"+mbid+'\''+datadataclassArtist(varid:Long,varname:String,varurl:String,varmbid:String)如,toString()到NullPointerException,我們就需要在使用它之前不停地去判斷它是否為null。Kotlin,如很多現(xiàn)代的語言,是空安全的,因?yàn)槲覀冃枰ㄟ^一個(gè)安全調(diào)用操作符(寫做?)來明確地指定一個(gè)對(duì)象是否能為空。這里不能通過編譯Artist不能是nullvarnotNullArtist:Artist=nullArtist可以是varartist:Artist?=無法編譯artist可能是null,我們需要進(jìn)行處理只要在artistnull時(shí)才會(huì)打印智能轉(zhuǎn)換if(artist!=null){只有在確保artist不是null使用Elvis操作符來給定一個(gè)在是nullvalname=artist?.name?:funfunFragment.toast(message:CharSequence,duration:Int=Toast.LENGTH_SHORT){Toast.makeText(getActivity(),message,fragment.toast(fragment.toast("Hello函數(shù)式支持view.setOnClickListenerview.setOnClickListener{toast("Helloworld!")AndroidAndroidAndroidAndroidIDE,它是2013年發(fā)布的預(yù)覽版,并在2014年發(fā)布了正式版。\h\h轉(zhuǎn)移AndroidStudio是Android開發(fā)者一個(gè)重要的改變。首先,因?yàn)槲覀兎艞壛薥h我不會(huì)去覆蓋到AndroidStudio和Gradle的使用,因?yàn)檫@些都不是本書的重點(diǎn),但\h安裝KotlinPreferencesPluginPreferencesPluginKotlin:這是一個(gè)基礎(chǔ)的插件。它能讓AndroidStudio懂得Kotlin代碼。它會(huì)每KotlinAndroidExtensions:Kotlin團(tuán)隊(duì)還為Android開發(fā)發(fā)布了另外一個(gè)有趣的插件。這個(gè)AndroidExtensions可以讓你自動(dòng)地從XML中注入所有的View到 有。所以你需要進(jìn)入AndroidStudio的Preferences的plugin欄,然后安裝Kotlin插\h我們的應(yīng)用是由一個(gè)簡單的天氣app組成,正如所使用的Google'sBeginnersCourseinUdacity。我們可能會(huì)關(guān)注不同的事情,但是app的想法都是一樣的,你在AndroidStudioCreatenewCreatenewWeatherWeather
下一步,它會(huì)讓你選擇最小的API版本。我們選擇API15,因?yàn)槲覀冇幸粋€(gè)庫需要最后,我們需要選擇一個(gè)Activity模版來作為入口。我們可以選擇Addno我將選擇BlankActivity,因?yàn)槲掖龝?huì)兒會(huì)給你展示Kotlin插件一個(gè)好玩的小特要,我待會(huì)兒會(huì)修改它。點(diǎn)擊Finish然后讓它繼續(xù)創(chuàng)建項(xiàng)目。配置首先,你需要如下修改父build.gradlebuildscriptbuildscriptext.support_version=ext.kotlin_version=ext.anko_version='0.8.2'repositories{dependencies{classpath'com.android.tools.build:gradle:1.5.0'classpath"org.jetbrains.kotlin:kotlin-gradle-pluginallprojectsrepositories{的地方用到那個(gè)版本號(hào),比如你需要加上新的Kotlin插件的dependency。你會(huì)在你指定的那些模塊中的build.gradle中再次需要到Kotlin標(biāo)準(zhǔn)庫。我們對(duì)于supportlibrary也是如此,Anko庫也是同樣的做法。用這個(gè)方式可我們會(huì)增加Kotlin標(biāo)準(zhǔn)庫,Anko庫,以及Kotlin和KotlinAndroidExtensionsplugin插件到dependencies。applyapplyplugin:'com.android.application'applyplugin:'kotlin-android'applyplugin:'kotlin-android-extensions'android{dependenciescompile"com.android.support:appcompat-v7:$support_version"compile"org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"compile"org.jetbrains.anko:anko-common:$anko_version"buildscriptrepositories{jcenter()}dependenciesanko,但是現(xiàn)在來說僅僅增加anko-common就足夠了。這個(gè)庫被分割成了一系列把MainActivity轉(zhuǎn)換成Kotlin所以我們?cè)贛ainActivity.java類中使用它。打開文件,然后選擇CodeConvertJavaFiletoKotlinFileimportimportoverrideoverridefunonCreate(savedInstanceState:Bundle?){message.text="HelloKotlin!"用message.text來代替message.setText。編譯器將會(huì)把它轉(zhuǎn)換成一般的如果你有疑問或者想查看代碼,請(qǐng)?jiān)赲hKotlinforAndroidDevelopersrepository查\h如果你想定義一個(gè)類,你只需要使用classclassclassclassclassPerson(name:String,surname:那么構(gòu)造函數(shù)的函數(shù)體在哪呢?你可以寫在initclassclassPerson(name:String,surname:String){默認(rèn)任何類都是基礎(chǔ)繼承自Any(與java中的Object類似),但是我們可以繼明open或者abstract的類:openopenclassAnimal(name:classPerson(name:String,surname:String):數(shù)。這是用來替換Java中的super調(diào)用的。函數(shù)(我們Java中的方法)可以使用fun關(guān)鍵字就可以定義funfunonCreate(savedInstanceState:Bundle?)如果你沒有指定它的返回值,它就會(huì)返回Unit,與Java中的void類似,但是Unit是一個(gè)真正的對(duì)象。你當(dāng)然也可以指定任何其它的返回類型:funfunadd(x:Int,y:Int):Int{returnx+yfunfunadd(x:Int,y:Int):Int=x+funfunadd(x:Int,y:Int):Int{returnx+yfunfuntoast(message:String,length:Int=Toast.LENGTH_SHORT){Toast.makeText(this,message,length).show()toast("Hello",voidvoidtoast(Stringvoidtoast(Stringmessage,intlength){Toast.makeText(this,message,length).show();funfunniceToast(message:tag:String=length:Int=Toast.LENGTH_SHORT){Toast.makeText(this,"[$className]$message",length).show()toast("Hello","MyTag")toast("Hello","MyTag",toast(messagetoast(message="Hello",length=[$className]$message"。如你所見,任何時(shí)候你使用一個(gè)$符號(hào)就可以插入一個(gè)表達(dá)式。如果這個(gè)表達(dá)式有一點(diǎn)復(fù)雜,你就需要使用一對(duì)大括號(hào)括起來:"Yournameis我們已經(jīng)有了MainActivity.kt類。這個(gè)Activity會(huì)展示下周一系列的天氣預(yù)報(bào),創(chuàng)建一個(gè)顯示天氣預(yù)報(bào)的列表我們使用RecyclerView,所以你需要在build.gradle中dependenciesdependenciescompilefileTree(dir:'libs',include:compile"com.android.support:appcompat-v7:$support_version"compile"com.android.support:recyclerview-v7:$support_version"然后,activity_main.xml<FrameLayout<FrameLayoutxmlns:android="\h/apk/res/a在Mainactivity.kt中刪除掉之前用來測試的能正常運(yùn)行的所有代碼(現(xiàn)在應(yīng)該會(huì)提示錯(cuò)誤)。暫且我們使用老的findViewByid()的方式:valvalforecastList=findViewById(R.id.forecast_list)asRecyclerVforecastList.layoutManager=如你所見,我們定義類一個(gè)變量并轉(zhuǎn)型為RecyclerView。這里與Java有點(diǎn)不同,我們會(huì)在下一章分析這些不同之處。LayoutManager會(huì)通過屬性的方式被設(shè)對(duì)象實(shí)例化也是與Java中有些不同。如你所見,我們?nèi)サ袅薾ew關(guān)鍵符。LinearLayoutManager(thisTheTheRecyclerTheRecycler\h我們同樣需要一個(gè)RecyclerView的Adapter。之前我在我博客中討論過RecyclerView,如果你以前沒有使用過,它可能會(huì)有所幫助。RecyclerView中所使用到的布局現(xiàn)在只需要一個(gè)TextView,我會(huì)手動(dòng)去創(chuàng)建這個(gè)簡單的文本列表。增加一個(gè)名為ForecastListAdapter.kt的Kotlin文件,包c(diǎn)lassclassForecastListAdapter(valitems:List<String>):overridefunonCreateViewHolder(parent:ViewGroup,viewType:Int):ViewHolder{returnoverridefunonBindViewHolder(holder:ViewHolder,position:Int){holder.textView.text=overridefungetItemCount():Int=classViewHolder(valtextView:TextView):RecyclerView.View回到MainActivity,現(xiàn)在簡單地創(chuàng)建一系列的String放入List中,然后使用創(chuàng)建privateprivatevalitems=listOf("Mon6/23-Sunny-31/17","Tue6/24-Foggy-21/8","Wed6/25-Cloudy-22/17","Thurs6/26-Rainy-18/11","Fri6/27-Foggy-21/10","Sat6/28-TRAPPEDINWEATHERSTATION-"Sun6/29-Sunny-overridefunonCreate(savedInstanceState:Bundle?)valforecastList=findViewById(R.id.forecast_list)asRecycforecastList.layoutManager=LinearLayoutManager(this)forecastList.adapter=ForecastListAdapter(items)你可以通過使用一個(gè)函數(shù)listOf創(chuàng)建一個(gè)常量的List(很快我們就會(huì)講到的immutable)。它接收一個(gè)任何類型的vararg(可變長的參還有很多其它的函數(shù)可以選擇,比如setOf,arrayListOf或者h(yuǎn)ashSetOf。數(shù)字類型中不會(huì)自動(dòng)轉(zhuǎn)型。舉個(gè)例子,你不能給Double變量分配一個(gè)Intvalvalvald:Double=字符(Char)不能直接作為一個(gè)數(shù)字來處理。在需要時(shí)我們需要把他們轉(zhuǎn)換為valvalvali:Int=位運(yùn)算也有一點(diǎn)不同。在Android中,我們經(jīng)常在flags中使用“或”,所以我////intbitwiseOr=FLAG1|FLAG2;intbitwiseAnd=FLAG1&FLAG2;//valbitwiseOr=FLAG1orFLAG2valbitwiseAnd=FLAG1andFLAG2還有很多其他的位操作符,比如sh1,shsushrxor或inv。當(dāng)我\h字面上可以寫明具體的類型。這個(gè)不是必須的,但是一個(gè)通用的Kotlin實(shí)踐時(shí)valvali=12//AnvaliHex0x0f一個(gè)十六進(jìn)制的Intvall=3L//ALongvald=3.5//ADoublevalf=3.5F//Avalvals=valcs[2這是一個(gè)字符迭代vals="Example"for(cins){變量可以很簡單地定義成可變(var)和不可變(val)的變量。這個(gè)與Java中使用的final很相似。但是不可變?cè)贙otlin(和其它很多現(xiàn)代語言)中是一個(gè)很重要變。一個(gè)重要的概念是:盡可能地使用val。除了個(gè)別情況(特別是在Androidvalvals="Example"http://AStringvali=23//AnIntvalactionBar=supportActionBar//AnActionBarinanActivityvalvala:Any=valc:Context=publicpublicclassPerson{privateStringname;publicStringgetName(){returnpublicvoidsetName(Stringname){=name;Personperson=newPerson();Stringname=publicpublicclassPerson{varname:String=""valperson=Person()="name"valname=publicpublicclasssPerson{varname:String=""get()=field.toUpperCase()field="Name:如果需要在getter和setter中訪問這個(gè)屬性自身的值,它需要?jiǎng)?chuàng)建一個(gè)backingfield??梢允褂胒ield這個(gè)預(yù)留字段來訪問,它會(huì)被編譯器找到正在使用的并而不是直接訪問這個(gè)屬性。backingfield只能在屬性訪問器內(nèi)訪問。Anko任何時(shí)候使用ctrl+點(diǎn)擊(Windows)或者cmd+點(diǎn)擊(Mac)的方式跳轉(zhuǎn)開始使用在之前,讓我們來使用kk庫中的某些東西,它們都會(huì)以屬性名、方法等方式被導(dǎo)入。這是因?yàn)閗使用了擴(kuò)展函數(shù)在框架中增加了一些新的功能。我們將會(huì)在以后看到擴(kuò)展函數(shù)是什么,怎么去編寫它。valvalforecastList:RecyclerView=以使用this關(guān)鍵字和調(diào)用所有public方法。funfunContext.toast(message:CharSequence,duration:Int=Toast.LENGTH_SHORT){Toast.makeText(this,message,toast(toast("Hellotoast("Helloworld!",些針對(duì)CharSequence和resource的函數(shù),還有兩個(gè)不同的toast和longToast方toast(toast("Helloworld!")publicpublicvarTextView.text:CharSequenceget()=getText()set(v)=\h\hpublicpublicclassRequest(valurl:String){publicfunrun(){valforecastJsonStr=URL(url).readText()Log.d(javaClass.simpleName,forecastJsonStr)簡單,因?yàn)槲覀兪褂胷eadText,這是Kotlin標(biāo)準(zhǔn)庫中的擴(kuò)展函數(shù)。這個(gè)方法不推比如HttpURLConnection、BufferedReader和需要達(dá)到相同效果所必要的迭在AndroidManifest.xml中添加::<uses-permission<uses-permissionandroid:name="android.permission.INTERNET"塞住UI線程是一個(gè)非常差的體驗(yàn)。Android中通用的做法是使用AsyncTask,但是果你使用不小心,AsyncTasks會(huì)非常危險(xiǎn),因?yàn)楫?dāng)運(yùn)行到postExecute時(shí),如基本的async函數(shù)用于在其它線程執(zhí)行代碼,也可以選擇通過調(diào)用uiThread的async()async()uiThread{longToast("Requestperformed")個(gè)Activity調(diào)用的,那么如果activity.isFinishing()返回true,則uiThread不會(huì)執(zhí)行,這樣就不會(huì)在Activity銷毀的時(shí)候遇到崩潰的情況了。假如你想使用Future來工作,async返回一個(gè)JavaFuture。而且如果你需要一個(gè)返回結(jié)果的Future,你可以使用asyncResult。真的很簡單,對(duì)吧?而且比AsyncTasks更加具有可讀性?,F(xiàn)在,我僅僅給請(qǐng)求datadataclassForecast(valdate:Date,valtemperature:Float,valdetails:String)copy你可以拷貝一個(gè)對(duì)象,可以根據(jù)你的需要去修改里面的屬性。我們會(huì)在舉個(gè)例子,如果我們需要修改Forecast中的temperature(溫度),我們可以valvalf1=Forecast(Date(),27.5f,"Shinyday")valf2=f1.copy(temperature=30f)如上,我們拷貝了第一個(gè)forecast對(duì)象然后只修改了temperature的屬性而沒有你還是可以訪問Date對(duì)象,然后改變它的值。有個(gè)簡單(不安全)的方另外一個(gè)方法是封裝這些類。你可以創(chuàng)建一個(gè)ImmutableDate類,它封裝了Date并且不允許去修改它們的狀態(tài)。決定哪種方式取決于你。本書什么會(huì)有componentX函數(shù)被自動(dòng)創(chuàng)建。使用上面的Forecast類舉個(gè)例子:valvalf1=Forecast(Date(),27.5f,"Shinyday")val(date,temperature,details)=f1valvaldate=valtemperature=ponent2()valdetails=ponent3()例子,Map類含有一些擴(kuò)展函數(shù)的實(shí)現(xiàn),允許它在迭代時(shí)使用key和value:forfor((key,value)inmap)Log.d("map","key:$key,轉(zhuǎn)換json我們現(xiàn)在知道怎么去創(chuàng)建一個(gè)數(shù)據(jù)類,那我們開始準(zhǔn)備去解析數(shù)據(jù)。在date包中,創(chuàng)建一個(gè)名為ResponseClasses.kt新的文件,如果你打開第8章中的url,你dataclassForecastResult(valcity:City,vallist:List<ForecasdataclassCity(valid:Long,valname:String,valcoord:Coordvalcountry:String,valpopulation:Int)dataclassCoordinates(vallon:Float,vallat:Float)dataclassForecast(valdt:Long,valtemp:Temperature,valpressure:Float,
valhumidity:Int,valweather:List<Weather>valspeed:Float,valdeg:Int,valclouds:valrain:dataclassTemperature(valday:Float,valmin:Float,valmax:valnight:Float,valeve:Float,val:dataclassWeather(valid:Long,valmain:String,valdescription:String,valicon:一樣,或者可以指定一個(gè)serialisedname(序列化名稱)。一個(gè)好的實(shí)踐是,現(xiàn)在,為了返回被解析后的結(jié)果,我們的Request類需要進(jìn)行一些修改。它將仍然只接收一個(gè)城市的zipcode作為參數(shù)而不是一個(gè)完整的url,因此這樣變得更加具有可讀性。現(xiàn)在,我會(huì)把這個(gè)靜態(tài)的url放在一個(gè)companionobject(伴隨對(duì)用companionobjecvt。這個(gè)對(duì)象被這個(gè)類的所有對(duì)象所共享,就像publicpublicclassForecastRequest(valzipCode:String){companionobject{privatevalAPP_ID="15646a06818f61f7b8d7823ca833e1ce"privatevalURL="\h/data/2.5/"privatevalCOMPLETE_URL="$URL&APPID=$APP_ID&q="funexecute():ForecastResultvalforecastJsonStr=URL(COMPLETE_URL+returnGson().fromJson(forecastJsonStr,ForecastResult::記得在build.gradle中增加你需要的Gsoncompilecompile構(gòu)建domain我們現(xiàn)在創(chuàng)建一個(gè)新的包作為domain層。這一層中會(huì)包含一些Commands的實(shí)首先,必須要定義一個(gè)CommandpublicpublicinterfaceCommand<T>{funexecute():T定,它就默認(rèn)返回一個(gè)Unit類。所以如果我們想讓Command不返回?cái)?shù)據(jù),我們datadataclassForecastList(valcity:String,valcountry:valdataclassForecast(valdate:String,valdescription:String,valhigh:Int,vallow:個(gè)DataMapper:publicclassForecastDataMapperfunconvertFromDataModel(forecast:ForecastResult):ForecastList{returnForecastList(,privatefunconvertForecastListToDomain(list:List<ModelForecast>returnlist.map{convertForecastItemToDomain(it)privatefunconvertForecastItemToDomain(forecast:Forecast):ModelForecast{returnModelForecast(convertDate(forecast.dt),forecast.weather[0].description,forecast.temp.m
privatefunconvertDate(date:Long):Stringvaldf=DateFormat.getDateInstance(DateFormat.MEDIUM,Lreturndf.format(date*importimportcom.antonioleiva.weatherapp.domain.model.ForecastasModereturnreturnlist.map{convertForecastItemToDomain(it)作并且任何方式轉(zhuǎn)換它們。對(duì)比Java7,這是Kotlin其中一個(gè)強(qiáng)大的功能。我們很classclassRequestForecastCommand(valzipCode:String)Command<ForecastList>{overridefunexecute():ForecastList{valforecastRequest=ForecastRequest(zipCode)returnForecastDataMapper().convertFromDataModel(在UIasync()async()valresult=RequestForecastCommand("94043").execute()forecastList.adapter=classclassForecastListAdapter(valweekForecast:ForecastList):overridefunonCreateViewHolder(parent:ViewGroup,viewType:ViewHolder?returnoverridefunonBindViewHolder(holder:ViewHolder,position:Int){with(weekForecast.dailyForecast[position]){holder.textView.text="$date-$description-$highoverridefungetItemCount():Int=weekForecast.dailyForecasclassViewHolder(valtextView:TextView):RecyclerView.View\h這里你可以看見一系列包括操作符和對(duì)應(yīng)方法的表。對(duì)應(yīng)方法必須在指定的類中a+a-a*a/a%aina!ina+=a-=a*=a/=a%=a[i,a.get(i,a[i_1,...,a.get(i_1,...,a[i]=a.set(i,a[i,j]=a.set(i,j,a[i_1,...,i_n]=a.set(i_1,...,i_n,a==a?.equals(b)?:b===a!=!(a?.equals(b)?:b===operatoroperatorfunequals(other:Any?):操作符和用來做身份檢查(它們分別是Java中的和),并且a(i,a(i_1,...,a.invoke(i_1,...,你可以想象,KotlinList是實(shí)現(xiàn)了數(shù)組操作符的,所以我們可以像Java中的數(shù)組一valvalx=myList[2]myList[2]=4如果你還記得,我們有一個(gè)叫css的數(shù)據(jù)類,它是由很多其他額外的信息組成的。有趣的是可以直接訪問它的每一項(xiàng)而不是請(qǐng)求內(nèi)部的s得到某一項(xiàng)。做一個(gè)完全不相關(guān)的事情,我要去實(shí)現(xiàn)一個(gè))方法,它能稍微能簡化一點(diǎn)當(dāng)前的:datadataclassForecastList(valcity:String,valcountry:valdailyForecast:List<Forecast>){operatorfunget(position:Int):Forecast=dailyForecast[pofunsize():Int=它會(huì)使我們的onBindViewHolderoverrideoverridefunonBindViewHolder(holder:ViewHolder,position:Int){with(weekForecast[position])holder.textView.text="$date-$description-當(dāng)然還有g(shù)etItemCount(overrideoverridefungetItemCount():Int=訪問ViewGroup的view:operatoroperatorfunViewGroup.get(position:Int):View=getChildAt(posvalvalcontainer:ViewGroup=find(R.id.container)valview=container[2]\h使Forecastlist述以及最高和最低溫度。所以讓我們創(chuàng)建一個(gè)名為item_forecast.xml的<?xmlversion="1.0"encoding="utf-\h\htools:text="May14,tools:text="LightdatadataclassForecast(valdate:String,valdescription:valhigh:Int,vallow:Int,valiconUrl:在ForecastDataMapperprivateprivatefunconvertForecastItemToDomain(forecast:Forecast):ModelForecast{returnModelForecast(convertDate(forecast.dt),forecast.weather[0].description,forecast.temp.max.tforecast.temp.min.toInt(),generateIconUrl(forecast.privatefungenerateIconUrl(iconCode:String):=\h方式是使用圖片加載庫。\hPicasso是一個(gè)不錯(cuò)的選擇。它需要加到build.gradlecompilecompilepublicpublicinterfaceOnItemClickListener{operatorfuninvoke(forecast:Forecast)如果你還記得上一課程,當(dāng)被調(diào)用時(shí)invoke方法可以被省略。所以我們來使用它c(diǎn)lassclassViewHolder(view:View,valitemClick:RecyclerView.ViewHolder(view){privatevaliconView:ImageViewprivatevaldateView:TextViewprivatevaldescriptionView:TextViewprivatevalmaxTemperatureView:TextViewprivatevalminTemperatureView:initiconView=view.find(R.id.icon)dateView=descriptionView=view.find(R.id.description)maxTemperatureView=view.find(R.id.maxTemperature)minTemperatureView=funbindForecast(forecast:Forecast){with(forecast){dateView.text=datedescriptionView.text=descriptionmaxTemperatureView.text="${high.toString()}"minTemperatureView.text="${low.toString()}"itemView.setOnClickListener{itemClick(forecast)}publicpublicclassForecastListAdapter(valweekForecast:ForecastList,valitemClick:ForecastListAdapter.OnItemClickListener)overridefunonCreateViewHolder(parent:ViewGroup,viewType:ViewHoldervalview=.inflate(R.layout.item_forecast,parent,false)returnViewHolder(view,itemClick)overridefunonBindViewHolder(holder:ViewHolder,position:Int){如果你使用了上面這些代碼,parent.ctx不會(huì)被編譯成功。Anko提供了大量的了ctx這個(gè)屬性,通過ctx這個(gè)屬性來返回context,但是在View中缺少這個(gè)屬性。所以我們要?jiǎng)?chuàng)建一個(gè)新的名叫ViewExtensions.kt文件來代替ui.utilsvalvalView.ctx:Contextget()=context擴(kuò)展的context屬性,但是我覺得如果我們使用ctx的話在其它類中也會(huì)更有連貫forecastList.adapterforecastList.adapter=ForecastListAdapter(result,object:ForecastListAdapter.OnItemClickListener{overridefuninvoke(forecast:Forecast){簡化的:View.setOnClickListener(方法。如果我們想用Java的方式去增加點(diǎn)擊事件的回調(diào),我首先要編寫一個(gè)OnClickListener接口:publicpublicinterfaceOnClickListener{voidonClick(Viewv);view.setOnClickListener(view.setOnClickListener(newOnClickListener(){publicvoidonClick(Viewv){Toast.makeText(v.getContext(),"Click",Toast.LENGTH_SHOview.setOnClickListener(view.setOnClickListener(object:OnClickListener{overridefunonClick(v:View){funfunsetOnClickListener(listener:(View)->view.setOnClickListener({view.setOnClickListener({view->view.setOnClickListener({view.setOnClickListener({toast("Click")view.setOnClickListener()view.setOnClickListener(){toast("Click")view.setOnClickListenerview.setOnClickListener{toast("Click")ForecastListAdapter的click在前面一章,我這么艱苦地寫了clicklistener的目的就是更好的在這一章中進(jìn)行開publicpublicclassForecastListAdapter(valweekForecast:valitemClick:(Forecast)->這個(gè)itemClick函數(shù)接收一個(gè)forecast參數(shù)然后不返回任何東西。ViewHolder中也可以這么修改:classclassViewHolder(view:View,valitemClick:(Forecast)->其它的代碼保持不變。僅僅改變MainActivityvalvaladapter=ForecastListAdapter(result){forecast->toast(forecast.date)}我們可以簡化最后一句。如果這個(gè)函數(shù)只接收一個(gè)參數(shù),那我們可以使用it引valvaladapter=ForecastListAdapter(result){toast(it.date)多虧這些改變,我們可以去創(chuàng)建自己的builder和代碼塊。我們已經(jīng)在使用一些有趣的函數(shù),比如with。如下簡單的實(shí)現(xiàn):inlineinlinefun<T>with(t:T,body:T.()->Unit){t.body()這個(gè)函數(shù)接收一個(gè)T類型的對(duì)象和一個(gè)被作為擴(kuò)展函數(shù)的函數(shù)。它的實(shí)現(xiàn)僅僅是用this和直接訪問所有的publicwith(forecast){with(forecast){dateView.text=datedescriptionView.text=descriptionmaxTemperatureView.text="$high"minTemperatureView.text="$low"itemView.setOnClickListener{itemClick(this)}另一個(gè)例子:我們可以創(chuàng)建代碼塊只提供LollipopinlineinlinefunsupportsLollipop(code:()->Unit)if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.LOLLIPOP)supportsLollipop{supportsLollipop{舉個(gè)例子,Anko也是基于這個(gè)思想來實(shí)現(xiàn)AndroidLayout的DSL化。你也可以查看Kotlinreference中\(zhòng)h使用DSL來編寫HTML的一個(gè)例子。飾符是public,這節(jié)約了很多的時(shí)間和字符。但是這里有一個(gè)詳細(xì)的解釋關(guān)于在private修飾符是我們使用的最限制的修飾符。它表示它只能被自己所在的文件可見。所以如果我們給一個(gè)類聲明為private,我們就不能在定義這個(gè)類之外的另一方面,如果我們?cè)谝粋€(gè)類里面使用了private修飾符,那訪問權(quán)限就被限制所以一等公民,類、對(duì)象、接口……(也就是包成員)如果被定義為private,為protected。定義在一個(gè)成員中,就與Java中的方式一樣了:它可以被成員自如果是一個(gè)定義為internal的包成員的話,對(duì)所在的整個(gè)module可見。如果了一個(gè)private類,那么它的internal修飾的函數(shù)的可見性就會(huì)限制與它所在我們可以訪問同一個(gè)module中的internal修飾的類,但是不能訪問其它module的。什么是根據(jù)Jetbrains的定義,一個(gè)module應(yīng)該是一個(gè)單獨(dú)的功能性的單位,它塊,可以在AndroidStudio中創(chuàng)建不同的module。在Eclipse中,這些module可以認(rèn)為是在一個(gè)workspace中的不同的project方被修飾為public,很明顯它只限制于它的領(lǐng)域。一個(gè)定義為public的成員被包含在一個(gè)private修飾的類中,這個(gè)成員在這個(gè)類以外也是不可見的。所有構(gòu)造函數(shù)默認(rèn)都是public的,它們類是可見的,可以被其它地方使用。我們也可以使用這個(gè)語法來把構(gòu)造函數(shù)修改為private:classclassCprivateconstructor(a:Int){... 改。比如,在RequestForecastCommand中,我們?cè)跇?gòu)造函數(shù)中我們創(chuàng)建的屬性zipCode可以定義為private:classclassRequestForecastCommand(privatevalzipCode:為privatedatadataclassForecastList(...)funget(position:Int)=dailyForecast[position]funsize()=dailyForecast.size()KotlinKotlinAndroidKotlinAndroid另一個(gè)Kotlin團(tuán)隊(duì)研發(fā)的可以讓開發(fā)更簡單的插件是KotlinAndroidExtensions。當(dāng)前僅僅包括了view的綁定。這個(gè)插件自動(dòng)創(chuàng)建了很多的屬性來讓KotlinAndroidExtensions的一個(gè)優(yōu)點(diǎn)是它不需要在我們的代碼中依賴其它裝置只會(huì)在Activity或者Fragment中才有效。如果它是在一個(gè)擴(kuò)展函數(shù)中增加的,這個(gè)緩存就會(huì)被跳過,因?yàn)樗梢员挥迷贏ctivity中但是插件不能被修怎么去使用KotlinAndroid這個(gè)項(xiàng)目,我們就已經(jīng)在build.gradle中增加了這個(gè)依賴:repositories{dependenciesclasspath"org.jetbrains.kotlin:kotlin-android-extension唯一一件需要這個(gè)插件做的事情是在類中增加一個(gè)特定的"手工"import來使用這Activities或者Fragments的Android這是最典型的使用方式。它們可以作為activity或fragment的屬性是可以被我們需要使用的import語句以kotlin.android.synthetic開頭,然后加上我importimport此后,我們就可以在setContentView被調(diào)用后訪問這些view。新的AndroidStudio版本中可以通過使用include標(biāo)簽在Activity默認(rèn)布局中增加內(nèi)嵌的布局。importimportkotlinx.android.synthetic.activity_main.*importkotlinx.android.synthetic.content_main.*Views的Android前面說的使用還是有局限性的,因?yàn)榭赡苡泻芏啻a需要訪問中的v。比如,一個(gè)自定義v或者一個(gè)。舉個(gè)例子,綁定一個(gè)x中的v到另一個(gè)v。唯一不同的就是需要t:importimportview.textView.textview.textView.text=現(xiàn)在是時(shí)候使用KotlinAndroidExtensions來修改我們的代碼了。修改相當(dāng)我們從MainActivity開始。我們當(dāng)前只是使用了forecastList的RecyclerView。但是我們可以簡化一點(diǎn)代碼。首先,為activity_mainXML增加importimport之前說過,我們使用id來訪問views。所以我要修改RecyclerView的id,不使用\h然后現(xiàn)在,我們可以不需要findoverridefunoverridefunonCreate(savedInstanceState:Bundle?){forecastList.layoutManager=LinearLayoutManager(this)這已經(jīng)是最小的簡化了,因?yàn)檫@個(gè)布局非常簡單。但是ForecastListAdapter也以幫助我們移除所有ViewHolder的find代碼。首先,為item_forecastimportimport然后現(xiàn)在我們可以在ViewHolder中使用包含在itemView中的屬性。實(shí)際上你classViewHolder(view:View,valitemClick:(Forecast)->RecyclerView.ViewHolder(view){funbindForecast(forecast:Forecast){
itemView.date.text=dateitemView.description.text=descriptionitemView.maxTemperature.text="${high.toString()}"itemView.minTemperature.text="${low.toString()}"itemView.onClick{itemClick(forecast)}Application單例化和屬性的ApplicatonclassclassApp:Application(){companionobject{privatevarinstance:Application?=nullfuninstance()=instance!!overridefunonCreate(){instance=this為了這個(gè)Application實(shí)例被使用,要記得在AndroidManifest.xml中增加這個(gè)App:有一個(gè)問題,就是我們不能去控制很多類的構(gòu)造函數(shù)。比如,我們不能初始化一個(gè)非屬性,因?yàn)樗闹敌枰跇?gòu)造函數(shù)中去定義。所以我們需要一個(gè)可的變量,和一個(gè)返回非值的函數(shù)。我們知道我們一直都有一個(gè)p實(shí)例,但是在它調(diào)用e們假設(shè))函數(shù)將會(huì)總是返回一個(gè)非的p實(shí)例。的委托屬性我們可能需要一個(gè)屬性具有一些相同的行為,使用lazy或者observable可以一個(gè)委托屬性到一個(gè)類的方法。這就是我們知道的委托屬性。當(dāng)我們使用屬性的get或者set的時(shí)候,屬性委托的getValue和setValue就會(huì)被調(diào)用。funsetValue(thisRef:Any?,property:KProperty<*>,value:classDelegate<T>:ReadWriteProperty<Any?,T>fungetValue(thisRef:Any?,property:KProperty<*>):T{return...這個(gè)T是委托屬性的類型。getValue函數(shù)接收一個(gè)類的引用和一個(gè)屬性的元數(shù)據(jù)。setValue函數(shù)又接收了一個(gè)被設(shè)置的值。如果這個(gè)屬性是不可修改classExamplevarp:classExamplevarp:Stringby它使用了by它包含一個(gè)lambda,當(dāng)?shù)谝淮螆?zhí)行g(shù)etValue的時(shí)候這個(gè)lambda會(huì)被調(diào)用,所以classclassApp:Application()valdatabase:SQLiteOpenHelperbylazy{overridefunonCreate(){valdb=在這個(gè)例子中,database并沒有被真正初始化,直到第一次調(diào)用onCreate時(shí)。了。lazy用lazy(LazyThreadSafeMode.NONE){...}。這個(gè)委托會(huì)幫我們監(jiān)測我們希望觀察的屬性的變化。當(dāng)被觀察屬性的set方法被classclassViewModel(valdb:MyDatabase)varmyPropertybyDelegates.observable(""){d,old,new->db.saveChanges(this,這是一個(gè)特殊的observable,它讓你決定是否這個(gè)值需要被保存。它可以被用varvarpositiveNumber=Delegates.vetoable(0){d,old,new->new>=Not第二種選擇是使用notNull委托。它會(huì)含有一個(gè)可null的變量并會(huì)在我們?cè)O(shè)置這個(gè)classclassApp:Application(){companionobject{varinstance:AppbyoverridefunonCreate(){instance=this另外一種屬性委托方式就是,屬性的值會(huì)從一個(gè)中獲取v這個(gè)中的ky。這個(gè)委托可以讓我們做一些很強(qiáng)大的事情,因?yàn)槲覀兛梢院芎唵蔚貜囊粋€(gè)動(dòng)態(tài)地中創(chuàng)建一個(gè)對(duì)象實(shí)例。如果我們tperties.getValue,我們可以從構(gòu)造函數(shù)映射到val屬性來得到perties.setValue。類需要一個(gè)MutableMap作為構(gòu)造函數(shù)的參importimportclassConfiguration(map:Map<String,Any?>){valwidth:Intbymapvalheight:Intbymapvaldp:IntbymapvaldeviceName:Stringbyconfconf=Configuration(mapOf("width"to1080,"height"to"dp"to"deviceName"to先來說說我們要實(shí)現(xiàn)什么,舉個(gè)例子,我們創(chuàng)建一個(gè)notNull的委托,它只能被現(xiàn):ReadOnlyProperty和ReadWriteProperty。具體取決于我們被委托的對(duì)象是val還是var。我們要做的第一件事就是創(chuàng)建一個(gè)類然后繼承ReadWritePropertyprivateprivateclassNotNullSingleValueVar<T>():?,T>overridefungetValue(thisRef:Any?,property:<*>):TthrowoverridefunsetValue(thisRef:Any?,property:<*>,value:T)privateprivateclassNotNullSingleValueVar<T>():?,T>privatevarvalue:T?=overridefungetValue(thisRef:Any?,property::Treturnvalue?:throw}""notoverridefunsetValue(thisRef:Any?,property:KProperty<*>,value:T){this.value=if(this.value==null)elsethrowIllegalStateException("${}alreadyiobjectobjectDelegatesExtfunReadWriteProperty<Any?,T>=重新實(shí)現(xiàn)Application們不能使用構(gòu)造函數(shù)去初始化屬性。所以我們可以使用notNull委托:classclassApp:Application(){companionobject{varinstance:AppbyoverridefunonCreate()instance=this用Delegates.notNull(,屬性必須是var的。但是我們可以使用剛剛創(chuàng)建的委companioncompanionobjectvarinstance:Appby創(chuàng)建一個(gè)SQL語句和你的對(duì)象與ContentValues或者Cursors之間的解析過程。很感激般的SqliteOpenHelper,我們需要去調(diào)用getReadableDatabase()或者getWritableDatabase(,然后我們可以執(zhí)行我們的搜索并拿到結(jié)果。在這之后,我們不能忘記調(diào)用close()。使用ManagedSqliteOpenHelper我們只需forecastDbHelper.useforecastDbHelper.use在lambda里面,我們可以直接使用SqliteDatabase中的函數(shù)。它是怎么工作publicpublicfun<T>use(f:SQLiteDatabase.()->T):T{try{return}finally首先,use接收一個(gè)SQLiteDatabase用this在大括號(hào)中,并且處于SQLiteDatabase對(duì)象中。這個(gè)函數(shù)擴(kuò)展可以返valvalresult=forecastDbHelper.use{valqueriedObject=...可以返回任何對(duì)象。甚至如果我們不想返回任何值就使用Unit。由于使用了try-finall,use方法會(huì)確保不管在數(shù)據(jù)庫操作執(zhí)行成功還是失敗而且,在sqliteDatabase中還有很多有用的擴(kuò)展函數(shù),我們會(huì)在之后使用到他們。但是現(xiàn)在讓我們先定義我們的表和實(shí)現(xiàn)SqliteOopenHelper。創(chuàng)建幾個(gè)objects可以讓我們避免表名列名拼寫錯(cuò)誤、重復(fù)等。我們需要兩個(gè)objectobjectCityForecastTable{valNAME="CityForecast"valID="_id"valCITY=valCOUNTRY=DayForecast有更多的信息,就如你下面看到的有很多的列。最后一列cityId,用來保持屬于的城市id。objectobjectDayForecastTable{valNAME="DayForecast"valID="_id"valDATE=valDESCRIPTION="description"valHIGH="high"valLOW=valICON_URL="iconUrl"valCITY_ID="cityId"實(shí)現(xiàn)我們SqliteOpenHelper個(gè)SqliteDatebase,使得我們可以用它來工作。查詢可以被抽取出來放在其它c(diǎn)lassclassForecastDbHelper():ManagedSQLiteOpenHelper(App.instance,ForecastDbHelper.DB_NAME,null,ION)我們?cè)谇懊娴恼鹿?jié)中使用過我們創(chuàng)建的App.instance,這次我們同樣的包括數(shù)據(jù)庫名稱和版本。這些值我們都會(huì)與SqliteOpenHelper一起定義在companionobject中:companioncompanionobjectvalDB_NAME="forecast.db"valDB_VERSION=1valinstance:ForecastDbHelperbylazy{instance這個(gè)屬性使用了lazy委托,它表示直到它真的被調(diào)用才會(huì)被創(chuàng)建。般lazy委托的代碼塊可以阻止在多個(gè)不同的線程中創(chuàng)建多個(gè)對(duì)象。這個(gè)只會(huì)發(fā)生在兩個(gè)線程在同事時(shí)間訪問這個(gè)instance對(duì)象,它很難發(fā)生但是發(fā)生具體還有看app的實(shí)現(xiàn)。無人如何,lazy委托是線程安全的。為了去創(chuàng)建這些定義的表,我們需要去提供一個(gè)onCreate函數(shù)的實(shí)現(xiàn)。當(dāng)沒有庫使用的時(shí)候,創(chuàng)建表會(huì)通過我們編寫原生的包含我們定義好的列和類型的CREATETABLE語句來實(shí)現(xiàn)。然而Anko提供了一個(gè)簡單的擴(kuò)展函數(shù),它接收一個(gè)表的名字和一系列由列名和類型構(gòu)建的Pair對(duì)象:db.createTable(CityForecastTable.NAME,db.createTable(CityForecastTable.NAME,true,Pair(CityForecastTable.ID,INTEGER+PRIMARY_KEY),Pair(CityForecastTable.CITY,TEXT),Pair(CityForecastTable.COUNTRY,TEXT))第三個(gè)參數(shù)是一個(gè)Pair類型的vararg參數(shù)。vararg也存在在Java中,Anko中有一種叫做SqlType的特殊類型,它可以與SqlTypeModifiers混合,比如PRIMARY_KEY。操作符像之前那樣被重寫了。這個(gè)plus函數(shù)會(huì)把兩者通過合適的方式結(jié)合起來,然后返回一個(gè)新的SqlType:funfunSqlType.plus(m:SqlTypeModifier):SqlTypereturnSqlTypeImpl(name,if(modifier==null)m.toString()else"$modifier$m")但是回到我們的代碼,我們可以修改得更好。Kotlin標(biāo)準(zhǔn)庫中包含了一個(gè)叫to的收另外一個(gè)對(duì)象作為參數(shù),把兩者組裝并返回一個(gè)Pair。publicpublicfun<A,B>A.to(that:B):Pair<A,B>=Pair(this,valvalpair=object1todb.createTable(CityForecastTable.NAME,db.createTable(CityForecastTable.NAME,true,CityForecastTable.IDtoINTEGER+PRIMARY_KEY,CityForecastTable.CITYtoTEXT,CityForecastTable.COUNTRYtoTEXT)overridefunoverridefunonCreate(db:SQLiteDatabase){db.createTable(CityForecastTable.NAME,true,CityForecastTable.IDtoINTEGER+PRIMARY_KEY,CityForecastTable.CITYtoTEXT,CityForecastTable.COUNTRYtoTEXT)db.createTable(DayForecastTable.NAME,true,DayForecastTable.IDtoINTEGER+PRIMARY_KEY+AUTOIDayForecastTable.DATEtoINTEGER,DayForecastTable.DESCRIPTIONtoTEXT,DayForecastTable.HIGHtoINTEGER,DayForecastTable.LOWtoINTEGER,DayForecastTable.ICON_URLtoTEXT,DayForecastTable.CITY_IDtoINTEGER)我們有一個(gè)相似的函數(shù)用于刪除表。onUpgrade將只是刪除表,然后重建它們?;痮nUpgrade的代碼,讓它根據(jù)數(shù)據(jù)庫版本來做相應(yīng)的數(shù)據(jù)轉(zhuǎn)移。overrideoverridefunonUpgrade(db:SQLiteDatabase,oldVersion:Int,newVersion:Int){db.dropTable(CityForecastTable.NAME,true)db.dropTable(DayForecastTable.NAME,true)\h\h如,在我們的ForecastDbHelper我們可以用更智能的方式提供一個(gè)context:classclassForecastDbHelper(ctx:Context=App.instance)ManagedSQLiteOpenHelper(ctx,ForecastDbHelper.DB_NAME,ForecastDbHelper.DB_VERSION)valvaldbHelper1ForecastDbHelper(它會(huì)使用valdbHelper2ForecastDbHelper(mockedContextIterable:父類。所有我們可以遍歷一系列的都是實(shí)現(xiàn)這個(gè)接口。MutableIterable:一個(gè)支持遍歷的同時(shí)可以執(zhí)行刪除的Iterables。Collection:這個(gè)類相是一個(gè)范性集合。我們通過函數(shù)訪問可以返回集合的MutableCollection:一個(gè)支持增加和刪除item的Collection。它提供了額外的函數(shù),比如add、remove、clear等等。List:可能是最流行的集合類型。它是一個(gè)范性有序的集合。因?yàn)樗挠行?,我們可以使用get函數(shù)通過position來訪問。MutableList:一個(gè)支持增加和刪除item的List。Set:一個(gè)無序并不支持重復(fù)item的集合。Map:一個(gè)key-value對(duì)的collection。key在map中是唯一的,也就是說不能有valvallist=listOf(1,2,3,4,5,6)assertTrue(list.any{it%2==0})assertFalse(list.any{it>10})assertTrue(list.all{it<10})assertFalse(list.allassertTrue(list.all{it<10})assertFalse(list.all{it%2==0})assertEquals(assertEquals(3,list.count{it%2==0assertEquals(assertEquals(25,list.fold(4){total,next->total+next與fol
溫馨提示
- 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請(qǐng)下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請(qǐng)聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會(huì)有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
- 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
- 5. 人人文庫網(wǎng)僅提供信息存儲(chǔ)空間,僅對(duì)用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對(duì)用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對(duì)任何下載內(nèi)容負(fù)責(zé)。
- 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請(qǐng)與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時(shí)也不承擔(dān)用戶因使用這些下載資源對(duì)自己和他人造成任何形式的傷害或損失。
最新文檔
- 2025山東菏澤曹縣蘇教高級(jí)中學(xué)教師招聘6人參考筆試題庫附答案解析
- 2025江西瑞昌市投資有限責(zé)任公司下屬瑞昌市瑞興置業(yè)有限公司招聘7人備考筆試題庫及答案解析
- 2025下半年四川綿陽市鹽亭縣人力資源和社會(huì)保障局面向全縣考調(diào)30人考試備考題庫及答案解析
- 2025廣東中山市三角鎮(zhèn)水務(wù)事務(wù)中心招聘水閘、泵站管理人員2人備考筆試題庫及答案解析
- 江西省水務(wù)集團(tuán)有限公司2025年第三批社會(huì)招聘【34人】備考考試試題及答案解析
- 雅安市名山區(qū)茶城建設(shè)工程有限公司2025年第二批次公開招聘項(xiàng)目用工員工考試備考題庫及答案解析
- 網(wǎng)吧維保合同范本
- 網(wǎng)架結(jié)構(gòu)合同范本
- 耕地贈(zèng)與合同范本
- 職場新秀合同范本
- 2025廣東廣州市衛(wèi)生健康委員會(huì)直屬事業(yè)單位廣州市紅十字會(huì)醫(yī)院招聘47人(第一次)筆試考試參考題庫及答案解析
- 中國外運(yùn)招聘筆試題庫2025
- 建筑物拆除施工溝通協(xié)調(diào)方案
- 2025食品行業(yè)專利布局分析及技術(shù)壁壘構(gòu)建與創(chuàng)新保護(hù)策略報(bào)告
- 2025四川省教育考試院招聘編外聘用人員15人考試筆試模擬試題及答案解析
- 特許經(jīng)營教學(xué)設(shè)計(jì)教案
- 2025年智能消防安全系統(tǒng)開發(fā)可行性研究報(bào)告
- 胎兒窘迫課件
- 2025年國家開放大學(xué)《刑事訴訟法》期末考試備考試題及答案解析
- 論文導(dǎo)論范文
- (正式版)DB65∕T 4636-2022 《電動(dòng)汽車充電站(樁)建設(shè)技術(shù)規(guī)范》
評(píng)論
0/150
提交評(píng)論