ایجاد نماهای سفارشی

این کد لبه بخشی از دوره Advanced Android in Kotlin است. اگر از طریق کدها به ترتیب کار کنید، بیشترین ارزش را از این دوره خواهید گرفت، اما اجباری نیست. همه کدهای دوره در صفحه فرود Android Advanced in Kotlin Codelabs فهرست شده اند.

مقدمه

Android مجموعه بزرگی از زیر کلاس‌های View ارائه می‌کند، مانند Button ، TextView ، EditText ، ImageView ، CheckBox یا RadioButton . می‌توانید از این زیر کلاس‌ها برای ایجاد رابط کاربری استفاده کنید که تعامل کاربر را فعال می‌کند و اطلاعات را در برنامه شما نمایش می‌دهد. اگر هیچ یک از زیر کلاس های View نیازهای شما را برآورده نکرد، می توانید یک زیر کلاس View به نام نمای سفارشی ایجاد کنید.

برای ایجاد یک نمای سفارشی، می‌توانید یک زیرکلاس View موجود را گسترش دهید (مانند یک Button یا EditText )، یا زیرکلاس View خود را ایجاد کنید. با گسترش مستقیم View ، می‌توانید یک عنصر رابط کاربری تعاملی با هر اندازه و شکلی با نادیده گرفتن متد onDraw() برای View ایجاد کنید تا آن را ترسیم کند.

پس از ایجاد نمای سفارشی، می‌توانید آن را به همان روشی که TextView یا Button اضافه می‌کنید، به طرح‌بندی‌های فعالیت خود اضافه کنید.

این درس به شما نشان می‌دهد که چگونه با گسترش View یک نمای سفارشی از ابتدا ایجاد کنید.

آنچه از قبل باید بدانید

  • نحوه ایجاد یک برنامه با Activity و اجرای آن با استفاده از Android Studio.

چیزی که یاد خواهید گرفت

  • نحوه گسترش View برای ایجاد نمای سفارشی
  • نحوه ترسیم نمای سفارشی که به شکل دایره است.
  • نحوه استفاده از شنوندگان برای مدیریت تعامل کاربر با نمای سفارشی.
  • نحوه استفاده از نمای سفارشی در یک طرح.

کاری که خواهی کرد

  • برای ایجاد نمای سفارشی، View گسترش دهید.
  • نمای سفارشی را با مقادیر طراحی و نقاشی راه‌اندازی کنید.
  • برای ترسیم نما، onDraw() را لغو کنید.
  • از شنوندگان برای ارائه رفتار نمای سفارشی استفاده کنید.
  • نمای سفارشی را به یک طرح اضافه کنید.

برنامه CustomFanController نحوه ایجاد یک زیرکلاس نمای سفارشی را با گسترش کلاس View نشان می دهد. زیر کلاس جدید DialView نام دارد.

این برنامه یک عنصر رابط کاربری دایره‌ای را نشان می‌دهد که شبیه یک کنترل فیزیکی فن است، با تنظیمات خاموش (0)، کم (1)، متوسط (2) و زیاد (3). وقتی کاربر روی نما ضربه می زند، نشانگر انتخاب به موقعیت بعدی می رود: 0-1-2-3، و به 0 برمی گردد. همچنین، اگر انتخاب 1 یا بالاتر باشد، رنگ پس زمینه قسمت دایره ای نما از خاکستری به سبز تغییر می کند (نشان دهنده روشن بودن برق فن).

نماها بلوک‌های اصلی رابط کاربری یک برنامه هستند. کلاس View زیر کلاس‌های زیادی را ارائه می‌کند که به ویجت‌های UI گفته می‌شود، که بسیاری از نیازهای رابط کاربری معمولی یک برنامه Android را پوشش می‌دهند.

بلوک های سازنده رابط کاربری مانند Button و TextView زیر کلاس هایی هستند که کلاس View را گسترش می دهند. برای صرفه جویی در زمان و تلاش توسعه، می توانید یکی از این زیر کلاس های View گسترش دهید. نمای سفارشی ظاهر و رفتار والد خود را به ارث می برد و می توانید رفتار یا جنبه ظاهری را که می خواهید تغییر دهید نادیده بگیرید. به عنوان مثال، اگر EditText برای ایجاد یک نمای سفارشی گسترش دهید، نما درست مانند نمای EditText عمل می کند، اما همچنین می تواند برای نشان دادن دکمه X که متن را از قسمت ورودی متن پاک می کند، سفارشی شود.

می‌توانید هر زیرکلاس View ، مانند EditText را گسترش دهید تا یک نمای سفارشی دریافت کنید— نزدیک‌ترین مورد را به آنچه می‌خواهید انجام دهید، انتخاب کنید. سپس می‌توانید از نمای سفارشی مانند هر زیرکلاس View در یک یا چند طرح‌بندی به عنوان یک عنصر XML با ویژگی‌ها استفاده کنید.

برای ایجاد نمای سفارشی خود از ابتدا، خود کلاس View گسترش دهید. کد شما روش‌های View را لغو می‌کند تا ظاهر و عملکرد نما را مشخص کند. کلید ایجاد نمای سفارشی خود این است که شما مسئول ترسیم کل عنصر UI با هر اندازه و شکلی روی صفحه هستید. اگر یک نمای موجود مانند Button را زیرکلاس کنید، آن کلاس طراحی را برای شما انجام می دهد. (شما بعداً در این کدنویسی درباره طراحی بیشتر خواهید آموخت.)

برای ایجاد یک نمای سفارشی این مراحل کلی را دنبال کنید:

  • یک کلاس نمای سفارشی ایجاد کنید که View را گسترش دهد یا یک زیرکلاس View را گسترش دهد (مانند Button یا EditText ).
  • اگر یک زیرکلاس View موجود را گسترش دهید، فقط رفتار یا جنبه‌هایی از ظاهری را که می‌خواهید تغییر دهید لغو کنید.
  • اگر کلاس View را گسترش دهید، شکل نمای سفارشی را ترسیم کنید و ظاهر آن را با نادیده گرفتن متدهای View مانند onDraw() و onMeasure() در کلاس جدید کنترل کنید.
  • برای پاسخ به تعامل کاربر کد اضافه کنید و در صورت لزوم نمای سفارشی را دوباره ترسیم کنید.
  • از کلاس view سفارشی به عنوان ویجت UI در طرح XML فعالیت خود استفاده کنید. همچنین می‌توانید ویژگی‌های سفارشی را برای نما تعریف کنید تا سفارشی‌سازی نمای در طرح‌بندی‌های مختلف ارائه شود.

در این کار شما:

  • یک برنامه با ImageView به عنوان یک مکان نگهدار موقت برای نمای سفارشی ایجاد کنید.
  • برای ایجاد نمای سفارشی، View گسترش دهید.
  • نمای سفارشی را با مقادیر طراحی و نقاشی راه‌اندازی کنید.

مرحله 1: یک برنامه با نگهدارنده ImageView ایجاد کنید

  1. با استفاده از قالب Empty Activity یک برنامه Kotlin با عنوان CustomFanController ایجاد کنید. مطمئن شوید که نام بسته com.example.android.customfancontroller باشد.
  2. برای ویرایش کد XML، activity_main.xml در تب Text باز کنید.
  3. TextView موجود را با این کد جایگزین کنید. این متن به عنوان یک برچسب در فعالیت برای نمای سفارشی عمل می کند.
<TextView
       android:id="@+id/customViewLabel"
       android:textAppearance="@style/Base.TextAppearance.AppCompat.Display3"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:padding="16dp"
       android:textColor="@android:color/black"
       android:layout_marginStart="8dp"
       android:layout_marginEnd="8dp"
       android:layout_marginTop="24dp"
       android:text="Fan Control"
       app:layout_constraintLeft_toLeftOf="parent"
       app:layout_constraintRight_toRightOf="parent"
       app:layout_constraintTop_toTopOf="parent"/>
  1. این عنصر ImageView به طرح اضافه کنید. این یک مکان نگهدار برای نمای سفارشی است که در این کد لبه ایجاد خواهید کرد.
<ImageView
       android:id="@+id/dialView"
       android:layout_width="200dp"
       android:layout_height="200dp"
       android:background="@android:color/darker_gray"
       app:layout_constraintTop_toBottomOf="@+id/customViewLabel"
       app:layout_constraintLeft_toLeftOf="parent"
       app:layout_constraintRight_toRightOf="parent"
       android:layout_marginLeft="8dp"
       android:layout_marginRight="8dp"
       android:layout_marginTop="8dp"/>
  1. استخراج منابع رشته و ابعاد در هر دو عنصر UI.
  2. روی تب Design کلیک کنید. طرح باید به شکل زیر باشد:

مرحله 2. کلاس view سفارشی خود را ایجاد کنید

  1. یک کلاس Kotlin جدید به نام DialView ایجاد کنید.
  2. تعریف کلاس را برای گسترش View تغییر دهید. وقتی از شما خواسته شد android.view.View را وارد کنید.
  3. روی View کلیک کنید و سپس روی لامپ قرمز کلیک کنید. افزودن سازندگان نمای Android با استفاده از «@JvmOverloads» را انتخاب کنید. Android Studio سازنده را از کلاس View اضافه می کند. حاشیه نویسی @JvmOverloads به کامپایلر Kotlin دستور می دهد تا برای این تابع اضافه بارهایی ایجاد کند که جایگزین مقادیر پارامترهای پیش فرض می شود.
class DialView @JvmOverloads constructor(
   context: Context,
   attrs: AttributeSet? = null,
   defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {
  1. بالای تعریف کلاس DialView ، درست در زیر importها، یک enum سطح بالا برای نمایش سرعت فن موجود اضافه کنید. توجه داشته باشید که این enum از نوع Int است زیرا مقادیر به جای رشته های واقعی منابع رشته ای هستند. Android Studio در هر یک از این مقادیر، خطاهایی را برای منابع رشته از دست رفته نشان می دهد. در مرحله بعد آن را برطرف خواهید کرد.
private enum class FanSpeed(val label: Int) {
   OFF(R.string.fan_off),
   LOW(R.string.fan_low),
   MEDIUM(R.string.fan_medium),
   HIGH(R.string.fan_high);
}
  1. در زیر enum ، این ثابت ها را اضافه کنید. شما از اینها به عنوان بخشی از ترسیم نشانگرها و برچسب های شماره گیری استفاده خواهید کرد.
private const val RADIUS_OFFSET_LABEL = 30      
private const val RADIUS_OFFSET_INDICATOR = -35
  1. در داخل کلاس DialView ، چندین متغیر مورد نیاز را برای ترسیم نمای سفارشی تعریف کنید. در صورت درخواست android.graphics.PointF را وارد کنید.
private var radius = 0.0f                   // Radius of the circle.
private var fanSpeed = FanSpeed.OFF         // The active selection.
// position variable which will be used to draw label and indicator circle position
private val pointPosition: PointF = PointF(0.0f, 0.0f)
  • شعاع شعاع فعلی دایره است radius این مقدار زمانی تنظیم می شود که نما روی صفحه نمایش داده می شود.
  • fanSpeed سرعت فعلی فن است که یکی از مقادیر fanSpeed شمارش FanSpeed است. به طور پیش فرض آن مقدار OFF است.
  • در نهایت postPosition یک نقطه X,Y است که برای ترسیم چندین عنصر نمای روی صفحه استفاده می شود.

این مقادیر به جای زمانی که نما واقعاً ترسیم می شود، در اینجا ایجاد و مقداردهی اولیه می شوند تا اطمینان حاصل شود که مرحله طراحی واقعی با بیشترین سرعت ممکن اجرا می شود.

  1. همچنین در داخل تعریف کلاس DialView ، یک شی Paint با تعداد انگشت شماری از سبک های اولیه مقداردهی اولیه کنید. android.graphics.Paint و android.graphics.Typeface را در صورت درخواست وارد کنید. همانطور که قبلا در مورد متغیرها، این سبک ها در اینجا مقداردهی اولیه می شوند تا به سرعت بخشیدن به مرحله ترسیم کمک کنند.
private val paint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
   style = Paint.Style.FILL
   textAlign = Paint.Align.CENTER
   textSize = 55.0f
   typeface = Typeface.create( "", Typeface.BOLD)
}
  1. res/values/strings.xml را باز کنید و منابع رشته را برای سرعت فن اضافه کنید:
<string name="fan_off">off</string>
<string name="fan_low">1</string>
<string name="fan_medium">2</string>
<string name="fan_high">3</string>

هنگامی که یک نمای سفارشی ایجاد کردید، باید بتوانید آن را ترسیم کنید. هنگامی که یک زیر کلاس View مانند EditText را گسترش می دهید، آن زیر کلاس ظاهر و ویژگی های نما را تعریف می کند و خود را روی صفحه می کشد. در نتیجه، برای ترسیم نما نیازی به نوشتن کد ندارید. می‌توانید روش‌های والد را لغو کنید تا نمای خود را سفارشی کنید.

اگر نمای خود را از ابتدا ایجاد می‌کنید (با گسترش View )، شما مسئول ترسیم کل نما در هر بار بازخوانی صفحه، و نادیده گرفتن روش‌های View هستید که طراحی را انجام می‌دهند. برای ترسیم درست نمای سفارشی که View را گسترش می دهد، باید:

  • اندازه نما را زمانی که برای اولین بار ظاهر می شود، و هر بار که اندازه آن نما تغییر می کند، با نادیده گرفتن متد onSizeChanged() محاسبه کنید.
  • برای ترسیم نمای سفارشی، با استفاده از یک شی Canvas که توسط یک شی Paint استایل شده است، متد onDraw() را لغو کنید.
  • زمانی که به کلیک کاربر پاسخ می‌دهد، متد invalidate() را فراخوانی کنید که نحوه ترسیم نما را تغییر می‌دهد تا کل view را باطل کند، در نتیجه فراخوانی به onDraw() مجبور می‌شود تا نمای را دوباره ترسیم کند.

متد onDraw() هر بار که صفحه رفرش می شود فراخوانی می شود که می تواند چندین بار در ثانیه باشد. به دلایل عملکرد و برای جلوگیری از اشکالات بصری، باید تا حد امکان در onDraw() کمتر کار کنید. به ویژه، تخصیص ها را در onDraw() قرار ندهید، زیرا تخصیص ها ممکن است منجر به جمع آوری زباله شود که ممکن است باعث ایجاد لکنت بصری شود.

کلاس های Canvas و Paint تعدادی میانبر طراحی مفید را ارائه می دهند:

  • با استفاده از drawText() متن را ترسیم کنید. فونت را با فراخوانی setTypeface() و رنگ متن را با فراخوانی setColor() مشخص کنید.
  • اشکال ابتدایی را با استفاده از drawRect() ، drawOval() و drawArc() رسم کنید. با فراخوانی setStyle() پر شده، مشخص شده یا هر دو شکل را تغییر دهید.
  • نقشه های بیت را با استفاده از drawBitmap() بکشید.

در کدهای بعدی درباره Canvas and Paint بیشتر خواهید آموخت. برای کسب اطلاعات بیشتر درباره نحوه ترسیم نماها توسط Android، به نحوه ترسیم نماها توسط Android مراجعه کنید.

در این کار، نمای سفارشی کنترل‌کننده فن را روی صفحه می‌کشید - خود شماره‌گیر، نشانگر موقعیت فعلی و برچسب‌های نشانگر - با روش‌های onSizeChanged() و onDraw() . شما همچنین یک روش کمکی، computeXYForSpeed(), برای محاسبه موقعیت X,Y فعلی برچسب نشانگر روی صفحه ایجاد خواهید کرد.

مرحله 1. موقعیت ها را محاسبه کنید و نما را بکشید

  1. در کلاس DialView ، در زیر مقادیر اولیه، متد onSizeChanged() را از کلاس View برای محاسبه اندازه شماره گیری نمای سفارشی نادیده بگیرید. واردات kotlin math.min در صورت درخواست

    متد onSizeChanged() هر زمان که اندازه نما تغییر می کند، از جمله اولین باری که در هنگام افزایش طرح بندی ترسیم می شود، نامیده می شود. برای محاسبه موقعیت‌ها، ابعاد و هر مقدار دیگر مربوط به اندازه نمای سفارشی‌تان، به جای اینکه هر بار که ترسیم می‌کنید، آن‌ها را دوباره محاسبه کنید onSizeChanged() لغو کنید. در این حالت شما از onSizeChanged() برای محاسبه شعاع فعلی عنصر دایره شماره گیری استفاده می کنید.
override fun onSizeChanged(width: Int, height: Int, oldWidth: Int, oldHeight: Int) {
   radius = (min(width, height) / 2.0 * 0.8).toFloat()
}
  1. در زیر onSizeChanged() این کد را اضافه کنید تا یک تابع پسوند computeXYForSpeed() برای PointF تعریف کنید. کلاس در صورت درخواست kotlin.math.cos و kotlin.math.sin را وارد کنید. این تابع افزودنی در کلاس PointF مختصات X، Y را روی صفحه برای برچسب متن و نشانگر جریان (0، 1، 2 یا 3) با توجه به موقعیت FanSpeed و شعاع شماره گیری فعلی محاسبه می کند. شما از این در onDraw().
private fun PointF.computeXYForSpeed(pos: FanSpeed, radius: Float) {
   // Angles are in radians.
   val startAngle = Math.PI * (9 / 8.0)   
   val angle = startAngle + pos.ordinal * (Math.PI / 4)
   x = (radius * cos(angle)).toFloat() + width / 2
   y = (radius * sin(angle)).toFloat() + height / 2
}
  1. متد onDraw() را لغو کنید تا نمای روی صفحه را با کلاس‌های Canvas و Paint ارائه کنید. در صورت درخواست android.graphics.Canvas را وارد کنید. این نادیده گرفتن اسکلت است:
override fun onDraw(canvas: Canvas) {
   super.onDraw(canvas)
   
}
  1. در داخل onDraw() ، این خط را اضافه کنید تا بسته به اینکه سرعت فن OFF باشد یا هر مقدار دیگری، رنگ رنگ را به خاکستری ( Color.GRAY ) یا سبز ( Color.GREEN ) تنظیم کنید. در صورت درخواست android.graphics.Color را وارد کنید.
// Set dial background color to green if selection not off.
paint.color = if (fanSpeed == FanSpeed.OFF) Color.GRAY else Color.GREEN
  1. با استفاده از متد drawCircle() این کد را برای رسم دایره برای شماره گیری اضافه کنید. این روش از عرض و ارتفاع نمای فعلی برای یافتن مرکز دایره، شعاع دایره و رنگ فعلی استفاده می کند. ویژگی های width و height اعضای سوپرکلاس View هستند و ابعاد فعلی نما را نشان می دهند.
// Draw the dial.
canvas.drawCircle((width / 2).toFloat(), (height / 2).toFloat(), radius, paint)
  1. این کد زیر را اضافه کنید تا یک دایره کوچکتر برای علامت نشانگر سرعت فن ترسیم کنید، همچنین با متد drawCircle() این قسمت از PointF استفاده می کند. روش توسعه computeXYforSpeed() برای محاسبه مختصات X,Y برای مرکز نشانگر بر اساس سرعت فن فعلی.
// Draw the indicator circle.
val markerRadius = radius + RADIUS_OFFSET_INDICATOR
pointPosition.computeXYForSpeed(fanSpeed, markerRadius)
paint.color = Color.BLACK
canvas.drawCircle(pointPosition.x, pointPosition.y, radius/12, paint)
  1. در نهایت، برچسب های سرعت فن (0، 1، 2، 3) را در موقعیت های مناسب اطراف صفحه بکشید. این قسمت از متد دوباره PointF.computeXYForSpeed() را فراخوانی می کند تا موقعیت هر برچسب را بدست آورد و هر بار از شی pointPosition برای جلوگیری از تخصیص مجدد استفاده می کند. برای رسم برچسب ها drawText() استفاده کنید.
// Draw the text labels.
val labelRadius = radius + RADIUS_OFFSET_LABEL
for (i in FanSpeed.values()) {
   pointPosition.computeXYForSpeed(i, labelRadius)
   val label = resources.getString(i.label)
   canvas.drawText(label, pointPosition.x, pointPosition.y, paint)
}

متد تکمیل شده onDraw() به شکل زیر است:

override fun onDraw(canvas: Canvas) {
   super.onDraw(canvas)
   // Set dial background color to green if selection not off.
   paint.color = if (fanSpeed == FanSpeed.OFF) Color.GRAY else Color.GREEN
   // Draw the dial.
   canvas.drawCircle((width / 2).toFloat(), (height / 2).toFloat(), radius, paint)
   // Draw the indicator circle.
   val markerRadius = radius + RADIUS_OFFSET_INDICATOR
   pointPosition.computeXYForSpeed(fanSpeed, markerRadius)
   paint.color = Color.BLACK
   canvas.drawCircle(pointPosition.x, pointPosition.y, radius/12, paint)
   // Draw the text labels.
   val labelRadius = radius + RADIUS_OFFSET_LABEL
   for (i in FanSpeed.values()) {
       pointPosition.computeXYForSpeed(i, labelRadius)
       val label = resources.getString(i.label)
       canvas.drawText(label, pointPosition.x, pointPosition.y, paint)
   }
}

مرحله 2. نمای را به طرح‌بندی اضافه کنید

برای افزودن نمای سفارشی به رابط کاربری برنامه، آن را به عنوان عنصری در طرح XML فعالیت مشخص می‌کنید. ظاهر و رفتار آن را با ویژگی های عنصر XML کنترل کنید، همانطور که برای هر عنصر UI دیگر انجام می دهید.

  1. در activity_main.xml ، تگ ImageView برای dialView را به com.example.android.customfancontroller.DialView تغییر دهید و ویژگی android:background حذف کنید. هم DialView و هم ImageView اصلی ویژگی های استاندارد را از کلاس View به ارث می برند، بنابراین نیازی به تغییر هیچ یک از ویژگی های دیگر نیست. عنصر DialView جدید به شکل زیر است:
<com.example.android.customfancontroller.DialView
       android:id="@+id/dialView"
       android:layout_width="@dimen/fan_dimen"
       android:layout_height="@dimen/fan_dimen"
       app:layout_constraintTop_toBottomOf="@+id/customViewLabel"
       app:layout_constraintLeft_toLeftOf="parent"
       app:layout_constraintRight_toRightOf="parent"
       android:layout_marginLeft="@dimen/default_margin"
       android:layout_marginRight="@dimen/default_margin"
       android:layout_marginTop="@dimen/default_margin" />
  1. برنامه را اجرا کنید. نمای کنترل فن شما در فعالیت ظاهر می شود.

وظیفه نهایی این است که نمای سفارشی خود را فعال کنید تا زمانی که کاربر روی نما ضربه می زند، عملی را انجام دهد. هر ضربه باید نشانگر انتخاب را به موقعیت بعدی منتقل کند: خاموش-1-2-3 و برگشت به خاموش. همچنین، اگر انتخاب 1 یا بالاتر است، پس‌زمینه را از خاکستری به سبز تغییر دهید، که نشان می‌دهد برق فن روشن است.

برای اینکه نمای سفارشی خود را فعال کنید تا قابل کلیک باشد، شما:

  • ویژگی isClickable view را روی true تنظیم کنید. این نمای سفارشی شما را قادر می‌سازد تا به کلیک‌ها پاسخ دهد.
  • برای اجرای عملیات زمانی که روی view کلیک می شود، performClick () کلاس View را پیاده سازی کنید.
  • متد invalidate() را فراخوانی کنید. این به سیستم اندروید می‌گوید که متد onDraw() را برای ترسیم مجدد view فراخوانی کند.

به طور معمول، با یک نمای استاندارد اندروید، OnClickListener() را پیاده سازی می کنید تا زمانی که کاربر روی آن نمای کلیک می کند یک عمل انجام دهد. برای نمای سفارشی، به جای آن، متد performClick () کلاس View را پیاده سازی کرده و super را فراخوانی می کنید. performClick(). روش پیش‌فرض performClick() نیز onClickListener() فراخوانی می‌کند، بنابراین می‌توانید اقدامات خود را به performClick() اضافه کنید و onClickListener() برای سفارشی‌سازی بیشتر توسط شما یا توسعه‌دهندگان دیگری که ممکن است از نمای سفارشی شما استفاده کنند، در دسترس بگذارید.

  1. در DialView.kt ، در داخل شمارش FanSpeed ، یک تابع افزونه next() اضافه کنید که سرعت فن فعلی را به سرعت بعدی در لیست تغییر می دهد (از OFF به LOW ، MEDIUM ، و HIGH ، و سپس به OFF برمی گردد). اکنون شمارش کامل به این صورت است:
private enum class FanSpeed(val label: Int) {
   OFF(R.string.fan_off),
   LOW(R.string.fan_low),
   MEDIUM(R.string.fan_medium),
   HIGH(R.string.fan_high);

   fun next() = when (this) {
       OFF -> LOW
       LOW -> MEDIUM
       MEDIUM -> HIGH
       HIGH -> OFF
   }
}
  1. در داخل کلاس DialView ، درست قبل از متد onSizeChanged() یک بلوک init() اضافه کنید. تنظیم ویژگی isClickable view روی true، این نما را قادر می سازد تا ورودی کاربر را بپذیرد.
init {
   isClickable = true
}
  1. در زیر init(), متد performClick() با کد زیر لغو کنید.
override fun performClick(): Boolean {
   if (super.performClick()) return true

   fanSpeed = fanSpeed.next()
   contentDescription = resources.getString(fanSpeed.label)
  
   invalidate()
   return true
}

فراخوان super . performClick() باید ابتدا اتفاق بیفتد، که رویدادهای دسترسی و همچنین فراخوانی onClickListener() فعال می کند.

دو خط بعدی سرعت فن را با متد next() افزایش می دهد و توضیحات محتوای view را روی منبع رشته ای تنظیم می کند که سرعت فعلی را نشان می دهد (خاموش، 1، 2 یا 3).

در نهایت، متد invalidate() کل view را باطل می‌کند و یک فراخوانی به onDraw() برای ترسیم مجدد view را مجبور می‌کند. اگر چیزی در نمای سفارشی شما به هر دلیلی از جمله تعامل کاربر تغییر کرد و نیاز به نمایش آن باشد، invalidate().

  1. برنامه را اجرا کنید. روی عنصر DialView ضربه بزنید تا نشانگر از حالت خاموش به 1 منتقل شود. صفحه باید سبز شود. با هر ضربه، نشانگر باید به موقعیت بعدی حرکت کند. وقتی نشانگر خاموش شد، صفحه باید دوباره خاکستری شود.

این مثال مکانیزم های اساسی استفاده از ویژگی های سفارشی با نمای سفارشی شما را نشان می دهد. شما ویژگی های سفارشی را برای کلاس DialView با رنگ های مختلف برای هر موقعیت شماره گیری فن تعریف می کنید.

  1. res/values/attrs.xml را ایجاد و باز کنید.
  2. در داخل <resources> ، یک عنصر منبع <declare-styleable> اضافه کنید.
  3. در داخل عنصر منبع <declare-styleable> ، سه عنصر attr ، یکی برای هر ویژگی، با name و format اضافه کنید. format مانند یک نوع است و در این مورد color است.
<?xml version="1.0" encoding="utf-8"?>
<resources>
       <declare-styleable name="DialView">
           <attr name="fanColor1" format="color" />
           <attr name="fanColor2" format="color" />
           <attr name="fanColor3" format="color" />
       </declare-styleable>
</resources>
  1. فایل layout activity_main.xml را باز کنید.
  2. در DialView ، ویژگی‌هایی را برای fanColor1 ، fanColor2 و fanColor3 اضافه کنید و مقادیر آنها را روی رنگ‌های نشان‌داده شده در زیر تنظیم کنید. از app: به عنوان پیشگفتار برای ویژگی سفارشی (مانند app:fanColor1 ) به جای android: زیرا ویژگی های سفارشی شما به جای نام android به فضای نام schemas.android.com/apk/res/ your_app_package_name تعلق دارد.
app:fanColor1="#FFEB3B"
app:fanColor2="#CDDC39"
app:fanColor3="#009688"

برای استفاده از ویژگی ها در کلاس DialView ، باید آنها را بازیابی کنید. آنها در یک AttributeSet ذخیره می شوند که در صورت وجود، پس از ایجاد به کلاس شما تحویل داده می شود. شما ویژگی‌ها را در init بازیابی می‌کنید و مقادیر مشخصه‌ها را برای ذخیره‌سازی به متغیرهای محلی اختصاص می‌دهید.

  1. فایل کلاس DialView.kt را باز کنید.
  2. در داخل DialView ، متغیرها را برای ذخیره مقادیر مشخصه ها اعلام کنید.
private var fanSpeedLowColor = 0
private var fanSpeedMediumColor = 0
private var fanSeedMaxColor = 0
  1. در بلوک init ، کد زیر را با استفاده از تابع افزونه withStyledAttributes اضافه کنید. شما ویژگی ها و view را ارائه می کنید و متغیرهای محلی خود را تنظیم می کنید. وارد کردن withStyledAttributes تابع getColor() مناسب را نیز وارد می کند.
context.withStyledAttributes(attrs, R.styleable.DialView) {
   fanSpeedLowColor = getColor(R.styleable.DialView_fanColor1, 0)
   fanSpeedMediumColor = getColor(R.styleable.DialView_fanColor2, 0)
   fanSeedMaxColor = getColor(R.styleable.DialView_fanColor3, 0)
}
  1. از متغیرهای محلی در onDraw() برای تنظیم رنگ شماره گیری بر اساس سرعت فن فعلی استفاده کنید. خطی را که رنگ رنگ تنظیم شده است ( paint . color = if ( fanSpeed == FanSpeed. OFF ) Color. GRAY else Color. GREEN ) با کد زیر جایگزین کنید.
paint.color = when (fanSpeed) {
   FanSpeed.OFF -> Color.GRAY
   FanSpeed.LOW -> fanSpeedLowColor
   FanSpeed.MEDIUM -> fanSpeedMediumColor
   FanSpeed.HIGH -> fanSeedMaxColor
} as Int
  1. برنامه خود را اجرا کنید، روی شماره گیری کلیک کنید، و تنظیمات رنگ باید برای هر موقعیت متفاوت باشد، همانطور که در زیر نشان داده شده است.

برای کسب اطلاعات بیشتر درباره ویژگی‌های نمای سفارشی، به ایجاد کلاس مشاهده مراجعه کنید.

دسترس‌پذیری مجموعه‌ای از تکنیک‌های طراحی، پیاده‌سازی و آزمایش است که برنامه شما را قادر می‌سازد برای همه، از جمله افراد دارای معلولیت، قابل استفاده باشد.

ناتوانی‌های رایجی که می‌تواند استفاده فرد از دستگاه اندرویدی را تحت تأثیر قرار دهد شامل نابینایی، کم‌بینایی، کوررنگی، ناشنوایی یا کاهش شنوایی و محدودیت‌های مهارت‌های حرکتی است. وقتی برنامه‌های خود را با در نظر گرفتن قابلیت دسترسی توسعه می‌دهید، تجربه کاربری را نه تنها برای کاربران دارای این ناتوانی‌ها، بلکه برای همه کاربران دیگرتان نیز بهتر می‌کنید.

Android چندین ویژگی دسترس‌پذیری را به‌طور پیش‌فرض در نمای‌های رابط کاربری استاندارد مانند TextView و Button فراهم می‌کند. با این حال، هنگامی که یک نمای سفارشی ایجاد می‌کنید، باید در نظر بگیرید که چگونه آن نمای سفارشی ویژگی‌های قابل دسترس مانند توضیحات گفتاری محتوای روی صفحه را ارائه می‌کند.

در این کار با TalkBack، صفحه‌خوان Android، آشنا می‌شوید و برنامه‌تان را طوری تغییر می‌دهید که نکات و توضیحات قابل‌گفتنی را برای نمای سفارشی DialView شامل شود.

مرحله 1. TalkBack را کاوش کنید

TalkBack صفحه‌خوان داخلی اندروید است. با فعال کردن TalkBack، کاربر می‌تواند بدون دیدن صفحه با دستگاه Android خود تعامل داشته باشد، زیرا Android عناصر صفحه را با صدای بلند توصیف می‌کند. کاربرانی که دارای اختلالات بینایی هستند ممکن است برای استفاده از برنامه شما به TalkBack متکی باشند.

در این کار، TalkBack را فعال می‌کنید تا بفهمد صفحه‌خوان‌ها چگونه کار می‌کنند و چگونه برنامه‌ها را پیمایش کنید.

  1. در دستگاه یا شبیه‌ساز Android، به تنظیمات > دسترس‌پذیری > TalkBack بروید.
  2. برای روشن کردن TalkBack روی دکمه روشن/خاموش ضربه بزنید.
  3. برای تایید مجوزها روی OK ضربه بزنید.
  4. در صورت درخواست رمز عبور دستگاه خود را تأیید کنید. اگر این اولین باری است که TalkBack را اجرا می کنید، یک آموزش راه اندازی می شود. (این آموزش ممکن است در دستگاه های قدیمی تر در دسترس نباشد.)
  5. ممکن است مفید باشد که آموزش را با چشمان بسته هدایت کنید. برای باز کردن مجدد آموزش در آینده، به تنظیمات > دسترسی > TalkBack > تنظیمات > راه اندازی آموزش TalkBack بروید.
  6. برنامه CustomFanController را کامپایل و اجرا کنید، یا آن را با دکمه Overview یا Recents در دستگاه خود باز کنید. با روشن بودن TalkBack، توجه کنید که نام برنامه و همچنین متن برچسب TextView ("کنترل فن") اعلام شده است. با این حال، اگر روی خود نمای DialView ضربه بزنید، هیچ اطلاعاتی در مورد وضعیت نما (تنظیم فعلی برای شماره گیری) یا عملکردی که هنگام ضربه زدن روی نما برای فعال کردن آن انجام می شود، بیان نمی شود.

مرحله 2. توضیحات محتوا را برای برچسب های شماره گیری اضافه کنید

توضیحات محتوا معنی و هدف نماها را در برنامه شما توصیف می کند. این برچسب‌ها به خوانندگان صفحه مانند ویژگی TalkBack Android اجازه می‌دهند تا عملکرد هر عنصر را به طور دقیق توضیح دهند. برای نماهای ثابت مانند ImageView ، می‌توانید توضیحات محتوا را با ویژگی contentDescription به نمای فایل طرح‌بندی اضافه کنید. نماهای متن ( TextView و EditText ) به طور خودکار از متن موجود در نمای به عنوان توضیحات محتوا استفاده می کنند.

برای نمای کنترل پنکه سفارشی، باید هر بار که روی نما کلیک می‌شود، توضیحات محتوا را به‌صورت پویا به‌روزرسانی کنید تا تنظیمات فعلی فن نشان داده شود.

  1. در پایین کلاس DialView ، یک تابع updateContentDescription() بدون آرگومان یا نوع بازگشتی اعلام کنید.
fun updateContentDescription() {
}
  1. در داخل updateContentDescription() ، ویژگی contentDescription را برای نمای سفارشی به منبع رشته مرتبط با سرعت فعلی فن (خاموش، 1، 2 یا 3) تغییر دهید. اینها همان برچسب هایی هستند که در onDraw() زمانی که شماره گیری روی صفحه کشیده می شود استفاده می شود.
fun updateContentDescription() {
   contentDescription = resources.getString(fanSpeed.label)
}
  1. به بلوک init() بروید و در انتهای آن بلوک یک فراخوانی به updateContentDescription() اضافه کنید. هنگامی که نما مقدار دهی اولیه می شود، توضیحات محتوا را مقداردهی اولیه می کند.
init {
   isClickable = true
   // ...

   updateContentDescription()
}
  1. یک فراخوان دیگر به updateContentDescription() در متد performClick() درست قبل از invalidate() اضافه کنید.
override fun performClick(): Boolean {
   if (super.performClick()) return true
   fanSpeed = fanSpeed.next()
   updateContentDescription()
   invalidate()
   return true
}
  1. برنامه را کامپایل و اجرا کنید و مطمئن شوید TalkBack روشن است. برای تغییر تنظیمات نمای شماره گیری ضربه بزنید و توجه کنید که اکنون که TalkBack برچسب فعلی را اعلام می کند (خاموش، 1، 2، 3) و همچنین عبارت «برای فعال کردن، دو ضربه سریع بزنید».

مرحله 3. اطلاعات بیشتری را برای عمل کلیک اضافه کنید

می‌توانید در آنجا توقف کنید و نمای شما در TalkBack قابل استفاده باشد. اما اگر نمای شما بتواند نه تنها نشان دهد که می‌توان آن را فعال کرد («برای فعال‌سازی دو ضربه سریع بزنید») بلکه توضیح دهید که هنگام فعال شدن نما چه اتفاقی می‌افتد («برای تغییر، دو ضربه سریع بزنید.» یا «برای بازنشانی، دو ضربه بزنید.»)

برای انجام این کار، اطلاعات مربوط به عملکرد نما (در اینجا، یک عمل کلیک یا ضربه زدن) به یک شی اطلاعات گره دسترسی، از طریق یک نماینده دسترسی، اضافه می شود. یک نماینده دسترس‌پذیری به شما امکان می‌دهد ویژگی‌های مرتبط با دسترسی برنامه خود را از طریق ترکیب (به جای ارثی) سفارشی کنید.

برای این کار از کلاس‌های دسترسی در کتابخانه‌های Android Jetpack ( androidx.* ) استفاده خواهید کرد تا از سازگاری با عقب اطمینان حاصل کنید.

  1. در DialView.kt ، در بلوک init ، یک نماینده دسترس‌پذیری را به عنوان یک شیء AccessibilityDelegateCompat جدید تنظیم کنید. در صورت درخواست androidx.core.view.ViewCompat و androidx.core.view.AccessibilityDelegateCompat را وارد کنید. این استراتژی بیشترین میزان سازگاری رو به عقب را در برنامه شما فعال می کند.
ViewCompat.setAccessibilityDelegate(this, object : AccessibilityDelegateCompat() {
   
})
  1. در داخل شی AccessibilityDelegateCompat ، تابع onInitializeAccessibilityNodeInfo() را با یک شی AccessibilityNodeInfoCompat نادیده بگیرید و متد super را فراخوانی کنید. وقتی از شما خواسته شد androidx.core.view.accessibility.AccessibilityNodeInfoCompat را وارد کنید.
ViewCompat.setAccessibilityDelegate(this, object : AccessibilityDelegateCompat() {
   override fun onInitializeAccessibilityNodeInfo(host: View, 
                            info: AccessibilityNodeInfoCompat) {
      super.onInitializeAccessibilityNodeInfo(host, info)

   }  
})

هر نما دارای درختی از گره های دسترسی است که ممکن است با اجزای طرح بندی واقعی نما مطابقت داشته باشد یا نباشد. سرویس‌های دسترس‌پذیری Android آن گره‌ها را برای یافتن اطلاعات مربوط به نما (مانند توضیحات محتوای قابل گفتن یا اقدامات احتمالی که می‌توان در آن نما انجام داد.) پیمایش می‌کند. هنگامی که یک نمای سفارشی ایجاد می‌کنید، ممکن است لازم باشد اطلاعات گره را نادیده بگیرید تا اطلاعات سفارشی برای دسترسی ارائه کنید. در این حالت شما اطلاعات گره را نادیده می گیرید تا نشان دهید که اطلاعات سفارشی برای عملکرد view وجود دارد.

  1. در داخل onInitializeAccessibilityNodeInfo() یک شی AccessibilityNodeInfoCompat.AccessibilityActionCompat جدید ایجاد کنید و آن را به متغیر customClick اختصاص دهید. ثابت AccessibilityNodeInfo.ACTION_CLICK و یک رشته نگهدارنده را به سازنده منتقل کنید. هنگام درخواست، AccessibilityNodeInfo را وارد کنید.
ViewCompat.setAccessibilityDelegate(this, object : AccessibilityDelegateCompat() {
   override fun onInitializeAccessibilityNodeInfo(host: View, 
                            info: AccessibilityNodeInfoCompat) {
      super.onInitializeAccessibilityNodeInfo(host, info)
      val customClick = AccessibilityNodeInfoCompat.AccessibilityActionCompat(
         AccessibilityNodeInfo.ACTION_CLICK,
        "placeholder"
      )
   }  
})

کلاس AccessibilityActionCompat یک عمل در یک view برای اهداف دسترسی را نشان می دهد. یک عمل معمولی یک کلیک یا ضربه است، همانطور که در اینجا استفاده می کنید، اما اقدامات دیگر می تواند شامل به دست آوردن یا از دست دادن فوکوس، عملیات کلیپ بورد (برش/کپی/پیست) یا پیمایش در نما باشد. سازنده این کلاس به یک ثابت کنش (در اینجا، AccessibilityNodeInfo.ACTION_CLICK ) و رشته ای نیاز دارد که TalkBack برای نشان دادن چیستی عمل استفاده می کند.

  1. برای بازیابی یک منبع رشته، رشته "placeholder" را با یک فراخوانی به context.getString() جایگزین کنید. برای منبع خاص، سرعت فعلی فن را تست کنید. اگر سرعت در حال حاضر FanSpeed.HIGH است، رشته "Reset" است. اگر سرعت فن هر چیز دیگری باشد، رشته "Change." این منابع رشته ای را در مرحله بعد ایجاد خواهید کرد.
ViewCompat.setAccessibilityDelegate(this, object : AccessibilityDelegateCompat() {
   override fun onInitializeAccessibilityNodeInfo(host: View, 
                            info: AccessibilityNodeInfoCompat) {
      super.onInitializeAccessibilityNodeInfo(host, info)
      val customClick = AccessibilityNodeInfoCompat.AccessibilityActionCompat(
         AccessibilityNodeInfo.ACTION_CLICK,
        context.getString(if (fanSpeed !=  FanSpeed.HIGH) R.string.change else R.string.reset)
      )
   }  
})
  1. پس از بسته شدن پرانتز برای تعریف customClick ، از متد addAction() برای افزودن عملکرد دسترسی جدید به شی اطلاعات گره استفاده کنید.
ViewCompat.setAccessibilityDelegate(this, object : AccessibilityDelegateCompat() {
   override fun onInitializeAccessibilityNodeInfo(host: View, 
                            info: AccessibilityNodeInfoCompat) {
       super.onInitializeAccessibilityNodeInfo(host, info)
       val customClick = AccessibilityNodeInfoCompat.AccessibilityActionCompat(
           AccessibilityNodeInfo.ACTION_CLICK,
           context.getString(if (fanSpeed !=  FanSpeed.HIGH) 
                                 R.string.change else R.string.reset)
       )
       info.addAction(customClick)
   }
})
  1. در res/values/strings.xml ، منابع رشته ای را برای «Change» و «Reset» اضافه کنید.
<string name="change">Change</string>
<string name="reset">Reset</string>
  1. برنامه را کامپایل و اجرا کنید و مطمئن شوید TalkBack روشن است. اکنون توجه داشته باشید که عبارت «برای فعال کردن دو بار ضربه بزنید» اکنون یا «دو بار ضربه بزنید تا تغییر دهید» (اگر سرعت فن کمتر از زیاد یا 3 باشد) یا «دو ضربه بزنید تا بازنشانی شود» (اگر سرعت فن از قبل بالا است یا 3). توجه داشته باشید که درخواست "دوبار ضربه زدن به..." توسط خود سرویس TalkBack ارائه می شود.

دانلود کد برای کد لبه تمام شده..

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


یا می‌توانید مخزن را به‌عنوان یک فایل Zip دانلود کنید، آن را از حالت فشرده خارج کنید و در Android Studio باز کنید.

زیپ را دانلود کنید

  • برای ایجاد یک نمای سفارشی که ظاهر و رفتار یک زیر کلاس View مانند EditText را به ارث می برد، یک کلاس جدید اضافه کنید که آن زیر کلاس را گسترش می دهد و با نادیده گرفتن برخی از متدهای زیر کلاس تنظیمات را انجام دهید.
  • برای ایجاد نمای سفارشی با هر اندازه و شکل، یک کلاس جدید اضافه کنید که View را گسترش دهد.
  • برای تعریف شکل و ظاهر اصلی نما، روش‌های View مانند onDraw() را لغو کنید.
  • از invalidate() برای ترسیم یا ترسیم مجدد view استفاده کنید.
  • برای بهینه‌سازی عملکرد، متغیرها را تخصیص داده و قبل از استفاده از آنها در onDraw() مانند مقداردهی اولیه متغیرهای عضو، مقادیر مورد نیاز برای طراحی و نقاشی را اختصاص دهید.
  • برای ارائه رفتار تعاملی view، به جای OnClickListener () performClick() به نمای سفارشی لغو کنید. این به شما یا سایر توسعه دهندگان اندرویدی که ممکن است از کلاس view سفارشی شما استفاده کنند، قادر می سازد onClickListener() برای ارائه رفتار بیشتر استفاده کنند.
  • نمای سفارشی را به یک فایل طرح‌بندی XML با ویژگی‌هایی اضافه کنید تا ظاهر آن را مشخص کنید، همانطور که با سایر عناصر UI انجام می‌دهید.
  • برای تعریف ویژگی های سفارشی، فایل attrs.xml را در پوشه values ایجاد کنید. سپس می توانید از ویژگی های سفارشی برای نمای سفارشی در فایل طرح بندی XML استفاده کنید.

دوره بی ادبی:

مستندات توسعه دهنده اندروید:

ویدئوها:

این بخش، تکالیف احتمالی را برای دانش‌آموزانی که در این آزمایشگاه کد به عنوان بخشی از دوره‌ای که توسط یک مربی هدایت می‌شود، کار می‌کنند، فهرست می‌کند. این وظیفه مربی است که موارد زیر را انجام دهد:

  • در صورت نیاز تکالیف را تعیین کنید.
  • نحوه ارسال تکالیف را با دانش آموزان در میان بگذارید.
  • تکالیف را نمره دهید.

مربیان می‌توانند از این پیشنهادات به اندازه‌ای که می‌خواهند استفاده کنند، و باید با خیال راحت هر تکلیف دیگری را که فکر می‌کنند مناسب است، محول کنند.

اگر به تنهایی از طریق این کدها کار می کنید، از این تکالیف برای آزمایش دانش خود استفاده کنید.

سوال 1

برای محاسبه موقعیت‌ها، ابعاد و هر مقدار دیگری که برای اولین بار به نمای سفارشی یک اندازه اختصاص داده می‌شود، کدام روش را نادیده می‌گیرید؟

onMeasure()

onSizeChanged()

invalidate()

onDraw()

سوال 2

برای نشان دادن اینکه می‌خواهید نمای شما با onDraw() دوباره ترسیم شود، پس از تغییر مقدار مشخصه، کدام متد را از رشته UI فراخوانی می‌کنید؟

▢ onMeasure()

▢ onSizeChanged()

▢ invalidate()

▢ getVisibility()

سوال 3

برای افزودن تعامل به نمای سفارشی خود، کدام روش View باید نادیده بگیرید؟

▢ setOnClickListener()

▢ onSizeChanged()

▢ isClickable()

▢ performClick()

برای پیوند به دیگر کدلب ها در این دوره، صفحه فرود Advanced Android in Kotlin Codelabs را ببینید.