View Lifecycle — उन विधियों का क्रम है जिन्हें Android स्क्रीन पर यूज़र इंटरफ़ेस तत्व (View) को ड्रा और रीड्रा करने के लिए कॉल करता है। Activity या Fragment के विपरीत, View एक हल्का-फुल्का घटक है जिसका विस्तारित जीवनचक्र नहीं होता, लेकिन यह एक सख्त तीन-चरणीय प्रक्रिया से गुज़रता है: onMeasure (माप), onLayout (स्थिति निर्धारण), onDraw (ड्राइंग)। कस्टम Views बनाने, प्रदर्शन को अनुकूलित करने और ड्राइंग समस्याओं को हल करने के लिए View Lifecycle को समझना आवश्यक है। Google के अनुसार, कस्टम Views सही कार्यान्वयन पर मानक नेस्टेड ViewGroups के संयोजन की तुलना में UI को 15–40% तक तेज़ बनाती हैं। कस्टम Views पर Android दस्तावेज़ीकरण onMeasure, onLayout और onDraw को View Lifecycle के तीन स्तंभों के रूप में वर्णित करता है।
मुख्य बातें
View Lifecycle वह प्रक्रिया है जिससे एक Android View (और ViewGroup) स्वयं को स्क्रीन पर प्रदर्शित करने के लिए गुज़रता है। Activity या Fragment के विपरीत, View में onStart/onStop/onDestroy नहीं होता — इसका “जीवन” माप, स्थिति निर्धारण और ड्राइंग की एक चक्रीय प्रक्रिया से बना होता है। यह चक्र हर बार ट्रिगर होता है जब View को प्रदर्शित या रीड्रा करने की आवश्यकता होती है।
View Lifecycle के तीन चरण:
पूर्ण View Lifecycle चक्र में View को विंडो से जोड़ने से संबंधित विधियाँ भी शामिल हैं: onAttachedToWindow (View विंडो से जुड़ी है, HW त्वरण है) और onDetachedFromWindow (View अलग हो गई है, संसाधन मुक्त हो गए हैं)। ये विधियाँ View के जीवनकाल में एक बार कॉल की जाती हैं और एनिमेशन/सेंसर को पंजीकृत/रद्द करने के लिए महत्वपूर्ण हैं।
Android Performance Blog के अनुसार, UI प्रदर्शन समस्याओं (jank, फ्रेम ड्रॉप) का 65% onMeasure और onDraw के गलत कार्यान्वयन से संबंधित है: अत्यधिक ओवरराइडिंग, अनावश्यक रूप से requestLayout() कॉल करना, onDraw में ऑब्जेक्ट बनाना।
onMeasure — View Lifecycle का सबसे महत्वपूर्ण और सबसे जटिल चरण. इस चरण में, Android यह निर्धारित करता है कि View स्क्रीन पर कितनी जगह घेरेगी। सिस्टम MeasureSpec पास करता है — int-पैक्ड निर्देश जो एक मोड और आकार से बने होते हैं।
तीन MeasureSpec मोड:
| मोड | स्थिरांक | अर्थ | उदाहरण |
|---|---|---|---|
| EXACTLY | MeasureSpec.EXACTLY | पैरेंट द्वारा निर्धारित सटीक आकार (match_parent या निश्चित चौड़ाई) | width=400dp → MeasureSpec(400, EXACTLY) |
| AT_MOST | MeasureSpec.AT_MOST | View निर्दिष्ट अधिकतम आकार तक हो सकती है (wrap_content) | width ≤ 400dp → MeasureSpec(400, AT_MOST) |
| UNSPECIFIED | MeasureSpec.UNSPECIFIED | कोई प्रतिबंध नहीं — View किसी भी आकार की हो सकती है (ScrollView, RecyclerView) | चौड़ाई असीमित → MeasureSpec(0, UNSPECIFIED) |
onMeasure कार्यान्वयन को:
setMeasuredDimension(int width, int height) कॉल करना चाहिए।getPaddingLeft() + getPaddingRight() घटाना चाहिए।measureChild() या measureChildWithMargins() के माध्यम से सभी चिल्ड्रन को मापना चाहिए।सामान्य गलती: wrap_content का उपयोग करते समय MeasureSpec को ध्यान में न रखना। यदि View को wrap_content पर सेट किया गया है, लेकिन onMeasure AT_MOST को हैंडल नहीं करता और एक निश्चित आकार लौटाता है, तो View या तो क्लिप हो जाएगी या आवश्यकता से अधिक जगह लेगी।
onLayout — वह चरण जिसमें View या ViewGroup अपने चिल्ड्रन को व्यवस्थित करता है अपनी सीमाओं के भीतर। सामान्य View (ViewGroup नहीं) के लिए, onLayout आवश्यक नहीं है — सिस्टम पैरेंट से पास किए गए पैरामीटर के साथ layout() कॉल करता है। ViewGroup के लिए, onLayout अनिवार्य है — इसके बिना चाइल्ड Views स्थापित नहीं होंगे।
onLayout हस्ताक्षर:
@Override
protected void onLayout(boolean changed,
int left, int top,
int right, int bottom) {
// चाइल्ड Views को व्यवस्थित करना
}
changed पैरामीटर इंगित करता है कि View की स्थिति या आकार पिछले layout की तुलना में बदला है या नहीं। यदि false है, तो View अनुकूलन के लिए चाइल्ड स्थितियों की पुनर्गणना छोड़ सकता है।
ViewGroup के लिए, onLayout को:
getChildCount() और getChildAt(i) के माध्यम से सभी चिल्ड्रन पर पुनरावृति करनी चाहिए।child.layout(l, t, r, b) कॉल करना चाहिए।onMeasure के बाद onLayout कॉल किया जाता है — मापे गए आयाम getMeasuredWidth()/getMeasuredHeight() के माध्यम से उपलब्ध होते हैं। यदि layout() के बाद किसी चाइल्ड View के वास्तविक आयाम भिन्न हैं, तो पुनर्मापन के लिए requestLayout() कॉल किया जाएगा। इसे “layout पास” कहा जाता है और यह पुनर्गणनाओं की श्रृंखला प्रतिक्रिया ट्रिगर कर सकता है।
onDraw — वह चरण जिसमें View स्वयं को Canvas पर ड्रा करता है. यह एकमात्र चरण है जिसे onMeasure और onLayout के बिना कई बार कॉल किया जा सकता है — यदि View को invalidate() के रूप में चिह्नित किया गया हो। Canvas ड्राइंग API प्रदान करता है: drawLine, drawRect, drawCircle, drawText, drawBitmap, और drawPath।
onDraw नियम:
canvas.clipRect() का उपयोग करें।ViewGroup में ड्राइंग क्रम: पृष्ठभूमि (setBackgroundDrawable) → onDraw (सामग्री) → dispatchDraw (चाइल्ड Views) → onDrawForeground (अग्रभूमि)। dispatchDraw प्रत्येक चाइल्ड का onDraw कॉल करता है। dispatchDraw को ओवरराइड करना चाइल्ड तत्वों के ऊपर प्रभाव लागू करने के लिए उपयोग किया जाता है।
Android Vitals आँकड़ों के अनुसार, onDraw में फ्रेम ड्रॉप के सबसे सामान्य कारण विधि के अंदर ऑब्जेक्ट बनाना (48%), decodeResource कॉल करना (22%), और कैशिंग के बिना जटिल Path संचालन (15%) हैं।
अमान्यकरण — वह तंत्र जो View रीड्राइंग को ट्रिगर करता है। invalidate() कॉल करना View को “गंदा” के रूप में चिह्नित करता है और अगले ड्राइंग चक्र में onDraw कॉल को शेड्यूल करता है। requestLayout() कॉल करना एक अधिक “भारी” ऑपरेशन है, जो पूर्ण चक्र ट्रिगर करता है: onMeasure → onLayout → onDraw।
| विधि | यह क्या करता है | कब उपयोग करें |
|---|---|---|
| invalidate() | onMeasure/onLayout के बिना onDraw ट्रिगर करता है | केवल दिखावट बदली (रंग, टेक्स्ट, प्रगति) |
| invalidate(Rect) | केवल निर्दिष्ट क्षेत्र को रीड्रा करता है | View का भाग बदला — एनिमेशन, चयन |
| postInvalidate() | गैर-UI थ्रेड से invalidate कॉल करता है | बैकग्राउंड थ्रेड ने ड्राइंग के लिए डेटा अपडेट किया |
| requestLayout() | onMeasure → onLayout → onDraw ट्रिगर करता है | सामग्री का आकार बदला (टेक्स्ट, इमेज) |
| forceLayout() | बलपूर्वक पुनर्मापन के लिए View चिह्नित करता है | आंतरिक स्थिति बदली, आकार बदल सकता है |
एनिमेशन और View Lifecycle: ViewPropertyAnimator और ValueAnimator प्रत्येक एनिमेशन फ्रेम पर invalidate() कॉल करते हैं। ObjectAnimator View पर setter कॉल करता है, जो यदि setter आकार (चौड़ाई/ऊँचाई) बदलता है, तो स्वचालित रूप से requestLayout() कॉल करता है। यह जटिल ViewGroups के लिए महँगा हो सकता है: प्रत्येक requestLayout रूट व्यू तक पूर्ण पदानुक्रम ट्रिगर करता है।
अनुकूलन नियम: हर जगह requestLayout() के बजाय invalidate() का उपयोग करें जहाँ केवल दिखावट बदलती है (रंग, पारदर्शिता, आकार बदले बिना रोटेशन)। requestLayout का उपयोग केवल आकार या सामग्री बदलने पर करें जो आकार को प्रभावित करता है।
कस्टम Views अद्वितीय UI बनाने के लिए एक शक्तिशाली उपकरण हैं, लेकिन उन्हें प्रदर्शन नियमों का सख्ती से पालन करना आवश्यक है। View Lifecycle अनुकूलन के लिए Google की मुख्य सिफारिशें यहाँ हैं।
setLayerType(LAYER_TYPE_HARDWARE) और पूरा होने पर setLayerType(LAYER_TYPE_NONE) का उपयोग करें।onMeasure, onDraw और invalidate के सही कार्यान्वयन के साथ एक सरल वृत्ताकार प्रगति संकेतक।
class CircularProgressView constructor(
context: Context, attrs: AttributeSet? = null
) : View(context, attrs) {
private val progressPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
color = Color.BLUE
style = Paint.Style.STROKE
strokeWidth = 8f
strokeCap = Paint.Cap.ROUND
}
private val backgroundPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
color = Color.LTGRAY
style = Paint.Style.STROKE
strokeWidth = 8f
}
private var progress = 0f
private var viewWidth = 0
private var viewHeight = 0
fun setProgress(value: Float) {
progress = value.coerceIn(0f, 100f)
invalidate()
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
val desiredSize = 100 * resources.displayMetrics.density.toInt()
val width = MeasureSpec.getSize(widthMeasureSpec)
val height = MeasureSpec.getSize(heightMeasureSpec)
val size = minOf(width, height).coerceAtLeast(desiredSize)
setMeasuredDimension(size, size)
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
val padding = progressPaint.strokeWidth / 2
val radius = (minOf(viewWidth, viewHeight) - padding) / 2
val cx = viewWidth / 2f
val cy = viewHeight / 2f
canvas.drawCircle(cx, cy, radius, backgroundPaint)
val sweepAngle = (progress / 100f) * 360f
canvas.drawArc(cx - radius, cy - radius, cx + radius, cy + radius,
-90f, sweepAngle, false, progressPaint)
}
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
super.onSizeChanged(w, h, oldw, oldh)
viewWidth = w
viewHeight = h
}
}
वृत्ताकार प्रगति बार: onMeasure MeasureSpec के आधार पर एक वर्गाकार आकार लौटाता है, onSizeChanged आयाम याद रखता है, onDraw पृष्ठभूमि और प्रगति चाप ड्रा करता है। प्रगति बदलने पर Invalidate कॉल किया जाता है — onMeasure/onLayout प्रभावित नहीं होते। Paint कंस्ट्रक्टर में एक बार बनाया जाता है, onDraw में नहीं।
एक कस्टम ViewGroup जो चाइल्ड Views को पंक्तियों में व्यवस्थित करती है (Flexbox wrap की तरह)।
class FlowLayout constructor(
context: Context, attrs: AttributeSet? = null
) : ViewGroup(context, attrs) {
private val horizontalSpacing = 8.dpToPx(resources)
private val verticalSpacing = 8.dpToPx(resources)
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
val width = MeasureSpec.getSize(widthMeasureSpec)
var totalHeight = paddingTop + paddingBottom
var rowWidth = paddingLeft
var rowHeight = 0
for (i in 0 until childCount) {
val child = getChildAt(i)
measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, totalHeight)
if (rowWidth + child.measuredWidth > width - paddingRight) {
totalHeight += rowHeight + verticalSpacing
rowWidth = paddingLeft
rowHeight = 0
}
rowWidth += child.measuredWidth + horizontalSpacing
rowHeight = maxOf(rowHeight, child.measuredHeight)
}
totalHeight += rowHeight
setMeasuredDimension(
MeasureSpec.getSize(widthMeasureSpec),
resolveSize(totalHeight, heightMeasureSpec)
)
}
override fun onLayout(changed: Boolean,
l: Int, t: Int, r: Int, b: Int) {
var rowTop = paddingTop
var rowLeft = paddingLeft
var rowHeight = 0
for (i in 0 until childCount) {
val child = getChildAt(i)
if (rowLeft + child.measuredWidth > r - paddingRight) {
rowTop += rowHeight + verticalSpacing
rowLeft = paddingLeft
rowHeight = 0
}
child.layout(rowLeft, rowTop, rowLeft + child.measuredWidth, rowTop + child.measuredHeight)
rowLeft += child.measuredWidth + horizontalSpacing
rowHeight = maxOf(rowHeight, child.measuredHeight)
}
}
override fun generateLayoutParams(attrs: AttributeSet?): LayoutParams {
return MarginLayoutParams(context, attrs)
}
}
FlowLayout onMeasure को ओवरराइड करता है: प्रत्येक चाइल्ड को मापता है, चौड़ाई पार होने पर नई पंक्ति में ले जाता है, कुल ऊँचाई की गणना करता है। onLayout चिल्ड्रन को पंक्ति विरामों को ध्यान में रखते हुए निर्देशांक द्वारा स्थापित करता है। generateLayoutParams चाइल्ड Views पर मार्जिन समर्थन के लिए MarginLayoutParams लौटाता है।
एक कस्टम View एक चिकना Bezier वक्र ड्रा करता है, Path की पूर्व-गणना और कैशिंग करता है।
class WaveView constructor(
context: Context, attrs: AttributeSet? = null
) : View(context, attrs) {
private val wavePaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
color = Color.parseColor("#4A90D9")
style = Paint.Style.FILL
}
private val wavePath = Path()
private var isPathDirty = true
private var viewWidth = 0
private var viewHeight = 0
fun refreshWave() {
isPathDirty = true
invalidate()
}
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
super.onSizeChanged(w, h, oldw, oldh)
viewWidth = w
viewHeight = h
isPathDirty = true
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
if (isPathDirty) {
wavePath.reset()
val amplitude = viewHeight * 0.1f
wavePath.moveTo(0f, viewHeight * 0.5f)
for (x in 0..viewWidth step 4) {
val y = viewHeight * 0.5f + amplitude * Math.sin(x * 2 * Math.PI / viewWidth).toFloat()
wavePath.lineTo(x.toFloat(), y)
}
wavePath.lineTo(viewWidth.toFloat(), viewHeight.toFloat())
wavePath.lineTo(0f, viewHeight.toFloat())
wavePath.close()
isPathDirty = false
}
canvas.drawPath(wavePath, wavePaint)
}
}
Path कैशिंग: isPathDirty = true केवल जब View आयाम बदलते हैं या refreshWave() कॉल किया जाता है। onDraw में, Path केवल तब पुनर्गणना होता है जब वह “गंदा” होता है। यह प्रत्येक एनिमेशन फ्रेम पर Bezier वक्र की पुनर्गणना को रोकता है, CPU बचाता है।
अक्सर पूछे जाने वाले प्रश्न
View Lifecycle एक चक्रीय ड्राइंग प्रक्रिया है (onMeasure → onLayout → onDraw) जो Activity निर्माण/विनाश से स्वतंत्र है। View में onStart/onStop नहीं है — यह या तो दृश्यमान है (विंडो से जुड़ा) या नहीं। Activity Lifecycle एप्लिकेशन घटक स्थिति का प्रबंधन करता है, View Lifecycle UI ड्राइंग का प्रबंधन करता है।
requestLayout() जड़ से पूरे View ट्री के लिए पूर्ण onMeasure → onLayout → onDraw चक्र ट्रिगर करता है। यदि requestLayout() को बार-बार कॉल किया जाता है (जैसे, प्रत्येक एनिमेशन फ्रेम), तो यह jank और ड्रॉप्ड फ्रेम का कारण बनता है। Google के अनुसार, 10 तत्वों वाली ViewGroup पर एक requestLayout औसतन 2–5 ms लेता है। एनिमेशन के लिए, invalidate() का उपयोग करें।
onAttachedToWindow तब कॉल किया जाता है जब View एक Window से जुड़ती है — दृश्य पदानुक्रम का हिस्सा बन जाती है। इस क्षण में, View HW त्वरण और Window संसाधनों (WindowManager, Display) तक पहुँच प्राप्त करती है। onAttachedToWindow एनिमेशन श्रोताओं और BroadcastReceivers को पंजीकृत करने का सही स्थान है जो View के दृश्यमान रहने तक जीवित रहते हैं।
ओवरड्रॉ एक स्थिति है जब एक पिक्सेल एक ही फ्रेम में कई बार ड्रा किया जाता है। प्रत्येक अतिरिक्त पास GPU समय बर्बाद करता है। कमी के तरीके: थीम में windowBackground सेट करें (layout में पृष्ठभूमि न ड्रा करें), canvas.clipRect() का उपयोग करें, नेस्टेड पृष्ठभूमि को मर्ज करने से बचें, नेस्टेड LinearLayout के बजाय ConstraintLayout का उपयोग करें। Android Studio → Profile GPU Rendering → Overdraw ओवरड्रॉ का रंग मानचित्र दिखाता है (नीला = 1x, लाल = 3x+).
हाँ, यदि View में पृष्ठभूमि है। super.onDraw() View की पृष्ठभूमि ड्रा करता है। यदि आपकी कस्टम View में पृष्ठभूमि नहीं है या आप अपनी स्वयं की पृष्ठभूमि ड्रा करते हैं, तो super.onDraw() छोड़ा जा सकता है — यह एक ड्राइंग पास बचाता है। ViewGroup के लिए, super.dispatchDraw() अनिवार्य है — यह चाइल्ड Views ड्रा करता है।
सारांश
हम एक मोबाइल एप्लिकेशन टर्नकी विकसित करेंगे
IT Sectr 2017 से स्टार्टअप और व्यवसायों के लिए iOS और Android एप्लिकेशन बनाता है। हम आपको सलाह देंगे और सर्वोत्तम समाधान प्रस्तावित करेंगे।
यह भी पढ़ें