
简短的列表可以通过定向布局实现,但是如果列表项非常多,那么使用定向布局就不合适了。与许多其他的移动开发技术一样,鸿蒙操作系统也提供了可复用列表项的列表组件,这就是本篇文章要介绍的ListContainer。
简短的列表可以通过定向布局实现,但是如果列表项非常多,则使用定向布局就不合适了。例如,需要创建100个列表项的列表,那么用定向布局实现至少需要创建100个以上的组件了。然而,限于设备屏幕大小的限制,绝大多数组件不会显示在屏幕上,却会占据大量的内存资源,甚至造成应用“闪退”。
与许多其他的移动开发技术一样,鸿蒙操作系统也提供了可复用列表项的列表组件ListContainer。
注意: 在Android和iOS系统中,均提供了与ListContainer类似的可复用列表项的列表组件。在Android系统中,这种组件被称为RecyclerView; 在iOS系统中,这种组件被称为UITableView。
ListContainer继承于ComponentContainer,属于布局的一种。在ListContainer中,每个列表项都是一个组件或者子布局,即列表项组件。不过,ListContainer非常“吝啬”。
例如,利用ListContainer实现具有100个列表项的列表,ListContainer绝对不会实实在在地创建100个组件,而是仅创建屏幕当前能够显示的列表项组件。
例如,当前的设备屏幕只能够显示6个列表项,那么ListContainer只创建6个列表项组件。当用户上下滑动到其他的列表项时,被滑出去的列表项组件会被新的列表项复用,重新更换数据后再次进入用户的视野,如图1所示。

■ 图1 “吝啬”的列表组件ListContainer
在图1中,Item 1组件被滑出列表,随后被ListContainer“换装”填入新的数据后再次从列表底部重新进入ListContainer。Item 1组件和Item 7组件实际上是1个组件,组件还是原来的组件,只不过数据已经不是原来的数据了。这种按需创建组件的思想对于应用程序能够流畅稳定地运行非常重要。这么说来,ListContainer就像一个掌管着系统资源的大臣,时时刻刻打着精细的算盘,用最少的内存资源来干更多事情。
那么,我们应该如何来使用ListContainer呢? 实际上,ListContainer已经封装好复用列表项的机制了,不需要开发者过多操心。作为开发者,只需为ListContainer提供需要显示的列表项所需要的数据和组件就可以了,而这项工作就全权交给RecycleItemProvider类完成了。RecycleItemProvider是一个抽象类,开发者在使用它之前需要至少实现以下4种方法。
(1) getCount(): 提供列表项数量。
(2) getItem(int i): 提供当前列表项的数据。
(3) getItemId(int i): 提供当前列表项ID。
(4) getComponent(int id,Component cpt,ComponentContainer ctn): 创建组件与数据绑定,即创建属于这个列表项的组件,然后绑定该列表项数据。在这种方法中,id表示这个列表项ID,cpt对象为上一次这个列表项的组件对象。作为开发者可以直接复用这个组件对象,当然也可以创建一个新的组件对象。ctn是cpt组件的父布局对象。
接下来,演示ListContainer和RecycleItemProvider的具体使用方法。
首先,通过布局文件(recycle_item.xml)创建列表项的用户界面,代码如下:
/javaUI/entry/src/main/resources/base/layout/recycle_item.xml
<?xml version="1.0" encoding="utf-8"?>
<DirectionalLayout
xmlns:ohos="http://schemas.huawei.com/res/ohos"
ohos:height="match_content"
ohos:width="match_parent"
ohos:orientation="vertical">
<Text
ohos:id="$+id:item_text"
ohos:height="match_content"
ohos:width="match_parent"
ohos:margin="4vp"
ohos:text_size="16fp"
ohos:text_alignment="center"/>
</DirectionalLayout>
这个列表项非常简单,仅仅显示了一个文本组件,用于显示列表项数据,但是,这个用户界面与之前介绍的AbilitySlice界面不同,这个列表项界面仅仅显示在屏幕的某一个部位,因此不能使用之前的setUIContent方法了。
此时,需要LayoutScatter类来解析这个布局文件,LayoutScatter并不能直接被初始化,需要通过其getInstance(Context context)方法获取,其中content为当前的上下文对象。获取LayoutScatter 对象后,通过其parse(int xmlId,ComponentContainer root,boolean attachToRoot)方法即可解析所需要的XML布局文件,并且转换为组件对象。在parse方法中,xmlId表示需要解析的布局资源ID。当attachToRoot参数为true时,可以将解析出来的组件对象自动添加到root布局中,但是,在绝大多数情况下并不需要这么做,此时传递root参数为null,传递attachToRoot参数为false即可。
因此,通过以下代码即可将上面创建的列表项组件转换为DirectionalLayout对象,代码如下:
LayoutScatter scatter = LayoutScatter.getInstance(getContext());
DirectionalLayout layout = scatter.parse(ResourceTable.Layout_recycle_item, null, false);
除了定义列表项界面以外,还需要在AbilitySlice中定义ListContainer对象。在布局文件中定义一个ListContainer,代码如下:
//JavaUI/entry/src/main/resources/base/layout/slice_list_container.xml
<?xml version="1.0" encoding="utf-8"?>
<DirectionalLayout
xmlns:ohos="http://schemas.huawei.com/res/ohos"
ohos:height="match_parent"
ohos:width="match_parent"
ohos:orientation="vertical">
<ListContainer
ohos:id="$+id:list_container"
ohos:height="match_parent"
ohos:width="match_parent"
ohos:orientation="vertical"/>
</DirectionalLayout>
接下来,就可以在相应的AbilitySlice中通过RecycleItemProvider实现一个列表了,代码如下:
//JavaUI/entry/src/main/java/com/example/javaui/slice/ListContainerAbilitySlice.java
//ListContainer对象
private ListContainer mListContainer;
//列表数据(1-1000整型值)
private List<Integer> mNumbers;
@Override
public void onStart(Intent intent) {
super.onStart(intent);
super.setUIContent(ResourceTable.Layout_slice_list_container);
//初始化列表数据对象
mNumbers = new ArrayList<>();
for (int i = 0; i < 1000; i++) {
mNumbers.add(i + 1);
}
//获取ListContainer对象
mListContainer = (ListContainer) findComponentById(ResourceTable.Id_list_container);
//为ListContainer对象设置RecycleItemProvider
mListContainer.setItemProvider(new RecycleItemProvider() {
@Override
public int getCount() {
//列表项数
return mNumbers.size();
}
@Override
public Object getItem(int i) {
//当前列表项的数据
return mNumbers.get(i);
}
@Override
public long getItemId(int i) {
//当前列表项ID
return i;
}
@Override
public Component getComponent(int i, Component component, ComponentContainer componentContainer) {
//列表项用户界面 (如果可以复用之前的界面,则直接复用)
DirectionalLayout layout = (DirectionalLayout) component;
if (layout == null) {
//如果之前的界面为空,则创建新的列表项用户界面
layout = (DirectionalLayout) LayoutScatter.getInstance(getContext()).parse(ResourceTable.Layout_recycle_item, null, false);
}
//获取列表项中的文本组件
Text text = (Text) layout.findComponentById(ResourceTable.Id_item_text);
//设置列表项数据
text.setText("当前数据:" + getItem(i).toString());
//返回该列表项用户界面
return layout;
}
});
}
在上面的代码中,创建了包含从1到1000整型数据的列表mNumbers作为列表项的数据。在RecycleItemProvider的getCount()方法中,返回了这个列表的长度1000; 在getItem(int i)方法中,返回了当前列表项的整数值; 在getItemId(int i)方法中,返回了当前列表项的ID。此时,在getComponent方法中,即可通过getItem(i)方法获取这个列表项所需要的整数值。
在上面的getComponent方法中,存在一个非常典型的判断方法,即判断可复用组件component是否为空。如果该对象为空,则只能再创建一个新的列表项组件。这充分体现了ListContainer 按需创建组件的优势。随后,通过列表项组件layout 的findComponentById方法获取列表项文本组件,并设置了相应的文本内容。上述代码的最终显示效果如图2所示。

■ 图2 列表组件ListContainer的显示效果
滑动列表,可以发现列表项中的【当前数据:××】(××为具体数字)可以从1到1000变化,整个过程丝滑流畅。
注意: 具有Android开发经验的开发者可以发现,ListContainer相对于Android中的RecyclerView,而RecycleItemProvider的功能非常类似于Android中的Adapter。唯一不同的是,鸿蒙操作系统中不再有ViewHolder概念了。不过对于复杂的需求来讲,开发者也可以创建类似于ViewHolder的类(不继承任何鸿蒙类),专门用于管理用户界面。这样一来,即可将用户界面管理的功能从RecycleItemProvider中解耦出去,在复杂需求场景下还是很实用的。
我正在编写一个包含C扩展的gem。通常当我写一个gem时,我会遵循TDD的过程,我会写一个失败的规范,然后处理代码直到它通过,等等......在“ext/mygem/mygem.c”中我的C扩展和在gemspec的“扩展”中配置的有效extconf.rb,如何运行我的规范并仍然加载我的C扩展?当我更改C代码时,我需要采取哪些步骤来重新编译代码?这可能是个愚蠢的问题,但是从我的gem的开发源代码树中输入“bundleinstall”不会构建任何native扩展。当我手动运行rubyext/mygem/extconf.rb时,我确实得到了一个Makefile(在整个项目的根目录中),然后当
我正在查看instance_variable_set的文档并看到给出的示例代码是这样做的:obj.instance_variable_set(:@instnc_var,"valuefortheinstancevariable")然后允许您在类的任何实例方法中以@instnc_var的形式访问该变量。我想知道为什么在@instnc_var之前需要一个冒号:。冒号有什么作用? 最佳答案 我的第一直觉是告诉你不要使用instance_variable_set除非你真的知道你用它做什么。它本质上是一种元编程工具或绕过实例变量可见性的黑客攻击
在我的应用程序中,我需要能够找到所有数字子字符串,然后扫描每个子字符串,找到第一个匹配范围(例如5到15之间)的子字符串,并将该实例替换为另一个字符串“X”。我的测试字符串s="1foo100bar10gee1"我的初始模式是1个或多个数字的任何字符串,例如,re=Regexp.new(/\d+/)matches=s.scan(re)给出["1","100","10","1"]如果我想用“X”替换第N个匹配项,并且只替换第N个匹配项,我该怎么做?例如,如果我想替换第三个匹配项“10”(匹配项[2]),我不能只说s[matches[2]]="X"因为它做了两次替换“1fooX0barXg
是否有类似“RVMuse1”或“RVMuselist[0]”之类的内容而不是键入整个版本号。在任何时候,我们都会看到一个可能包含5个或更多ruby的列表,我们可以轻松地键入一个数字而不是X.X.X。这也有助于rvmgemset。 最佳答案 这在RVM2.0中是可能的=>https://docs.google.com/document/d/1xW9GeEpLOWPcddDg_hOPvK4oeLxJmU3Q5FiCNT7nTAc/edit?usp=sharing-知道链接的任何人都可以发表评论
我正在尝试修改当前依赖于定义为activeresource的gem:s.add_dependency"activeresource","~>3.0"为了让gem与Rails4一起工作,我需要扩展依赖关系以与activeresource的版本3或4一起工作。我不想简单地添加以下内容,因为它可能会在以后引起问题:s.add_dependency"activeresource",">=3.0"有没有办法指定可接受版本的列表?~>3.0还是~>4.0? 最佳答案 根据thedocumentation,如果你想要3到4之间的所有版本,你可以这
我已经在Sinatra上创建了应用程序,它代表了一个简单的API。我想在生产和开发上进行部署。我想在部署时选择,是开发还是生产,一些方法的逻辑应该改变,这取决于部署类型。是否有任何想法,如何完成以及解决此问题的一些示例。例子:我有代码get'/api/test'doreturn"Itisdev"end但是在部署到生产环境之后我想在运行/api/test之后看到ItisPROD如何实现? 最佳答案 根据SinatraDocumentation:EnvironmentscanbesetthroughtheRACK_ENVenvironm
我有一个正在构建的应用程序,我需要一个模型来创建另一个模型的实例。我希望每辆车都有4个轮胎。汽车模型classCar轮胎模型classTire但是,在make_tires内部有一个错误,如果我为Tire尝试它,则没有用于创建或新建的activerecord方法。当我检查轮胎时,它没有这些方法。我该如何补救?错误是这样的:未定义的方法'create'forActiveRecord::AttributeMethods::Serialization::Tire::Module我测试了两个环境:测试和开发,它们都因相同的错误而失败。 最佳答案
我正在处理旧代码的一部分。beforedoallow_any_instance_of(SportRateManager).toreceive(:create).and_return(true)endRubocop错误如下:Avoidstubbingusing'allow_any_instance_of'我读到了RuboCop::RSpec:AnyInstance我试着像下面那样改变它。由此beforedoallow_any_instance_of(SportRateManager).toreceive(:create).and_return(true)end对此:let(:sport_
我们的git存储库中目前有一个Gemfile。但是,有一个gem我只在我的环境中本地使用(我的团队不使用它)。为了使用它,我必须将它添加到我们的Gemfile中,但每次我checkout到我们的master/dev主分支时,由于与跟踪的gemfile冲突,我必须删除它。我想要的是类似Gemfile.local的东西,它将继承从Gemfile导入的gems,但也允许在那里导入新的gems以供使用只有我的机器。此文件将在.gitignore中被忽略。这可能吗? 最佳答案 设置BUNDLE_GEMFILE环境变量:BUNDLE_GEMFI
这似乎非常适得其反,因为太多的gem会在window上破裂。我一直在处理很多mysql和ruby-mysqlgem问题(gem本身发生段错误,一个名为UnixSocket的类显然在Windows机器上不能正常工作,等等)。我只是在浪费时间吗?我应该转向不同的脚本语言吗? 最佳答案 我在Windows上使用Ruby的经验很少,但是当我开始使用Ruby时,我是在Windows上,我的总体印象是它不是Windows原生系统。因此,在主要使用Windows多年之后,开始使用Ruby促使我切换回原来的系统Unix,这次是Linux。Rub