【UE·UI篇】ListView使用经验总结
本文总结了我在使用ListView时遇到的一些问题和经验,以及如何解决这些问题。源码源码
在使用ListView时,解析需要了解数据和表现是普通分开的,数据是源码源码UObject类型,表现是解析影视开源码搭建UWidget类型。ListView会动态填充需要显示的普通数据给UI,因此在子物体(继承IUserObjectListEntry接口)的源码源码代码逻辑里修改数据必定无效。
关于ListView的解析滚动条,可以通过调用SetScrollBarVisibility方法来控制其显示状态。普通若需在蓝图中预览,源码源码可连上EventPreConstruct事件。解析然而,普通ListView自带的源码源码滚动条样式不支持自定义,需要通过改源码或从头写一个ListView来实现。解析
若想实现鼠标左键滚动,可以使用方法1或方法2,具体取决于ListView、TileView、ScrollBox等组件的使用情况。若遇到子物体(如Button)导致ListView无法滚动的问题,可以尝试将Button的Interaction里的ClickMethod和TouchMethod设置为Precisexxx选项。
更新界面时,不能在EntryWidget中修改数据。解决方法是使用其他方式更新数据,如触发事件、调用方法等。
关于使用Touch实现鼠标左键滑动功能的思考,源于对源码的深入探索。通过查找STableViewBase类的函数,发现左键点击不会触发OnMouseEnter函数,js 埋码源码而会触发OnTouch相关函数。因此,通过使用Touch实现鼠标左键滑动的思路得以形成。最终,通过搜索相关关键词,找到了实现答案。
总结来说,开源资源对于解决问题非常有价值。在遇到困难时,深入研究源码和搜索相关关键词往往能提供有效的解决方案。
å¦ä½å¨ListViewä¸åµå¥ListView
å¦æä½ æ¯æ³å®ç°ç±»ä¼¼QQ微信ç好ååç»çè¯,é£éº»ç¦ä½ ç¨ExpandableListè¿ä¸ªå¹¶ä¸æ¯ListViewéåµå¥ListView
å¦æä½ éè¦åµå¥,ä¹ä¸æ¯ä¸è¡,åListViewå¿ é¡»è¦è®¾ç½®å®çé«åº¦,å³è¦å±å¼ææitem,å¦åæ»å¨ä¸äºç
å±å¼ææitemçListView,æè¿æç°æç,å¨xmléç¨è¿ä¸ªå»å£°æå§
QML ListView几个常用且非常重要的属性
用 QML 开发界面的好处想必就不用多说了吧,可以总结为一个字:爽。
的确如此,用 QML 可以快速的开发出一些非常酷炫和复杂的界面,并且代码还非常简洁,可读性很强,容易理解。
今天要总结的是关于 ListView 中的一些常用的属性,非常实用,不过如果是刚接触 ListView 的话,可能会有些陌生(本人刚开始用的时候也是,然后翻遍了 Qt 帮助文档),所以这里汇总一些最常用的属性并介绍其用法。
类似相片浏览
相片浏览的场景相信大家都非常熟悉了,在手机中打开相册浏览相片的时候,通过左右滑动可以一张张的浏览相片,那么如果用ListView如何来实现呢,来看个简单的 demo
效果图:
代码很简单,最重要的两句话:
第一句是将切换模式设置为单张切换;
第二句是将列表改成水平浏览模式。
currentIndex动态切换
不知道大家有没有遇到过这种情况,qt源码编译5.1.0当切换ListView的item的时候,currentIndex并不会跟随着变化,来看个示例:
那么,如何将在我们切换 item 的时候将 currentIndex 也跟随着变化呢?只需要添加一句:
再看看效果图:
为什么要特意将 currentIndex动态切换提出来单独说呢,有时候这个功能会特别有用,特别是我在开发相片浏览功能的时候,比如我们在切换 item 后需要用到当前的 currentIndex,这时候如果 index 不跟随变化那就麻烦了。之前网上有人的做法是,在 delegate 中添加 MouseAera中,响应点击事件然后手动currentIndex ,这种方法存在很多弊端。
禁止列表首尾滑动
这个标题可能有些拗口,意思其实就是,当列表在首页或尾页的时候,如果再继续滑动会有回弹的效果。
先看个效果图:
但是有些设计中可能并不需要这种默认的设置,那么该如何修改呢,其实很简单,加上这句话:
这其实就是设置在边缘的时候不能再滑动,再看一下效果:
设置最大滑动速度
不知道大家在做 QML for Android 开发的时候有没有遇到过这种问题,特别是前几年 Qt 低版本的时候,使用 ListView 编译到 Android 手机上,列表滑动特别不自然,滑动速度特别快(也和手机屏幕像素有关),和平台的兼容性没有做得很好,幸好随着 Qt 版本的升级,解决了这个问题,不过有时候还是需要自己控制最大的滑动速度,设置属性如下:
这里的直播平台发展源码数值单位是: 像素/秒
如果不做设置,会默认跟随平台特性来执行。如果要自己定义这个滑动速度,需要多调试一下,找到适合的滑动效果。
添加 header
ListView 可以设置 headerItem,话不多说,直接看效果图吧
和 header 对应的还有一个 footer,原理是一样的,只是显示的位置是在底部。这里就不多介绍了。
下拉刷新
ListView列表下拉刷新是最常见的 场景了吧,这里为了演示做一个简单的 demo,先来看看效果
源码:
OK,暂且介绍这几种最常用的功能,写得不好的地方请多指出。
Android-RecyclerView原理
ListView的基本实现与优化
ListView是Android中实现列表最简单的方法,通过Adapter可以将数据转换为视图。为了优化性能,可以充分利用ListView的缓存机制。ListView内部有两层缓存,第一层是一级缓存,主要缓存当前屏幕内的View。第二层是二级缓存,当一级缓存中找不到时,会从缓存池中查找可复用的ItemView。当滑动到特定位置,ListView会将当前position位置的ItemView作为参数传给getView,此时可以复用ItemView,仅需更新数据,而不需重新创建View。如何阅读源码解析
RecyclerView的特性与优势
尽管ListView已有两层缓存机制,但Android开发团队依然开发了RecyclerView。RecyclerView提供了更高效的性能优化,如避免在一次数据更新时执行两次layout,这将减少多次调用getView的情况。此外,RecyclerView在创建视图和数据绑定上提供了更简洁的API,如onCreateViewHolder和onBindViewHolder,使用缓存时只需调用onBindViewHolder来更新数据,无需执行onCreateViewHolder。这使得数据渲染过程与视图创建过程分离,提高了代码的复用性和性能。
性能与动画
RecyclerView在性能和动画效果方面具有明显优势。其动画效果API使ItemView的动画变得简单,而ListView则需要在每个ItemView中单独设置。在动画处理方面,RecyclerView也更为高效。另外,RecyclerView支持局部刷新,而非ListView的全局刷新。这在性能上更为优越,尤其是在处理如Feed流这样的大量数据时,局部刷新能显著减少性能开销。
自定义缓存与数据同步
在数据同步和自定义缓存方面,RecyclerView提供了更多灵活性。它拥有四级缓存机制,使用者可以根据不同的场景进行缓存优化,从而达到更好的性能效果。此外,通过自定义缓存扩展(ViewCacheExtension),开发人员可以针对特定需求实现更个性化的缓存策略。
RecyclerView的简单使用与源码解析
使用RecyclerView时,首先在布局中添加一个RecyclerView,然后定义每个Item的数据类和Adapter。设置RecyclerView的布局管理器和Adapter。当需要局部刷新时,只需调用RecyclerView的局部刷新API,其他位置的ViewHolder不会执行onCreateViewHolder和onBindViewHolder。
从Adapter到RecyclerView的刷新逻辑
当调用Adapter的notify系列方法时,RecyclerView会自动触发刷新。这源于AdapterDataObservable对象的使用,它是Adapter中的可观察对象,监听数据变更并触发监听回调。在Adapter的registerAdapterDataObserver方法中,RecyclerView会将其注册为观察者,确保在数据变更时能够自动刷新UI。
刷新过程解析
当数据变更时,RecyclerView内部会调用mObserver对象的onXXXChanged系列方法。这些方法通常会调用requestLayout来主动触发布局刷新。在requestLayout过程中,会调用View的onMeasure和onLayout方法,从而触发布局更新。通过深入分析RecyclerView源码,可以了解到整个刷新过程中的缓存机制、ViewHolder的复用和数据绑定流程,以及如何实现动画效果和局部刷新。
在解析RecyclerView源码时,重点关注AdapterDataObservable、mObserver、mLayout以及其子类(如LinearLayoutManager)的实现细节。通过理解这些核心组件的工作原理,可以更好地掌握RecyclerView的高级特性和优化技巧。
Android自定义控件之像ListView一样使用RecyclerView - 自定义控件属性
通过分析,我们了解到ListView在XML文件中通过定义属性实现诸如分隔条、分隔条高度以及使用string数组作为数据源等特性。在strings.xml文件中定义string数组,然后引用其name作为android:entries属性值,实现数据源设置。
为了深入理解ListView的源码处理,我们在项目列表中切换到Project视图,查看所有依赖的库和编译平台。在res\values\attrs.xml文件中,系统定义了所有控件的自定义属性,通过搜索"ListView"找到相关的定义。其中,entries属性引用了已有的定义以解决同名属性冲突问题。
进一步,我们查看了ListView的源码,特别是其构造方法。在处理entries属性时,通过TypedArray对象获取自定义属性,使用getTextArray方法获取字符串数组。若未定义,则返回null。之后,创建ArrayAdapter对象将数组作为数据源设置给Adapter,并绑定至R.layout.simple_list_item_1布局中的TextView,最后调用setAdapter方法。
ArrayAdapter是用于将数据列表绑定至item布局中的TextView,系统提供了此类以方便开发者使用ListView适配器。除了ArrayAdapter,还有SimpleAdapter和CursorAdapter等。
divider属性通过getDrawable方法获取Drawable对象,然后调用setDivider方法设置分隔线。
为了使RecyclerView具备类似功能,我们直接复制并粘贴ListView的自定义属性声明至attrs.xml中。然而,在进行编译时,发现与系统控件同名属性冲突。为解决此问题,我们为自定义属性前加上前缀"android:"并去除"format",再次编译时错误消除。
然而,这种解决方案导致在使用自定义属性时,Android Studio无法提供提示。为兼容性和提示性,我们再次定义属性,修改为:
这样做后,Android Studio将提供属性值选择提示。
layoutinflater.inflate å view.inflate çåºå«
å¹³æ¶ListViewå è½½itemä¸ï¼adapterçgetViewæ¹æ³ä¸ï¼æ们ç»å¸¸ç¨å°ï¼
LayoutInflater.from(mContext).inflate(R.layout.it,parent,false);
è¿æ ·çæ¹æ³æ¥å è½½å¸å±xml,å¹³æ¶ä¸ç´å°±æ¯è¿ä¹ç¨çï¼ä¹æ²¡ä»ä¹çé®ãä»å¤©ç½ä¸çäºä¸ªèªå®ä¹å¸å±çæºç ï¼èªå®ä¹å¸å±ä¸å è½½å¸å±xmlç¨çView.inflateæ¹æ³ï¼
public class SettingItemView extends RelativeLayout {
private CheckBox cb_status;
private TextView tv_description;
private TextView tv_title;
private String desc_on;
private String desc_off;
private void iniView(Context context) {
View.inflate(context, R.layout.setting_item_view, this);//第ä¸ä¸ªåæ°ä¼ å¸å±æ件çç¶ç±»
cb_status=(CheckBox) this.findViewById(R.id.cb_status);
tv_description=(TextView) this.findViewById(R.id.tv_description);
tv_title=(TextView) this.findViewById(R.id.tv_title);
}
第ä¸æ¬¡è§ç¨è¿ç§æ¹å¼æ¥å è½½å¸å±çï¼çäºä¸ä»çlistviewå è½½item,ä¹æ¯ç¨è¿ç§æ¹å¼ï¼
@Override
public View getView(final int position, View convertView, ViewGroup parent) {
View view;
ViewHolder holder;
if(convertView==null){
view=View.inflate(getApplicationContext(), R.layout.list_item_callsms, null);//æåä¸ä¸ªä¼ äºnull
holder=new ViewHolder();
holder.tv_number=(TextView) view.findViewById(R.id.tv_black_number);
holder.tv_mode=(TextView) view.findViewById(R.id.tv_black_mode);
holder.iv_delete=(ImageView) view.findViewById(R.id.iv_delete);
view.setTag(holder);
好å§ï¼çä¸ä¸View.inflateç说æï¼
Open Declaration View android.view.View.inflate(Context context, int resource, ViewGroup root)
Inflate a view from an XML resource. This convenience method wraps the
LayoutInflater class, which provides a full range of options for view
inflation.
Parameters: context The Context object for your activity or
application. resource The resource ID to inflate root A view group
that will be the parent. Used to properly inflate the layout_
*parameters.
See Also: LayoutInflater
æåæä¸å¥è®©ä½ çLayoutInflaterè¿ä¸ªç±»ï¼æçå®å é¨ä¹æ¯ç¨LayoutInflaterå®ç°çï¼è¿å ¥æºç ï¼
public static View inflate(Context context, int resource, ViewGroup root) {
LayoutInflater factory = LayoutInflater.from(context);
return factory.inflate(resource, root);
}
æç¶å é¨ä¹æ¯ç¨LayoutInflaterå®ç°çï¼ä¸ç¥é为å¥androidè¿è¦ç¨View.inflatå°è£ ä¸ä¸ããão(ãââ½âã)o
å ¶ä¸LayoutInflaterçInflateçä¸ä¸ªåæ°ææ为ï¼
对äºInflateçä¸ä¸ªåæ°(int resource, ViewGroup root, boolean attachToRoot)
å¦æinflate(layoutId, null )ålayoutIdçæå¤å±çæ§ä»¶ç宽é«æ¯æ²¡æææç
å¦æinflate(layoutId, root, false ) å认为åä¸é¢æææ¯ä¸æ ·ç
å¦æinflate(layoutId, root, true ) å认为è¿æ ·çè¯layoutIdçæå¤å±æ§ä»¶ç宽é«æè½æ£å¸¸æ¾ç¤º
对è¿ä¸ä¸ªåæ°åºå«ä¸ç解çè¯å¯ä»¥çè¿ç¯æç« ï¼
inflate第ä¸ä¸ªåæ°ææ
ä»æºç è§åº¦è§£æçæé大ç¥çï¼
Android LayoutInflateråçåæï¼å¸¦ä½ ä¸æ¥æ¥æ·±å ¥äºè§£View(ä¸)
以åå¦ä¸ç¯æè§å¾ä¸éçï¼
Android LayoutInflate深度解æ ç»ä½ 带æ¥å ¨æ°ç认è¯
çå®ï¼ä½ åºè¯¥ç¥éè¿ä¸ªåæ°ææäºï¼okï¼åæ¥çä¸é¢ä»£ç ï¼ è¿æ¶å°±å¯ä»¥æ¿æ¢ä¸ºlayoutInflaterçæ¹å¼äºï¼
对äºç¬¬ä¸ä¸ªèªå®ä¹å¸å±ï¼
//View.inflate(context, R.layout.setting_item_view, this);//第ä¸ä¸ªåæ°ä¼ å¸å±æ件çç¶ç±»
LayoutInflater.from(context).inflate(R.layout.setting_item_view, this, true);//çä»·äºä¸é¢
第äºä¸ªéé å¨ä¸getViewï¼
//view=View.inflate(getApplicationContext(), R.layout.list_item_callsms, null);
view=LayoutInflater.from(getApplicationContext()).inflate(R.layout.list_item_callsms,parent,false);
VBçListviewä¿å读å
å®æ´ä»£ç 注ï¼Listviewå®ä¹äº4个å,èªå·±éè¦å åæ¹ä¸å°±å¥½Option Explicit
'读åINIæ件
Private Declare Function GetPrivateProfileString Lib "kernel" Alias "GetPrivateProfileStringA" (ByVal lpApplicationName As String, ByVal lpKeyName As Any, ByVal lpDefault As String, ByVal lpReturnedString As String, ByVal nSize As Long, ByVal lpFileName As String) As Long
Private Declare Function WritePrivateProfileString Lib "kernel" Alias "WritePrivateProfileStringA" (ByVal lpApplicationName As String, ByVal lpKeyName As Any, ByVal lpString As Any, ByVal lpFileName As String) As LongPrivate Sub Form_Load()
Randomize
End SubPrivate Sub éæºå¼_Click()
Dim i As Integer, j As Integer, lv As ListItem
ListView1.ListItems.Clear
For i = 0 To
Set lv = ListView1.ListItems.Add(, , i + 1)
For j = 0 To 2
lv.ListSubItems.Add , , CInt(Rnd * )
Next j
Next i
End SubPrivate Sub ä¿å_Click()
Dim si As Integer, sj As Integer, tmpstr As String
If ListView1.ListItems.Count = 0 Then MsgBox "没æå¯ä¿åçå 容", vbInformation, "æ示": Exit Sub
tmpstr = ListView1.ListItems.Count
WritePrivateProfileString "ç»è®¡é¡¹", "æ»è¡æ°", tmpstr, App.Path & "\ABC.ini"
tmpstr = ListView1.ListItems(1).ListSubItems.Count
WritePrivateProfileString "ç»è®¡é¡¹", "æ»åæ°", tmpstr, App.Path & "\ABC.ini"
With ListView1.ListItems
For si = 1 To .Count
WritePrivateProfileString "第" & si & "è¡", "第1å", .Item(si).Text, App.Path & "\ABC.ini"
For sj = 1 To .Item(si).ListSubItems.Count
WritePrivateProfileString "第" & si & "è¡", "第" & sj + 1 & "å", .Item(si).SubItems(sj), App.Path & "\ABC.ini"
Next sj
Next si
End With
MsgBox "ä¿åæåï¼", vbInformation, "æ示"
End SubPrivate Sub 读å_Click()
Dim sr As Integer, sc As Integer, li As Integer, lj As Integer, tmptxt As String, lvs As ListItem
tmptxt = Space$()
GetPrivateProfileString "ç»è®¡é¡¹", "æ»è¡æ°", "0", tmptxt, , App.Path & "\ABC.ini"
sr = CInt(tmptxt) 'æ»è¡æ°
tmptxt = Space$()
GetPrivateProfileString "ç»è®¡é¡¹", "æ»è¡æ°", "0", tmptxt, , App.Path & "\ABC.ini"
sc = CInt(tmptxt)
If sr = 0 Then
MsgBox "æ»è¡æ°ä¸ºé¶å¦"
Exit Sub
Else
ListView1.ListItems.Clear
End If
For li = 1 To sr
For lj = 1 To sc 'è¿éä½ çå表
tmptxt = Space$()
GetPrivateProfileString "第" & li & "è¡", "第" & lj & "å", "", tmptxt, , App.Path & "\ABC.ini"
If lj = 1 Then
Set lvs = ListView1.ListItems.Add(, , tmptxt)
Else
lvs.ListSubItems.Add , , tmptxt
End If
Next lj
Next li
End Sub
GridView 你怎么那么皮——从需求出发,如何让 GridView 的尺寸不再难以控制
在开发过程中,ListView 和 GridView 是常用的控件。它们用于绘制列表和展示瀑布流式的宫格布局。每个item都是独立的布局,开发者可以自定义其尺寸。在Android中,可以通过父控件尺寸约束和item间关系动态调整尺寸。而在Flutter的ListView和GridView中,我们也遇到了类似的问题。
我在做平板项目时,尝试使用GridView展示账单列表,每个账单需指定宽高。由于GridView没有设置宽高的属性,我试图固定item的宽高,但显示效果不符合预期。查阅资料后,我了解到childAspectRatio属性,但设置后仍然与预期不符。多次使用GridView后,体验不佳,感觉像是在抓泥鳅,非常不舒服。
为了更好地理解GridView的工作原理,我打开了源码。GridView继承于BoxScrollView,有多个构造方法,其中的核心在于构造SliverGridDelegate和SliverChildDelegate两个对象。这些对象共同构建了GridView的核心逻辑。
在SliverGrid中,childrenDelegate用于预测最大滚动距离,而gridDelegate则处理item的布局。buildChildLayout方法将这些对象整合,构建出SliverGrid对象。此对象被层层封装后,最终显示在视图树中。
SliverChildBuilderDelegate的初始化过程是构建item的关键步骤。通过这个代理构造函数,我们可以控制item的构建过程。sliverGrid最终在层层封装后显示,但真正的构建过程发生在childrenDelegate的初始化阶段。
通过分析GridView的源码,我们了解到childrenDelegate和gridDelegate在构建过程中的作用。childrenDelegate帮助处理item的绘制和布局,gridDelegate负责尺寸测绘。这两个对象协同工作,使GridView具有动态布局的能力。
回到最初的问题,是否可以编写一个固定宽高,剩余空间均匀分布的GridView呢?通过上面的分析,我们知道gridDelegate是布局的关键。我们可以通过继承SliverGridDelegate并重写其布局逻辑来实现。
参考SliverGridDelegate的子类SliverGridDelegateWithFixedCrossAxisCount和SliverGridDelegateWithMaxCrossAxisExtent,我们可以使用自定义的GridDelegate构建界面。这样,我们就能够实现固定宽高,剩余空间均匀分布的效果。
总结而言,GridView通过两个助手childrenDelegate和gridDelegate协同工作,实现了动态的布局。通过深入理解其源码,我们可以更好地控制GridView的行为,实现更多定制化的功能。