通常,我们会在App首页的滚动控件上放一个ImageListViewer,

滚动控件可以是ScollBox,ListBox或ListView,

它们都具有上下滑动的功能,

而ImageListViewer具有左右切换图片的功能,

这样一来,当我在ImageListViewer左右切换图片时,

很容易造成滚动控件的上下滑动,

十分影响用户体验,

 

正确的效果应该是,

我在ImageListViewer上垂直切换时滚动控件才会上下滑动,

其他情况下,ImageListViewer都应该能切换图片,

 

如何解决这种手势冲突呢?

 

所有滚动控件都是从ScrollControl继承的,

ScrollControl有两个手势管理组件,

VertControlGestureManager和HorzControlGestureManager,

VertControlGestureManager用来处理上下滑动,上下惯性滚动等,

我们需要把VertControlGestureManager.IsNeedDecideFirstGestureKind设置为True,

IsNeedDecideFirstGestureKind为True了之后,

表示每次开始手势滑动ScrollControl时,都需要先滑动一段距离,

然后VertControlGestureManager判断出滑动的方向为上下垂直方向之后,

才响应上下滚动,

当ScrollControl上面放了ImageListViewer之后,

只需要手势滑动在ImageListViewer上时需要判断上下垂直方向,

在ImageListViewer之外的地方则不需要判断,

如下箭头所示:

那就要编写VertControlGestureManager.OnPrepareDecidedFirstGestureKind事件,

这个事件会传入鼠标滑动所在的位置,

如果鼠标滑动位置在ImageListViewer中,

那么需要判断上下垂直方向,

如果不在ImageListViewer中,那么不需要判断方向,

ScrollBox处理的代码如下:

procedure TFrameHome.DoScrollBoxVertManagerPrepareDecidedFirstGestureKind(

Sender: TObject; AMouseMoveX, AMouseMoveY: Double;

var AIsDecidedFirstGestureKind: Boolean;

var ADecidedFirstGestureKind: TGestureKind);

var

APlayerOriginPoint:TPointF;

AFirstItemRect:TRectF;

begin

//传给ScrollBox的是相对窗体的绝对坐标

//广告轮播Item的绘制区域

APlayerOriginPoint:=PointF(0,0);

APlayerOriginPoint:=imgPlayer.LocalToAbsolute(APlayerOriginPoint);

AFirstItemRect:=RectF(APlayerOriginPoint.X,APlayerOriginPoint.Y,

APlayerOriginPoint.X+Self.imgPlayer.Width,

APlayerOriginPoint.Y+Self.imgPlayer.Height

);

if PtInRect(AFirstItemRect,PointF(AMouseMoveX,AMouseMoveY)) then

begin

//在广告轮播控件内,那么要检查初始手势方向

end

else

begin

//不在在广告轮播控件内,那么随意滑动

//AIsDecidedFirstGestureKind表示我已经确定好方向了,不需要再判断了

//ADecidedFirstGestureKind表示判断好的方向

AIsDecidedFirstGestureKind:=True;

ADecidedFirstGestureKind:=TGestureKind.gmkVertical;

end;

end;

 

初始设置代码如下:

Self.sbClient.Prop.VertControlGestureManager.IsNeedDecideFirstGestureKind:=True;

Self.sbClient.Prop.VertControlGestureManager.OnPrepareDecidedFirstGestureKind:=

Self.DoScrollBoxVertManagerPrepareDecidedFirstGestureKind;

 

 

ListBox处理的代码如下:

procedure TFrameShop.DoListBoxVertManagerPrepareDecidedFirstGestureKind(

Sender: TObject; AMouseMoveX, AMouseMoveY: Double;

var AIsDecidedFirstGestureKind: Boolean;

var ADecidedFirstGestureKind: TGestureKind);

var

AFirstItemRect:TRectF;

begin

//广告轮播Item的绘制区域

AFirstItemRect:=Self.lbHome.Prop.Items[0].ItemDrawRect;

if PtInRect(AFirstItemRect,PointF(AMouseMoveX,AMouseMoveY)) then

begin

//在广告轮播控件内,那么要检查初始手势方向

end

else

begin

//不在在广告轮播控件内,那么随意滑动

AIsDecidedFirstGestureKind:=True;

ADecidedFirstGestureKind:=TGestureKind.gmkVertical;

end;

end;

 

初始设置代码如下:

Self.lbHome.Prop.VertControlGestureManager.IsNeedDecideFirstGestureKind:=True;

Self.lbHome.Prop.VertControlGestureManager.OnPrepareDecidedFirstGestureKind:=

Self.DoListBoxVertManagerPrepareDecidedFirstGestureKind;

 

 

 

先拖一个SkinImageList在窗体上,

取名为imglistPlayer,

添加如下6张图片:

再放一个ImageListViewer在窗体上,

Align设置为Top,Height设置为150,

然后把imglistPlayer设置给ImageListViewer.Properties.Picture.SkinImageList,

ImageListViewer.Properties.ImageListAnimated设置为True,

ImageListViewer.SelfOwnMaterial.DrawPictureParam.IsStretch设置为True,

效果如下:

接下来拖一个ButtonGroup在ImageListViewer中,

因为我们这次是6张图片,需要6个按钮的宽度,

我们把ButtonGroup的Height设置为20,

ButtonGroup.Properties.ButtonSize设置为了20,

表示每个按钮的宽度为20,

ButtonGroup.Properties.ButtonSizeCalcType设置为bsctFixed,

表示每个按钮的尺寸都统一,

Width设置为20*6=120,

并把它的Anchors属性设置为[akTop,akRight],

让它适应不同尺寸的手机,

ImageListViewer绑定ButtonGroup之后,

会根据SkinImageList中的图片个数来创建按钮,

并且给自动创建的按钮设置点击事件,

点击上面的按钮自动切换到对应的图片,

 

接下来设置ButtonGroup上按钮的素材,

ButtonGroup中有三个类型的按钮素材,

FirstButtonMaterial:第一个按钮的素材,

MiddleButtonMaterial:中间按钮的素材,

LastButtonMaterial:最后一个按钮的素材,

按钮的默认素材在ButtonGroup.SelfOwnMaterial.MiddleButtonMaterial中设置,

MiddleButtonMaterial.IsTransparent设置为False,

MiddleButtonMaterial.BackColor.IsFill设置为True,

MiddleButtonMaterial.BackColor.IsRound设置为True,

MiddleButtonMaterial.BackColor.RoundWidth设置为10,

MiddleButtonMaterial.BackColor.RoundHeight设置为10,

如下图所示:

效果如下:

ImageListViewer每次切换图片的时候,

会把ButtonGroup上对应序号的Button的IsPushed属性设置为True,

来指示出当前显示的是第几张图片,

因此要设置ButtonGroup.MiddleButtonMaterial.BackColor.PushedEffect效果,

PushedEffect.EffectTypes中的drpetFillColorChange打勾,

PushedEffect.FillColor设置为黑色,

如下图所示:

效果如下:

 

 

 

 

 

 

 

在1.70以前的版本中,如果要实现下拉刷新面板,

需要拖一个PullLoadPanel在窗体上,

然后把它设置给ListBox.VertScrollBar.Prop.MinPullLoadPanel,

再在PullLoadPanel的OnExecuteLoad事件中写下拉刷新事件,

给用户造成很大的不便,

因此在1.70版本中,

加入了自动下拉刷新和上拉加载更多的功能,

原理还是使用PullLoadPanel,只不过放在控件中自动创建而已,

 

ListBox.Properties添加了如下属性:

EnableAutoPullDownRefreshPanel:表示启用自动下拉刷新面板的功能,

EnableAutoPullUpLoadMorePanel:表示启用自动上拉加载更多面板的功能,

PullDownRefreshPanel:自定义的下拉刷新的面板

PullUpLoadMorePanel:自定义的上拉加载更多的面板

 

ListBox.SelfOwnMaterial添加了如下属性:

PullDownRefreshPanelMaterial:自动下拉刷新面板的素材

PullUpLoadMorePanelMaterial:自动上拉加载更多面板的素材

 

ListBox添加了两个事件:

OnPullDownRefresh是下拉刷新事件,

OnPullUpLoadMore是上拉加载更多事件,

 

ListBox.Properties添加了如下方法:

StartPullDownRefresh:开始下拉刷新

StartPullUpLoadMore:开始上拉加载更多

StopPullDownRefresh:结束下拉刷新

StopPullUpLoadMore:结束上拉加载更多

 

 

接下来举例说明,

先准备如下ListBox,

选中该ListBox,然后在设计期用鼠标滚轮滚动,列表项会下移,

这是用来模拟在运行时手势下拉刷新的方法,

但此时并不会出现下拉刷新面板,

我们把EnableAutoPullDownRefreshPanel打上勾,

表示启用自动下拉刷新面板的功能,

如下图所示:

再选中该ListBox,然后在设计期用鼠标滚轮滚动,就会出现下拉刷新面板,

如下图所示:

在设计时下拉刷新面板会出现三秒,然后自动消失,

这个下拉刷新面板是根据ListBox.SelfOwnMaterial.PullDownRefreshPanelMaterial素材而自动创建的PullLoadPanel控件,

在PullDownRefreshPanelMaterial素材中:

LoadingCaption为下拉面板正在刷新时的标题,默认为”正在刷新”

DecidedLoadCapton为下拉面板拉到可以刷新时的标题,默认为”松开刷新”

UnDecideLoadCaption为下拉面板拉到还不可以刷新时的标题,默认为”下拉刷新”

DrawLoadingCaptionParam为绘制下拉面板标题的字体绘制参数,

LoadingPicture为滚动图片,

当指定一张图片时,会自动旋转这张图片来表示正在加载

当指定SkinImageList时,会循环播放ImageList中的所有图片来表示正在刷新,

IndicatorColor为自动创建的滚动图片的颜色,默认为黑色,

当LoadingPicture为空时,会使用IndicatorColor来创建十二张滚动图片

然后这十二张滚动图片组成一个SkinImageList赋给下拉面板上的Image,

在下拉刷新时循环播放这十二张图片表示正在刷新

 

目前我们只需要把所有属性保持默认即可,

然后运行看一下效果:

下拉一小段距离

下拉一大段距离

松开手指

接下来还需要在下拉刷新时从网络获取数据,

在OnPullDownRefresh事件中启动线程,

在线程中执行从网络获取数据,

在线程结束后把从网络获取到的数据加载到ListBox中,

 

 

先放一个线程任务事件组件TTimerTaskEvent,

在它的OnExecute中编写从网络获取数据,

代码如下:

procedure TFrameListBox_AutoPullDownRefresh.TimerTaskEvent1Execute(

Sender: TObject);

begin

//模拟从网络获取数据

Sleep(3000);

end;

注意:OnExecute事件在线程中运行,

因此不能在此事件中访问UI组件或设置UI组件。

如果取数据不放在线程中执行,会造成界面无响应

 

 

再在它的OnExecuteEnd中编写把从网络获取到的数据加载到ListBox中,

代码如下:

procedure TFrameListBox_AutoPullDownRefresh.TimerTaskEvent1ExecuteEnd(

Sender: TObject);

var

APicServerUrl:String;

AListBoxItem:TSkinListBoxItem;

begin

//把从网络获取到的数据加载到ListBox中

//加载

Self.lbMultiPic.Prop.Items.BeginUpdate;

try

//清空列表项

Self.lbMultiPic.Prop.Items.Clear(True);

 

//图片服务器链接地址

APicServerUrl:=’http://www.orangeui.cn/download/’

+’testdownloadpicturemanager/mobileposthumbpic/’;

 

 

//添加列表项

AListBoxItem:=Self.lbMultiPic.Prop.Items.Add;

AListBoxItem.Caption:=’阿尔代雪赤霞珠银标’;

AListBoxItem.Icon.Url:=APicServerUrl+’阿尔代雪赤霞珠银标.jpg’;

 

AListBoxItem:=Self.lbMultiPic.Prop.Items.Add;

AListBoxItem.Caption:=’阿尔岱雪丹娜斯’;

AListBoxItem.Icon.Url:=APicServerUrl+’阿尔岱雪丹娜斯.jpg’;

 

AListBoxItem:=Self.lbMultiPic.Prop.Items.Add;

AListBoxItem.Caption:=’安溪铁观音’;

AListBoxItem.Icon.Url:=APicServerUrl+’安溪铁观音.jpg’;

 

AListBoxItem:=Self.lbMultiPic.Prop.Items.Add;

AListBoxItem.Caption:=’娃哈哈茶咖’;

AListBoxItem.Icon.Url:=APicServerUrl+’娃哈哈茶咖.jpg’;

 

AListBoxItem:=Self.lbMultiPic.Prop.Items.Add;

AListBoxItem.Caption:=’娃哈哈猫缘咖啡’;

AListBoxItem.Icon.Url:=APicServerUrl+’娃哈哈猫缘咖啡.jpg’;

 

AListBoxItem:=Self.lbMultiPic.Prop.Items.Add;

AListBoxItem.Caption:=’人鱼恋带鱼系列’;

AListBoxItem.Icon.Url:=APicServerUrl+’人鱼恋带鱼系列.jpg’;

 

AListBoxItem:=Self.lbMultiPic.Prop.Items.Add;

AListBoxItem.Caption:=’食品套餐128元’;

AListBoxItem.Icon.Url:=APicServerUrl+’食品套餐128元.jpg’;

 

finally

Self.lbMultiPic.Prop.Items.EndUpdate();

//结束下拉刷新

Self.lbMultiPic.Prop.StopPullDownRefresh();

end;

end;

 

注意:在加载数据结束之后,需要调用StopPullDownRefresh方法结束下拉刷新,

StopPullDownRefresh方法有两个参数,

ALoadingStopCaption,表示此次下拉刷新是成功还是失败,默认值是”刷新成功!”,

AWaitHintTime,表示下拉刷新结束时提示”刷新成功!”结果多少时间,默认值是600毫秒,

以上参数保持默认即可。

 

再在ListBox.OnPullDownRefresh事件中,启动线程,

代码如下:

procedure TFrameListBox_AutoPullDownRefresh.lbMultiPicPullDownRefresh(

Sender: TTimerTask);

begin

//执行

TimerTaskEvent1.Run;

end;

 

 

我们运行看一下效果:

下拉一大段距离后松开

三秒之后,数据获取结束,加载到ListBox上,并结束刷新,

 

 

 

 

瀑布流视图适用于每个列表项高度不同,却又要紧凑排列在一起的情况,

如淘宝、花瓣,如下图所示:

 

 

 

ListView.Properties.ViewType除了lvtIcon图标视图,lvtList列表视图,

还有lvtWaterfall瀑布流视图,

 

ListView在lvtIcon图标视图下,每个Item都是一个个水平排列的,

如果排列列表项时遇到宽度放不下了,那就会换一行,

并且换行时,Top取的是上一行最高的那个列表项的Bottom,

如下图所示:

 

ListView在lvtList列表模式下,ListView就是一个ListBox,

一行一个每个列表项,如下图所示:

而ListView在lvtWaterfall瀑布流模式下,

每个列表项排列的时候,都需要判断上方是否有空的地方可以插入,

如下图所示:

因此,只需要将ListView.Properties.ViewType设置为lvtWaterfall,

就可以实现瀑布流效果。

 

下面以朋友圈的瀑布流为示例,

先放一个ListView,

设置ListView.Properties.ViewType为vtWaterfall

ListView.Properties.ItemHeight设置为200,

ListView.Properties.ItemWidth设置为140,

ListView.Properties.ItemSpace设置为10,

ListView.Properties.ColCount设置为2,表示每行显示2个列表项,

双击ListView,添加好三个Item,

需要的数据如下图所示:

把大图放在Item.Icon,

头像放在Item.Pic,

标题(如大家辛苦了)放在Item.Caption,

用户名(如DelphiTeacher)放在Item.Detail,

图片分类(如郊游.风景)放在Item.Detail1,

每个列表项的高度需要设置成不一样,

比如第一个列表项的高度为200,

第二个列表项的高度为150,

第三个列表项的高度为180,

如下图所示:

 

 

再在上面放一个ItemDesignerPanel,

设置给ListView.Properties.ItemDesignerPanel,

在ItemDesignerPanel上放好Image,Panel和Label,

并做好绑定,如下图所示:

最后,效果如下图所示: