[TOC]
为什么是梦幻开局呢?因为新冠爆发了, 还记得我在《2019就此告别2020新的征程》里提到过:
到了2020年初, 也就是2、3月份彻底爆发了。什么是梦幻开局?这才是梦幻开局。(战术后仰.gif)
2019年, 智晗来了很多新的小伙伴, 也发生了很多人员和组织架构上的变动。(什么?这是2020年的年终总结, 为什么要说2019年?) 换了2个CTO, 又来了1个PMO,业务线又细分,但人力却不能很好地收敛,导致不少人在多条业务线上反复横跳。加之疫情的原因, 工作又是远程进行,信息沟通不及时,以及半吊子PMO欺上瞒下散布周六加班且包含加班工资的错误消息…总而言之, 就是多方原因导致了我萌生了跳槽的想法。
从第一场面试算起到最终确定Offer, 花了近一个月。其中也面试了一些其他知名的公司,有点不在状态,之前总想着先面试几家小公司找找感觉,后来发现战线拖得越长,后面越疲于应对。最佳的状态是开门见山,直捣黄龙。好在最后斩获了心仪的Offer, 最终来到了网易云音乐。
之前没在网上看到网易加班的负面新闻,尽管hr有提醒目前工作状态是995,但我仍以为并未如此夸张。后来证明我错了。网易云音乐8.0的开发任务,比想象中的还要繁重。与智晗的工作节奏对比之下,我一度觉得我亏了。好在8.0之后,恢复了较为稳健的迭代节奏。
不得不说云音乐的代码质量,确实比智晗高上不少;开发人员的科学素养也较以往经历过的公司好上不少。有非常多值得学习的地方,当然也有不足,取其精华去其糟粕嘛。不过云音乐的组件化进程确实有些慢,早在2017年大搜车就已经开始了组件化,但云音乐到2020年下半年才开始做这件事。
在网易一期见到了血吼本体:
结识了一群有趣的小伙伴。每天准时干饭; 日常吹逼; 周常开车(羽毛球、英雄联盟, 比较活跃的小伙伴还有其他活动,比如吃饭,唱k,滑雪)。非常欢乐。
内推了一位小伙伴。拿到了伯乐奖¥3k+云音乐黑胶年费会员。
钟于意,衷于心,忠于一人,终于修成正果。
为什么是儿童节领证?以后有了孩子,一家三口可以一起过节。:D
放生了一只小乌龟。
但甲鱼就没这么好运气了。
俊杰请客游横店。毕业多年没见了, 有点怀念以前的日子。
就是逛的有点累。
买了人生的第一台主机电脑。10850k+3070TUF。
买了人生的第一台iPhone。
老婆入职新公司, 买了一台MBP。
去年提到的《我的英雄学院》突然被爆出辱华, 全网下架了。侮辱我们中华民族的狗东西, 滚粗。
JoJo第六部还是没出,就像给他爱六还没出。(都2020年了到底是谁还在买给她爱五?!)
讲现代兵王穿越到异世界装逼的。有点上头。
国漫之光啊! 必须吹爆! 据说2021年春季会出一集60分钟的超长大结局【灵笼-终章】。期待ing~
陪老婆又看了一遍。还是精彩!!!
Nintendo Switch
终于全神庙了。目前迷你任务88/90。呀哈哈还差的有点多。山神还没去看过。双弹击飞初有成效, 现在都不好好走路了。哈哈哈哈…坐等续作。
无双割草游戏。就当补补100年前的剧情了。小舅子是真的可爱~
前期还挺好玩,后面发现东西堆的到处都是,又要整理,是真的费劲。钓鱼也钓不到好东西。开岛都是开到垃圾岛。大头菜也卖不出好价格。唉,弃坑了~
炉石传说
日常下棋。版本迭代了几波,有点不会玩了。任务系统也变样了,每次新赛季重置战旗积分, 还能不能好好玩了。最近潘sir在我号上做了一套奇迹换家德,还挺有意思。
PC
Epic送了不少游戏,但没啥好玩的。都是先领了再说。
被CDPR骗了呀。在游戏中随处可见的未完成的痕迹。只能期待日后打补丁修复吧。
先买了,还没玩。
其实在Switch买了这个游戏了。最近又跟陈老师和阿帅一起玩了一下。玩了后面还没玩过的关卡,还是挺有意思的。
和干饭群的同事们一起玩。日常拖后腿。
一个目标也没完成。
既然如此, 那就大胆点。2021年我要买房买车!
]]>[TOC]
两年多前, 我从事一档被称为“Android for Beginners”的面向零编程基础的学生并引导他们建立自己的第一个Android应用程序的课程。作为课程的一部分, 学生们需要构建一个简单的单页面应用——《篮球计分器》。
《篮球计分器》是一个非常直观的App。它的按钮用于修改篮球队的得分。尽管最终完成的app有一个bug, 如果你旋转手机屏幕,你当前的分数就会莫名其妙地消失。
发生了什么?旋转设备是应用在其生命周期内可以进行的一些配置更改之一,包括键盘可用性和更改设备语言。所有这些配置更改都会导致Activity被销毁并重新创建。
这种行为引导我们应当进行诸如在设备旋转时使用横向特定布局的操作。不幸的是,这对于新手(哪怕有时候并不是那么新手)的工程师来说可能是个头疼的问题。
在2017年的Google I/O大会上, Android框架团队推出了一套新的架构组件,其中一个组件可以解决这一确切的旋转问题。
ViewModel类旨在以生命周期感知的方式保存和管理与UI相关的数据。它使得数据可以在配置变更(比如屏幕旋转)的情况下存活。
这篇文章是探索ViewModel来龙去脉的系列文章的第一篇。在这篇文章中,我将:
现在存在一个潜在的挑战是: Android的Activity的生命周期有很多状态,并且由于配置更改,单个Activity可能会在这些不同状态之间循环多次。
当一个Activity经历了所有这些状态,您可能还需要将瞬态UI数据保存在内存中。我将瞬态UI数据定义为UI所需的数据,包括用户输入的数据,运行时生成的数据或从数据库加载的数据。这些数据可以是图片位图、用于RecyclerView的对象列表等,在这个例子中是篮球队分数。
显然, 你也许使用了onRetainNonConfigurationInstance
在配置更改到重新加载它的期间保存瞬态UI数据。但是,如果您的数据不需要知道或管理Activity所处于的生命周期状态会不会隆起(swell)?与其在Activity内不声明像scoreTeamA这样的变量,而是异想天开地将其绑定到Activity生命周期,不如将这些数据存储在Activity之外的其他地方,该怎么办?这就是ViewModel类的目的。
在下面的图表中, 你可以看到Activity的生命周期在旋转过程中的状态流转直到finish
。ViewModel的生命周期显示在关联的Activity的生命周期的旁边。请注意,这个ViewModels可以简单轻松地与UI控制器(Activities/Fragments)结合使用。
ViewModel存在于你第一次请求一个ViewModel(通常在Activity的onCreate
中)直到Activity已经finished或者destoryed期间。onCreate
在Activity的一次声明中也许会被地调用多次,比如当app发生旋转时,但是ViewModel在此期间一直存活着。
使用ViewModel的三个步骤:
注意:为了创建ViewModel类,你第一步需要添加正确的lifecycle
依赖. 看这里如何做。
通常情况下,你需要为你的app的每隔页面创建一个ViewModel类。这个ViewModel类会掌控和管理所有的与页面相关数据,并且提供get/set方法用于存取数据。这会将显示Activity的UI代码与您的用于显示UI的数据分离开(显示UI的数据现在位于ViewModel中)。那么,让我们为《篮球计分器》的那个页面创建一个ViewModel类:
1 | public class ScoreViewModel extends ViewModel { |
为了简洁起见,我选择将数据作为公共成员存储在我的ScoreViewModel.java
中,但是创建更好的封装getter和setter的方法是一个好主意。
你的UI控制器(即Activity或Fragment)需要知道你的ViewModel。这样在诸如“在《篮球计分器》中按下按钮去增加某一队分数”等UI交互发生时, UI控制器可以展示和更新数据。
但是,ViewModels不应保留对Activity,Fragment或Context的引用。此外,ViewModels不应包含那些拥有对UI控制器的引用的元素,例如Views,因为这将创建对Context的间接引用。
你不该保存这些对象的原因是, ViewModels的寿命超出了特定的UI控制实例之外——如果您将Activity旋转3次,则您刚刚创建了三个不同的Activity实例,但是只有一个ViewModel实例。
考虑到这一点,让我们来创建这个UI控制器/ViewModel关联。你想要为在UI控制器中的ViewModel创建一个成员变量。那么在onCreate
中,你应该这样写:
1 | ViewModelProviders.of(<Your UI controller>).get(<Your ViewModel>.class) |
对于《篮球计分器》,它应该像这样写:
1 |
|
注意: “ViewModel中没有上下文”的规则有一个例外: 有时您可能需要一个Application的Context(而不是Activity的Context)来与诸如系统服务之类的东西一起使用。那么,您可以将应用程序上下文储存在ViewModel中,因为应用程序上下文与应用程序生命周期相关联。这不同于与Activity生命周期相关的Activity的Context。实际上如果需要Application的Context,则应该拓展AndroidViewModel
,它只是一个包含Application引用的ViewModel。
为了访问和修改UI数据,你现在可以在你的ViewModel中使用数据。这里有一个新的onCreate
方法和一个为队伍A增加分数的更新方法的例子:
1 | // The finished onCreate method |
专业意见: ViewModel也可以很好地和其他架构组件一起工作。比如: LiveData, 这我就不再这篇文章中拓展了。使用LiveData的另一个好处是它可以观察到:当数据更改时,它可以触发UI更新。您可以在此处了解有关LiveData的更多信息。
ViewModelsProviders.of
的更深入了解在MainActivity中第一次调用ViewModelsProviders.of
方法时,一个ViewModel实例就创建完成了。当这个方法再次被调用时(即onCreate
方法被再次调用),它会返回一个与确切的《篮球计分器》的MainActivity相关联的预先存在的ViewModel。这就是保存数据的原因。
仅当您传入正确的UI控制器作为第一个参数时, 此方法才有效。尽管你永远不应将UI控制器储存在ViewModel内,但ViewModel类确实会使用您传入的第一个参数(UI控制器)来跟踪ViewModel和UI控制器实力在后台之间的关联。
1 | ViewModelProviders.of(<THIS ARGUMENT>).get(ScoreViewModel.class); |
这使得您的app可以打开很多不同的Activity/Fragment实例,但是持有不同的ViewModel内容。让我们想象一下,如果我们拓展我们的《篮球计分器》例子——可以记录多场篮球赛的比分。所有的比赛都展示在一个列表页。点击列表中的其中一个比赛,就会打开一个像我们当前的MainActivity的页面,我们可以将它称作GameScoreActivity
。
为了你所打开的每场不同的比赛的积分页面,你可以在OnCreate
中关联GameScoreActivity
和ViewModel, 这样就会创建不同的ViewModel实例。如果你旋转了其中一个屏幕,那么与相同的ViewModel的关联关系会被保存下来。
通过调用ViewModelProviders.of(<Your UI controller>).get(<Your ViewModel>.class)
所有的这些逻辑都为你完成了。所以只要你传递了正确的UI控制器实例(Your UI controller
),它就起作用了。
最后的思考: ViewModels十分巧妙地分离了用于展示UI的数据和UI控制器的代码。它们不能完全解决数据持久性和保存应用程序状态的问题。在下一篇文章里, 我会探索Activity生命周期与ViewModels的微妙交互,以及ViewModels与onSaveInstanceState
的比较。
在这篇文章里吗,我探索了ViewModel类的最基础用法。关键要点是:
ViewModelProviders.of
方法跟踪通过作为参数传入的UI控制器与ViewModel关联的UI控制器。想要了解更多ViewModel化的优势吗?请查阅:
架构组件的创建给予你们的反馈。如果您有什么问题和意见关于ViewModel或者任何其他的架构组件,请查看我们的意见反馈页面。有任何问题关于此系列文章吗?留下评论吧!
]]>在Android开发中有很多用于处理延迟处理的后台任务的方式。这个代码实验室(codelab)涵盖了WorkManager(一个向后兼容的、灵活的、简单易用的、用于延迟处理后台任务的代码库(library))的用法。
WorkManager是Android JetPack的一部分,是结合了机会性执行和保证执行的的用于处理后台任务的架构组件(Architecture Component)。机会性执行,意为WorkManager将尽快完成您的后台工作。保证执行,意为无论在什么样的情况下,即使用户离开了应用程序, WorkManager也会保证按逻辑执行工作。
WorkManager是一个难以置信的、灵活的代码库,它拥有很多额外的优势。比如:
注意:
WorkManager提供了一些API。例如: JobSCheduler和AlarmManager。WorkManager根据诸如用户设备API之类的条件选择要使用的正确API。欲了解更多信息,请查阅WorkManager文档。
对于即使用户离开特定的屏幕或您的应用程序也需要完成的有价值的任务,WorkManager库也是个不错的选择。
以下是使用WorkManager的几个简单例子:
WorkManager提供有保证的执行,但并非所有任务都需要执行。因此,这不是将所有任务避免在主线程中运行的万能方法。
有关何时使用WorkManager的更多详细信息,请查阅《后台处理指南》。
近些年来,智能手机越来越擅长拍照。摄影师能够可靠地模糊拍摄神秘事物的日子已经一去不复返了。在此代码实验室中,您将使用Blur-O-Matic,该应用程序可模糊照片和图像并将结果保存到文件中。那是尼斯湖怪兽或evelopera玩具潜水艇吗?使用Blur-O-Matic,没人会知道。
在你的项目中使用WorkManager
安排一个简单的任务
输入和输出参数
串行的任务
独特的任务
将任务状态展示在UI上
取消任务
任务约束
如果你卡关了,如果你需要查看最终状态的代码,你可以使用下面的代码链接:
或者, 如果你更希望从Github克隆完整的WorkManager的代码实验室的代码:
1 | git clone -b kotlin https://github.com/googlecodelabs/android-workmanager |
[TOC]
2019年年初阔别了工作两年的大搜车,满怀新鲜感来到了学智云。至此开启了我的梦幻2019年。
初到公司接触的第一个工程就是学智云Android主工程。初见之时真是说不出的梦幻感。
xuezhi
, 它的根目录是xuezhi/xuezhi
,再往里面才是build.gradle
、setting.gradle
等工程代码文件)鉴于这些存在的问题,我进入公司的第一件事便是,对其进行工程结构改造。首要解决的问题是代码回溯问题。
改造之前稍微解释一下本地相对路径依赖。
改造前目录结构如下:
1 | |--xuezhi(学智云Android主工程Git仓库) |
其中module.depend
配置文件代码片段:
1 | 说明: |
然后在setting.gradle
中遍历module.depend
中的文件
1 | // 省略解析配置文件的代码 |
工程结构的问题很明显了,除了我说的难回溯的问题外,还有配置麻烦的问题。对于多人协作的项目而言, 增加和删除新的业务模块都需要每个人本地的子模块的本地路径保持一致。这个问题不复杂但是麻烦。哪有别人改了代码后我拉去了远程代码还不行, 还跟学他的本地路径配置?! 这工程结构是真的有够反人类的。回归主题,那么这个工程结构为什么会有难回溯的问题呢?我来描述一个场景。主工程和业务模块都在各自提交代码, 但是比如app发布1.0版本,此时主工程知道自己在哪个git节点,也知道各自的业务模块在哪个git节点。但是当我发布2.0版本的时候,还知道各自的业务模块在发布1.0的时候是哪个git节点吗?显然是不能的。因为业务模块的git节点与主工程之间没有任何的git约束,仅仅通过源码依赖而已。甚至于你想回到1.0的发布版本重新打1.0包都有可能运行不起来(要是找不到正确的节点的话)。只有找对了所有业务模块在app发布1.0时的各自的git节点,才能完全还原1.0的包。费劲不?我甚至不知道这样的工程结构怎么根据线上bug日志排查问题…
此时git submodule
的工程结构方案应运而生。它保留了原来的工程目录结构,对工程的破坏性小, 同时又加强了主/子工程之间的git节点联系。
什么是git submodule
?
子模块允许你将一个 Git 仓库作为另一个 Git 仓库的子目录。 它能让你将另一个仓库克隆到自己的项目中,同时还保持提交的独立。
使用了git submodule
的工程下会生成一个.gitmodules
文件, 文件内容如下:
1 | [submodule "submodules/AndroidLearnCenter"] |
改造后的项目的目录结构如下:
1 | |--xuezhi(学智云Android主工程Git仓库) |
setting.gradle
中的代码也更改为:
1 | // 主工程 |
完成改造后, 每个子模块的git记录都可以查看,也可以提交到子模块的仓库,再也不需要打开多个AndroidStudio了。
maven私服+坐标依赖是目前的主流做法。私服其实早有搭建, 但疏于使用。在工程结构的规范化进程推动下,后期我们慢慢地将多个业务稳定的模块拆分, 打包上传到maven私服。经过几个月的努力, 所有的业务模块全都上传到了maven。git submodule结构也已经彻底移除。但在工程结构改造的路上, git submodule功不可没。
其实在2018年我已经使用了一段时间的Kotlin, 但并没有用于商业级项目中,只是偶尔耍耍题,小打小闹罢了。顺便给我们的仓库打个广告: RichCodersAndMe/LeetCode-Solution
自从Google在2017年的Google I/O大会上宣布Kotlin
成为Android 开发的一级(first-class)编程语言后,Kotlin在社区的影响力和发展稳步上升;而后又在2019年的Google I/O大会上宣布, Android 团队将会优先提供 Kotlin 版本的 Jetpack(Kotlin first)。官方如此尽力地推广, 我们开发者还有什么理由拒绝呢?
但是商业级项目,以稳定为主,团队成员对Kotlin的掌握程度参差不齐,难免出现很多问题。那么如何优雅地过渡这个阶段,以推动Kotlin在自己团队内使用呢?
悬浮窗开发调试工具应运而生。现已开源: relish-wang/genos。
由于开发调试工具仅用于开发测试环境, 并不会运用于线上, 所以可以放心地使用Kotlin开发各种各样的插件;同时各种各样的功能强大的插件也极大地提高了我们开发和调试的效率。真可谓是一举两得!秒啊!
随着对Kotlin的熟悉, 越来越感受到Kotlin为我们的工作带来的效率提升以及它优良的语法设计带来的舒适的编码体验,我们开始尝试在实际项目中使用Kotlin。恰好有一个新的XX云App的开发需求, 我迎难而上接下这个开发任务, 并全程使用Kotlin编写这个App。下图是截止到现在的XX云App的Kotlin语言在工程中所占的比例:
在实际开发中其实有很多“水土不服”, 这些“水土不服”都源于我们长期对Java语言的使用而带来的惯性思维。举几个简单的例子:
Java写法:
1 | if(obj != null && obj.someCondition()){ |
要是你仍然保持着Java的写法, 那这段代码转成Kotlin应该是这样:
1 | if(obj?.someCondition() == true){ |
如果用上takeIf
函数, 这段代码应该写成:
1 | obj?.takeIf{ it.someCondition() }?.doSomeThing() |
条件判断语句写成了一行代码的链式的调用形式。代码简短, 逻辑清晰明了。
data
)我们在Java中写一个DTO
的话应该是这样的:
1 | public class User { |
如果你仍然保持着Java的写法, 那这段代码转成Kotlin应该是这样:
1 | class User { |
但是如果你用上数据类(data
), 那应该写成这样:
1 | data class User( |
成员属性声明为val
, 则对于Java调用来说只有get
方法, 声明为var
则有get
和set
方法。
相比很多的工程中都有类似这样的工具方法:
1 | DisplayUtils.dp2px(Context context, int dp) |
每次调用的时候都要传入context
和具体数值。Kotlin的扩展函数为这种代码提供了更加舒适的语法:
1 | // 1 声明扩展函数 |
Kotlin还有很多与Java的不同的语法习惯和编程思想:
with
/apply
)更多的Kotlin习惯用法<-点这里
Coroutines
)在XX云需求完成后,我转而负责学智云App的开发。有了在XX云中使用Kotlin的经验, 这次在学智云中我更加大胆地引入了Kotlin炙手可热的协程库(Coroutines).
为了体现Coroutines
的语法优势这里我用Retrofit+RxJava
和Retrofit+Coroutines
做对比:
判断课程是否可用, 可用的情况下拉去课程的讲师列表,并展示
Retrofit+RxJava
:
1 | // 1 根据课程ID判断课程是否可用 |
Retrofit+Coroutines
:
1 | // Rxjava2不允许传递null对象, 但Kotlin Coroutines可以直接在异常处退出(就像写同步代码一样)。因此两段代码的逻辑稍有不同。 |
拉取讲师列表, 将服务端返回的DTO转化为VO进行展示
Retrofit+RxJava
:
1 | CourseApi.getLecturerList(courseId) |
Retrofit+Coroutines
:
1 | lifecycleScope.launch(Dispatchers.Main) { |
拉去课程的课时列表并展示; 拉去课程的详情并展示
Retrofit+RxJava
:
1 | // 1 根据课程ID,获取课程课时目录 |
Retrofit+Coroutines
:
1 | lifecycleScope.launch(Dispatchers.Main) { |
不得不说Kotlin语法的简洁性, 相同的逻辑代码, 使用Kotlin Coroutines减少了一半以上的代码量。而且协程的异步代码的同步写法可读性高, 逻辑也更为清晰,实乃提供工作效率的利器!
工作不是完成日常需求就行,应该更多地考虑工程的稳定性,不同职能之间的协作效率的提升,如何优化开发/测试流程等等。比如,前文提到的Genos
系列开发调试工具的设计,不仅方便了我们开发人员定位问题,同时极大地方便了测试人员的测试工作。再比如,需求评审会和设计评审会的功能是否有大量重复的部分,是否可以合二为一,减少重复无意义的会议,给前端/客户端开发人员留更多的时间进行开发?还有,应当自发整理项目工程文档,为工作内容留下记录,哪怕有任何紧急情况不在岗位上,也可以很好的做好交接。
这些都是完成日常工作之余,值得思考的问题。提升主人翁意识,提高工作的效率,创造舒适的工作体验。
前文挑了几个值得展开讨论的要点进行了分析,除此之外2019年我还做了些什么事呢?
这一年疏于Github的经营, commit记录也是零散惨淡。惭愧惭愧。
我在2018年搭建的新城中学OJ系统现已更名为聚慧编程。目前已有369名用户在此学习和训练编程语言。不过都是俊杰在打理,我就出个服务器的钱。
个人域名的博客实在没啥人气。反倒是我的CSDN博客稍稍有些气色。访问量破20w了,积分榜总排名26648。留个记录吧。交给2020年的我来赶超吧。
小婷婷提醒我说:”你去年就18w访问量了, 今年才20w。你这访问量明明就是靠前几年的文章涨的,你就是吃老本。” 对对对,我是吃老本,罪过罪过。明年加油!
换次工作搬次家,相当折腾。这次兜兜转转搬到了我实习之初住的地方的——隔壁小区。因为我2年多前租的房子拆迁了(目瞪狗呆),小道消息据说每户平均获赔千万级别(再次目瞪狗呆)。
这次虽然搬的还是单间,而且没有独卫,还是一楼,但好在房间大啊,除了房间原来就有的2.2m的大床和又大又长的靠墙衣柜(目测0.8m*2.2m*2.5m)外,还可以放下两张书桌, 一张化妆台, 一个约门高的小书架, 还有个小阳台晾衣服,剩下的面积还可以打个地铺。(咦?我为什么要打地铺?) 虽然是一楼其实并不潮湿, 因为它朝南呀。血——赚——(模仿敬汉卿说话)
房东叔叔和房东阿姨人很好,还经常做菜给我们吃, 还教我们做菜。
小区里有个室外游泳池,仅在暑假开放两个月。¥20/人不计时。虽然池子小,但强在离家近。
把轮滑鞋带来了, 不过好像没玩几次, 就在钱塘江边滑了几次。后来买了肥宅快乐环, 也就不再出去滑了。
原来在大搜车的不少老朋友们都四散到了各地,阿呆薪资double, 还在北京; 家🍐涨薪60%, 突破20k大关; 耗子也double了,去了蚂蚁金服。大家都有美好的前程。
我也结识了一群新朋友。经常和小剑剑一起搓炉石,周五还搭他的车回家,是个聊天爱发黄图的帅小伙; 和沉默的阿岩一起来到这个新环境,临走时还没好好打招呼; 看着洋洋身上的拼劲,像看到当年的自己,小小的眼睛装着大大的疑惑。哈哈哈哈。
单独抽一节说说这个牙的事。你看这个牙,它又丑又歪skr
牙疼不是病, 疼起来真要命。这四颗牙,没一颗好牙。尤其下面那两颗,害的我下排牙都对不齐上排的牙了。每颗牙的拔牙费用2134依次递增, 越歪的越贵,掏空了我医保的历年余额。总共大概花了¥4000+把。血——亏——。拔完牙的一周里, 只能喝粥度日, 了无生趣。以后有了孩子一定好好带他/她保护牙齿,定期检查牙齿,免得受这份罪。
2019新型冠状病毒,即“2019-nCoV”, 因2019年武汉病毒性肺炎病例而被发现,2020年1月12日被世界卫生组织命名。冠状病毒是一个大型病毒家族,已知可引起感冒以及中东呼吸综合征(MERS)和严重急性呼吸综合征(SARS)等较严重疾病。新型冠状病毒是以前从未在人体中发现的冠状病毒新毒株。
出门要戴口罩了, 符合标准的口罩还买不到。回武汉的小伙伴很慌, 回家路过武汉的小伙伴也慌。听说武汉的小伙伴出门是这样的:
在景1的鼓动下, 开始从第一季看《JOJO》。差点被画风劝退, 而后一发不可收拾。就连小婷婷看了之后也开始“木大木大”“平角裤平角裤”了。一口气看完了五季。坐等2020年的第六季——《石之海》。
要是通行百万当主角就好了,他那么努力还是拼不过主角光环的绿谷。
听网友说,春节估计看不到路飞打凯多, 估计要到清明节。顺便一提《狂热行动》还不错。
一口气看完了6集爽漫画早就完结了居然还能出动漫!听说是网飞给的钱太多了…资本的力量真牛逼…
Nintendo Switch
我曾拥有你,想到就心酸。
上图大部分游戏都卖了回血了。只剩下《塞尔达传说·旷野之息》和《超级马里奥·奥德赛》。
回血的钱又入手了《健身环大冒险》。
稍微评价一下2019年玩的几个游戏:
虽然是2018年年初就买了的游戏, 但我一直没玩。很奇怪,大家越是吹的厉害,我越是不想玩。后来慢慢玩下去,沉浸在海拉鲁大陆里之后,我只想说:”真香!”。旷野之息里有很多细节真的做的很棒,不注意的话不会发现。比如,快要下雨了蜻蜓会飞得很低。
前期一直是那种见了怪就跑, 不敢干架,到处乱跑,错过很多好装备。后来入了DLC,开始肝剑之试炼。对于我这种手残党来说,剑之试炼真是太难了,被关卡虐得自闭。好几次去网上找盾跳穿墙的教程。练了好几次,明明储存了扭曲就是穿不了墙,我也是服了。最后老老实实地干架。肝了几周总算成功开光三次。剑之试炼出来之后,干架都有了自信。直接去了岛之试炼,那叫一个爽。
再后来神兽任务都做完了,就差救公主了。但我还是喜欢在这片美丽的大陆上旅旅游,采采蘑菇,放放火,炸炸鱼。
在2019年6月11日的任天堂E3展上, 放出重磅消息——《塞尔达传说·旷野之息》续作正在开发中。后来又有知名游戏主播称2020年就能玩上(不知道是不是有内幕)。这个要放入2020年的规划里(嗯, 确信,没错。认真脸。)。
说实话, 织梦岛这游戏真的配不上它的售价。剧情主线太短了,而且总是在固定的路线上走来走去,没多久就没新鲜感了(可能我被旷野之息的开放世界惯坏了吧)。通关后我就卖给Mawh了,听说后来他也没怎么玩就又卖了。哈哈哈哈。果然大家对烂作的认知都是统一的。
买的电子版, 去年跟景1玩过一点点。今年跟小剑剑、景1又分别玩了一次,打到第二章了。说实话这游戏太考验配合了,一不小心就做错了,真是游戏如其名分手厨房。
不是喜欢的类型,已出。
本来以为只有跟同样买了《人类一败涂地》的Switch玩家才能一起联机玩。后来才发现可以本机两个手柄就可以双人进行游戏了。跟景1玩了一晚上,相当欢乐。
其实是去年(2018)年底买的,感觉一般。只能抓野怪,都不能与野怪对战,没有灵魂。打完四大天王,还没抓超梦就卖了。还在超梦的洞窟里捡了好几个大师球。
比《出发吧!皮卡丘》好多了。极巨化还是挺有意思的。近期抓了只百变怪,沉迷配种中。期待春节回家和小伙伴交换宝可梦。
《健身环大冒险》是任天堂自主研发的,不是外面的野鸡游戏工作室做的小垃圾可以比的。网上又不少专业的健身教练评测,都是正面的评价,也就是说健身环大冒险真的可以健身。而且效果还不错,还会为你纠正姿势。
炉石传说
什么炉石传说?是那个酒馆战旗的启动器吗?
酒馆战旗的火爆程度都快盖过巨龙降临新版本了。反正我是记不得巨龙降临有哪些强势构筑了。每次上线,好友们都在下棋。下棋最大的乐趣就是双排。互通情报,互相py。比如,第一回合遇到小伙伴就商量好都不下怪;中后期遇到小伙伴了,开黑双方有一边鱼人快成形了但血量堪忧了,就让小伙伴站位py一下, 放点水,被小入一点点。一个吃鸡另一人还能恰烂分, 岂不美哉。但也有错误估计小伙伴的实力,饶的太多,导致提前速8的情况…这就是另一个故事了。
战旗初始积分4000分。我目前战旗天梯分数6494,留个记录。来年再战。
嫌之前的Cherry
入门款键盘太占桌面面积了, 就入了一块Magic Keyboard。
本来想给小婷婷当做生日礼物的,可惜她的手机版本太低,连接不了Apple Watch S5。只好去老赵那淘了一台iPhone 5SE(这个就不单独记录了), 手表就给我自己用了。后来去Apple 西湖修电脑屏幕(有点花屏)又顺便买了一块蓝色布表带。说实话还是硅胶舒服, 而且耐脏; 布表带比较轻,但是待在手腕上不安分,滑来滑去的,系的紧了又勒手。没几天又换回硅胶表带了。
Bye~
制定几个目标吧。目标不比总结,有明确方向和内容就行,不需要长篇大论。
计划为github上的kotlin
项目提交代码。
今年的支付宝年账单有够扯的,我花的居然比我挣得还多。还好我记了账不然就被它骗了。2020年计划继续保持今年的开支水平吧。毕竟2020年有更多需要花钱的地方。加油吧!
少买点游戏吧,要买也买实体版,回头转卖还能回血。之前一时脑热买了好几个电子版游戏,结果不是自己喜欢的类型…目标明确一下,除了《塞尔达传说·旷野之息》续作,其他游戏都不买了。要是续作跳票了,那也只能买一部游戏。
这次话太多了, 不符合我的风格,我还是喜欢张牧之的发言:
“咳咳, 出发!”
根据《Sonatype Nexus在Maven Central Repository开户实践笔记》所述步骤,会走到”验证域名归属”这一步。最快的方式就是添加为自己的域名添加TXT记录。
以阿里云(万网)为例(relish.wang是笔者的域名)
1 打开阿里云控制台首页https://homenew.console.aliyun.com/
2 选择域名
3 选择对应域名点击【解析】
4 添加TXT记录
5 验证TXT记录
1 | nslookup -q=TXT yourdomain.com |
6 验证成功后在工单上回复管理员(在管理员确认过了之后,你也可以删除那条TXT记录)
7 这时你就可以上传你自己的Android构建包了。第一次上传成功后,需再次在原来的工单上回复管理员,告知他你已经上传成功了。
##
hkp://pool.sks-keyservers.net
改为
hkp://ipv4.pool.sks-keyservers.net
1 | gpg --keyserver hkp://ipv4.pool.sks-keyservers.net --send-keys XXXXXXXX |
自从博客搭建(2017.8)至今,一直用着hexo的next主题,也从未想过做任何美化的工作。某次在搜索资料时,看到某位网友的个人博客,一时间惊为天人。卧槽,这也太好看了吧!我的博客简直就是个草窝啊我去。随后我就开始了我的博客美化工作。
国际惯例先上图:
*博客美化前: *查看动图
*博客美化后: *查看动图
修改文件位置:/themes/next/layout/_layout.swig
在<div class="headband"></div>
下方添加如下代码:
1 | <!-- 记得改成你的github地址 --> |
修改主题色需要修改有多处地方
1 github徽标颜色,见前文
2 修改网站头部颜色
在文件themes/next/source/css/_custom/custom.styl
中添加:
1 | // 修改网站头部颜色 |
3 如果你还想把按钮的hover颜色的也改成主题色的话:
在/themes/next/source/css/_variables/custom.styl
添加:
1 | $btn-default-hover-bg = #49b1f5 // 主题色 |
1 设置背景图片
在/themes/next/source/css/_custom/custom.styl
中添加:
1 | body { |
2 设置透明度
2.1 修改主页文章列表的背景色
在themes/next/source/css/_custom/custom.styl
中添加:
1 | .post { |
2.2 修改侧边栏透明度
themes/next/source/css/_schemes/Pisces/_layout.styl
的以下节点下的background
属性.header-inner
节点: 由white
改为rgba(255,255,255,0.75);
(0.75表示透明度。0-全透明;1-不透明).content-wrap
节点:由white
改为rgba(255,255,255,0);
.sidebar
节点: 由white
改为rgba(255,255,255,0);
_sidebar.styl
的..sidebar-inner
节点下的background
属性由white;
改成rgba(255,255,255,0.75);
2.3 修改页面底部页数布局透明度
在/themes/next/source/css/_common/components/pagination.styl
中修改:
1 | .pagination { |
2.4 修改按钮的透明度
在themes/next/source/css/_common/components/post/post-button.styl
中的.post
节点的.btn
的background
属性修改为transparent;
(当然如果你觉得白色按钮也挺美观的,也可以不改。)
请参看开源项目hexo-helper-live2d
4.1 在项目根目录下运行
1 | npm install --save hexo-helper-live2d |
4.2 在根目录的_config.yml
中添加以下配置
1 | live2d: |
4.3 安装你选择的模型的package
1 | npm install live2d-widget-model-shizuku |
4.4 跑起来你就能看到了
1 | hexo clean && hexo g && hexo s |
5.1 保存love.js
文件
将love.js文件保存到/themes/next/source/js/src/
目录下, 文件名就取为love.js
5.2 引用love.js
文件
在/themes/next/layout\_layout.swig
文件末尾添加js文件引用:
1 | <!-- 页面点击小红心 --> |
在themes/next/source/css/_custom/custom.styl
中添加以下代码:
1 | // 主页文章添加阴影效果 |
在themes/next/source/css/_common/components/sidebar/sidebar-author.styl
末尾添加:
1 | .site-author-image { |
要不是这次想着要给博客整容,我甚至都不知道我早年使用的不蒜子早就换了域名了。之前就已经添加了不蒜子计数,我这次只能算修复, 就不赘述添加过程了。
如何修复:
将/theme/next/layout/_third-party/analytics/busuanzi-counter.swig
中不蒜子的域名dn-lbstatics.qbox.me
修改为busuanzi.ibruce.info
;
1 添加功能
在themes/next/layout/_layout.swig
中的</body>
标签前添加以下代码:
1 | {% if theme.canvas_nest %} |
2 启用功能
将/themes/next/_config.yml
中的canvas_nest
属性改为true
(如果之后不想用改成false
就行了):
3 效果图
俄罗斯方块Java版本(Cover: 小翼)
仓库地址: https://github.com/relish-wang/Tetris
关于笔者
景三,程序员,主要从事Android平台基础架构方面的工作,欢迎交流技术方面的问题,可以去我的Github提issue或者发邮件至relish.wang@gmail.com与我交流。
@[toc]
国际惯例先上图。
先确保你电脑上装了git和java, 然后随便找个合适的文件下运行下面的代码:
1 | git clone https://github.com/relish-wang/Tetris.git && cd Tetris && java -jar Tetris.jar |
下面方法选其一即可
1 在项目根目录下打开终端执行
1 | sh start.sh |
2 在项目根目录下打开终端执行
1 | ./start.sh # 若遇到": permission denied: " 就先执行`chmod +x ./start.sh` |
下面方法选其一即可
start.bat
文件1 | start start.bat |
《俄罗斯方块》视频教程下载地址(包含图片素材和excel讲解图)
链接: https://pan.baidu.com/s/1JOCfsOgEcwq0qvMfK8cqDQ
提取码: 3u6x
此版本的《俄罗斯方块》的原作者小翼于2012年12月12日在java吧发布此教程。由于教程在当时质量极高,颇受吧友欢迎。但时至今日, 此教程已不再适合作为新手的练手项目。其本人也曾发帖吐槽,不要再学习此教程,也不要因此事过多打扰他。
Ps:
小翼吐槽贴: 《【吐槽】不要迷恋哥,J8的破事在J8解决嘛》
]]>关于作者
景三,程序员,主要从事Android平台基础架构方面的工作,欢迎交流技术方面的问题,可以去我的Github提issue或者发邮件至relish.wang@gmail.com与我交流。
@[toc]
KeyboardListener是从facebook/react-native源码中抽离出来的用于监听Android软键盘弹出与收回事件的工具。
扫描二维码下载Demo
可以直接把DisplayMetricsHolder、GlobalLayoutListener、OnKeyboardChangedListener三个文件直接拷贝到你的工程里使用。
1 | rootView.getViewTreeObserver().addOnGlobalLayoutListener( |
感谢facebook/react-native提供代码解决方案。
本仓库使用的react-native相关代码:
ReactRootView.java里的内部类CustomGlobalLayoutListener
监听键盘事件的监听器。
通过反射获取相关尺寸数值。
PixelUtil.java(非必要)
度量单位转换工具。px<->sp/dp之间的转化。
1 搭建好了新城中学OJ系统(http://oj.relish.wang/), 还用上了二级域名,注册人数300+。不仅有学生参与其中,还吸引了当地其他学校的老师前来学习,给俊杰赚足了面子。在此也要感谢zhblue的开源项目hustoj(https://github.com/zhblue/hustoj)。
2 发表了一篇博客投稿了郭霖的微信公众号——Android Studio项目模板全面解析。
3 国庆期间和女票去南京好好游玩了一通。拍了很多照片,看了很多地方,吃了很多好吃的。
4 进行了一次义无反顾的杭州-湖州骑行之旅。骑了6个多小时,可惜最后爆胎了。
5 gitlab上拥有比去年更多的commit。虽说commit既不能代表代码量也不代表代码质量,但能说明比去年忙。2018年一度加班到身体有些吃不住。
还有一些烦心事,一一叙述的话就显得杂乱了。搬了一次家,也搬了一次办公地点;购置了Switch,也买了不少游戏,只有奥德赛深得我心,全通关+全收集;收入勉强达到毕业时的期望吧,绝不是高看自己;年终抽到一台天猫精灵,父母甚是喜爱;…
2018就这么过去了,不能总纠结于过去的烦心事,应当展望未来。我给2019定了8个字的标语,用来警醒自己:
考虑周全。万事一定要考虑周全,不能想着之后再补。要知道补过的东西,就不再完美了。不是任何尝试都有机会体验第二次的。当你只有一次机会的时候,应当考虑周全,如何把事情做得漂亮,才不至于错过这次机会。也不要在错过之后再抱怨”如果再给我一次机会”。
拒绝拖延。2018年我曾给自己定了一套时间表,但最终没能按照计划执行。作息十分不规律。曾经我一度以为千万别把一切都规划了,那将失去无数的可能性。要是所有的事都规划好了,那人生还有什么乐趣。后来我发现我错了,没有规划的人生,只会跟咸鱼一样,毫无方向感,蹦跶了几下也就翻了个身,要知道咸鱼翻身还是咸鱼。所以2019我将制定更多更细致的规划,并克服惰性,将它们执行下去。现在每每想到接下来要做的事情,都充满了期待,瞬间充满了动力。
2019年还有很多值得期待的事。背起行囊,阔步向前。
]]>[TOC]
在使用MacOS中的遇到的一些常见问题。记录下亲测有效的解决方案, 以备下次遇到相同的问题。
brew --repo
命令是打印HomeBrew的git地址。
第一步:修改homebrew及homebrew-core的远程仓库地址
1 | # homebrew |
第二步:执行brew update
1 系统偏好设置 -> 键盘 -> 快捷键 -> 服务,勾选「新建位于文件夹位置的终端窗口」(后面的键盘快捷键可以不选)
2 在 Finder 里面选中文件夹右键菜单的「服务」下面就会有「新建位于文件夹位置的终端窗口」这一子菜单了。
参考资料: 《MAC OSX右键菜单能否添加类似于“在终端中打开当前目录”的快捷方式》
修改系统配置:系统偏好设置 -> 安全性与隐私 -> 认证 -> 修改为任何来源
如果没有此选项的,运行以下命令:
1 | sudo spctl --master-disable |
注:进行以下步骤前请先备份U盘内重要文件。
第一步**:在MacOS上打开磁盘工具(使用Sportlight 搜索
)
找不到Sportlight 搜索
的读者请点击苹果电脑屏幕右上角的放大镜按钮
第二步:抹掉U盘并选择exFAT
格式的U盘
完成!现在你的U盘在Windows和Mac都支持读写了!
]]>此系列文章记录了一次使用AutoCompleteTextView(以下简称ACTV)的踩坑过程,并复盘整个的解决流程。
以下是此系列所有文章
关于作者
]]>景三,程序员,主要从事Android平台基础架构方面的工作,欢迎交流技术方面的问题,可以去我的Github提issue或者发邮件至relish.wang@gmail.com与我交流。
文章地址:https://mp.weixin.qq.com/s/h_jlf9EAbE9dg7WJdiHKbg
大家一定经历过等电梯的烦恼,尤其是在赶时间的时候,则更为头疼。如果你是一名程序员的话, 你一定有在等电梯时盘算过如何对电梯调度进行优化。
文章对各种算法的介绍还算细致, 具体介绍了如何针对不同的情况对电梯的调度算法进行优化,使其能更高效地运作,但并未给出相应的代码。笔者不才,愿对其介绍的几种较易理解的非实时调度算法进行Java代码实现,权当抛砖引玉之效。献丑献丑。
根据乘客请求乘坐电梯的先后次序进行调度。
优点:
公平、简单,且每个乘客的请求都能依次地得到处理,不会出现某一乘客的请求长期得不到满足的情况。
缺点:
在载荷较大的情况下,这种算法的性能就会严重下降,甚至恶化。
(缺点)举个例子: 电梯处于1楼, A乘客请求从10楼到1楼,B乘客请求从1楼到10楼, C乘客请求从9楼到1楼。根据FCFS算法,电梯会先满足A乘客的请求,再满足B乘客的请求,最后满足C乘客的请求。
代码:
1 | /** |
1 | /** |
1 | public class FCFS { |
点评:代码简单。任务执行只需按请求队列的顺序执行即可。
此算法选择下一个服务对象的原则是最短寻找楼层的时间。
优点:
注重电梯寻找楼层的优化。请求队列中距当前能够最先到达的楼层的请求信号就是下一个服务对象。
缺点:
队列中的某些请求可能长时间得不到响应,出现所谓的“饿死”现象。
(缺点)补充说明: 处于中间楼层的请求会被快速响应,但处于底层和上层的请求,会被长时间搁置。
代码:
1 | public class SSTF { |
点评: 与FCFS算法相比,SSTF算法会先对请求队列进行排序,每次都会优先处理距离最近的请求。
扫描算法(SCAN), 也被称为电梯调度算法,是一种按照楼层顺序依次服务请求,它让电梯在最底层和最顶层之间连续往返运行,在运行过程中响应处在于电梯运行方向相同的各楼层上的请求。
优点:
进行寻找楼层的优化,效率比较高。较好地解决了电梯移动的问题,在这个算法中,每个电梯响应乘客请求使乘客获得服务的次序是由其发出请求的乘客的位置与当前电梯位置之间的距离来决定的。
缺点:
扫描算法的平均响应时间比最短寻找楼层时间优先算法长。
扫描算法的响应时间方差比最短寻找楼层时间优先算法小,从统计学角度来讲,扫描算法要比最短寻找楼层时间优先算法稳定。
代码:
1 | public class SCAN { |
1 | /** |
文章还介绍了其他两种非实时算法——LOOK算法和SATF算法。LOOK算法是对SCAN算法的改进,当判定继续往上/下走已经没有请求的楼层,便不会继续前行而是转向,而SCAN算法会继续运行到顶/底层再转向;SATF(Shortest Access Time First)算法与 SSTF 算法的思想类似,唯一的区别就是 SATF 算法将 SSTF 算法中的寻找楼层时间改成了访问时间。
文章还介绍了几种实时电梯调度算法。基本上是基于前面介绍的几种非实时调度算法的优化和改进。读者如有兴趣,可以对其进行深入研究,细细思考算法的精妙,还颇有趣味。
最后借用精读文章内的一句话来结束此文:
]]>哪个算法都不是一个最佳方案,只是它确实解决了一定情况的问题。但是对一个优秀的程序员而言,研究各种算法是无比快乐的。
本篇文章已授权微信公众号 guolin_blog (郭霖)独家发布
先上对比图(左侧是我们手动构建的最简单的Android项目;右侧是Android Studio默认创建的项目模板工程):
进入正题前,先说个与手写程序(不借助IDE)类似的场景。我回想起以前初学Java时的场景—— 如何用记事本写一个HelloWorld。
1 先写个最简单的Java程序:
1 | public class A{ |
2 在当前目录下运行javac A.java
(编译A.java文件,生成A.class文件)
3 在当前目录下运行javac A
(执行class文件)
4 就会看到Hello Java!
被打印在控制台(终端)上
用记事本开发Java程序确实简单,三两句话就讲完了。Android程序有那么多文件(AndroidManifest.xml
、启动的Activity
、layout文件
、icon图标
、strings.xml
、styles.xml文件
),这可咋整啊?
要是你认同这句话, 说明你早就已经习惯于Android Studio为你生成的项目模板(以下简称”AS项目模板”), 而失去了作为一位Android开发者自我的判断——一个最简单的Android程序到底需要哪些文件。
AS项目模板(未在括号里标注的文件/文件夹都是gradle相关的, 下文会详细讲解):
1 | ├── app(主工程) |
我可以告诉你, 上面所说的文件里,其实只有AndroidManifest.xml是必须的(图形页面对于Android程序来说并不是必须的, 比如只运行在后台的Service)。但是为了本文章的展示效果, 还是保留Activity。(有Activity也并不意味着一定需要layout.xml)
前面丢了些悬念, 但我们还不能进入正题。先做或确保一些准备工作。
我们需要准备三样东西: Java SDK
、Android SDK
、Gradle SDK
。
前两样是Android开发必备的环境, 作为Android开发者自然不用多说。(请确保配置了ANDROID_HOME)
第三样是项目自动化构建工具(gradle), 可以用于构建Android项目。
如果你是macOS用户, 只需执行一条命令即可配置好最新版本的gradle环境: brew install gradle
; 如果你使用的是其他操作系统, 可以参看: https://gradle.org/install/
(确保配置了GRADLE_HOME)
配置完毕后运行gradle -v
, 检查是否配置完成。
开门见山, 先放目录结构。需要手动编写的就三个文件MainActivity.java、AndroidManifest.xml、build.gradle。
1 | demo |
MainActivity.java
内容如下:
1 | package wang.relish.demo; |
AndroidManifest.xml
内容如下:
1 | <?xml version="1.0" encoding="utf-8" ?> |
build.gradle
内容如下:
1 | buildscript { |
接下来才是真正见证奇迹的时刻。
在demo
(项目根目录)下执行gradle assemble
, 如果你看到下图的执行结果说明运行成功了, 可以略过下文的错误说明, 请看执行完成。
如果你遇到如下错误, 则说明你未配置ANDROID_HOME
环境变量。
解决方案有二:
local.properties
文件, 输入AndroidSDK所在路径:1 | sdk.dir=/Users/relish/Library/Android/sdk |
下图是笔者电脑上的配置:
执行完成后目录结构如下:
1 | ├── .gradle(gradle任务相关缓存) |
在/build/outputs/apk
的debug
和release
文件下就可以看到生成的apk文件了。
安装apk文件后, 桌面图标如下(不同Android版本上显示的默认logo不同):
运行可以看到如下画面:
也许看到这里的读者开始有些疑问了。为什么只要这么三个文件就够了?哪怕构建后也只多了两个文件夹?AndroidStudio生成的空项目里还有setting.gradle
、.idea
文件夹、gradle
文件夹、*.iml
文件、gradlew
、gradlew.bat
等, 这么多文件呢!
如果你觉得这篇文章就这么结束了, 那你就想得太简单了。
莫慌, 正片开始! 接下来我要讲的是, 这个项目如何一步一步转化为我们熟悉的AndroidStudio的默认创建项目。
Gradle Wrapper是对Gradle的一层包装, 便于团队开发过程中统一Gradle构建的版本。 我们在项目开发过程中, 用的都是Wrapper这种方式。所以前面用的gradle相关命令建议都改为gradlew的命令(如: gradle assemble
改为./gradlew assemble
)。
在demo
(项目根目录)下执行gradle wrapper
, 执行完成后目录结构下新增的文件/文件夹如下:
1 | ├─ gradle |
gradlew
和gradlew.bat
分别是Linux和Windows下的可执行脚本。gradle-wrapper.jar
是具体业务逻辑实现的jar包。gradlew最终是使用这个jar包来执行相关的Gradle操作。gradle-wrapper.properties
是配置文件, 用于配置使用哪个版本的Gradle等。
打开gradle-wrapper.properties
文件, 可以看到以下内容:
1 | distributionBase=GRADLE_USER_HOME |
字段名 | 说明 |
---|---|
distributionBase | 下载的Gradle压缩包解压后储存的主目录 |
distributionPath | 相对于distributionBase的解压后的Gradle压缩包的路径 |
zipStoreBase | 同distributionBase, 只不过是存放zip压缩包的 |
zipStorePath | 同distributionPath, 只不过是存放zip压缩包的 |
distributionUrl | Gradle发型版压缩包的下载地址 |
这里我们基本只需关注distributionUrl 即可, 这个字段决定了你的gradle wrapper依赖哪个gradle版本。 |
因为使用gradle wrapper的方式不需要提前将gradle下载好,而是会自动根据本地的缓存情况决定是否需要联网下载gradle。而且假设每个开发者电脑上未安装/配置gradle环境, 那么他/她仍能通过执行gradlew命令执行gradle相关任务。而且wrapper规定了使用的gradle版本, 在团队开发中, 执行gradlew命令运行的都是同一个版本的gradle。避免了每个团队成员电脑上配置的gradle版本不同而带来的执行结果的差异。
到这里为止, 先来看一下我们项目的目录结构:
1 | ├─ .gradle(文件夹) |
仔细观察发现, 少了settings.gradle
文件, 而且这个src
目录外面应该再包一层app
目录…即便这样app
里也少了一些其他的文件…莫慌, 我们一点点来分析。
settings.gradle
文件大多数的作用是为了配置子工程(moudle)。但由于我们这个”最简单的Android程序”是一个单工程的项目, 所以settings.gradle
并不是必须的。但是, 在实际开发过程中, 项目(Project)大多为多工程(module), 因此AS项目模板是会默认配置好settings.gradle
, 并且主工程也被包装成一个名为app
的子工程(moudle)。
settings.gradle
的内容也很简单:
1 | include ':app' |
如果有多个module则用逗号隔开(不过我们这次不需要):
1 | include ':app', ':module1', ':module2' |
在demo
(项目根目录)下, 新建app
文件夹,将demo
(项目根目录)下的src文件夹和build.gradle
移动/剪切到新建的app
目录下。
这时再次执行./gradlew assemble
, 已经可以生成apk文件了。但总觉得还是跟AS项目模板有些不同——根目录少了一个build.gradle文件。此时的工程目录:
1 | ├─ .gradle(文件夹) |
既然是多工程配置, 那么每个子工程(module)都需要的配置就可以做成项目(project)配置, 减少重复代码。
因此我们把./app/build.gradle
内的部分内容移动到根目录下的build.gradle
中。
./build.gradle内容:
1 | buildscript { // 一个在项目构建之前, 为项目进行前期准备和初始化相关配置依赖的地方 |
./app/build.gradle内容:
1 | apply plugin: 'com.android.application' |
可以看到根目录下的build.gradle已经和AS项目模板长得很像了, 区别在与下面这段代码:
1 | task clean(type: Delete) { |
这里定义了一个名为clean的gradle任务(task),我们可以通过运行./gradlew clean
执行这个任务。它的执行结果就是删除主项目的build文件夹。
忘了说, 现在再执行./gradlew assemble
, build
文件夹会生成在app
目录下。再执行./gradlew clean
就会删除app
目录下的build
文件夹。
其实文章讲解到这里, 我们手动修改的项目已经和AS项目模板大致相同了。因为此文介绍的重点其实是gradle构建工具和一个最简单的Android项目需要那些文件。但鉴于这里还有一些文件未提及,笔者决定简单介绍一下它们。
文件 | 说明 |
---|---|
*.iml、.idea文件夹 | JetBrain家的IDEA系列IDE的项目配置文件(Android Studio是基于IntelliJ IDEA的), 可以删除,下次用Android Studio打开时会自动生成 |
gradle.properties | 可以放置gradle相关的全局常量声明和项目运行内存设置等 |
local.properties | 声明AndroidSDK和NDK所在路径 |
app/proguard-rules.pro | 代码混淆配置 |
app/libs | 存放jar/aar包 |
.gitignore、app/.gitignore | 记录需要被git忽略的文件/文件夹 |
.gitignore
文件通常需要填写的内容:
1 | .gradle/ |
app/.gitignore文件通常需要填写的内容:
1 | build/ |
文章讲解到这里, 我们手动修改的项目和AS项目模板的区别剩下app(主工程目录)目录下的文件内容、目录结构不同了。
二者不同的地方都已经用黄框和蓝框标出来了。其中黄框标注的文件已经在前文介绍过它们的作用了。下面介绍一下蓝框里的文件/文件夹。
androidTest
文件夹和test
文件夹分别是Android单元测试和Java单元测试相关的目录。AS模板项目所用的单元测试框架是Java单元测试框架junit、AndroidJUnit、Android UI自动化测试框架espresso。
由于篇幅原因,关于单元测试的用法就不在此文中详细描述,感兴趣的读者可以查阅文末的引用资料或自行搜索相关资料进行学习。
所有的资源文件夹都有-v[api-level]
的形式、-[各国语言缩写]
国际化资源的形式。如:drawable-zh-ldpi
、values-en
、drawable-v21
。
存放图片/图标文件以及样式相关的文件
文件夹 | 说明 |
---|---|
drawable | 放置selector、shape、vector文件 |
drawable-mdpi | 中分辨率图标(72*72, 320*480) |
drawable-hdpi | 高分辨率图标(尺寸标准:48*48, 对应手机分辨率:480*800) |
drawable-xhdpi | 超高分辨率图标(96*96, 720*1280) |
drawable-xxhdpi | 超超高分辨率图标(144*144, 1080*1920), 主流分辨率 |
drawable-xxxhdpi | 超超超高分辨率图标(192*192, 3840*2160) |
drawable-v19 | Android4.4(API19)及以上特别设置的样式/图标 |
drawable-v21 | Android5.0(API21)及以上特别设置的样式/图标 |
drawable-v24 | Android7.0(API24)及以上特别设置的样式/图标 |
drawable-v[API-version]: 此类文件夹下的同名图标会默认使用最高版本的。如: ic_avatar.png
分别在drawable-v19
和drawable-v21
分别有两个长得不同的图标文件, 如果运行的手机是Android5.1(API22)的, 那么它会加载drawable-v21
下的ic_avatar.png
;但如果仅在drawable-v19
下放置了ic_avatar.png
,drawable-v21
没有的话, 在这台手机上运行时就会加载drawable-v19
下的ic_avatar.png
;如果drawable-v[API-version]此类文件夹未放置ic_avatar.png
图标, 则会加载对应分辨率文件夹下的ic_avatar.png
; 要是各分辨率的文件夹里也没有ic_avatar.png
的话,就会加载drawable
文件夹下的ic_avatar.png
。
图标加载顺序:
drawable-v[对应的高API]
->drawble-v[低API]
->drawable-[对应高分辨率]dpi
->drawable-[低分辨率]dpi
->drawable
文件夹 | 说明 |
---|---|
layout | 默认加载的布局文件 |
layout-land | 横屏时加载的布局文件 |
layout-port | 竖屏时加载的布局文件 |
布局文件加载顺序: | |
(根据屏幕状态而定)layout-land 或layout-port ->layout |
用法与drawable一致。区别在于: mipmap文件夹仅仅用于放置app的logo图标。
AS项目模板:
文件 | 说明 |
---|---|
colors.xml | 颜色值常量 |
strings.xml | 字符串常量 |
styles.xml | 样式常量 |
除此之外还可以有以下文件:
文件 | 说明 |
---|---|
ids.xml | id常量(int类型) |
arrays.xml | 数组常量 |
attrs.xml | 属性声明, 用于自定义View |
dimens.xml | 尺寸常量。长度(dp)、字体大小(sp)、像素值(px)等 |
… | … |
其实并不需要拘泥于这些文件名, 想取啥文件名都行, 甚至这些文件里的内容也可以全写在一个文件里(但推荐写法还是分开写, 各司其职):
1 | <resources> |
这里还有一些常见资源文件/文件夹未提及, 将在下方表格中列出:
文件夹 | 说明 |
---|---|
anim | 动画文件 |
raw | 媒体文件(音频、视频文件) |
xml | 其他的xml类型文件 |
menu | 菜单文件 |
AS项目模板的app/build.gradle
文件内容:
1 | apply plugin: 'com.android.application' // 应用Android Gradle插件. (有些网友在介绍gradle的文章中把它描述为"声明是Android程序",纯属扯淡) |
从Android Gradle plugin 3.0
开始推荐使用implementation
和api
来替换原先的compile
。理论上你可以把所有的compile
替换成api
。下面说说它们的区别:
命令 | 说明 |
---|---|
compile | 本module将会泄露其依赖的module的内容 |
api | 同compile |
implementation | 本module不会通过自身的接口向外部暴露其依赖module的内容。推荐使用implementation来进行依赖(而不是api或compile),这会大大改善工程的构建时间 |
其他改动:
AS项目模板的AndroidManifest.xml
文件内容:
1 | <?xml version="1.0" encoding="utf-8"?> |
本来只是想介绍一个不借助IDE可以手写出来的最简单的Android程序的教程。讲着讲着就想把Android Studio的模板工程目录结构、文件作用都说了一遍, 不知不觉写了这么多。也把我们这个最简单的Android程序一步步拓展成了Android Studio的模板工程。希望各位读者在体会到Android Studio为我们广大Android开发者的开发工作带来莫大的帮助的同时, 明白Android Studio为我们都做了哪些事。
Wrapper (gradlew):
Gradle官方文档:
Android单元测试-如何开始?
自适应图标(Adaptive icons):
https://developer.android.com/guide/practices/ui_guidelines/icon_design_adaptive
迁移到插件3.0.0:
]]>https://developer.android.com/studio/build/gradle-plugin-3-0-0-migration#new_configurations
关键命令
1 | git pull origin master --allow-unrelated-histories |
或
1 | git merge [branch] --allow-unrelated-histories |
背景: 我和好友分别维护着一份PAT题解相关的repository, 区别是她使用的是JS代码,我使用的是Java(偶尔会用C/C++、python等)(PS:欢迎使用各种语言的各路大佬加入我们的repo)。后来我们一拍即合,决定合成一个repository。
想法是这样的,我fork她的repository,commit & push我的代码后,再发起pull request交由她处理,并合入她的repository的主分支。那么问题来了,我确实可以复制粘贴我的代码,然后提交后发起pull request。但是这样做的话,我之前的commit记录都会消失。我更希望能够保留我的commit记录。
一般情况下只需要像上图描述的步骤进行就行了。如果要保留旧仓库的commit记录,那就需要在2、3两步之间多加一些步骤。
git remote set-url origin https://github.com/relish-wang/PAT.git
直接git pull
的话,会出现这样的错误:拒绝合并无关的历史记录。这是为什么呢?因为一般来说一个git仓库的起源肯定是一个点。而这次合并会产生两个起源点(一个是我的旧仓库的master的起源点,另一个是她的旧仓库的master分支的起源点)。
这就用到了下面这个命令: 允许合并无关的历史记录
git pull origin master –allow-unrelated-histories
合并后的git记录树:
然后就可以发起pull request了。
上述步骤完成后,意外地发现IDEA无法识别这个项目里的java文件了。
而能运行的java文件长这样:
使用快捷键Command+;
打开。下图是正确的配置。
修改后发现,仍未解决问题。
安装下面如图所示的步骤操作后,就完成了!
欢迎使用各种语言的各路大佬加入我们的repo。项目地址:http://github.com/taryn2016/coding
]]>去年年末看到好多小伙伴在写年终总结,有关生活的,有关工作的,对来年(2018)的展望的,没有丝毫的想法,不知怎的没有动笔的冲动。直到今天(开年第一天上班),头哥给大伙开了个会,讲了很多关于工作上的规划和以及每个人在团队里的定位,内容很多,并不能完全消化,但确实给了我很大的启发,让我动了写这篇总结的念头。
其实我一直是个有规划的人,大学四年的所有资料、文档、代码,我都会挨个儿根据科目、学期分文件夹。这一次,也要细细来讲,但并不想确保条理清晰。
虽然说是2017-2018,其实应该是2017的春节-2018的春节。那么,开始吧。
2017.3中
带着刚毕业的傲气,仗着笔试题简单,拿了个高分;面试时说话,人就有点飘了,还班门弄斧地“指点”了大风车的热补丁方案。现在想想真是蠢。因为当时的面试官就是我现在的师父。总觉得我那种绣花枕头一包草的形象已经在我师父眼里根深蒂固了。每每想到此处,都想跳槽跑路,真是没脸待下去了。
2017.3下
入职时刚好是无线部拆分成无线业务部和无线架构部的时候,当时还不知道有什么区别也不知道自己被分在架构部。后来才知道架构部都是大佬(除了我),一般来说p级都是比业务组要高的。(后来还从测试同学那听来,测试同学都不太会给架构组同学提bug,因为都是大佬,说话都是恭恭敬敬的。)
那我可能是个假人。
刚入职可能想展现一下实力吧。师父给了我一个日期范围选择控件(自定义View)任务, 本来给了两周时间,结果我3天就完成了基本功能。但师父似乎并不怎么满意。后来证明,确实可扩展性太差了,业务组的小伙伴也反应这个组件太难用。这又是后话了。
2017.4
趁热打铁,分享了一波Android7.x新特性。因为原本在税友就分享过一次,这次分享得游刃有余,不仅介绍了新特性,同时还提到了多处兼容问题,需要在编码过程中注意的。而且都是大多是亲身经历,比较有说服力。
2017.5
年会。抽了个华米运动手表。用了半年左右,电池电量撑不了一天,废了。
正式接手聊天库。当时在做的需求是xxx(app名)的“xx交易2.0”。(比较讽刺的是,在后来的某个版本里,整个群聊交易系统都被砍了。)此时,聊天库至少经手了3个人的维护,我是第4个。未曾料想,我可能一不小心就成了这4个人里维护时间最长的维护者。
很膨胀,提了提前转正申请。也成功了。
2017.6-8
聊天库也不总是有需求,期间师父让我做了”图片加载包装库&webp”和”网络接口流量统计”。
前者,一直找不到好的处理方式,尤其是fresco使用的不是ImageView而是Fresco特有的SimpleDraweeView。不知该如何处理各个图片加载引擎的切换。我简单地理解成,包装好后不用关心当前使用的是什么图片加载框架,用相同的代码就可以实现图片加载。似乎并不是这么简单,但我想不到,我第一次感受到我的知识储备限制了我的能力施展空间。
后者,也做了几个月。让我较为深入地了解了http协议。学会了抓包和计算报文大小。本来测试都通过了。最终因为数据库相关的操作,被打了回来。后来说是修复好后,等下个版本再上。
2017.9
内推了女朋友。通过了笔试,技术面,hr面。在最后hr将要给offer的时候。
“你和内推人是什么关系?” “他是我男朋友。”
因为一句话毁了所有努力。
工作上不顺利,对公司制度也充满了失望。
2017.10
又是一波聊天、消息相关的需求。匿名xxx需求、进群xx通知、xx上拍播报、动态卡片定义、消息重新归类。这些没什么挑战性,也算是拾回了一些信心,却开始被一些外界因素所影响。杨总做iOS端开发,基本上跟我接的都是一致的需求。但他对工作的懒散态度,让我很不习惯。师父也教导我不要学他(杨总),他(杨总)是要走(离职)的人。
2017.11-12
业务组那边有每周一次的代码review会议,每次都会叫上一名架构组大佬。这一次轮到了我,本来想好好参与其中,粗心的自己却犯了好几处错误,真是给我们架构组丢人。
易诚拍相关的业务,大佬们忙不过来了,扔了一小部分给我(和杨总)。分别是“xxx扫描”和“消息库接入xxx(app名字)”。有时候真的不是很好界定任务的归属,消息库确实没话可说,跟聊天库一起都归属于消息中心,所以消息库也归我维护,自然归我接入。但是行驶证扫描本就是业务组同学写的库,现在需要接入易诚拍,需要我在他的库基础上修改,这种改动有些不合常理。
又接到一个比较富有挑战性的自定义View的任务,日历任务管理(仿钉钉日历)。本来完成得挺好的,但是…说来话长。iOS端原本是杨总实现,但他找了第三方的日历控件,实现后无法满足业务场景,又因为要离职的事情。所以任务直接压到了成哥那边,成哥本来就有其他任务在身,也来不及自己实现,只好又找了一个第三方的日历控件(但比杨总找的好),又自己实现了和RN的事件回调。而我对自己的能力还是很有信心的,所以一切从头撸。不过我的实现与他截然不同,导致了两端实现的控件在RN中使用时不统一,这我就很烦。我承认我的实现不够人性化,甚至有点Android布局的思想在里面。(正确的做法应该倾向于‘提供给RN使用’的角度去设计控件)。但是,我做的日历组件性能好啊~滑动开合毫无阻塞。也说不好两端不统一的情况下应该让哪一端改。不过我确实很不想改,毕竟辛辛苦苦实现了这么久,iOS端只是copy+paste。后来师父跟我谈心,提及此事,略加批评。我也认为做法不妥。改不改都该给个回音,不应该在上级要求改之后,并不改动也不向上级请示能否不改。凡事都可商量,凡事都该有个回响。
2018.1-2018.2
复活xxx聊天xxx·一期
又是消息中心的一个大需求。背景是大风车的微信机器人屡次被微信封杀,现在需要将机器人业务迁移到xxx(app名称)上。但是之前的聊天相关的业务都不成功。聊天中也存在很多bug。故一期主要是为了优化原来的聊天库。
前期开发很顺利。后期由于杨总的盲目乐观和不根据设计稿、需求文档开发,导致大家等他一个人修bug、打包。我也算尽心尽力提醒他了相关的注意点,但他似乎心不在焉。我无话可说,只是希望以后给我派个靠谱的搭档。
后来发现我想多了,因为杨总的离开和聊天库的业务化,聊天库将交给业务组打理。有点舍不得,毕竟维护了这么久。算了算了,先做完复活xxx聊天xxx·二期吧。一切向前看。
最后说几句
刚入职时,大搜车才B轮,结果在春节前已经完成了C、D、E轮融资,这融资速度有点惊人。但是对员工的福利的一点儿也没涨。年前发的年货还不如我在税友当实习生时拿的多;年后的开门红包也缩水严重(据同事透露,往年是今年的3倍);还有亲属不得内推的规定也是让人恼火,一点也没有阿里系的大气作风。
2017一年工作上对自己的打击其实挺大的。一度认为自己待在大搜车没什么价值,不是没东西学,而且做了东西没什么用。
2018年,希望可以多完成一些有挑战性的任务吧。把失去的找回来!
]]>一款基于HSV颜色空间的仿Photoshop取色器的Android版颜色拾取器。
github地址:https://github.com/relish-wang/ColorPicker
上一篇已经简单介绍了ColorPicker的核心自定义控件ColorPickerView的监听事件相关代码。接下来我们详细解析一下ColorPickerDialog的相关代码
注:
这是ColorPickerDialog
唯一的构造方法,里面的主要内容都在setUp()
方法里。
稍微梳理一下代码:
1 | private ColorPickerDialog(Context context, int initialColor) { |
当点击软键盘上的【完成】按钮时触发监听
1 | @Override |
排除异常情况,正常情况下执行功能的主要是一下三行代码:
1 | int c = Utils.convertToColorInt(hexVal); |
让我们查看一下Utils.convertToColorInt()
的实现代码:
1 | /** |
这里的逻辑我主要参考了Photoshop里颜色选择器对手动输入颜色进行的操作。
略微观察可以发现,除了3位的时候要特殊处理,其他的情况下都是在前面加足够数量的0,以凑齐6位颜色编码。
关于异常处理Tips:在这里,只要是不符合正则表达式的颜色字符串,都会直接向外抛出异常,而不是进行捕捉。回到前一段关于颜色监听的代码可以发现,在监听中我们捕获了异常,并用输入框颜色的改变以反馈用户。因此对于异常的捕捉与抛也是有讲究的。
这里的逻辑就比较显而易见了
注:这里可能会有疑问。我在输入框中手动输入了颜色编码,从而ColorPickerView
更新了颜色,触发了onColorChanged()
,反过来又更新输入框里的颜色编码。这这这…这难道不就循环了吗?这也是我之前考虑到的一个问题,因此并没有给显示颜色编码的输入框设置TextWatcher
,而是OnEditorActionListener
,且只有在用户输入完成点输入法右下角的[完成/Done]按钮才会触发。
1 | @Override |
ColorPickerDialog的代码量并没有ColorPickerView那么多,其实讲完初始化就已经讲完了它的核心功能的代码了,接下来直接讲解使用。
回到本文最初介绍ColorPicker初始化的地方,可以看到ColorPickerDialog的唯一的一个构造方法是private
的,因为我把它做成了Builder模式,这样使用起来更灵活。
1 | new ColorPickerDialog.Builder(MainActivity.this, mColor)//上下文, 初始化颜色 |
颜色拾取监听器OnColorPickedListener:
1 | @Override |
好了,经过四篇文章的学习,我们已经将ColorPicker中最核心的代码学习完了,相信你从头看到这里对Android自定义View的理解又更深了一层,甚至学到了颜色相关的一些其他知识,比如HSV颜色空间、HSV与RGB颜色转化。
最后,欢迎大家给我提供宝贵的意见和建议,进一步优化ColorPicker。
GitHub地址: https://github.com/relish-wang/ColorPicker
一款基于HSV颜色空间的仿Photoshop取色器的Android版颜色拾取器。
github地址:ColorPicker
上一篇已经简单介绍了ColorPicker的核心自定义控件ColorPickerView的绘制流程。接下来我们详细解析一下ColorPickerView的监听事件相关代码。
注:
在讲颜色改变监听之前,先来讲讲初始颜色传入时,代码的变化吧!
invalidate
说明:调用invalidate
后,View会触发onDraw()
方法,因此两个调色板会根据现在的HSV值绘制当前显示的色板以及指示器的位置。
1 | public void setColor(int color) { |
在选择颜色过程中,会触发两种监听事件:轨迹球拖拽事件(onTrackballEvent
)、屏幕点击事件(onTouchEvent
)
观察这边的代码,这里分为两种情况处理:
最后再将HSV值换算成RGB值,通过监听器回调给上一级(ColorPickerDialog)
1 | @Override |
moveTrackersIfNeeded()
方法中invalidate();
1 | @Override |
最后汇总一下,今天这篇文章主要讲了,ColorPickerView的初始颜色传入+颜色改变监听。下一篇将讲解ColorPickerDialog的主要代码,敬请期待。
]]>一款基于HSV颜色空间的仿Photoshop取色器的Android版颜色拾取器。
github地址:ColorPicker
上一篇已经简单介绍了ColorPicker的项目结构以及两种颜色空间,接下来我们详细解析一下ColorPicker的核心自定义控件ColorPickerView。
在阅读代码之前,我们先看一下ColorPicker的布局以及一些标注的数值在代码里的变量名称。
阅读一个自定义View的代码,只需记住四步走:构造方法
、onMeasure
、onLayout
、onDraw
1 | public ColorPickerView(Context context) { |
可见,前2个构造器最后都调用了第三个构造器。而第三个构造器里的主要内容都在一个名为init()
的方法。
1 | private void init() { |
init
方法里有很多变量,它们的含义如下图所示:
1 | @Override |
测量模式(MeasureSpecMode
)分为EXACTLY
、AT_MOST
、UNSPECIFIED
三种类型。前两种类型可以通过MeasureSpec.getSize(int measureSpec)
获得具体的值;当测量模式为UNSPECIFIED
时,需要我们自己确定自定义View的高宽。这里我设定了一些默认值,使ColorPicker看起来更舒服。
这里使用了父类的onLayout方法。
绘制ColorPickerView
分为两个部分:饱和度灰度调色板mSatValRect
+色相调色板mHueRect
。
1 | @Override |
开始了解具体绘制细节之前,希望读者已经了解过Shader
(着色器)的相关知识。
1 先画一个大矩形,再在上面画我们的饱和度灰度调色板,这样看起来有一个描边的效果。
2 绘制饱和度灰度指示器。根据当前选择的颜色定位到具体的坐标,在这个坐标上做画。先画一个半径为mSVTrackerRadius
、边线粗细为mDensity
的空心大圆,再在上面画一个半径为mSVTrackerRadius-mDensity
、边线粗细为mDensity
的空心小圆。
1 | /** |
1 同理绘制矩形调色板
2 同理绘制空心圆角矩形
1 | /** |
这样一来,整个ColorPickerView的绘制流程就都在这里了。
下一篇主要讲解ColorPickerView初始颜色传入和颜色改变监听相关代码:仿Photoshop取色器ColorPicker(三)
一款仿Photoshop取色器的Android版取色器。采用HSV颜色空间,可手动选取想要的颜色,也可以手动输入具体颜色的16进制编码以获取颜色(如:0xFFFFFF表示白色)。
github地址:ColorPicker
ColorPicker项目结构如下图所示:
核心自定义控件: ColorPickerView.java
颜色拾取对话框: ColorPickerDialog.java
颜色转换相关工具: Utils.java
对话框布局文件: dialog_color_picker.xml(纵向布局)+dialog_color_picker.xml(横向布局)
其他文件内容都为项目构建时的默认内容。
在开始了解ColorPicker的具体实现之前,需要首先了解一些颜色相关的概念。
而ColorPicker库的核心自定义控件ColorPickerView的颜色拾取功能就是基于HSV颜色空间的。而且HSV和RGB是可以进行转换。不过不必担心转换,因为android.graphics.Color.java提供了转换方法:
下一篇主要讲解ColorPickerView的绘制流程:仿Photoshop取色器ColorPicker(二)
]]>项目地址:https://github.com/relish-wang/MVPActivity-Template
MVP模式耳熟能详。虽然它降低了程序的耦合度,利于团队协作以及后期维护,但是它增加了代码量和文件量。程序员不得不每次为一个界面建立至少6个文件(三个接口,三个实现类,分别对应M、V、P),以及接口与实现类的继承关系、MVP三者之间的关系。如图:
Presenter持有View和Model的实例,View、Model持有Presenter的实例。View与Model通过Presenter进行信息交互。
本文讲解重点并不是MVP模式。而是介绍该项目的使用来减少因MVP模式带来的重复劳动。
首先需要了解什么是Android Studio Template。
打开AndroidStudio安装目录下文件夹
[AndroidStudio安装目录]\plugins\android\lib\templates\activities
可以在这里看到很多模板(Templates)
看见这些文件夹名是不是很熟悉?没错这些就是我们在新建项目或新建Activity看到的
我们只需要将自己编写的模板(Template)放到该文件夹下,就可以使用自己的模板了。
Android Studio Template资料传送门:
鸿洋_大神的两篇博客:
下载MVPActivity-Template。将MVPActivity文件夹拷贝到
[AndroidStudio安装目录]\plugins\android\lib\templates\activities
该目录下,重启Android Studio即可使用。(New->Activity->MVPActivity)
]]>