QT應用程序分辨率自適應

轉自:https://blog.csdn.net/matengxiao/article/details/52853332

QT應用程序分辨率自適應
一、應用程序分辨率自適應
    爲了滿足應用程序能在不同尺寸及分辨率的屏幕下能夠正常的運行顯示,就需要對不同的分辨率進行自適應,而且應用程序分辨率自適應的問題在應用UI設計佈局以及UI代碼編寫階段進行設計規劃,如在界面完成後期再考慮分辨率問題可能需要更大的工作量,並且自適應效果不一定能達到要求。一般來說,應用程序的設計應該按照支持的最小分辨率來進行設計,在應用程序分辨率變化時應用程序中的各個元素進行尺寸的縮放以及位置的調整,同時增加或減少應用程序顯示界面內容,同時還要考慮圖片失真,控件變形,字體顯示等問題。 
    對於Qt應用來說,官方並沒有提供一整套完整的解決有效方案,因此Qt程序的分辨率自適應問題需要按照應用程序的要求,在程序中自己實現相應的控件適應,但是分辨率適配的大體思路都是一致的,即調整控件尺寸及位置以及修改界面元素顯示內容,Qt程序又可分爲QtWidget程序以及qml程序,下面分別描述這兩部分程序的界面分辨力的自適應解決方案。

二、QtWidget中的分辨率自適應
    在QtWidget中Qt已經提供有相應的解決方案來實現控件分辨率的自適應,即通過layout對控件進行佈局來實現控件的自適應,此外還可以通過自定義QTWidget中控件的自適應策略來實現分辨率自適應,下面分別介紹這兩種方法:

2.1layout佈局
    通過layout佈局的方式對QtwWidget的分辨率自適應,即將需要將自適應的控件添加進layout佈局中,當layout的父對象的尺寸變化時,layout會根據父對象相應的變化寬、高比例對佈局中的控件進行縮放,以此來實現相應控件的分辨率自適應。 
    通過layout的方式實現控件的方式實現自動縮放使用簡單,且無須關注其具體的實現細節,但是這種方式需要就界面剛開始佈局時,就要需要採用layout的方式進行佈局,但是對於已經完成且沒有采用layout佈局的界面,可在QtCreator設計器中將需要添加布局的控件選中,然後點擊右鍵,選擇柵格化即可。 
但是在QtCreator Designer中添加的layout存在一個問題,如下圖在Designer中添加一個Vertical Layout,layout中添加兩個PushButton,當運行程序後,調整程序大小,中間的PushButton並不會隨着窗口大小而進行縮放。

 


 
 
    通過查看QtCreator中通過ui文件生成的頭文件,即ui_widget.h中的代碼可以發現,在QtDesigner中添加layout時,會給layout自動添加一個widget的父對象,即下圖中的verticalLayoutWidget,因此verticalLayout會隨着其父對象verticalLayoutWidget的大小變化,對其中的控件進行縮放調整,因此纔會出現上面的程序窗口變化時,layout中的控件不會自動縮放。

 

    對於上述情況,需要在窗口大小變化時先調整verticalLayoutWidget的位置及大小,這樣verticalLayout就會隨着窗口大小的變化而自動調整其中的控件,具體操作如下 
重載widget的resizeEvent函數,依據窗口大小的變化調整動態verticalLayoutWidget

     QWidget * resizeWidget=ui->verticalLayoutWidget;
     QRect resizeRect=resizeWidget->rect();
     static float baseWidth=400;
     static float baseHeight=300;
     static float widgetWidth=resizeRect.width();
     static float widgetHeight=resizeRect.height();
     static float widgetX=ui->verticalLayoutWidget-
>geometry().x();
     static float widgetY=ui->verticalLayoutWidget->geometry().y();
     qDebug()<<resizeRect<<widgetX<<widgetY;
     float horRatio=this->rect().width()/baseWidth;
     float verRatio=this->rect().height()/baseHeight;
     //dajust the position of verticalLayoutWidget
     resizeRect.setX(widgetX*horRatio);
     resizeRect.setY(widgetY*verRatio);

     //resize the verticalLayoutWidget
     resizeRect.setWidth(widgetWidth*horRatio);
     resizeRect.setHeight(widgetHeight*verRatio);
     //set Geometry
     resizeWidget->setGeometry(resizeRect);
 
 


    對於控件的縮放和位置調整,都是依據程序窗口的變化來調整的,這裏選用horRatio,verRatio兩個變量來記錄窗口的寬度方向和高度方向的縮放比例,對於verticalLayoutWidget的寬度和高度來說直接按窗口的縮放比例按等比例縮放即可,對於X,Y按比例縮放 
resizeRect.setX(widgetX*horRatio); 
resizeRect.setY(widgetY*verRatio);

2.1自定義控件縮放
    利用layout對控件進行佈局縮放使用簡單,且無需關注內部細節,但是利用layout進行佈局時,控件的位置和大小受佈局的約束無法實現大小和位置的精確控制,對於界面又複雜佈局要求時layout並不能滿足使用要求,對於這部分控件只能自定義控件的縮放,在程序窗口縮放是實現分辨率的自適應。 
具體的縮放規則與前面類似,在程序窗口尺寸變化時,即首先獲得程序窗口的縮放比例horRatio,verRatio放,然後對各個控件的X,Y以及寬度width和高度height進行相應比例的縮放即可,但是對於每個單獨的控件都進行自定義縮放,實現起來較複雜,因此需要採用通用的方法來對各個控件進行縮放。 
    由於大部分的可以控件都是繼承於QWidget的,因此查找到程序中所有的QWidget對象,然後在實現相應的變換即可。Qt提供了findChildren方法可以查找特定類型的子對象,因此可以利用這個方法來實現查找QWidget的子對象,並進行自定義縮放: 
首先定義結構體來存儲控件的基本尺寸及位置

struct AutoResizeOriginalData
{
    QRect data_rect;
    QFont data_font;
};
//define a map to store the resize items
QMap<QWidget*,AutoResizeOriginalData> m_resizeMap;
然後在查找窗口中全部的QWidget對象,並記錄其初始位置

QWidget *item=NULL;
AutoResizeOriginalData resizeData;
QRect tmp;
QList<QLabel*> _labelList=m_autoResizeObj->findChildren<QLabel *>();
for(auto it=_labelList.begin();it!=_labelList.end();it++)
{
    item=*it;
    tmp=item->geometry();
    tmp.setX(item->x());
    tmp.setY(item->y());
    tmp.setWidth(abs(tmp.width()));
    tmp.setHeight(abs(tmp.height()));
    resizeData.data_rect=tmp;
    resizeData.data_font=item->font();
    m_resizeMap[item]=resizeData;
}

再重載窗口對象的resizeEvent函數,對各個控件進行自適應控制

m_horRatio=this->rect().width()/m_baseWidth;
m_verRatio=this->rect().height()/m_baseHeight;
QMapIterator<QWidget*, AutoResizeOriginalData> _itarator(m_resizeMap);
        while(_itarator.hasNext())
        {
            _itarator.next();
            QWidget* _item=_itarator.key();
            QRect tmp=_itarator.value().data_rect;
            tmp.setWidth(tmp.width()*m_horRatio);
            tmp.setHeight(tmp.height()*m_verRatio);
            QRect after=QRect(tmp.x()*m_horRatio,tmp.y()*m_verRatio,tmp.width(),tmp.height());    
_item->setGeometry(after);
        }
    如果界面中有一部分控件包含在layout中,在查找QWidget對象時會把這一部分控件也包含在其中,這樣的話在進行尺寸縮放時回合layout相互影響,導致界面發成錯位,應去除這些控件,交由layout來控制其縮放,具體操作如下: 
首先定義一個函數用於移除其所有子對象

void AutoResize::ignoreAllChiledren(QObject* obj)
{
    QList<QObject*> children=obj->children();
    for(auto it=children.begin();it!=children.end();it++)
    {
        QWidget *item=qobject_cast<QWidget*>(*it);
        m_ignoreItem.push_back(item);
        AutoResizeOriginalData resizeData;
        if(!item)
            continue;
        m_resizeMap.remove(item);
    }
}
查找所有的layout:

QString desName="widget";
QList<QLayout*> layoutList=m_autoResizeObj->findChildren<QLayout*>();
for(auto it=layoutList.begin();it!=layoutList.end();it++)
{
    QString objName=(*it)->parent()->objectName();
    if(objName.contains(desName))
    {
        //need to find the items in layout by the parent widget of layout                
        QWidget* layoutWidget=qobject_cast<QWidget*>((*it)->parent());
        ignoreAllChiledren(layoutWidget);
    }
}
2.3字體縮放
    Qt中的字體Qfont定義字體大小是有兩種方式,一種是PixelSize,另一種是PointSize,PixelSize實際上是以像素爲單位,即PixelSize的大小即爲實際的像素大小。PointSize的單位不是像素,它是以字體在屏幕實際顯示的大小爲單位的,它和屏幕的分辨率以及屏幕的真實尺寸相關,即它的單位即爲屏幕上顯示的字體大小。 
    對於相同尺寸不同分辨率的屏幕,通過設置PointSize大小的字體,不同分辨率的屏幕上顯示的實際字體的大小是一樣的,通過設置PointSize的字體來說,字體大小是隨着屏幕大小以及分辨率自適應的,因此無須處理字體的縮放;但是對於設置PixelSize大小的字體來說,由於所佔分辨率大小固定,因此在相同尺寸上更高分辨率的屏幕上,由於其單位長度內的像素點數更多,即像素密度更大,因此對於更好分辨率的屏幕來說,字體會看起來小一些,要處理這種情況,一種辦法就是所有字體都用PointSize來表示大小,但對於已經採用PixelSize的字體來說,就要對其進行控制縮放。 
首先創建用於縮放字體的函數

void AutoResize::fontAutoResize(QWidget *obj,int fontSize) 

if(fontSize<=0) 
return; 
bool hasTextStyle=false; 
fontSize*=m_fontRatio; 
QFont changedFont; 
changedFont=obj->font(); 
changedFont.setPixelSize(fontSize); 
obj->setFont(changedFont); 
}

    由於控件都繼承與QWidget,且QWidget具有字體屬性,因此可以通過QWidget來查找自控件,並記錄相應的字體信息

QWidget *item=NULL;
AutoResizeOriginalData resizeData;
QRect tmp;
QList<QLabel*> _labelList=m_autoResizeObj->findChildren<QLabel *>();
for(auto it=_labelList.begin();it!=_labelList.end();it++)
{
    item=*it;
    tmp=item->geometry();
    tmp.setX(item->x());
    tmp.setY(item->y());
    tmp.setWidth(abs(tmp.width()));
    tmp.setHeight(abs(tmp.height()));
    resizeData.data_rect=tmp;
    resizeData.data_font=item->font();
    m_resizeMap[item]=resizeData;
    //the pixelsize !=-1 when set font size by pixelsize
    if(resizeData.pixelSize()!=-1)
    {
        m_fontMap[item]=resizeData;
    }
}

計算字體縮放比例
1
void AutoResize::calculateResizeRatio()
{
    m_horRatio=m_autoResizeObj->width()/m_baseWidth;
    m_verRatio=m_autoResizeObj->height()/m_baseHeight;
    m_fontRatio=m_horRatio<m_verRatio?m_horRatio:m_verRatio;
}
重載resizeEvent函數,縮放字體

void AutoResize::doAutoResize()
{
    calculateResizeRatio();
    if(m_autoResize)
    {
        QMapIterator<QWidget*,AutoResizeOriginalData> _fontIt(m_fontMap);
        while(_fontIt.hasNext())
        {
            _fontIt.next();
            QWidget* _item=_fontIt.key();
              changedFont=_fontIt.value().data_font;
              fontAutoResize(_item,changedFont.pointSize());
        }
    }
}
本章節相應的代碼和示例請查看本人GitHub

三、qml中的分辨率自適應
    qml中沒有提供類似QtWidget中的layout進行佈局,因此qml中的所有控件的分辨率自適應都需要自定義實現,其自適應原理與QtWidget中類似,都是在程序窗口發生變化時,對窗口尺寸變化事件進行響應,依據父窗口的中寬度以及高度的縮放比例,分辨對各個子對象進行位置以及尺寸的變換。 
下面實現一種通用的分辨率自適應的組件

// AutoResize.qml
import QtQuick 2.0

Item {
    id:globalResize
    property var targetItem: parent  // the parent of all items
property bool fixedAspectRatio: false // Else zoom from width and height
property bool fontAccordingToMax: false
    property bool ifAutoResize: true
    property string ignoreAll: "ignoreAll"
    property string ignoreChildren: "ignoreChildren"
    //變換比例
    property real horizontalRatio: 1.0
    property real verticalRatio: 1.0
    property real fontRatio: 1.0

    property var targetItemGeometry
    property var childrenItemGeometry
    property var childrenText
    property real fontSizeScaleFactor: 1.0
    property bool isBegin: false
    signal resized()
    Component.onCompleted: {
        begin();
    }

    Component {
        id: connections

        Connections {
            target: targetItem

            onWidthChanged: {
                resize();
            }
            onHeightChanged:
            {
                resize();
            }
        }
    }
    Loader {
        Component.onCompleted: {
            sourceComponent = connections;
        }
    }
}
定義begin( )函數用於纔開始時獲取程序窗口中要進行縮放的所有子對象的,並記錄其基本的尺寸信息,具體試下如下

function begin() {
        var _childrenItemGeometry=new Array;
        targetItemGeometry = new Object;
        targetItemGeometry["width"] = targetItem.width;
        targetItemGeometry["height"] = targetItem.height;
        var children = targetItem.children;
        for(var index = 1; index < children.length; index++)
        {
            var currentItem = children[index];
            var buf = new Object;

            buf["item"] = currentItem;
            buf["name"]=currentItem.objectName;
            buf["x"] = currentItem.x;
            buf["y"] = currentItem.y;
            buf["centerX"] = currentItem.x + (currentItem.width / 2);
            buf["centerY"] = currentItem.y + (currentItem.height / 2);
            buf["width"] = currentItem.width;
            buf["height"] = currentItem.height;

            //to scale the font size
            buf["fontSize"]=0;
            if(currentItem.font!=undefined)
            {
                buf["fontSize"]=currentItem.font.pointSize
            }
            if(buf["name"]==ignoreAll)
            {
                continue;
            }
            else if(buf["name"]==ignoreChildren)
            {
                _childrenItemGeometry.push(buf)
            }
            else
            {
                _childrenItemGeometry.push(buf)
                getAllChildren(_childrenItemGeometry,currentItem)
            }
        }
        childrenItemGeometry=_childrenItemGeometry
        isBegin = true;
    }
    getAllChildren用於獲取某個對象的全部子對象,由於qml中的children只能獲得當前對象的直系子對象,對孫子對象等是獲取不到的,因此需要遞歸調用才能獲取全部子對象

function getAllChildren(_childrenItemGeometry,target)
    {
        var children = target.children;
        for(var index = 0; index < children.length; index++)
        {
            var currentItem = children[index];
            var buf = new Object;
            buf["item"] = currentItem;
            buf["name"]=currentItem.objectName;
            buf["x"] = currentItem.x;
            buf["y"] = currentItem.y;
            buf["centerX"] = currentItem.x + (currentItem.width / 2);
            buf["centerY"] = currentItem.y + (currentItem.height / 2);
            buf["width"] = currentItem.width;
            buf["height"] = currentItem.height;
            buf["fontSize"]=0;
            if(currentItem.font!=undefined)
            {
                buf["fontSize"]=currentItem.font.pointSize
            }
            if(buf["name"]=="ingnoreAll")
            {
                continue;
            }
            else if(buf["name"]=="ingnoreChildren")
            {
                _childrenItemGeometry.push(buf)
            }
            else
            {
                _childrenItemGeometry.push(buf)
                getAllChildren(_childrenItemGeometry,currentItem)
            }
        }
    }

    resize函數是在targetItem對象的寬度或這高度發生變化時出發的,用於實現所有子對象的縮放

    function resize() {
        if(isBegin&&ifAutoResize)
        {
            //calculate the ratio
            horizontalRatio = targetItem.width / targetItemGeometry["width"];
            verticalRatio = targetItem.height / targetItemGeometry["height"];
            fontRatio=horizontalRatio>verticalRatio?verticalRatio:horizontalRatio;
            for(var index = 0; index < childrenItemGeometry.length; index++)
            {
                var currentItem=childrenItemGeometry[index]
        //adjust the size of item
                childrenItemGeometry[index]["item"].width  = childrenItemGeometry[index]["width"] * horizontalRatio;
                childrenItemGeometry[index]["item"].height = childrenItemGeometry[index]["height"] * verticalRatio;
        //adjust the position of item
                childrenItemGeometry[index]["item"].x = childrenItemGeometry[index]["x"] * horizontalRatio;
                childrenItemGeometry[index]["item"].y = childrenItemGeometry[index]["y"] * verticalRatio;

                if(childrenItemGeometry[index]["item"].font!=undefined)
                {
                    childrenItemGeometry[index]["item"].font.pixelSize = childrenItemGeometry[index]["fontSize"]*fontRatio*fontSizeScaleFactor
                }
            }
           //emit the resize signal
            globalResize.resized();
        }
    }

AutoResize.qml的使用示例

//AutoResizeDemo
import QtQuick 2.6
import QtQuick.Controls 2.0

Rectangle {
    visible: true
    width: 400
    height: 300
    AutoResize{
        id:resizeHandler
    }
    Rectangle{
        x:20
        y:20
        color: "red"
        width: 100
        height: 50
    }
    Rectangle{
        x:200
        y:20
        color: "yellow"
        width: 100
        height: 50
        Text {
            anchors.centerIn: parent
            text: qsTr("Text Size Test")
        }
    }
    Row{
        x:20
        y:150
        spacing: 4*resizeHandler.horizontalRatio
        Button{
            text: "Button1"
        }
        Button{
            text: "Button2"
        }
    }
    ComboBox{
        x:20
        y:200
        model: ["Combobox Test"]
    }

    ListView{
        y:150
        x:250
        width: 100
        height:200
        model: 5
        header:Rectangle{
            height: 20
            Text {
                text: qsTr("ListView Test")
            }
        }

        delegate: Rectangle{
            height: 20
            border.width: 2
            Text {
                text: index
            }
        }
    }
}


本章節相應的實例代碼請查看本人的GitHub
--------------------- 
作者:matengxiao 
來源:CSDN 
原文:https://blog.csdn.net/matengxiao/article/details/52853332 
版權聲明:本文爲博主原創文章,轉載請附上博文鏈接!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章