说三道四技术文摘-感悟人生的经典句子
说三道四 > 文档快照

优化UITableViewCell高度计算的那些事

HTML文档下载 WORD文档下载 PDF文档下载
在将UITableView+FDTemplateLayoutCell自动算高工具更新至1.2版本之后,百度知道iOS团队对UITableViewCell利用AutoLayout自动高度计算和UITableView滑动优化进行了总结,以及RunLoop实践技巧。

前言

这篇文章是我和我们团队最近对UITableViewCell利用AutoLayout自动高度计算和UITableView滑动优化的一个总结。从这篇文章里,你可以读到:

  • UITableView高度计算和估算的机制
  • 不同iOS系统在高度计算上的差异
  • iOS8 self-sizing cell
  • UITableView+FDTemplateLayoutCell如何用一句话解决高度问题
  • UITableView+FDTemplateLayoutCell中对RunLoop的使用技巧

UITableViewCell高度计算

rowHeight

UITableView是我们再熟悉不过的视图了,它的delegate和data source回调不知写了多少次,也不免遇到UITableViewCell高度计算的事。UITableView询问cell高度有两种方式。一种是针对所有Cell具有固定高度的情况,通过:

self.tableView.rowHeight = 88;

上面的代码指定了一个所有cell都是88高度的UITableView,对于定高需求的表格,强烈建议使用这种(而非下面的)方式保证不必要的高度计算和调用。rowHeight属性的默认值是44,所以一个空的UITableView显示成那个样子。

另一种方式就是实现UITableViewDelegate中的:

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {    // return xxx}

需要注意的是,实现了这个方法后,rowHeight的设置将无效。所以,这个方法适用于具有多种cell高度的UITableView。

estimatedRowHeight

这个属性iOS7就出现了,文档是这么描述它的作用的:

If the table contains variable height rows, it might be expensive to calculate all their heights when the table loads. Using estimation allows you to defer some of the cost of geometry calculation from load time to scrolling time.

恩,听上去蛮靠谱的。我们知道,UITableView是个UIScrollView,就像平时使用UIScrollView一样,加载时指定contentSize后它才能根据自己的bounds、contentInset、contentOffset等属性共同决定是否可以滑动以及滚动条的长度。而UITableView在一开始并不知道自己会被填充多少内容,于是询问data source个数和创建cell,同时询问delegate这些cell应该显示的高度,这就造成它在加载的时候浪费了多余的计算在屏幕外边的cell上。和上面的rowHeight很类似,设置这个估算高度有两种方法:

self.tableView.estimatedRowHeight = 88;// or- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath {    // return xxx}

有所不同的是,即使面对种类不同的cell,我们依然可以使用简单的estimatedRowHeight属性赋值,只要整体估算值接近就可以,比如大概有一半cell高度是44, 一半cell高度是88, 那就可以估算一个66,基本符合预期。

说完了估算高度的基本使用,可以开始吐槽了:

  1. 设置估算高度后,contentSize.height根据“cell估算值 x cell个数”计算,这就导致滚动条的大小处于不稳定的状态,contentSize会随着滚动从估算高度慢慢替换成真实高度,肉眼可见滚动条突然变化甚至“跳跃”。
  2. 若是有设计不好的下拉刷新或上拉加载控件,或是KVO了contentSize或contentOffset属性,有可能使表格滑动时跳动。
  3. 估算高度设计初衷是好的,让加载速度更快,那凭啥要去侵害滑动的流畅性呢,用户可能对进入页面时多零点几秒加载时间感觉不大,但是滑动时实时计算高度带来的卡顿是明显能体验到的,个人觉得还不如一开始都算好了呢(iOS 8更过分,即使都算好了也会边划边计算)。

iOS8 self-sizing cell

具有动态高度内容的cell一直是个头疼的问题,比如聊天气泡的cell,frame布局时代通常是用数据内容反算高度:

CGFloat height = textHeightWithFont() + imageHeight + topMargin + bottomMargin + ...;

供UITableViewDelegate调用时很可能是个cell的类方法:

@interface BubbleCell : UITableViewCell+ (CGFloat)heightWithEntity:(id)entity;@end

各种魔法margin加上耦合了屏幕宽度。

AutoLayout时代好了不少,提供了-systemLayoutSizeFittingSize:的API,在contentView中设置约束后,就能计算出准确的值;缺点是计算速度肯定没有手算快,而且这是个实例方法,需要维护专门为计算高度而生的template layout cell,它还要求使用者对约束设置的比较熟练,要保证contentView内部上下左右所有方向都有约束支撑,设置不合理的话计算的高度就成了0。

这里还不得不提到一个UILabel的蛋疼问题,当UILabel行数大于0时,需要指定preferredMaxLayoutWidth后它才知道自己什么时候该折行。这是个“鸡生蛋蛋生鸡”的问题,因为UILabel需要知道superview的宽度才能折行,而superview的宽度还依仗着子view宽度的累加才能确定。这个问题好像到iOS 8才能够自动解决(不过我们找到了解决方案)。

回到正题,iOS 8 WWDC中推出了self-sizing cell的概念,旨在让cell自己负责自己的高度计算,使用frame layout和auto layout都可以享受到:


这个特性首先要求是iOS 8,要是最低支持的系统版本小于8的话,还得针对老版本单写套老式的算高(囧),不过用的API到不是新面孔:

self.tableView.estimatedRowHeight = 213;self.tableView.rowHeight = UITableViewAutomaticDimension;

这里又不得不吐槽了,自动计算rowHeight跟estimatedRowHeight到底是有什么仇,如果不加上估算高度的设置,自动算高就失效了。

PS:iOS 8系统中rowHeight的默认值已经设置成了UITableViewAutomaticDimension,所以第二行代码可以省略。

问题:

  • 这个自动算高在push到下一个页面或者转屏时会出现高度特别诡异的情况,不过现在的版本修复了。
  • 求一个能让最低支持iOS 8的公司。

iOS 8抽风的算高机制

相同的代码在iOS 7和iOS 8上滑动顺畅程度完全不同,iOS 8莫名奇妙的卡。很大一部分原因是iOS 8上的算高机制大不相同,这是我做的小测试:


研究后发现这么多次额外计算有下面的原因:

  1. 不开启高度估算时,UITableView上来就要对所有cell调用算高来确定contentSize。
  2. dequeueReusableCellWithIdentifier:forIndexPath: 相比不带“forIndexPath”的版本会多调用一次高度计算。
  3. iOS 7计算高度后有“缓存”机制,不会重复计算;而iOS 8不论何时都会重新计算cell高度。

iOS 8把高度计算搞成这个样子,从WWDC也倒是能找到点解释,cell被认为随时都可能改变高度(如从设置中调整动态字体大小),所以每次滑动出来后都要重新计算高度。

说了这么多,究竟有没有既能省去算高烦恼,又能保证顺畅的滑动,还能支持iOS 6+的一站式解决方案呢?

UITableView+FDTemplateLayoutCell

使用UITableView+FDTemplateLayoutCell无疑是解决算高问题的最佳实践之一,既有iOS 8 self-sizing功能简单的API,又可以达到iOS7流畅的滑动效果,还保持了最低支持iOS6。

使用起来大概是这样:

#import <UITableView+FDTemplateLayoutCell.h>- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {    return [tableView fd_heightForCellWithIdentifier:@"identifer" cacheByIndexPath:indexPath configuration:^(id cell) {        // 配置 cell 的数据源,和 "cellForRow" 干的事一致,比如:        cell.entity = self.feedEntities[indexPath.row];    }];}

写完上面的代码后,你就已经使用到了:

  • 和每个UITableViewCell ReuseID一一对应的template layout cell
这个cell只为了参加高度计算,不会真的显示到屏幕上;它通过UITableView的-dequeueCellForReuseIdentifier: 方法lazy创建并保存,所以要求这个ReuseID必须已经被注册到了UITableView中,也就是说,要么是Storyboard中的原型cell,要么就是使用了UITableView的-registerClass:forCellReuseIdentifier:或-registerNib:forCellReuseIdentifier:其中之一的注册方法。
  • 根据autolayout约束自动计算高度
使用了系统在iOS 6就提供的API:-systemLayoutSizeFittingSize:
  • 根据index path的一套高度缓存机制
计算出的高度会自动进行缓存,所以滑动时每个cell真正的高度计算只会发生一次,后面的高度询问都会命中缓存,减少了非常可观的多余计算。
  • 自动的缓存失效机制
无须担心你数据源的变化引起的缓存失效,当调用如-reloadData,-deleteRowsAtIndexPaths:withRowAnimation:等任何一个触发 UITableView 刷新机制的方法时,已有的高度缓存将以最小的代价执行失效。如删除一个indexPath为[0:5]的cell时,[0:0] ~ [0:4]的高度缓存不受影响,而[0:5]后面所有的缓存值都向前移动一个位置。自动缓存失效机制对UITableView的9个公有API都进行了分别的处理,以保证没有一次多余的高度计算。
  • 预缓存机制
预缓存机制将在UITableView没有滑动的空闲时刻执行,计算和缓存那些还没有显示到屏幕中的cell,整个缓存过程完全没有感知,这使得完整列表的高度计算既没有发生在加载时,又没有发生在滑动时,同时保证了加载速度和滑动流畅性,下文会着重讲下这块的实现原理。

我们在设计这个工具的API时斟酌了非常长的时间,既要保证功能的强大,也要保证接口的精简,一行调用背后隐藏着很多功能。

这一套缓存机制能对滑动起多大影响呢?除了肉眼能明显的感知到外,我还做了个小测试。一个有54个内容和高度不同cell的table view,从头滑动到尾,再从尾滑动到头,iOS 8系统下,iPhone 6,使用Time Profiler监测算高函数所花费的时间:

  • 未使用缓存API、未使用估算,共花费877ms


  • 使用缓存API、开启估算,共花费77ms


测试数据的精度先不管,从量级上就差了一个数量级,说实话自己也没想到差距有这么大。

同时,工具也顺手解决了-preferredMaxLayoutWidth的问题,在计算高度前向contentView加了一条和table view宽度相同的宽度约束,强行让contentView内部的控件知道了自己父view的宽度,再反算自己被外界约束的宽度,破除“鸡生蛋蛋生鸡”的问题,这里比较tricky,就不展开说了。下面说说利用RunLoop预缓存的实现。

利用RunLoop空闲时间执行预缓存任务

FDTemplateLayoutCell的高度预缓存是一个优化功能,它要求页面处于空闲状态时才执行计算,当用户正在滑动列表时显然不应该执行计算任务影响滑动体验。

一般来说,这个功能要耦合UITableView的滑动状态才行,但这种实现十分不优雅且可能破坏外部的delegate结构,但好在我们还有RunLoop这个工具,了解它的运行机制后,可以用很简单的代码实现上面的功能。

空闲RunLoopMode

当用户正在滑动UIScrollView时,RunLoop将切换到UITrackingRunLoopMode接受滑动手势和处理滑动事件(包括减速和弹簧效果),此时,其他Mode(除NSRunLoopCommonModes这个组合Mode)下的事件将全部暂停执行,来保证滑动事件的优先处理,这也是iOS滑动顺畅的重要原因。

当UI没在滑动时,默认的Mode是NSDefaultRunLoopMode(同CF中的kCFRunLoopDefaultMode),同时也是CF中定义的“空闲状态Mode”。当用户啥也不点,此时也没有什么网络 IO时,就是在这个Mode下。

用RunLoopObserver找准时机

注册RunLoopObserver可以观测当前RunLoop的运行状态,并在状态机切换时收到通知:

  1. RunLoop开始
  2. RunLoop即将处理Timer
  3. RunLoop即将处理Source
  4. RunLoop即将进入休眠状态
  5. RunLoop即将从休眠状态被事件唤醒
  6. RunLoop退出

因为“预缓存高度”的任务需要在最无感知的时刻进行,所以应该同时满足:

  1. RunLoop处于“空闲”状态Mode;
  2. 当这一次RunLoop迭代处理完成了所有事件,马上要休眠时。

使用CF的带block版本的注册函数可以让代码更简洁:

CFRunLoopRef runLoop = CFRunLoopGetCurrent();CFStringRef runLoopMode = kCFRunLoopDefaultMode;CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault, kCFRunLoopBeforeWaiting, true, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity _) {    // TODO here});CFRunLoopAddObserver(runLoop, observer, runLoopMode);

在其中的TODO位置,就可以开始任务的收集和分发了,当然,不能忘记适时的移除这个observer。

分解成多个RunLoop Source任务

假设列表有20个cell,加载后展示了前5个,那么开启估算后table view只计算了这5个的高度,此时剩下15个就是“预缓存”的任务,而我们并不希望这15个计算任务在同一个RunLoop迭代中同步执行,这样会卡顿UI,所以应该把它们分别分解到15个RunLoop迭代中执行,这时就需要手动向RunLoop中添加Source任务(由应用发起和处理的是Source 0任务)

Foundation层没对RunLoopSource提供直接构建的API,但是提供了一个间接的、既熟悉又陌生的API:

- (void)performSelector:(SEL)aSelector               onThread:(NSThread *)thr              withObject:(id)arg           waitUntilDone:(BOOL)wait                   modes:(NSArray *)array;

这个方法将创建一个Source 0任务,分发到指定线程的RunLoop中,在给定的Mode下执行,若指定的RunLoop处于休眠状态,则唤醒它处理事件,简单来说就是“睡你xx,起来嗨!”

于是,我们用一个可变数组装载当前所有需要“预缓存”的index path,每个RunLoopObserver回调时都把第一个任务拿出来分发:

NSMutableArray *mutableIndexPathsToBePrecached = self.fd_allIndexPathsToBePrecached.mutableCopy;CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault, kCFRunLoopBeforeWaiting, true, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity _) {    if (mutableIndexPathsToBePrecached.count == 0) {        CFRunLoopRemoveObserver(runLoop, observer, runLoopMode);        return;    }    NSIndexPath *indexPath = mutableIndexPathsToBePrecached.firstObject;    [mutableIndexPathsToBePrecached removeObject:indexPath];    [self performSelector:@selector(fd_precacheIndexPathIfNeeded:)                 onThread:[NSThread mainThread]               withObject:indexPath            waitUntilDone:NO                    modes:@[NSDefaultRunLoopMode]];});

这样,每个任务都被分配到下个“空闲”RunLoop迭代中执行,其间但凡有滑动事件开始,Mode切换成UITrackingRunLoopMode,所有的“预缓存”任务的分发和执行都会自动暂定,最大程度保证滑动流畅。

本文原载于Sunny's Blog,作者:孙源(@我就叫Sunny怎么了),90后非主流iOS程序猿,现负责百度知道iOS团队。


CSDN移动将持续为您优选移动开发的精华内容,共同探讨移动开发的技术热点话题,涵盖移动应用、开发工具、移动游戏及引擎、智能硬件、物联网等方方面面,如果您有想分享的技术、观点,可通过电子邮件(tangxy#csdn.net,请把#改成@)投稿。

第一时间掌握最新移动开发相关信息和技术,请关注mobilehub公众微信号(ID: mobilehub)。


《近匠》专访AbleCloud李海磊:IoT平台求变 腾讯、京东、微软等设计专家齐聚,探讨移动应用体验创新设计 TIOBE 2015年5月编程语言排行榜:微软系语言份额上升 《Hadoop核心技术》作者翟周伟 :我与Hadoop的不解之缘 云集百位核心专家 中国云计算大会演讲议题公布(表) 细品这杯香浓的咖啡——阿里中间件高级专家沈询的Java之旅 超图研究院院长李绍俊:创新2.0时代的平台软件研发体系 优麒麟(Ubuntu Kylin)15.04发布派对在天津成功举行 【CTO讲堂】以API为核心的移动应用云大发展时代 游戏设计的迭代误用:从半成品到概念修正 成功的社区在于拥抱变化——知乎创始人周源专访 优化UITableViewCell高度计算的那些事 国外iOS大牛:开发Apple Watch应用我犯过的错 云上Java System Profiling与Debugging——蚂蚁金服观察与实践 搜狗商业平台Java技术实践 Java在电信软件领域的技术实战 关于Java框架Vert.x的几点思考 Java在游戏服务器开发中的应用 Java框架研发思考 那些年,Java程序员用过的开发工具 Java内存模型的历史变迁 Java 8与Apache Ignite Java开发与技术挑战——关于技术的技术思考 专访唯品会架构师肖桦:做编码的架构师 越来越“简单”的Java 我的Java!越过山丘 专访沙梓社:做个“Think Different”的技术牛人 基于ES6,使用React、Webpack、Babel构建模块化JavaScript应用 财税街创始人石克清:创业须空杯心态+有效社交 自我知识管理:连贯性就是生产率 玩转Google I/O 2015:Android M、IoT、Glass 2.0、VR、ATAP 求助capboy等网络视频播放高手!!! 菜鸟问题:在VC中用ALTER TABLE指令需要什么头文件? 请高手帮忙,感觉难度很高 大家做GUI时有什么好用又免费的日期控件呢? 在javabean中用sql server2000的jdbc驱动连不起数据库! 添加图片到数据库中,奇怪现象! 如果我已经得到了一个DOM树, 我第一次运行HTML HELP时,new怎么会是无效的? 我的 程序在调用FORM.ACTION的用法不正确。能告诉我怎样用它 怎幺样实现一对主从表的数据库基本操作?用ADOQuery连接SQL2000. 有关统计打印的问题 一个关于用VB调用C++builder编写的DLL文件,DLL的API函数中存在函数指针。请各位高手给点意见! 大专生如何考研?(up有分) ★★ 为高兴而送分 ★★ 关于打印的问题,请各位帮忙 请问应该用何种信用卡 利用INTERNET远程连接问题 移动办公相关问题?如在两台机器里编写同一段Web程序。 怎样将oracle sql中的in语句改为exists语句? 求教win 2000双网卡上宽带的问题,请高手帮忙! win2000,给文件改日期的命令是什莫??? 为什么我的背景图片显示不出来? 请问如何在DAO的SELECT语句中使用COUNT等运算符? 请教:请问在程序中怎么在Check box 前边的小框上打上对号 老板让我一个月内学会vb,大家说可能吗? 我装了project 2000后,原来的office 2000只能用50次了,怎么回事? 请能提供DES和RSA算法的源程序呀,vb或VC的都行,最好是VB 请问如何让一个报表的内容同一个DBGrid一致,可以自由设置字段的是否显示? 我用vb新建了一个文件请问如何能双击就能打开我的程序,就跟word一样例:我新建一个kk.doc 只要双击kk.doc 就能打开word 使用Enumwindows的问题 我的jsp页面能够运行但是WEBLOGIC显示了<2002-9-2 上午10时41分13秒> <Error> <HTTP> <Connection failure 大家是怎么看下载来的MFC的程序原代码的? 在JAVASCRIPT中,我怎样用语句控制一组选钮中那一个被选中? 如何对 redhat-config-network 进行配制? 各位师兄:哪里可以下载讲Delphi6多层结构的教材?很急!!!! 小问题 看完了钱能的《C++程序设计教程》继续深入下去该找什么书来看看呢? 关闭窗口 除了onunload 是否还有其他事件? 为什么我的电脑每个目录下都有一个folder文件? 请教一下:delphi6中nmftp的用法 急!!!水晶报表问题 用代码把数据窗口指定到某一打印机打印? 如何配置jConnect连接Sybase数据库?需要安装jdk吗? 奇怪的出错:java.lang.NoClassDefFoundError: com/sun/java/util/collections/HashMap 我装了project 2000后,原来的office 2000只能用50次了,怎么回事? 各位有没考虑过买房一事,看看北京的房价再看看我的收入,怎么办??? EDITBOX和URl字符串 帮帮忙,提供一份辞职信样本 各位高手,帮我解决下处理BMP,位图的方法. SDI分割视图,不允许用户调整每个分割视的大小?怎么实现 很简单语法的问题。 蒸馏设备的原理?里面要加多少水?要蒸到什么时候?多长时间么时候?酿酒 人吃进的食物中的各种动物蛋白质和植物蛋白质,是怎样变成人体自身的蛋白质的? 为什么计算工程量时必须遵守计价规范中的工程量计算规则 求助一下大家奥数题大全就告诉我吧本人先在此谢谢各位4Z 问一下大家2013年最火的一道小学奥数题,求解!了解的说下吧,打心底感谢了7p 下列溶液中,在空气里既不易被氧化,也不易分解,且可以用无色玻璃试剂瓶存放的是?A石炭酸B氢硫酸C氢氟酸D醋酸(我就只知道C不行呀~) 六年级数学题大全 问一下大家2013年最火的一道小学奥数题,求解!了解的说下吧,打心底感谢了9p 、可用无色滴瓶存放较长时间的试剂是( )A.浓硝酸 B.BaCl2溶液 C.NaOH溶液 D.碳酸钠溶液在滴瓶里 也要考虑是否会与空气中物质发生反应吗?滴瓶密封性不好吗? 最好多一点 我想复习 C语言中\n的作用 有两个沙漏.甲沙漏漏完需要3分钟,乙沙漏漏完需要5分钟.两个沙漏同时开始漏沙.第一次、甲沙漏漏完后立有两个沙漏.甲沙漏漏完需要3分钟,乙沙漏漏完需要5分钟.两个沙漏同时开始漏沙.\x05第 麻烦大家看下奥数题大全n有点急, 请问下有谁知道2013年最火的一道小学奥数题,了解的说下吧,打心底感谢了5F 掘进工作面瓦斯大怎么办 送沙漏代表什么!我是男生,有一个男性朋友要过生日,我想送他一个沙漏.但不知道该送什么颜色的? 请问下有谁知道2013年最火的一道小学奥数题,了解的说下吧, 什么固态非金属单质是红色的? 绿色沙漏代表什么意思? 有什么东西的腐蚀性比香蕉水低我是做涂装的,公司用来遮蔽产品的塑料塞子(具体什么材值不清楚)时间长了塞子上就会累积很厚的粉,使用起来很不方便,用香蕉水泡下又会把塞子搞坏,求达人 红色的固态非金属单质都有什么黑色的呢? 世上最大的 峡谷是哪个峡谷 一个沙漏计时器,上下容器皆密封,若将此计时器内的沙子用水代替,则水会怎样?一个沙漏计时器,上下容器皆密封,若将此计时器内的沙子用水代替,则水会( )A.会流下来 B.C.可能会流下来 D.装 计算采用22.05KHz采样频率,16位采样精度,录制30分钟双声道立体声,其数据量大约为多少MB.(求答案以及计算过程,) 下列哪个峡谷是世界上海拔最高、最深、最长的峡谷? 能燃烧的固态非金属单质是 为什么天体都是圆的? 世界上最大的峡谷是哪里? 如果直线斜率为k 则倾斜角为arctan k 是否对(请祥解) 为什么所有的天体都是圆的 硫酸铜溶液与硫酸钠溶液为什么颜色不同 世界上最大的峡谷是什么峡谷? 采暖工程量如何计算?(计算规则) 如何除去硫酸亚铁中的硫酸钠并提纯硫酸铜原溶液中有硫酸亚铁,硫酸钠和硫酸铜,如何除去硫酸亚铁中的硫酸钠并提纯硫酸铜中的铜离子 世界上最长的峡谷 我有一瓶四氧化三铁试剂,请问有什么用或能做点什么越多越好 我国最大的三角洲是呃…… 日本和英国都是岛屿国家,日本多火山地震,而英国火山地震很少,为什么? 坚固的药剂瓶有什么用 结晶度的高低对聚合物性能有哪些影响 请帮忙说说几种换热器形式 白磷存放在装有水的试剂瓶中,金属Li存放在装有煤油的试剂瓶中这样的保存方法为什么错 什么是聚合物的结晶 请问有谁知道谁能做一道初中找规律的数学题?要用呀,打心底谢谢给位朋友了1d c语言中k+=n+1是什么意思 有同学将记录表中4次测量的电阻的平均值作为小灯泡的电阻,你认为这样做正确吗?为什么? c语言中 =n 怎么理解 初中化学有哪些固态非金属?高手快回答我 结晶度的大小对聚合物的性能有哪些影响? 润滑油对金属漆有腐蚀性吗请问汽车润滑油对金属表面喷漆或者刷的漆有腐蚀性吗 化学非金属与非金属非金属与非金属之间有些为什么可以反应有些却不可以反应 有什么规律没有?只在短周期中 为什么通过蒸馏就可以除去自来水中的氯离子等杂质?原理是什么,只有这种方法吗 机油有腐蚀性吗我经常用缝纫机油擦手电筒,这对手电有腐蚀吗. 化学非金属.在已经发现的一百多种元素中,除稀有气体外,非金属元素只有______种,它们大都位于元素周期表的______部分.地壳中含量最多的前两种元素是______和______.空气中含量最多的元素是____ 日本地震对相机影响会持续多久?什么时候能恢复正常?想买相机,结果就地震了.这次地震对数码影响究竟多大很多厂子都关了,松下停产.数码业全部涨价,这叫没影响? 请问航空油润滑油的话腐蚀性有一定的腐蚀性,那么容器采用何种材料或者热处理工艺来满足腐蚀性要求呢 采掘工作面的进风流中氧气和二氧化碳的浓度各有什么规定? 求内行来指点:有关地震导致数码相机涨价~想买个相机,春游用,可是目前单反都在涨,Canon600D,60D;NikonD90,D7000;……等等,商家都说有货,但是国美,大中这种地方,一分钱都不能降,标多少就是多 汽车机油腐蚀性强吗前几天去做保养,拆机滤的时候,机油淌下来都淌到球笼套上了,就是那个胶皮的套,我也让他们擦了,可是上面还是亮亮的,我有点担心会不会腐蚀了? 直线斜率 倾斜角与斜率大小的函数关系图像
备案号:鲁ICP备13029499号-2 说三道四 www.s3d4.cn 说三道四技术文摘