قطع كائنات اللوحات

يُعد هذا الدرس التطبيقي جزءًا من الدورة التدريبية المتقدّمة لنظام التشغيل Android في لغة Kotlin. ستحصل على أقصى استفادة من هذه الدورة التدريبية إذا كنت تعمل من خلال الدروس التطبيقية حول الترميز بالتسلسل، ولكن هذا ليس إلزاميًا. يتم إدراج جميع الدروس التطبيقية حول ترميز الدورات التدريبية في الصفحة المقصودة لبرنامج Android المتقدّم في لغة ترميز Kotlin.

مقدمة

لأغراض هذا الدرس التطبيقي، يُعدّ الاقتصاص طريقة لتحديد مناطق من الصورة أو اللوحة أو الصورة الرمزية التي يتم رسمها بشكل انتقائي أو غير مرسومة على الشاشة. والغرض من قطع الفيديو هو تقليل السحب الزائد. السحب الزائد هو عندما يتم رسم وحدة بكسل على الشاشة أكثر من مرة لعرض الصورة النهائية. عند تقليل الرسم الزائد، يمكنك تقليل عدد مرات رسم وحدة البكسل أو المنطقة، وذلك من أجل تحسين أداء الرسم إلى أقصى حد. ويمكنك أيضًا استخدام الاقتصاص لإنشاء تأثيرات مثيرة للاهتمام في تصميم واجهة المستخدم والصور المتحركة.

على سبيل المثال، عند رسم حزمة من البطاقات المتداخلة كما هو موضّح أدناه، بدلاً من رسم كل بطاقة من الأسفل إلى الأعلى، يكون من الأفضل عادةً رسم الأجزاء المرئية فقط. "Usuly" نظرًا لما تنطوي عليه عمليات الاقتصاص من تكلفة أيضًا وبشكل عام فإن نظام Android ينفذ الكثير من التحسينات في الرسم.

لرسم الأجزاء المرئية من البطاقات فقط، يجب تحديد منطقة اقتصاص لكل بطاقة. على سبيل المثال، في المخطط أدناه، عند تطبيق مستطيل الاقتصاص على صورة، لا يتم عرض سوى الجزء داخل هذا المستطيل.

عادةً ما تكون منطقة الاقتصاص مستطيلة، ولكنها يمكن أن تكون أي شكل أو مجموعة من الأشكال، حتى النص. يمكنك أيضًا تحديد ما إذا كنت تريد تضمين أو استبعاد المنطقة داخل منطقة الاقتصاص. على سبيل المثال، يمكنك إنشاء منطقة اقتصاص دائرية وعرض ما لا يظهر خارج الدائرة فقط.

في هذا الدرس التطبيقي حول الترميز، ستجرّب طرقًا مختلفة للاقتصاص.

ما يجب معرفته

ويجب أن تكون على دراية بما يلي:

  • كيفية إنشاء تطبيق باستخدام Activity وتشغيله باستخدام"استوديو Android"
  • طريقة إنشاء Canvas والرسم عليها
  • كيفية إنشاء View مخصّص وإلغاء onDraw() وonSizeChanged()

ما ستتعرَّف عليه

  • طريقة اقتصاص العناصر للرسم على Canvas
  • كيفية حفظ حالات الرسم واستعادتها في لوحة رسم
  • كيفية تطبيق التحويلات على لوحة رسم وعلى نص.

الإجراءات التي ستنفذّها

  • يمكنك إنشاء تطبيق يرسم أشكالاً مقتطعة على الشاشة تعرض طرقًا مختلفة للاقتصاص ونتيجة لظهور هذه الأشكال.
  • سترسم أيضًا بعض النصوص المترجمة والمائلة.

يوضح تطبيق ClippingExample كيفية استخدام الأشكال ودمجها لتحديد أجزاء اللوحة التي يتم عرضها في إحدى طرق العرض. سيبدو تطبيقك النهائي على هيئة لقطة شاشة أدناه.

ستُنشئ هذا التطبيق من البداية، لذا عليك إعداد مشروع وتحديد المكوّنات وسلاسل البيانات والتصريح عن بعض المتغيّرات.

الخطوة 1: إنشاء مشروع ClippingExample

  1. أنشئ مشروع Kotlin باسم ClippingExample باستخدام نموذج Empty Activity. استخدِم com.example.android لبادئة اسم الحزمة.
  2. فتح MainActivity.kt
  3. في طريقة onCreate()، استبدل طريقة عرض المحتوى التلقائية واضبط عرض المحتوى على مثيل جديد من ClippedView. هذه هي طريقة العرض المخصّصة لأمثلة المقطع التي سيتم إنشاؤها بعد ذلك.
setContentView(ClippedView(this))
  1. عند المستوى MainActivity.kt نفسه، أنشئ ملفًا وفئة لغة جديدة بلغة Kotlin لعرضًا مخصّصًا باسم ClippedView يمتد إلى View. امنحه التوقيع الوارد أدناه. وستكون جميع الأعمال الأخرى داخل ClippedView. توجّه التعليقات التوضيحية @JvmOverloads إلى برنامج التجميع في Kotlin لإنشاء أعباء تراكمية لهذه الدالة التي تستبدل قيم المعلمات التلقائية.
class ClippedView @JvmOverloads constructor(
   context: Context,
   attrs: AttributeSet? = null,
   defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {
}

الخطوة 2: إضافة الموارد وسلسلة البيانات

  1. حدّد الأبعاد التي ستستخدمها للملفات الشخصية التي تم اقتصاصها في ملف موارد جديد في res/values/dimens.xml. وتكون هذه الأبعاد الافتراضية برموز ثابتة وبحجم مناسب لتلائم شاشة صغيرة جدًا.
<?xml version="1.0" encoding="utf-8"?>
<resources>
   <dimen name="clipRectRight">90dp</dimen>
   <dimen name="clipRectBottom">90dp</dimen>
   <dimen name="clipRectTop">0dp</dimen>
   <dimen name="clipRectLeft">0dp</dimen>

   <dimen name="rectInset">8dp</dimen>
   <dimen name="smallRectOffset">40dp</dimen>

   <dimen name="circleRadius">30dp</dimen>
   <dimen name="textOffset">20dp</dimen>
   <dimen name="strokeWidth">4dp</dimen>

   <dimen name="textSize">18sp</dimen>
</resources>

لكي يظهر التطبيق بشكل جيد على شاشة أكبر (وللاطّلاع على التفاصيل بسهولة أكبر)، يمكنك إنشاء ملف dimens بقيم أكبر لا ينطبق إلا على الشاشات الأكبر حجمًا.

  1. في "استوديو Android"، انقر بزر الماوس الأيمن على المجلد values (القيم) واختَر New > Value Resource file (ملف موارد جديد).
  2. في مربع حوار ملف مورد جديد، يمكنك استدعاء الملف dimens. في مؤهِّلات متاحة، اختَر أصغر عرض شاشة وانقر على زر >> لإضافته إلى مؤهِّلات محدّد الإجابة. أدخِل 480 في مربع أصغر عرض شاشة وانقر على حسنًا.

  1. ويجب أن يظهر الملف في مجلد القيم كما هو موضّح أدناه.

  1. إذا لم تتمكن من رؤية الملف، بدّل إلى عرض ملفات المشروع للتطبيق. المسار الكامل للملف الجديد كما هو موضح أدناه: ClippingExample/app/src/main/res/values-sw480dp/dimens.xml.

  1. استبدِل المحتوى التلقائي لملف values-sw480dp/dimens.xml بالأبعاد التالية.
<?xml version="1.0" encoding="utf-8"?>
<resources>
   <dimen name="clipRectRight">120dp</dimen>
   <dimen name="clipRectBottom">120dp</dimen>

   <dimen name="rectInset">10dp</dimen>
   <dimen name="smallRectOffset">50dp</dimen>

   <dimen name="circleRadius">40dp</dimen>
   <dimen name="textOffset">25dp</dimen>
   <dimen name="strokeWidth">6dp</dimen>
</resources>
  1. في strings.xml، أضِف السلاسل التالية. سيتم استخدامها في عرض نص على اللوحة.
<string name="clipping">Clipping</string>
<string name="translated">translated text</string>
<string name="skewed">"Skewed and "</string>

الخطوة 3: إنشاء "كائن عرض رسم" و"كائن المسار"

  1. التبديل مرة أخرى إلى عرض Android لمشروعك.
  2. في ClippedView، حدِّد متغيّر Paint للرسم باستخدامه. يُرجى تفعيل منع التحويل، واستخدام عرض الخط وحجم النص المحدّدَين في الأبعاد، كما هو موضّح أدناه.
private val paint = Paint().apply {
   // Smooth out edges of what is drawn without affecting shape.
   isAntiAlias = true
   strokeWidth = resources.getDimension(R.dimen.strokeWidth)
   textSize = resources.getDimension(R.dimen.textSize)
}
  1. في ClippedView، يمكنك إنشاء Path وإعدادها محليًا لتخزين المسار المرسوم. استيراد android.graphics.Path.
private val path = Path()

الخطوة 4: إعداد الأشكال

في هذا التطبيق، يتم عرض صفوف وعمودين من الأشكال التي تم اقتصاصها بطرق مختلفة.

وَتَتَضَمَّنْ جَمِيعُ الْمُسْتَوَىَاتْ:

  • مستطيل كبير (مربّع) يعمل كحاوية
  • خط قطري عبر المستطيل الكبير
  • دائرة
  • سلسلة نصية قصيرة

في هذه الخطوة، يمكنك إعداد المكونات لهذه الأشكال من الموارد، بحيث لا تضطر إلى الحصول على الأبعاد إلا مرة واحدة عند استخدامها لاحقًا.

  1. في ClippedView، أسفل path، أضِف متغيّرات لمستطيل اقتصاص حول مجموعة الأشكال بالكامل.
private val clipRectRight = resources.getDimension(R.dimen.clipRectRight)
private val clipRectBottom = resources.getDimension(R.dimen.clipRectBottom)
private val clipRectTop = resources.getDimension(R.dimen.clipRectTop)
private val clipRectLeft = resources.getDimension(R.dimen.clipRectLeft)
  1. أضِف متغيّرات لإدخال المستطيل والإزاحة للمستطيل الصغير.
private val rectInset = resources.getDimension(R.dimen.rectInset)
private val smallRectOffset = resources.getDimension(R.dimen.smallRectOffset)
  1. أضِف متغيّرًا لنطاق جغرافي دائرة. هذا هو نصف قطر الدائرة المرسومة داخل المستطيل.
private val circleRadius = resources.getDimension(R.dimen.circleRadius)
  1. أضِف قيمة الإزاحة وحجم النص للنص المرسوم داخل المستطيل.
private val textOffset = resources.getDimension(R.dimen.textOffset)
private val textSize = resources.getDimension(R.dimen.textSize)

الخطوة 4: إعداد مواقع الصفوف والأعمدة

يتم عرض أشكال هذا التطبيق في عمودين وأربعة صفوف، ويتم تحديدهما حسب قيم السمات التي تم إعدادها أعلاه. ولا تُعدّ المسألة الحسابية هذه جزءًا من هذا الدرس التطبيقي، إلّا أنّك تنظر إليها عند نسخ الترميز إلى الرمز الوارد في هذه الخطوة.

  1. اضبط الإحداثيات لعمودين.
private val columnOne = rectInset
private val columnTwo = columnOne + rectInset + clipRectRight
  1. أضِف الإحداثيات لكل صف، بما في ذلك الصف الأخير للنص الذي تم تحويله.
private val rowOne = rectInset
private val rowTwo = rowOne + rectInset + clipRectBottom
private val rowThree = rowTwo + rectInset + clipRectBottom
private val rowFour = rowThree + rectInset + clipRectBottom
private val textRow = rowFour + (1.5f * clipRectBottom)
  1. شغِّل تطبيقك. سيتم فتح التطبيق باستخدام شاشة بيضاء فارغة أسفل اسم التطبيق.

في onDraw()، يمكنك اتّباع طرق رسم سبع مستطيلات مختلفة مقتطعة كما هو موضّح في لقطة شاشة التطبيق أدناه. يتم رسم المستطيلات بالطريقة نفسها، والفرق الوحيد هو مناطق الاقتصاص المحددة والموقع على الشاشة.

تعمل الخوارزمية المستخدمة لرسم المستطيلات كما هو موضّح في المخطط وتفسيرها أدناه. باختصار، يمكنك رسم سلسلة من المستطيلات من خلال نقل أصل Canvas. من الناحية النظرية، يتألف هذا الإجراء من الخطوات التالية:

(1) أولاً، عليك ترجمة Canvas إلى المكان الذي تريد رسم المستطيل فيه. وهذا يعني أنه بدلاً من حساب المكان الذي يجب فيه رسم المستطيل التالي وجميع الأشكال الأخرى، يمكنك نقل أصل Canvas، أي نظام الإحداثيات.

(2) بعد ذلك، يمكنك رسم المستطيل في الأصل الجديد للوحة. وهذا يعني أنك ترسم الأشكال في الموقع نفسه في نظام الإحداثيات المترجَم. وتعدّ هذه الطريقة أسهل كثيرًا وأكثر كفاءة قليلاً.

(3) وأخيرًا، يمكنك استعادة Canvas إلى Origin الأصلي.

إليك الخوارزمية أثناء تنفيذها:

  1. في الدالة onDraw()، يمكنك استدعاء دالة لملء Canvas باللون الرمادي الرمادي ورسم الأشكال الأصلية.
  2. يجب استدعاء دالة لكل مستطيل مقتطع والنص الذي يجب رسمه.

لكل مستطيل أو نص:

  1. احفظ الحالة الحالية لـ Canvas حتى تتمكّن من إعادة الضبط إلى الحالة الأولية هذه.
  2. عليك ترجمة Origin للوحة الرسم إلى الموقع الذي تريد الرسم فيه.
  3. تطبيق أشكال الاقتصاص ومساراتها.
  4. ارسم المستطيل أو النص.
  5. يمكنك استعادة حالة Canvas.

الخطوة: تجاوز onDraw()

  1. يمكنك إلغاء onDraw() كما هو موضّح في الرمز أدناه. عليك استدعاء دالة لكل شكل ترسم، وستنفذه لاحقًا.
 override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)
        drawBackAndUnclippedRectangle(canvas)
        drawDifferenceClippingExample(canvas)
        drawCircularClippingExample(canvas)
        drawIntersectionClippingExample(canvas)
        drawCombinedClippingExample(canvas)
        drawRoundedRectangleClippingExample(canvas)
        drawOutsideClippingExample(canvas)
        drawSkewedTextExample(canvas)
        drawTranslatedTextExample(canvas)
        // drawQuickRejectExample(canvas)
    }
  1. يمكنك إنشاء رموز بديلة لكل من وظائف الرسم حتى يستمر تجميع الرمز. يمكنك نسخ الرمز أدناه.
private fun drawBackAndUnclippedRectangle(canvas: Canvas){
}
private fun drawDifferenceClippingExample(canvas: Canvas){
}
private fun drawCircularClippingExample(canvas: Canvas){
}
private fun drawIntersectionClippingExample(canvas: Canvas){
}
private fun drawCombinedClippingExample(canvas: Canvas){
}
private fun drawRoundedRectangleClippingExample(canvas: Canvas){
}
private fun drawOutsideClippingExample(canvas: Canvas){
}
private fun drawTranslatedTextExample(canvas: Canvas){
}
private fun drawSkewedTextExample(canvas: Canvas){
}
private fun drawQuickRejectExample(canvas: Canvas){
}

يرسم التطبيق المستطيل نفسه والأشكال نفسها سبع مرات، أولاً بدون اقتصاص، ثم ست مرات مع تطبيق مسارات اقتصاص مختلفة. تأخذ طريقة drawClippedRectangle() في الاعتبار رمز رسم مستطيل واحد، كما هو موضّح أدناه.

الخطوة 1: إنشاء طريقةDrawClippedRectular()

  1. أنشئ طريقة drawClippedRectangle() تستخدم وسيطة canvas من النوع Canvas.
private fun drawClippedRectangle(canvas: Canvas) {
}
  1. داخل طريقة drawClippedRectangle()، اضبط حدود مستطيل الاقتصاص للشكل بأكمله. طبِّق مستطيلًا للاقتصاص يقتصر على رسم المربّع فقط.
canvas.clipRect(
       clipRectLeft,clipRectTop,
       clipRectRight,clipRectBottom
)

تقلّل الطريقة Canvas.clipRect(...) من مساحة الشاشة التي يمكن لها عمليات السحب في المستقبل. تضبط هذه الميزة حدود الاقتصاص لكي تكون تقاطعًا مكانيًا لمستطيل الاقتصاص الحالي والمستطيل الذي تم تمريره إلى clipRect(). هناك طرق متعددة للطريقة clipRect() تقبل أشكال مختلفة للمناطق وتسمح بعمليات مختلفة على مستطيل القطع.

  1. املأ canvas باللون الأبيض. نعم. نظرًا لأنك لا ترسم المستطيلات، فأنت تقصّر! بسبب مستطيل القطع، تمت تعبئة المنطقة المحدّدة في مستطيل القطع فقط، مما يؤدي إلى إنشاء مستطيل أبيض. تظل بقية السطح رمادية اللون.
canvas.drawColor(Color.WHITE)
  1. غيِّر اللون إلى اللون الأحمر وارسم خطًا قطريًا داخل مستطيل القطع.
paint.color = Color.RED
canvas.drawLine(
   clipRectLeft,clipRectTop,
   clipRectRight,clipRectBottom,paint
)
  1. اضبط اللون على اللون الأخضر وارسم دائرة داخل مستطيل القطع.
paint.color = Color.GREEN
canvas.drawCircle(
   circleRadius,clipRectBottom - circleRadius,
   circleRadius,paint
)
  1. يمكنك ضبط اللون على اللون الأزرق ورسم نص محاذي مع الحافة اليسرى لمستطيل الاقتصاص. استخدِم canvas.drawText() لرسم نص.
paint.color = Color.BLUE
// Align the RIGHT side of the text with the origin.
paint.textSize = textSize
paint.textAlign = Paint.Align.RIGHT
canvas.drawText(
   context.getString(R.string.clipping),
   clipRectRight,textOffset,paint
)

الخطوة 2: تنفيذ طريقةDrawbackAndUnclippedRectular()

  1. للاطلاع على طريقة drawClippedRectangle() عمليًا، ارسم المستطيل الأول بدون اقتطاع عن طريق تنفيذ طريقة drawBackAndUnclippedRectangle() كما هو موضّح أدناه. يمكنك حفظ canvas، والترجمة إلى الصف الأول وموضع العمود، والرسم باستدعاء drawClippedRectangle()، ثم استعادة canvas إلى حالته السابقة.
private fun drawBackAndUnclippedRectangle(canvas: Canvas){
   canvas.drawColor(Color.GRAY)
   canvas.save()
   canvas.translate(columnOne,rowOne)
   drawClippedRectangle(canvas)
   canvas.restore()
}
  1. شغِّل تطبيقك. من المفترض أن ترى المستطيل الأبيض الأول مع دائرته وخطه الأحمر ونصه على خلفية رمادية.

في الطرق التالية لاقتصاص المقاطع، يمكنك تطبيق مجموعات مختلفة من مناطق الاقتصاص لتحقيق تأثيرات رسومية والتعرّف على كيفية الدمج بين مناطق الاقتصاص لإنشاء أي شكل تحتاج إليه.

وتتّبع كل طريقة من هذه الطرق النمط نفسه.

  1. حفظ الحالة الحالية للوحة: canvas.save()

يحتفظ سياق النشاط بحزمة من حالات الرسم. تتألف حالات الرسم من مصفوفة التحويل الحالية ومنطقة الاقتصاص الحالية. يمكنك حفظ الحالة الحالية، وتنفيذ الإجراءات التي تغيّر حالة الرسم (مثل ترجمة اللوحة أو تدويرها)، ثم استعادة حالة الرسم المحفوظة. (ملاحظة: هذا يشبه الأمر "stash"; في git!).

عندما يتضمن الرسم إحالات ناجحة، تكون سلاسل الإحالات الناجحة والتراجع عنها عكسية. على سبيل المثال، إذا ترجمت المحتوى وتمدده ثم تدويره، سيصبح الأمر معقّدًا بسرعة. بدلاً من ذلك، احفظ حالة اللوحة، وطبِّق الإحالات الناجحة، وارسم، ثم استعِد الحالة السابقة.

على سبيل المثال، يمكنك تحديد منطقة اقتصاص وحفظ تلك الحالة. بعد ذلك، يمكنك ترجمة اللوحة وإضافة منطقة اقتصاص وتدوير. بعد إجراء بعض الرسم، يمكنك استعادة حالة الاقتصاص الأصلية، ويمكنك أيضًا المتابعة لإجراء ترجمة مختلفة وانحراف، كما هو موضح في المخطط.

  1. ترجمة مصدر اللوحة إلى إحداثيات الصف/العمود: canvas.translate()

من الأسهل نقل مصدر اللوحة ورسم العنصر نفسه في نظام إحداثيات جديد بدلاً من نقل جميع العناصر للرسم. (ملاحظة: يمكنك استخدام الأسلوب نفسه للعناصر بالتناوب.)

  1. طبِّق الإحالات الناجحة على path، إن توفّرت.
  2. تطبيق الاقتصاص: canvas.clipPath(path)
  3. رسم الأشكال: drawClippedRectangle() or drawText()
  4. استعادة حالة اللوحة السابقة: canvas.restore()

الخطوة 1: تنفيذDrawDifferenceClippingExample(canvas)

أضِف رمزًا لرسم المستطيل الثاني، الذي يستخدم الفرق بين مستطيلين لاقتصاص المحتوى لإنشاء تأثير إطار صورة.

استخدِم الرمز التالي:

  1. احفظ اللوحة.
  2. ترجم أصل اللوحة إلى مساحة مفتوحة من الصف الأول إلى العمود الثاني على يسار المستطيل الأول.
  3. طبِّق مستطيلين للاقتصاص. يطرح عامل التشغيل DIFFERENCE المستطيل الثاني من المستطيل الأول.
  1. استدعِ الطريقة drawClippedRectangle() لرسم اللوحة المعدَّلة.
  2. استعادة حالة اللوحة.
private fun drawDifferenceClippingExample(canvas: Canvas) {
   canvas.save()
   // Move the origin to the right for the next rectangle.
   canvas.translate(columnTwo,rowOne)
   // Use the subtraction of two clipping rectangles to create a frame.
   canvas.clipRect(
       2 * rectInset,2 * rectInset,
       clipRectRight - 2 * rectInset,
       clipRectBottom - 2 * rectInset
   )
   // The method clipRect(float, float, float, float, Region.Op
   // .DIFFERENCE) was deprecated in API level 26. The recommended
   // alternative method is clipOutRect(float, float, float, float),
   // which is currently available in API level 26 and higher.
   if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O){
       canvas.clipRect(
           4 * rectInset,4 * rectInset,
           clipRectRight - 4 * rectInset,
           clipRectBottom - 4 * rectInset,
            Region.Op.DIFFERENCE
       )
   } else {
       canvas.clipOutRect(
           4 * rectInset,4 * rectInset,
           clipRectRight - 4 * rectInset,
           clipRectBottom - 4 * rectInset
       )
   }
   drawClippedRectangle(canvas)
   canvas.restore()
}
  1. شغِّل تطبيقك ومن المفترض أن يظهر على النحو التالي.

الخطوة 2: تنفيذDrawCircularClippingExample(canvas)

بعد ذلك، أضِف رمزًا لرسم مستطيل يستخدم منطقة اقتصاص دائرية تم إنشاؤها من مسار دائري، مع إزالة الدائرة (وليس رسمها) بشكل أساسي، وبالتالي عرض الخلفية الرمادية بدلاً من ذلك.

private fun drawCircularClippingExample(canvas: Canvas) {

   canvas.save()
   canvas.translate(columnOne, rowTwo)
   // Clears any lines and curves from the path but unlike reset(),
   // keeps the internal data structure for faster reuse.
   path.rewind()
   path.addCircle(
       circleRadius,clipRectBottom - circleRadius,
       circleRadius,Path.Direction.CCW
   )
   // The method clipPath(path, Region.Op.DIFFERENCE) was deprecated in
   // API level 26. The recommended alternative method is
   // clipOutPath(Path), which is currently available in
   // API level 26 and higher.
   if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
       canvas.clipPath(path, Region.Op.DIFFERENCE)
   } else {
       canvas.clipOutPath(path)
   }
   drawClippedRectangle(canvas)
   canvas.restore()
}

الخطوة 3: تنفيذDrawIntersectionClippingExample(canvas)

بعد ذلك، أضِف رمزًا لرسم تقاطع مستطيلين للاقتصاص في الصف والعمود الثاني.

يُرجى العِلم بأنّ جودة هذه المنطقة ستختلف بناءً على درجة دقة الشاشة. جرّب السمة smallRectOffset لتغيير حجم المنطقة المرئية. تؤدي smallRectOffset الصغيرة إلى منطقة أكبر على الشاشة.

private fun drawIntersectionClippingExample(canvas: Canvas) {
   canvas.save()
   canvas.translate(columnTwo,rowTwo)
   canvas.clipRect(
       clipRectLeft,clipRectTop,
       clipRectRight - smallRectOffset,
       clipRectBottom - smallRectOffset
   )
   // The method clipRect(float, float, float, float, Region.Op
   // .INTERSECT) was deprecated in API level 26. The recommended
   // alternative method is clipRect(float, float, float, float), which
   // is currently available in API level 26 and higher.
   if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
       canvas.clipRect(
           clipRectLeft + smallRectOffset,
           clipRectTop + smallRectOffset,
           clipRectRight,clipRectBottom,
           Region.Op.INTERSECT
       )
   } else {
       canvas.clipRect(
           clipRectLeft + smallRectOffset,
           clipRectTop + smallRectOffset,
           clipRectRight,clipRectBottom
       )
   }
   drawClippedRectangle(canvas)
   canvas.restore()
}

الخطوة 4: تنفيذDrawMergedClipingExample(canvas)

بعد ذلك، ادمج الأشكال ودائرة ومستطيل وارسم أي مسار لتحديد منطقة الاقتصاص.

private fun drawCombinedClippingExample(canvas: Canvas) {
   canvas.save()
   canvas.translate(columnOne, rowThree)
   path.rewind()
   path.addCircle(
       clipRectLeft + rectInset + circleRadius,
       clipRectTop + circleRadius + rectInset,
       circleRadius,Path.Direction.CCW
   )
   path.addRect(
       clipRectRight / 2 - circleRadius,
       clipRectTop + circleRadius + rectInset,
       clipRectRight / 2 + circleRadius,
       clipRectBottom - rectInset,Path.Direction.CCW
   )
   canvas.clipPath(path)
   drawClippedRectangle(canvas)
   canvas.restore()
}

الخطوة 5: تنفيذDrawroundedRectularClippingExample(canvas)

أضِف مستطيلاً مستديرًا يمثّل شكل قصاً شائع الاستخدام.

  1. في المستوى الأعلى، عليك إنشاء متغيّر مستطيل وإعداده. RectF هي فئة تحتوي على إحداثيات مستطيلة في النقطة العائمة.
private var rectF = RectF(
   rectInset,
   rectInset,
   clipRectRight - rectInset,
   clipRectBottom - rectInset
)
  1. نفِّذ الدالة drawRoundedRectangleClippingExample(). تأخذ دالة addRoundRect() مستطيلًا وقيمًا لقيم x وy حول نصف قطر الزاوية، واتجاه اتجاه المستطيل الدائري. تُحدِّد Path.Direction كيفية توجيه الأشكال المغلقة (مثل المستطيلات والبيضاوي) عند إضافتها إلى مسار. يشير CCW إلى عكس عقارب الساعة.
private fun drawRoundedRectangleClippingExample(canvas: Canvas) {
   canvas.save()
   canvas.translate(columnTwo,rowThree)
   path.rewind()
   path.addRoundRect(
       rectF,clipRectRight / 4,
       clipRectRight / 4, Path.Direction.CCW
   )
   canvas.clipPath(path)
   drawClippedRectangle(canvas)
   canvas.restore()
}

الخطوة 6: تنفيذDrawExternalClippingExample(canvas)

اقتطع الجزء الخارجي حول المستطيل من خلال مضاعفةدخلات مستطيل القطع.

private fun drawOutsideClippingExample(canvas: Canvas) {
   canvas.save()
   canvas.translate(columnOne,rowFour)
   canvas.clipRect(2 * rectInset,2 * rectInset,
       clipRectRight - 2 * rectInset,
       clipRectBottom - 2 * rectInset)
   drawClippedRectangle(canvas)
   canvas.restore()
}

الخطوة 7: تنفيذDrawlocalizedTextExample(canvas)

ولا يختلف رسم النص عن أي أشكال أخرى، ويمكنك تطبيق التحويلات على النصوص. على سبيل المثال، يمكنك ترجمة النص من خلال ترجمة اللوحة ورسم النص.

  1. نفِّذ الدالة أدناه.
private fun drawTranslatedTextExample(canvas: Canvas) {
   canvas.save()
   paint.color = Color.GREEN
   // Align the RIGHT side of the text with the origin.
   paint.textAlign = Paint.Align.LEFT
   // Apply transformation to canvas.
   canvas.translate(columnTwo,textRow)
   // Draw text.
   canvas.drawText(context.getString(R.string.translated),
       clipRectLeft,clipRectTop,paint)
   canvas.restore()
}
  1. شغِّل تطبيقك لرؤية النص المترجم.

الخطوة 8: تنفيذDrawSkewedTextExample(canvas)

يمكنك أيضًا انحراف النص. وهذا يعني تشويهها بطرق مختلفة.

  1. يمكنك إنشاء الدالة أدناه في ClippedView.
private fun drawSkewedTextExample(canvas: Canvas) {
   canvas.save()
   paint.color = Color.YELLOW
   paint.textAlign = Paint.Align.RIGHT
   // Position text.
   canvas.translate(columnTwo, textRow)
   // Apply skew transformation.
   canvas.skew(0.2f, 0.3f)
   canvas.drawText(context.getString(R.string.skewed),
       clipRectLeft, clipRectTop, paint)
   canvas.restore()
}
  1. شغِّل تطبيقك لرؤية النص المائل المرسوم قبل النص المترجم.

تتيح لك طريقة quickReject() Canvas التحقق مما إذا كان مستطيل أو مسار محدد يقع خارج المناطق المرئية حاليًا بعد تطبيق جميع التحويلات.

تُعد الطريقة quickReject() مفيدة للغاية عند إنشاء رسومات أكثر تعقيدًا وتحتاج إلى إجراء ذلك في أسرع وقت ممكن. باستخدام quickReject()، يمكنك تحديد العناصر التي ليس عليك رسمها على الإطلاق، بدون الحاجة إلى كتابة منطق التقاطع الخاص بك.

  • تعرض طريقة quickReject() true إذا لم يظهر المستطيل أو المسار على الشاشة على الإطلاق. بالنسبة إلى التداخلات الجزئية، لا يزال عليك إجراء عمليات التدقيق الخاصة بك.
  • EdgeType إما AA (معادِل للبريد الإلكتروني: يمكنك معالجة الحواف بالتقريب، لأنه قد يكون مضادًا للأسماء المستعارة) أو BW (أبيض أسود: معالجة الحواف بتقريبها إلى أقرب حد بكسل) لتقريبها إلى أقرب وحدة بكسل.

هناك إصدارات متعددة من quickReject()، ويمكنك أيضًا العثور عليها في المستندات.

boolean

QuickRejected(float left, float top, float right, float bottom, Canvas.EdgeType type)

boolean

quickRejected(RectF rect, Canvas.EdgeType type)

boolean

Quickرفض(المسار path, Canvas.EdgeType type)

في هذا التمرين، ستسحب صفًا جديدًا أسفل النص وداخل clipRect كما حدث في السابق.

  • عليك أولاً الاتصال بـ quickReject() باستخدام مستطيل inClipRectangle، ويتداخل مع clipRect. تعرض quickReject() القيمة "خطأ"، ويتم ملء clipRect بـ BLACK، ويتم رسم المستطيل inClipRectangle.

  • وبعد ذلك، عليك تغيير الرمز والاتصال بـ quickReject() باستخدام notInClipRectangle. تعرض quickReject() الآن القيمة true، ويتم ملء clipRect بـ WHITE، ولا يتم رسم notInClipRectangle.

عندما يكون لديك رسومات معقّدة، يمكن أن يوضّح لك ذلك بسرعة الأشكال الخارجة عن منطقة الاقتصاص بالكامل، والتي قد تحتاج إلى إجراء عمليات حسابية إضافية ورسم لأنّها تقع بشكل جزئي أو كامل في منطقة الاقتصاص.

الخطوة: جرّب Quickquick()

  1. في المستوى الأعلى، أنشِئ متغيّرًا للإحداثيات y لصف إضافي.
   private val rejectRow = rowFour + rectInset + 2*clipRectBottom
  1. أضف الدالة drawQuickRejectExample() التالية إلى ClippedView. اقرأ الرمز، لأنه يحتوي على كل ما تحتاج إلى معرفته لاستخدام quickReject().
private fun drawQuickRejectExample(canvas: Canvas) {
   val inClipRectangle = RectF(clipRectRight / 2,
       clipRectBottom / 2,
       clipRectRight * 2,
       clipRectBottom * 2)

   val notInClipRectangle = RectF(RectF(clipRectRight+1,
       clipRectBottom+1,
       clipRectRight * 2,
       clipRectBottom * 2))

   canvas.save()
   canvas.translate(columnOne, rejectRow)
   canvas.clipRect(
       clipRectLeft,clipRectTop,
       clipRectRight,clipRectBottom
   )
   if (canvas.quickReject(
           inClipRectangle, Canvas.EdgeType.AA)) {
       canvas.drawColor(Color.WHITE)
   }
   else {
       canvas.drawColor(Color.BLACK)
       canvas.drawRect(inClipRectangle, paint
       )
   }
       canvas.restore()
}
  1. في onDraw()، ألغِ استدعاء drawQuickRejectExample().
  2. شغِّل تطبيقك، وسترى مستطيلاً أسود، وهو منطقة الاقتصاص التي تم ملؤها وأجزاء من inClipRectangle، لأن المستطيلين يتداخلان، وبالتالي يتم عرض quickReject() لعرض false وinClipRectangle.

  1. في drawQuickRejectExample()، غيِّر الرمز لتشغيل quickReject() مقابل notInClipRectangle. تعرض quickReject() الآن true ويتم ملء منطقة الاقتصاص باللون الأبيض.

نزِّل الرمز للدرس التطبيقي النهائي للترميز.

$  git clone https://github.com/googlecodelabs/android-kotlin-drawing-clipping


بدلاً من ذلك، يمكنك تنزيل المستودع كملف Zip وفك ضغطه وفتحه في "استوديو Android".

تنزيل ملف Zip

  • تحافظ Context من النشاط على حالة تحافظ على الانتقالات ومناطق الاقتصاص في Canvas.
  • يمكنك استخدام canvas.save() وcanvas.restore() للرسم والعودة إلى الحالة الأصلية للوحة.
  • لرسم أشكال متعددة على لوحة رسم، يمكنك إما حساب موقعها أو نقل (ترجمة) أصل لوحة الرسم. قد يُسهِّل هذا الخيار إنشاء طرق مساعدة لتسلسلات السحب المتكررة.
  • يمكن أن تكون مناطق الاقتصاص أي شكل أو مجموعة من الأشكال أو المسار.
  • يمكنك إضافة مناطق الاقتصاص وتقاطعها وتقاطعها للحصول على المنطقة التي تريدها بالضبط.
  • يمكنك تطبيق التحويلات على النص عن طريق تحويل اللوحة.
  • تتيح لك طريقة quickReject() Canvas التحقق مما إذا كان مستطيل أو مسار محدد يقع خارج المناطق المرئية حاليًا.

دورة Udacity:

مستندات مطوّر برامج Android:

يمكنك أيضًا الاطّلاع على سلسلة بنية الرسومات من المقالات للحصول على شرح مفصّل لكيفية رسم إطار عمل Android على الشاشة.

يسرد هذا القسم المهام الدراسية المحتملة للطلاب الذين يعملون من خلال هذا الدرس التطبيقي حول الترميز في إطار دورة تدريبية يُديرها معلِّم. يجب أن ينفِّذ المعلّم ما يلي:

  • يمكنك تخصيص واجب منزلي إذا لزم الأمر.
  • التواصل مع الطلاب بشأن كيفية إرسال الواجبات المنزلية
  • وضع درجات للواجبات المنزلية.

ويمكن للمعلّمين استخدام هذه الاقتراحات بقدر ما يريدون أو بقدر ما يريدون، ويجب عدم التردد في تخصيص أي واجبات منزلية أخرى مناسبة.

إذا كنت تستخدم هذا الدرس التطبيقي بنفسك، يمكنك استخدام هذه الواجبات المنزلية لاختبار معلوماتك.

الإجابة عن هذه الأسئلة

السؤال 1

ما الطريقة التي تستدعيها لاستبعاد الأشكال بكفاءة من رسمها؟

excludeFromDrawing()

quickReject()

onDraw()

clipRect()

السؤال 2

ما هي المعلومات التي يمكن حفظها أو استعادتها من قِبل Canvas.save() وCanvas.restore()؟

▢ اللون وعرض الخط وما إلى ذلك

▢ الانتقالات الحالية فقط

▢ منطقة النقل والاقتصاص الحالية

▢ منطقة الاقتصاص الحالية فقط

السؤال 3

تحدِّد السمة Paint.Align ما يلي:

▢ كيفية محاذاة أشكال الرسم التالية

▢ ما مصدر أصل النص الذي يتم رسم النص منه

▢ حيث تتم المحاذاة في منطقة الاقتصاص

▢أي جانب من النص ستتم محاذاته مع المصدر

للحصول على روابط إلى دروس تطبيقية أخرى حول الترميز في هذه الدورة التدريبية، اطّلِع على الصفحة المقصودة للإصدارات المتقدّمة من Android في لغة ترميز Kotlin.