আমি UI উপাদান তৈরি করতে পছন্দ করি। এটি আমাকে একটি বিল্ড তৈরি করার পরে দেখতে এবং ব্যবহার করতে এক ধরণের নান্দনিক আনন্দ দেয়। এটি সামগ্রিকভাবে MacOS/iOS ইকোসিস্টেমের প্রতি আমার ভালবাসার সাথে একত্রিত হয়। সম্প্রতি, আমাকে UIKit
ব্যবহার করে একটি কাস্টম অগ্রগতি বার তৈরি করার দায়িত্ব দেওয়া হয়েছিল। এটি একটি বড় চুক্তি নয়, তবে সম্ভবত 99% iOS ডেভেলপাররা তাদের ক্যারিয়ারের কোনো না কোনো সময়ে স্ক্র্যাচ থেকে একটি তৈরি করেছেন। আমার ক্ষেত্রে, আমি 0 থেকে 1 এর মান পরিসরের উপর ভিত্তি করে অ্যানিমেশন সহ কয়েকটি CALayer
তৈরি করার চেয়ে আরও কিছু করতে চেয়েছিলাম। আমি এমন একটি সমাধানের লক্ষ্যে ছিলাম যা অনেক পরিস্থিতিতে যেখানে একটি অগ্রগতি দণ্ডের প্রয়োজন হয় তার জন্য উপযুক্ত হবে .
আমি নিজেকে সেই দিনগুলিতে ফিরিয়ে দিতে চাই যখন অ্যাপল বিশ্বের UI উপাদানগুলি এইরকম দেখায়:
UI ডিজাইনের বিবর্তনের উপর প্রতিফলিত করে, অ্যাপল বিশ্বের UI উপাদানগুলি 2014 সালে iOS 7 প্রবর্তনের সাথে একটি ফ্ল্যাট ডিজাইন গ্রহণ করার পর থেকে প্রায় এক দশক হয়ে গেছে। সুতরাং, আসুন ম্যাক ওএস এবং iOS এর আগের সংস্করণগুলির জন্য নস্টালজিয়ায় লিপ্ত হই এবং তৈরি করি একসাথে একটি সুন্দর অগ্রগতি বার।
আপনি যদি UIKit
ব্যবহার করে উপভোগ করেন কিন্তু CALayers কিভাবে ব্যবহার করতে হয় সে সম্পর্কে এখনও অনিশ্চিত, এই নিবন্ধটি আপনার জন্য উপযোগী হবে। এখানে, আমরা সাধারণ লেআউট, অ্যানিমেশন এবং অঙ্কন চ্যালেঞ্জের মধ্য দিয়ে যাব, আপনাকে পথের মধ্যে সাহায্য করার জন্য অন্তর্দৃষ্টি এবং সমাধান প্রদান করব।
অগ্রগতি বারটি সহজবোধ্য বলে মনে হতে পারে, কিন্তু আমরা যদি বিশদ বিবরণে গভীরভাবে ডুব দিই, তাহলে আপনি বুঝতে পারবেন যে এটির জন্য প্রচুর সাবলেয়ারের প্রয়োজন।
আমি এখানে এটি বিস্তারিতভাবে CALayers
এ অনুসন্ধান করতে যাচ্ছি না। আপনি এই নিবন্ধটি দেখতে পারেন যেখানে আমি CALayers
কী এবং তারা কীভাবে কাজ করে তা সংক্ষেপে বর্ণনা করেছি: https://hackernoon.com/rolling-numbers-animation-using-only-calayers ।
সুতরাং, আসুন এটিকে ছোট স্তরে ভাঙ্গা যাক:
পটভূমি স্তরে দুটি অতিরিক্ত শিশু স্তর রয়েছে:
আসুন এটি সঠিকভাবে গঠন করি:
RetroProgressBar [UIView] |--General background [CALayer] |--Inner Shadow [CALayer] |--Border Gradient [CALayer] |--Progress Background Layer [CALayer] |--Animated Mask Layer [CALayer] |--Glare [CALayer] |--Shimering [CALayer]
সংক্ষেপে, এটি কীভাবে কাজ করবে তা এখানে: অগ্রগতি দণ্ডটি 0.0 থেকে 1.0 পর্যন্ত একটি মানকে প্রতিক্রিয়া জানাতে হবে। যখন মান পরিবর্তন হয়, আমাদের এই মানের উপর ভিত্তি করে একটি স্তরের প্রস্থ সামঞ্জস্য করতে হবে:
bounds.width * value
এই সহজ হিসাব ব্যবহার করে, আমরা CATransaction
ব্যবহার করে প্রস্থ পরিবর্তন করতে পারি। কিন্তু আমরা এটি করার আগে, আমাদের বুঝতে হবে যে মান পরিবর্তন হলে কোন স্তরগুলি পরিবর্তন করা উচিত। আসুন কাঠামোটি পুনরায় দেখুন এবং মান পরিবর্তনের জন্য প্রতিক্রিয়াশীল হওয়া প্রয়োজন এমন স্তরগুলি চিহ্নিত করুন।
RetroProgressBar [UIView] |--General background [CALayer] |--Inner Shadow [CALayer] |--Border Gradient [CALayer] |--Progress Background Layer [CALayer] |--Animated Mask Layer [CALayer] : [Animated Width] |--Glare [CALayer] : [Animated Width] |--Shimering [CALayer] : [Animated Width]
দেখা যাচ্ছে যে আমাদের একটি প্রোটোকল তৈরি করতে হবে যা স্তরগুলির দ্বারা গৃহীত হতে পারে যার মান ভিত্তিক অ্যানিমেটেড প্রস্থ প্রয়োজন।
প্রোটোকলে, আমাদের মাত্র দুটি পদ্ধতির প্রয়োজন: অ্যানিমেশন এবং ছাড়াই লেয়ারের প্রস্থ পরিবর্তন করুন।
protocol ProgressAnimatable: CALayer { /// Sets the layer's width instantly to the specified value. func setToWidth(_ width: CGFloat) /// Animates the layer's width to the specified value. func animateToWidth(_ width: CGFloat, duration: TimeInterval, animationType: CAMediaTimingFunctionName, completion: (() -> Void)?) }
বাস্তবায়ন:
extension ProgressAnimatable { func setToWidth(_ width: CGFloat) { guard width >= 0 else { return } removeAllAnimations() CATransaction.begin() CATransaction.setDisableActions(true) self.bounds.size.width = width CATransaction.commit() } func animateToWidth(_ width: CGFloat, duration: TimeInterval, animationType: CAMediaTimingFunctionName = .easeInEaseOut, completion: (() -> Void)? = nil) { guard width >= 0, width != self.bounds.width else { completion?() return } let animation = CABasicAnimation(keyPath: "bounds.size.width") animation.fromValue = bounds.width animation.toValue = width animation.duration = duration animation.fillMode = .forwards animation.isRemovedOnCompletion = false animation.timingFunction = CAMediaTimingFunction(name: animationType) CATransaction.begin() CATransaction.setCompletionBlock { self.bounds.size.width = width completion?() } self.add(animation, forKey: "widthChange") CATransaction.commit() } }
উভয় পদ্ধতিই CATransaction
ব্যবহার করে। প্রথম পদ্ধতিটি অদ্ভুত বলে মনে হতে পারে কারণ আমাদের শুধুমাত্র অ্যানিমেশন ছাড়াই একটি মান পরিবর্তন করতে হবে। তাহলে, কেন CATransaction
প্রয়োজন?
একটি মৌলিক CALayer
তৈরি করার চেষ্টা করুন এবং কোনো অ্যানিমেশন ছাড়াই এর প্রস্থ বা উচ্চতা পরিবর্তন করুন। আপনি যখন একটি বিল্ড চালান, আপনি একটি অ্যানিমেশনের সাথে CALayer
পরিবর্তনগুলি লক্ষ্য করবেন। এটা কিভাবে সম্ভব? কোর অ্যানিমেশনে, স্তর বৈশিষ্ট্যের পরিবর্তনগুলি (যেমন সীমা, অবস্থান, অস্বচ্ছতা, ইত্যাদি) সাধারণত সেই সম্পত্তির সাথে সম্পর্কিত ডিফল্ট অ্যাকশনের উপর ভিত্তি করে অ্যানিমেট করা হয়। এই ডিফল্ট আচরণটি প্রতিটি সম্পত্তি পরিবর্তনের জন্য স্পষ্ট অ্যানিমেশনের প্রয়োজন ছাড়াই মসৃণ ভিজ্যুয়াল ট্রানজিশন প্রদান করার উদ্দেশ্যে করা হয়েছে।
এর মানে হল যে আমাদের CALayer
এ ক্রিয়াকলাপগুলিকে স্পষ্টভাবে নিষ্ক্রিয় করতে হবে। CATransaction.setDisableActions(true)
সেট করে, আপনি নিশ্চিত করেন যে স্তরের প্রস্থ তাৎক্ষণিকভাবে নতুন মানতে আপডেট করা হয়েছে কোনো মধ্যবর্তী অ্যানিমেটেড ট্রানজিশন ছাড়াই।
দ্বিতীয় পদ্ধতিটি ProgressAnimatable
প্রোটোকলের সাথে সামঞ্জস্যপূর্ণ যে কোনও স্তরের প্রস্থকে অ্যানিমেট করার জন্য একটি নমনীয় এবং পুনরায় ব্যবহারযোগ্য উপায় সরবরাহ করে। এটি অ্যানিমেশনের সময়কাল এবং গতির উপর নিয়ন্ত্রণ প্রদান করে এবং একটি সমাপ্তি কর্মের জন্য একটি বিকল্প অন্তর্ভুক্ত করে। এটি UIKit কাঠামোর মধ্যে বিভিন্ন অ্যানিমেশন প্রয়োজনের জন্য এটিকে উপযুক্ত করে তোলে।
এই প্রোটোকল মেনে চলার জন্য আমাদের প্রার্থীরা হবে তিনটি স্তর: অ্যানিমেটেড মাস্ক লেয়ার, গ্লেয়ার লেয়ার এবং শিমারিং লেয়ার। এর এগিয়ে যান এবং এটি বাস্তবায়ন করা যাক.
এটি আমাদের প্রোটোকল মেনে চলার সময়, এবং এই সেটআপের সবচেয়ে গুরুত্বপূর্ণ স্তরটি হবে আমাদের সাধারণ CAShapeLayer
। কেন আমাদের এই মাস্ক দরকার? কেন এটি শুধুমাত্র একটি অ্যানিমেটেড প্রস্থ সহ একটি CALayer
হতে পারে না? একজন iOS ইঞ্জিনিয়ার হিসেবে, বিশেষ করে ফ্রন্ট-এন্ড ডেভেলপমেন্টের উপর ফোকাস করে, আপনাকে প্রায়শই আপনার উপাদানগুলির জন্য সম্ভাব্য ব্যবহারের ক্ষেত্রে পূর্বাভাস দিতে হবে। আমার উদাহরণে, একটি অগ্রগতি পটভূমি স্তর আছে। এটি একটি অ্যানিমেটেড CALayer
নয়, তবে এটি যদি CAReplicatorLayer
এর মতো কিছু দিয়ে অ্যানিমেটেড হয় তবে কী হবে? আপনি যদি কর্মক্ষমতা সম্পর্কে উদ্বিগ্ন হন, তাহলে আপনি সম্ভবত বিবেচনা করবেন যে এই ধরনের একটি স্তর একবার রেন্ডার করা উচিত এবং তারপরে কেবল তার অ্যানিমেশনটি সম্পাদন করা উচিত। তাই এই মাস্ক অত্যন্ত উপকারী হতে পারে। এটি এখনও বিভিন্ন অ্যানিমেশন পরিস্থিতির জন্য প্রয়োজনীয় নমনীয়তা অফার করার সময় দক্ষ রেন্ডারিংয়ের অনুমতি দেয়।
final class ProgressMaskLayer: CAShapeLayer, ProgressAnimatable { override init() { super.init() backgroundColor = UIColor.black.cgColor anchorPoint = CGPoint(x: 0, y: 0.5) } override init(layer: Any) { super.init(layer: layer) } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } }
আসুন এই লাইনটি একবার দেখে নেওয়া যাক:
anchorPoint = CGPoint(x: 0, y: 0.5)
এই লাইনটি আমাদের অগ্রগতি বারের জন্য অত্যন্ত গুরুত্বপূর্ণ। ডিফল্টরূপে, CALayers
কেন্দ্রীভূত হয়, anchorPoint
স্তরের কেন্দ্রে অবস্থিত, বাম প্রান্তের মাঝখানে নয় যেমনটি সাধারণত অনুমান করা হয়। anchorPoint
হল সেই বিন্দু যার চারপাশে স্তরের সমস্ত রূপান্তর ঘটে। একটি অগ্রগতি বার হিসাবে ব্যবহৃত একটি স্তরের জন্য, এই সেটিংটি সাধারণত মানে যখন স্তরটির প্রস্থ পরিবর্তিত হয়, তখন এটি anchorPoint
থেকে উভয় দিকে সমানভাবে প্রসারিত হয়। যাইহোক, যদি আমরা anchorPoint
বাম প্রান্তের মাঝখানে সামঞ্জস্য করি, তবে স্তরটি কেবল ডানদিকে প্রসারিত হবে যখন বাম প্রান্তটি জায়গায় স্থির থাকবে। এই আচরণটি একটি অগ্রগতি বারের জন্য অপরিহার্য, এটি নিশ্চিত করে যে বারের বৃদ্ধি উভয়ের পরিবর্তে এক দিক থেকে প্রদর্শিত হয়।
private let progressMaskLayer = ProgressMaskLayer() private let progressBackgroundLayer = ProgressBackgroundLayer() public init() { super.init(frame: .zero) layer.addSublayer(progressMaskLayer) layer.addSublayer(progressBackgroundLayer) progressBackgroundLayer.mask = progressMaskLayer }
উপরের স্নিপেটে, আমরা প্রগ্রেস বারের দুটি প্রধান অংশ শুরু করি: ProgressMaskLayer
এবং ProgressBackgroundLayer
। এর দ্বিতীয় এক কটাক্ষপাত করা যাক.
final class ProgressBackgroundLayer: CAGradientLayer { override init() { super.init() locations = [0, 0.5, 1] } override init(layer: Any) { super.init(layer: layer) } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func layoutSublayers() { super.layoutSublayers() colors = [ backgroundColor?.lightened(by: 0.3), backgroundColor, backgroundColor?.darkened(by: 0.5) ].compactMap({ $0 }) } }
আপনি দেখতে পাচ্ছেন, এটি একটি CAGradientLayer
যা তিনটি রঙের বৈশিষ্ট্যযুক্ত। এই ইমপ্লিমেন্টেশনটি একটি ইনজেকশন করা রঙকে একটি উল্লম্ব গ্রেডিয়েন্টে রূপান্তরিত করে, প্রগ্রেস বারের দৃশ্যমানতা বাড়ায় এবং এটিকে আরও বড় আকার দেয়।
এটি সম্ভব করার জন্য, আমি একটি CGColor
এক্সটেনশনে দুটি পদ্ধতি প্রস্তুত করেছি। মজার জন্য, আমি CGColor
UIColor
এ রূপান্তরিত না করে শুধুমাত্র CGColor
এর রাজ্যে খেলতে বেছে নিয়েছি:
extension CGColor { func darkened(by value: CGFloat) -> CGColor { guard let colorSpace = self.colorSpace, let components = self.components, colorSpace.model == .rgb, components.count >= 3 else { return self } let multiplier = 1 - min(max(value, 0), 1) let red = components[0] * multiplier let green = components[1] * multiplier let blue = components[2] * multiplier let alpha = components.count > 3 ? components[3] : 1.0 return CGColor( colorSpace: colorSpace, components: [red, green, blue, alpha] ) ?? self } func lightened(by value: CGFloat) -> CGColor { guard let colorSpace = self.colorSpace, let components = self.components, colorSpace.model == .rgb, components.count >= 3 else { return self } let red = min(components[0] + value, 1.0) let green = min(components[1] + value, 1.0) let blue = min(components[2] + value, 1.0) let alpha = components.count > 3 ? components[3] : 1.0 return CGColor( colorSpace: colorSpace, components: [red, green, blue, alpha] ) ?? self } }
আপনি দেখতে পাচ্ছেন, এটি খুব জটিল নয়। আমাদের শুধু colorSpace
, components
( red
, green
, blue
এবং alpha
), এবং model
পেতে হবে। এর মানে হল যে মানের উপর ভিত্তি করে, আমরা একটি নতুন রঙ গণনা করতে পারি: হয় গাঢ় বা হালকা।
আসুন আমাদের প্রগ্রেস বারকে আরও 3D-ইশ করার দিকে এগিয়ে যাই। এখন, আমাদের আরেকটি লেয়ার যোগ করতে হবে, যাকে আমরা গ্লেয়ার বলব।
এটি একটি সহজবোধ্য বাস্তবায়ন, কিন্তু এটি ProgressAnimatable
প্রোটোকলের সাথে সামঞ্জস্যপূর্ণ হওয়া উচিত।
final class ProgressGlareLayer: CALayer, ProgressAnimatable { override init() { super.init() backgroundColor = UIColor.white.withAlphaComponent(0.3).cgColor anchorPoint = CGPoint(x: 0, y: 0.5) } override init(layer: Any) { super.init(layer: layer) } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } }
জিনিসগুলি সহজ রাখার জন্য, আমাদের এখানে শুধুমাত্র 0.3 এর আলফা সহ পটভূমির রঙ সাদা হতে হবে।
আমি জানি কেউ কেউ বলতে পারে যে ম্যাকওএস এক্স বা আইওএস সংস্করণ 6 এর আগে এর মতো ঝলকানো প্রভাব সহ UI উপাদান ছিল না। তবে, আমি যেমন বলেছি, এটি একটি বাস্তব গল্প দ্বারা অনুপ্রাণিত একটি সিনেমার মতো। এই ঝিকিমিকি 3D প্রভাবকে উচ্চারণ করে এবং এটিকে আরও দৃশ্যমান করে তোলে।
final class ShimmeringLayer: CAGradientLayer, ProgressAnimatable { let glowColor: UIColor = UIColor.white private func shimmerAnimation() -> CABasicAnimation { let animation = CABasicAnimation(keyPath: "locations") animation.fromValue = [-1.0, -0.5, 0.0] animation.toValue = [1.0, 1.5, 2.0] animation.duration = 1.5 animation.repeatCount = Float.infinity return animation } private func opacityAnimation() -> CABasicAnimation { let animation = CABasicAnimation(keyPath: "opacity") animation.fromValue = 1.0 animation.toValue = 0.0 animation.duration = 0.1 // Quick transition to transparent animation.beginTime = shimmerAnimation().duration animation.fillMode = .forwards animation.isRemovedOnCompletion = false return animation } private func animationGroup() -> CAAnimationGroup { let pauseDuration = 3.0 // Duration of pause between shimmering let shimmerAnimation = shimmerAnimation() let opacityAnimation = opacityAnimation() let animationGroup = CAAnimationGroup() animationGroup.animations = [shimmerAnimation, opacityAnimation] animationGroup.duration = shimmerAnimation.duration + pauseDuration animationGroup.repeatCount = Float.infinity return animationGroup } override init() { super.init() setupLayer() } override init(layer: Any) { super.init(layer: layer) } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func layoutSublayers() { super.layoutSublayers() startAnimation() } private func setupLayer() { let lightColor = UIColor.white.withAlphaComponent(0.7).cgColor let darkColor = UIColor.white.withAlphaComponent(0).cgColor colors = [ darkColor, lightColor, darkColor ] anchorPoint = CGPoint(x: 0, y: 0.5) locations = [0.0, 0.5, 1.0] startPoint = CGPoint(x: 0.0, y: 0.5) endPoint = CGPoint(x: 1.0, y: 0.5) shadowColor = glowColor.cgColor shadowRadius = 5.0 shadowOpacity = 1 shadowOffset = .zero } private func startAnimation() { if animation(forKey: "shimmerEffect") == nil { let animationGroup = animationGroup() add(animationGroup, forKey: "shimmerEffect") } } }
এখানে, আমাদের দুটি অ্যানিমেশন প্রয়োজন (অস্বচ্ছতা এবং অবস্থান) একটি CAAnimationGroup
এ একত্রিত। এছাড়াও, CAAnimationGroup
ব্যবহার করে আমরা অ্যানিমেশন সেশনগুলির মধ্যে একটি বিরতির ব্যবস্থা করতে পারি। এই বৈশিষ্ট্যটি বেশ কার্যকর কারণ ক্লায়েন্টরা এই সম্পত্তিটিকেও নিয়ন্ত্রণ করতে চাইতে পারে।
আপনি ছবিতে দেখতে পাচ্ছেন, এটি আরও 3D প্রভাব যুক্ত করে, তবে এটি যথেষ্ট নয়। এগিয়ে চলুন.
আমাদের স্ট্যাটিক ব্যাকগ্রাউন্ড লেয়ারের সাথে কাজ করতে হবে, যা অ্যানিমেটেড প্রোগ্রেস লাইনের নিচে থাকে। আপনি যদি মনে করেন, আমাদের সেখানে দুটি অতিরিক্ত উপস্তর রয়েছে: অভ্যন্তরীণ ছায়া এবং বর্ডার গ্রেডিয়েন্ট।
|--General background [CALayer] |--Inner Shadow [CALayer] |--Border Gradient [CALayer]
আমি বলব যে এই দুটি স্তর বেশ কয়েক বছর আগে অনুসন্ধান প্রশ্নে বেশ জনপ্রিয় ছিল, সম্ভবত UI প্রবণতার কারণে। আসুন আমাদের নিজস্ব সংস্করণ তৈরি করি এবং ভবিষ্যত প্রজন্মের জন্য এই নিবন্ধে সেগুলিকে অমর করে রাখি :)
ক্লাসটি একটি অভ্যন্তরীণ ছায়া প্রভাব সহ একটি স্তর তৈরি করার জন্য ডিজাইন করা হয়েছে, যা UI উপাদানগুলিতে গভীরতা যোগ করার জন্য একটি দৃশ্যত আকর্ষণীয় উপায় হতে পারে।
final class InnerShadowLayer: CAShapeLayer { override init() { super.init() masksToBounds = true shadowRadius = 3 shadowColor = UIColor.black.cgColor shadowOffset = CGSize(width: 0.0, height: 1.0) shadowOpacity = 0.5 } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func layoutSublayers() { super.layoutSublayers() // Creates a path for the shadow that extends slightly outside the bounds of the layer. let shadowPath = UIBezierPath(roundedRect: bounds.insetBy(dx: -5, dy: -5), cornerRadius: cornerRadius) // Creates a cutout path that is the inverse of the layer's bounds. let cutoutPath = UIBezierPath(roundedRect: bounds, cornerRadius: cornerRadius).reversing() shadowPath.append(cutoutPath) self.shadowPath = shadowPath.cgPath } }
আমাদের একটি CAShapeLayer
দরকার কারণ আমাদের shadowPath
সেট করতে হবে। এর জন্য, আমাদের ছায়ার জন্য একটি পথ তৈরি করতে হবে যা স্তরের সীমানার বাইরে কিছুটা প্রসারিত হয় insetBy
from bounds ব্যবহার করে। এবং তারপরে আরেকটি UIBezierPath
— একটি কাটআউট পাথ যা স্তরের সীমানার বিপরীত।
দৃষ্টান্তে, এটা ইতিমধ্যে প্রায় আছে. কিন্তু আমাদের শেষ স্তর দরকার, যা আমাদের MacOS X Leopard-এ ফিরিয়ে আনবে।
এটি একটি স্তরের চারপাশে একটি গ্রেডিয়েন্ট বর্ডার তৈরি করার জন্য ডিজাইন করা CAGradientLayer
এর একটি সাবক্লাস। এটি সীমানা প্রভাব অর্জন করতে একটি মুখোশ হিসাবে একটি CAShapeLayer
ব্যবহার করে। shapeLayer
একটি স্ট্রোক কালার (প্রাথমিকভাবে কালো) এবং কোন ফিল কালার দিয়ে কনফিগার করা হয়েছে এবং BorderGradientLayer
মাস্ক হিসেবে ব্যবহার করা হয়। এই সেটআপটি গ্রেডিয়েন্টটিকে শুধুমাত্র দৃশ্যমান করার অনুমতি দেয় যেখানে shapeLayer
একটি স্ট্রোক রয়েছে, কার্যকরভাবে একটি গ্রেডিয়েন্ট সীমানা তৈরি করে।
final class BorderGradientLayer: CAGradientLayer { let shapeLayer: CAShapeLayer = { let layer = CAShapeLayer() layer.strokeColor = UIColor.black.cgColor // Temporary color layer.fillColor = nil return layer }() init(borderWidth: CGFloat, colors: [UIColor]) { super.init() self.mask = shapeLayer self.colors = colors.map { $0.cgColor } self.setBorderWidth(borderWidth) } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func layoutSublayers() { super.layoutSublayers() shapeLayer.path = UIBezierPath(roundedRect: bounds, cornerRadius: cornerRadius).cgPath } func setBorderWidth(_ borderWidth: CGFloat) { self.shapeLayer.lineWidth = borderWidth } }
BorderGradientLayer
এর সূচনাকারী গ্রেডিয়েন্টের জন্য একটি borderWidth
এবং UIColor
s এর অ্যারে গ্রহণ করে। এটি গ্রেডিয়েন্টের রং সেট আপ করে, সীমানা প্রস্থ shapeLayer
প্রয়োগ করে এবং সীমানা এলাকায় গ্রেডিয়েন্টকে সীমাবদ্ধ করতে মাস্ক হিসাবে shapeLayer
ব্যবহার করে।
layoutSublayers
পদ্ধতি নিশ্চিত করে যে shapeLayer
লেয়ারের সীমানা এবং কোণার ব্যাসার্ধের সাথে মেলে, নিশ্চিত করে যে সীমানাটি লেয়ারের সাথে সঠিকভাবে ফিট করে। setBorderWidth
পদ্ধতিটি শুরু করার পরে সীমানার প্রস্থের গতিশীল সমন্বয়ের অনুমতি দেয়।
ভাল উপায়. ঠিক আছে. চলুন ভিজ্যুয়াল স্টাফ তৈরি করা বন্ধ করি, এবং আসুন যুক্তি নিয়ে কাজ করি।
এখন, এই সমস্ত স্তরগুলিকে একত্রিত করার এবং সেগুলিকে জীবনে পরিণত করার সময় এসেছে৷
private lazy var backgroundLayer = BackgroundLayer( borderWidth: borderWidth, colors: borderColors ) private let glareLayer = ProgressGlareLayer() private let shimmeringLayer = ShimmeringLayer() private let progressMaskLayer = ProgressMaskLayer() private let progressBackgroundLayer = ProgressBackgroundLayer() public init() { super.init(frame: .zero) layer.backgroundColor = UIColor.white.cgColor layer.masksToBounds = true layer.addSublayer(progressMaskLayer) layer.addSublayer(progressBackgroundLayer) layer.insertSublayer(backgroundLayer, at: 0) progressBackgroundLayer.addSublayer(shimmeringLayer) progressBackgroundLayer.addSublayer(glareLayer) progressBackgroundLayer.mask = progressMaskLayer }
আসুন এক নজরে দেখে নেওয়া যাক কিভাবে আমরা সমস্ত স্তরগুলির সাথে মোকাবিলা করতে যাচ্ছি যেগুলি অ্যানিমেশনের সাথে তাদের প্রস্থ পরিবর্তন করা উচিত:
public func setValueWithAnimation(_ value: Double, duration: TimeInterval = 1.0, animationType: CAMediaTimingFunctionName = .easeInEaseOut, completion: (() -> Void)? = nil) { let newWidth = calculateProgressWidth(value) let animationGroup = DispatchGroup() animationGroup.enter() shimmeringLayer.animateToWidth( newWidth - WIDTH_ANIMATABLE_INSET, duration: duration, animationType: animationType, completion: animationGroup.leave ) animationGroup.enter() progressMaskLayer.animateToWidth( newWidth, duration: duration, animationType: animationType, completion: animationGroup.leave ) animationGroup.enter() glareLayer.animateToWidth( newWidth - WIDTH_ANIMATABLE_INSET, duration: duration, animationType: animationType, completion: animationGroup.leave ) animationGroup.notify(queue: .main) { completion?() } }
প্রথমত, আমাদের একটি মান থেকে একটি প্রস্থ প্রস্তুত করতে হবে। মান 0-এর কম এবং 1-এর বেশি হওয়া উচিত। এছাড়াও আমাদের একটি সম্ভাব্য সীমানা প্রস্থ বিবেচনা করতে হবে।
fileprivate func normalizeValue(_ value: Double) -> Double { max(min(value, 1), 0) } private func calculateProgressWidth(_ value: Double) -> CGFloat { let normalizedValue = normalizeValue(value) let width = bounds.width * normalizedValue - borderWidth return width }
সঠিক 3D প্রভাবের জন্য, একদৃষ্টি এবং ঝলকানো স্তরগুলি ডান এবং বাম দিক থেকে সামান্য প্যাডিং সহ হওয়া উচিত:
newWidth - WIDTH_ANIMATABLE_INSET
এবং অবশেষে, পদ্ধতিটি তিনটি স্তরের অ্যানিমেশনগুলিকে সিঙ্ক্রোনাইজ করতে একটি অ্যানিমেশন গ্রুপ ( DispatchGroup
) ব্যবহার করে: shimmeringLayer
, progressMaskLayer
, এবং glareLayer
। প্রতিটি স্তরের প্রস্থ গণনাকৃত প্রস্থের সাথে অ্যানিমেটেড করা হয় ( shimmeringLayer
জন্য WIDTH_ANIMATABLE_INSET
দ্বারা সামঞ্জস্য করা হয় এবং ডিজাইনে ফিট করার জন্য glareLayer
) নির্দিষ্ট সময়কাল ধরে এবং নির্দিষ্ট অ্যানিমেশন বক্ররেখার সাথে।
অ্যানিমেশনগুলিকে DispatchGroup
ব্যবহার করে সমন্বিত করা হয়, নিশ্চিত করে যে সমস্ত অ্যানিমেশন একই সাথে শুরু হয় এবং সমস্ত অ্যানিমেশন শেষ হওয়ার পরেই সমাপ্তি বন্ধ করা হয়৷ এই পদ্ধতিটি তার বিভিন্ন আলংকারিক স্তর জুড়ে একটি মসৃণ, সিঙ্ক্রোনাইজড অ্যানিমেশন সহ অগ্রগতি বারের মান আপডেট করার জন্য একটি দৃশ্যত আকর্ষণীয় উপায় সরবরাহ করে।
সর্বশেষ সংস্করণটি একটি সুইফট প্যাকেজ এবং পড হিসাবে বিদ্যমান। সুতরাং আপনি এখান থেকে এটি দখল করতে পারেন রেট্রো প্রগ্রেস বার রিপোজিটরি । Btw, অবদান স্বাগত জানাই!
RetroProgressBar এর একটি উদাহরণ তৈরি করুন। আপনি এটিকে আপনার ভিউ হায়ারার্কিতে যোগ করতে পারেন যেমন আপনি যেকোনো UIView এর সাথে করেন।
let progressBar = RetroProgressBar()
কাস্টমাইজেশন:
progressBar.progressColor = UIColor.systemBlue progressBar.cornerRadius = 5.0 progressBar.borderWidth = 2.0 progressBar.borderColors = [UIColor.white, UIColor.gray]
অ্যানিমেশন দিয়ে মান পরিবর্তন করতে:
progressBar.setValueWithAnimation(0.75, duration: 1.0, animationType: .easeInEaseOut) { print("Animation Completed") }
অ্যানিমেশন ছাড়া:
progressBar.setValue(0.5)
পুরানো-শৈলীর এই অগ্রগতি বারটি পুনরায় তৈরি করা নান্দনিকতার মধ্যে একটি নস্টালজিক ডুব ছিল যা আমি গভীরভাবে মিস করি। UIKit এবং CALayer
এর সাথে কাজ করা আমাকে ডেভেলপার হিসাবে আমাদের সরঞ্জামগুলির বহুমুখীতা এবং শক্তির কথা মনে করিয়ে দেয়। এই প্রকল্পটি কেবল প্রযুক্তিগত বাস্তবায়নের জন্য ছিল না; এটি একটি প্রিয় ডিজাইনের যুগে ফিরে যাওয়ার একটি যাত্রা ছিল, যা প্রমাণ করে যে আমরা এখনও পুরানো ডিজাইনের নিরবধি কবজ সহ যেকোনো কিছু তৈরি করতে পারি।
এই অভিজ্ঞতাটি একটি মর্মস্পর্শী অনুস্মারক যে UI ডিজাইনে , সরলতা এবং নস্টালজিয়া সুন্দরভাবে সহাবস্থান করতে পারে, অতীত এবং বর্তমানকে সেতু করে।
শুভ কোডিং!