{"id":13643469,"url":"https://github.com/fashare2015/TimerView","last_synced_at":"2025-04-21T01:32:47.196Z","repository":{"id":202209469,"uuid":"78839469","full_name":"fashare2015/TimerView","owner":"fashare2015","description":"一个解耦良好的计时控件，可自由扩展。","archived":false,"fork":false,"pushed_at":"2017-03-22T12:54:58.000Z","size":881,"stargazers_count":389,"open_issues_count":1,"forks_count":67,"subscribers_count":13,"default_branch":"master","last_synced_at":"2024-11-09T15:42:43.537Z","etag":null,"topics":["timer","timer-view"],"latest_commit_sha":null,"homepage":"","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/fashare2015.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null}},"created_at":"2017-01-13T10:26:13.000Z","updated_at":"2024-09-19T16:10:25.000Z","dependencies_parsed_at":null,"dependency_job_id":"ed7d5c64-17bd-4db6-a348-aabe59d5a530","html_url":"https://github.com/fashare2015/TimerView","commit_stats":null,"previous_names":["fashare2015/timerview"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fashare2015%2FTimerView","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fashare2015%2FTimerView/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fashare2015%2FTimerView/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fashare2015%2FTimerView/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/fashare2015","download_url":"https://codeload.github.com/fashare2015/TimerView/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":249982642,"owners_count":21355735,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["timer","timer-view"],"created_at":"2024-08-02T01:01:48.028Z","updated_at":"2025-04-21T01:32:45.882Z","avatar_url":"https://github.com/fashare2015.png","language":"Java","readme":"# 解耦解的早，改需求没烦恼\n\u003e世上本没有解耦，需求改的多了也便有了解耦。 —— 产品经理\n\n本例将通过一个计时控件，聊聊如何解耦~\n\n本`TimerView`仅作为`demo`，不保证其健壮性，请勿在实际项目中使用。\n\n# 特点\n- `UI容器`与`计时逻辑`分离\n- `UI容器`与`具体UI布局`分离\n\n# Let's Go\n话说，小明在做一个电商项目，有个倒计时需求。\n\n## 需求1.0\n要求\"时、分、秒\"数字显示。\n\n这个简单，小明很快自定义了一个`TimerView`:\n```java\npublic class TimerView {\n    TextView tvHour, tvMinute, tvSecond;\n    TextView divider1, divider2;\n    ...\n}\n```\n\n![需求1.0](screen-record/dtv_default.gif)\n\n## 需求2.0\n界面太丑啦，加点颜色和背景吧~\n\n这个也简单，小明很快加了一些自定义属性：\n```java\npublic class TimerView {\n    TextView tvHour, tvMinute, tvSecond;\n    TextView divider1, divider2;\n    \n    // 添加 自定义属性\n    int tvHourBgRes, tvMinuteBgRes, tvSecondBgRes;\n    int tvHourColor, tvMinuteColor, tvSecondColor;\n    ...\n}\n```\n![需求2.0](screen-record/dtv_diy.gif)\n\n## 需求3.0\n这时，产品经理又跑了过来，你看我发现了啥~\n\n发现一套火焰数字.jpg，好炫酷的说，帮忙改上去吧~\n\n![需求3.0](screen-record/dtv_fire.gif)\n\n小明内心：你TM有病啊！！！\n\n你发现了么，这下小明把自己带到沟里了。新需求要求显示`火焰数字图片`(ImageView)。\u003cbr/\u003e\n然而，由于`TimerView`由`TextView`构成，再怎么自定义属性也实现不了新需求(ImageView)了。\u003cbr/\u003e\n说的就是你呀：https://github.com/iwgang/CountdownView\n\n### 分析\n为啥会这样呢？因为一开始就设计紧耦合了。\u003cbr/\u003e\n`TimerView`依赖了具体子类`TextView`，功能也就被局限在`TextView`了。\u003cbr/\u003e\n那我们只需这么调整一下，把`TextView`改成更抽象的`View`。\u003cbr/\u003e\n这样一来`tvHour`既可以是`TextView`，也可以是`ImageView`，或者某个`ViewGroup`，功能得以拓展：\n\n```java\npublic class TimerView {\n    //TextView tvHour, tvMinute, tvSecond;\n    View tvHour, tvMinute, tvSecond;\n    //TextView divider1, divider2;\n    View divider1, divider2;\n    \n    // 自定义属性也不用了，因为无法确定 tvHour 这些究竟是啥子类。\n    //int tvHourBgRes, tvMinuteBgRes, tvSecondBgRes;\n    //int tvHourColor, tvMinuteColor, tvSecondColor;\n    ...\n}\n```\n\n这也体现了软件设计的一大原则：**要依赖抽象(View)而不要依赖具体(TextView)。**\n\n### 依赖注入\n还有一个问题：`tvHour`究竟是啥呢，这个得由用户决定。\u003cbr/\u003e\n通常我们会提供一系列`setXXX()`方法给用户进行设置。这个套路叫做**依赖注入**。\u003cbr/\u003e\n依赖注入是解耦的一种常见的方式。通常，当你有无法确定的一些东西，都应该抛给用户决定。\u003cbr/\u003e\n举个例子，`View`被点击时，设计者不知道你想干嘛，于是设计了`View.setOnClickListener()`。这是典型的依赖注入。\n\n好了，`ImageView`可以支持了，然而对于界面更新`ImageView`和`TextView`肯定是不一样的。\u003cbr/\u003e\n该怎么更新又无法确定了，我们可以再次用`依赖注入`的方式解耦，把难题抛给用户。\u003cbr/\u003e\n因此，我设计了类似`Adapter`的东西，都在代码里，就不详细展开了。\n\n## 需求4.0\n嗨呀~还不够啊，产品经理的脑洞总是很大的。\n\n产品经理：我看到一个 svg 诶~\n\n小明：算我倒霉。不过，我早就重构解耦过了。改需求, 小case~\n\n![需求4.0](screen-record/gtv.gif)\n\n## 需求5.0\n产品经理：小明，你还活着那？我发现机械表更好看诶~\n\n小明: ******, 我改就是了\n\n![需求5.0](screen-record/mtv.gif)\n\n# 感谢\nhttps://github.com/lypeer/GoogleClock\n\nhttps://github.com/gnehsuy/ClockView\n","funding_links":[],"categories":["日历时间"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffashare2015%2FTimerView","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ffashare2015%2FTimerView","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffashare2015%2FTimerView/lists"}