2018年谷歌I/O,Jetpack横空出世,官方介绍如下:
Jetpack是一套库、工具和指南,可帮助开发者更轻松地编写优质应用。这些组件可帮助您遵循最佳做法、让您摆脱编写样板代码的工作并简化复杂任务,以便您将精力集中放在所需的代码上。
好好琢磨这段介绍就能解释我们刚才的问题。
Jetpack到底是什么?
google推出这个系列的原因是什么?
当然,这两年的实践也确实证明了Jetpack做到了它介绍的那样,便捷,快速,优质。所以我们作为开发者还是应该早点应用到这些工具,提高自己的开发效率,也规范我们自己的开发行为。下面我们就一起了解下Jetpack的所有工具指南。GOGOGO!
先来一张官网的总揽图:(温馨提示本文严格按照下图顺序对组件进行分析,有需要的可以从目录进入或者直接搜索查看)
AndroidKTX是包含在AndroidJetpack及其他Android库中的一组Kotlin扩展程序。KTX扩展程序可以为Jetpack、Android平台及其他API提供简洁的惯用Kotlin代码。为此,这些扩展程序利用了多种Kotlin语言功能
所以AndroidKTX就是基于kotlin特性而扩展的一些库,方便开发使用。
举:现在有个需求,让两个Set数组的数据相加,赋值给新的Set数组。正常情况下实现功能:
valarraySet1=LinkedHashSet
不知道大家发现没,原来Activity继承的Activity类都被要求改成继承AppCompatActivity类。这个AppCompatActivity类就属于AppCompat库,主要包含对MaterialDesign界面实现的支持,相类似的还包括ActionBar,AppCompatDialog和ShareActionProvider,一共四个关键类。
那么AppCompatActivity类到底对比Activity类又什么区别呢?
让您在编写应用时无需担心特定于车辆的硬件差异(如屏幕分辨率、软件界面、旋钮和触摸式控件)。用户可以通过手机上的AndroidAuto应用访问您的应用。或者,当连接到兼容车辆时,运行Android5.0(或更高版本)的手持设备上的应用可以与通过AndroidAuto投射到车辆的应用进行通信。
AndroidAuto,这个大家估计有点陌生。但是说到CarPlay大家是不是很熟悉呢?没错,AndroidAuto是Google出的车机手机互联方案。国内销售的汽车大多数没有搭载谷歌的AndroidAuto墙太高,触及不到),所以我们接触的很少。但是国外还是应用比较广泛的。
怎么让你的应用支持AndroidAuto?
使用Jetpack基准库,您可以在AndroidStudio中快速对Kotlin或Java代码进行基准化分析。该库会处理预热,衡量代码性能,并将基准化分析结果输出到AndroidStudio控制台。
为了方便我们直接创建一个Benchmark模块,右键New>Module>BenchmarkModule。这样就会帮我们导入好库了,然后我们在androidTest—java目录下创建我们的测试用例类BitmapBenchmark,并添加两个测试用例方法。
androidTestImplementation'junit:junit:4.12'androidTestImplementation'androidx.benchmark:benchmark-junit4:1.0.0'privateconstvalJETPACK="images/test001.jpg"@LargeTest@RunWith(AndroidJUnit4::class)classBitmapBenchmark{@get:RulevalbenchmarkRule=BenchmarkRule()privatevalcontext=ApplicationProvider.getApplicationContext
这个应该大家都很熟悉,65536方法数限制。由于65536等于64X1024,因此这一限制称为“64K引用限制”。意思就是单个DEX文件内引用的方法总数限制为65536,超过这个方法数就要打包成多个dex。
解决办法:
问题来了?为什么5.0以上就默认支持这个功能了呢?
这里的安全指的是数据安全,涉及到的库为Security库,具体就是安全读写文件以及安全设置共享偏好SharedPreferences。不知道大家以前加密文件都是怎么做的,我是把数据加密后再写入文件的,现在用Security库就会方便很多。
首先代码导入
测试应用在Android项目中是必不可缺的步骤,包括功能测试,集成测试,单元测试。这里主要说的是通过代码的形式编写测试用例,测试应用的的稳定性,完整性等等。
具体体现在AndroidStudio中有两个测试目录:
//手机启动Activity
//判断当前运行环境是TV环境valuiModeManager=getSystemService(UI_MODE_SERVICE)asUiModeManagerif(uiModeManager.currentModeType==Configuration.UI_MODE_TYPE_TELEVISION){Log.d(TAG,"RunningonaTVDevice")}else{Log.d(TAG,"Runningonanon-TVDevice")}//检查TV硬件的某些功能是否存在//Checkifandroid.hardware.touchscreenfeatureisavailable.if(packageManager.hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN)){Log.d("HardwareFeatureTest","Devicehasatouchscreen.")}3)按键TV中的界面事件主要包括:
BUTTON_B、BACK 返回BUTTON_SELECT、BUTTON_A、ENTER、DPAD_CENTER、KEYCODE_NUMPAD_ENTER 选择DPAD_UP、DPAD_DOWN、DPAD_LEFT、DPAD_RIGHT 导航按键配置包括:
nextFocusDown 定义当用户向下导航时下一个获得焦点的视图。nextFocusLeft 定义当用户向左导航时下一个获得焦点的视图。nextFocusRight 定义当用户向右导航时下一个获得焦点的视图。nextFocusUp 定义当用户向上导航时下一个获得焦点的视图。
这个模块的组件就是专门为MVVM框架服务的,但是每个库都是可以单独使用的,也是jetpack中比较重要的一大模块。简单说下MVVM,Model—View—ViewModel。
所以就需要,databinding进行数据的绑定,单向或者双向。viewmodel进行数据管理,绑定view和数据。lifecycle进行生命周期管理。LiveData进行数据的及时反馈。迫不及待了吧,跟随我一起看看每个库的神奇之处。
主要指的就是数据绑定库DataBinding,下面从六个方面具体介绍下
配置应用使用数据绑定:
android{...dataBinding{enabled=true}}1)布局和绑定表达式通过数据绑定,我们可以让xml布局文件中的view与数据对象进行绑定和赋值,并且可以借助表达式语言编写表达式来处理视图分派的事件。举个:
2)可观察的数据对象可观察性是指一个对象将其数据变化告知其他对象的能力。通过数据绑定库,您可以让对象、字段或集合变为可观察。
比如上文刚说到的User类,我们将name属性改成可观察对象,
dataclassUser(valname:ObservableField
3)生成的绑定类
刚才我们获取绑定布局是通过DataBindingUtil.setContentView方法生成ActivityMainBinding对象并绑定布局。那么ActivityMainBinding类是怎么生成的呢?只要你的布局用layout属性包围,编译后就会自动生成绑定类,类名称基于布局文件的名称,它会转换为Pascal大小写形式并在末尾添加Binding后缀。
正常创建绑定对象是通过如下写法:
//ActivityoverridefunonCreate(savedInstanceState:Bundle){super.onCreate(savedInstanceState)valbinding:MyLayoutBinding=MyLayoutBinding.inflate(layoutInflater)setContentView(binding.root)}//Fragment@NullablefunonCreateView(inflater:LayoutInflater,container:ViewGroup,savedInstanceState:Bundle):View{mDataBinding=DataBindingUtil.inflate(inflater,R.layout.fragment_layout,container,false)returnmDataBinding.getRoot()}4)绑定适配器
适配器这里指的是布局中的属性设置,android:text="@{user.name}"表达式为例,库会查找接受user.getName()所返回类型的setText(arg)方法。重要的是,我们可以自定义这个适配器了,也就是布局里面的属性我们可以随便定义它的名字和作用。来个
@BindingAdapter("imageUrl")funloadImage(view:ImageView,url:String){Picasso.get().load(url).into(view)}
5)将布局视图绑定到架构组件这一块就是实际应用了,和jetpack其他组件相结合使用,形成完整的MVVM分层架构。
//ObtaintheViewModelcomponent.valuserModel:UserViewModelbyviewModels()//Inflateviewandobtainaninstanceofthebindingclass.valbinding:ActivityDatabindingMvvmBinding=DataBindingUtil.setContentView(this,R.layout.activity_databinding_mvvm)//Assignthecomponenttoapropertyinthebindingclass.binding.viewmodel=userModel
刚才我们介绍的都是单向绑定,也就是布局中view绑定了数据对象,那么如何让数据对象也对view产生绑定呢?也就是view改变的时候数据对象也能接收到讯息,形成双向绑定。
很简单,比如一个EditText,需求是EditText改变的时候,user对象name数据也会跟着改变,只需要把之前的"@{}"改成"@={}"
这里要注意的一个点是,双向绑定要考虑到死循环问题,当View被改变,数据对象对应发生更新,同时,这个更新又回通知View层去刷新UI,然后view被改变又会导致数据对象更新,无限循环下去了。所以防止死循环的做法就是判断view的数据状态,当发生改变的时候才去更新view。
生命周期感知型组件可执行操作来响应另一个组件(如Activity和Fragment)的生命周期状态的变化。这些组件有助于您写出更有条理且往往更精简的代码,这样的代码更易于维护。
Lifecycles,称为生命周期感知型组件,可以感知和响应另一个组件(如Activity和Fragment)的生命周期状态的变化。
可能有人会疑惑了,生命周期就那几个,我为啥还要导入一个库呢?有了库难道就不用写生命周期了吗,有什么好处呢?举个,让你感受下。
首先导入库,可以根据实际项目情况导入
//ViewModelimplementation"androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"//LiveDataimplementation"androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"//Lifecyclesonly(withoutViewModelorLiveData)implementation"androidx.lifecycle:lifecycle-runtime-ktx:$lifecycle_version"//.......现在有一个定位监听器,需要在Activity启动的时候开启,销毁的时候关闭。正常代码如下:
classBindingActivity:AppCompatActivity(){privatelateinitvarmyLocationListener:MyLocationListeneroverridefunonCreate(savedInstanceState:Bundle){super.onCreate(savedInstanceState)myLocationListener=MyLocationListener(this){location->//updateUI}}publicoverridefunonStart(){super.onStart()myLocationListener.start()}publicoverridefunonStop(){super.onStop()myLocationListener.stop()}internalclassMyLocationListener(privatevalcontext:Context,privatevalcallback:(Location)->Unit){funstart(){//connecttosystemlocationservice}funstop(){//disconnectfromsystemlocationservice}}}乍一看也没什么问题是吧,但是如果需要管理生命周期的类一多,是不是就不好管理了。所有的类都要在Activity里面管理,还容易漏掉。所以解决办法就是实现解耦,让需要管理生命周期的类自己管理,这样Activity也不会遗漏和臃肿了。上代码:
overridefunonCreate(savedInstanceState:Bundle){super.onCreate(savedInstanceState)myLocationListener=MyLocationListener(this){location->//updateUI}lifecycle.addObserver(myLocationListener)}internalclassMyLocationListener(privatevalcontext:Context,privatevalcallback:(Location)->Unit):LifecycleObserver{@OnLifecycleEvent(Lifecycle.Event.ON_START)funstart(){}@OnLifecycleEvent(Lifecycle.Event.ON_STOP)funstop(){//disconnectifconnected}}很简单吧,只要实现LifecycleObserver接口,就可以用注释的方式执行每个生命周期要执行的方法。然后在Activity里面addObserver绑定即可。
同样的,Lifecycle也支持自定义生命周期,只要继承LifecycleOwner即可,然后通过markState方法设定自己类的生命周期,举个
LiveData是一种可观察的数据存储器类。与常规的可观察类不同,LiveData具有生命周期感知能力,意指它遵循其他应用组件(如Activity、Fragment或Service)的生命周期。这种感知能力可确保LiveData仅更新处于活跃生命周期状态的应用组件观察者。
LiveData是一种可观察的数据存储器类。等等,这个介绍好像似曾相识?对,前面说数据绑定的时候就有一个可观察的数据对象ObservableField。那两者有什么区别呢?
1)LiveData具有生命周期感知能力,可以感知到Activity等的生命周期。这样有什么好处呢?很常见的一点就是可以减少内存泄漏和崩溃情况了呀,想想以前你的项目中针对网络接口返回数据的时候都要判断当前界面是否销毁,现在LiveData就帮你解决了这个问题。
具体为什么能解决崩溃和泄漏问题呢?
2)LiveData更新数据更灵活,不一定是改变数据,而是调用方法(postValue或者setValue)的方式进行UI更新或者其他操作。
好了。还是举个更直观的看看吧:
所以说白了,Navigation就是一个Fragment的管理框架。怎么实现?创建Activity,Fragment,进行连接。
1)导入库
defnav_version="2.3.0"implementation"androidx.navigation:navigation-fragment-ktx:$nav_version"implementation"androidx.navigation:navigation-ui-ktx:$nav_version"2)创建3个Fragment和一个Activity
3)创建res/navigation/my_nav.xml文件
5)配置完了之后,就可以设置具体的跳转逻辑了。
Room持久性库在SQLite的基础上提供了一个抽象层,让用户能够在充分利用SQLite的强大功能的同时,获享更强健的数据库访问机制。
所以Room就是一个数据库框架。问题来了,市面上那么多数据库组件,比如ormLite,greendao等等,为什么google还要出一个room,有什么优势呢?
既然Room这么优秀,那就用起来吧。Room的接入主要有三大点:DataBase、Entity、Dao。分别对应数据库,表和数据访问。
1)首先导入库:
@Database(entities=arrayOf(User::class),version=1)abstractclassUserDb:RoomDatabase(){abstractfunuserDao():UserDaocompanionobject{privatevarinstance:UserDb=null@Synchronizedfunget(context:Context):UserDb{if(instance==null){instance=Room.databaseBuilder(context.applicationContext,UserDb::class.java,"StudentDatabase").build()}returninstance!!}}}3)建表,可以设置主键,外键,索引,自增等等
@EntitydataclassUser(@PrimaryKey(autoGenerate=true)valid:Int,valname:String)4)Dao,数据操作
分页库可帮助您一次加载和显示一小块数据。按需载入部分数据会减少网络带宽和系统资源的使用量。
所以Paging就是一个分页库,主要用于Recycleview列表展示。下面我就结合Room说说Paging的用法。使用Paging主要注意两个类:PagedList和PagedListAdapter。1)PagedList用于加载应用数据块,绑定数据列表,设置数据页等。结合上述Room的Demo我继续写了一个UserModel进行数据管理:
classUserModel(app:Application):AndroidViewModel(app){valdao=UserDb.get(app).userDao()varidNum=1companionobject{privateconstvalPAGE_SIZE=10}//初始化PagedListvalusers=LivePagedListBuilder(dao.getAllUser(),PagedList.Config.Builder().setPageSize(PAGE_SIZE).setEnablePlaceholders(true).build()).build()//插入用户funinsert()=ioThread{dao.insert(newTenUser())}//获取新的10个用户funnewTenUser():ArrayList
classUserAdapter:PagedListAdapter
ok,数据源,adapter都设置好了,接下来就是监听数据,刷新数据就可以了
//监听users数据,数据改变调用submitList方法viewModel.users.observe(this,Observer(adapter::submitList))对,就是这么一句,监听PagedList,并且在它改变的时候调用PagedListAdapter的submitList方法。这分层够爽吧,其实这也就是paging或者说jetpack给我们项目带来的优势,层层解耦,adapter都不用维护list数据源了。
终于说到ViewModel了,其实之前的demo都用了好多遍了,ViewModel主要是从界面控制器逻辑中分离出视图数据,为什么要这么做呢?主要为了解决两大问题:
所以ViewModel诞生了,还是解耦,我把数据单独拿出来管理,还加上生命周期,那不就可以解决这些问题了吗。而且当所有者Activity完全销毁之后,ViewModel会调用其onCleared()方法,以便清理资源。
接下来举个,看看ViewModel具体是怎么使用的:
使用WorkManagerAPI可以轻松地调度即使在应用退出或设备重启时仍应运行的可延迟异步任务。
听听这个介绍就很神奇了,应用退出和设备重启都能自动运行?通过广播?那数据又是怎么保存的呢?听说还可以执行周期性异步任务,顺序链式调用哦!接下来一一解密
一般这个API应用到什么场景呢?想想,可靠运行,还可以周期异步。对了,发送日志。可以通过WorkManager设定周期任务,每天执行一次发送日志的任务。而且能够保证你的任务可靠运行,一定可以上传到,当然也是支持监听任务结果等。:
dependencies{defwork_version="2.3.4"//Kotlin+coroutinesimplementation"androidx.work:work-runtime-ktx:$work_version"//optional-RxJava2supportimplementation"androidx.work:work-rxjava2:$work_version"//optional-GCMNetworkManagersupportimplementation"androidx.work:work-gcm:$work_version"}2)新建任务类,继承Worker,重写doWork方法,返回任务结果。
classUploadLogcatWork(appContext:Context,workerParams:WorkerParameters):Worker(appContext,workerParams){overridefundoWork():Result{if(isUploadLogcatSuc()){returnResult.success()}elseif(isNeedRetry()){returnResult.retry()}returnResult.failure()}funisUploadLogcatSuc():Boolean{varisSuc:Boolean=falsereturnisSuc}funisNeedRetry():Boolean{varisSuc:Boolean=falsereturnisSuc}}3)最后就是设定约束(是否需要网络,是否支持低电量,是否支持充电执行,延迟等等),执行任务(单次任务或者循环周期任务)
CameraX是一个Jetpack支持库,旨在帮助您简化相机应用的开发工作。它提供一致且易于使用的APISurface,适用于大多数Android设备,并可向后兼容至Android5.0(API级别21)。虽然它利用的是camera2的功能,但使用的是更为简单且基于用例的方法,该方法具有生命周期感知能力。它还解决了设备兼容性问题,因此您无需在代码库中添加设备专属代码。这些功能减少了将相机功能添加到应用时需要编写的代码量。
可能是官方听到了我的抱怨,于是CameraX来了,CameraX是基于camera2进行了封装,给我们提供了更简单的解决方案来解决我们之前的困境。来了
DownloadManager,大家应该都很熟悉吧,android2.3就开通提供的API,很方便就可以下载文件,包括可以设置是否通知显示,下载文件夹名,文件名,下载进度状态查询等等。来
Android多媒体框架支持播放各种常见媒体类型,以便您轻松地将音频、视频和图片集成到应用中。
MediaPlayer不用说了,应该所有人都用过吧,待会就顺便提一嘴。ExoPlayer是一个单独的库,也是google开源的媒体播放器项目,听说是YoutubeAPP所使用的播放器,所以他的功能也是要比MediaPlayer强大,支持各种自定义,可以与IJKPlayer媲美,只是使用起来比较复杂。
1)MediaPlayer
compile'com.google.android.exoplayer:exoplayer:r2.X.X'varplayer:SimpleExoPlayer=nulloverridefunonCreate(savedInstanceState:Bundle){super.onCreate(savedInstanceState)setContentView(R.layout.activity_exoplayer)//初始化player=SimpleExoPlayer.Builder(this).build()video_view.player=playerplayer.playWhenReady=true//设置播放资源valdataSourceFactory:DataSource.Factory=DefaultDataSourceFactory(this,Util.getUserAgent(this,"yourApplicationName"))valuri:Uri=Uri.EMPTYvalvideoSource:MediaSource=ProgressiveMediaSource.Factory(dataSourceFactory).createMediaSource(uri)player.prepare(videoSource)}privatefunreleasePlayer(){//释放player.release()player=null}好像也不复杂?哈哈,更强大的功能需要你去发现。
通知是指Android在应用的界面之外显示的消息,旨在向用户提供提醒、来自他人的通信信息或应用中的其他实时信息。用户可以点按通知来打开应用,也可以直接在通知中执行某项操作。
这个应该都了解,直接上个
权限的作用是保护Android用户的隐私。Android应用必须请求权限才能访问敏感的用户数据(例如联系人和短信)以及某些系统功能(例如相机和互联网)。系统可能会自动授予权限,也可能会提示用户批准请求,具体取决于访问的功能。
权限大家应该也都很熟悉了。
建议使用AndroidXPreferenceLibrary将用户可配置设置集成至您的应用中。此库管理界面,并与存储空间交互,因此您只需定义用户可以配置的单独设置。此库自带Material主题,可在不同的设备和操作系统版本之间提供一致的用户体验。
开始看到这个标题我是懵逼的,设置?我的设置页官方都可以帮我写了?然后我就去研究了Preference库,嘿,还真是,如果你的App本身就是Material风格,就可以直接用这个了。但是也正是由于风格固定,在实际多样的APP中应用比较少。来个
Android应用的一大优点是它们能够互相通信和集成。如果某一功能并非应用的核心,而且已存在于另一个应用中,为何要重新开发它?
这里的共享主要指的是应用间的共享,比如发邮件功能,打开网页功能,这些我们都可以直接调用系统应用或者其他三方应用来帮助我们完成这些功能,这也就是共享的意义。
切片是界面模板,可以在Google搜索应用中以及Google助理中等其他位置显示您应用中的丰富而动态的互动内容。切片支持全屏应用体验之外的互动,可以帮助用户更快地执行任务。您可以将切片构建成为应用操作的增强功能。
这个介绍确实有点模糊,但是说到Slice你会不会有点印象?2018年GoogleI/0宣布推出新的界面操作Action&Slice。而这个Slice就是这里说的切片。他能做什么呢?可以让使用者能快速使用到app里的某个特定功能。只要开发者导入Slice功能,使用者在使用搜寻、GooglePlay商店、GoogleAssitant或其他内建功能时都会出现Slice的操作建议。
说白了就是你的应用一些功能可以在其他的应用显示和操作。
怎么开发这个功能呢?很简单,只需要一步,右键New—other—SliceProvider就可以了。slice库,provider和SliceProvider类都配置好了,方便吧。贴下代码:
好了,接下来就是测试切片使用了,完整的切片URI是slice-content://{authorities}/{action},所以这里对应的就是slice-content://com.panda.jetpackdemo.slice/hello。
动画也是老生常谈的内容了。说到动画,我们都会想到帧动画,属性动画,补间动画等等。今天我们从不一样的角度归类一些那些你熟悉又不熟悉的动画。
1)为位图添加动画
其中主要讲下AnimatedVectorDrawable,VectorDrawable是为了支持SVG而生,SVG是可缩放矢量图形,用xml代码描绘图像。下面举个
3)基于物理特性的动作这部分可以让动画应尽可能运用现实世界的物理定律,以使其看起来更自然。比如弹簧动画和投掷动画。这里举个弹簧动画的
defdynamicanimation_version="1.0.0"implementation"androidx.dynamicanimation:dynamicanimation:$dynamicanimation_version"valspringForce=SpringForce(0.0f).setDampingRatio(0f)//设置阻尼.setStiffness(0.5f)//设置刚度imageView2.setOnClickListener{SpringAnimation(imageView2,DynamicAnimation.TRANSLATION_Y).apply{spring=springForcesetStartVelocity(500f)//设置速度start()}}4)为布局更改添加动画借助Android的过渡框架,您只需提供起始布局和结束布局,即可为界面中的各种运动添加动画效果。也就是说我们只需要提供两个场景,代表动画前后,然后就可以自动生成动画了。要注意的是,两个场景其实在一个页面中。
EmojiCompat支持库旨在让Android设备及时兼容最新的表情符号。它可防止您的应用以的形式显示缺少的表情符号字符,该符号表示您的设备没有用于显示文字的相应字体。通过使用EmojiCompat支持库,您的应用用户无需等到AndroidOS更新即可获取最新的表情符号。
这一模块就是为了兼容性提供的一个库:EmojiCompat,通过CharSequence文本中的emoji对应的unicode编码来识别emoji表情,将他们替换成EmojiSpans,最后呈现emoji表情符号。
Fragment表示FragmentActivity中的行为或界面的一部分。您可以在一个Activity中组合多个片段,从而构建多窗格界面,并在多个Activity中重复使用某个片段。您可以将片段视为Activity的模块化组成部分,它具有自己的生命周期,能接收自己的输入事件,并且您可以在Activity运行时添加或移除片段(这有点像可以在不同Activity中重复使用的“子Activity”)。片段必须始终托管在Activity中,其生命周期直接受宿主Activity生命周期的影响。
布局可定义应用中的界面结构(例如Activity的界面结构)。布局中的所有元素均使用View和ViewGroup对象的层次结构进行构建。View通常绘制用户可查看并进行交互的内容。然而,ViewGroup是不可见容器,用于定义View和其他ViewGroup对象的布局结构
布局部分主要注意下比较新的两个布局ConstraintLayout和MotionLayout。
所以MotionLayout就是带动画的ConstraintLayout呗,这里举个看看效果:
主要是通过app:layoutDescription="@xml/scene_01"设定动画场景,然后在scene_01场景中就可以设置起始和结束位置,动画属性,就可以完成对动画的设置了。是不是有点自定义view那味了,关键这个只需要布局一个xml文件就可以了!还不试试?
出色的视觉设计是应用成功的关键所在,而配色方案是设计的主要组成部分。调色板库是一个支持库,用于从图片中提取突出颜色,帮助您创建具有视觉吸引力的应用。
没想到吧,Android还有官方的调色板库—Palette。那到底这个调色板能做什么呢?主要用来分析图片中的色彩特性。比如图片中的暗色,亮色,鲜艳颜色,柔和色,文字颜色,主色调,等等。