小说绘上架版本
This commit is contained in:
@@ -0,0 +1,51 @@
|
||||
//
|
||||
// WXYZ_BadgeView.h
|
||||
// WXReader
|
||||
//
|
||||
// Created by Andrew on 2020/8/7.
|
||||
// Copyright © 2020 Andrew. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
FOUNDATION_EXPORT CGFloat const RKNotificationHubDefaultDiameter;
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface WXYZ_BadgeView : NSObject
|
||||
|
||||
//%%% setup
|
||||
- (id)initWithView:(UIView *)view;
|
||||
- (id)initWithBarButtonItem:(UIBarButtonItem *)barButtonItem;
|
||||
|
||||
//%%% adjustment methods
|
||||
- (void)setView:(UIView *)view andCount:(int)startCount;
|
||||
- (void)setCircleAtFrame:(CGRect)frame;
|
||||
- (void)setCircleColor:(UIColor*)circleColor labelColor:(UIColor*)labelColor;
|
||||
- (void)setCircleBorderColor:(UIColor *)color borderWidth:(CGFloat)width;
|
||||
- (void)moveCircleByX:(CGFloat)x Y:(CGFloat)y;
|
||||
- (void)scaleCircleSizeBy:(CGFloat)scale;
|
||||
@property (nonatomic, strong) UIFont *countLabelFont;
|
||||
|
||||
//%%% changing the count
|
||||
- (void)increment;
|
||||
- (void)incrementBy:(int)amount;
|
||||
- (void)decrement;
|
||||
- (void)decrementBy:(int)amount;
|
||||
@property (nonatomic, assign) int count;
|
||||
@property (nonatomic, assign) int maxCount;
|
||||
|
||||
//%%% hiding / showing the count
|
||||
- (void)hideCount;
|
||||
- (void)showCount;
|
||||
|
||||
//%%% animations
|
||||
- (void)pop;
|
||||
- (void)blink;
|
||||
- (void)bump;
|
||||
|
||||
@property (nonatomic)UIView *hubView;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,409 @@
|
||||
//
|
||||
// WXYZ_BadgeView.m
|
||||
// WXReader
|
||||
//
|
||||
// Created by Andrew on 2020/8/7.
|
||||
// Copyright © 2020 Andrew. All rights reserved.
|
||||
//
|
||||
|
||||
#import "WXYZ_BadgeView.h"
|
||||
#import <QuartzCore/QuartzCore.h>
|
||||
|
||||
//%%% default diameter
|
||||
CGFloat const RKNotificationHubDefaultDiameter = 30;
|
||||
static CGFloat const kCountMagnitudeAdaptationRatio = 0.3;
|
||||
//%%% pop values
|
||||
static CGFloat const kPopStartRatio = .85;
|
||||
static CGFloat const kPopOutRatio = 1.05;
|
||||
static CGFloat const kPopInRatio = .95;
|
||||
|
||||
//%%% blink values
|
||||
static CGFloat const kBlinkDuration = 0.1;
|
||||
static CGFloat const kBlinkAlpha = 0.1;
|
||||
|
||||
//%%% bump values
|
||||
static CGFloat const kFirstBumpDistance = 8.0;
|
||||
static CGFloat const kBumpTimeSeconds = 0.13;
|
||||
static CGFloat const SECOND_BUMP_DIST = 4.0;
|
||||
static CGFloat const kBumpTimeSeconds2 = 0.1;
|
||||
|
||||
@interface RKView : UIView
|
||||
@property (nonatomic) BOOL isUserChangingBackgroundColor;
|
||||
@end
|
||||
|
||||
@implementation RKView
|
||||
|
||||
- (void)setBackgroundColor:(UIColor *)backgroundColor
|
||||
{
|
||||
if (self.isUserChangingBackgroundColor) {
|
||||
super.backgroundColor = backgroundColor;
|
||||
self.isUserChangingBackgroundColor = NO;
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation WXYZ_BadgeView{
|
||||
int curOrderMagnitude;
|
||||
UILabel *countLabel;
|
||||
RKView *redCircle;
|
||||
CGPoint initialCenter;
|
||||
CGRect baseFrame;
|
||||
CGRect initialFrame;
|
||||
BOOL isIndeterminateMode;
|
||||
}
|
||||
|
||||
@synthesize hubView;
|
||||
|
||||
#pragma mark - SETUP
|
||||
|
||||
- (id)initWithView:(UIView *)view
|
||||
{
|
||||
self = [super init];
|
||||
if (!self) return nil;
|
||||
|
||||
_maxCount = 100000;
|
||||
[self setView:view andCount:0];
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (id)initWithBarButtonItem:(UIBarButtonItem *)barButtonItem
|
||||
{
|
||||
self = [self initWithView:[barButtonItem valueForKey:@"view"]];
|
||||
[self scaleCircleSizeBy:0.7];
|
||||
[self moveCircleByX:-5.0 Y:0];
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
//%%% give this a view and an initial count (0 hides the notification circle)
|
||||
// and it will make a hub for you
|
||||
- (void)setView:(UIView *)view andCount:(int)startCount
|
||||
{
|
||||
curOrderMagnitude = 0;
|
||||
|
||||
CGRect frame = view.frame;
|
||||
|
||||
isIndeterminateMode = NO;
|
||||
|
||||
redCircle = [[RKView alloc]init];
|
||||
redCircle.userInteractionEnabled = NO;
|
||||
redCircle.isUserChangingBackgroundColor = YES;
|
||||
redCircle.backgroundColor = [UIColor redColor];
|
||||
|
||||
countLabel = [[UILabel alloc]initWithFrame:redCircle.frame];
|
||||
countLabel.userInteractionEnabled = NO;
|
||||
self.count = startCount;
|
||||
[countLabel setTextAlignment:NSTextAlignmentCenter];
|
||||
countLabel.textColor = [UIColor whiteColor];
|
||||
countLabel.backgroundColor = [UIColor clearColor];
|
||||
|
||||
[self setCircleAtFrame:CGRectMake(frame.size.width- (RKNotificationHubDefaultDiameter*2/3), -RKNotificationHubDefaultDiameter/3, RKNotificationHubDefaultDiameter, RKNotificationHubDefaultDiameter)];
|
||||
|
||||
[view addSubview:redCircle];
|
||||
[view addSubview:countLabel];
|
||||
[view bringSubviewToFront:redCircle];
|
||||
[view bringSubviewToFront:countLabel];
|
||||
hubView = view;
|
||||
[self checkZero];
|
||||
}
|
||||
|
||||
//%%% set the frame of the notification circle relative to the button
|
||||
- (void)setCircleAtFrame:(CGRect)frame
|
||||
{
|
||||
[redCircle setFrame:frame];
|
||||
initialCenter = CGPointMake(frame.origin.x+frame.size.width/2, frame.origin.y+frame.size.height/2);
|
||||
baseFrame = frame;
|
||||
initialFrame = frame;
|
||||
countLabel.frame = redCircle.frame;
|
||||
redCircle.layer.cornerRadius = frame.size.height/2;
|
||||
[countLabel setFont:[UIFont fontWithName:@"HelveticaNeue" size:frame.size.width/2]];
|
||||
[self expandToFitLargerDigits];
|
||||
}
|
||||
|
||||
//%%% moves the circle by x amount on the x axis and y amount on the y axis
|
||||
- (void)moveCircleByX:(CGFloat)x Y:(CGFloat)y
|
||||
{
|
||||
CGRect frame = redCircle.frame;
|
||||
frame.origin.x += x;
|
||||
frame.origin.y += y;
|
||||
[self setCircleAtFrame:frame];
|
||||
}
|
||||
|
||||
//%%% changes the size of the circle. setting a scale of 1 has no effect
|
||||
- (void)scaleCircleSizeBy:(CGFloat)scale
|
||||
{
|
||||
CGRect fr = initialFrame;
|
||||
CGFloat width = fr.size.width * scale;
|
||||
CGFloat height = fr.size.height * scale;
|
||||
CGFloat wdiff = (fr.size.width - width) / 2;
|
||||
CGFloat hdiff = (fr.size.height - height) / 2;
|
||||
|
||||
CGRect frame = CGRectMake(fr.origin.x + wdiff, fr.origin.y + hdiff, width, height);
|
||||
[self setCircleAtFrame:frame];
|
||||
}
|
||||
|
||||
//%%% change the color of the notification circle
|
||||
- (void)setCircleColor:(UIColor*)circleColor labelColor:(UIColor*)labelColor
|
||||
{
|
||||
redCircle.isUserChangingBackgroundColor = YES;
|
||||
redCircle.backgroundColor = circleColor;
|
||||
[countLabel setTextColor:labelColor];
|
||||
}
|
||||
|
||||
- (void)setCircleBorderColor:(UIColor *)color borderWidth:(CGFloat)width {
|
||||
redCircle.layer.borderColor = color.CGColor;
|
||||
redCircle.layer.borderWidth = width;
|
||||
}
|
||||
|
||||
- (void)hideCount
|
||||
{
|
||||
countLabel.hidden = YES;
|
||||
isIndeterminateMode = YES;
|
||||
}
|
||||
|
||||
- (void)showCount
|
||||
{
|
||||
isIndeterminateMode = NO;
|
||||
[self checkZero];
|
||||
}
|
||||
|
||||
#pragma mark - ATTRIBUTES
|
||||
|
||||
//%%% increases count by 1
|
||||
- (void)increment
|
||||
{
|
||||
[self incrementBy:1];
|
||||
}
|
||||
|
||||
//%%% increases count by amount
|
||||
- (void)incrementBy:(int)amount
|
||||
{
|
||||
self.count += amount;
|
||||
}
|
||||
|
||||
//%%% decreases count
|
||||
- (void)decrement
|
||||
{
|
||||
[self decrementBy:1];
|
||||
}
|
||||
|
||||
//%%% decreases count by amount
|
||||
- (void)decrementBy:(int)amount
|
||||
{
|
||||
if (amount >= self.count) {
|
||||
self.count = 0;
|
||||
return;
|
||||
}
|
||||
self.count -= amount;
|
||||
}
|
||||
|
||||
//%%% set the count yourself
|
||||
- (void)setCount:(int)newCount
|
||||
{
|
||||
_count = newCount;
|
||||
|
||||
NSString *labelText = [NSString stringWithFormat:@"%@", @(self.count)];
|
||||
|
||||
if (_count > self.maxCount){
|
||||
labelText = [NSString stringWithFormat:@"%@+", @(self.maxCount)];
|
||||
}
|
||||
|
||||
countLabel.text = labelText;
|
||||
[self checkZero];
|
||||
[self expandToFitLargerDigits];
|
||||
}
|
||||
|
||||
//%% set the font of the label
|
||||
- (void)setCountLabelFont:(UIFont *)font
|
||||
{
|
||||
[countLabel setFont:font];
|
||||
}
|
||||
|
||||
- (UIFont *)countLabelFont
|
||||
{
|
||||
return countLabel.font;
|
||||
}
|
||||
|
||||
#pragma mark - ANIMATION
|
||||
|
||||
//%%% animation that resembles facebook's pop
|
||||
- (void)pop
|
||||
{
|
||||
const float height = baseFrame.size.height;
|
||||
const float width = baseFrame.size.width;
|
||||
const float pop_start_h = height * kPopStartRatio;
|
||||
const float pop_start_w = width * kPopStartRatio;
|
||||
const float time_start = 0.05;
|
||||
const float pop_out_h = height * kPopOutRatio;
|
||||
const float pop_out_w = width * kPopOutRatio;
|
||||
const float time_out = .2;
|
||||
const float pop_in_h = height * kPopInRatio;
|
||||
const float pop_in_w = width * kPopInRatio;
|
||||
const float time_in = .05;
|
||||
const float pop_end_h = height;
|
||||
const float pop_end_w = width;
|
||||
const float time_end = 0.05;
|
||||
|
||||
CABasicAnimation *startSize = [CABasicAnimation animationWithKeyPath:@"cornerRadius"];
|
||||
startSize.duration = time_start;
|
||||
startSize.beginTime = 0;
|
||||
startSize.fromValue = @(pop_end_h / 2);
|
||||
startSize.toValue = @(pop_start_h / 2);
|
||||
startSize.removedOnCompletion = FALSE;
|
||||
|
||||
CABasicAnimation *outSize = [CABasicAnimation animationWithKeyPath:@"cornerRadius"];
|
||||
outSize.duration = time_out;
|
||||
outSize.beginTime = time_start;
|
||||
outSize.fromValue = startSize.toValue;
|
||||
outSize.toValue = @(pop_out_h / 2);
|
||||
outSize.removedOnCompletion = FALSE;
|
||||
|
||||
CABasicAnimation *inSize = [CABasicAnimation animationWithKeyPath:@"cornerRadius"];
|
||||
inSize.duration = time_in;
|
||||
inSize.beginTime = time_start+time_out;
|
||||
inSize.fromValue = outSize.toValue;
|
||||
inSize.toValue = @(pop_in_h / 2);
|
||||
inSize.removedOnCompletion = FALSE;
|
||||
|
||||
CABasicAnimation *endSize = [CABasicAnimation animationWithKeyPath:@"cornerRadius"];
|
||||
endSize.duration = time_end;
|
||||
endSize.beginTime = time_in+time_out+time_start;
|
||||
endSize.fromValue = inSize.toValue;
|
||||
endSize.toValue = @(pop_end_h / 2);
|
||||
endSize.removedOnCompletion = FALSE;
|
||||
|
||||
CAAnimationGroup *group = [CAAnimationGroup animation];
|
||||
[group setDuration: time_start+time_out+time_in+time_end];
|
||||
[group setAnimations:@[startSize, outSize, inSize, endSize]];
|
||||
|
||||
[redCircle.layer addAnimation:group forKey:nil];
|
||||
|
||||
[UIView animateWithDuration:time_start animations:^{
|
||||
CGRect frame = redCircle.frame;
|
||||
CGPoint center = redCircle.center;
|
||||
frame.size.height = pop_start_h;
|
||||
frame.size.width = pop_start_w;
|
||||
redCircle.frame = frame;
|
||||
redCircle.center = center;
|
||||
}completion:^(BOOL complete){
|
||||
[UIView animateWithDuration:time_out animations:^{
|
||||
CGRect frame = redCircle.frame;
|
||||
CGPoint center = redCircle.center;
|
||||
frame.size.height = pop_out_h;
|
||||
frame.size.width = pop_out_w;
|
||||
redCircle.frame = frame;
|
||||
redCircle.center = center;
|
||||
}completion:^(BOOL complete){
|
||||
[UIView animateWithDuration:time_in animations:^{
|
||||
CGRect frame = redCircle.frame;
|
||||
CGPoint center = redCircle.center;
|
||||
frame.size.height = pop_in_h;
|
||||
frame.size.width = pop_in_w;
|
||||
redCircle.frame = frame;
|
||||
redCircle.center = center;
|
||||
}completion:^(BOOL complete){
|
||||
[UIView animateWithDuration:time_end animations:^{
|
||||
CGRect frame = redCircle.frame;
|
||||
CGPoint center = redCircle.center;
|
||||
frame.size.height = pop_end_h;
|
||||
frame.size.width = pop_end_w;
|
||||
redCircle.frame = frame;
|
||||
redCircle.center = center;
|
||||
}];
|
||||
}];
|
||||
}];
|
||||
}];
|
||||
}
|
||||
|
||||
//%%% animation that flashes on an off
|
||||
- (void)blink
|
||||
{
|
||||
[self setAlpha:kBlinkAlpha];
|
||||
|
||||
[UIView animateWithDuration:kBlinkDuration animations:^{
|
||||
[self setAlpha:1];
|
||||
}completion:^(BOOL complete){
|
||||
[UIView animateWithDuration:kBlinkDuration animations:^{
|
||||
[self setAlpha:kBlinkAlpha];
|
||||
}completion:^(BOOL complete){
|
||||
[UIView animateWithDuration:kBlinkDuration animations:^{
|
||||
[self setAlpha:1];
|
||||
}];
|
||||
}];
|
||||
}];
|
||||
}
|
||||
|
||||
//%%% animation that jumps similar to OSX dock icons
|
||||
- (void)bump
|
||||
{
|
||||
if (!CGPointEqualToPoint(initialCenter,redCircle.center)) {
|
||||
//%%% canel previous animation
|
||||
}
|
||||
|
||||
[self bumpCenterY:0];
|
||||
[UIView animateWithDuration:kBumpTimeSeconds animations:^{
|
||||
[self bumpCenterY:kFirstBumpDistance];
|
||||
}completion:^(BOOL complete){
|
||||
[UIView animateWithDuration:kBumpTimeSeconds animations:^{
|
||||
[self bumpCenterY:0];
|
||||
}completion:^(BOOL complete){
|
||||
[UIView animateWithDuration:kBumpTimeSeconds2 animations:^{
|
||||
[self bumpCenterY:SECOND_BUMP_DIST];
|
||||
}completion:^(BOOL complete){
|
||||
[UIView animateWithDuration:kBumpTimeSeconds2 animations:^{
|
||||
[self bumpCenterY:0];
|
||||
}];
|
||||
}];
|
||||
}];
|
||||
}];
|
||||
}
|
||||
|
||||
#pragma mark - HELPERS
|
||||
|
||||
//%%% changes the Y origin of the notification circle
|
||||
- (void)bumpCenterY:(float)yVal
|
||||
{
|
||||
CGPoint center = redCircle.center;
|
||||
center.y = initialCenter.y-yVal;
|
||||
redCircle.center = center;
|
||||
countLabel.center = center;
|
||||
}
|
||||
|
||||
- (void)setAlpha:(float)alpha
|
||||
{
|
||||
redCircle.alpha = alpha;
|
||||
countLabel.alpha = alpha;
|
||||
}
|
||||
|
||||
//%%% hides the notification if the value is 0
|
||||
- (void)checkZero
|
||||
{
|
||||
if (self.count <= 0) {
|
||||
redCircle.hidden = YES;
|
||||
countLabel.hidden = YES;
|
||||
} else {
|
||||
redCircle.hidden = NO;
|
||||
if (!isIndeterminateMode) {
|
||||
countLabel.hidden = NO;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)expandToFitLargerDigits {
|
||||
int orderOfMagnitude = log10((double)self.count);
|
||||
orderOfMagnitude = (orderOfMagnitude >= 2) ? orderOfMagnitude : 1;
|
||||
CGRect frame = initialFrame;
|
||||
frame.size.width = initialFrame.size.width * (1 + kCountMagnitudeAdaptationRatio * (orderOfMagnitude - 1));
|
||||
frame.origin.x = initialFrame.origin.x - (frame.size.width - initialFrame.size.width) / 2;
|
||||
|
||||
[redCircle setFrame:frame];
|
||||
initialCenter = CGPointMake(frame.origin.x+frame.size.width/2, frame.origin.y+frame.size.height/2);
|
||||
baseFrame = frame;
|
||||
countLabel.frame = redCircle.frame;
|
||||
curOrderMagnitude = orderOfMagnitude;
|
||||
}
|
||||
|
||||
@end
|
||||
Reference in New Issue
Block a user