وقتی میخوایم یه ویو جدیدی بسازیم و قراره انیمیشن داشته باشه، کنترل تایمر یه موضوع خیلی مهمه که میتونه ایرادات زیادی وارد کنه و دردسر ساز باشه. باید کارایی که در هر فریم انجام میشه رو کاهش بدیم که بتونیم انیمیشن روان تری داشته باشیم.
برای انیمیشن و تایمر از ValueAnimator استفاده میکنیم. حالا ValueAnimation چیه؟!
انیمیشنها معمولا دو قسمت دارن. یکی آپدیت مقادیری که دارن انیمیت میکنن و یکی آپدیت UI طبق اون مقادیری که آپدیت شدهان. ValueAnimator موتور اصلی زمان بندی انیمیشنها هست که فقط قسمت اول رو انجام میده و برای استفادهاش باید از listenerهاش استفاده کنین و تغییرات ui رو خودتون انجام بدین. برای این پست از ریپو زیر به عنوان مثال استفاده میکنم.
سه تابع مهمی که داریم init, onMeasure, onDraw هست که تابع onDraw در هر دور انیمیشن اجرا خواهد شد پس باید کارای داخل اون رو به حداقل برسونیم تا عملکرد بهتری داشته باشیم. پس برای انجام محاسبات و مقداردهیهای اولیه از توابع init و onMeasure استفاده میکنیم.
private void init() { movementRange = barHeight - (2 * dotRadius); dotbarPaint = new Paint(Paint.ANTI_ALIAS_FLAG); dotbarPaint.setColor(dotColor); animator = ValueAnimator.ofInt(0, 7 * (movementRange / 2)); animator.setDuration(duration); animator.addUpdateListener(this); animator.addListener(this); }
ابتدا یه محاسبه کوچیک برای تعیین مقادیری که انیمیت خواهند شد انجام میدیم.
بعد شی paint رو میسازیم که تو onDraw در هر فریم استفاده خواهد شد پس یک بار برای همیشه تولیدش میکنیم تا از تکرار جلوگیری بشه.
در آخر animator رو میسازیم که قراره مقداری از نوع int رو از 0 تا یک عددی که برحسب ارتفاع ویو مون محاسبه میشه، انیمیت کنه در زمان تعیین شده. در هربار آپدیت مقدار، توسط یه listener از اون مقدار با خبر میشیم و ویو مون رو آپدیت میکنیم. برای listener از ValueAnimator.AnimatorUpdateListener استفاده میکنیم که در هر آپدیت تابع onAnimationUpdate صدا زده میشه و میتونیم مقداری که آپدیت شده رو مثل خط زیر استفادهاش کنیم.
int value = (int) animation.getAnimatedValue();
من از Animator.AnimatorListener هم استفاده کردهام که از شروع، پایان و تکرار انیمیشن با خبر میشیم که من در مثال LoadingDotBar فقط به پایانش احتیاج داشتم.
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int padLeft = getPaddingLeft(); int padTop = getPaddingTop(); int padRight = getPaddingRight(); int padBottom = getPaddingBottom(); //calculate the view size int width = (3 * 2 * dotRadius) + // 3 dots * 2 radius (2 * gapSize) + // 2 gaps padLeft + padRight; int height = barHeight + padTop + padBottom; //calculate positions and helper sizes halfHeight = height / 2; dotOneX = padLeft + dotRadius; dotTwoX = padLeft + (3 * dotRadius) + gapSize; dotThreeX = padLeft + (5 * dotRadius) + (2 * gapSize); setMeasuredDimension(width, height); }
تابع onMeasure هدفش اینه که تصمیم بگیریم اندازه ویو چقدر خواهد بود. دو ورودی widthMeasureSpec و heightMeasureSpec هرکدام شامل دو نوع اطلاعات هستند که بصورت زیر میتوان اطلاعات رو استخراج کرد
int mode = MeasureSpec.getMode(widthMeasureSpec); int size = MeasureSpec.getSize(widthMeasureSpec);
اینجا mode میتونه سه تا مقدار داشته باشه:
UNSPECIFIED
یعنی پدر هیچ محدودیتی برای سایز تعیین نکرده و ویو میتونه هر سایزی داشته باشه.
EXACTLY
یعنی پدر اندازه دقیقی برای فرزند انتخاب کرده و در هر شرایطی فرزند در اون محدوده خواهد بود.
AT_MOST
فرزند میتونه هر سایزی که خواست بگیره در محدوده ای که پرد تعیین کرده باشه. پدر یه اندازه حداکثیر تعیین کرده.
با دونستن اینا و اندازه ای که ما برای ویو نیاز داریم، محاسبات لازم رو انجام میدیم و اندازه نهایی رو تصمیم میگیریم و با تابع setMeasuredDimension تصمیمون رو اعلام میکنیم.
private void drawBar(Canvas canvas, int barXCenter, int barHeight) { if (barHeight == 0) { canvas.drawCircle(barXCenter, halfHeight, dotRadius, dotbarPaint); } else if (barHeight == 1) { canvas.drawCircle(barXCenter, halfHeight, dotRadius, dotbarPaint); canvas.drawCircle(barXCenter, halfHeight + 1, dotRadius, dotbarPaint); } else { int halfBarHeight = barHeight / 2; canvas.drawCircle(barXCenter, halfHeight + halfBarHeight, dotRadius, dotbarPaint); canvas.drawCircle(barXCenter, halfHeight - halfBarHeight, dotRadius, dotbarPaint); canvas.drawRect(barXCenter - dotRadius, halfHeight + halfBarHeight, barXCenter + dotRadius, halfHeight - halfBarHeight, dotbarPaint); } }
اینجا هم که کار نهایی و کشیدن ویو روی canvas رو انجام میدیم که خیلی وارد جزئیات این قسمت نمیشم. شرطها برای اینه که اگر دو تا دایرهها دقیقا روی هم بیافتن، یکی رو نکشیم که کار رو کمتر کرده باشیم.
و در آخر برای اینکه هربار بعد از آپدیت شدن مقادیر توسط animator ویو رو دوباره بکشیم و از invalidate در آخر تابع onAnimationUpdate استفاده میکنیم.
خببب همیننن. اگر جزئیات بیشتری درمورد هر بخشی از این پست یا کلا بحثهای دیگه خواستین، میتونین تو نظرات بهم بگین. یادتون نره به کاراتون انیمیشن اضافه کنید و فعلا تا یه پست دیگه. ??