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

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

منطقة القص هي عادةً مستطيل، ولكن يمكن أن تكون أي شكل أو مجموعة من الأشكال، حتى النص. يمكنك أيضًا تحديد ما إذا كنت تريد تضمين المنطقة داخل منطقة القص أو استبعادها. على سبيل المثال، يمكنك إنشاء منطقة قص دائرية وعرض ما يقع خارج الدائرة فقط.
في هذا الدرس التطبيقي، ستجرّب طرقًا مختلفة لقص المحتوى.
ما يجب معرفته
يجب أن تكون على دراية بما يلي:
- كيفية إنشاء تطبيق باستخدام
Activityوتشغيله باستخدام "استوديو Android" - كيفية إنشاء
Canvasوالرسم عليه - كيفية إنشاء
Viewمخصّص وتجاوزonDraw()وonSizeChanged()
أهداف الدورة التعليمية
- كيفية قصّ الكائنات للرسم على
Canvas - كيفية حفظ حالات الرسم على لوحة العرض واستعادتها
- كيفية تطبيق التحويلات على لوحة عرض وعلى النص
الإجراءات التي ستنفذّها
- أنشئ تطبيقًا يرسم أشكالاً مقصوصة على الشاشة يوضّح الطرق المختلفة للقص ونتيجته على إمكانية رؤية هذه الأشكال.
- سترسُم أيضًا بعض النصوص المترجمة والمائلة.
يوضّح تطبيق ClippingExample كيفية استخدام الأشكال ودمجها لتحديد الأجزاء التي يتم عرضها من لوحة الرسم في طريقة العرض. سيبدو تطبيقك النهائي كما هو موضّح في لقطة الشاشة أدناه.

ستنشئ هذا التطبيق من البداية، لذا عليك إعداد مشروع وتحديد الأبعاد والسلاسل وتعريف بعض المتغيرات.
الخطوة 1: إنشاء مشروع ClippingExample
- أنشئ مشروع Kotlin باسم
ClippingExampleباستخدام نموذج Empty Activity. استخدِمcom.example.androidلبادئة اسم الحزمة. - فتح "
MainActivity.kt" - في طريقة
onCreate()، استبدِل طريقة العرض التلقائية للمحتوى واضبط طريقة العرض على مثيل جديد منClippedView. ستكون هذه طريقة العرض المخصّصة لأمثلة المقاطع التي ستنشئها لاحقًا.
setContentView(ClippedView(this))- في المستوى نفسه الذي يظهر فيه
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: إضافة السمات وموارد السلسلة
- حدِّد السمات التي ستستخدمها للعروض المقتطعة في ملف موارد جديد في
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 بقيم أكبر لا ينطبق إلا على الشاشات الأكبر.
- في "استوديو Android"، انقر بزر الماوس الأيمن على مجلد values واختَر New (جديد) > ملف موارد القيم (Values resource file).
- في مربّع الحوار ملف الموارد الجديد، أطلِق على الملف الاسم
dimens. في المحدّدات المتاحة، اختَر أصغر عرض للشاشة وانقر على الزر >> لإضافته إلى المحدّدات المحدّدة. أدخِل 480 في مربّع أصغر عرض للشاشة وانقر على حسنًا.

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

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

- استبدِل المحتوى التلقائي لملف
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>- في
strings.xml، أضِف السلاسل التالية. سيتم استخدامها لعرض النص على لوحة العرض.
<string name="clipping">Clipping</string>
<string name="translated">translated text</string>
<string name="skewed">"Skewed and "</string>الخطوة 3: إنشاء كائنَي Paint وPath وتهيئتهما
- عُد إلى طريقة عرض Android لمشروعك.
- في
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)
}- في
ClippedView، أنشئPathواضبطه لتخزين مسار ما تم رسمه على الجهاز. استيرادandroid.graphics.Path
private val path = Path()الخطوة 4: إعداد الأشكال
في هذا التطبيق، تعرض عدة صفوف وعمودَين من الأشكال التي تم قصّها بطرق مختلفة.
تشترك جميعها في ما يلي:
- مستطيل كبير (مربع) يعمل كحاوية
- خط قطري يمر عبر المستطيل الكبير
- دائرة
- سلسلة نصية قصيرة

في هذه الخطوة، يمكنك إعداد سمات لهذه الأشكال من الموارد، بحيث لا تحتاج إلى الحصول على السمات إلا مرة واحدة عند استخدامها لاحقًا.
- في
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)- أضِف متغيرات لإزاحة مستطيل وإزاحة مستطيل صغير.
private val rectInset = resources.getDimension(R.dimen.rectInset)
private val smallRectOffset = resources.getDimension(R.dimen.smallRectOffset)- أضِف متغيّرًا لنصف قطر الدائرة. هذا هو نصف قطر الدائرة المرسومة داخل المستطيل.
private val circleRadius = resources.getDimension(R.dimen.circleRadius)- أضِف إزاحة وحجم نص للنص الذي يتم رسمه داخل المستطيل.
private val textOffset = resources.getDimension(R.dimen.textOffset)
private val textSize = resources.getDimension(R.dimen.textSize)الخطوة 4: إعداد مواقع الصفوف والأعمدة
يتم عرض أشكال هذا التطبيق في عمودَين وأربعة صفوف، ويتم تحديدها من خلال قيم السمات التي تم إعدادها أعلاه. لا يشكّل الجزء الرياضي من هذه العملية جزءًا من هذا الدرس العملي، ولكن يمكنك الاطّلاع عليه أثناء نسخ الرمز البرمجي المقدَّم في هذه الخطوة.
- اضبط إحداثيات عمودَين.
private val columnOne = rectInset
private val columnTwo = columnOne + rectInset + clipRectRight- أضِف الإحداثيات لكل صف، بما في ذلك الصف الأخير للنص المحوَّل.
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)- شغِّل تطبيقك. من المفترض أن يفتح التطبيق بشاشة بيضاء فارغة أسفل اسم التطبيق.

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

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

(1) أولاً، عليك ترجمة Canvas إلى المكان الذي تريد رسم المستطيل فيه. أي بدلاً من احتساب موضع المستطيل التالي وجميع الأشكال الأخرى التي يجب رسمها، يمكنك نقل Canvas نقطة الأصل، أي نظام الإحداثيات.
(2) بعد ذلك، ارسم المستطيل عند نقطة الأصل الجديدة للوحة العرض. أي أنّك ترسم الأشكال في الموقع الجغرافي نفسه في نظام الإحداثيات المترجَم. هذه الطريقة أبسط بكثير وأكثر كفاءة قليلاً.
(3) أخيرًا، أعِد Canvas إلى Origin الأصلي.
في ما يلي الخوارزمية التي ستنفّذها:
- في
onDraw()، استدعِ دالة لملءCanvasبلون الخلفية الرمادي ورسم الأشكال الأصلية. - استدعِ دالة لكل مستطيل مقصوص والنص المطلوب رسمه.
لكل مستطيل أو نص:
- احفظ الحالة الحالية
Canvasلتتمكّن من إعادة الضبط إلى تلك الحالة الأولية. - ترجِم
Originلوحة العرض إلى الموقع الجغرافي الذي تريد الرسم فيه. - تطبيق أشكال ومسارات القص
- ارسم المستطيل أو النص.
- استعادة حالة
Canvas
الخطوة: إلغاء طريقة onDraw()
- استبدِل
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)
}- أنشئ رموزًا صورية لكل وظائف الرسم حتى يستمر تجميع الرمز. يمكنك نسخ الرمز أدناه.
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: إنشاء طريقة drawClippedRectangle()
- أنشئ طريقة
drawClippedRectangle()تأخذ وسيطًاcanvasمن النوعCanvas.
private fun drawClippedRectangle(canvas: Canvas) {
}- داخل طريقة
drawClippedRectangle()، اضبط حدود مستطيل القص للشكل بأكمله. طبِّق مستطيلاً للقص يحصر الرسم في المربع فقط.
canvas.clipRect(
clipRectLeft,clipRectTop,
clipRectRight,clipRectBottom
)تقلّل طريقة Canvas.clipRect(...) من مساحة الشاشة التي يمكن لعمليات الرسم المستقبلية الكتابة فيها. يتم ضبط حدود القص لتكون التقاطع المكاني لمستطيل القص الحالي والمستطيل الذي تم تمريره إلى clipRect(). تتوفّر العديد من صيغ طريقة clipRect() التي تقبل أشكالاً مختلفة للمناطق وتسمح بعمليات مختلفة على مستطيل القص.
- املأ
canvasباللون الأبيض. نعم. اللوحة بأكملها، لأنّك لا ترسم مستطيلات، بل تقصّ. بسبب مستطيل الاقتصاص، يتم ملء المنطقة المحدّدة بمستطيل الاقتصاص فقط، ما يؤدي إلى إنشاء مستطيل أبيض. ويظل باقي السطح باللون الرمادي.
canvas.drawColor(Color.WHITE)- غيِّر اللون إلى الأحمر وارسم خطًا قطريًا داخل مستطيل القص.
paint.color = Color.RED
canvas.drawLine(
clipRectLeft,clipRectTop,
clipRectRight,clipRectBottom,paint
)- اضبط اللون على الأخضر وارسم دائرة داخل مستطيل القص.
paint.color = Color.GREEN
canvas.drawCircle(
circleRadius,clipRectBottom - circleRadius,
circleRadius,paint
)- اضبط اللون على الأزرق وارسم نصًا محاذيًا للحافة اليمنى لمستطيل الاقتصاص. استخدِم
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: تنفيذ طريقة drawBackAndUnclippedRectangle()
- لمشاهدة طريقة
drawClippedRectangle()أثناء العمل، ارسم المستطيل الأول غير المقتصّ من خلال تنفيذ طريقةdrawBackAndUnclippedRectangle()كما هو موضّح أدناه. احفظcanvas، وترجِم إلى موضع الصف والعمود الأول، وارسم عن طريق استدعاءdrawClippedRectangle()، ثم استعِدcanvasإلى حالته السابقة.
private fun drawBackAndUnclippedRectangle(canvas: Canvas){
canvas.drawColor(Color.GRAY)
canvas.save()
canvas.translate(columnOne,rowOne)
drawClippedRectangle(canvas)
canvas.restore()
}- شغِّل تطبيقك. من المفترض أن يظهر المستطيل الأبيض الأول مع الدائرة والخط الأحمر والنص على خلفية رمادية.

في المثال التالي الخاص بقص الأشكال، يمكنك تطبيق مجموعات مختلفة من مناطق القص لتحقيق تأثيرات رسومية والتعرّف على كيفية دمج مناطق القص لإنشاء أي شكل تريده.
تتّبع كل طريقة من هذه الطرق النمط نفسه.
- حفظ الحالة الحالية للوحة العرض:
canvas.save()
يحتفظ سياق النشاط بمجموعة من حالات الرسم. تتألف حالات الرسم من مصفوفة التحويل الحالية ومنطقة القص الحالية. يمكنك حفظ الحالة الحالية، وتنفيذ إجراءات تغيّر حالة الرسم (مثل ترجمة اللوحة أو تدويرها)، ثم استعادة حالة الرسم المحفوظة. (ملاحظة: يشبه هذا الأمر "stash" في git).
عندما يتضمّن الرسم عمليات تحويل، يكون ربط عمليات التحويل وإلغاؤها عن طريق عكسها عرضة للأخطاء. على سبيل المثال، إذا ترجمتَ ثم مددتَ ثم أدرتَ، ستصبح العملية معقّدة بسرعة. بدلاً من ذلك، احفظ حالة لوحة العرض، وطبِّق عمليات التحويل، وارسم، ثم استعِد الحالة السابقة.
على سبيل المثال، يمكنك تحديد منطقة قص وحفظ هذه الحالة. بعد ذلك، يمكنك ترجمة لوحة العرض وإضافة منطقة قص وتدويرها. بعد الرسم، يمكنك استعادة حالة القص الأصلية، ويمكنك المتابعة لإجراء ترجمة مختلفة وتحويل قص، كما هو موضّح في الرسم التخطيطي.

- ترجمة أصل لوحة العرض إلى إحداثيات الصف/العمود:
canvas.translate()
من الأسهل بكثير نقل نقطة بداية لوحة الرسم ورسم الشيء نفسه في نظام إحداثيات جديد بدلاً من نقل جميع العناصر المطلوب رسمها. (ملاحظة: يمكنك استخدام الأسلوب نفسه لتدوير العناصر).
- طبِّق عمليات التحويل على
path، إذا كان ذلك منطبقًا. - تطبيق القص:
canvas.clipPath(path) - ارسم الأشكال:
drawClippedRectangle() or drawText() - استعادة حالة اللوحة السابقة:
canvas.restore()
الخطوة 1: تنفيذ drawDifferenceClippingExample(canvas)
أضِف رمزًا لرسم المستطيل الثاني الذي يستخدم الفرق بين مستطيلَي قص لإنشاء تأثير إطار صورة.

استخدِم الرمز أدناه الذي ينفّذ ما يلي:
- احفظ اللوحة.
- ترجِم أصل لوحة العرض إلى مساحة مفتوحة في الصف الأول والعمود الثاني على يسار المستطيل الأول.
- تطبيق مستطيلَي قص يطرح عامل التشغيل
DIFFERENCEالمستطيل الثاني من المستطيل الأول.
- استدعِ الدالة
drawClippedRectangle()لرسم اللوحة المعدَّلة. - استعادة حالة لوحة العرض
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()
}- شغِّل تطبيقك، ويجب أن يظهر على النحو التالي.

الخطوة 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: تنفيذ drawCombinedClippingExample(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: تنفيذ drawRoundedRectangleClippingExample(canvas)
بعد ذلك، أضِف مستطيلاً مدوّر الزوايا وهو شكل قص شائع الاستخدام.

- على المستوى الأعلى، أنشئ متغيّر مستطيل وقم بتهيئة قيمته.
RectFهي فئة تحتوي على إحداثيات المستطيل بنقطة عائمة.
private var rectF = RectF(
rectInset,
rectInset,
clipRectRight - rectInset,
clipRectBottom - rectInset
)- تنفيذ الدالة
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: تنفيذ drawOutsideClippingExample(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: تنفيذ drawTranslatedTextExample(canvas)
لا يختلف رسم النص كثيرًا عن أي أشكال أخرى، ويمكنك تطبيق عمليات تحويل على النص. على سبيل المثال، يمكنك ترجمة النص من خلال ترجمة لوحة العرض ورسم النص.

- نفِّذ الدالة أدناه.
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()
}- شغِّل تطبيقك للاطّلاع على النص المترجَم.

الخطوة 8: تنفيذ drawSkewedTextExample(canvas)
يمكنك أيضًا إمالة النص. أي تشويهه بطرق مختلفة.

- أنشئ الدالة أدناه في
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()
}- شغِّل تطبيقك لعرض النص المشوّه قبل النص المترجَم.

تتيح لك الطريقة quickReject() Canvas التحقّق مما إذا كان مستطيل أو مسار محدّد سيقع خارج المناطق المرئية حاليًا تمامًا، وذلك بعد تطبيق جميع عمليات التحويل.
تكون طريقة quickReject() مفيدة للغاية عند إنشاء رسومات أكثر تعقيدًا والحاجة إلى إنجاز ذلك بأسرع ما يمكن. باستخدام quickReject()، يمكنك تحديد العناصر التي لا تحتاج إلى رسمها على الإطلاق بكفاءة، ولا حاجة إلى كتابة منطق التقاطع الخاص بك.
- تعرض الطريقة
quickReject()القيمةtrueإذا كان المستطيل أو المسار غير مرئيَّين على الشاشة على الإطلاق. في حال حدوث تطابق جزئي، عليك إجراء عملية التحقّق بنفسك. - تكون قيمة
EdgeTypeإماAA(تنعيم الحواف: يتم التعامل مع الحواف من خلال التقريب، لأنّها قد تكون منسّقة) أوBW(أبيض وأسود: يتم التعامل مع الحواف من خلال التقريب إلى أقرب حدود بكسل) للتقريب إلى أقرب بكسل فقط.
تتوفّر عدة إصدارات من quickReject()، ويمكنك أيضًا العثور عليها في المستندات.
| quickReject |
| quickReject |
| quickReject |
في هذا التمرين، سترسم في صف جديد أسفل النص وداخل clipRect، كما فعلت سابقًا.
- أولاً، يمكنك استدعاء
quickReject()باستخدام المستطيلinClipRectangleالذي يتداخل معclipRect. وبالتالي، تعرضquickReject()القيمة "خطأ"، ويتم ملءclipRectبالقيمةBLACK، ويتم رسم المستطيلinClipRectangle.

- بعد ذلك، غيِّر الرمز واستدعِ
quickReject()، معnotInClipRectangle. تعرضquickReject()الآن القيمة "صحيح"، ويتم ملءclipRectبالقيمةWHITE، ولا يتم رسمnotInClipRectangle.

عندما يكون لديك رسومات معقّدة، يمكن أن يوضّح لك ذلك بسرعة الأشكال التي تقع خارج منطقة القص تمامًا، والأشكال التي قد تحتاج إلى إجراء حسابات ورسم إضافيين لها لأنّها تقع جزئيًا أو كليًا داخل منطقة القص.
الخطوة: تجربة quickReject()
- في المستوى الأعلى، أنشئ متغيّرًا لإحداثيات y لصف إضافي.
private val rejectRow = rowFour + rectInset + 2*clipRectBottom- أضِف الدالة
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()
}- في
onDraw()، أزِل التعليق من استدعاءdrawQuickRejectExample(). - شغِّل تطبيقك، وسيظهر لك مستطيل أسود، وهو منطقة الاقتصاص المملوءة، وأجزاء من
inClipRectangle، لأنّ المستطيلين يتداخلان، لذا تعرض الدالةquickReject()القيمةfalseويتم رسمinClipRectangle.

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

نزِّل الرمز البرمجي للدرس التطبيقي حول الترميز المكتمل.
$ git clone https://github.com/googlecodelabs/android-kotlin-drawing-clipping
يمكنك بدلاً من ذلك تنزيل المستودع كملف Zip وفك ضغطه وفتحه في Android Studio.
- تحتفظ
Contextالخاصة بأحد الأنشطة بحالة تحافظ على عمليات التحويل ومناطق القص الخاصة بـCanvas. - استخدِم
canvas.save()وcanvas.restore()للرسم والرجوع إلى الحالة الأصلية للوحة العرض. - لرسم أشكال متعددة على لوحة الرسم، يمكنك إما حساب موقعها الجغرافي أو نقل (تحويل) نقطة بداية سطح الرسم. يمكن أن يسهّل ذلك إنشاء طرق مساعدة لتسلسلات الرسم المتكررة.
- يمكن أن تكون مناطق القص أي شكل أو مجموعة من الأشكال أو المسارات.
- يمكنك إضافة مناطق قص وطرحها وتقاطعها للحصول على المنطقة التي تحتاج إليها بالضبط.
- يمكنك تطبيق عمليات تحويل على النص من خلال تحويل لوحة العرض.
- تتيح لك طريقة
quickReject()Canvasالتحقّق مما إذا كان مستطيل أو مسار محدّد سيقع خارج المناطق المرئية حاليًا تمامًا.
دورة Udacity التدريبية:
مستندات مطوّري تطبيقات Android:
CanvasصفBitmapصفViewصفPaintصف- إعدادات
Bitmap.config - عوامل التشغيل
Region.Op PathصفCanvasصفBitmapصفViewصفPaintصف- إعدادات
Bitmap.config - عوامل التشغيل
Region.Op Pathصفandroid.graphicsأدوات الرسومات- إعدادات
Bitmap.ConfigCanvas - لوحة الرسم والعناصر القابلة للرسم
- ما هي وظيفة canvas.translate()؟
- فهم وظيفتَي save() وrestore() في سياق Canvas
- التقاط مقاطع
- السحب على المكشوف
@JvmOverloads
يمكنك أيضًا الاطّلاع على سلسلة مقالات بنية الرسومات للحصول على شرح مفصّل حول كيفية رسم إطار عمل Android على الشاشة.
يسرد هذا القسم مهامًا منزلية محتملة للطلاب الذين يعملون على هذا الدرس التطبيقي العملي كجزء من دورة تدريبية يقودها مدرّب. على المعلّم تنفيذ ما يلي:
- حدِّد واجبًا منزليًا إذا لزم الأمر.
- توضيح كيفية إرسال الواجبات المنزلية للطلاب
- وضع درجات للواجبات المنزلية
يمكن للمدرّبين استخدام هذه الاقتراحات بالقدر الذي يريدونه، ويجب ألا يترددوا في تكليف الطلاب بأي واجبات منزلية أخرى يرونها مناسبة.
إذا كنت تعمل على هذا الدرس العملي بنفسك، يمكنك استخدام مهام الواجب المنزلي هذه لاختبار معلوماتك.
الإجابة عن هذه الأسئلة
السؤال 1
ما هي الطريقة التي تستخدمها لاستبعاد الأشكال من الرسم بكفاءة؟
▢ excludeFromDrawing()
▢ quickReject()
▢ onDraw()
▢ clipRect()
السؤال 2
ما هي المعلومات التي يتم حفظها واستعادتها باستخدام Canvas.save() وCanvas.restore()؟
▢ اللون وعرض الخط وما إلى ذلك
▢ عمليات التحويل الحالية فقط
▢ عمليات التحويل ومنطقة القص الحالية
▢ منطقة القص الحالية فقط
السؤال 3
تحدّد Paint.Align ما يلي:
▢ كيفية محاذاة أشكال الرسومات التالية
▢ الجانب الذي يتم استخلاص النص منه
▢ المكان الذي تتم فيه محاذاة الصورة داخل منطقة القص
▢ جهة النص التي يجب محاذاتها مع نقطة الأصل
للحصول على روابط تؤدي إلى دروس برمجية أخرى في هذه الدورة التدريبية، يمكنك الانتقال إلى الصفحة المقصودة للدروس البرمجية المتقدّمة حول Android بلغة Kotlin.