What you will learn: You will learn how to decorate SeekBar, and in particular how to create custom Drawable that uses custom Animation in order to make some nice animations.
:?: Problems/Questions: Write it right below...
Difficulty: 2 of 5
Description:
0.) The easiest way to decorate SeekBar is to create an custom Drawable.
Create a class called AnimSeekBarDrawable.java:
Using java Syntax Highlighting
- public class AnimSeekBarDrawable extends Drawable implements Runnable {
- }
Parsed in 0.030 seconds, using GeSHi 1.0.8.4
1.) Now lets fill it with code: create some constants and fields.
Constants STATE_* will be used later on to check what state our Drawable is in.
Field mText contains text to be drawn over our Drawable.
Field mTextXScale ranges from 0 to 1: when set to 0 mText will be drawn on the left side (with some small left padding), when set to 1 mText will be drawn on the right side (with some small right padding), any other values move mText somewhere between left and right side - proportionally to mTextXScale (e.g. 0.5 - center).
Fields mPaint and mOutlinePaint are used for mText drawing.
Field mAnimation is our custom Animation.
Using java Syntax Highlighting
- static final int[] STATE_FOCUSED = {android.R.attr.state_focused};
- static final int[] STATE_PRESSED = {android.R.attr.state_pressed};
- private static final long DELAY = 30;
- private static final String TAG = "AnimSeekBarDrawable";
- private String mText;
- private float mTextWidth;
- private Drawable mProgress;
- private Paint mPaint;
- private Paint mOutlinePaint;
- private float mTextXScale;
- private int mDelta;
- private ScrollAnimation mAnimation;
Parsed in 0.031 seconds, using GeSHi 1.0.8.4
2.) Initialize fields: mProgress is SeekBar's std progress Drawable and will be used in draw method.
Using java Syntax Highlighting
- public AnimSeekBarDrawable(Resources res, boolean labelOnRight) {
- mProgress = res.getDrawable(android.R.drawable.progress_horizontal);
- mText = "";
- mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
- mPaint.setTypeface(Typeface.DEFAULT_BOLD);
- mPaint.setTextSize(16);
- mOutlinePaint = new Paint(mPaint);
- mOutlinePaint.setStyle(Style.STROKE);
- mOutlinePaint.setStrokeWidth(4);
- mOutlinePaint.setMaskFilter(new BlurMaskFilter(1, Blur.NORMAL));
- mTextXScale = labelOnRight? 1 : 0;
- mAnimation = new ScrollAnimation();
- }
Parsed in 0.035 seconds, using GeSHi 1.0.8.4
3.) Now when our Drawable gets resized we need to resize mProgress too.
Using java Syntax Highlighting
- @Override
- protected void onBoundsChange(Rect bounds) {
- mProgress.setBounds(bounds);
- }
Parsed in 0.034 seconds, using GeSHi 1.0.8.4
4.) Our Drawable will be "stateful": which means that it will be presented in different ways when pressed/focused and in normal state. Local variable "active" indicates if state is pressed/focused (true) or normal (false).
Using java Syntax Highlighting
- @Override
- protected boolean onStateChange(int[] state) {
- boolean active = StateSet.stateSetMatches(STATE_FOCUSED, state) | StateSet.stateSetMatches(STATE_PRESSED, state);
- mOutlinePaint.setColor(active? 0xffffffff : 0xffbbbbbb);
- mPaint.setColor(active? 0xff000000 : 0xff606060);
- invalidateSelf();
- return false;
- }
- @Override
- public boolean isStateful() {
- return true;
- }
Parsed in 0.036 seconds, using GeSHi 1.0.8.4
5.) Now animations start! If we moved our finger to the left (level < 4000) lets start animation from the left to the right, so that mText is always visible. The similar amination is started if we move our finger to the right (lefel > 6000), but in this case animation starts from the right to the left.
Method startScrolling calls mAnimation.startScrolling(mTextXScale, to) but it doesn't do any magic (we need to do animation drawing by ourselves), so we call scheduleSelf(this, SystemClock.uptimeMillis() + DELAY) which in DELAY ms call run method.
Using java Syntax Highlighting
- @Override
- protected boolean onLevelChange(int level) {
- mText = (level / 100) + " %";
- mTextWidth = mOutlinePaint.measureText(mText);
- if (level < 4000 && mDelta <= 0) {
- mDelta = 1;
- // move to the right
- startScrolling(1);
- } else
- if (level > 6000 && mDelta >= 0) {
- mDelta = -1;
- // move to the left
- startScrolling(0);
- }
- return mProgress.setLevel(level);
- }
- private void startScrolling(int to) {
- mAnimation.startScrolling(mTextXScale, to);
- scheduleSelf(this, SystemClock.uptimeMillis() + DELAY);
- }
Parsed in 0.037 seconds, using GeSHi 1.0.8.4
6.) Here we decorate our SeekBar: first draw original SeekBar's progress drawable. Then check if we are in pending animation: if so we need to update our animation with the current time and get its current value.
Finally we draw our mText according to mTextXScale.
Using java Syntax Highlighting
- @Override
- public void draw(Canvas canvas) {
- mProgress.draw(canvas);
- if (mAnimation.hasStarted() && !mAnimation.hasEnded()) {
- // pending animation
- mAnimation.getTransformation(AnimationUtils.currentAnimationTimeMillis(), null);
- mTextXScale = mAnimation.getCurrent();
- }
- Rect bounds = getBounds();
- float x = 6 + mTextXScale * (bounds.width() - mTextWidth - 6 - 6);
- float y = (bounds.height() + mPaint.getTextSize()) / 2;
- canvas.drawText(mText, x, y, mOutlinePaint);
- canvas.drawText(mText, x, y, mPaint);
- }
Parsed in 0.037 seconds, using GeSHi 1.0.8.4
7.) Abstract methods we need to override: no magic here.
Using java Syntax Highlighting
- @Override
- public int getOpacity() {
- return PixelFormat.TRANSLUCENT;
- }
- @Override
- public void setAlpha(int alpha) {
- }
- @Override
- public void setColorFilter(ColorFilter cf) {
- }
Parsed in 0.036 seconds, using GeSHi 1.0.8.4
8.) Method that will be called as the result of scheduleSelf(this, SystemClock.uptimeMillis() + DELAY). It checks if we have pending animation: if so we call scheduleSelf again in order to make next step of animation. Finally we call invalidateSelf() for repainting our Drawable.
Using java Syntax Highlighting
- public void run() {
- mAnimation.getTransformation(AnimationUtils.currentAnimationTimeMillis(), null);
- // close interpolation of mTextX
- mTextXScale = mAnimation.getCurrent();
- if (!mAnimation.hasEnded()) {
- scheduleSelf(this, SystemClock.uptimeMillis() + DELAY);
- }
- invalidateSelf();
- }
Parsed in 0.036 seconds, using GeSHi 1.0.8.4
9.) This is our custom Animation: nothing special here, we just need to update mCurrent in applyTransformation method (it's called by mAnimation.getTransformation method).
Using java Syntax Highlighting
- static class ScrollAnimation extends Animation {
- private static final long DURATION = 750;
- private float mFrom;
- private float mTo;
- private float mCurrent;
- public ScrollAnimation() {
- setDuration(DURATION);
- setInterpolator(new DecelerateInterpolator());
- }
- public void startScrolling(float from, float to) {
- mFrom = from;
- mTo = to;
- startNow();
- }
- @Override
- protected void applyTransformation(float interpolatedTime, Transformation t) {
- mCurrent = mFrom + (mTo - mFrom) * interpolatedTime;
- }
- public float getCurrent() {
- return mCurrent;
- }
- }
Parsed in 0.037 seconds, using GeSHi 1.0.8.4
The Full Source:
file main.xml
Using xml Syntax Highlighting
- <?xml version="1.0" encoding="utf-8"?>
- <TableLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="fill_parent"
- android:layout_height="wrap_content"
- android:stretchColumns="1"
- >
- <TableRow>
- <TextView
- android:padding="4dip"
- android:layout_gravity="center_vertical|right"
- android:textStyle="bold"
- android:text="voltage:"
- />
- <SeekBar
- android:id="@+id/seek_bar0"
- android:padding="4dip"
- />
- </TableRow>
- <TableRow>
- <TextView
- android:padding="4dip"
- android:layout_gravity="center_vertical|right"
- android:textStyle="bold"
- android:text="pressure:"
- />
- <SeekBar
- android:id="@+id/seek_bar1"
- android:padding="4dip"
- />
- </TableRow>
- <TableRow>
- <TextView
- android:padding="4dip"
- android:layout_gravity="center_vertical|right"
- android:textStyle="bold"
- android:text="temperature:"
- />
- <SeekBar
- android:id="@+id/seek_bar2"
- android:padding="4dip"
- />
- </TableRow>
- </TableLayout>
Parsed in 0.005 seconds, using GeSHi 1.0.8.4
file AnimSeekBarTest.java
Using java Syntax Highlighting
- package org.pskink.animseekbar;
- import android.app.Activity;
- import android.content.res.Resources;
- import android.graphics.drawable.Drawable;
- import android.os.Bundle;
- import android.widget.SeekBar;
- public class AnimSeekBarTest extends Activity {
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.main);
- setup((SeekBar) findViewById(R.id.seek_bar0), 25);
- setup((SeekBar) findViewById(R.id.seek_bar1), 50);
- setup((SeekBar) findViewById(R.id.seek_bar2), 75);
- }
- private void setup(SeekBar seekBar, int v) {
- Resources res = getResources();
- Drawable d = new AnimSeekBarDrawable(res, v < seekBar.getMax() / 2);
- seekBar.setProgressDrawable(d);
- seekBar.setProgress(v);
- }
- }
Parsed in 0.039 seconds, using GeSHi 1.0.8.4
file AnimSeekBarDrawable.java
Using java Syntax Highlighting
- package org.pskink.animseekbar;
- import android.content.res.Resources;
- import android.graphics.BlurMaskFilter;
- import android.graphics.Canvas;
- import android.graphics.ColorFilter;
- import android.graphics.Paint;
- import android.graphics.PixelFormat;
- import android.graphics.Rect;
- import android.graphics.Typeface;
- import android.graphics.BlurMaskFilter.Blur;
- import android.graphics.Paint.Style;
- import android.graphics.drawable.Drawable;
- import android.os.SystemClock;
- import android.util.Log;
- import android.util.StateSet;
- import android.view.animation.Animation;
- import android.view.animation.AnimationUtils;
- import android.view.animation.DecelerateInterpolator;
- import android.view.animation.Transformation;
- public class AnimSeekBarDrawable extends Drawable implements Runnable {
- static final int[] STATE_FOCUSED = {android.R.attr.state_focused};
- static final int[] STATE_PRESSED = {android.R.attr.state_pressed};
- private static final long DELAY = 30;
- private static final String TAG = "AnimSeekBarDrawable";
- private String mText;
- private float mTextWidth;
- private Drawable mProgress;
- private Paint mPaint;
- private Paint mOutlinePaint;
- private float mTextXScale;
- private int mDelta;
- private ScrollAnimation mAnimation;
- public AnimSeekBarDrawable(Resources res, boolean labelOnRight) {
- mProgress = res.getDrawable(android.R.drawable.progress_horizontal);
- mText = "";
- mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
- mPaint.setTypeface(Typeface.DEFAULT_BOLD);
- mPaint.setTextSize(16);
- mOutlinePaint = new Paint(mPaint);
- mOutlinePaint.setStyle(Style.STROKE);
- mOutlinePaint.setStrokeWidth(4);
- mOutlinePaint.setMaskFilter(new BlurMaskFilter(1, Blur.NORMAL));
- mTextXScale = labelOnRight? 1 : 0;
- mAnimation = new ScrollAnimation();
- }
- @Override
- protected void onBoundsChange(Rect bounds) {
- mProgress.setBounds(bounds);
- }
- @Override
- protected boolean onStateChange(int[] state) {
- boolean active = StateSet.stateSetMatches(STATE_FOCUSED, state) | StateSet.stateSetMatches(STATE_PRESSED, state);
- mOutlinePaint.setColor(active? 0xffffffff : 0xffbbbbbb);
- mPaint.setColor(active? 0xff000000 : 0xff606060);
- invalidateSelf();
- return false;
- }
- @Override
- public boolean isStateful() {
- return true;
- }
- @Override
- protected boolean onLevelChange(int level) {
- mText = (level / 100) + " %";
- mTextWidth = mOutlinePaint.measureText(mText);
- if (level < 4000 && mDelta <= 0) {
- mDelta = 1;
- // move to the right
- startScrolling(1);
- } else
- if (level > 6000 && mDelta >= 0) {
- mDelta = -1;
- // move to the left
- startScrolling(0);
- }
- return mProgress.setLevel(level);
- }
- private void startScrolling(int to) {
- mAnimation.startScrolling(mTextXScale, to);
- scheduleSelf(this, SystemClock.uptimeMillis() + DELAY);
- }
- @Override
- public void draw(Canvas canvas) {
- mProgress.draw(canvas);
- if (mAnimation.hasStarted() && !mAnimation.hasEnded()) {
- // pending animation
- mAnimation.getTransformation(AnimationUtils.currentAnimationTimeMillis(), null);
- mTextXScale = mAnimation.getCurrent();
- }
- Rect bounds = getBounds();
- float x = 6 + mTextXScale * (bounds.width() - mTextWidth - 6 - 6);
- float y = (bounds.height() + mPaint.getTextSize()) / 2;
- canvas.drawText(mText, x, y, mOutlinePaint);
- canvas.drawText(mText, x, y, mPaint);
- }
- @Override
- public int getOpacity() {
- return PixelFormat.TRANSLUCENT;
- }
- @Override
- public void setAlpha(int alpha) {
- }
- @Override
- public void setColorFilter(ColorFilter cf) {
- }
- public void run() {
- mAnimation.getTransformation(AnimationUtils.currentAnimationTimeMillis(), null);
- // close interpolation of mTextX
- mTextXScale = mAnimation.getCurrent();
- if (!mAnimation.hasEnded()) {
- scheduleSelf(this, SystemClock.uptimeMillis() + DELAY);
- }
- invalidateSelf();
- }
- static class ScrollAnimation extends Animation {
- private static final long DURATION = 750;
- private float mFrom;
- private float mTo;
- private float mCurrent;
- public ScrollAnimation() {
- setDuration(DURATION);
- setInterpolator(new DecelerateInterpolator());
- }
- public void startScrolling(float from, float to) {
- mFrom = from;
- mTo = to;
- startNow();
- }
- @Override
- protected void applyTransformation(float interpolatedTime, Transformation t) {
- mCurrent = mFrom + (mTo - mFrom) * interpolatedTime;
- }
- public float getCurrent() {
- return mCurrent;
- }
- }
- }
Parsed in 0.055 seconds, using GeSHi 1.0.8.4
[align=center]Thats it :)[/align]
Regards,





