我有一个垂直的单像素视图,我想在一个代表正在播放的音符的网格上移动。根据歌曲的BPM(每分钟节拍数),动画应该更慢或更快。这些音符彼此相邻挤压,宽度为25像素。
我现在这样做的方法是每次x样本通过时将视图1px向右移动:
int bpm = 90;
int interval = (44100 * 60.0f / song.bpm) / 25.0f; // = 1176
// sample rate=44100 multiplied by 60 (seconds) divided by number of pixels
因此,在BPM = 90的情况下,每播放1176个样本,我就会移动UIView 1px。这种方法运行得相当好,但是如果BPM高于180,则会给我带来问题。此时,视图落后于播放的音乐,当BPM上升到240时,视图就会消失。
如何使用视图的内置动画功能执行此操作,并根据我上面发布的计算动画速度?我想在音乐开始播放后启动动画,所以我不喜欢需要逐个像素地手动移动它。
答案 0 :(得分:4)
如果我理解正确,你想做的是:
有两个问题需要解决:
要获得正确的绘图性能,需要考虑两种实现:
在OpenGL中实现视图的代码太多,不能作为示例发布,但这里是Core Animation选项的代码:
// In the UIViewController:
- (CALayer *)markerLayer
{
static NSString *uniqueName = @"com.mydomain.MarkerLayer";
for (CALayer *layer in self.view.layer.sublayers) {
if ([layer.name isEqualToString:uniqueName])
return layer;
}
CALayer *markerLayer = [CALayer layer];
markerLayer.backgroundColor = [UIColor whiteColor].CGColor;
markerLayer.name = uniqueName;
markerLayer.anchorPoint = CGPointZero;
[self.view.layer addSublayer:markerLayer];
return markerLayer;
}
- (void)startAnimationWithDuration:(NSTimeInterval)duration
{
[CATransaction begin];
[CATransaction setDisableActions:YES];
CALayer *markerLayer = [self markerLayer];
markerLayer.bounds = (CGRect){.size = {1, self.view.bounds.size.height}};
markerLayer.position = CGPointZero;
[CATransaction commit];
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"position.x"];
animation.fromValue = @0.0f;
animation.toValue = [NSNumber numberWithFloat:self.view.bounds.size.width];
animation.duration = duration;
[markerLayer addAnimation:animation forKey:@"audioAnimation"];
}
音频同步在很大程度上取决于您播放的方式,例如使用AVFoundation或Core Audio。要获得精确的同步,您需要从音频引擎进行某种回调,告诉您现在正好回放的位置或启动时间。
也许仅在你启动音频之前立即调用startAnimationWithDuration:
就足够了,但这看起来很脆弱,取决于音频框架的延迟。
答案 1 :(得分:1)
一种解决方案:您拥有如图所示的背景视图 - 蓝色框。确保此视图不透明。定义一个新的UIView子类并将其放在我称之为背景视图的地方 - 这个视图将是非透明的 - 让我们称之为标记视图。
标记视图随时会知道绘制线的x偏移量。它将提供一个drawRect方法,将rect参数设置为清晰颜色(alpha = 0)。然后,您将使用Quartz调用单个垂直线绘制(如果某个布尔值表示这样做)。
当您的控制器启动时,它会告诉此视图不绘制线条。启动时,将布尔值更改为YES,将其赋予x坐标,然后告诉该视图setNeedsDisplayInRect:CGRectMake(theXoffset,0,1,heightOfView)];
即使有一些延迟,当视图最终确实绘制时,x值应该是最新的,因为你正在“实时”更新。
编辑:
通过标记,我的意思是垂直线 - 它“标记”了这个地方没有?
alpha 0表示(在这种情况下)大多数叠加视图(“marker”视图)将是一个清晰的颜色,这意味着每个像素都是清晰的(alpha = 0),以便不阻挡视图在它下面。此视图绘制的唯一像素是垂直线。
BOOL,“x”等 - 视图的区域属性。你可能需要更多,但你不需要很多。然后,这些属性指示视图在调用“drawRect:”时应该执行的操作。
您需要学习一些关于Quartz(Apple实际绘制的技术)的知识。在drawRect中,您将获得当前的CGContextRef,并在几乎所有调用中使用它,设置您想要绘制的线条的宽度(默认为1),设置线条颜色,移动到某个点,将线条绘制到其他一些点(这将是一个“路径”),然后描绘路径。与大多数其他解决方案相比,这将非常快(但本身可以加速)。
如果这一切看起来令人生畏,那么也许别人会有一个更简单的解决方案,或者你可以提供赏金,看看是否有人(像我一样)会为你准备好整个事情。
EDIT2:
这是执行指标线的视图的代码(我使用红线来更好地看到它,此数组设置颜色:{1,0,0,在R / G / B空间中)
#import "SlideLine.h"
#define LINE_WIDTH 1.0f
@implementation SlideLine
@synthesize showLine, x;
- (void)drawRect:(CGRect)rect
{
if(!showLine) return;
CGRect bounds = self.bounds;
CGContextRef c = UIGraphicsGetCurrentContext();
CGContextSetLineWidth(c, LINE_WIDTH);
// draws two slightly transparent lines on either side to soften the look
for(int i=0; i<3; ++i) {
CGFloat val[4] = { 1, 0, 0, i==1 ? 1 : 0.5f}; // half alpha for each side line
CGContextSetStrokeColor(c, val);
CGFloat xx = x - LINE_WIDTH/2 - 1 + i;
CGContextMoveToPoint(c, xx, 0);
CGContextAddLineToPoint(c, xx, bounds.size.height);
CGContextStrokePath(c);
}
}
- (void)setShowLine:(BOOL)val
{
showLine = val;
[self setNeedsDisplay];
}
@end
我创建了一个Xcode project,向您展示如何使用它,使用控件来改变速度等:
EDIT3:所以我开始修改使用动画,但不幸的是它没有完全出炉。我们的想法是采用标记线(3像素宽)并使其成为单个视图(3像素宽)。一个新的SlidingLineController从它的所有者获取更新(将由音频回调驱动),告诉控制器x应该在什么时间(所以它的速率)。然后,控制器使用该速率为标记设置动画,以便在指定时间显示它应该在的位置。随着更新的进行,动画速率可能会略微加快或减慢,以便绝对位置始终接近您想要的位置。
虽然这看起来比原版技术更好,但它实际上看起来比原版更“波涛汹涌”。可能最佳的解决方案是使用图层(不是视图)然后为其设置动画,并在其移动时更新其目标和速率,而不是启动和重新启动。可能这个Second Project可以使用从第一个开始的技术,使用相同的管理速率概念以30fps的速率简单地重绘自己。不幸的是,由于我现在看到它即将到期,我没有时间去追求这个。
编辑:4好吧,我做了另一次更新,以30fps的速度重绘,使用上面的控制器,它工作得很好 - 它没有完成,但你可以看到in the third spin它非常流畅答案 2 :(得分:0)
NSTimer
。setNeedsDisplay
或重新定位您的歌曲位置视图。我建议这样做,因为如果最终推送到多个渲染器,您的优化尝试可能会适得其反。