Duilib多标签选项卡拖拽效果:添加动画特效!

动画是小型界面库的“难题”、“通病”

请添加图片描述

几年前就有人分享了如何用direct UI制作多标签选项卡界面的方法。还有人出了一个简易的浏览器demo。但是他们的标签栏都没有Chrome浏览器那样的动画特效。

如何给界面添加布局是的动画特效呢?

动画使界面看起来高大上,使用起来也更直观。

我调查了一些小型界面库,包括imgui、lcui等,都没有内置这样的组件。

难道仅仅为了这一个小的控件效果,真的要内置一个浏览器?(sortablejs?)


多标签选项卡拖拽效果 【三百行精简版本】

Duilib多标签选项卡拖拽效果 - 知乎
洋洋洒洒八百行 —— 大多是图标啊,背景啊之类的。然后他还特别设计了。子控件类型和父控件配套使用。太麻烦了。

我简化一番,将原理呈现,只需三百行:


class CTabBarUI :public CHorizontalLayoutUI
{
public:CTabBarUI();~CTabBarUI();LPCTSTR GetClass() const;LPVOID GetInterface(LPCTSTR pstrName);//添加一个CControlUI* AddItem(LPCTSTR pstrText);//dragvoid DoDragBegin(CControlUI *pTab);void DoDragMove(CControlUI *pTab, const RECT& rcPaint);void DoDragEnd(CControlUI *pTab, const POINT& Pt);private:CControlUI *m_pZhanWeiOption = NULL;CControlUI *m_pDragOption = NULL;};#define DUI_MSGTYPE_OPTIONTABCLOSE 		   	(_T("closeitem_tabbar"))//std::function<bool(CControlUI* this_, HDC hDC, const RECT& rcPaint)> postDraw;
std::function<bool(CControlUI* this_, TEventUI& evt)> evtListener;POINT m_ptLastMouse;
POINT m_ptLButtonDownMouse;
RECT m_rcNewPos;//判断开始拖拽
bool m_bFirstDrag = true;//判断是否忽略拖拽,首次需要鼠标按住拖拽一定距离才触发拖拽
bool m_bIgnoreDrag = true;//
//
CTabBarUI::CTabBarUI()
{m_pZhanWeiOption = new CControlUI();m_pZhanWeiOption->SetMaxWidth(0);m_pZhanWeiOption->SetForeColor(0x000000ff);m_pZhanWeiOption->SetEnabled(false);Add(m_pZhanWeiOption);auto box = this;postDraw = [box](CControlUI* this_, HDC hDC, const RECT& rcPaint){return true;};evtListener = [box](CControlUI* this_, TEventUI& event){//if (!this_->IsMouseEnabled() && event.Type > UIEVENT__MOUSEBEGIN && event.Type < UIEVENT__MOUSEEND) {//	if (box != NULL) box->DoEvent(event);//	else COptionUI::DoEvent(event);//	return true;//}auto _manager = box->GetManager();auto & m_rcItem = this_->GetPos();if (event.Type == UIEVENT_BUTTONDOWN){if (::PtInRect(&this_->GetPos(), event.ptMouse) && this_->IsEnabled()){this_->m_uButtonState |= UISTATE_PUSHED | UISTATE_CAPTURED;this_->Invalidate();if (this_->IsRichEvent()) _manager->SendNotify(this_, DUI_MSGTYPE_BUTTONDOWN);if (::PtInRect(&this_->GetPos(), event.ptMouse)/* && !::PtInRect(&rcClose, event.ptMouse)*/){this_->Activate();}m_bIgnoreDrag = true;m_ptLButtonDownMouse = event.ptMouse;m_ptLastMouse = event.ptMouse;m_rcNewPos = m_rcItem;if (_manager){_manager->RemovePostPaint(this_);_manager->AddPostPaint(this_);}}}else if (event.Type == UIEVENT_MOUSEMOVE){if ((this_->m_uButtonState & UISTATE_CAPTURED) != 0){LONG cx = event.ptMouse.x - m_ptLastMouse.x;LONG cy = event.ptMouse.y - m_ptLastMouse.y;m_ptLastMouse = event.ptMouse;RECT rcCurPos = m_rcNewPos;rcCurPos.left += cx;rcCurPos.right += cx;rcCurPos.top += cy;rcCurPos.bottom += cy;//将当前拖拽块的位置 和 当前拖拽块的前一时刻的位置,刷新CDuiRect rcInvalidate = m_rcNewPos;m_rcNewPos = rcCurPos;rcInvalidate.Join(m_rcNewPos);if (_manager) _manager->Invalidate(rcInvalidate);this_->NeedParentUpdate();}}else if (event.Type == UIEVENT_BUTTONUP){if ((this_->m_uButtonState & UISTATE_CAPTURED) != 0){this_->m_uButtonState &= ~(UISTATE_PUSHED | UISTATE_CAPTURED);this_->Invalidate();CTabBarUI* pParent = static_cast<CTabBarUI*>(box);if (pParent){pParent->DoDragEnd(this_, m_ptLastMouse);}if (_manager){_manager->RemovePostPaint(this_);_manager->Invalidate(m_rcNewPos);}this_->NeedParentUpdate();m_bFirstDrag = true;}}if ((this_->m_uButtonState & UISTATE_CAPTURED) != 0){auto & m_rcItem = this_->GetPos();lxxx(m_bIgnoreDrag dd, 13)if (m_bIgnoreDrag && abs(m_ptLastMouse.x - m_ptLButtonDownMouse.x) < 15){return true;}m_bIgnoreDrag = false;lxxx(dd, 13)CTabBarUI* pParent = static_cast<CTabBarUI*>(box);//if (!pParent) return true;if (m_bFirstDrag){pParent->DoDragBegin(this_);m_bFirstDrag = false;return true;}CDuiRect rcParent = box->GetPos();RECT rcUpdate = { 0 };rcUpdate.left = m_rcNewPos.left < rcParent.left ? rcParent.left : m_rcNewPos.left;rcUpdate.top = m_rcItem.top < rcParent.top ? rcParent.top : m_rcItem.top;rcUpdate.right = m_rcNewPos.right > rcParent.right ? rcParent.right : m_rcNewPos.right;rcUpdate.bottom = m_rcItem.bottom > rcParent.bottom ? rcParent.bottom : m_rcItem.bottom;//CRenderEngine::DrawColor(hDC, rcUpdate, 0xAAFFFFFF);pParent->DoDragMove(this_, rcUpdate);}return true;};}CTabBarUI::~CTabBarUI()
{
}LPCTSTR CTabBarUI::GetClass() const
{return _T("TabBarUI");
}LPVOID CTabBarUI::GetInterface(LPCTSTR pstrName)
{if (_tcsicmp(pstrName, _T("TabBar")) == 0) return static_cast<CTabBarUI*>(this);return CHorizontalLayoutUI::GetInterface(pstrName);
}CControlUI* CTabBarUI::AddItem(LPCTSTR pstrText)
{if (!pstrText){return NULL;}CLabelUI* pTab = new CLabelUI();pTab->evtListeners.push_back(evtListener);pTab->postDraws.push_back(postDraw);pTab->SetRichEvent(true);//pTab->SetName(_T("tabbaritem"));//pTab->SetGroup(_T("tabbaritem"));pTab->SetTextColor(0xff333333);//pTab->SetNormalImage(_T("file='img/bk_tabbar_item.png' source='0,0,10,8' corner='4,4,4,2'"));//pTab->SetHotImage(_T("file='img/bk_tabbar_item.png' source='10,0,20,8' corner='4,4,4,2'"));//pTab->SetSelectedImage(_T("file='img/bk_tabbar_item.png' source='20,0,30,8' corner='4,4,4,2'"));pTab->SetMaxWidth(226);//pTab->SetFixedWidth(100);pTab->SetMinWidth(20);//pTab->SetBorderRound({ 2, 2 });pTab->SetText(pstrText);pTab->SetAttribute(_T("align"), _T("left"));pTab->SetAttribute(_T("textpadding"), _T("28,0,16,0"));pTab->SetAttribute(_T("iconsize"), _T("16,16"));pTab->SetAttribute(_T("iconpadding"), _T("6,0,0,0"));pTab->SetAttribute(_T("iconimage"), _T("img/icon_360.png"));pTab->SetAttribute(_T("selectediconimage"), _T("img/icon_baidu.png"));pTab->SetAttribute(_T("endellipsis"), _T("true"));pTab->SetAttribute(_T("haveclose"), _T("true"));pTab->SetAttribute(_T("closepadding"), _T("0,0,6,0"));pTab->SetAttribute(_T("closesize"), _T("16,16"));pTab->SetAttribute(_T("closeimage"), _T("file='img/btn_tabbaritem.png' source='0,0,16,16'"));pTab->SetAttribute(_T("closehotimage"), _T("file='img/btn_tabbaritem.png' source='16,0,32,16'"));pTab->SetAttribute(_T("closepushimage"), _T("file='img/btn_tabbaritem.png' source='32,0,48,16'"));//pTab->OnNotify += MakeDelegate(this, &CTabBarUI::OnItemClose);if (Add(pTab)){return pTab;}return NULL;
}void CTabBarUI::DoDragBegin(CControlUI *pTab)
{if (!pTab){return;}int index = GetItemIndex(pTab);if (index < 0){return;}int index_blue = GetItemIndex(m_pZhanWeiOption);if (index_blue < 0){return;}m_pDragOption = pTab;m_items.SetAt(index, m_pZhanWeiOption);m_items.SetAt(index_blue, m_pDragOption);m_pZhanWeiOption->SetMaxWidth(m_pDragOption->GetWidth());m_pDragOption->SetMaxWidth(0);
}void CTabBarUI::DoDragMove(CControlUI *pTab, const RECT& rcPaint)
{if (m_pDragOption != pTab){return;}int x = rcPaint.left + (rcPaint.right - rcPaint.left) / 2;int y = rcPaint.top + (rcPaint.bottom - rcPaint.top) / 2;if (x < m_rcItem.left || x > m_rcItem.right){return;}int index = -1;for (int it1 = 0; it1 < m_items.GetSize(); it1++) {CControlUI* pControl = static_cast<CControlUI*>(m_items[it1]);if (!pControl) continue;if(pControl!=m_pZhanWeiOption)if (/*_tcsicmp(pControl->GetClass(), _T("tabbaritemui")) == 0 && */::PtInRect(&pControl->GetPos(), { x, y })){index = it1;break;}}if (index == -1){return;}CControlUI *pOption = static_cast<CControlUI*>(GetItemAt(index));int index_blue = GetItemIndex(m_pZhanWeiOption);m_items.SetAt(index, m_pZhanWeiOption);m_items.SetAt(index_blue, pOption);}void CTabBarUI::DoDragEnd(CControlUI *pTab, const POINT& Pt)
{if (m_pDragOption != pTab){return;}int index = GetItemIndex(m_pDragOption);if (index < 0){return;}int index_blue = GetItemIndex(m_pZhanWeiOption);if (index_blue < 0){return;}m_items.SetAt(index, m_pZhanWeiOption);m_items.SetAt(index_blue, m_pDragOption);m_pDragOption->SetMaxWidth(m_pZhanWeiOption->GetWidth());m_pZhanWeiOption->SetMaxWidth(0);
}

和chrome浏览器不同的是他没有使用标准的拖拽事件,而是分别处理了点击触摸移动事件。


DirectUI 动画方案入门

Direct是比较早的,他的技术比较老。他是直接用那个hdc绘制。和普通的win程序是一样的。区别仅仅是使用自己的布局系统。然后他的控件大多是没有句柄的。所以说比较直接。

最初的DirectUI 公开方案里的动画。那个是dx插特效,是不一样的,在播放dx特效之时,会有一个阻塞之类的,特效组合也不是很自由。

其实很简单,无非是三种方法:

  1. 最简单的timer
  2. 循环Invalidate
  3. 用一个新的线程去控制它刷新。

第三和第二很相似。第二个循环Invalidate是一个折中。

为了入门,简单实现上面动图中的滚动跑马灯特效:

float xx;
int tick;auto updateFun = [newbar, menu](float spd){int t = GetTickCount64(), dt = t-tick[i];xx += dt * spd;tick = t;menu->SetFixedXY({(int)round(xx),0});if (xx>newbar->GetWidth()-menu->GetWidth()){xx = 0;}return dt;};if (开始滚动){newbar->postDraws.push_back([updateFun, newbar](CControlUI* thiz, HDC hDC, const RECT& rcPaint){int dt = updateFun(.45f);newbar->NeedUpdate(); Sleep(1);return true;});}

这个需要修改界面库代码在绘制之后调用传进去的函数:

DuiLib\Core\UIControl.cpp

bool CControlUI::DoPaint(HDC hDC, const RECT& rcPaint, CControlUI* pStopControl){...if (postDraws.size()){for (size_t i = 0; i < postDraws.size(); i++){auto ret = postDraws[i](this, hDC, rcPaint);if (!ret){postDraws.erase(postDraws.begin()+i);}}}return true;}

类似于安卓的循环postInvalidate。

注意需要睡眠一秒钟。不然跑的太快,CPU飙升过于明显。当然最大值也不是很大,就是sleep调度一下的话,性能变得很轻盈。


WinQkUI 标签动画

有了这个基础之后,我们就可以实现界面拖拽排序之时的动画效果。

也是需要修改这个源代码库。循环Invalidate还是在dopaint方法内部末尾调用,但是设置位置偏移的话,须在setpos之后调用。


void CTabBarUI::DoDragMove(CControlUI *pTab, const RECT& rcPaint)
{...AnimationJob* job = new AnimationJob{true, pItem->GetPos().left, pItem->GetPos().top, GetTickCount64(), 200};auto animator = [job](CControlUI* this_, RECT& rcItem){int ww = rcItem.right - rcItem.left;int hh = rcItem.bottom - rcItem.top;int time = GetTickCount64() - job->start;if (time>job->duration)time = job->duration;if (time>=job->duration)job->active = false;rcItem.left = job->xx + (rcItem.left - job->xx)*1.f/job->duration*time;rcItem.top = job->yy + (rcItem.top - job->yy)*1.f/job->duration*time;rcItem.right = rcItem.left + ww;rcItem.bottom = rcItem.top + hh;//this_->NeedParentUpdate();//this_->GetParent()->NeedUpdate();//Sleep(1);return job->active;};pItem->postSize.resize(0);pItem->postSize.push_back(animator);	//if (1)//{//	return;//}pItem->_view_states |= VIEWSTATEMASK_IsAnimating;pItem->postDraws.push_back([job](CControlUI* thiz, HDC hDC, const RECT& rcPaint){if (job->active){//RECT* rcItem = (RECT*)&thiz->GetPos();int time = GetTickCount64() - job->start;if (time>job->duration)time = job->duration;//if (time>=job->duration)//	job->active = false;thiz->GetParent()->NeedUpdate();//Sleep(1);} else {thiz->postSize.resize(0);thiz->_view_states &= ~VIEWSTATEMASK_IsAnimating;delete job;}return job->active;});}

后面的代码不是很完整,但原理已经讲得十分清楚了。待我整理一番再上传。

只需在DoDragMove方法。在触发交换元素位置的时候,为每个被移动的元素安排动画 AnimationJob 就行。

struct AnimationJob{bool active;LONG xx;LONG yy;ULONGLONG start;int duration;
};

AnimationJob 结构体记录起始位置,然后根据一个动画时长,一路插值到目标位置即可。

目标位置由父容器布局,由 setPos 决定。

在postSize的循环中,实时修改动画过程中控件的位置,不直接采用setPos 的值,从而实现布局动画,原理十分的简单。

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/344274.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

HQL面试题练习 —— 求连续段的最后一个数及每个连续段的个数

目录 1 题目2 建表语句3 题解 题目来源&#xff1a;拼多多。 1 题目 有一张表t_id记录了id&#xff0c;id不重复&#xff0c;但是会存在间断&#xff0c;求出连续段的最后一个数及每个连续段的个数。 ----- | id | ----- | 1 | | 2 | | 3 | | 5 | | 6 | | 8 | | …

2024 年最新 Python 基于百度智能云实现文字识别 OCR 详细教程

文字识别 OCR 概述 文字识别OCR&#xff08;Optical Character Recognition&#xff09;提供多场景、多语种、高精度的文字检测与识别服务&#xff0c;多项ICDAR指标居世界第一。广泛适用于金融服务、财税报销、法律政务、保险医疗、快递物流、交通出行、教育培训等场景&#…

设计模式-外观(门面)模式(结构型)

外观模式 外观模式又称门面模式&#xff08;结构型模式&#xff09;&#xff0c;它是一个可以屏蔽系统复杂性的设计模式。俗话说没有什么问题是加一层“介质”解决不了的&#xff0c;如果有那就在加一层。在开发过程中肯定封装过Utils类&#xff0c;我认为这就是一种门面模式&…

RocketMQ的安装

首先到RocketMQ官网下载页面下载 | RocketMQ (apache.org)&#xff0c;本机解压缩&#xff0c;作者在这里用的是最新的5.2.0版本。按照如下步骤安装。 1、环境变量配置rocket mq地址 ROCKETMQ_HOME D:\rocketmq-all-5.2.0-bin-release 在变量path中添加”%ROCKETMQ_HOME%\bi…

Python | Leetcode Python题解之第132题分割回文串II

题目&#xff1a; 题解&#xff1a; class Solution:def minCut(self, s: str) -> int:n len(s)g [[True] * n for _ in range(n)]for i in range(n - 1, -1, -1):for j in range(i 1, n):g[i][j] (s[i] s[j]) and g[i 1][j - 1]f [float("inf")] * nfor …

Block Transformer:通过全局到局部的语言建模加速LLM推理

在基于transformer的自回归语言模型&#xff08;LMs&#xff09;中&#xff0c;生成令牌的成本很高&#xff0c;这是因为自注意力机制需要关注所有之前的令牌&#xff0c;通常通过在自回归解码过程中缓存所有令牌的键值&#xff08;KV&#xff09;状态来解决这个问题。但是&…

英码科技推出鸿蒙边缘计算盒子:提升国产化水平,增强AI应用效能,保障数据安全

当前&#xff0c;随着国产化替代趋势的加强&#xff0c;鸿蒙系统Harmony OS也日趋成熟和完善&#xff0c;各行各业都在积极拥抱鸿蒙&#xff1b;那么&#xff0c;边缘计算要加快实现全面国产化&#xff0c;基于鸿蒙系统开发AI应用势在必行。 关于鸿蒙系统及其优势 鸿蒙系统是华…

SpringCloud-面试篇(二十三)

&#xff08;1&#xff09;SpringCloud常见组件有那些 有无数微服务需要相互调用&#xff1a;可以用远程调用组件OpenFeign组件&#xff0c;也可以用Dobble 这么多微服务相互调用怎么管理&#xff1a;就用到注册中心组件Nacos&#xff0c;Eureka 所有的服务去找注册中心做注…

计算机组成结构—IO方式

目录 一、程序查询方式 1. 程序查询基本流程 2. 接口电路 3. 接口工作过程 二、程序中断方式 1. 程序中断基本流程 2. 接口电路 3. I/O 中断处理过程 三、DMA 方式 1. DMA 的概念和特点 2. DMA 与 CPU 的访存冲突 3. DMA 接口的功能 4. DMA 接口的组成 5. DMA 的…

先求生存,再谋发展:俞敏洪的创业哲学与产品创新之路

引言&#xff1a; 在创业的道路上&#xff0c;每一个创业者都面临着无数的挑战和选择。俞敏洪&#xff0c;新东方教育科技集团的创始人&#xff0c;以其独特的创业哲学和坚韧不拔的精神&#xff0c;带领新东方从一个小小的培训机构成长为全球知名的教育品牌。他的成功经验告诉…

AndroidStudio无法识别连接夜神模拟器

方法一(无法从根本上解决) ①进入夜神模拟器安装路径下的bin路径(安装路径可以带有中文路径) ②打开cmd窗口,输入以下代码(一定要打开模拟器) nox_adb.exe connect 127.0.0.1:62001 方法二(根本上解决) 原因:Android Studio的adb版本与夜神模拟器的adb版本不一致 ①打开And…

突发!凌晨4点某制造业大厂国产数据库集群故障...

&#x1f4e2;&#x1f4e2;&#x1f4e2;&#x1f4e3;&#x1f4e3;&#x1f4e3; 作者&#xff1a;IT邦德 中国DBA联盟(ACDU)成员&#xff0c;10余年DBA工作经验&#xff0c; Oracle、PostgreSQL ACE CSDN博客专家及B站知名UP主&#xff0c;全网粉丝10万 擅长主流Oracle、My…

【C/C++】IO流

目录 前言&#xff1a; 一&#xff0c;C语言的I/O流 二&#xff0c;C的I/O流 2-1&#xff0c;C标准IO流 2-2&#xff0c;IO流的连续输入 前言&#xff1a; “流”即是流动的意思&#xff0c;是物质从一处向另一处流动的过程&#xff0c;是对一种有序连续且具有方向性的数据…

新品发布 | 飞凌嵌入式RK3576核心板,为AIoT应用赋能

为了充分满足AIoT市场对高性能、高算力和低功耗主控日益增长的需求&#xff0c;飞凌嵌入式全新推出基于Rockchip RK3576处理器开发设计的FET3576-C核心板&#xff01; 集成4个ARM Cortex-A72和4个ARM Cortex-A53高性能核&#xff0c;内置6TOPS超强算力NPU&#xff0c;为您的AI…

【RK3568】制作Android11开机动画

Android 开机 logo 分为两种&#xff1a;静态显示和动态显示。静态显示就是循环显示一张图片&#xff1b;动态显示就是以特定帧率顺序显示多张图片 1.准备 android logo 图片 Android logo最好是png格式的&#xff0c;因为同一张图片的情况下&#xff0c;png 格式的比 jpg和b…

海思SS928(SD3403)部署YOLOv5-YOLOv7步骤详解

1. YOLO模型资料 本文档内容以yolov5-7.0工程、yolov5s模型为例。 a. 模型结构 详细的模型结构可以利用netron工具打开.pt或.onnx模型查看。 b. 模型参数即验证结果 其中,YOLOv5n、YOLOv5s、YOLOv5m、YOLOv5l、YOLOv5x为五种类型的预训练模型,其包含的检测类别相…

呆滞物料规范管理了,问题就好办了

对于制造企业来说&#xff0c;库存是生存和发展的重要保障&#xff0c;过高的库存会占用企业大量的资金和管理成本&#xff0c;影响企业的正常生产&#xff0c;然而多数中小制造企业还在用人工干预管理&#xff0c;如何控制呆滞物料成为仓储管理的一大难题。 什么是呆滞料&…

Django 创建项目及应用

1&#xff0c;安装 Django pip install Django3.1.5 2&#xff0c;创建 Django项目 django-admin startproject myshop 3&#xff0c;创建 Django应用 python manage.py startapp app1 4&#xff0c;启动 Django项目 python .\manage.py runserver 到这里项目及应用创建…

Redis中的主从复制

分布式系统中的几种Redis部署方式 为了解决一个程序只部署在一个服务器上的单点问题&#xff1a; 可用性问题&#xff0c;如果这个机器挂了&#xff0c;就意味着服务就中断了 一个程序只部署在一台机器上&#xff0c;它的性能/支持的并发量也是有限的 所以&#xff0c;就引…

型号FM152A,FM148R和利时

型号FM152A,FM148R和利时。控制系统的仿真,综合考虑多方面的因素,本文将用MCGS组态软件设计一个仿真实验监控平台来对其进行实时控制.&#xff0c;完成仿真实验监控平台的设计,型号FM152A,FM148R和利时。最终达到对水箱液位实时监控,实验数据采集,报表的输出和数据的同步显示MC…