این کد لبه بخشی از دوره آموزشی Advanced Android in Kotlin است. اگر از طریق کدها به صورت متوالی کار کنید، بیشترین ارزش را از این دوره خواهید گرفت، اما اجباری نیست. همه کدهای دوره در صفحه فرود Android Advanced in Kotlin Codelabs فهرست شده اند.
مقدمه
برای هدف این نرم افزار کد، برش روشی برای تعریف مناطقی از یک تصویر، بوم یا بیت مپ است که به طور انتخابی روی صفحه کشیده شده یا کشیده نمی شوند. یکی از اهداف کلیپینگ کاهش اضافه برداشت است. Overdraw زمانی است که یک پیکسل روی صفحه نمایش بیش از یک بار برای نمایش تصویر نهایی کشیده می شود. وقتی اضافه برداشت را کاهش می دهید، تعداد دفعاتی که یک پیکسل یا ناحیه نمایشگر کشیده می شود را به حداقل می رسانید تا عملکرد طراحی را به حداکثر برسانید. همچنین می توانید از clipping برای ایجاد افکت های جالب در طراحی رابط کاربری و انیمیشن استفاده کنید.
به عنوان مثال، هنگامی که یک پشته از کارتهای روی هم را مطابق شکل زیر میکشید، بهجای کشیدن کامل هر کارت از پایین به بالا، معمولاً کشیدن فقط بخشهای قابل مشاهده کارآمدتر است. "معمولا"، زیرا عملیات برش هزینه ای نیز دارد و در کل سیستم اندروید بهینه سازی ترسیم زیادی را انجام می دهد.
برای اینکه فقط قسمتهای قابل مشاهده کارتها را بکشید، برای هر کارت یک ناحیه برش مشخص میکنید. به عنوان مثال در نمودار زیر، هنگامی که یک مستطیل برش بر روی یک تصویر اعمال می شود، تنها بخشی از داخل آن مستطیل نمایش داده می شود.
ناحیه برش معمولاً مستطیل است، اما می تواند هر شکل یا ترکیبی از اشکال، حتی متن باشد. همچنین میتوانید تعیین کنید که آیا میخواهید منطقه داخل منطقه برش گنجانده شود یا حذف شود. به عنوان مثال، می توانید یک منطقه برش دایره ای ایجاد کنید و فقط آنچه را که خارج از دایره است نمایش دهید.
در این کد لبه، شما می خواهید روش های مختلف برش را آزمایش کنید.
آنچه از قبل باید بدانید
باید با:
- نحوه ایجاد یک برنامه با
Activity
و اجرای آن با استفاده از Android Studio. - نحوه ایجاد و طراحی روی
Canvas
. - چگونه یک
View
سفارشی ایجاد کنیم، وonDraw()
وonSizeChanged()
را لغو کنیم.
چیزی که یاد خواهید گرفت
- نحوه برش دادن اشیا برای کشیدن روی
Canvas
. - نحوه ذخیره و بازیابی حالت های طراحی یک بوم.
- نحوه اعمال تبدیل به بوم و متن
کاری که خواهی کرد
- برنامهای ایجاد کنید که اشکال بریدهشده را روی صفحه میکشد و روشهای مختلف برش و نتیجه آن را بر روی دید آن اشکال نشان میدهد.
- همچنین مقداری متن ترجمه شده و اریب را ترسیم خواهید کرد.
برنامه ClippingExample نشان می دهد که چگونه می توانید از اشکال استفاده و ترکیب کنید تا مشخص کنید کدام بخش از بوم در یک نما نمایش داده می شود. برنامه نهایی شما مانند تصویر زیر خواهد بود.
شما میخواهید این برنامه را از ابتدا بسازید، بنابراین باید یک پروژه راهاندازی کنید، ابعاد و رشتهها را تعریف کنید و برخی از متغیرها را اعلام کنید.
مرحله 1: پروژه ClippingExample را ایجاد کنید
- با قالب Empty Activity یک پروژه Kotlin به نام
ClippingExample
ایجاد کنید. برای پیشوند نام بسته از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 Studio، روی پوشه مقادیر کلیک راست کرده و New > Values resource file را انتخاب کنید.
- در گفتگوی فایل منبع جدید ، فایل را
dimens
فراخوانی کنید. در واجد شرایط , Smallest Screen Width را انتخاب کنید و روی دکمه >> کلیک کنید تا آن را به واجد شرایط انتخاب شده اضافه کنید . 480 را در کادر Smallest screen width وارد کرده و روی OK کلیک کنید.
- فایل باید در پوشه مقادیر شما مانند شکل زیر نمایش داده شود.
- اگر نمیتوانید فایل را ببینید، به نمای Project Files برنامه بروید. مسیر کامل فایل جدید مطابق شکل زیر است:
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
برای طراحی تعریف کنید. Anti-aliasing را فعال کنید و از عرض خط و اندازه متن تعریف شده در ابعاد، مانند شکل زیر استفاده کنید.
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
( Antialiased : درمان لبهها با گرد کردن، زیرا ممکن است ضد نام مستعار باشند) یاBW
(سیاه-سفید: لبهها را فقط با گرد کردن به نزدیکترین مرز پیکسل درمان کنید) فقط برای گرد کردن به نزدیکترین پیکسل.
چندین نسخه از quickReject()
وجود دارد، و شما همچنین می توانید آنها را در اسناد پیدا کنید.
| quickReject |
| quickReject |
| quickReject |
در این تمرین میخواهید مانند قبل در یک ردیف جدید، زیر متن و داخل clipRect
کنید.
- شما ابتدا
quickReject()
را با یک مستطیلinClipRectangle
می کنید که باclipRect
همپوشانی دارد. بنابراینquickReject()
false را برمی گرداند،clipRect
باBLACK
پر می شود و مستطیلinClipRectangle
رسم می شود.
- سپس کد را تغییر دهید و
quickReject()
را باnotInClipRectangle
کنید.quickReject()
اکنون true را برمی گرداند و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
به شما امکان می دهد بررسی کنید که آیا یک مستطیل یا مسیر مشخص شده کاملاً خارج از مناطق قابل مشاهده فعلی قرار دارد یا خیر.
دوره بی ادبی:
مستندات توسعه دهنده اندروید:
- کلاس
Canvas
- کلاس
Bitmap
-
View
کلاس - کلاس
Paint
- تنظیمات
Bitmap.config
- اپراتورهای
Region.Op
- کلاس
Path
- کلاس
Canvas
- کلاس
Bitmap
-
View
کلاس - کلاس
Paint
- تنظیمات
Bitmap.config
- اپراتورهای
Region.Op
- کلاس
Path
- ابزارهای گرافیکی
android.graphics
-
Bitmap.Config
تنظیماتCanvas
- بوم و نقاشی
- ()canvas.translate چه کاری انجام می دهد
- درک save() و restore() برای زمینه Canvas
- بریدن
- اضافه برداشت .
-
@JvmOverloads
همچنین سری مقالات معماری گرافیک را برای توضیح عمیق در مورد چگونگی ترسیم فریمورک اندروید روی صفحه ببینید.
این بخش، تکالیف احتمالی را برای دانشآموزانی که در این آزمایشگاه کد به عنوان بخشی از دورهای که توسط یک مربی هدایت میشود، فهرست میکند. این وظیفه مربی است که موارد زیر را انجام دهد:
- در صورت نیاز تکالیف را تعیین کنید.
- نحوه ارسال تکالیف را با دانش آموزان در میان بگذارید.
- تکالیف را درجه بندی کنید.
مربیان میتوانند از این پیشنهادات به اندازهای که میخواهند استفاده کنند، و باید با خیال راحت هر تکلیف دیگری را که فکر میکنند مناسب است به آنها اختصاص دهند.
اگر به تنهایی بر روی این کدها کار می کنید، از این تکالیف برای آزمایش دانش خود استفاده کنید.
یه این سوالات پاسخ دهید
سوال 1
چه روشی را برای حذف کارآمد اشکال از ترسیم میخواهید؟
▢ excludeFromDrawing()
▢ quickReject()
▢ onDraw()
▢ clipRect()
سوال 2
Canvas.save()
و Canvas.restore()
کدام اطلاعات را ذخیره و بازیابی می کنند؟
▢ رنگ، عرض خط و غیره
▢ فقط تحولات فعلی
▢ تحولات فعلی و منطقه برش
▢ فقط منطقه برش فعلی
سوال 3
Paint.Align
مشخص می کند:
▢ نحوه تراز کردن اشکال طراحی زیر
▢ متن از کدام طرف مبدأ گرفته شده است
▢ جایی که در ناحیه برش تراز شده است
▢ کدام سمت متن را با مبدا تراز کنید
برای پیوند به دیگر کدلب ها در این دوره، صفحه فرود Advanced Android in Kotlin Codelabs را ببینید.