class XRenderWidget<T extends ChangeNotifier> extends LeafRenderObjectWidget {
BaseRender baseRender;
XRenderWidget({Key key, this.baseRender}) : super(key: key);
@override
RenderObject createRenderObject(Object context) {
try {
Provider.of<T>(context);
} catch (Exception) {
// ignore
}
return XRenderBox(baseRender: baseRender);
}
@override
void updateRenderObject(BuildContext context, XRenderBox renderObject) {
super.updateRenderObject(context, renderObject);
print("$baseRender updateRenderObject");
renderObject.updateRender();
}
}
複製代碼
XRenderWidget 作了兩件事canvas
在rebuild以後,updateRenderObject會被執行,和StatefulWidget相似,重複利用RenderObject來渲染UI,提升利用率bash
class XRenderBox extends RenderBox {
BaseRender baseRender;
XRenderBox({this.baseRender});
@override
void performLayout() {
super.performLayout();
baseRender?.onPerformLayout(size);
}
@override
void paint(PaintingContext context, Offset offset) {
super.paint(context, offset);
baseRender?.onPaint(context, offset);
}
@override
bool get sizedByParent => true;
@override
bool hitTestSelf(Offset position) => true;
void updateRender() {
baseRender?.updateRender();
markNeedsPaint();
}
}
複製代碼
XRenderBox 很簡單,靜態代理了一些核心函數網絡
咱們先想一想開始的效果都拆解了什麼。app
沒錯,大概是作了這些事ide
Size size;
複製代碼
List<T> _aboveChildren = [];
List<T> _underChildren = [];
BaseMaxMinRender parent;
void addChild(T child, {int elevation = 0}) {
if (elevation < 0) {
_underChildren.add(child);
} else {
_aboveChildren.add(child);
}
}
void delChild(T child) {
_aboveChildren.remove(child);
_underChildren.remove(child);
}
複製代碼
/// 該render本身這層的matrix4
vm.Matrix4 _matrix4 = vm.Matrix4.identity();
/// 圖層疊加後的matrix4
vm.Matrix4 get matrix4 => parent?.matrix4 ?? _matrix4;
void _calcMaxMin() {
MaxMin newMaxMin;
/// 該層的最大最小值
MaxMin cur = calcOwnMaxMin();
/// children的最大最小值
MaxMin children = _childrenMaxMin();
/// 沒有children,或者children無需計算最大最小則是null
if (children == null) {
newMaxMin = cur;
} else {
// 把本身的最大值和最小值與children合併成新的最大最小值
newMaxMin = cur.merge(children);
}
if (newMaxMin != maxMin) {
if (maxMin == null || maxMin.isZero()) {
/// maxMin爲初始化則直接賦值
maxMin = newMaxMin;
} else {
/// 最大值也最小值發生了變化,則平滑改變最大最小,體驗會流程不少
_smoothChangeMaxMin(newMaxMin);
}
}
}
/// 經過MaxMin的值,變換成K線圖的正交座標系,變換系數存儲在matrix4中,方便數據變化
void _transformMatrix() {
if (_maxMin != null) {
_matrix4.setIdentity();
/// 計算Y軸的縮放值
var scaleY = (height - edgeInsets.bottom - edgeInsets.top) / _maxMin.delta;
/// 設置矩陣的在X軸的偏移量,由於圖中的最小值並不都是0開始,所以須要在X軸上移動相應的距離
_matrix4.setTranslation(vm.Vector3(0, height - edgeInsets.bottom + _maxMin.min * scaleY, 0.0));
/// 設置矩陣的對角線值 對角線的值分別是x,y,z的縮放值。1表示不縮放,-scaleY表示Y軸的值都要與-scaleY相乘,所以至關因而縮放了scaleY,而且反轉的Y軸的反向。
_matrix4.setDiagonal(vm.Vector4(1, -scaleY, 1, 1));
} else {
_matrix4.setIdentity();
}
}
複製代碼
render有了層次處理,座標變換的能力以後,就能夠方便的繪製圖像了。
函數
class CandleRender extends BaseKLineRender {
Paint _klinePaint = Paint();
/// 屏幕顯示區域的蠟燭芯數據
List<double> wickData = [];
/// 屏幕顯示區域的蠟燭數據
List<double> candleData = [];
CandleRender(ControllerModel controller) : super(controller);
Color _itemColor(int i) => controller.getColorRelativeStartIndex(i);
@override
void fillOwnData() {
super.fillOwnData();
wickData.clear();
candleData.clear();
/// 遍歷須要顯示在屏幕部分的數據
forEachData((i) {
double x = controller.getXByIndex(i);
/// 三個數據表示一個點(x,y,z),這裏的z是0
/// 添加蠟燭芯的線段數據
wickData..add(x)..add(klineData[i].high)..add(0)..add(x)..add(klineData[i].low)..add(0);
/// 添加蠟燭體的線段數據
candleData..add(x)..add(klineData[i].open)..add(0)..add(x)..add(klineData[i].close)..add(0);
});
}
@override
void transformData() {
/// 把上面添加的數據,通過座標變換,轉成屏幕上的數據。
/// 上面在添加的源數據(x,y,z),其中x已是屏幕上的像素值了,可是y是價格,y也要作必定的縮放和平移
matrix4.applyToVector3Array(wickData);
matrix4.applyToVector3Array(candleData);
}
/// 計算自身的這一層的最大最小值。
@override
MaxMin calcOwnMaxMin() {
double max = -double.maxFinite;
double min = double.maxFinite;
for (int i = controller.startIndex; i <= controller.endIndex; i++) {
if (i == controller.startIndex) {
min = klineData[i].low;
max = klineData[i].high;
} else {
max = math.max(max, klineData[i].high);
min = math.min(min, klineData[i].low);
}
}
return MaxMin(min: min, max: max);
}
@override
void onRealPaint(Canvas canvas) {
super.onRealPaint(canvas);
_klinePaint.strokeWidth = 1;
/// 蠟燭芯的畫筆大小是1就能夠了
drawLines(canvas, wickData, controller.needDrawCount(), _klinePaint, color: _itemColor);
_klinePaint.strokeWidth = controller.candleWidth - 1;
/// 蠟燭體的畫筆大小是, 蠟燭所佔據的寬度 - 1, 這樣蠟燭直接就有個1的空隙。比較美觀點
drawLines(canvas, candleData, controller.needDrawCount(), _klinePaint, color: _itemColor);
}
}
複製代碼
有個蠟燭的繪製,成交量的繪製就再交單不過了
一樣的填充數據,計算最大最小值,轉換數據,繪製數據。ui
class VolumeRender extends BaseKLineRender {
Paint _klinePaint = Paint();
List<double> _volData = [];
VolumeRender(ControllerModel controller) : super(controller);
Color _itemColor(int i) => controller.getColorRelativeStartIndex(i);
@override
void fillOwnData() {
super.fillOwnData();
_volData.clear();
forEachData((i) {
double x = controller.getXByIndex(i);
_volData..add(x)..add(klineData[i].amount)..add(0)..add(x)..add(0)..add(0);
});
}
@override
void transformData() {
matrix4.applyToVector3Array(_volData);
}
@override
MaxMin calcOwnMaxMin() {
double min = 0;
double max = 0;
forEachData((i) {
max = math.max(max, klineData[i].amount);
});
return MaxMin(min: min, max: max);
}
@override
void onRealPaint(Canvas canvas) {
super.onRealPaint(canvas);
_klinePaint.strokeWidth = controller.candleWidth - 1;
drawLines(canvas, _volData, controller.needDrawCount(), _klinePaint, color: _itemColor);
}
}
複製代碼
蠟燭和成交量都有網格,而且是在最下面的圖層this
class GridLineRender extends _BaseGridLineRender {
Paint _paint = Paint();
GridLineConfig gridLineConfig;
GridLineRender(this.gridLineConfig, controller) : super(controller);
@override
void onRealPaint(Canvas canvas) {
super.onRealPaint(canvas);
/// 座標變換的逆變換,用途是更加屏幕上的座標算出對應的價格。
/// 這樣網絡線上對應的價格就很方便的得知了。
Matrix4 m = matrix4.clone()..invert();
/// 根據配置繪製水平線
for (int i = 0; i < gridLineConfig.horizontalCount; i++) {
double y = height / (gridLineConfig.horizontalCount - 1) * i;
_paint.strokeWidth = gridLineConfig.horizontalStrokeWidth;
_paint.color = gridLineConfig.horizontalColor;
canvas.drawLine(Offset(0, y), Offset(width, y), _paint);
if (isNotEmpty(klineData) && totalMaxMin != null) {
List<double> yy = [0, y, 0];
m.applyToVector3Array(yy);
if (i == 0) {
drawText(canvas, "${format != null ? format(yy[1]) : yy[1]}", Offset(width, y), align: TextAlign.end);
} else if (i == gridLineConfig.horizontalCount - 1) {
drawText(canvas, "${format != null ? format(yy[1]) : yy[1]}", Offset(width, y - 12), align: TextAlign.end);
} else {
drawText(canvas, "${format != null ? format(yy[1]) : yy[1]}", Offset(width, y - 12), align: TextAlign.end);
}
}
}
/// 根據配置繪製垂直線
for (int i = 0; i < gridLineConfig.verticalCount; i++) {
double x = width / (gridLineConfig.verticalCount - 1) * i;
_paint.strokeWidth = gridLineConfig.verticalStrokeWidth;
_paint.color = gridLineConfig.verticalColor;
canvas.drawLine(Offset(x, 0), Offset(x, height), _paint);
}
}
}
class GridLineConfig {
int verticalCount = 6;
Color verticalColor = Colors.grey[300];
double verticalStrokeWidth = 0.5;
int horizontalCount = 3;
Color horizontalColor = Colors.grey[300];
double horizontalStrokeWidth = 0.5;
}
複製代碼
/// 成交量render
volumeRender = VolumeRender(_controllerModel);
/// 蠟燭圖render
candleRender = CandleRender(_controllerModel);
/// 蠟燭圖添加個子render繪製底層網格
candleRender.addChild(
GridLineRender(GridLineConfig()
..horizontalCount = 5, _controllerModel)
..format = (double val) => formatNumber(val, 2),
elevation: -1);
/// 成交量添加子render,繪製底層網格
volumeRender.addChild(
GridLineRender(GridLineConfig(), _controllerModel)
..format = (double val) => formatNumber(val, 2),
elevation: -1);
return MultiProvider(
providers: [
ChangeNotifierProvider<DataModel>(create: (_) => _dataModel),
ChangeNotifierProvider<ConfigModel>(create: (_) => _configModel),
ChangeNotifierProvider<ControllerModel>(create: (_) => _controllerModel),
ChangeNotifierProvider<KLineHighlightModel>(create: (_) => _hightLightModel),
],
child: _wrapperGesture(
Consumer<ControllerModel>(builder: (context, controllerModel, child) {
_logger.debug("Consumer KLineControllerModel");
return Column(
children: <Widget>[
xRenderWidget<DataModel>(candleRender, height: 200),
xRenderWidget<DataModel>(volumeRender, height: 100),
],
);
}),
));
複製代碼
如今完成了蠟燭圖的繪製和成交量的繪製,算是有個初步的樣子了。文中有些代碼比較醜,等寫完在重構整理整理。spa
下一節聊聊手勢處理debug