Duilib中带有权重的灵活控件排列实现(一)

来源:互联网 发布:js cells 编辑:程序博客网 时间:2024/06/16 02:22
在开发播放器软件过程中,因为窗口的大小是可变的,为了让控制栏部分的控件(播放,上一集,下一集,全屏,字幕等)适应窗口的尺寸的变化而显示隐藏,产品经理会定义一系列的规则,好让在任何时候都最核心的功能提供给用户使用。


先列一下产品经理给予的需求:
两边往中间缩,保证左侧LOGO和右侧X最优先显示。
顶部隐藏优先级:搜索栏,换肤,意见反馈,播放记录,最小化,最大化
底部隐藏优先级:全屏,画质增强,无痕,打开文件,播放顺序,音量条

在处理这个需求过程中,前人也尝试了一些方法,比较通过全float绝对布局的方式,自己通过管理类来完全订制。而我个人还是希望通过而优雅的相对布局,并利用Container自身的排布算法来实现。经过几天的探索,大致实现上这种效果,在此分享一下思路与实现。

在分析此需求时,我希望引来类似于第3维的权重(weight)概念,即当父容器所提供的位置不足于将所有子控件摆放显示完全时,就按照重要性自低到高依次隐藏,将该控件的显示腾出来提供给更为重要的控件来摆放,简单推演一下,应该是可行的,按照这样简单的规则,可以比较轻松地解决这个需求,并且代码维护起来相对简单。只需要一个能够按照子控件的weight值来排列的父容器,命名为CWeightHorizontalLayoutUI。

OK,让我们来实现这个父容器,首先我介绍一下 CHorizontalLayoutUI::SetPos的方法。
其实就是两次for循环,前一次来试算,将没有设置宽度的控件记录下来,将剩余的空间求个平均数,设置给自适应的控件,而设置了宽度的控件则按设置的值排布。

    void CWeightHorizontalLayoutUI::SetPos(RECT rc)    {        CControlUI::SetPos(rc);        rc = m_rcItem;        std::map<CControlUI*, int /*width*/> mapAjust;        // Adjust for inset        rc.left     += m_rcInset.left;        rc.top      += m_rcInset.top;        rc.right    -= m_rcInset.right;        rc.bottom   -= m_rcInset.bottom;        if (m_items.GetSize() == 0) {            ProcessScrollBar(rc, 0, 0);            return;        }        if (!m_bScrollFloat && m_pVerticalScrollBar && m_pVerticalScrollBar->IsVisible())             rc.right -= m_pVerticalScrollBar->GetFixedWidth();        if (!m_bScrollFloat && m_pHorizontalScrollBar && m_pHorizontalScrollBar->IsVisible())             rc.bottom -= m_pHorizontalScrollBar->GetFixedHeight();        ResetWeightDisplayControlState(); //  读取当前子控件的权重        m_mapWeight.clear();        for (int i = 0; i < m_items.GetSize(); i++) {            DuiLib::CControlUI* pControl = static_cast<DuiLib::CControlUI*>(m_items[i]);            if (pControl == nullptr) continue;            CStdString strweight = pControl->GetCustomAttribute(L"weight");            int nweight = _ttoi(strweight.GetData());            m_mapWeight[i] = nweight;        }        m_mapWeightCache = m_mapWeight;        // 查找最小权重控件          auto HideMinWeigth = [&](int index) -> bool {            CControlUI* pControl = static_cast<CControlUI*>(m_items[index]);            if (pControl && pControl->IsVisible()) {                pControl->SetInternVisible(false);                m_arrWeightHideControl.push_back(pControl);                return true;            }            return false;        };        // Determine the width of elements that are sizeable        SIZE szAvailable = { rc.right - rc.left, rc.bottom - rc.top };        if (m_pHorizontalScrollBar && m_pHorizontalScrollBar->IsVisible())            szAvailable.cx += m_pHorizontalScrollBar->GetScrollRange();        int nAdjustables = 0;        int cxFixed = 0;        int nEstimateNum = 0;        int cxExpand = 0;        std::pair<int, int> min_weight; // first:control index, second:weight        std::map<CControlUI*, int /*width*/> m_mapEstimateWidth;  // 试算时控件的预设宽度        std::map<CControlUI*, int /*width*/> m_mapBookWidth;        do {            // 查找出当前权重值最小,并且显示着的控件            min_weight = std::make_pair(999, 999);            std::for_each(m_mapWeight.begin(), m_mapWeight.end(), [&](std::pair<int, int> item)-> void {                CControlUI* pControl = static_cast<CControlUI*>(m_items[item.first]);                if (item.second <= min_weight.second && pControl->IsVisible())                    min_weight = item;            });            // 已经找到后从map中移除,以避免下次查找的还是这个控件            auto min_it = std::find_if(m_mapWeight.begin(), m_mapWeight.end(), [min_weight](std::pair<int, int> item)-> bool {                return (item.first == min_weight.first && item.second == item.second);            });            if (min_it != m_mapWeight.end()) m_mapWeight.erase(min_it);            // 试算中            nAdjustables = 0;            cxFixed = 0;            nEstimateNum = 0;            for (int it1 = 0; it1 < m_items.GetSize(); it1++) {                CControlUI* pControl = static_cast<CControlUI*>(m_items[it1]);                if (pControl->IsFloat()) continue;                if (!pControl->IsVisible()) continue;                SIZE sz = pControl->EstimateSize(szAvailable);                if (sz.cx == 0) {                                                         nAdjustables++;                }                else {                    if (sz.cx < pControl->GetMinWidth()) sz.cx = pControl->GetMinWidth();                    if (sz.cx > pControl->GetMaxWidth()) sz.cx = pControl->GetMaxWidth();                }                m_mapEstimateWidth[pControl] = sz.cx;                cxFixed += sz.cx + pControl->GetPadding().left + pControl->GetPadding().right;                nEstimateNum++;            }            cxFixed += (nEstimateNum - 1) * m_iChildPadding;            cxExpand = 0;            if (nAdjustables > 0) cxExpand = max(0, (szAvailable.cx - cxFixed) / nAdjustables);// 此处下节解释            // 如果小于空间不够,先尝试从低到高缩减带有adjust属性的控件            if (szAvailable.cx - cxFixed < 0) {                std::vector<std::pair<CControlUI*, int>> adj_ctrls;                for (int it1 = 0; it1 < m_items.GetSize(); it1++) {                    CControlUI* pControl = static_cast<CControlUI*>(m_items[it1]);                    if (pControl->IsFloat()) continue;                    //if (!pControl->IsVisible()) continue;                    if (pControl->GetCustomAttribute(L"adjustwidth") == NULL ||                        _tcscmp(pControl->GetCustomAttribute(L"adjustwidth"), L"true")) continue;                    adj_ctrls.push_back(std::make_pair(pControl, m_mapWeightCache[it1]));                }                // 自小到大按权重,先尝试最小权重                std::sort(adj_ctrls.begin(), adj_ctrls.end(), [&](std::pair<CControlUI*, int> lhs,                    std::pair<CControlUI*, int> rhs) -> bool {                    return lhs.second < rhs.second;                });                             for (auto it = adj_ctrls.begin(); it != adj_ctrls.end(); it++) {                    auto ctrl = it->first;                    if (m_mapEstimateWidth[ctrl] > ctrl->GetMinWidth()) {                        int sub_width = min(abs(szAvailable.cx - cxFixed), m_mapEstimateWidth[ctrl] - ctrl->GetMinWidth());                        m_mapEstimateWidth[ctrl] = m_mapEstimateWidth[ctrl] - sub_width;    // 新的试算宽度                         m_mapBookWidth[ctrl] = m_mapEstimateWidth[ctrl];                        cxFixed -= sub_width;                        if (szAvailable.cx - cxFixed >= 0) break; // 已经满足排列空间                    }                                    }            }            // 空间不够,从权重最小的依次隐藏        } while (szAvailable.cx - cxFixed < 0 && HideMinWeigth(min_weight.first));        // Position the elements        SIZE szRemaining = szAvailable;        int iPosX = rc.left;        if (m_pHorizontalScrollBar && m_pHorizontalScrollBar->IsVisible()) {            iPosX -= m_pHorizontalScrollBar->GetScrollPos();        }        int iAdjustable = 0;        int cxFixedRemaining = cxFixed;        for (int it2 = 0; it2 < m_items.GetSize(); it2++) {            CControlUI* pControl = static_cast<CControlUI*>(m_items[it2]);            if (!pControl->IsVisible()) continue;            if (pControl->IsFloat()) {                SetFloatPos(it2);                continue;            }            RECT rcPadding = pControl->GetPadding();            szRemaining.cx -= rcPadding.left;            SIZE sz = pControl->EstimateSize(szRemaining);            if (sz.cx == 0) {                iAdjustable++;                sz.cx = cxExpand;                //最后一个自适应尺寸大小无需另行计算                //if( iAdjustable == nAdjustables ) {                //sz.cx = MAX(0, szRemaining.cx -cxFixedRemaining);                //}                if (sz.cx < pControl->GetMinWidth()) sz.cx = pControl->GetMinWidth();                if (sz.cx > pControl->GetMaxWidth()) sz.cx = pControl->GetMaxWidth();            }            else {                if (sz.cx < pControl->GetMinWidth()) sz.cx = pControl->GetMinWidth();                if (sz.cx > pControl->GetMaxWidth()) sz.cx = pControl->GetMaxWidth();            }            sz.cy = pControl->GetFixedHeight();            if (sz.cy == 0) sz.cy = rc.bottom - rc.top - rcPadding.top - rcPadding.bottom;            if (sz.cy < 0) sz.cy = 0;            if (sz.cy < pControl->GetMinHeight()) sz.cy = pControl->GetMinHeight();            if (sz.cy > pControl->GetMaxHeight()) sz.cy = pControl->GetMaxHeight();            //padding不算控件宽度//2012/09/12            //RECT rcCtrl = { iPosX + rcPadding.left, rc.top + rcPadding.top, iPosX + sz.cx + rcPadding.left + rcPadding.right, rc.top + rcPadding.top + sz.cy};            if (m_mapBookWidth.find(pControl) != m_mapBookWidth.end() &&                m_mapBookWidth[pControl] != sz.cx) {                sz.cx = m_mapBookWidth[pControl];            }            RECT rcCtrl = { iPosX + rcPadding.left, rc.top + rcPadding.top, min(iPosX + sz.cx + rcPadding.left, rc.right) , rc.top + rcPadding.top + sz.cy };            pControl->SetPos(rcCtrl);            iPosX += sz.cx + m_iChildPadding + rcPadding.left + rcPadding.right;            szRemaining.cx -= sz.cx + m_iChildPadding + rcPadding.right;            if (m_bWholeDisplay && rcCtrl.right > rc.left + szAvailable.cx)                m_arrWholeDisplayControl.push_back(pControl);        }        // Process the scrollbar        ProcessScrollBar(rc, 0, 0);    }

以上代码主要是在试算过程中,在剩余的空间宽度不足时隐藏一下最小的权重控件,再次试算一遍
需要留心的是当Hide时不能够直接调用子控件的SetVisible接口,这样会改变控件的基础状态,而应使用SetInternVisible,并且在再次SetPos时,清除这样的记录,并将状态重置,这样,不影响再次的试算过程。基本以上实现了控制栏的自定义隐藏顺序。而我们只需要在xml文件中配置一下各个控件的权重即可。

实现效果:



0 0
原创粉丝点击