如果你觉得这篇文章太长,而且还没有往下阅读的话,我可以给你简要的介绍文章要讲的内容:我使用纯 Java 通过数据绑定的方式提供了一种
Android UI 开发的代码往往是支离破碎的,写出来的代码通常都是大量的模板化代码,而且没有结构可言。下面是一些问题(纯属个人见解):
Android UI 开发很少符合 MVC 模式(或者是 M-V-其他任何东西)
XML文件通常包含了很多重复的代码,在代码复用方面比较糟糕
XMLS 非常脆弱,这使得你在写 XML 文件时,即使输入了 TextVeiw ,在编译过程中编译器也不会警告你,但在 App 运行时又会抛出 InflateException 异常
缺少对 styles 的支持,缺少对变量的支持,不支持宏和计算结果(例如 10dp + 2px)
没有数据绑定,这使得你必须自己把所有的 findViewById 和 setOn...Listener 写好
你可以通过 Java 实现你的布局,但是写出来的代码有如天书
在 Web 开发中,开发者们很快就意识到在没有 MVx 的情况下开发复杂的应用会很吃力,这使得他们意识到 jQuery 中存在的问题,并开发了 Backbone,Knockout,Angular,Ember...等等,来提高他们的开发效率
但在 Android 中,我们还在通过那一点点函数毫无章法可言地设置 View 的属性,就像在 jQuery 里一样:
$('.myview').text('Hello');
$('.myview').on('click', function() {
});
myView.setText("Hello");
myView.setOnClickListener(new View.OnClickListener() { ...});
我们在一个目录下定义了我们的 Layout ,又在另一个目录中使用它们,然后在 UI 开发的代码里改变 ,这样并不好。
React.js 对 Web 开发有一点点影响:他们以树状关系的自定义对象创建了一个虚拟的 DOM 概念,并以此展示实际的 HTML 布局。虚拟树创建和切换的时间都很短,所以当实际的 DOM 需要被渲染,两棵虚拟树(前一棵和新的那棵)将进行对比,只有不匹配的部分才会被渲染。
Mithril.js 是一个精悍、短小的框架,使用它能使 React.js 的实现更整洁。在 Mithril 中,除了纯 JavaScript,你几乎能摆脱一切,同时,它还能让你在写布局的时候感受到图灵完备的语言所具备的力量。
return m('div',
m('p', someText),
m('ul',
items.map((item) => m('li', item))),
m('button', {onclick: myClickHandler}));
因此,你能用循环生成许多 View,你能用判断语句改变布局中的某个部分,最后你能绑定数据和设置事件监听器。
那这个方法能在 Android 中被使用吗?
虚拟布局(使用类似 Web 中虚拟 DOM 的概念)是树状的自定义Java对象集合,被用于展示实际的 Android 布局。虽然 App 的数据改变多少次,树就会被构建多少次,但布局改变的内容应该仅仅是前后不一致的部分(当前的布局和改变前布局)。
我们的框架只导入一个静态类,所以所有类中的静态方法都不需要类名前缀就能被使用(例如我们只需要使用 v(),而不是 Render.v()),这是语言特性带来的好处。下面是我们如何创建布局的例子:
v(LinearLayout.class,
orientation(LinearLayout.VERTICAL),
v(TextView.class,
text(someText)),
v(Button.class,
text("Click me"),
onClick(someClickHandler)));
第一个 v() 方法返回了一个虚拟布局,每一次调用后它会返回当前应用状态的实际展示(不是实际的 View!)
当一些文字变量被改变 - 虚拟树会获得一个被用于下次渲染的发生了改变的结点值,然后调用 setText()改变相应的 TextView 实例。但是其余的布局不会发生任何变化。
一棵虚拟布局树在理想情况下应该只是一个类,我们就把它叫作结点吧。但是结点主要有两种类型:View 结点(TextView.class等等)和属性设置结点,例如text(someText)
那这就意味着结点应该任意包含一个 View 类和一个方法去改变 View 的属性。
interface AttributeSetter {
public void set(View v);
}
public static class Node {
List<Node> attrs = new ArrayList<Node>();
Class<? extends View> viewClass; // for view nodes
AttributeSetter setter; // for attribute setter nodes
public Node(Class<? extends View> c) {
this.viewClass = c;
}
public Node(AttributeSetter setter) {
this.setter = setter;
}
}
现在我们需要定义类在产生虚拟布局的时候实际能干的事情了,那就让我们来调用可渲染类吧。一个可渲染类可以是一个 Activity,或者一个自定义的 ViewGroup,或者 Fragment 也凑合。每一个可渲染类都应该有一个用于返回虚拟布局的方法,此外,如果这个方法指定了它将要作用于实际布局中的哪个 View 会更好。
public interface Renderable {
Node view();
ViewGroup getRootView();
}
由于 v() 方法的第一个参数是 View 子类的泛型,所以你不用担心类型安全问题。剩下的参数都是结点类型,所以我们只需要把它们添加到 list 中,无视掉空结点的话效果会更好一些。
public static Node v(final Class<? extends View> cls, final Node ...nodes) {
return new Node(cls) ;
}
Here's an example of the text() attribute setter (the real code is a bit different, but it could have been implemented like this):
下面是一个 text() 属性的设置方法(实际代码会有点不一样,但是也能像下面这样实现):
public static Node text(final String s) {
return new Node(new AttributeSetter() {
public void set(View v) {
((TextView) v).setText(s);
}
});
}
其他类似的工具方法也能用于改变线性布局的方向,View 的大小、页边距、间距,总之所有 View 的参数都能被改变。
现在我们需要一个“渲染者”。这是一个能够根据类名创建 View ,使用 AttributeSetters修改对应的参数并且递归地添加子 View的方法。(同样的,下面的代码也是被简化的,实际的代码会有些不一样,主要差别在于当结点没有被改变的时候,我们应该如何避免视图的渲染)
public static View inflateNode(Context c, Node node, ViewGroup parent) {
if (node.viewClass == null) {
throw new RuntimeException("Root is not a view!");
}
// Exception handling skipped here to make the code look shorter
View v = (View) node.viewClass.getConstructor(Context.class).newInstance(c);
parent.addView(v);
for (Node subnode: node.attrs) {
if (subnode.setter != null) {
subnode.setter.set(v);
} else {
View subview = inflateNode(c, subnode, (ViewGroup) v);
}
}
return v;
}
现在我们真的可以摆脱 XMLS,并以一种简洁的方式通过 Java 进行布局了。
布局结点不应该直接地被使用,而应该是通过 render(Renderer r) 和 render()被使用。前者用于重渲染某一个 View,后者用于重渲染所有被展示的 View。Renderer 通过一个弱哈希表存储,使得在 View 被移除或者 Activity 被销毁的同时 - 他们的渲染者也不会再被使用。
这个框架的核心在于 自动进行重渲染,使得 UI 总能展示当前的虚拟布局状态。这就意味着 render() 应该在某个特定的节点被调用。
我参考 Mithril 的方法,把每一个 On...Listener 和 调用 render 的方法捆绑在每一次 UI 的交互中。
public static Node onClick(final View.OnClickListener listener) {
return new Node(new AttributeSetter() {
public void set(View v) {
v.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
listener.onClick(v);
// After the click was processed - some data may have been changed
// so we try to re-render the UI
render();
}
});
}
});
}
我觉得这样做是有道理的,因为大多数 Android 应用的数据都是在发生用户交互的时候被改变的。如果你的数据是因为其他因素被改变的 - 那就只能手动通过 render()渲染了。
这个方法虽然简单,却非常有用:
你能用类似 XML 的方式定义你的布局结构(通过嵌套调用 v() 方法)
你能用一种清晰易懂的方式绑定数据和监听器
布局都是类型安全的,并且你的编译器会自动完成相应的工作
没有运行时产生的开销,没有使用反射机制,没有自动生成代码
你能在任何地方使用 Java(变量,语句,宏)生成布局
你能用自定义 View 和自定义的属性设置方法
因为你的所有 UI 数据都被保存在属性中,因此你能轻易的保存它们
使用纯 Java 实现这些逻辑需要的代码还不到 250 行!
以上证明了这个方法是可行的。现在我在想,如果有人想要用这个方法开发一个功能齐全的库呢?
设计一个好的“区分”算法会是其中的关键。基本地,它应该能判断一个结点是否被添加/移除/修改,而文件就在于属性节点。简单的数据类型我们只要调用 equals() 去比较两个值就可以了,但是监听器呢?
v(SomeView.java,
onClick(v => ...));
这样做的话每一次虚拟树被创建,都会创建一个对应的监听器对象。那怎么去比较它们?还是永远都不更新监听器,只更新发生了改变的监听器类?或者使用某种事件分发机制分发事件,而不是使用监听器?
另一件需要被注意的是:我不想自己把所有属性设置方法写好。这里有一个更好的方法,也就是 Kotlin 他们在 koan 库中做的那样。
我现在在研究怎么从 android.jar 的类中自动生成设置器,以使得这个项目更有用。
不管怎样,现在的代码我都放在 Github 上了,有 MIT 的许可。欢迎大家来评论和 PR!
京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。
日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。
据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。
今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。
日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。
近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。
据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。
9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...
9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。
据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。
特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。
据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。
近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。
据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。
9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。
《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。
近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。
社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”
2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。
罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。