دوره آموزش پروژه محور Retrofit و LiveData در اندروید منتشر شد! جهت تهیه پیش‌فروش دوره با تخفیف ویژه کلیک کنید!  

ProgressDialog برنامه‌تان را با دکمه‌ی Progress جایگزین کنید

دوشنبه ۲۸ مرداد ۹۸ توسط سالار ساری نوایی

یکی از روش‌های نشان دادن پیشرفت غیربلوکی در برنامه‌های اندروید استفاده از دکمه‌ی Progress است که توسط گوگل در راهنمای متریال دیزاین معرفی شد. متاسفانه گوگل این ویژگی را به شکلی ارائه داده است که راه‌اندازی آن می‌تواند کمی دشوار باشد، اما می‌توانیم با کمک گرفتن از کامپوننت موجود روش پیاده‌سازی آن را مشاهده کنیم و بدون ایجاد تغییرات خاصی در Layout صفحه، این ویژگی را به برنامه‌مان اضافه کنیم. در ادامه این مسئله را به طور کامل بررسی می‌کنیم؛ با ما همراه باشید:

بسیار خب، چیزی که مد نظرمان است به این شکل خواهد بود:

حالا چند راه مختلف پیش روی ما قرار دارد

راه اول این است که ProgressBar را به Layout برنامه‌مان اضافه کنیم اما این کار همواره به درستی پیش نمی‌رود و می‌تواند کمی دشوار باشد. دکمه‌ها به صورت از پیش تعیین شده مقداری پدینگ دارند و تراز کردن ProgressBar در مرکز دکمه کار آسانی نخواهد بود. اگر بخواهیم در ادامه یک متن به آن اضافه کنیم چه اتفاقی می‌افتد؟ آیا هر بار باید Layout را تغییر بدهیم؟

راه دوم این است که از یک کامپوننت خارجی مانند این مورد استفاده کنیم. اما این روش‌ هم ایرادهای خود را به همراه دارد. با این کار بخشی از آزادی عمل خود را از دست می‌دهید و به عنوان مثال دیگر نمی‌توانید که از MaterialButton جدید گوگل استفاده کنید. ضمن این که با چارچوب‌های متریال دیزاین نیز مطابقت ندارد.

راه سوم: جدا از این روش‌ها یک راه دیگر برای نمایش پیشرفت نیز وجود دارد و آن استفاده از Drawable هایی است که توسط اندروید فراهم شده‌اند. به طور دقیق‌تر AnimationDrawable. خوشبختانه یک Drawable پیشرفت از پیش آماده شده در پکیج support-v4 در اختیارمان قرار دارد (CircularProgressDrawable). همچنین برای نمایش Drawable در کنار متن نیز راهکاری وجود دارد (SpannableString + DynamicDrawableSpan). پس کافی است که همین چند خط را به کدمان اضافه کنیم...

// create progress drawable
val progressDrawable = CircularProgressDrawable(this).apply {
    // let's use large style just to better see one issue
    setStyle(CircularProgressDrawable.LARGE)
    setColorSchemeColors(Color.WHITE)

    //bounds definition is required to show drawable correctly
    val size = (centerRadius + strokeWidth).toInt() * 2
    setBounds(0, 0, size, size)
}

// create a drawable span using our progress drawable
val drawableSpan = object : DynamicDrawableSpan() {
    override fun getDrawable() = progressDrawable
}

// create a SpannableString like "Loading [our_progress_bar]"
val spannableString = SpannableString("Loading ").apply {
    setSpan(drawableSpan, length - 1, length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
}

//start progress drawable animation
progressDrawable.start()

button.text = spannableString

اما آن طور که می‌خواهیم نشد. چند مشکل وجود دارد:

  • انیمیشن فریز می‌شود.
  • Drawable هم‌تراز با متن نیست.
  • هیچ گونه پدینگی میان متن و Drawable وجود ندارد.

بگذارید به سراغ اولین مشکل برویم. انیمیشن ما به این دلیل فریز می‌شود که دکمه نمی‌داند چه زمانی وضعیتش را مجددا ترسیم کند. DynamicDrawableSpan به طور خودکار این کار را انجام نمی‌دهد پس نیاز است که به صورت درستی انجامش بدهیم. می‌توانیم از به‌روزرسانی انیمیشن در AnimationDrawable استفاده کنیم و button.invalidate را فراخوانی کنیم تا دکمه را مجبور کند که حین اجرای انیمیشن نیز درخواست ترسیم مجدد کند. حالا انیمیشن ما به درستی کار می‌کند:

 ...

//start progress drawable animation
progressDrawable.start()

val callback = object : Drawable.Callback {
    override fun unscheduleDrawable(who: Drawable, what: Runnable) {
    }

    override fun invalidateDrawable(who: Drawable) {
        button.invalidate()
    }

    override fun scheduleDrawable(who: Drawable, what: Runnable, `when`: Long) {
    }
}
progressDrawable.callback = callback

button.text = spannableString

در مرحله‌ی بعد باید Drawable را با دیگر اجزا هم‌تراز کنیم. برای این کار می‌توانیم ImageSpan را گسترش دهیم و متد‌های getSize و draw را اورراید کنیم. برای درک بهتر این که هر واحد اندازه‌گیری فونت چه معنایی دارد می‌توانید به این مطلب مراجعه کنید.

override fun getSize(paint: Paint, text: CharSequence, start: Int, end: Int, fontMetricsInt: Paint.FontMetricsInt?): Int {
        // get drawable dimensions
        val rect = drawable.bounds 
        fontMetricsInt?.let {
            val fontMetrics = paint.fontMetricsInt

            // get a font height
            val lineHeight = fontMetrics.bottom - fontMetrics.top

            //make sure our drawable has height >= font height
            val drHeight = Math.max(lineHeight, rect.bottom - rect.top)

            val centerY = fontMetrics.top + lineHeight / 2

            //adjust font metrics to fit our drawable size
            fontMetricsInt.apply {
                ascent = centerY - drHeight / 2
                descent = centerY + drHeight / 2
                top = ascent
                bottom = descent
            }
        }

        //return drawable width which is in our case = drawable width + margin from text
        return rect.width() + marginStart
}
override fun draw(canvas: Canvas, text: CharSequence, start: Int, end: Int, x: Float, top: Int, y: Int, bottom: Int, paint: Paint) {

        canvas.save()
        val fontMetrics = paint.fontMetricsInt
        // get font height. in our case now it's drawable height
        val lineHeight = fontMetrics.bottom - fontMetrics.top

        // adjust canvas vertically to draw drawable on text vertical center
        val centerY = y + fontMetrics.bottom - lineHeight / 2
        val transY = centerY - drawable.bounds.height() / 2

        // adjust canvas horizontally to draw drawable with defined margin from text
        canvas.translate(x + marginStart, transY.toFloat())

        drawable.draw(canvas)

        canvas.restore()
}

و از یک کلاس Spannable جدید استفاده کنید.

val drawableSpan = CustomDrawableSpan(progressDrawable, marginStart = 20)

نتیجه‌ی کار ما به این شکل خواهد بود:

همان طور که می‌بینید تغییر حالت میان متن و پیشرفت به اندازه‌ی کافی نرم و روان نیست. می‌توانیم این مشکل را با استفاده از ObjectAnimator برطرف کنیم. در اینجا به بررسی این موضوع نمی‌پردازم اما در حالت کلی می‌توانید از این نمونه الگو بگیرید.

با این وجود یک کد آماده‌ی استفاده برای شما فراهم کرده‌ام که نیاز نباشد این چرخه را طی کنید و کدها را از اول بنویسید. همچنین به صورت پیش فرض یک انیمیشن روان برای تغییر متن نیز در آن قرار داده شده است.

آموزش انیمیشن در اندروید
دوست عزیز شما میتوانید با تخفیف ویژه در این دوره شرکت کنید
کد تخفیف:
inla20nim

کلیدواژه: اندروید اپلیکیشن layout Progress Button Progress Dialog

منابع: proandroiddev.com

دیدگاه ها:
m.alipoor73
۱ ماه قبل
reply
سلام .
ممنون بابت آموزش هایی ک تهیه میکنید.
فقط مسئله ای رو مد نظر بگیرید اینکه شما دارید زمان میزارید برای تهیه ی آموزش خب چ بهتر ک تخصصی تر عمیق تر و کاربردی تر باشه .
مثلا بحث solid من مقالشو دیدم ولی واقعا جا داره بهتر از اینا باشه .(یه نمونه از آموزشای خوب برای پروژه های واقعی)
مباحثی ک در عرض ۵ دقیقه میشه با یه سرچ ساده پیاده سازی کرد زیاد برای کسانی ک تو سازمان ها کار میکنن ارزش نداره .. ب نظرم برای دیده شدن بهتره مباحث رو عمیق تر کنید.
تشکر
پشتیبانی آرکادمی
۱ ماه قبل در پاسخ به m.alipoor73
reply
سلام دوست عزیز، ممنون از همراهی شما
قسمت مقالات آرکادمی عمدتا از مقالات ترجمه‌ای تشکیل شده که صرفا جنبه‌ی آشنایی دارند.
ما مدتی است که آموزش مباحث تخصصی‌تر را در دوره‌های آنلاینمان آغاز کرده‌ایم. به عنوان مثال در سری دوره‌های متریال دیزاین بحث طراحی متریال به صورت خیلی مفصل و کاربردی مورد بررسی قرار داده شده و یا بحث معماری چند لایه MVVM را در آموزشها آغاز کردیم. در دوره آموزش SQLite در اندروید با کاتلین بحث این معماری آغاز شده و در دوره آموزش پروژه محور Retrofit و LiveData که در حال حاضر در حال آماده‌سازی است و به زودی در وبسایت منتشر می‌شود بحث این معماری ادامه داده می‌شود و بدون شک برنامه‌های زیادی برای تولید بیشتر چنین محتوای تخصصی‌ که مورد نیاز بازار کار باشد را داریم. پس حتما ما را دنبال کنید و منتظر محتوای تخصصی‌تر باشید.
پیشاپیش از صبر و همراهی شما متشکریم. موفق و پیروز باشید.
ارسال دیدگاه:
برای ارسال دیگاه باید به سیستم وارد شوید و یا ثبت نام کنید. ثبت نام چند لحظه بیشتر زمان شما را نمیگیرد.