كيفية إنشاء طرق عرض مخصصة؟

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

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

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

هذا هو دورة حياة عرض الروبوت الأساسية

onMeasure (widthMeasureSpec: Int، heightMeasureSpec: Int)
 تمرير كل طريقة عرض الأصل قيد الطول والعرض إلى طريقة العرض التابعة الخاصة به استناداً إلى طريقة العرض التابعة التي تحدد حجمها. يستعرض العرض الفرعي ثم setMeasuredDimension () لتخزين العرض والارتفاع المقاس.

كيف يتم تمرير هذه القيود؟

يستخدم Android int 32 بت يسمى spec spec لحزم البعد ووضعه. الوضع قيد ويمكن أن يكون من 3 أنواع:

  • MeasureSpec.EXACTLY: يجب أن تكون طريقة العرض بنفس حجم البعد الذي يتم تمريره مع المواصفات. على سبيل المثال. layout_width = "100dp" ، layout_width = "match_parent" ، layout_weight = "1".
  • MeasureSpec.AT_MOST: يمكن أن يكون للعرض أقصى ارتفاع / عرض للبعد. ومع ذلك ، يمكن أن يكون أصغر أيضًا إذا رغب في ذلك. على سبيل المثال android: layout_width = "wrap_content"
  • MeasureSpec.UNSPECIFIED: يمكن أن يكون العرض من أي حجم. يتم تمرير هذا عندما نستخدم ScrollView أو ListView بصفتنا الوالدين.

onLayout (تم التغيير: منطقي ، يسار: كثافة العمليات ، أعلى: كثافة العمليات ، اليمين: كثافة العمليات ، أسفل: كثافة العمليات)
يطبق Android أي إزاحة أو هوامش ويستدعي هذه الطريقة لإبلاغ وجهة نظرك بالمكان الذي سيتم وضعه فيه على الشاشة بالضبط. على عكس onMeasure ، يتم استدعاؤه مرة واحدة فقط أثناء النقل. لذلك يوصى بإجراء أي حسابات معقدة في هذه الطريقة.

onDraw (قماش: قماش)
أخيرًا ، يوفر لك Android سطح رسم ثنائي الأبعاد ، أي اللوحة التي يمكنك الرسم باستخدام كائن الطلاء.

يقوم مؤشر ترابط واجهة المستخدم بعد ذلك بتمرير قوائم العرض لتقديم مؤشر ترابط يقوم بالكثير من التحسينات وأخيراً يقوم GPU بمعالجة البيانات التي يتم تمريرها إليها بواسطة مؤشر ترابط التجسيد.

كيفية تحديد سمات لرأيك؟

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


    
    
    
    
    
    
        
        
        
        
    

تتم الإشارة إلى الشيء نفسه أثناء إنشاء طريقة عرض بالطريقة التالية.

التطبيق: circleRadius = "5dp"
التطبيق: circleDrawable = "@ drawable / ic_bell"
التطبيق: circleColorType = "fillStroke"
...

الآن ، يتعين علينا تحليل هذه السمات في صف جافا الخاص بك أو kotlin.

  • إنشاء فئة العرض الخاصة بك والتي تمتد فئة android.view
  • الحصول على إشارة إلى السمات المعلنة في XML. بينما يتم تمرير attrs في المنشئ ، المعلمة الثانية هي إشارة إلى styleable أعلنا للتو. يتم استخدام الأخيرتين للحصول على سمات النمط الافتراضي في السمة أو لتزويد سمات النمط الافتراضية.
val a = context.theme.obtainStyledAttributes (attrs، R.styleable.YourCustomViewClass، 0، 0)
  • تحليل وسيطات السمة
radi = a.getDimension (R.styleable.CustomView_circleRadius ، احتياطي)
showLabel = a.getDimension (R.styleable.CustomView_showName ، احتياطي)
colorType = a.getInteger (R.styleable.CustomView_colorType ، colorType)

يتعامل Android تلقائيًا مع عملية تحويل dp أو sp إلى المقدار الصحيح من البكسل وفقًا لحجم الشاشة عند تحليل سمة البعد. لكن ، عليك التأكد من أن القيمة الاحتياطية يتم تحويلها إلى قيمة بكسل مناسبة حيث أن android يُرجع القيمة الاحتياطية دون أي تحويلات إذا لم يتم تحديد قيمة السمة في XML.

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

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

  • أخيرًا ، أعد تدوير الصفيف المكتوب ليستخدمه المتصل لاحقًا.
a.recycle ()

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

حساب حجم وجهة نظرك

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

  • احسب مقدار العرض والارتفاع الذي تتطلبه طريقة العرض الخاصة بك. على سبيل المثال ، إذا كنت ترسم دائرة بسيطة مع تسمية أسفل الدائرة. العرض المقترح سيكون:
     (قطر الدائرة + أي عرض إضافي إذا احتلها الملصق).
  • حساب العرض المطلوب عن طريق إضافة العرض المقترح مع paddingStart و paddingEnd. على نحو مماثل ، يتم اقتراح الارتفاع المرغوب بالإضافة إلى الحشوة أعلى الحشوأعلى.
  • حساب الحجم الفعلي احترام القيود. لحساب هذا ، تحتاج ببساطة إلى تمرير مواصفات القياس التي تم تمريرها إليك في onMeasure () والبعد الذي تريده في هذه الطريقة يسمى حلال الحجم (). ستخبرك هذه الطريقة بأقرب بُعد ممكن لعرضك أو طولك المرغوب فيه مع مراعاة قيود الوالدين.
  • الأهم من ذلك ، تحتاج إلى ضبط العرض النهائي والارتفاع في طريقة onMeasure عن طريق استدعاء setMeasuredDimension (يقاس العرض ، المقاس بالارتفاع) لتخزين العرض والارتفاع المقاسين لطريقة العرض هذه ، وإلا ، فقد ترى طريقة العرض الخاصة بك تتعطل مع IllegalStateException.

تحديد المواقع وجهات نظركم

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

رسم وجهة نظرك

قبل استخدام اللوحة هناك بعض الأشياء التي نحتاج إلى فهمها:

  • الرسام: تحتوي فئة الرسام على معلومات عن النمط واللون حول كيفية رسم الأشكال الهندسية والنصية والصور النقطية. هنا هو كيفية إنشاء كائن الطلاء.
mPaint = جديد الرسام () ؛
mPaint.setColor (mDrawColor)؛
mPaint.setStyle (Paint.Style.STROKE)؛ // الافتراضي: ملء
/ ينعم حواف ما يتم رسمه دون التأثير على الشكل الداخلي.
mPaint.setAntiAlias ​​(صحيح)؛
يؤثر التدرج على كيفية خفض الألوان ذات الدقة العالية عن الجهاز.
mPaint.setDither (صحيح)؛
mPaint.setStrokeWidth (mStrokeWidth)؛

يمكنك قراءة المزيد حول الخصائص هنا.

  • رسم الأشكال: يمكنك رسم أشكال مباشرة مثل الخط أو القوس أو الدائرة ، إلخ على القماش. دعونا نلقي نظرة على الرسم البياني أدناه للحصول على فهم أفضل.
وصف كيفية رسم البيضاوي والقوس والمستطيل على القماش
تجاوز متعة onDraw (قماش: قماش) {

    super.onDraw (قماش)
    val cx = canvas.width / 2
    فال cy = canvas.height / 2

    ovalRect.left = cx - circleRadius
    ovalRect.top = cy - circleRadius
    ovalRect.right = cx + circleRadius
    ovalRect.bottom = cy + circleRadius

    canvas.drawArc (ovalRect، 0F، 90F، true، mCircleFillPaint)
    canvas.drawOval (شكل بيضوي ، mCircleStrokePaint)
    canvas.drawRect (ovalRect، mRectStrokePaint)

}

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

val cx = canvas.width / 2
فال cy = canvas.height / 2
زاوية val = 2.0 * Math.PI / 5
/ / الانتقال إلى pos 1
path.moveTo (
        cx + (radi * Math.cos (0.0)). toFloat () ،
        cy + (radi * Math.sin (0.0)). toFloat ())
/ / ارسم كل الخطوط حتى نقاط البيع 5
من أجل (أنا في 1 حتى 5) {
    path.lineTo (
            cx + (نصف القطر * Math.cos (الزاوية * i)). toFloat () ،
            cy + (radi * Math.sin (angle * i)). toFloat ())
}
// إنضم إلى pos 5 مع pos 1
path.close ()
// إذا كنت ترغب في إضافة دائرة حول المضلع باستخدام المسار
// path.addCircle (cx، cy، circleRadius، Path.Direction.CW)
// رسم المضلع
canvas.drawPath (المسار ، mShapePaint)
  • تأثيرات المسار: إذا قمت أيضًا بتطبيق تأثير مسار الركن على كائن الطلاء الخاص بك بنصف قطر معين ، سيبدو المضلع هكذا. يمكنك أيضًا استخدام تأثيرات مسار أخرى مثل DashPathEffect و DiscretePath إلخ. لدمج اثنين من تأثيرات المسار المختلفة ، يمكنك استخدام ComposePathEffect.
mShapePaint.pathEffect = CornerPathEffect (20f)
المضلع باستخدام تأثير مسار الزاوية
  • رسم الصور النقطية: لرسم الصور النقطية على القماش ، يمكنك استخدام
canvas.drawBitmap (bitamp، src، dest، paint)

صورة نقطية: صورة نقطية تريد رسمها على قماش
src: يستغرق كائنًا مستقيمًا يحدد جزء الصورة النقطية الذي تريد رسمه. يمكن أن يكون هذا فارغًا إذا كنت تريد رسم الصورة النقطية الكاملة.
dest: كائن مستقيم يخبر المساحة التي تريد تغطيتها على اللوحة القماشية باستخدام الصورة النقطية
paint: كائن الطلاء الذي تريد رسم الصورة النقطية به

يقوم Android تلقائيًا بكل المقاييس أو الترجمة اللازمة لتناسب المصدر في منطقة الوجهة.

يمكنك أيضا رسم drawables على قماش.

drawable.setBounds (يسار ، أعلى ، يمين ، أسفل)
drawable.draw (قماش)

قبل رسم أحد الدرجات ، ستحتاج إلى تعيين حدود على الدرج الخاص بك. يصف اليسار ، أعلى ، يمين وأسفل حجم الدرج وموضعه على القماش. يمكنك العثور على الحجم المفضل لـ Drawables باستخدام أساليب getIntrinsicHeight () و getIntrinsicWidth () وتحديد الحدود وفقًا لذلك.

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

textPaint.getTextBounds (yourText ، startIndex ، endIndex ، rect)

بعد ذلك ، سيحتوي الكائن المستقيم الذي تم تمريره في النهاية على حدود النص للنص الفعلي المراد رسمه. وبهذه الطريقة يمكنك حساب الارتفاع الفعلي للنص المراد رسمه وتعيين خط الأساس الصحيح للنص. لحساب عرض النص ، يجب عليك استخدام textPaint.measureText () لأنه أكثر دقة من العرض المقدم بواسطة حدود نص الطلاء (بسبب الطريقة التي يتم بها تطبيق هذه الأساليب في مكتبة skia). بدلاً من ذلك ، لضمان توسيط النص أفقياً على لوحة الرسم القماشية ، يمكنك فقط ضبط محاذاة الطلاء الخاص بك على TextAlign.CENTER وتمرير نقطة مركز اللوحة القماشية الخاصة بك في الإحداثي س.

canvas.drawText (النص ، xPos ، yPos ، الطلاء)

رسم نص متعدد الأسطر: إذا كنت ترغب في التعامل مع فواصل الأسطر (\ n) أو إذا كان لديك عرض ثابت لرسم نص ، يمكنك استخدام Static Layout أو Dynamic Layout. سيؤدي ذلك تلقائيًا إلى معالجة جميع فواصل الكلمات أو فواصل الأسطر وإعلامك أيضًا بمدى الارتفاع المطلوب لرسم نص في عرض معين.

// بناء تخطيط ثابت
var builder = StaticLayout.Builder.obtain (text، 0، text.length ()، textPaint، width)؛
StaticLayout myStaticLayout = builder.build ()؛
فار heightRequired = myStaticLayout.height
// الجزء الرسم
canvas.save ()
canvas.translate (xPos، yPos)
myStaticLayout.draw (قماش)
canvas.restore ()
  • حفظ واستعادة قماش: كما قد تلاحظ ، نحتاج إلى حفظ قماش الرسم وترجمته قبل الرسم عليه وأخيراً يتعين علينا استعادة اللوحة القماشية. في كثير من الأحيان ، نحتاج إلى رسم شيء ذي إعداد مختلف مثل تدوير اللوحة القماشية أو ترجمتها أو قص جزء معين من اللوحة القماشية أثناء رسم شكل ما. في هذه الحالة ، يمكننا الاتصال بـ canvas.save () والذي من شأنه أن يحفظ إعدادات قماشنا الحالية على رصة. بعد ذلك ، نقوم بتغيير إعدادات القماش (الترجمة إلخ) ثم نرسم كل ما نريد باستخدام هذه الإعدادات. أخيرًا ، عندما ننتهي من الرسم ، يمكننا استدعاء canvas.restore () والذي سيعيد قماش الرسم إلى التكوين السابق الذي قمنا بحفظه.
  • معالجة إدخالات المستخدم: أخيرًا ، قمت بإنشاء طريقة العرض المخصصة الخاصة بك باستخدام سمات XML ، ولكن ماذا لو كنت ترغب في تغيير أي خاصية في وقت التشغيل مثل نصف قطر الدائرة ولون النص وما إلى ذلك. ستحتاج إلى إبلاغ Android API's لتعكس التغييرات. الآن ، إذا كان أي تغيير في الخاصية يؤثر على حجم العرض الخاص بك ، فستقوم بتعيين المتغير واستدعاء requestLayout () لإعادة حساب حجم العرض وإعادة رسمه. ومع ذلك ، إذا تم تغيير خاصية مثل لون النص ، فستحتاج فقط إلى إعادة رسمه بلون جديد للدهان النصي وفي هذه الحالة ، سيكون من الحكمة استدعاء invalidate ().

ملاحظة إضافية: الآن إذا كانت طريقة العرض الخاصة بك تحتوي على الكثير من السمات ، فقد يكون هناك الكثير من المرات التي ستضطر فيها إلى كتابة invalidate () / requestLayout بعد كل محدد. يمكن حل هذه المشكلة عن طريق استخدام مندوبي kotlin. دعونا نلقي نظرة على المثال أدناه لنكون أكثر وضوحا.

var textColor: Int by OnValidateProp (Color.BLACK)
var circleColor: Int بواسطة OnValidateProp (Color.CYAN)
class OnValidateProp  (حقل var الخاص: T ، السطر الخاص inline var func: () -> Unit = {}) {
    set fun funValue (thisRef: Any؟، p: KProperty <*>، v: T) {
        المجال = الخامس
        إبطال ()

    }

    متعة المشغل getValue (thisRef: Any؟ ، p: KProperty <*>): T {
        مجال العودة
    }

}

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

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

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

  • إبطال بذكاء: لا تدعو إلى إبطال مفعولها إلا إذا تغير شيء مرئي للمستخدم. ثانياً ، عند الاتصال بإبطال ، إن أمكن ، قم بتمرير كائن مستقيم بطريقة غير صالحة لإعلام وحدة معالجة الرسومات (GPU) بالجزء الذي يجب رسمه من الشاشة.
  • ارسم بعناية: لا ترسم أشياء غير مرئية للمستخدم. بعد كل شيء سطح 2D وسيكون من غير المجدي رسم شيء تداخل فيما بعد بشيء آخر. يمكنك تحقيق ذلك باستخدام Canvas.clipRect (). أيضا ، لا ترسم شيئا خارج حدود الشاشة. يمكنك تحقيق ذلك باستخدام canvas.quickReject ().
  • عدم تخصيص كائنات في onDraw: يتم استدعاء onDraw () 60 مرة في الثانية. على الرغم من أن جامعي القمامة سريعون جدًا ، فقد لا يكون هناك انخفاض في GC لكنهم يعملون على خيط منفصل ، مما يعني أنك قد تتناول الكثير من البطارية. علاوة على ذلك ، منذ معظم الوقت نوع الكائنات التي تمت تهيئتها في onDraw هي الكائنات الرسومية (wrappers حول C ++). إنه يؤدي إلى استدعاء الكثير من المدمرات ومن ثم تشغيل عناصر نهائية لاستعادة ذاكرة الكائن التي لا تفيد الأداء أبدًا.