Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save nking/355975 to your computer and use it in GitHub Desktop.
Save nking/355975 to your computer and use it in GitHub Desktop.
Here's a way to make an activity indicator that is simple to import into an
activity, and allows the rest of the window to continue receiving events.
The 5 classes ActivityIndicatorView.java, ActivityIndicatorListener.java,
IActivityIndicatorCallback.java, ActivityIndicatorAction.java, and ActivityIndicatorEventTypes.java are printed below 2 snippets showing how
to import the view and how to broadcast messages that the view will indirectly
respond to.
The animation is in ActivityIndicatorView.java which extends android.view.View.
It renders a simple activity indicator and registers itself as a callback for
start and stop events.
The callback invoker is in ActivityIndicatorListener.java which registers and unregisters instances of ActivityIndicatorView for start and stop activity
indicator events in the application.
An example of adding the activity indicator to an activity's views follows:
public class SelectGroupsViewController extends Activity {
protected ActivityIndicatorView activityIndicatorView = null;
...
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
...
}
@Override
public void onAttachedToWindow() {
// create an instance of the view after the window has been rendered.
activityIndicatorView = new ActivityIndicatorView(this, types);
// you can change the position of the activity indicator from the
// default. For example, use the view port dimensions to position
// it at the bottom
DisplayMetrics dm = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(dm);
int h = dm.heightPixels;
int w = dm.widthPixels;
activityIndicatorView.setPosition( 30,
(int) (h - 2*activityIndicatorView.getIndicatorHeight() - 30));
}
@Override
protected void onDestroy() {
// call destructor for activity indicator
activityIndicatorView.destroy();
super.onDestroy();
}
}
The worker performing a task that wants to broadcast when the task has
started and finished would use something like:
private void broadcastStartActivityIndicator() {
Intent intent = new Intent();
intent.setAction(
ActivityIndicatorAction.START_ACTIVITY_INDICATOR_INTENT.toString());
Bundle dataBundle = new Bundle();
dataBundle.putInt("indicatorType",
ActivityIndicatorEventTypes.CREATE_ACCOUNT.ordinal());
intent.putExtras(dataBundle);
getApplicationContext().sendBroadcast(intent);
}
The 5 classes for the activity indicator package are below.
package com.climbwithyourfeet.android.activityIndicator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import com.climbwithyourfeet.android.activityIndicator.ActivityIndicatorListener.ActivityIndicatorCallbackHandler;
/**
* Class to draw an activity indicator on a view. it starts and stops with
* application wide intents ActivityIndicatorActions.
*
* The types of events that the view will listen for should be set, but the
* default is to receive ActivityIndicatorEvent.FETCH_GROUP_NAMES events.
*
* To set position specific to a window, use setPosition. Note, the activity
* that uses this should call destroy().
*
* @author nichole
*/
public class ActivityIndicatorView extends View implements IActivityIndicatorCallback {
private String tag = "ActIndView";
private boolean isActive = false;
private long intervalInMillis = 200;
private final ActivityIndicatorEventTypes[] types;
/**
* @see android.view.View#View(Context context,
* ActivityIndicatorEventTypes[] eventTypes)
*/
public ActivityIndicatorView(Context context,
ActivityIndicatorEventTypes[] eventTypes) {
super(context);
this.types = eventTypes;
init();
}
/**
* @see android.view.View#View(Context context, AttributeSet attrs,
* ActivityIndicatorEventTypes[] eventTypes)
*/
public ActivityIndicatorView(Context context, AttributeSet attrs,
ActivityIndicatorEventTypes[] eventTypes) {
super(context, attrs);
this.types = eventTypes;
init();
}
/**
* @see android.view.View#View(Context context, AttributeSet attrs,
* int defStyle, ActivityIndicatorEventTypes[] eventTypes)
*/
public ActivityIndicatorView(Context context, AttributeSet attrs,
int defStyle, ActivityIndicatorEventTypes[] eventTypes) {
super(context, attrs, defStyle);
this.types = eventTypes;
init();
}
private int x = 50;
private int y = 250;
private float width = 12;
private float height = 12;
private float radius;
private float startAngleDegrees = 0.f;
private float sweepAngleDegrees = 30.f;
private void incrementStartAngle() {
startAngleDegrees+=sweepAngleDegrees;
if (startAngleDegrees > 360.)
startAngleDegrees -= 360.;
}
/**
* set position in view port coordinates. default is x = 50, y = 250.
*
* @param x x coord in view port
* @param y y coord in view port
*/
public void setPosition(int x, int y) {
this.x = x;
this.y = y;
}
public float getIndicatorWidth() {
return width;
}
public float getIndicatorHeight() {
return height;
}
private void init() {
stopIndicator();
float tmp = (float) (2. * width * height);
radius = (float) Math.sqrt(tmp);
setWillNotCacheDrawing(true);
ActivityIndicatorCallbackHandler cbHandler =
ActivityIndicatorCallbackHandler.getInstance();
cbHandler.register(this);
Log.d(tag, "***instantiated an activity indicator view");
}
private float getXMult() {
float mult = +1.f;
if ( ((startAngleDegrees >= 90.01) && (startAngleDegrees <= 180.0)) ||
((startAngleDegrees >= 270.01) && (startAngleDegrees <= 360.0)) )
mult = -1.f * mult;
return mult;
}
private float getYMult() {
float mult = +1.f;
if ( ((startAngleDegrees >= 0.01) && (startAngleDegrees <= 90.0)) ||
((startAngleDegrees >= 180.01) && (startAngleDegrees <= 270.0)) )
mult = -1.f * mult;
return mult;
}
/**
* @see android.view.View##onDraw(Canvas canvas)
*
* @param canvas
*/
protected void onDraw(Canvas canvas) {
//Log.d(tag, "onDraw angle=" + startAngleDegrees);
float xMult = getXMult();
float yMult = getYMult();
double startAngle = (startAngleDegrees* (Math.PI / 180.));
double sweepAngle = (sweepAngleDegrees* (Math.PI / 180.));
float x0 = (float) (xMult * radius * Math.cos(startAngle));
float y0 = (float) (yMult * radius * Math.sin(startAngle));
float x1 = (float) (xMult * radius * Math.cos(startAngle + sweepAngle));
float y1 = (float) (yMult * radius * Math.sin(startAngle + sweepAngle));
Paint background = new Paint();
background.setColor(Color.GRAY);
Paint bckClr = new Paint();
bckClr.setColor(0x333866c5);
Paint clr2 = new Paint();
clr2.setColor(0x11aa33aa);
canvas.drawCircle(x, y, radius, bckClr);
invalidate();
Path path = new Path();
path.moveTo(x, y);
path.lineTo(x + x0, y + y0);
path.lineTo(x + x1, y + y1);
path.close();
canvas.drawPath(path, clr2);
path = new Path();
path.moveTo(x, y);
path.lineTo(x - x0, y - y0);
path.lineTo(x - x1, y - y1);
path.close();
canvas.drawPath(path, clr2);
incrementStartAngle();
}
/**
* @see com.climbwithyourfeet.android.activityIndicator.IActivityIndicatorCallback#startIndicator()
*/
public void startIndicator() {
Log.d(tag, "***show/start indicator");
if (!this.isShown()) {
this.setVisibility(View.VISIBLE);
isActive = true;
animateIndicator();
}
}
/**
* animate the indicator until isActive flag is false;
*/
private void animateIndicator() {
Thread sleepThread = new Thread(
new Runnable() {
public void run() {
while(!Thread.currentThread().isInterrupted() && isActive) {
postInvalidate();
try {
Thread.sleep(intervalInMillis);
} catch (InterruptedException ex) {
Thread.currentThread().interrupt();
}
}
}
}
);
sleepThread.start();
}
/**
* @see com.climbwithyourfeet.android.activityIndicator.IActivityIndicatorCallback#stopIndicator()
*/
public void stopIndicator() {
Log.d(tag, "***hide/stop indicator");
this.setVisibility(View.INVISIBLE);
isActive = false;
}
/**
* unregister the activity indicator to allow instance to be destroyed
*/
public void destroy() {
ActivityIndicatorCallbackHandler cbHandler = ActivityIndicatorCallbackHandler.getInstance();
cbHandler.unregister(this);
}
/**
* @see com.climbwithyourfeet.android.activityIndicator.IActivityIndicatorCallback#getTypes()
*
* @return
*/
public ActivityIndicatorEventTypes[] getTypes() {
return types;
}
}
package com.climbwithyourfeet.android.activityIndicator;
/**
* callback class used by ActivityIndicatorListener to invoke start and
* stop of the activity indicator which is in ActivityIndicatorView
*
* @author nichole
*/
public interface IActivityIndicatorCallback {
/**
* start activity indicator
*/
public void startIndicator();
/**
* stop activity indicator
*/
public void stopIndicator();
/**
* get the event types that the activity view will respond to
*
* @return event types that the activity will respond to
*/
public ActivityIndicatorEventTypes[] getTypes();
}
package com.climbwithyourfeet.android.activityIndicator;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import java.util.ArrayList;
import java.util.concurrent.locks.ReentrantLock;
/**
* Class to receive start and stop all registered activity indicators in the application.
*
* @author nichole
*/
public final class ActivityIndicatorListener extends BroadcastReceiver {
private static String tag = "ActivityIndicatorListener";
private static ActivityIndicatorCallbackHandler cbHandler = ActivityIndicatorCallbackHandler.getInstance();
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
Bundle data = intent.getExtras();
int indicatorType = data.getInt("indicatorType", 0);
if (action.equalsIgnoreCase(
ActivityIndicatorAction.START_ACTIVITY_INDICATOR_INTENT.toString())) {
cbHandler.startIndicators(
ActivityIndicatorEventTypes.resolve(indicatorType));
} else if (action.equalsIgnoreCase(
ActivityIndicatorAction.STOP_ACTIVITY_INDICATOR_INTENT.toString())){
cbHandler.stopIndicators(
ActivityIndicatorEventTypes.resolve(indicatorType));
}
}
static class ActivityIndicatorCallbackHandler {
private static ReentrantLock cbLock = new ReentrantLock();
private ArrayList<IActivityIndicatorCallback> callbacks =
new ArrayList<IActivityIndicatorCallback>();
private static ActivityIndicatorCallbackHandler instance = null;
private ActivityIndicatorCallbackHandler() {
}
public static ActivityIndicatorCallbackHandler getInstance() {
try {
cbLock.lock();
if (instance == null) {
instance = new ActivityIndicatorCallbackHandler();
}
} finally {
cbLock.unlock();
}
return instance;
}
public void register(IActivityIndicatorCallback callback) {
callbacks.add(callback);
}
public void unregister(IActivityIndicatorCallback callback) {
callbacks.remove(callback);
}
private void startIndicators(ActivityIndicatorEventTypes type) {
for (IActivityIndicatorCallback cb : callbacks) {
ActivityIndicatorEventTypes[] cbTypes = cb.getTypes();
for (ActivityIndicatorEventTypes cbType : cbTypes) {
if (cbType.equals(type)) {
cb.startIndicator();
}
}
}
}
private void stopIndicators(ActivityIndicatorEventTypes type) {
for (IActivityIndicatorCallback cb : callbacks) {
ActivityIndicatorEventTypes[] cbTypes = cb.getTypes();
for (ActivityIndicatorEventTypes cbType : cbTypes) {
if (cbType.equals(type)) {
cb.stopIndicator();
}
}
}
}
}
}
package com.climbwithyourfeet.android.activityIndicator;
/**
* enum holding events that an activity indicator will respond to.
*
* @author nichole
*/
public enum ActivityIndicatorEventTypes {
FETCH_GROUP_NAMES, FETCH_MEMBER_NAMES, CREATE_ACCOUNT, FETCH_INVITATIONS;
public static ActivityIndicatorEventTypes resolve(int type) {
switch(type) {
case 0 :
return ActivityIndicatorEventTypes.FETCH_GROUP_NAMES;
case 1 :
return ActivityIndicatorEventTypes.FETCH_MEMBER_NAMES;
case 2 :
return ActivityIndicatorEventTypes.CREATE_ACCOUNT;
case 3 :
return ActivityIndicatorEventTypes.FETCH_INVITATIONS;
}
return null;
}
}
package com.climbwithyourfeet.android.activityIndicator;
/**
* enumeration of actions that the activity view will respond to.
*
* @author nichole
*/
public enum ActivityIndicatorAction {
START_ACTIVITY_INDICATOR_INTENT("com.climbwithyourfeet.android.intent.action.startActivityIndicator"),
STOP_ACTIVITY_INDICATOR_INTENT("com.climbwithyourfeet.android.intent.action.stopActivityIndicator");
private final String action;
ActivityIndicatorAction(String actionName) {
action = actionName;
}
@Override
public String toString() {
return action;
}
}
And you'll need to add the broadcast receiver to AndroidManifest.xml's application
element.
<receiver android:name=".activityIndicator.ActivityIndicatorListener"
android:enabled="true" android:exported="false"
android:label="Activity Indicator Intent Listener" >
<intent-filter>
<action android:name="com.climbwithyourfeet.android.intent.action.startActivityIndicator" />
<action android:name="com.climbwithyourfeet.android.intent.action.stopActivityIndicator" />
</intent-filter>
</receiver>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment