Вопрос по uiview – Как мне создать круговой UIView с плавным изменением размера?

10

Я пытаюсь создать UIView, который показывает полупрозрачный круг с непрозрачной границей внутри его границ. Я хочу быть в состоянии изменить границы двумя способами - внутри-[UIView animateWithDuration:animations:] блокировать и с помощью действия распознавателя жеста ущипнуть, которое срабатывает несколько раз в секунду. Я пробовал три подхода, основанные на ответах в других местах, посвященных SO, и ни один из них не подходит.

Setting the corner radius of the view's layer in layoutSubviews gives smooth translations, but the view doesn't stay circular during animations; it seems that cornerRadius isn't animatable.

Drawing the circle in drawRect: gives a consistently circular view, but if the circle gets too big then resizing in the pinch gesture gets choppy because the device is spending too much time redrawing the circle.

Adding a CAShapeLayer and setting its path property in layoutSublayersOfLayer, which doesn't animate inside UIView animations since path isn't implicitly animatable.

Есть ли способ для меня, чтобы создать представление, которое является последовательно круглым и плавно изменяемого размера? Есть ли какой-нибудь другой тип слоя, который я мог бы использовать, чтобы воспользоваться аппаратным ускорением?

ОБНОВИТЬ

Комментатор попросил меня уточнить, что я имею в виду, когда говорю, что хочу изменить границы внутри-[UIView animateWithDuration:animations:] блок. В моем коде у меня есть представление, которое содержит мое представление круга. Представление круга (версия, которая использует cornerRadius) переопределяет-[setBounds:] чтобы установить радиус угла:

-(void)setBounds:(CGRect)bounds
{
    self.layer.cornerRadius = fminf(bounds.size.width, bounds.size.height) / 2.0;
    [super setBounds:bounds];
}

Границы кругового вида установлены в-[layoutSubviews]:

-(void)layoutSubviews
{
    // some other layout is performed and circleRadius and circleCenter are
    // calculated based on the properties and current size of the view.

    self.circleView.bounds = CGRectMake(0, 0, circleRadius*2, circleRadius*2);
    self.circleView.center = circleCenter;
}

Размер представления иногда изменяется в анимации, например, так:

[UIView animateWithDuration:0.33 animations:^(void) {
    myView.frame = CGRectMake(x, y, w, h);
    [myView setNeedsLayout];
    [myView layoutIfNeeded];
}];

но во время этих анимаций, если я рисую круговое представление, используя слой с cornerRadius, получаются забавные формы. Я не могу передать продолжительность анимации в layoutSubviews, поэтому мне нужно добавить правильную анимацию в-[setBounds].

animateWithDuration: сама группировка, поэтому явная группа анимации не требуется. Анимация радиуса угла вместе с размером должна работать до тех пор, пока он остается на половине стороны прямоугольника. David Rönnqvist
Мне нужно, чтобы он работал внутри animateWithDuration: animations: - будет ли это работать с CAAnimationGroup? Simon
1 должен оставаться круглым, пока вы анимируете угловой радиус и размер вместе (возможно, в CAAnimationGroup). Всегда угол cornerRadius должен быть вдвое меньше ширины, а ширина должна совпадать с высото David Rönnqvist
Хммм ... ну, это не так. То, что происходит, - то, что новый угловой радиус показывает немедленно, и затем анимация продолжается оттуда. Simon
Смотрите такжеэтот вопро где у пользователя такая же проблема с cornerRadius. Simon

Ваш Ответ

4   ответа
1

cornerRadius если вы измените его внутри блока анимации.

0

Неявно animatable, но это анимируемо. Должно быть довольно легко создать CABasicAnimation, которая изменяет размер траектории круга. Просто убедитесь, что путь имеет одинаковое количество контрольных точек (например, изменение радиуса дуги полного круга). Если вы измените количество контрольных точек, все становится действительно странным. «Результаты не определены», согласно документа

Как мне заставить это работать с animateWithDuration: animations:? Simon
Ты не знаешь. Вы должны создать объект CABasicAnimation и добавить его в слой вашего представления. Вы можете сделать это одновременно с анимацией вида. Duncan C
Ah. Я настроил его для работы с методами анимации UIView, так как одновременно анимирую другие объекты. Simon
На самом деле это не имеет ничего общего с неявной или явной анимацией, но больше связано с анимацией свойств вида и анимацией свойств слоя. David Rönnqvist
@ Дэвид, термин неявные анимации относится к анимациям, которые происходят автоматически при изменении свойства анимации слоя. Так что это оба. Свойство пути слоя формы является нечетным случаем. Это анимируемо, но не безоговорочно. Если вы измените путь слоя формы напрямую, это изменение не будет анимировано, но если вы сделаете это из CAAnimation, оно будет анимировать изменение. Duncan C
17

раздел по анимации в «Руководстве по программированию для iOS» говорится

Both UIKit и Core Animation предоставляют поддержку анимации, но уровень поддержки, предоставляемый каждой технологией, варьируется. В UIKit анимация выполняется с использованием объектов UIView

The полный список свойств что вы можете анимировать, используя либо более старый

[UIView beginAnimations:context:];
[UIView setAnimationDuration:];
// Change properties here...
[UIView commitAnimations];

или новее

[UIView animateWithDuration:animations:];

(что вы используете):

Рамк Оценкицентtransform (CGAffineTransform, а не CATransform3D)альффоновый цве ContentStretch

То, что смущает людей, заключается в том, что вы также можете анимировать те же свойства на слое внутри блока анимации UIView, то есть рамку, границы, положение, непрозрачность, backgroundColor.

В том же разделе говорится:

В местах, где вы хотите выполнять более сложные анимации или анимации, не поддерживаемые классом UIView, вы можете использовать Core Animation и базовый слой представления для создания анимации. Поскольку объекты вида и слоя сложно связаны друг с другом, изменения в слое вида влияют на сам вид.

Несколько строк внизу вы можете прочитать список свойств анимации Core Animation, где вы видите это:

Граница слоя (включая то, закруглены ли углы слоя)

Есть как минимум два хороших варианта для достижения желаемого эффекта:

Анимация углового радиуса Используя CAShapeLayer и анимируя путь

И то, и другое требует анимации с помощью Core Animation. Вы можете создать группу CAAnimationGroup и добавить к ней массив анимаций, если вам нужно запустить несколько анимаций как одну.

Обновить

Исправление всего возможного с меньшим количеством изменений кода будет выполнено с помощью анимации углового радиуса на слое в то же время, что и другие анимации. Я ставлю кавычки примерно в одно и то же время, поскольку не гарантируется, что анимации, не входящие в одну и ту же группу, завершатся в одно и то же время. В зависимости от того, какие другие анимации вы делаете, может быть лучше использовать только базовые анимации и группы анимаций. Если вы применяете изменения ко многим различным представлениям в одном и том же блоке анимации представления, возможно, вы могли бы изучить CATransactions.

Приведенный ниже код анимирует рамку и угловой радиус так же, как вы описали.

UIView *circle = [[UIView alloc] initWithFrame:CGRectMake(30, 30, 100, 100)];
[[circle layer] setCornerRadius:50];
[[circle layer] setBorderColor:[[UIColor orangeColor] CGColor]];
[[circle layer] setBorderWidth:2.0];
[[circle layer] setBackgroundColor:[[[UIColor orangeColor] colorWithAlphaComponent:0.5] CGColor]];
[[self view] addSubview:circle];

CGFloat animationDuration = 4.0; // Your duration
CGFloat animationDelay = 3.0; // Your delay (if any)

CABasicAnimation *cornerRadiusAnimation = [CABasicAnimation animationWithKeyPath:@"cornerRadius"];
[cornerRadiusAnimation setFromValue:[NSNumber numberWithFloat:50.0]]; // The current value
[cornerRadiusAnimation setToValue:[NSNumber numberWithFloat:10.0]]; // The new value
[cornerRadiusAnimation setDuration:animationDuration];
[cornerRadiusAnimation setBeginTime:CACurrentMediaTime() + animationDelay];

// If your UIView animation uses a timing funcition then your basic animation needs the same one
[cornerRadiusAnimation setTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]];

// This will keep make the animation look as the "from" and "to" values before and after the animation
[cornerRadiusAnimation setFillMode:kCAFillModeBoth];
[[circle layer] addAnimation:cornerRadiusAnimation forKey:@"keepAsCircle"];
[[circle layer] setCornerRadius:10.0]; // Core Animation doesn't change the real value so we have to.

[UIView animateWithDuration:animationDuration
                      delay:animationDelay
                    options:UIViewAnimationOptionCurveEaseInOut 
                 animations:^{
                     [[circle layer] setFrame:CGRectMake(50, 50, 20, 20)]; // Arbitrary frame ...
                     // You other UIView animations in here...
                 } completion:^(BOOL finished) {
                     // Maybe you have your completion in here...
                 }]; 
OK - есть ли способ узнать, что я нахожусь в существующем анимационном блоке UIView, чтобы я мог добавить основную анимацию в сеттер? Simon
@ Simon Я не уверен, что вы имеете в виду под словом «сеттеру». Я обновил свой ответ некоторым примером кода, который использует как базовую анимацию, так и блок animateWithDuration:, чтобы показать вам, как это можно сделать. David Rönnqvist
Спасибо - это хороший пример кода, даже если это не совсем то, что я ищу. Под «сеттером» я подразумеваю то, что я хочу переопределить setBounds: в моем классе представления таким образом, чтобы он знал, когда он находится внутри блока анимации UIView, и добавляет анимацию с правильными параметрами при ее вызове. Я обновлю вопрос, чтобы объяснить. Simon
Ваш ответ привел меня к правильному ответу, поэтому я проголосовал за некоторые другие ваши ответы - надеюсь, что все в порядке. Simon
это должен быть принятый ответ! Pavel Gurov
10

вот решение, которое я нашел. В итоге ключом к этому оказалось использование представления-[actionForLayer:forKey:]етод @, поскольку он используется внутри блоков UIView вместо того, чтобы использовать слой-[actionForKey] возвращается.

@implementation SGBRoundView

-(CGFloat)radiusForBounds:(CGRect)bounds
{
    return fminf(bounds.size.width, bounds.size.height) / 2;
}

- (id)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        self.backgroundColor = [UIColor clearColor];
        self.opaque = NO;
        self.layer.backgroundColor = [[UIColor purpleColor] CGColor];
        self.layer.borderColor = [[UIColor greenColor] CGColor];
        self.layer.borderWidth = 3;
        self.layer.cornerRadius = [self radiusForBounds:self.bounds];
    }
    return self;
}

-(void)setBounds:(CGRect)bounds
{
    self.layer.cornerRadius = [self radiusForBounds:bounds];
    [super setBounds:bounds];
}

-(id<CAAction>)actionForLayer:(CALayer *)layer forKey:(NSString *)event
{
    id<CAAction> action = [super actionForLayer:layer forKey:event];

    if ([event isEqualToString:@"cornerRadius"])
    {
        CABasicAnimation *boundsAction = (CABasicAnimation *)[self actionForLayer:layer forKey:@"bounds"];
            if ([boundsAction isKindOfClass:[CABasicAnimation class]] && [boundsAction.fromValue isKindOfClass:[NSValue class]])
        {            
            CABasicAnimation *cornerRadiusAction = [CABasicAnimation animationWithKeyPath:@"cornerRadius"];
            cornerRadiusAction.delegate = boundsAction.delegate;
            cornerRadiusAction.duration = boundsAction.duration;
            cornerRadiusAction.fillMode = boundsAction.fillMode;
            cornerRadiusAction.timingFunction = boundsAction.timingFunction;

            CGRect fromBounds = [(NSValue *)boundsAction.fromValue CGRectValue];
            CGFloat fromRadius = [self radiusForBounds:fromBounds];
            cornerRadiusAction.fromValue = [NSNumber numberWithFloat:fromRadius];

            return cornerRadiusAction;
        }
    }

    return action;
}

@end

Используя действие, предоставляемое представлением для границ, я смог получить правильную длительность, режим заполнения и функцию синхронизации, а главное - делегировать - без этого блок завершения анимаций UIView не запускался.

Анимация радиуса повторяет анимацию границ практически во всех обстоятельствах - есть несколько крайних случаев, которые я пытаюсь сгладить, но в основном они есть. Стоит также упомянуть, что жесты прижима все еще иногда бывают рывками - я думаю, что даже ускоренное рисование все еще стоит дорого.

Это было действительно полезно, и работает почти идеально. Я просто хотел бы отметить, что я думаю, что вы должны добавить анимацию cornerRadiusAction к слою, а не возвращать его (должно быть возвращено исходное действие), чтобы конец анимации был правильно отправлен (в моем случае блок анимации UIView никогда не вызывал конец блок, если я не вернул оригинальное действие). Кроме того, вы будете сталкиваться с проблемами всякий раз, когда анимация отличается от стандартной (я взломал метод для CASpringAnimation, но это частный API). Rupert
Ваше решение отлично работает, за исключением того, что CASpringAnimation была проблемой и для меня - мое решение - использоватьCABasicAnimation *cornerRadiusAction = [boundsAction copy];, а затем просто установите.keypath а также.fromValue свойства отдельно. Все остальные.delegateвойства @ etc скопированы. Loz
В быстром, я получаю ошибку при приведенииCAAction вCABasicAnimation. Какие-либо предложения alcamla
Этот подход можно изменить, чтобы он снова работал в более новой версии iOS и работает для анимации с автоповоротом. Видеть этот ответ. rob mayoff

Похожие вопросы