• 手机 APP 的卖货界面


    需求

    网上买菜的微信小程序,其典型界面是左侧一个列表显示商品分类,右侧一个列表,显示商品明细。左侧列表要显示当前选中的是哪条分类记录(高亮这条记录)。右侧列表滑到底部后,往上再滑一次,自动切换到下一个分类,此时左侧列表的选中画面也同时要更新(高亮下一条记录)。

    使用 Delphi 的实现

    使用 Delphi 做手机 APP,不管是安卓还是 iOS,都是同一套代码,也就是采用 Delphi 的 FireMonkey 框架。

    在 FireMonkey 框架下,要显示列表,比较常见的是 TListBox 控件,和 TListView 控件。

    这里,采用 TListView 控件,比较简单。

    实现方法

    首先搞定数据。不管数据从哪里来,比如现在流行的是服务器有 REST 接口,那么,Delphi 程序可以调用 REST 接口获得 JSON 数据。具体做法这里不讨论。

    获得数据以后,把数据放进 TFdMemTable 里面,用这个内存表来维护数据。

    这里因为有一个分类数据和一个具体商品数据,那么,数据结构大概是:

    分类数据:

    分类编号,类名称

    商品数据:

    分类编号,商品编号,商品名称

    内存表

    那么,拖两个 TFdMemTable 到界面上(当然,应该放到一个 DataModule 里面去,这里仅仅是描述界面实现方法,所以就直接放界面上了),一个 FdMemTable1 用来存放分类数据,一个 FdMemTable2 用来存放商品名称数据。为它创建好字段。然后,鼠标右键点击一个 FdMemTable,在下拉菜单里面选择 Edit DataSet 就会看到一个表格,在这个表格里面填入一些数据。两个 FdMemTable 都填入数据,在设计期,你就已经有数据了。程序运行后,也能看到数据。这样不需要等写完从服务器拉数据的代码,就能看到界面效果。

    界面

    拖两个 ListView 到界面上,第一个 ListView1 的 Align 设置为 Left,让它占满屏幕左侧;第二个 ListView2 的 Align 设置为 Client,让它占满屏幕剩下部分。要想界面美观,设置一下这两个 ListView 的 Margin 的 Left 和 Right 的数字,从默认的 0 改为 2.

    鼠标右键点击随便哪个 ListView ,在弹出来的下拉菜单,选择:Bind Visually,Delphi 的 IDE 会出现 Live Bindings Designer 窗口。在这个窗口里面,对 ListView 和 FdMemTable 拉线,实现在设计期的数据绑定。 这时候,已经可以在设计期的界面上看到 ListView 显示之前在 FdMemTable 里面输入的数据了。运行程序,确实能看到界面数据。

    调试

    在 Delphi 里面开发手机 APP,不必每次都把程序运行到手机上去调试看运行效果。可以选择编译目标是 WIN32,点 Delphi 界面上的绿色三角按钮(播放按钮,也就是运行程序的按钮),直接就在 Windows 里面看到这个程序编译为 Windows 程序运行起来了。同样也能看到 ListView 的效果。这样做比编译发布运行到手机上,时间短很多。

    界面效果

    1. 左侧分类的 ListView1 上面,用户选择(触摸,或者鼠标点击)一条记录,右侧 ListView2 上面要显示对应的分类的商品,而不是显示全部商品;同时左侧要高亮选中的分类条目;

    2. 用户在右侧往上划动,右侧商品列表网上滚动,当滚动到最底一条时,用户再次往上划,左侧分类自动走到下一条分类,右侧的 ListView 显示对应的新的分类的商品列表。

    界面效果实现

    1. 把两个内存表做成主从表。这样,当主表的当前游标指向某条记录,从表跟随主表,自动被过滤,只能看到从表对应的主表的记录;

    2. ListView2 滑到底部后,如何自动让分类表也就是主表走到下一条?

    一. 主从表的实现

    Delphi 里面,两个或者多个 DataSet 可以实现主次关系。实现起来非常简单,网上很多文章有讲到。这里只简单说一下:

    1. 拖一个 DataSource1 到界面上,设置它的 DataSet 属性为 FdMemTable1(也就是商品分类表,主表);

    2. 选择 FdMemTable2 商品列表,在属性窗口里面找到三个属性:

    2.1. MasterSource,设置为 DataSource1;

    2.2. MasterField,设置为【商品分类编号】字段的名字;

    2.3. IndexFieldNames,设置为【商品分类编号】字段的名字。

    到此搞定这两个表的主从表关系。

    二. 显示商品列表的从表的 ListView2 滑到底

    ListView2 滑到底,就执行 FdMemTable1.Next,则 ListView1 上显示的高亮记录自动走到下一条。如果分类走到底,就让它自动回到第一条:

    1. with FdMemTable1 do
    2. begin
    3. Next;
    4. if Eof then First;
    5. end;

    但是,我怎么知道这个 ListView2 已经滑到底了?

    FireMonkey 的 TListView 有个下拉刷新的功能。但这个是下拉,而不是上划。

    如何判断 ListView 的条目往上滚动,已经滚动到最后一条?代码如下:

    1. MyBottom := Self.GetBottomValueByItemCount(FdMemTable2.RecordCount);
    2. if (ListView2.ScrollViewPos = MyBottom) or (MyBottom <= 0) then
    3. begin
    4. //说明滚动到底了
    5. end;
    6. function TForm1.GetBottomValueByItemCount(const ACount: Integer): Integer;
    7. begin
    8. //当前 ListView2.ScrollViewPos 的最大值,滚动到底部的位置
    9. //滚动到最底部的位置就是滚动到最底部时的 ListView2.ScrollViewPos 这个值;
    10. //最底部时,ListView2.ScrollViewPos 刚好等于【条数 X 条高 - ListView 自己的高度】
    11. //如果条数不够,没装满,则这里计算出来的值就是个负数值。刚好装满,则计算出来的值应该是 0 ;
    12. Result := Trunc(Listview2.ItemAppearance.ItemHeight * ACount - ListView2.Height);
    13. end;

    这个已经滑到底的代码,在哪里执行?

    把它放到上划手势的事件里面。

    手势操作

    1. 拖一个 GestureManager1 到界面上,选择 ListView2 的属性面板里面的:Touch -- Gesture Manager 属性,下拉选择为这个 GestureManager1。

    2. ListView2 属性面板里面的 Touch -- Gestures -- Standard -- 勾选 Up,这样当你在 APP 里面上划,就能触发 ListView2 的 OnGesture 事件。

    3. 在 ListView2 的 OnGesture 事件里面写代码,把上述判断是否到底的代码填进去。

    到这里,需求里面描述的效果,已经做出来了。如果是在 Windows 底下测试,你用鼠标拉右侧 ListView2 的滚动条,拉到最底(这里是往下拉),然后,鼠标到 ListView2 里面,按住左键往上移动鼠标,同样会触发手势的 Up 操作,就能看到测试结果。

    这时候,可以把安卓手机插到电脑上,然后在 Delphi 里面直接选择编译目标为这个安卓手机,点击绿色三角形运行箭头,等 Delphi 执行完编译和下载安装 APP,就能看到手机上这个 APP 跑起来了。能够看到两个 ListView 也能看到里面的数据。滑动,确实有效果。

    优化

    这里还有两个问题,让用户体验没那么好

    1. 手指往上划,ListView2 滚动到底部,还没来得及看清楚最后一条,它已经切换分类,显示另外一个分类的商品了;

    2. 切换太快,没留意到它已经切换了。

    解决办法

    一. 不要滑到底就切换

    而是滑到底,能够停下来让用户看清楚商品列表。当用户再次往上划,才切换。

    实现方法:增加一个布尔标志变量,第一次滑到底时它是 False,而 False 时,它不执行切换分类的代码;

    二. 切换放慢

    模仿那些网页版 APP 刷新数据要从远程服务器重新获取数据,有点慢,转个圈,也就是转圈提示用户,商品分类在切换中,商品列表正在更新。

    实现方法:拖一个转圈的控件(AniIndicator1)到界面上,设置它的 Visible 为 False,不要显示。然后在切换商品分类的时候,显示这个转圈,并且暂停1秒到2秒,然后再切换。暂停程序不能让界面冻结,所以暂停2秒要放到一个线程里面去执行。这里我使用 TTask 来执行。

    切换到完整代码如下:

    1. function TForm1.GetBottomValueByItemCount(const ACount: Integer): Integer;
    2. begin
    3. //当前 ListView2.ScrollViewPos 的最大值,滚动到底部的位置
    4. //滚动到最底部的位置就是滚动到最底部时的 ListView2.ScrollViewPos 这个值;
    5. //最底部时,ListView2.ScrollViewPos 刚好等于【条数 X 条高 - ListView 自己的高度】
    6. //如果条数不够,没装满,则这里计算出来的值就是个负数值。刚好装满,则计算出来的值应该是 0 ;
    7. Result := Trunc(Listview2.ItemAppearance.ItemHeight * ACount - ListView2.Height);
    8. end;
    9. procedure TForm1.ListView2Gesture(Sender: TObject; const EventInfo:
    10. TGestureEventInfo; var Handled: Boolean);
    11. var
    12. MyBottom: Integer;
    13. begin
    14. //('手势');
    15. MyBottom := Self.GetBottomValueByItemCount(FdMemTable2.RecordCount);
    16. if (ListView2.ScrollViewPos = MyBottom) or (MyBottom <= 0) then
    17. begin
    18. if not FGoBottom then
    19. begin
    20. FGoBottom := not FGoBottom;
    21. Exit; //第一次拉到底,不要走。第二次拉到底,才算用户要刷新。
    22. end;
    23. //这样直接切换,太突然,视觉上感觉不好。可以考虑切换代码放到一个动画结束后。
    24. TTask.Run(
    25. procedure
    26. begin
    27. TThread.Synchronize(nil,
    28. procedure
    29. begin
    30. AniIndicator1.Visible := True;
    31. AniIndicator1.Enabled := AniIndicator1.Visible; //显示转圈圈
    32. end
    33. );
    34. Sleep(1500);
    35. TThread.Synchronize(nil,
    36. procedure
    37. begin
    38. FdMemTable1.Next;
    39. if FdMemTable1.Eof then FdMemTable1.First;
    40. FGoBottom := not FGoBottom;
    41. AniIndicator1.Enabled := not AniIndicator1.Enabled;
    42. AniIndicator1.Visible := AniIndicator1.Enabled; //转圈隐藏不显示
    43. end
    44. )
    45. end
    46. );
    47. end;
    48. end;

    说了那么多,代码就这几行。这就是用 Delphi 做界面比较厉害的地方。大部分的代码不需要写,仅仅在设计期鼠标点几下做点设置就搞定。这就是现在流行的所谓【Low code】

    进阶

    TListView 的条目的内容,本身可以做得很复杂,比如包含商品名称,商品价格,商品图片,等等。这个也可以在设计期搞定而无需写代码。具体如何搞,请翻一翻本博客前面的文章。

  • 相关阅读:
    基于springboot的图书管理系统源码数据库
    有什么好的开源自动化测试框架可以推荐?
    2024年去除视频水印的5种方法
    Java 枚举(enum)学习笔记
    PyQt学习随笔:QStackedWidget堆叠窗口属性与使用
    【LeetCode】55. 跳跃游戏 - Go 语言题解
    The significance of void 0 in JS
    MySQL之SQL语句优化
    【sfu】rtc 入口
    17、生成长图,并上传至服务器
  • 原文地址:https://blog.csdn.net/pcplayer/article/details/126945466