我在Flash Pro(AS3)中开发一个简单的教育应用程序时处于尴尬的停滞状态。
我试图制作一个简单的图表,显示某些事情发生时的垂直峰值(以及闲置时的平线)。该图是实时的,因为它从程序开始并且每隔一帧更新一次。该行从" Graph"的一端开始。反对并继续直到它到达另一侧(比如说超过60秒)。到达结束后,整个图形开始向左滚动,为更多数据腾出空间,最后添加数据。它旨在永久保持滚动和记录数据(直到用户停止/暂停)。
以下是一个例子:http://img442.imageshack.us/img442/4784/runninggraph.png
实施此类图表的最佳方式是什么? 一个简单的滚动图表,由基线和刻度组成。
不应该这么难......或者我想。我尝试了4种不同的方式(其中两种证明100%工作但速度太慢)。那里肯定有更好的东西!也许矩阵变换+ bitmapFill?
问题的结束;简化代码如下。
public class Graph extends Sprite
{
include "constants.as";
const pixelFactor:Number = (1 / GRAPH_TIMESCALE) * 1050; //60,000ms
const amountToMove:Number = POLLING_RATE * pixelFactor, inverseTS:Number = 1.0/GRAPH_TIMESCALE;
var graphHolder:GraphHolder; // A simple (large) sprite with many Graph instances
var baseLine:Shape = new Shape(), stretched:Boolean = false;
var apArray:Array = [], apRecycle:Vector.<Shape> = new <Shape>[];
var apLength:int = 0, firstPeak:Shape;
//var numberIndicator:TextField = new TextField();
public function Graph(graphHolder:GraphHolder)
{
//Set variables
this.graphHolder = graphHolder;
mouseEnabled = false; mouseChildren = false;
addChild(baseLine);
with (baseLine)
{
x = 55; y = 10; // the base-line. Starts 55 pixels out so labels can be added
graphics.lineStyle(2,0x000000); // Thickness 2, color black
graphics.lineTo(1050,0);
scaleX = 0.0005; // base-line starts as a little dot, expands with time
}
}
// When not scrolling, draws idle baseline. When scrolling, drags peaks backwards.
function drawIdle(timeElapsed:int):void
{
if (timeElapsed <= GRAPH_TIMESCALE) baseLine.scaleX = inverseTS * timeElapsed;
else
{
//if (graphNo < 1) graphHolder.scrollAxis(pixelFactor);
if (!stretched) { baseLine.scaleX = 1.001; stretched = true; }
// scroll all peaks
if (apLength)
{
for (var i:int = 0; i < apLength; i++)
{
apArray[i].x -= amountToMove;
}
firstPeak = apArray[0];
if (firstPeak.x < 55)
{
firstPeak.visible = false;
apRecycle[apRecycle.length] = firstPeak; // peaks are recycled instead of removed
apArray.shift(); // apArray is a linked-list Array; less shift cost
--apLength;
}
}
}
//lbd.copyPixels(graphHolder.baseCache,lbr,new Point(timeElapsed * pixelFactor,0), null, null, true);
//line.graphics.lineTo(timeElapsed * pixelFactor,10);
}
function drawAP(timeElapsed:int):void
{
// Check for a peak in the recycle array
if (apRecycle.length)
{
firstPeak = apRecycle.pop();
firstPeak.x = ( (timeElapsed > GRAPH_TIMESCALE) ? 1050 : (timeElapsed * pixelFactor) ) + 54; //55-1
firstPeak.visible = true;
apArray[apArray.length] = firstPeak;
}
else
{
// Line drawn from baseline up 7 pixels.
var peakShape:Shape = new Shape();
//peakShape.cacheAsBitmap = true;
addChild(peakShape);
with (peakShape)
{
//cacheAsBitmap = true;
graphics.lineStyle(2, 0x000000);
graphics.moveTo(1, 10);
graphics.lineTo(1, 3);
x = ( (timeElapsed > GRAPH_TIMESCALE) ? 1050 : (timeElapsed * pixelFactor) ) + 54; //55-1
apArray[apArray.length] = peakShape;
}
}
++apLength;
}
/* Reset baseline as tiny dot, remove peaks, reset peak array.
All peaks in recycle remain to draw from during next cycle. */
function resetGraph():void
{
baseLine.scaleX = 0.0005; stretched = false;
for each (var peak:Shape in apArray) removeChild(peak);
apArray.length = 0; apLength = 0;
}
}
因此,基本上,图表上应该有标记,表示&#34;动作&#34;那是实时发生的。图形在到达屏幕末尾(N秒)后开始向左滚动。换句话说,它在到达指定空间的边缘后无限期地滚动。以下是我试图实现这三种方法 - 其中没有一种是快速的,其中一些还有其他问题。
在图表上的相应时间点创建并绘制新的线段 - 基线线段(微小水平线)和刻度线(垂直线)。图表仅上升到N秒,但仍然会绘制N + x时刻的所有刻度线,就像图表永远向右移动一样。然后我使用一个遮罩作为图形的矩形区域,并且一旦达到N秒,就会随着时间的推移将图形向左移动。
创建一个包含刻度线(小垂直线)和基线(微小水平线)形状的bitmapdata对象,并将每个帧copypixel下一个位图(线段)放到图形对象上(现在也是一个位图)。 ScrollRect向左滚动。简单,非常类似#1,除了使用bitmapData和copyPixel而不是显示对象。
有一个形状,一条水平线(基线),&#34;放大&#34;填写图表,然后停止(N秒)。因此,它是&#34;基线&#34;因为它似乎随着时间在图表上伸展,然后在N秒之后什么都不做。然后,基线顶部的每个刻度线都是一个Shape对象,并在基线顶部绘制。然后foreach循环改变所有刻度的x坐标,直到它们到达图的左边,此时它们变得不可见并添加到循环向量中,如果它包含任何内容,将用于任何后续峰值。右(在N处绘制)。
吐温。实际上,还没有使用补间实现图形本身,但到目前为止,用Tweens替换我的enterFrame移动处理程序(每帧手动更改对象的坐标)使得所有动画都变得更慢,更慢。去搞清楚。所以我认为它不会对图表产生帮助......我也不能使用TweenLite(许可证)。
#3,我认为我走在正确的轨道上,但我担心我对AVM渲染的细节知之甚少。峰值应该使用位图而不是形状吗?我是否应该使用与#3##3对象相同的技术(即将峰值添加到一个大的移动&#34; tickSprite&#34;但仍然&#34;回收&#34;当他们离开屏幕时的峰值)?在这种情况下,尽管屏幕外没有刻度,但包含的Sprite将继续被推到屏幕左侧。如果它在屏幕左侧完全为空,它是否会线性占用资源?
怀疑是#3中的速度罪魁祸首: - 大量移动的形状和移动它们所需的for循环。 - 矢量,位图和背景的特征。例如,Graph对象本身只是一个背景形状(缓存为位图)的简单容器,它是图形后面的alpha 0.75灰色矩形,一组标签和轴(也缓存为位图),以及图形形状(一个缩放&#34; line&#34;基线形状和众多&#34; tick&#34;存储在矢量中的形状)... - 用户可以拖动图表后面的法向矢量对象,这可能会将图形的子项缓存为位图,从而消除任何性能优势,因为它们必须在alpha 0.75背景形状后面的所有内容中进行运动。
答案 0 :(得分:0)
选项1是唯一可行的方式。
您需要做的是将数据保存在某个位置(数组/向量),然后在屏幕外显示行/标记。如果用户想要向左滚动,则使用您保存的数据重新创建刻度(滚动时向右溢出的内容相同)。
最终虽然理论上会耗尽内存,但您可能需要实现本地存储技术,例如带有某种种子(日期时间?)的本地共享对象来存储数据,因此它并不总是在内存中。
修改强>
由于您不需要向左滚动,因此非常简单。 只需将每个'滴答'作为自己的显示对象。 (可以是Shape或Sprite),然后在滚动时检查单个刻度是否在屏外,如果是 - 处理它。
示例图表类:
public class Graph extends Sprite
private var container:Sprite;
public function Graph():void {
//create your baseline
//create the container
container = new Sprite();
addChild(container);
this.addEventListener(Event.ENTER_FRAME, drawIdle);
}
private function drawIdle(e:Event){
//move your baseline?
//scroll container
container.x += scrollAmount;
//iterate through all the ticks
var i:int = container.numChildren;
var tick:Shape;
//iterate through the container children backwards (has to be backwards since your potentially removing items)
while(i--){
tick = container.getChildAt(i);
//check if the tick is off screen to the left
if(container.x + tick.x < 0){
//if so, remove it so it can be garbage collected
container.removeChild(tick);
}
}
public function drawAp(timeElapsed):void {
//create your tick
var tick:Shape = new Shape();
//draw your tick
tick.graphics.beginFill(0);
tick.graphics.drawRect(0,0,tickWidth,tickHeight);
tick.graphics.endFill();
//position your tick
tick.x = whatever;
tick.y = whatever;
//add it to your tick container
container.addChild(tick);
}
}