Qt Quick实现的涂鸦程序
之前一直以为 Qt Quick 里 Canvas 才干够自绘。后来发觉不是,原来还有好几种方式都能够画图!
能够使用原始的 OpenGL(Qt Quick 使用 OpenGL 渲染)。能够构造QSGNode 来画图,还能够使用 QPainter !哇。 QPainter 我非常熟悉啊。于是,我用 QPainter 结合 QML 实现了一个简单的涂鸦程序: PaintedItem 。它有下列功能:
- 设置线条宽度
- 设置线条颜色
- 设置背景颜色
- 清除涂鸦
- 无限级undo
程序非常简陋。效果例如以下:
watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvZm9ydW9r/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" alt="" />
图1 PaintedItem效果图
程序尽管简单。但也还是有一些新内容之前没有提到:
- QQuickPaintedItem
- C++实现QML可视图元(Item)
- 自己定义图元怎样处理鼠标事件
以下咱们一个一个来说一下。
版权全部 foruok ,转载请注明出处:http://blog.csdn.net/foruok 。
QQuickPaintedItem
Qt Quick 的核心是 Scene Graph ,能够在 Qt 帮助的索引模式下以 “Scene Graph” 为keyword来检索学习。 Scene Graph 的设计思想和 QGraphicsView/QGraphicsScene 框架相似,一个场景。非常多图元往场景里放。不同之处是 Item 的绘制。 QGraphicsView 框架里是通过 View 的画图事件来驱动 Item 的绘制,QGraphicsItem 有一个 paint() 虚函数,仅仅要你从 QGraphicsItem 继承来的 Item 实现这个 paint() 函数。就能够往 QPaintDevice 上绘制了,逻辑直接。而 Qt Quick 的绘制,事实上另有一个渲染线程, Scene 里的 Item 没有 paint() 这样的直观的画图函数,仅仅有一个 updatePaintNode() 方法让你来构造你的 Item 的几何表示。当程序轮转到渲染循环时。渲染循环把全部 Item 的 QSGNode 树取出来绘制。
updatePaintNode() 这样的绘制的方式非常不直观,它来自 OpenGL 或者 Direct 3D 的画图模式:你构造图元的几何表示,别人会在某一个时刻依据你提供的材料帮你绘制,就像你扔一袋垃圾到门口。过一阵子有人会来帮你收走这样的感觉。用惯 Qt Widgets 和 QPainter 的开发人员可能会不适应这样的方式,所以 Qt Quick 提供了一种兼容老习惯的方式:引入 QQuickPaintedItem ,使用 QPainter 绘制。
一般地,你能够这样理解: QQuickPaintedItem 使用 Qt Widgets 里惯常的 2D 画图方式。将你想要的线条、图片、文字等绘制到一个内存中的 QImage 上。然后把这个 QImage 作为一个 QSGNode 放在那里等着 Qt Quick 的渲染线程来取走它,把它绘制到实际的场景中。依照这样的理解, QQuickPaintedItem 会多个画图步骤。有性能上的损失!
只是为了开发方便,有时候这一点点性能损失是能够承受的——仅仅要你的应用仍然能够流畅执行。
QQuickPaintedItem 是一切想使用 QPainter 来画图的 Qt Quick Item 的基类。它有一个纯虚函数—— paint(QPainter * painter) ,你自己定义的 Item 仅仅要实现 paint() 虚函数就能够了。
QQuickPaintedItem 是 QQuickItem 的派生类。 QQuickItem 的 boundingRect() 方法返回一个 Item 的矩形,你能够依据它来绘制你的 Item 。fillColor() 返回 Item 的填充颜色(默认是透明的), Qt Quick 会使用这个颜色在 paint() 方法调用前绘制你的 Item 的背景。
setFillColor() 能够改变填充颜色。
Qt Quick 提供了一个“Scene Graph - Painted Item”演示样例来演示 QQuickPaintedItem 的使用方法,你能够參考。
C++实现QML可视图元
Qt Quick 提供的相当一部分图形元素都是在 C++ 中实现后导出到 QML 环境中的,比方 Text 。
那我们也能够这么做,仅仅要你从 QQuickItem(相应 QML 中的 Item 元素) 继承来实现你的 C++ 类就可以。
我们的演示样例要使用 QPainter 画图。所以从 QQuickPaintedItem 继承,重写 paint() 方法。
完毕了 C++ 类,导出到 QML 环境中。就能够像使用 QML 内建元素一样来使用我们导出的类。怎样导出又怎样在 QML 中使用,请參看《Qt Quick 之 QML 与 C++ 混合编程具体解释》。
自己定义图元怎样处理鼠标事件
在 QML 中我们一直使用 MouseArea 来处理鼠标事件。 MouseArea 相应 C++ 中的 QQuickMouseArea 类,事实上也是 QQuickItem 的派生类。
事实上 QQuickItem 定义了一系列处理鼠标事件的虚函数。比方 mousePressEvent 、 mouseMoveEvent 、 mouseMoveEvent 等。它本身就能够处理鼠标事件。仅仅只是 QQuickItem 没有导出这些函数。我们在 QML 中无法使用。
而之所以引入 QQuickMouseArea (QML 中的 MouseArea ),是为了方便鼠标事件的处理,你不须要为每一个 Item 像 QWidget 那样来重写非常多方法。那样真的非常烦的, QML 的这样的方式尽管多用了一个对象。但是更方便一些。但是我们的 PaintedItem 类,假设绕回到 QML 中使用 MouseArea 来处理鼠标事件,那我们跟踪鼠标轨迹来绘制线条时,就须要不断地将鼠标事件中携带的像素点信息再回传到 C++ 中来。非常麻烦。性能也不好,所以我们直接重写 QQuickItem 的相关虚函数来处理鼠标事件。
我们知道 MouseArea 有一个 acceptedButtons 属性,能够设置 Item 处理哪个鼠标按键,而实际上。“要处理的鼠标按键”这个信息。是保存在 QQuickItem 中的,通过 setAcceptedMouseButtons() 方法来设置。
默认情况下。 QQuickItem 不处理不论什么鼠标按键,所以我们要处理鼠标按键,必须在我们的 PaintedItem 中来设置一下,就像 MouseArea 那样。我们的演示样例中,在 PaintedItem 的构造函数中做了这件事:
PaintedItem::PaintedItem(QQuickItem *parent)
: QQuickPaintedItem(parent)
, m_element(0)
, m_bEnabled(true)
, m_bPressed(false)
, m_bMoved(false)
, m_pen(Qt::black)
{
setAcceptedMouseButtons(Qt::LeftButton);
}
如代码所看到的,我们仅仅处理鼠标左键。假设你不设置这个。你收不到不论什么鼠标事件。
PaintedItem 源代码分析
由于我们实现的功能简单,源代码也不复杂。
自己定义 Item
先看 PaintedItem.h :
#ifndef PAINTEDITEM_H
#define PAINTEDITEM_H
#include <QQuickPaintedItem>
#include <QVector>
#include <QPointF>
#include <QLineF>
#include <QPen> class ElementGroup
{
public:
ElementGroup()
{
} ElementGroup(const QPen &pen)
: m_pen(pen)
{
} ElementGroup(const ElementGroup &e)
{
m_lines = e.m_lines;
m_pen = e.m_pen;
} ElementGroup & operator=(const ElementGroup &e)
{
if(this != &e)
{
m_lines = e.m_lines;
m_pen = e.m_pen;
}
return *this;
} ~ElementGroup()
{
} QVector<QLineF> m_lines;
QPen m_pen;
}; class PaintedItem : public QQuickPaintedItem
{
Q_OBJECT
Q_PROPERTY(bool enabled READ isEnabled WRITE setEnabled)
Q_PROPERTY(int penWidth READ penWidth WRITE setPenWidth)
Q_PROPERTY(QColor penColor READ penColor WRITE setPenColor) public:
PaintedItem(QQuickItem *parent = 0);
~PaintedItem(); bool isEnabled() const{ return m_bEnabled; }
void setEnabled(bool enabled){ m_bEnabled = enabled; } int penWidth() const { return m_pen.width(); }
void setPenWidth(int width) { m_pen.setWidth(width); } QColor penColor() const { return m_pen.color(); }
void setPenColor(QColor color) { m_pen.setColor(color); } Q_INVOKABLE void clear();
Q_INVOKABLE void undo(); void paint(QPainter *painter); protected:
void mousePressEvent(QMouseEvent *event);
void mouseMoveEvent(QMouseEvent *event);
void mouseReleaseEvent(QMouseEvent *event);
void purgePaintElements(); protected:
QPointF m_lastPoint;
QVector<ElementGroup*> m_elements;
ElementGroup * m_element; // the Current ElementGroup
bool m_bEnabled;
bool m_bPressed;
bool m_bMoved;
QPen m_pen; // the Current Pen
}; #endif // PAINTEDITEM_H
说下 ElementGroup 这个类。它保存了鼠标左键按下、移动、直到左键释放这一个动作序列产生的须要绘制的线条,保存在成员变量 m_lines 中,而绘制这些线条所用的画笔则由 m_pen 表示。
在 PaintedItem 中,成员变量 m_elements 表示画图过程中的全部动作序列。 m_element 则指向当前的动作序列, m_pen 代表用户所配置的画笔。
其他的方法都比較直观,不再赘述。
以下是 PaintedItem.cpp :
#include "PaintedItem.h"
#include <QPainter>
#include <QPen>
#include <QBrush>
#include <QColor>
#include <QDebug> PaintedItem::PaintedItem(QQuickItem *parent)
: QQuickPaintedItem(parent)
, m_element(0)
, m_bEnabled(true)
, m_bPressed(false)
, m_bMoved(false)
, m_pen(Qt::black)
{
setAcceptedMouseButtons(Qt::LeftButton);
} PaintedItem::~PaintedItem()
{
purgePaintElements();
} void PaintedItem::clear()
{
purgePaintElements();
update();
} void PaintedItem::undo()
{
if(m_elements.size())
{
delete m_elements.takeLast();
update();
}
} void PaintedItem::paint(QPainter *painter)
{
painter->setRenderHint(QPainter::Antialiasing); int size = m_elements.size();
ElementGroup *element;
for(int i = 0; i < size; ++i)
{
element = m_elements.at(i);
painter->setPen(element->m_pen);
painter->drawLines(element->m_lines);
}
} void PaintedItem::mousePressEvent(QMouseEvent *event)
{
m_bMoved = false;
if(!m_bEnabled || !(event->button() & acceptedMouseButtons()))
{
QQuickPaintedItem::mousePressEvent(event);
}
else
{
//qDebug() << "mouse pressed";
m_bPressed = true;
m_element = new ElementGroup(m_pen);
m_elements.append(m_element);
m_lastPoint = event->localPos();
event->setAccepted(true);
}
} void PaintedItem::mouseMoveEvent(QMouseEvent *event)
{
if(!m_bEnabled || !m_bPressed || !m_element)
{
QQuickPaintedItem::mousePressEvent(event);
}
else
{
//qDebug() << "mouse move";
m_element->m_lines.append(QLineF(m_lastPoint, event->localPos()));
m_lastPoint = event->localPos();
update();
}
} void PaintedItem::mouseReleaseEvent(QMouseEvent *event)
{
if(!m_element || !m_bEnabled || !(event->button() & acceptedMouseButtons()))
{
QQuickPaintedItem::mousePressEvent(event);
}
else
{
//qDebug() << "mouse released";
m_bPressed = false;
m_bMoved = false;
m_element->m_lines.append(QLineF(m_lastPoint, event->localPos()));
update();
}
} void PaintedItem::purgePaintElements()
{
int size = m_elements.size();
if(size > 0)
{
for(int i = 0; i < size; ++i)
{
delete m_elements.at(i);
}
m_elements.clear();
}
m_element = 0;
}
说一下“清除”功能的实现,当你点击图1中的“清除”button时,会调用 PaintedItem 的 clear() 方法, clear() 内部调用 purgePaintElements() 。把 m_elements 内保存的全部画图序列都删除,再调用 update() 方法触发又一次绘制。
undo() 方法相应界面上的“撤销”功能,它删除近期的一个画图序列,然后触发绘制。
如今我们说一下画图序列的生成逻辑。
在 mousePressEvent() 中生成一个新的画图序列,在 mouseMoveEvent() 中讲当前点和上一个点组合为一条线,增加当前画图序列( m_element ),当 mouseReleaseEvent() 被调用时,把鼠标左键抬起时的指针位置的坐标也处理了,这样一个完整的画图序列就生成了。
导出自己定义Item
直接看代码(main.cpp ):
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
qmlRegisterType<PaintedItem>("an.qml.Controls", 1, 0, "APaintedItem"); QQmlApplicationEngine engine;
engine.load(QUrl(QStringLiteral("qrc:///main.qml"))); return app.exec();
}
QML文档
有两个 QML 文档, main.qml 负责主界面, ColorPicker.qml 实现了颜色选择button。
main.qml
main.qml 文档没什么好说的了,PaintedItem 导出为 APaintedItem ,它的使用与一般的 QML 元素一致。以下是完整的 main.qml :
import QtQuick 2.2
import QtQuick.Window 2.1
import an.qml.Controls 1.0
import QtQuick.Controls 1.2
import QtQuick.Layouts 1.1
import QtQuick.Controls.Styles 1.2 Window {
visible: true;
minimumWidth: 600;
minimumHeight: 480; Rectangle {
id: options;
anchors.left: parent.left;
anchors.right: parent.right;
anchors.top: parent.top;
implicitHeight: 70;
color: "lightgray";
Component{
id: btnStyle;
ButtonStyle {
background: Rectangle {
implicitWidth: 70;
implicitHeight: 28;
border.width: control.hovered ? 2 : 1;
border.color: "#888";
radius: 4;
gradient: Gradient {
GradientStop { position: 0 ; color: control.pressed ? "#ccc" : "#eee" }
GradientStop { position: 1 ; color: control.pressed ? "#aaa" : "#ccc" }
}
} label: Text {
text: control.text;
font.pointSize: 12;
color: "blue";
horizontalAlignment: Text.AlignHCenter;
verticalAlignment: Text.AlignVCenter;
}
}
}
ColorPicker {
id: background;
anchors.left: parent.left;
anchors.leftMargin: 4;
anchors.verticalCenter: parent.verticalCenter;
text: "背景";
selectedColor: "white";
onColorPicked: painter.fillColor = clr;
}
ColorPicker {
id: foreground;
anchors.left: background.right;
anchors.top: background.top;
anchors.leftMargin: 4;
text: "前景";
selectedColor: "black";
onColorPicked: painter.penColor = clr;
}
Rectangle {
id: splitter;
border.width: 1;
border.color: "gray";
anchors.left: foreground.right;
anchors.leftMargin: 4;
anchors.top: foreground.top;
width: 3;
height: foreground.height;
}
Slider {
id: thickness;
anchors.left: splitter.right;
anchors.leftMargin: 4;
anchors.bottom: splitter.bottom;
minimumValue: 1;
maximumValue: 100;
stepSize: 1.0;
value: 1;
width: 280;
height: 24;
onValueChanged: if(painter != null)painter.penWidth = value;
} Text {
id: penThickLabel;
anchors.horizontalCenter: thickness.horizontalCenter;
anchors.bottom: thickness.top;
anchors.bottomMargin: 4;
text: "画笔:%1px".arg(thickness.value);
font.pointSize: 16;
color: "steelblue";
} Text {
id: minLabel;
anchors.left: thickness.left;
anchors.bottom: thickness.top;
anchors.bottomMargin: 2;
text: thickness.minimumValue;
font.pointSize: 12;
} Text {
id: maxLabel;
anchors.right: thickness.right;
anchors.bottom: thickness.top;
anchors.bottomMargin: 2;
text: thickness.maximumValue;
font.pointSize: 12;
}
Rectangle {
id: splitter2;
border.width: 1;
border.color: "gray";
anchors.left: thickness.right;
anchors.leftMargin: 4;
anchors.top: foreground.top;
width: 3;
height: foreground.height;
} Button {
id: clear;
anchors.left: splitter2.right;
anchors.leftMargin: 4;
anchors.verticalCenter: splitter2.verticalCenter;
width: 70;
height: 28;
text: "清除";
style: btnStyle;
onClicked: painter.clear();
} Button {
id: undo;
anchors.left: clear.right;
anchors.leftMargin: 4;
anchors.top: clear.top;
width: 70;
height: 28;
text: "撤销";
style: btnStyle;
onClicked: painter.undo();
} Rectangle {
border.width: 1;
border.color: "gray";
width: parent.width;
height: 2;
anchors.bottom: parent.bottom;
}
} APaintedItem {
id: painter;
anchors.top: options.bottom;
anchors.left: parent.left;
anchors.right: parent.right;
anchors.bottom: parent.bottom;
}
}
不必多说了……
颜色选择button的实现
也比較直观,直接上代码了:
import QtQuick 2.2
import QtQuick.Dialogs 1.0 Rectangle {
id: colorPicker;
width: 64;
height: 60;
color: "lightgray";
border.width: 2;
border.color: "darkgray";
property alias text: label.text;
property alias textColor: label.color;
property alias font: label.font;
property alias selectedColor: currentColor.color;
property var colorDialog: null; signal colorPicked(color clr); Rectangle {
id: currentColor;
anchors.top: parent.top;
anchors.topMargin: 4;
anchors.horizontalCenter: parent.horizontalCenter;
width: parent.width - 12;
height: 30;
} Text {
id: label;
anchors.bottom: parent.bottom;
anchors.bottomMargin: 4;
anchors.horizontalCenter: parent.horizontalCenter;
font.pointSize: 14;
color: "blue";
} MouseArea {
anchors.fill: parent
onClicked: if(colorDialog == null){
colorDialog = Qt.createQmlObject("import QtQuick 2.2;import QtQuick.Dialogs 1.0; ColorDialog{}",
colorPicker, "dynamic_color_dialog");
colorDialog.accepted.connect(colorPicker.onColorDialogAccepted);
colorDialog.rejected.connect(colorPicker.onColorDialogRejected);
colorDialog.open();
}
}
function onColorDialogAccepted(){
selectedColor = colorDialog.color;
colorPicked(colorDialog.color);
colorDialog.destroy();
colorDialog = null;
} function onColorDialogRejected(){
colorPicked(color);
colorDialog.destroy();
colorDialog = null;
}
}
ColorPicker 内部调用 ColorDialog 来选择颜色。
ColorDialog 是使用 Qt.createQmlObject() 动态创建的,具体使用方法请參考《Qt Quick 组件与对象动态创建具体解释》。
用户选择了一个颜色后。button上半部分的矩形的填充颜色会变化,同一时候也会发出 colorPicked() 信号。假设用户取消选择,则使用默认的颜色。
OK ,就介绍到这里了。
版权全部 foruok ,转载请注明出处:http://blog.csdn.net/foruok 。
回想一下我的Qt Quick系列文章:
- Qt Quick 简单介绍
- QML 语言基础
- Qt Quick 之 Hello World 图文具体解释
- Qt Quick 简单教程
- Qt Quick 事件处理之信号与槽
- Qt Quick事件处理之鼠标、键盘、定时器
- Qt Quick 事件处理之捏拉缩放与旋转
- Qt Quick 组件与对象动态创建具体解释
- Qt Quick 布局介绍
- Qt Quick 之 QML 与 C++ 混合编程具体解释
- Qt Quick 图像处理实例之美图秀秀(附源代码下载)
- Qt Quick 之 PathView 具体解释
- Qt Quick实例之挖头像
- Qt Quick综合实例之文件查看器
- Qt Quick调试之显示代码行号
Qt Quick实现的涂鸦程序的更多相关文章
- Qt Quick实现的疯狂算数游戏
使用 Qt Quick 写了个小游戏:疯狂算数.支持 Windows 和 Android 两个平台. 游戏简单,但牵涉到下面你的 Qt Quick 主题: 自己实现一个按钮 自适应分辨率 国际化 QM ...
- Qt Quick程序的发布
要将程序发布出去,首先需要使用release方式编译程序,然后将生成的.exe可执行文件和需要的库文件发在一起打包进行发布. 要确定需要哪些动态库文件,可以直接双击.exe文件,提示缺少那个dll文件 ...
- 发布Qt Quick桌面应用程序的方法(使得planets在XP上运行)
发布Qt Quick桌面应用程序的方法 Qt是一款优秀的跨平台开发框架,它可以在桌面.移动平台以及嵌入式平台上运行.目前Qt 5介绍程序发布的文章帖子比较少.大家又非常想要知道如何发布Qt应用程序,于 ...
- 打包发布 Qt Quick/Widgets 程序
使用的QT自带的部署工具(windeployqt.exe,路径QT安装路径),版本替换debug/release Qt Quick "C:\Qt\Qt5.8.0\5.8\mingw53_32 ...
- 从头学Qt Quick(1) --体验快速构建动态效果界面
自2005年Qt4发布以来,Qt已经为成千上万的应用程序提供了框架服务,现在Qt已经基本上支持所有的开发平台了,这里面既包含了桌面.嵌入式领域,也包括了Android.IOS.WP等移动操作平台,甚至 ...
- Qt on Android: Qt Quick 之 Hello World 图文具体解释
在上一篇文章,<Qt on Android:QML 语言基础>中,我们介绍了 QML 语言的语法,在最后我们遗留了一些问题没有展开,这篇呢,我们就正式開始撰写 Qt Quick 程序,而那 ...
- Qt 学习之路 :Qt Quick Controls
自 QML 第一次发布已经过去一年多的时间,但在企业应用领域,QML 一直没有能够占据一定地位.很大一部分原因是,QML 缺少一些在企业应用中亟需的组件,比如按钮.菜单等.虽然移动领域,这些组件已经变 ...
- Qt Quick 与 QML语言(初学笔记1)
Qt Quick Qt Quick是一些新的UI技术的集合,用来帮助开发者创建一种现在越来越多用于手机.多媒体播放器.机顶盒以及其他便携式设备上的直观的.现代的.流畅的用户界面.简单来说,Qt Qui ...
- 对Qt for Android的评价(很全面,基本已经没有问题了,网易战网客户端就是Qt quick写的),可以重用QT积累20年的RTL是好事,QML效率是HTML5的5倍
现在Qt不要光看跨平台了,Qt也有能力和原生应用进行较量的.可以直接去Qt官网查看他和那些厂商合作.关于和Java的比较,框架和Java进行比较似乎不且实际.如果是C++和Java比较,网上有很多文章 ...
随机推荐
- python代码编程规范
一.内容格式 1.注释部分:模块名及简介(一般用一行写完),模块描述(包含各类方法),其它描述(注意点,功能,示例等,可以分多段) 2.导入模块:Import XXX 3.全局变量定义:wantobj ...
- nagios高可用性设置
1. 前言 如何来实现nagios监控系统的高可用,监控是很重要的,在关键时刻进行通知报警,通知人员进行相应的处理. 在进行配置的时候,需要配置两台相同服务的nagios服务器,配置相同,同时在运行, ...
- kali 安装完成后,无法进入界面
vmware 下安装 kali-1.9 ,安装完成后,无法进入界面,提示: 系统出错且无法恢复,请联系管理员 解决办法如下: 在新建虚拟机的时候,选择客户端系统:linux Debian 7. 因 ...
- asp.net mvc 使用Ajax
使用asp.net mvc 调用Action方法很简单. 一.无参数方法. 1.首先,引入jquery-1.5.1.min.js 脚本,根据版本不同大家自行选择. <script src=&qu ...
- 想要完全导入swc中的所有类
其实还有个这个办法.修改参数.-include-libraries library [...] 强制导入SWC文件中的所有类,不管是否曾使用过这个类 (((比如把swc文件放到src里,然后编译参数加 ...
- JQuery发送Put、Delete请求 - 摘自网络
DELETE: $.ajax({ url: '/script.cgi', type: 'DELETE', success: function(result) { // Do something wit ...
- nodejs 基本操作
查看nodejs版本 nodejs -v 升级nodejs node有一个模块叫n(这名字可够短的...),是专门用来管理node.js的版本的.首先安装n模块:npm install -g n 第二 ...
- 基于Storm 分布式BP神经网络,将神经网络做成实时分布式架构
将神经网络做成实时分布式架构: Storm 分布式BP神经网络: http://bbs.csdn.net/topics/390717623 流式大数据处理的三种框架:Storm,Spark和Sa ...
- php--opp--2.什么是类,什么是对象,类和对象这间的关系
类的概念:类是具有相同属性和服务的一组对象的集合.它为属于该类的所有对象提供了统一的抽象描述,其内部包括属性和服务两个主要部分.在面向对象的编程语言中,类是一个独立的程序单位,它应该有一个类名并包括属 ...
- Spark RDD概念学习系列之RDD的转换(十)
RDD的转换 Spark会根据用户提交的计算逻辑中的RDD的转换和动作来生成RDD之间的依赖关系,同时这个计算链也就生成了逻辑上的DAG.接下来以“Word Count”为例,详细描述这个DAG生成的 ...