পাঠ ১৮ · ৫৭-এর মধ্যে · মডিউল ৪
Home / Courses / Mobile App Development / Flutter উইজেট ট্রি

Flutter — উইজেট ট্রি ও রেন্ডারিং ইঞ্জিন

Flutter widget tree and the rendering engine
১০ মিনিট পড়া মধ্যবর্তী · Intermediate Python কোডসহ সম্পূর্ণ বাংলায়

এই পাঠে যা শিখবেন

  • "সবকিছুই একটি উইজেট" ধারণাটি — কেন প্যাডিং/লেআউটও উইজেট হিসেবে গণ্য হয়
  • Flutter কেন প্ল্যাটফর্মের নেটিভ উইজেট ব্যবহার না করে নিজস্ব রেন্ডারিং ইঞ্জিন দিয়ে UI আঁকে
  • একটি উইজেট ট্রিকে নেস্টেড dict কাঠামো হিসেবে বাস্তবায়ন করা
  • একটি সত্যিকারের render() ফাংশন যা ট্রি হেঁটে প্রকৃত ড্র-অপারেশনের তালিকা তৈরি করে

১ · সবকিছুই একটি উইজেট

FlutterFlutterGoogle-এর তৈরি একটি ক্রস-প্ল্যাটফর্ম UI টুলকিট, Dart ভাষায় লেখা হয় — একটি একক কোডবেস থেকে iOS, Android ও আরও কয়েকটি প্ল্যাটফর্মের জন্য নেটিভ মেশিন কোডে কম্পাইল হয়, এবং নিজস্ব রেন্ডারিং ইঞ্জিন দিয়ে UI আঁকে।-এর সবচেয়ে গুরুত্বপূর্ণ ডিজাইন সিদ্ধান্তগুলোর একটি হলো — সবকিছুই একটি উইজেট। একটি বাটন উইজেট, একটি টেক্সট উইজেট — এটা তো স্বাভাবিক মনে হয়, কিন্তু Flutter-এ প্যাডিং, অ্যালাইনমেন্ট, এমনকি একটি স্ক্রিনের পুরো লেআউট কাঠামোও উইজেট। প্রতিটি উইজেট তার শিশু উইজেট(গুলো)-কে র‍্যাপ করে অতিরিক্ত আচরণ (স্পেসিং, পজিশনিং, স্টাইলিং) যোগ করে — ফলে একটি স্ক্রিন আসলে নেস্টেড উইজেটের একটি গাছ-কাঠামো (tree)।

React Native (L17) প্রকৃত প্ল্যাটফর্ম নেটিভ উইজেট ব্যবহার করে, শুধু JS থেকে সেগুলো নিয়ন্ত্রণ করে। Flutter সম্পূর্ণ ভিন্ন পথে হাঁটে — এটি Dart কোড থেকে নেটিভ ARM মেশিন কোডে কম্পাইল হয়, কিন্তু স্ক্রিনে UI আঁকার জন্য প্ল্যাটফর্মের নেটিভ বাটন/টেক্সট উইজেট ব্যবহার করে না। বরং এটি নিজস্ব রেন্ডারিং ইঞ্জিন দিয়ে প্রতিটি পিক্সেল সরাসরি একটি ক্যানভাসে আঁকে — এই কারণেই একটি Flutter অ্যাপ সব প্ল্যাটফর্মে প্রায় হুবহু একই রকম দেখায়, কিন্তু কখনো কখনো প্ল্যাটফর্ম-নির্দিষ্ট সূক্ষ্ম আচরণ নিজে থেকে অনুকরণ করতে হয়।

Padding left:16 top:20 Column Container 240×60 Text "রাহুল আহমেদ" Row Text "ফলো" Text "মেসেজ"
Padding ও Column নিজে কিছু আঁকে না — তারা শুধু গঠন/স্পেসিং যোগ করে; শুধু Container ও Text পাতা-উইজেটগুলোই প্রকৃত ড্র-অপারেশন তৈরি করে।

২ · একটি সত্যিকারের উইজেট ট্রি ও render() ফাংশন

নিচের কোড সেলে উপরের ট্রি-টি একটি নেস্টেড Python dict কাঠামো হিসেবে বাস্তবায়ন করা হলো, এবং একটি প্রকৃত render(widget, x, y) ফাংশন লেখা হলো যা ট্রি-টি রিকার্সিভভাবে হেঁটে প্রতিটি পাতা-উইজেটের জন্য একটি সত্যিকারের ড্র-অপারেশন গণনা করে — Padding ও Column/Row-এর মতো কাঠামোগত উইজেট নিজে কিছু আঁকে না, শুধু তাদের সন্তানদের সঠিক (x, y) পজিশনে বসায়।

Python
# একটি Flutter-অনুপ্রাণিত উইজেট ট্রি -- সবকিছুই একটি উইজেট, এমনকি Padding/Column/Row-ও
# প্রতিটি উইজেট: {"type": ..., "props": {...}, "children": [...]}

tree = {
    "type": "Padding",
    "props": {"left": 16, "top": 20},
    "children": [
        {
            "type": "Column",
            "props": {},
            "children": [
                {"type": "Container", "props": {"width": 240, "height": 60, "color": "blue"}, "children": []},
                {"type": "Text", "props": {"content": "রাহুল আহমেদ", "width": 140}, "children": []},
                {
                    "type": "Row",
                    "props": {},
                    "children": [
                        {"type": "Text", "props": {"content": "ফলো", "width": 60}, "children": []},
                        {"type": "Text", "props": {"content": "মেসেজ", "width": 80}, "children": []},
                    ],
                },
            ],
        }
    ],
}

TEXT_HEIGHT = 24  # প্রতিটি Text/Row-এর ধরে নেওয়া লাইন-উচ্চতা (px) -- এই সরলীকৃত লেআউট মডেলে স্থির

def widget_height(widget):
    t = widget["type"]
    if t == "Container":
        return widget["props"]["height"]
    if t in ("Text", "Row"):
        return TEXT_HEIGHT
    return 0

def widget_width(widget):
    t = widget["type"]
    if t == "Container":
        return widget["props"]["width"]
    if t == "Text":
        return widget["props"]["width"]
    return 0

def render(widget, x=0, y=0):
    """ট্রি হেঁটে প্রকৃত ড্র-অপারেশনের তালিকা তৈরি করে -- Padding/Column/Row নিজে কিছু আঁকে না"""
    ops = []
    t = widget["type"]

    if t == "Padding":
        left = widget["props"].get("left", 0)
        top = widget["props"].get("top", 0)
        for child in widget["children"]:
            ops.extend(render(child, x + left, y + top))

    elif t == "Column":
        cur_y = y
        for child in widget["children"]:
            ops.extend(render(child, x, cur_y))
            cur_y += widget_height(child)

    elif t == "Row":
        cur_x = x
        for child in widget["children"]:
            ops.extend(render(child, cur_x, y))
            cur_x += widget_width(child)

    elif t == "Container":
        w = widget["props"]["width"]
        h = widget["props"]["height"]
        color = widget["props"].get("color", "black")
        ops.append(("draw_rect", x, y, w, h, color))

    elif t == "Text":
        content = widget["props"]["content"]
        ops.append(("draw_text", x, y, content))

    return ops

draw_ops = render(tree)

print(f"মোট ড্র-অপারেশন: {len(draw_ops)}\n")
for op in draw_ops:
    print(op)

def count_widgets(widget):
    return 1 + sum(count_widgets(c) for c in widget["children"])

total_widgets = count_widgets(tree)
print(f"\nমোট উইজেট (কাঠামোগত + দৃশ্যমান): {total_widgets}")
print(f"যার মধ্যে প্রকৃত ড্র-অপারেশন তৈরি করেছে: {len(draw_ops)} (বাকি {total_widgets - len(draw_ops)}টি শুধু কাঠামো/স্পেসিং)")

    
হাতে-হিসাব মিলিয়ে দেখুন: Padding (left:16, top:20) প্রথমে Column-কে (16, 20)-এ বসায়। Column-এর প্রথম সন্তান Container (16, 20)-তেই আঁকা হয় এবং এর উচ্চতা ৬০ হওয়ায় পরবর্তী কার্সার-y হয় ৮০। দ্বিতীয় সন্তান Text ("রাহুল আহমেদ") তাই (16, 80)-এ আঁকা হয়, এবং TEXT_HEIGHT=24 যোগ হয়ে কার্সার-y হয় ১০৪। তৃতীয় সন্তান Row তাই y=104-এ বসে, এবং তার ভেতরে প্রথম Text ("ফলো") (16, 104)-এ ও দ্বিতীয় Text ("মেসেজ") তার width=60 যোগ হয়ে (76, 104)-এ আঁকা হয়। মোট ৪টি ড্র-অপারেশন — যদিও ট্রি-তে মোট ৭টি উইজেট আছে (৩টি কাঠামোগত: Padding, Column, Row — কিছুই আঁকে না)।
মূল কথা · Key takeaway

Flutter-এ "সবকিছুই একটি উইজেট" মানে বাস্তবেই প্যাডিং ও লেআউটও গাছ-কাঠামোর অংশ — উপরের কোড সেলে Padding, Column, Row কোনো ড্র-অপারেশন তৈরি করেনি, শুধু তাদের সন্তানদের সঠিক পজিশনে বসিয়েছে; শুধু পাতা-উইজেট (Container, Text) প্রকৃত আঁকার নির্দেশনা তৈরি করেছে। বাস্তব Flutter-এ এই একই render()-এর মতো একটি ধাপ Skia-ভিত্তিক রেন্ডারিং ইঞ্জিনের মাধ্যমে সরাসরি স্ক্রিনের ক্যানভাসে চূড়ান্ত পিক্সেল আঁকে — প্ল্যাটফর্মের নেটিভ UI টুলকিট ছাড়াই।

ভাবনার প্রশ্ন

প্রতিটি প্রশ্ন নিজে কিছুক্ষণ ভাবুন — তারপর "→ উত্তর" চাপুন।

প্র ০১ প্যাডিংকে একটি আলাদা উইজেট হিসেবে ডিজাইন করার সুবিধা কী?

যেকোনো উইজেটকে Padding-এ র‍্যাপ করলেই সেটি স্পেসিং পায় — কোনো বিশেষ "padding প্রপার্টি" প্রতিটি উইজেট টাইপে আলাদাভাবে বাস্তবায়ন করতে হয় না। এটি Flutter-এর "সবকিছুই উইজেট" দর্শনের একটি বাস্তব সুবিধা — মাত্র কয়েকটি সরল বিল্ডিং ব্লক দিয়ে (র‍্যাপ করে) জটিল লেআউট তৈরি করা যায়, ঠিক যেমন উপরের কোড সেলে Padding ও Column/Row একসাথে ব্যবহার করা হয়েছে।

প্র ০২ Flutter কেন প্ল্যাটফর্মের নেটিভ উইজেট ব্যবহার না করে নিজস্ব রেন্ডারিং ইঞ্জিন বেছে নিয়েছে?

এতে Flutter সব প্ল্যাটফর্মে হুবহু একই রকম পিক্সেল-নিয়ন্ত্রণ পায় — একটি অ্যানিমেশন বা কাস্টম UI iOS ও Android-এ প্রায় অভিন্ন আচরণ করে, কারণ Flutter নিজে সবকিছু আঁকছে, প্ল্যাটফর্মের ভিন্ন ভিন্ন নেটিভ উইজেট বাস্তবায়নের উপর নির্ভর করছে না। বিনিময়ে, প্ল্যাটফর্মের একেবারে খাঁটি নেটিভ "ফিল" পুনরুৎপাদন করতে Flutter-কে সচেতনভাবে চেষ্টা করতে হয় (L19-এ এই ট্রেড-অফ আরও বিস্তারিত)।

প্র ০৩ কোড সেলে Column ও Row-এ widget_height/widget_width ব্যবহার করে কার্সার-পজিশন এগিয়ে নেওয়া কেন দরকার?

একটি লেআউট উইজেটের কাজ হলো তার সন্তানদের একে অপরের সাথে না মিলিয়ে সঠিক ক্রমে বসানো। Column প্রতিটি সন্তানের পরে y-কার্সার তার উচ্চতা দিয়ে এগিয়ে নেয় যাতে পরবর্তী সন্তান আগেরটির নিচে বসে, একইভাবে Row x-কার্সার প্রস্থ দিয়ে এগিয়ে নেয় যাতে সন্তানরা পাশাপাশি বসে, একে অপরের উপর ওভারল্যাপ না করে।

অনুশীলন

  1. চিন্তা করুন: আপনার ব্যবহৃত কোনো অ্যাপ যদি সব প্ল্যাটফর্মে (ফোন, ট্যাবলেট, এমনকি ওয়েব) প্রায় হুবহু একই রকম দেখতে হয়, সেটি কি Flutter-এ তৈরি হতে পারে বলে মনে হয়? কেন?

    হতে পারে — যেহেতু Flutter প্ল্যাটফর্মের নেটিভ উইজেটের বদলে নিজস্ব রেন্ডারিং ইঞ্জিন দিয়ে সবকিছু আঁকে, একটি Flutter অ্যাপ বিভিন্ন প্ল্যাটফর্মে সহজেই প্রায় পিক্সেল-পারফেক্ট অভিন্ন দেখাতে পারে — যেখানে React Native (প্রকৃত নেটিভ উইজেট ব্যবহার করে বলে) সাধারণত প্রতিটি প্ল্যাটফর্মের নিজস্ব লুক কিছুটা বহন করে।

  2. পরীক্ষা করুন: কোড সেলের tree-তে Column-এর children তালিকায় একটি নতুন {"type": "Text", "props": {"content": "প্রোফাইল", "width": 100}, "children": []} সবার শুরুতে যোগ করুন, তারপর নতুন প্রথম ড্র-অপারেশনটির (x, y) কী হবে তা আগে থেকে অনুমান করে যাচাই করুন।

    নতুন Text এখন Column-এর প্রথম সন্তান, তাই এটি সরাসরি Column-এর শুরুর পজিশনে আঁকা হবে — ("draw_text", 16, 20, "প্রোফাইল")। এরপর কার্সার-y TEXT_HEIGHT=24 দিয়ে এগিয়ে ৪৪ হবে, ফলে বাকি সব উইজেট (Container, পুরনো Text, Row) ২৪ পিক্সেল নিচে সরে যাবে — এটি দেখায় একটি একক উইজেট যোগ করলে পুরো সাবট্রি-র পজিশন কীভাবে পুনরায় গণনা হয়।

আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ

আগের পাঠ
React Native — ব্রিজ আর্কিটেকচার ও এর কার্যপ্রণালী