View Lifecycle — ان طریقوں کا سلسلہ ہے جو Android اسکرین پر یوزر انٹرفیس عنصر (View) کو ڈرا اور دوبارہ ڈرا کرنے کے لیے کال کرتا ہے۔ Activity یا Fragment کے برعکس، View ایک ہلکا پھلکا جز ہے جس کا توسیعی لائف سائیکل نہیں ہے، لیکن یہ ایک سخت تین مرحلے کے عمل سے گزرتا ہے: onMeasure (پیمائش)، onLayout (مقام بندی)، onDraw (ڈرائنگ)۔ View Lifecycle کو سمجھنا کسٹم Views بنانے، کارکردگی بہتر بنانے اور ڈرائنگ کے مسائل حل کرنے کے لیے ضروری ہے۔ 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 ایپلیکیشنز بناتا ہے۔ ہم آپ کو مشورہ دیں گے اور بہترین حل تجویز کریں گے۔
مزید پڑھیں