affine工程难点、亮点汇总
工程存放的路徑在:Examples\Qt-XX.XX.XX\widgets\painting\affine
其中XX.XX.XX為Qt的版本號(hào),如:5.14.1。
該工程有如下個(gè)人認(rèn)為的難點(diǎn)、亮點(diǎn):
- 《利用QPainter、QColor繪制黑白棋盤(pán)功能》
- 《QPushButton延時(shí)單擊功能》
- 《利用QCommonStyle繪制自定義的窗體部件》
對(duì)XFormView類的drawPixmapType函數(shù)解釋:
void XFormView::drawPixmapType(QPainter *painter) {QPointF center(m_pixmap.width() / qreal(2), m_pixmap.height() / qreal(2));/* 將painter移動(dòng)(ctrlPoints.at(0) - center)距離,也即將HoverPoints類對(duì)象pts的第一個(gè)控制點(diǎn)移動(dòng)到位圖的中心位置,以便第一個(gè)控制點(diǎn)繪制在位圖的中心*/painter->translate(ctrlPoints.at(0) - center);// 進(jìn)行變換,以便位圖繞著自己的中心軸旋轉(zhuǎn)painter->translate(center);painter->rotate(m_rotation);painter->scale(m_scale, m_scale);painter->shear(0, m_shear);painter->translate(-center);.............................// 其它代碼 }如果被繪制對(duì)象(本例是指m_pixmap對(duì)象表示的圖片)的中心點(diǎn)和繪圖對(duì)象painter的中心點(diǎn)不重合,這時(shí)候旋轉(zhuǎn)被繪制對(duì)象,則這個(gè)對(duì)象會(huì)圍繞painter設(shè)置的中心點(diǎn)進(jìn)行旋轉(zhuǎn),會(huì)轉(zhuǎn)一個(gè)大圈。那么怎么做才能讓它在任何位置的時(shí)候,都圍繞自己的軸心進(jìn)行旋轉(zhuǎn)?解決思路如下:
1. 先保存物體在世界坐標(biāo)系下的坐標(biāo),即物體在世界坐標(biāo)系下的中心點(diǎn)坐標(biāo)
 2. 再將物體移動(dòng)到世界坐標(biāo)系的原點(diǎn)。
 3. 在世界坐標(biāo)系的原點(diǎn)旋轉(zhuǎn)好后,再移動(dòng)回原來(lái)的位置,即步驟1中的提到的坐標(biāo)。
上段代碼的思路就是按這樣來(lái)的。
- drawTextType函數(shù)同drawPixmapType函數(shù),不再贅述。
- HoverPoints分析
構(gòu)造函數(shù)分析:
HoverPoints::HoverPoints(QWidget *widget, PointShape shape): QObject(widget) {m_widget = widget;widget->installEventFilter(this);widget->setAttribute(Qt::WA_AcceptTouchEvents);m_connectionType = CurveConnection;m_sortType = NoSort;m_shape = shape;m_pointPen = QPen(QColor(255, 255, 255, 191), 1);m_connectionPen = QPen(QColor(255, 255, 255, 127), 2);m_pointBrush = QBrush(QColor(191, 191, 191, 127));m_pointSize = QSize(11, 11);m_currentIndex = -1;m_editable = true;m_enabled = true;connect(this, &HoverPoints::pointsChanged,m_widget, QOverload<>::of(&QWidget::update)); }構(gòu)造函數(shù)初始化了一些變量,對(duì)各個(gè)變量作用解釋如下:
- ?m_widget:外層傳入的窗體部件對(duì)象,也即HoverPoints類附屬在哪個(gè)窗體上,通過(guò)通讀代碼可以發(fā)現(xiàn)其是指外層的XFormView窗體對(duì)象。
- 函數(shù)的第5行為外層傳入的窗體部件對(duì)象安裝了事件過(guò)濾器。installEventFilter函數(shù)的用法請(qǐng)參見(jiàn)《installEventFilter、eventFilter函數(shù)理解》
- 第6行設(shè)置觸屏事件,從后續(xù)代碼可以看到本例支持觸屏操作。
- m_connectionType:連接類型。取值為:直線、曲線(貝塞爾曲線)和無(wú)
- m_sortType:?點(diǎn)的排序類型。取值為:不排序、按點(diǎn)的x坐標(biāo)排序、按點(diǎn)的y坐標(biāo)排序。
- m_shape:懸浮點(diǎn)的現(xiàn)狀。取值為:圓形、矩形。
- m_pointPen:用于畫(huà)懸浮點(diǎn)的畫(huà)筆。
- m_connectionPen:用于畫(huà)連接線的畫(huà)筆。
- m_pointBrush:用于點(diǎn)的畫(huà)刷。
- m_pointSize:點(diǎn)所在形狀的外圍矩形寬高。
- m_currentIndex:當(dāng)前在控制點(diǎn)容器m_points中的點(diǎn)的索引。
- m_editable:是否可編輯。
- m_enabled:是否可用。
? ? ?eventFilter 中的鼠標(biāo)左鍵按下流程分析:
? ? ?如下鼠標(biāo)左鍵按下時(shí)的流程:(圖片下載到本地放大看):
說(shuō)明:
起始m_points中只有A、B兩個(gè)控制點(diǎn),則畫(huà)出上圖類似的A到B的線,當(dāng)鼠標(biāo)左鍵單擊的點(diǎn)為C時(shí),且C在A、B之間時(shí),則畫(huà)出如下直線:
?當(dāng)C在B 的右側(cè)時(shí),則畫(huà)出如下的:
3 .鼠標(biāo)左鍵按下時(shí),當(dāng)步驟2完成后,將符合條件的鼠標(biāo)左鍵單擊時(shí)所在的點(diǎn)插入m_points、m_locks,插入的索引號(hào)為步驟2記錄的索引號(hào)然后調(diào)用?firePointChange函數(shù)。
? ?4. firePointChange函數(shù)中根據(jù)m_sortType表示的排序規(guī)則將m_points排序。排序后再次從m_points中找到步驟2鼠標(biāo)左鍵按下時(shí)的點(diǎn)在m_points中的索引m_currentIndex,然后發(fā)送pointsChanged信號(hào)。
eventFilter 中的觸摸事件流程分析:
即QEvent::TouchBegin、QEvent::TouchUpdate、Qt::TouchPointReleased、QEvent::TouchEnd事件。其中最需要搞懂的是QEvent::TouchBegin、QEvent::TouchUpdate中的Qt::TouchPointPressed,實(shí)現(xiàn)代碼如下:
// find the point, move it const auto mappedPoints = m_fingerPointMapping.values(); QSet<int> activePoints = QSet<int>(mappedPoints.begin(), mappedPoints.end()); int activePoint = -1; qreal distance = -1; const int pointsCount = m_points.size(); const int activePointCount = activePoints.size(); if (pointsCount == 2 && activePointCount == 1) { // only two pointsactivePoint = activePoints.contains(0) ? 1 : 0; } else {for (int i = 0; i < pointsCount; ++i) {if (activePoints.contains(i))continue;qreal d = QLineF(touchPoint.pos(), m_points.at(i)).length();if ((distance < 0 && d < 12 * pointSize) || d < distance) {distance = d;activePoint = i;}} } if (activePoint != -1) {m_fingerPointMapping.insert(touchPoint.id(), activePoint);movePoint(activePoint, touchPoint.pos()); }其基本思路是:將觸摸點(diǎn)的id和觸摸點(diǎn)索引值作為QHash的鍵值對(duì)插入到類型為QHash的成員變量m_fingerPointMapping,以后移動(dòng)、釋放都從m_fingerPointMapping取出相應(yīng)的點(diǎn)進(jìn)行操作。
eventFilter 中的QEvent::Paint流程分析:
代碼如下:
QWidget *that_widget = m_widget;m_widget = nullptr;QCoreApplication::sendEvent(object, event);m_widget = that_widget;paintPoints();return true;這里需要著重說(shuō)明的是:在發(fā)送QEvent::Paint事件之前必須m_widget將先保存起來(lái)(第1句代碼),然后將m_widget設(shè)置為nullptr(第2句),發(fā)送完后再將m_widget恢復(fù)為原來(lái)的。設(shè)置為nullptr是避免eventFilter 函數(shù)無(wú)限遞歸調(diào)用導(dǎo)致棧耗盡程序崩潰,恢復(fù)回來(lái)為了下次再次發(fā)送QEvent::Paint事件。
updateCtrlPoints函數(shù)分析:
void XFormView::updateCtrlPoints(const QPolygonF &points) {QPointF trans = points.at(0) - ctrlPoints.at(0);if (qAbs(points.at(0).x() - points.at(1).x()) < 10&& qAbs(points.at(0).y() - points.at(1).y()) < 10)pts->setPoints(ctrlPoints);if (!trans.isNull()) {ctrlPoints[0] = points.at(0);ctrlPoints[1] += trans;pts->setPoints(ctrlPoints);}ctrlPoints = points;QLineF line(ctrlPoints.at(0), ctrlPoints.at(1));m_rotation = 360 - QLineF(0, 0, 1, 0).angleTo(line);if (trans.isNull())emit rotationChanged(int(m_rotation * 10)); }第3行代碼:就是計(jì)算通過(guò)信號(hào)pointsChanged發(fā)送過(guò)來(lái)的m_points第一個(gè)控制點(diǎn)和當(dāng)前的第一個(gè)控制點(diǎn)的在x、y坐標(biāo)上的偏移量。
第5-7行:檢測(cè)m_points第1個(gè)控制點(diǎn)、第2個(gè)控制點(diǎn)x、y坐標(biāo)上的偏移量都小于10,如果是,則控制點(diǎn)還是設(shè)置為當(dāng)前控制點(diǎn)。
第8-11行:如果第3句代碼算出的偏移量不為空,則當(dāng)前第1個(gè)控制點(diǎn)更新為m_points中的第1個(gè)點(diǎn),注意:在m_sortType為NoSort時(shí),且鼠標(biāo)左鍵按下時(shí),m_points中的第1個(gè)點(diǎn)為鼠標(biāo)左鍵按下時(shí)的點(diǎn),而在XFormView類的paint函數(shù)繪制函數(shù)如:drawPixmapType、drawTextType經(jīng)過(guò)如下代碼將繪制中心移動(dòng)到了被繪制對(duì)象(如:圖片、文本)的中心了(參見(jiàn)前面對(duì)drawPixmapType的分析):
painter->translate(ctrlPoints.at(0) - center);? ? ? ? 所以這樣造成的現(xiàn)象是鼠標(biāo)左鍵在哪單擊,被繪制物體的中心就移動(dòng)到鼠標(biāo)左鍵單擊的點(diǎn)。
當(dāng)前第2個(gè)控制點(diǎn)累加第3行產(chǎn)生的偏移量,并重新設(shè)置HoverPoints類的控制點(diǎn)容器m_points
第15-16行根據(jù)更新后的控制點(diǎn)ctrlPoints算出其和水平線的夾角,并設(shè)置旋轉(zhuǎn)成員變量m_rotation。根據(jù)Qt的元系統(tǒng)屬性技術(shù)( 屬性通過(guò)Q_PROPERTY關(guān)鍵字標(biāo)識(shí)),一旦m_rotation被更新,則XFormView類的setRotation函數(shù)會(huì)自動(dòng)調(diào)用,從而導(dǎo)致被繪制的物體呈現(xiàn)旋轉(zhuǎn)。
程序bug說(shuō)明:
HoverPoints類的m_sortType 為 XSort或YSort時(shí),會(huì)崩潰,更改如下:
movePoint函數(shù)加入如下代碼:
void HoverPoints::movePoint(int index, const QPointF &point, bool emitUpdate) {// 索引越界了,要判斷下if ((index < 0) || (m_points.size() <= index) ){return;}if ( m_locks.size() <= index ){return;}// 其它代碼 }firePointChange()函數(shù)加入如下代碼:
void HoverPoints::firePointChange() {// printf("HoverPoints::firePointChange(), current=%d\n", m_currentIndex);if (m_sortType != NoSort) {QPointF oldCurrent;/*if (m_currentIndex != -1) {*/// 這里有崩潰,索引越界了,要判斷下if ((0 <= m_currentIndex) && (m_currentIndex < m_points.size())){oldCurrent = m_points[m_currentIndex];}// 其它代碼}?XFormView類的updateCtrlPoints函數(shù)加入如下判斷:
void XFormView::updateCtrlPoints(const QPolygonF &points) {// 右鍵在控制點(diǎn)單擊時(shí),這里有崩潰,索引越界了,要判斷下if (points.size() < 2){return;}.................. }未完,待續(xù)!
總結(jié)
以上是生活随笔為你收集整理的affine工程难点、亮点汇总的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
 
                            
                        - 上一篇: 消息称 OPPO Find X6 系列手
- 下一篇: installEventFilter、e
