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.mzph.cn/pingmian/24926.shtml

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

相关文章

【录制,纯正人声】OBS录制软件,音频电流音,杂音解决办法,录制有噪声的解决办法

速度解决的方法 &#xff08;1&#xff09;用RNNoise去除噪声。RNNoise是一个开源的&#xff0c;效果不好的噪声去除器。使用方法就是点击滤镜&#xff0c;然后加噪声抑制RNNoise。【这方法不好用】 &#xff08;2&#xff09;用Krisp(https://krisp.ai/) 去除噪声。这个Kris…

探索C++ STL中的std::list:链式存储的艺术与实践

目录 ​编辑 引言 一、std::list详解 二、std::list的关键成员函数 三、示例代码 四、std::list与std::vector的对比 内存布局&#xff1a; 插入与删除&#xff1a; 迭代器稳定性&#xff1a; 五、应用场景 结语 引言 在C标准模板库(STL)中&#xff0c;std::list作…

skywalking学习

文章目录 前言一、skywalking单体安装部署1. 下载skywalking2. 部署oap和oap-ui服务3. 测试skywalking监控springboot应用 二、搭建swck(skywalking集群)1.安装k8s2.下载swck3.设置pod自动注入java agent 三、skywalking监控python四、skywalking监控cpp总结参考 前言 本文主要…

SSL/TLS和HTTPS

HTTPS就是用了TLS包装的Socket进行通信的HTTP 混合加密 被称为混合加密。具体过程如下&#xff1a; 使用非对称加密协商对称密钥&#xff1a; 在通信的开始阶段&#xff0c;通常由客户端和服务器使用非对称加密算法&#xff08;如RSA&#xff09;来协商一个对称密钥。通常情…

vue3中的ref与reactive的区别

目录 1、两者的区别底层实现响应式引用与响应式对象 2、用法3、vue3中声明的数组/对象3.1 通过reactive 声明的Array/Object&#xff0c;给它重新分配一个全新的对象时&#xff0c;会出错、或失去响应式效果 3.2 解决方案 4、cosnt 说明5、Proxy 与 definePropertyref 浅层响应…

人工智能与能源约束的矛盾能否化解

以下文章来源&#xff1a;澎湃新闻 人工智能技术在台前展示的是比特世界的算力、算法和数据&#xff0c;但其“轻盈的灵魂”背后则是土地、能源和水等物理世界“沉重的肉身”。根据本文三种情境的模拟测算&#xff0c;未来人工智能发展需要可持续的巨量能源支撑&#xff0c;能源…

基于Python的北京天气数据可视化分析

项目用到库 import numpy as np import pandas as pd import datetime from pyecharts.charts import Line from pyecharts.charts import Boxplot from pyecharts.charts import Pie,Grid from pyecharts import options as opts from pyecharts.charts import Calendar 1.2…

Python应用开发——30天学习Streamlit Python包进行APP的构建(5)

上几次我们已经将一些必备的内容进行了快速的梳理,让我们掌握了streanlit的凯快速上手,接下来我们将其它的一些基础函数再做简单的梳理,以顺便回顾我们未来可能用到的更丰富的函数来实现应用的制作。 st.write_stream 将生成器、迭代器或类似流的序列串流到应用程序中。 …

vue -ant -design 卡片是布局 实现动态计算 当前的 左右间距 实现居中

是这样的一个样式 我们使用display :flex 布局的时候 我们全部剧中 display: flex;align-items: center;justify-content: center; 如果是上述的代码来说的话 总是最后的一个也是会居中的 这样就比较丑 我们好像就没有什么好的办法了 我们这自己写的 肯定没有组件牛 如果有…

三十六篇:未来架构师之道:掌握现代信息系统典型架构

未来架构师之道&#xff1a;掌握现代信息系统典型架构 1. 引言 在企业的数字化转型浪潮中&#xff0c;信息系统架构的角色变得日益重要。它不仅承载了企业的IT战略&#xff0c;更是确保企业在复杂、动态的市场环境中稳定运行的关键。作为信息系统的骨架&#xff0c;一个精心设…

Linux环境在非root用户中搭建(java-tomcat-redis)

注: 本文在内网(离线)环境&#xff0c;堡垒机中搭建&#xff0c;服务器不同可能有所差异&#xff0c;仅供参考 本文安装JDK-20.0.1版本&#xff0c;apache-tomcat-10.1.10版本&#xff0c;redis-6.2.15版本 本文服务器IP假设&#xff1a;192.168.88.133 root用户创建子用户并…

人工智能对话系统源码 手机版+电脑版二合一 全端支持 前后端分离 带完整的安装代码包以及搭建部署教程

系统概述 该系统采用前后端分离的设计模式&#xff0c;前端负责用户界面展示与交互&#xff0c;后端负责数据处理与业务逻辑实现。前后端通过API接口进行通信&#xff0c;实现数据的实时传输与处理。系统支持全端访问&#xff0c;无论是手机还是电脑&#xff0c;都能获得良好的…

u盘内容无故消失了是什么原因?u盘部分内容无故消失了怎么恢复

在数字化时代&#xff0c;U盘作为便携存储设备&#xff0c;承载着许多重要的数据。然而&#xff0c;有时我们可能会遭遇U盘部分内容无故消失的情况&#xff0c;这无疑给我们的工作和生活带来了不小的困扰。本文将为您解析U盘内容消失的可能原因&#xff0c;并分享几招实用的数据…

重生奇迹MU格斗家技能

格斗家有9个技能&#xff0c;其中幽冥青狼拳.斗气爆裂拳.神圣气旋.这3个是武器上带的技能。 回旋踢.幽冥光速拳.炎龙拳.这3个是买羊皮纸学的技能&#xff08;回旋踢是格斗最强技能&#xff09;。 还有3个给自己加BUFF的技能&#xff0c;斗神破&#xff08;增加无视防御的状态&…

移动端 UI 风格,视觉盛宴

移动端 UI 风格&#xff0c;视觉盛宴

linux常用命令及其选项

1、常用命令 1.1、ls 选项说明-a显示所有文件及目录 (包括隐藏文件)-i显示inode-A同 -a选项 &#xff0c;但不列出 "." (目前目录) 及 ".." (父目录)-l列出信息详细(如文件型态、权限、拥有者、文件大小等)-R递归显示(若目录下有文件&#xff0c;则以下之…

htb_office

端口扫描 namp -sSVC 10.10.11.1380&#xff0c;445 80端口 robots.txt 只有/administrator可以访问 Joomla joomscan扫描 joomscan --url http://10.10.11.3/ 版本为4.2.7&#xff0c;存在cve CVE-2023-23752 Joomla未授权访问Rest API漏洞 访问路径 /api/index.php/…

使用OpenCV dnn c++加载YOLOv8生成的onnx文件进行实例分割

在网上下载了60多幅包含西瓜和冬瓜的图像组成melon数据集&#xff0c;使用 EISeg 工具进行标注&#xff0c;然后使用 eiseg2yolov8 脚本将.json文件转换成YOLOv8支持的.txt文件&#xff0c;并自动生成YOLOv8支持的目录结构&#xff0c;包括melon.yaml文件&#xff0c;其内容如下…

Linux宝塔部署数据库连接问题

博主在部署项目时发现网页可以成功部署&#xff0c;但是登录界面一直登录不进去推测是数据库连接问题。 博主当时在IDEA中写的是用户名为root 密码123456 但是在宝塔中因为自己是跟着教程学的所以就顺手把用户名和密码都改了&#xff0c;于是java中的配置和数据库配置连接不上…

利用streamlit结合langchain_aws实现claud3的页面交互

测试使用的代码如下 import streamlit as st from langchain_aws import ChatBedrockdef chat_with_model(prompt, model_id):llm ChatBedrock(credentials_profile_name"default", model_idmodel_id, region_name"us-east-1")res llm.invoke(prompt)re…