कैनवस ऑब्जेक्ट को क्लिप करना

यह कोडलैब, Kotlin में ऐडवांस Android कोर्स का हिस्सा है. अगर इस कोर्स के कोडलैब को क्रम से पूरा किया जाता है, तो आपको सबसे ज़्यादा फ़ायदा मिलेगा. हालांकि, ऐसा करना ज़रूरी नहीं है. कोर्स के सभी कोडलब, Advanced Android in Kotlin कोडलब के लैंडिंग पेज पर दिए गए हैं.

परिचय

इस कोडलैब के लिए, क्लिपिंग का मतलब है कि इमेज, कैनवस या बिटमैप के उन हिस्सों को तय करना जिन्हें स्क्रीन पर चुना गया है या नहीं चुना गया है. क्लिपिंग का एक मकसद, ओवरड्रॉ को कम करना है. ओवरड्रॉ तब होता है, जब स्क्रीन पर किसी पिक्सल को फ़ाइनल इमेज दिखाने के लिए एक से ज़्यादा बार ड्रॉ किया जाता है. ओवरड्रॉ कम करने का मतलब है कि डिसप्ले के किसी पिक्सल या हिस्से को कम से कम बार ड्रॉ किया जाए, ताकि ड्रॉइंग की परफ़ॉर्मेंस को बेहतर बनाया जा सके. क्लिपिंग का इस्तेमाल, यूज़र इंटरफ़ेस डिज़ाइन और ऐनिमेशन में दिलचस्प इफ़ेक्ट बनाने के लिए भी किया जा सकता है.

उदाहरण के लिए, जब ओवरलैप होने वाले कार्ड का स्टैक बनाया जाता है, जैसा कि यहां दिखाया गया है, तो हर कार्ड को नीचे से ऊपर तक पूरी तरह से बनाने के बजाय, सिर्फ़ दिखने वाले हिस्सों को बनाना ज़्यादा बेहतर होता है. "आम तौर पर", क्योंकि क्लिपिंग ऑपरेशनों में भी लागत लगती है. साथ ही, Android सिस्टम, ड्रॉइंग को ऑप्टिमाइज़ करने के लिए कई काम करता है.

कार्ड के सिर्फ़ दिखने वाले हिस्सों को ड्रा करने के लिए, हर कार्ड के लिए क्लिपिंग क्षेत्र तय करें. उदाहरण के लिए, यहां दिए गए डायग्राम में दिखाया गया है कि किसी इमेज पर क्लिपिंग रेक्टैंगल लागू करने पर, सिर्फ़ रेक्टैंगल के अंदर वाला हिस्सा दिखता है.

क्लिपिंग रीजन आम तौर पर एक आयत होता है, लेकिन यह किसी भी आकार या आकारों का कॉम्बिनेशन हो सकता है. यहां तक कि टेक्स्ट भी हो सकता है. यह भी तय किया जा सकता है कि क्लिपिंग क्षेत्र के अंदर मौजूद क्षेत्र को शामिल करना है या नहीं. उदाहरण के लिए, एक गोलाकार क्लिपिंग क्षेत्र बनाया जा सकता है और सिर्फ़ सर्कल के बाहर मौजूद कॉन्टेंट दिखाया जा सकता है.

इस कोडलैब में, क्लिप करने के अलग-अलग तरीकों के बारे में बताया गया है.

आपको पहले से क्या पता होना चाहिए

आपको इनके बारे में जानकारी होनी चाहिए:

  • Activity की मदद से ऐप्लिकेशन बनाने और उसे Android Studio का इस्तेमाल करके चलाने का तरीका.
  • Canvas बनाने और उस पर ड्रॉ करने का तरीका.
  • कस्टम View बनाने का तरीका. साथ ही, onDraw() और onSizeChanged() को बदलने का तरीका.

आपको क्या सीखने को मिलेगा

  • किसी Canvas पर ड्रॉ करने के लिए, ऑब्जेक्ट को क्लिप करने का तरीका.
  • इस कुकी का इस्तेमाल, कैनवस की ड्रॉइंग की स्थितियों को सेव करने और उन्हें वापस लाने के लिए किया जाता है.
  • कैनवस और टेक्स्ट पर ट्रांसफ़ॉर्मेशन लागू करने का तरीका.

आपको क्या करना होगा

  • एक ऐसा ऐप्लिकेशन बनाएं जो स्क्रीन पर क्लिप किए गए शेप बनाए. इसमें क्लिप करने के अलग-अलग तरीके दिखाए गए हों. साथ ही, यह भी दिखाया गया हो कि क्लिप करने से शेप की दृश्यता पर क्या असर पड़ता है.
  • आपको कुछ टेक्स्ट का अनुवाद करके उसे तिरछा भी करना होगा.

ClippingExample ऐप्लिकेशन से पता चलता है कि कैनवस के किन हिस्सों को व्यू में दिखाया जाए. इसके लिए, अलग-अलग शेप का इस्तेमाल और उन्हें एक साथ जोड़ा जा सकता है. आपका फ़ाइनल ऐप्लिकेशन, यहाँ दिए गए स्क्रीनशॉट की तरह दिखेगा.

आपको इस ऐप्लिकेशन को शुरू से बनाना है. इसलिए, आपको एक प्रोजेक्ट सेट अप करना होगा, डाइमेंशन और स्ट्रिंग तय करनी होंगी, और कुछ वैरिएबल का एलान करना होगा.

पहला चरण: ClippingExample प्रोजेक्ट बनाना

  1. Empty Activity टेंप्लेट का इस्तेमाल करके, ClippingExample नाम का Kotlin प्रोजेक्ट बनाएं. पैकेज के नाम के प्रीफ़िक्स के लिए com.example.android का इस्तेमाल करें.
  2. MainActivity.kt खोलें.
  3. onCreate() तरीके में, डिफ़ॉल्ट कॉन्टेंट व्यू को बदलें और कॉन्टेंट व्यू को ClippedView के नए इंस्टेंस पर सेट करें. यह क्लिप के उदाहरणों के लिए आपका कस्टम व्यू होगा, जिसे आपको आगे बनाना है.
setContentView(ClippedView(this))
  1. MainActivity.kt के साथ-साथ, ClippedView नाम का एक कस्टम व्यू बनाने के लिए, नई Kotlin फ़ाइल और क्लास बनाएं. यह View को बढ़ाता है. इसे यहां दिखाया गया हस्ताक्षर दें. आपका बाकी काम, इस ClippedView में होगा. @JvmOverloads एनोटेशन, Kotlin कंपाइलर को इस फ़ंक्शन के लिए ओवरलोड जनरेट करने का निर्देश देता है. ये ओवरलोड, डिफ़ॉल्ट पैरामीटर वैल्यू को बदल देते हैं.
class ClippedView @JvmOverloads constructor(
   context: Context,
   attrs: AttributeSet? = null,
   defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {
}

दूसरा चरण: डाइमेंशन और स्ट्रिंग संसाधन जोड़ना

  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 Studio में, values फ़ोल्डर पर राइट क्लिक करें. इसके बाद, New > Values 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>

तीसरा चरण: Paint और Path ऑब्जेक्ट बनाना और उन्हें शुरू करना

  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()

चौथा चरण: शेप सेट अप करना

इस ऐप्लिकेशन में, अलग-अलग तरीकों से क्लिप किए गए शेप की कई लाइनें और दो कॉलम दिखाए जा रहे हैं.

इन सभी में ये बातें एक जैसी हैं:

  • बड़ा आयत (वर्ग) जो कंटेनर के तौर पर काम करता है
  • बड़े रेक्टैंगल के बीच में एक डायगनल लाइन
  • एक सर्कल
  • टेक्स्ट की छोटी स्ट्रिंग

इस चरण में, संसाधनों से उन शेप के लिए डाइमेंशन सेट अप किए जाते हैं, ताकि बाद में उनका इस्तेमाल करते समय आपको सिर्फ़ एक बार डाइमेंशन मिलें.

  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)

चौथा चरण: लाइन और कॉलम की जगहें सेट अप करना

इस ऐप्लिकेशन के लिए शेप, दो कॉलम और चार लाइनों में दिखाए जाते हैं. ये ऊपर सेट अप किए गए डाइमेंशन की वैल्यू के हिसाब से तय होते हैं. इस कोडलैब में, इस गणित के बारे में नहीं बताया गया है. हालांकि, इस चरण में दिए गए कोड को कॉपी करते समय, इसे देख लें.

  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() तरीके से, एक रेक्टैंगल बनाने के लिए कोड को फ़ैक्टर किया जाता है. इसे नीचे दिखाया गया है.

पहला चरण: drawClippedRectangle() तरीका बनाना

  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
)

दूसरा चरण: drawBackAndUnclippedRectangle() तरीके को लागू करना

  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()

गतिविधि का कॉन्टेक्स्ट, ड्रॉइंग की स्थितियों का स्टैक बनाए रखता है. ड्राइंग की स्थितियों में, मौजूदा ट्रांसफ़ॉर्मेशन मैट्रिक्स और मौजूदा क्लिपिंग क्षेत्र शामिल होता है. मौजूदा स्थिति को सेव किया जा सकता है. इसके बाद, ड्रॉइंग की स्थिति में बदलाव करने वाली कार्रवाइयां की जा सकती हैं. जैसे, कैनवस का अनुवाद करना या उसे घुमाना. इसके बाद, सेव की गई ड्रॉइंग की स्थिति को वापस लाया जा सकता है. (ध्यान दें: यह git में "stash" कमांड की तरह है!).

अगर आपकी ड्राइंग में ट्रांसफ़ॉर्मेशन शामिल हैं, तो उन्हें उलटा करके ट्रांसफ़ॉर्मेशन को चेन करने और पहले जैसा करने में गड़बड़ी हो सकती है. उदाहरण के लिए, अगर किसी ऑब्जेक्ट को ट्रांसलेट, स्ट्रेच, और फिर रोटेट किया जाता है, तो यह प्रोसेस तुरंत जटिल हो जाती है. इसके बजाय, कैनवस की स्थिति को सेव करें, बदलाव लागू करें, ड्रॉ करें, और फिर पिछली स्थिति को वापस लाएं.

उदाहरण के लिए, क्लिपिंग क्षेत्र तय किया जा सकता है और उस स्थिति को सेव किया जा सकता है. इसके बाद, कैनवस का अनुवाद करें, क्लिपिंग क्षेत्र जोड़ें, और घुमाएं. कुछ ड्राइंग करने के बाद, क्लिपिंग की मूल स्थिति को वापस लाया जा सकता है. इसके बाद, डायग्राम में दिखाए गए तरीके से, अलग-अलग अनुवाद और तिरछा करने की प्रोसेस की जा सकती है.

  1. कैनवस के ऑरिजिन को पंक्ति/कॉलम के निर्देशांकों में बदलें: canvas.translate()

ड्रॉ करने के लिए सभी एलिमेंट को मूव करने के बजाय, कैनवस के ऑरिजिन को मूव करना और नए कोऑर्डिनेट सिस्टम में एक ही चीज़ को ड्रॉ करना ज़्यादा आसान है. (अहम जानकारी: एलिमेंट को रोटेट करने के लिए भी इसी तरीके का इस्तेमाल किया जा सकता है.)

  1. अगर कोई path है, तो उस पर ट्रांसफ़ॉर्मेशन लागू करें.
  2. क्लिपिंग लागू करें: canvas.clipPath(path)
  3. आकृतियां बनाएं: drawClippedRectangle() or drawText()
  4. कैनवस की पिछली स्थिति वापस लाएं: canvas.restore()

पहला चरण: 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. ऐप्लिकेशन चलाएं. यह ऐसा दिखना चाहिए.

दूसरा चरण: 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()
}

तीसरा चरण: 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()
}

चौथा चरण: 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()
}

पाँचवाँ चरण: drawRoundedRectangleClippingExample(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()
}

छठा चरण: 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()
}

सातवां चरण: drawTranslatedTextExample(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. अनुवाद किया गया टेक्स्ट देखने के लिए, अपना ऐप्लिकेशन चलाएं.

आठवां चरण: 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

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

boolean

quickReject(RectF rect, Canvas.EdgeType type)

boolean

quickReject(Path path, Canvas.EdgeType type)

इस गतिविधि में, आपको टेक्स्ट के नीचे और clipRect के अंदर, पहले की तरह एक नई लाइन बनानी है.

  • आपने सबसे पहले, रेक्टैंगल inClipRectangle की मदद से quickReject() को कॉल किया. यह रेक्टैंगल clipRect के साथ ओवरलैप होता है. इसलिए, quickReject() की वैल्यू गलत है, clipRect में BLACK भरा गया है, और inClipRectangle रेक्टैंगल बनाया गया है.

  • इसके बाद, कोड बदलें और quickReject() को notInClipRectangle के साथ कॉल करें. अब quickReject() की वैल्यू 'सही' के तौर पर दिखती है. साथ ही, clipRect में WHITE भर जाता है और notInClipRectangle नहीं दिखता.

जटिल ड्रॉइंग के मामले में, इससे आपको तुरंत पता चल सकता है कि कौनसी आकृतियां क्लिपिंग क्षेत्र से पूरी तरह बाहर हैं. साथ ही, आपको यह भी पता चल सकता है कि किन आकृतियों के लिए आपको अतिरिक्त कैलकुलेशन और ड्रॉइंग करनी पड़ सकती है, क्योंकि वे क्लिपिंग क्षेत्र के अंदर आंशिक या पूरी तरह से मौजूद हैं.

चरण: quickReject() फ़ंक्शन को आज़माएँ

  1. सबसे ऊपर के लेवल पर, किसी दूसरी लाइन के y कोऑर्डिनेट के लिए एक वैरिएबल बनाएं.
   private val rejectRow = rowFour + rectInset + 2*clipRectBottom
  1. ClippedView में यह drawQuickRejectExample() फ़ंक्शन जोड़ें. कोड पढ़ें, क्योंकि इसमें 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() में, कोड को बदलकर notInClipRectangle. के हिसाब से quickReject() चलाएं अब 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 डेवलपर का दस्तावेज़:

Android फ़्रेमवर्क, स्क्रीन पर कैसे ड्रॉ करता है, इस बारे में ज़्यादा जानकारी के लिए, ग्राफ़िक्स आर्किटेक्चर लेखों की सीरीज़ भी देखें.

इस सेक्शन में, उन छात्र-छात्राओं के लिए होमवर्क असाइनमेंट की सूची दी गई है जो किसी शिक्षक के कोर्स के हिस्से के तौर पर इस कोडलैब पर काम कर रहे हैं. शिक्षक के पास ये विकल्प होते हैं:

  • अगर ज़रूरी हो, तो होमवर्क असाइन करें.
  • छात्र-छात्राओं को बताएं कि होमवर्क असाइनमेंट कैसे सबमिट किए जाते हैं.
  • होमवर्क असाइनमेंट को ग्रेड दें.

शिक्षक इन सुझावों का इस्तेमाल अपनी ज़रूरत के हिसाब से कर सकते हैं. साथ ही, वे चाहें, तो कोई दूसरा होमवर्क भी दे सकते हैं.

अगर आपको यह कोडलैब खुद से पूरा करना है, तो अपनी जानकारी की जांच करने के लिए, इन होमवर्क असाइनमेंट का इस्तेमाल करें.

इन सवालों के जवाब दें

पहला सवाल

शेप को ड्रॉ होने से रोकने के लिए, किस तरीके का इस्तेमाल किया जाता है?

excludeFromDrawing()

quickReject()

onDraw()

clipRect()

दूसरा सवाल

Canvas.save() और Canvas.restore() कौनसी जानकारी सेव करते हैं और उसे वापस लाते हैं?

▢ रंग, लाइन की चौड़ाई वगैरह.

▢ सिर्फ़ मौजूदा ट्रांसफ़ॉर्मेशन

▢ मौजूदा ट्रांसफ़ॉर्मेशन और क्लिपिंग क्षेत्र

▢ सिर्फ़ मौजूदा क्लिपिंग क्षेत्र

तीसरा सवाल

Paint.Align के बारे में यह जानकारी देता है:

▢ ड्राइंग की इन शेप को कैसे अलाइन करें

▢ टेक्स्ट को ओरिजनल कॉन्टेंट के किस हिस्से से लिया गया है

▢ क्लिपिंग क्षेत्र में यह कहां अलाइन किया गया है

▢ टेक्स्ट को मूल जगह के किस तरफ़ अलाइन करना है

इस कोर्स में मौजूद अन्य कोडलैब के लिंक के लिए, Advanced Android in Kotlin कोडलैब का लैंडिंग पेज देखें.