在两个UIButton图像之间实现渐变

16

我希望能够在UITableView中设置收藏品时,在两个UIButton图像之间进行淡入淡出的过渡效果。

目前,转换是没有任何效果的 - 它只会在点击/触摸时直接更改图像:

trans_img = [UIImage imageNamed:@"fav_on.png"];

NSArray *subviews = [owningCell subviews];
UIButton *favbutton = [subviews objectAtIndex:2];

favbutton.imageView.animationImages =
[NSArray arrayWithObjects:trans_img,
 nil];

[favbutton.imageView startAnimating];

我找到的一切都是在UIView之间的过渡 :(

如果图像fav_off可以平滑地转换为fav_on,反之亦然,就像淡入淡出一样,那就太好了。

8个回答

36

看起来你所要寻找的是这个。它可以动画地显示UIButton上的图片,而无需添加新图片、创建数组或更改透明度值!

CABasicAnimation *crossFade = [CABasicAnimation animationWithKeyPath:@"contents"];
crossFade.duration = 0.7;
crossFade.fromValue = (id)oldImage.CGImage;
crossFade.toValue = (id)newImage.CGImage;
crossFade.removedOnCompletion = NO;
crossFade.fillMode = kCAFillModeForwards;
[button.imageView.layer addAnimation:crossFade forKey:@"animateContents"];

//Make sure to add Image normally after so when the animation
//is done it is set to the new Image
[button setImage:newImage forState:UIControlStateNormal];

1
感谢您提供有关使用核心动画的迷你课程! - phatmann
2
出于好奇,你为什么选择将 removedOnCompletion 设置为 NO - ravron
3
这句话的意思是,这样做可以确保动画不会在结束时还原。说实话,我不确定这对于此操作是否百分之百必要,因为我们无论如何都将newImage设置为按钮的图像......但我通常总是加入它以保险起见。 - jkanter

7

在 Swift 中:

  import UIKit

   extension UIButton {

       func changeImageAnimated(image: UIImage?) {
           guard let imageView = self.imageView, currentImage = imageView.image, newImage = image else {
               return
           }
           let crossFade: CABasicAnimation = CABasicAnimation(keyPath: "contents")
           crossFade.duration = 0.3
           crossFade.fromValue = currentImage.CGImage
           crossFade.toValue = newImage.CGImage
           crossFade.removedOnCompletion = false
           crossFade.fillMode = kCAFillModeForwards
           imageView.layer.addAnimation(crossFade, forKey: "animateContents")
       }
   }


   self.playAndPauseVideo.changeImageAnimated(UIImage(named: "pauseVideo"))

1
太好了!谢谢!有一个备注:我在func self.setImage(newImage, for: .normal)中添加了最后一行。这对于第二次按按钮是必要的。 - Włodzimierz Woźniak

7

感谢 @jkanter 提供的优秀答案。我将其制作成了一个Swift 3.0的扩展,认为这也可能对任何遇到这篇文章的人有用。

extension UIButton {

    func setImage(_ image: UIImage?, for state: UIControlState, animated: Bool) {
        guard animated, let oldImage = imageView?.image, let newImage = image else {
            // Revert to default functionality
            setImage(image, for: state)
            return
        }

        let crossFade = CABasicAnimation(keyPath:"contents")
        crossFade.duration = 0.35
        crossFade.fromValue = oldImage.cgImage
        crossFade.toValue = newImage.cgImage
        crossFade.isRemovedOnCompletion = false
        imageView?.layer.add(crossFade, forKey: "animateContents")

        setImage(image, for: state)
    }
}

3
这是对@Ponja答案的轻微改进,通常效果很好。不幸的是,新图像不会“粘住”,所以如果你想在两个图像之间切换,第一个淡入淡出会顺利进行,但返回到原始图像时将“瞬间”回退而没有淡入淡出。通过使用CATransaction解决了这个问题:
import UIKit

extension UIButton {
    func changeImageAnimated(image: UIImage?) {
        guard let imageView = self.imageView, currentImage = imageView.image, newImage = image else {
            return
        }
        CATransaction.begin()
        CATransaction.setCompletionBlock {
            self.setImage(newImage, forState: UIControlState.Normal)
        }
        let crossFade: CABasicAnimation = CABasicAnimation(keyPath: "contents")
        crossFade.duration = 0.3
        crossFade.fromValue = currentImage.CGImage
        crossFade.toValue = newImage.CGImage
        crossFade.removedOnCompletion = false
        crossFade.fillMode = kCAFillModeForwards
        imageView.layer.addAnimation(crossFade, forKey: "animateContents")
        CATransaction.commit()
    }
}

这个功能非常好,但是我有一个tintColor,它会变成黑色。你知道我怎样才能保持当前的tintColor吗? - Morgan
最终我只是单独添加了另一个CABasicAnimation来处理颜色,这似乎完成了任务。 - Morgan

3
您可以尝试像这样过渡Alpha值,以获得您想要的效果:
trans_img = [UIImage imageNamed:@"fav_on.png"];

NSArray *subviews = [owningCell subviews];
UIButton *favbutton = [subviews objectAtIndex:2];
[UIView animateWithDuration:0.5 animations:^{
    favbutton.alpha = 0.0f;
} completion:^(BOOL finished) {
    favbutton.imageView.animationImages = [NSArray arrayWithObjects:trans_img,nil];
    [favbutton.imageView startAnimating];
    [UIView animateWithDuration:0.5 animations:^{
        favbutton.alpha = 1.0f;
    }];
}];

这个可以运行,但我不明白为什么。你能解释一下第二部分的animationImages吗? - Fabien Warniez

1
@SuperDuperTango的答案添加了tintColor:
extension UIButton {
    func changeImageAnimated(image: UIImage?) {
        guard let imageView = self.imageView, currentImage = imageView.image, newImage = image else {
            return
        }
        CATransaction.begin()
        CATransaction.setCompletionBlock {
            self.setImage(newImage, forState: UIControlState.Normal)
        }
        let crossFade: CABasicAnimation = CABasicAnimation(keyPath: "contents")
        crossFade.duration = 0.3
        crossFade.fromValue = currentImage.CGImage
        crossFade.toValue = newImage.CGImage
        crossFade.removedOnCompletion = false
        crossFade.fillMode = kCAFillModeForwards
        imageView.layer.addAnimation(crossFade, forKey: "animateContents")
        CATransaction.commit()

        let crossFadeColor: CABasicAnimation = CABasicAnimation(keyPath: "contents")
        crossFadeColor.duration = 0.3
        crossFadeColor.fromValue = UIColor.blackColor()
        crossFadeColor.toValue = UIColor(red: 232.0/255.0, green: 85.0/255.0, blue: 71.0/255.0, alpha: 1.0)
        crossFadeColor.removedOnCompletion = false
        crossFadeColor.fillMode = kCAFillModeForwards
        imageView.layer.addAnimation(crossFadeColor, forKey: "animateContents")
        CATransaction.commit()
    }
}

0

@SuperDuperTango在Swift 4.2中的回答:

extension UIButton {
    func changeImageAnimated(image: UIImage?) {
        guard let imageView = self.imageView, let currentImage = imageView.image, let newImage = image else { return }

        CATransaction.begin()
        CATransaction.setCompletionBlock {
            self.setImage(newImage, for: .normal)
        }
        let crossFade: CABasicAnimation = CABasicAnimation(keyPath: "contents")
        crossFade.duration = 0.3
        crossFade.fromValue = currentImage.cgImage
        crossFade.toValue = newImage.cgImage
        crossFade.isRemovedOnCompletion = false
        crossFade.fillMode = CAMediaTimingFillMode.forwards
        imageView.layer.add(crossFade, forKey: "animateContents")
        CATransaction.commit()
    }
}

-1

jkanterSwift 中的回答:

let crossFade = CABasicAnimation.init(keyPath: "contents")
crossFade.duration = 0.7
crossFade.fromValue = oldImage.cgImage
crossFade.toValue = newImage.cgImage
crossFade.isRemovedOnCompletion = false
crossFade.fillMode = kCAFillModeForwards
button.imageView?.layer.add(crossFade, forKey: "animateContents")

//Make sure to add Image normally after so when the animation
//is done it is set to the new Image
button.setImage(newImage, for: .normal)

为什么要点踩呢?我没事,但请不要犹豫地告诉我原因。 - Hemang

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接