diff --git a/.gitignore b/.gitignore
index 433d269..29932c5 100644
--- a/.gitignore
+++ b/.gitignore
@@ -34,3 +34,5 @@ target/
#Maven release files
*.releaseBackup
*.versionsBackup
+robodemo-lib/project.properties
+robodemo-sample/project.properties
diff --git a/README.md b/README.md
index c62702c..6fabd59 100644
--- a/README.md
+++ b/README.md
@@ -2,8 +2,7 @@ RoboDemo
========
RoboDemo is a ShowCase library for Android to demonstrate to users how a given Activity works.
-
-A sample is available in the [download area](https://github.com/stephanenicolas/RoboDemo/downloads) of the repository.
+Additionally use RoboDemo to walk a user through a first time introduction to the app or show them how to use a new feature.
Screenshots
-----------
@@ -29,18 +28,18 @@ There are some cases where applications require more complex interactions from u
or new interactions not covered by Android UI Guidelines.
RoboDemo eases creating showcases / explaining / demonstrating of such activities to users.
-It will display an overlay activity to illustrate the `Activity` under showcase. The explanations consist of a serie of
+It will display an overlay to illustrate the `Activity` under showcase. The explanations consist of a series of
points to click on and their associated labels. The `Activity` under showcase is dimmed and the showcase highlights
transparent areas to point views or positions users have to click.
-Creation of `DemoActivity` is straightforward, have a look at the sample to put in place RoboDemo in your own app :
+Creation of `DemoFragment` is straightforward, have a look at the sample to put RoboDemo in your own app :
-1. create a `DemoActivity`, using a custom `DrawAdapter`
+1. create a `DemoFragment`, use a custom xml layout
2. in the `Activity` undershowcase, pass views or coordinates and their associated labels.
-RoboDemo has been designed to be convinient.
+RoboDemo can also walk a user through your own app. Great for first time use or explaining a feature that is not used often. Look in the sample to see how to switch from a showcase to a walkthrough.
-To learn more, visit [RoboDemo Starter Guide](https://github.com/stephanenicolas/RoboDemo/wiki/RoboDemo-Starter-Guide) and [browse RoboDemo Javadocs online](http://stephanenicolas.github.com/RoboDemo/apidocs/index.html).
+To learn more, visit [RoboDemo Fragment Starter Guide](https://github.com/ericharlow/RoboDemo/wiki/RoboDemo-FragmentStarterGuide) and [browse RoboDemo Javadocs online](http://stephanenicolas.github.com/RoboDemo/apidocs/index.html).
Customization
-------------
@@ -52,18 +51,7 @@ RoboDemo can be customized in different ways :
* using custom drawable and text locations
* and some more for sure...
-To learn more, visit [RoboDemo Starter Guide](https://github.com/stephanenicolas/RoboDemo/wiki/RoboDemo-Starter-Guide) and [browse RoboDemo Javadocs online](http://stephanenicolas.github.com/RoboDemo/apidocs/index.html).
-
-
-Know limitations
-----------------
-
-The base class for DemoActivity is based on `android.app.Activity`. Unfortunately, this can't cover all inheritance cases for projects
-based on ActionBarSherlock or RoboGuice or a custom Activity base class per project.
-
-In that case, we recommend using all classes from the library as well but rewrite your own `DemoActivity` changing only its super class.
-
-In the case you use ActionBarSherlock, check the code comments, they will give you hints to support ActionBarSherlock themes.
+To learn more, visit [RoboDemo Fragment Starter Guide](https://github.com/ericharlow/RoboDemo/wiki/RoboDemo-FragmentStarterGuide) and [browse RoboDemo Javadocs online](http://stephanenicolas.github.com/RoboDemo/apidocs/index.html).
Modules
-------
diff --git a/robodemo-lib-1.0.0.jar b/robodemo-lib-1.0.0.jar
deleted file mode 100644
index 1a883cb..0000000
Binary files a/robodemo-lib-1.0.0.jar and /dev/null differ
diff --git a/robodemo-lib-2.2.0.jar b/robodemo-lib-2.2.0.jar
new file mode 100644
index 0000000..c214e5f
Binary files /dev/null and b/robodemo-lib-2.2.0.jar differ
diff --git a/robodemo-lib/AndroidManifest.xml b/robodemo-lib/AndroidManifest.xml
index 8c0d0b4..fa0cf5c 100644
--- a/robodemo-lib/AndroidManifest.xml
+++ b/robodemo-lib/AndroidManifest.xml
@@ -4,7 +4,7 @@
android:versionName="1.0" >
+ * boolean neverShowDemoAgain = RoboDemo.isNeverShowAgain( this, demoFragmentId ); + * + * if ( !neverShowDemoAgain ) { + * //create an ArrayList+ * + * @author ericharlow + * + */ +public class DemoFragment extends DialogFragment { + + public static final String TAG = "DemoFragment"; + public final static String DEMO_FRAGMENT_ID = "demo-main-fragment"; + public final static String DEMO_FRAGMENT_SHOW = "demo-fragment-show"; + + private ArrayList< LabeledPoint > listPoints; + private String demoFragmentId; + private int mResourceId; + + private DrawView drawView; + private CheckBox checkBox; + + /** + * Create a DemoFragment using a list of LabeledPoints. + * @param arrayListPoints - list of LabeledPoints to show. + * @return The DemoFragment. + */ + public static DemoFragment newInstance(ArrayList< LabeledPoint > arrayListPoints) { + return newInstance(R.layout.fragment_demo, arrayListPoints); + } + + /** + * Create a customized DemoFragment with a xml layout and supplied list of LabeledPoints. + * @param resource - custom layout. + * @param arrayListPoints - list of LabeledPoints to show. + * @return The DemoFragment. + * @see DrawView + */ + public static DemoFragment newInstance(int resource, ArrayList< LabeledPoint > arrayListPoints) { + DemoFragment f = new DemoFragment(); + f.demoFragmentId = DEMO_FRAGMENT_ID; + f.mResourceId = resource; + f.listPoints = arrayListPoints; + return f; + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setStyle(STYLE_NO_FRAME, R.style.Theme_RoboDemo); + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + + if (mResourceId == 0) { + mResourceId = savedInstanceState.getInt(DEMO_FRAGMENT_ID); + } + + View v = inflater.inflate(mResourceId, null); + + View temp = v.findViewById( R.id.drawView_move_content_demo ); + if (temp != null) { + drawView = (DrawView) temp; + } + + temp = v.findViewById( R.id.checkbox_demo_never_again ); + if (temp != null) + checkBox = (CheckBox) temp; + + temp = v.findViewById(R.id.textview_demo_never_again); + if (temp != null) + temp.setOnClickListener(createDemoNeverAgainListener()); + + temp = v.findViewById(R.id.button_demo_finish); + if (temp != null) + temp.setOnClickListener(createFinishButtonListener()); + + //TODO: add a delegate, and provide the view for custom listeners to be added + + return v; + } + + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + + if (drawView == null) + return; + + drawView.setAnimationListener( new DrawViewAnimationListener() ); + drawView.setDrawViewAdapterLabeledPoints(listPoints); + drawView.setTouchDispatchDelegate(new FragmentTouchDispatchDelegate()); + } + + /** + * Removes any previous fragment identified by the tag. + */ + @Override + public void show(FragmentManager manager, String tag) { + Fragment prev = manager.findFragmentByTag(tag); + if (prev != null) { + FragmentTransaction ft = manager.beginTransaction(); + ft.remove(prev).commit(); + } + super.show(manager, tag); + } + + @Override + public void onSaveInstanceState(Bundle outState) { + outState.putInt(DEMO_FRAGMENT_ID, mResourceId); + super.onSaveInstanceState(outState); + } + + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + Dialog dialog = super.onCreateDialog(savedInstanceState); + dialog.setOnKeyListener(new OnKeyListener() { + + @Override + public boolean onKey(DialogInterface dialog, int keyCode, KeyEvent event) { + if( keyCode == KeyEvent.KEYCODE_BACK){ + finish(null);// need to take care of our mess and state + return true; + } + return false; + } + }); + return dialog; + } + + public void checkNeverShowAgain( View view ) { + checkBox.setChecked( !checkBox.isChecked() ); + } + + public void finish( View view ) { + if ( checkBox != null && checkBox.isChecked() ) { + RoboDemo.setNeverShowAgain(getActivity(), demoFragmentId, true); + } else { + RoboDemo.showAgain(getActivity(), demoFragmentId); + } + + RoboDemo.setShowDemo(getActivity(), DemoFragment.DEMO_FRAGMENT_SHOW, false); + removeSelf(); + } + + private void setButtonsVisible( boolean visible ) { + View view = getView(); + if (view == null) + return; + + final View layoutButtons = view.findViewById( R.id.layout_demo_buttons ); + if (layoutButtons == null) + return; + + int animationResId = visible ? android.R.anim.fade_in : android.R.anim.fade_out; + Animation animation = AnimationUtils.loadAnimation( getActivity(), animationResId ); + animation.setDuration( getResources().getInteger( android.R.integer.config_shortAnimTime ) ); + animation.setAnimationListener( new ButtonsAnimationListener( visible, layoutButtons ) ); + layoutButtons.startAnimation( animation ); + } + + private boolean removeSelf() { + FragmentManager fManager = getFragmentManager(); + fManager.beginTransaction().remove(this).commit(); + return true; + } + + public String getDemoFragmentId() { + return demoFragmentId; + } + + public void setDemoFragmentId(String demoFragmentId) { + this.demoFragmentId = demoFragmentId; + } + + private OnClickListener createFinishButtonListener() { + return new OnClickListener() { + + @Override + public void onClick(View v) { + finish(v); + } + }; + } + + private OnClickListener createDemoNeverAgainListener() { + return new OnClickListener() { + + @Override + public void onClick(View v) { + checkNeverShowAgain(v); + } + }; + } + + /** + * Animate the buttons at the bottom of the screen. + * + * @author sni + * + */ + private final class DrawViewAnimationListener implements AnimationListener { + + @Override + public void onAnimationStart( Animation animation ) { + setButtonsVisible( false ); + } + + @Override + public void onAnimationRepeat( Animation animation ) { + + } + + @Override + public void onAnimationEnd( Animation animation ) { + setButtonsVisible( true ); + } + } + + private final class ButtonsAnimationListener implements AnimationListener { + private final boolean visibleAtEnd; + private final View layoutButtons; + + private ButtonsAnimationListener( boolean visibleAtEnd, View layoutButtons ) { + this.visibleAtEnd = visibleAtEnd; + this.layoutButtons = layoutButtons; + } + + @Override + public void onAnimationStart( Animation animation ) { + layoutButtons.setVisibility( View.VISIBLE ); + } + + @Override + public void onAnimationRepeat( Animation animation ) { + } + + @Override + public void onAnimationEnd( Animation animation ) { + layoutButtons.setVisibility( visibleAtEnd ? View.VISIBLE : View.GONE ); + } + } + + /** + * Gives the Context for the {@link TouchDispatchDelegate}. + * @author ericharlow + */ + private class FragmentTouchDispatchDelegate implements TouchDispatchDelegate { + + @Override + public boolean sendTouchEvent(MotionEvent event) { + View v = getActivity().getWindow().getDecorView(); + if (v != null) + return v.dispatchTouchEvent(event); + else + return false; + } + + } + +} diff --git a/robodemo-lib/src/com/octo/android/robodemo/DrawView.java b/robodemo-lib/src/com/octo/android/robodemo/DrawView.java index aa1371f..31f3071 100644 --- a/robodemo-lib/src/com/octo/android/robodemo/DrawView.java +++ b/robodemo-lib/src/com/octo/android/robodemo/DrawView.java @@ -1,9 +1,12 @@ package com.octo.android.robodemo; import java.lang.ref.WeakReference; +import java.util.List; import android.content.Context; +import android.content.res.TypedArray; import android.graphics.Canvas; +import android.graphics.Color; import android.graphics.Paint; import android.graphics.Point; import android.graphics.PorterDuff.Mode; @@ -13,7 +16,9 @@ import android.os.Handler; import android.os.Message; import android.text.Layout; +import android.text.TextPaint; import android.util.AttributeSet; +import android.view.MotionEvent; import android.view.View; import android.view.animation.Animation.AnimationListener; @@ -22,47 +27,73 @@ * draw. * * @author sni - * + * @author ericharlow */ public class DrawView extends View { private static final int DRAW_UNDER_TEXT_CORNER_RADIUS = 7; + private static final int DEFAULT_FONT_SIZE = 22; /** * The defaut delay between points in animation in ms. */ private static final long DELAY_BETWEEN_POINTS = 2000; + private static final boolean ISAUTOMATEDTESTMODE = false; private DrawViewAdapter drawViewAdapter; - + private int handlerType; private AnimatorHandler handler; private int currentPointPositionToDisplay = 0; - private long delayBetweenPoints = DELAY_BETWEEN_POINTS; - private boolean isShowingAllPointsAtTheEndOfAnimation = true; - private boolean isDrawingOnePointAtATime = false; + private long delayBetweenPoints; + private boolean isShowingAllPointsAtTheEndOfAnimation; + private boolean isDrawingOnePointAtATime; private AnimationListener animationListener; private Paint underTextPaint; private boolean isClearPorterDuffXfermodeEnabled = true; + + private TouchDispatchDelegate mTouchDispatchDelegate; public DrawView( Context context, AttributeSet attrs, int defStyle ) { super( context, attrs, defStyle ); - initializeHandler(); - initUnderTextPaint(); + + if (attrs!=null) { + TypedArray a = context.obtainStyledAttributes(attrs, + R.styleable.DrawView, + defStyle, 0); + TextPaint textPaint = new TextPaint(); + textPaint.setColor(a.getColor(R.styleable.DrawView_textColor, Color.WHITE)); + textPaint.setAntiAlias(a.getBoolean(R.styleable.DrawView_textAntiAlias, true)); + textPaint.setTextSize(a.getDimension(R.styleable.DrawView_textSize, DEFAULT_FONT_SIZE)); + textPaint.setShadowLayer(a.getFloat(R.styleable.DrawView_shadowLayerBlurRadius, 2.0f), + a.getFloat(R.styleable.DrawView_shadowLayerXOffset, 0), + a.getFloat(R.styleable.DrawView_shadowLayerYOffset, 2.0f), + a.getColor(R.styleable.DrawView_shadowLayerColor, Color.BLACK)); + + Drawable drawable = a.getDrawable(R.styleable.DrawView_drawable); + drawViewAdapter = new DefaultDrawViewAdapter(context, drawable, textPaint, null); + + underTextPaint = new Paint(); + underTextPaint.setColor( a.getColor(R.styleable.DrawView_underTextPaintColor, Color.DKGRAY)); + underTextPaint.setAlpha( a.getInt(R.styleable.DrawView_underTextPaintAlpha, 150) ); + + isShowingAllPointsAtTheEndOfAnimation = a.getBoolean(R.styleable.DrawView_isShowingAllPointsAtTheEndOfAnimation, true); + isDrawingOnePointAtATime = a.getBoolean(R.styleable.DrawView_isDrawingOnePointAtATime, false); + delayBetweenPoints = a.getInt(R.styleable.DrawView_delayBetweenPoints, (int) DELAY_BETWEEN_POINTS); + handlerType = a.getInt(R.styleable.DrawView_handlerType, 1); + initializeHandler(handlerType); + a.recycle(); + } } - public DrawView( Context context, AttributeSet attrs ) { - super( context, attrs ); - initializeHandler(); - initUnderTextPaint(); + public DrawView( Context context, AttributeSet attrs ) { + this(context, attrs, 0); } public DrawView( Context context ) { - super( context ); - initializeHandler(); - initUnderTextPaint(); + this(context, null); } @Override @@ -71,7 +102,7 @@ public void onDraw( Canvas canvas ) { if ( isAnimationTerminated() ) { if ( isShowingAllPointsAtTheEndOfAnimation ) { - for ( int index = 0; index < drawViewAdapter.getPointsCount(); index++ ) { + for ( int index = 0; index < getAdapterPointCount(); index++ ) { drawPoint( index, canvas ); } } else { @@ -99,7 +130,7 @@ public void setUnderTextPaint( Paint underTextPaint ) { } public boolean isAnimationTerminated() { - return currentPointPositionToDisplay >= getDrawViewAdapter().getPointsCount() - 1; + return currentPointPositionToDisplay >= getAdapterPointCount() - 1; } /** @@ -119,13 +150,17 @@ public void resetAnimation() { */ public void terminateAnimation() { handler.removeMessages( AnimatorHandler.ANIMATION_MESSAGE_ID ); - currentPointPositionToDisplay = getDrawViewAdapter().getPointsCount() - 1; + currentPointPositionToDisplay = getAdapterPointCount() - 1; if ( animationListener != null ) { animationListener.onAnimationEnd( null ); } refreshDrawableState(); invalidate(); } + + private int getAdapterPointCount() { + return drawViewAdapter == null ? 0 : drawViewAdapter.getPointsCount(); + } public void setDrawViewAdapter( DrawViewAdapter drawViewAdapter ) { this.drawViewAdapter = drawViewAdapter; @@ -134,6 +169,17 @@ public void setDrawViewAdapter( DrawViewAdapter drawViewAdapter ) { public DrawViewAdapter getDrawViewAdapter() { return drawViewAdapter; } + + /** + * Supply the data to the Adapter. + * @see DefaultDrawViewAdapter + * @param listPoints - the data. + */ + public void setDrawViewAdapterLabeledPoints(List< LabeledPoint > listPoints) { + ((DefaultDrawViewAdapter) drawViewAdapter).setListPoints(listPoints); + if (currentPointPositionToDisplay > getAdapterPointCount() - 1) // almost the same as isAnimationTerminated() + terminateAnimation(); + } /** * Sets the delay between animation of two points. @@ -143,7 +189,8 @@ public DrawViewAdapter getDrawViewAdapter() { */ public void setDelayBetweenPoints( long delayBetweenPoints ) { this.delayBetweenPoints = delayBetweenPoints; - initializeHandler(); + handlerType = 1; + initializeHandler(handlerType); } public long getDelayBetweenPoints() { @@ -193,19 +240,19 @@ public AnimationListener getAnimationListener() { return animationListener; } - private void initializeHandler() { - handler = new AnimatorHandler( this, delayBetweenPoints ); - } - - private void initUnderTextPaint() { - underTextPaint = new Paint(); - underTextPaint.setColor( getResources().getColor( android.R.color.darker_gray ) ); - underTextPaint.setAlpha( 150 ); + private void initializeHandler(int type) { + handler = new AnimatorHandler( this, delayBetweenPoints ); // since there are no null checks for handler + + if (type == 1) { + handler.sendEmptyMessageDelayed(); + } else if (type == 2) { + + } } private void showNextPoint() { - if ( currentPointPositionToDisplay < getDrawViewAdapter().getPointsCount() - 1 ) { + if ( currentPointPositionToDisplay < getAdapterPointCount() - 1 ) { currentPointPositionToDisplay++; if ( isAnimationTerminated() && animationListener != null ) { @@ -227,6 +274,7 @@ private void showNextPoint() { protected void drawPoint( int position, Canvas canvas ) { drawText( position, canvas ); drawDrawable( position, canvas ); + updateContentDescription(position); } /** @@ -308,7 +356,67 @@ protected void doUseClearPorterDuffXfermode( Canvas canvas, Drawable drawable ) } } + @Override + public boolean dispatchTouchEvent(MotionEvent event) { + if (ISAUTOMATEDTESTMODE) + event = putTouchEventInLabledPoint(event, drawViewAdapter.getDrawableAt( currentPointPositionToDisplay )); + + if (isTouchHandler() && isTouchEventInLabeledPoint(event, drawViewAdapter.getDrawableAt( currentPointPositionToDisplay ))) { + if (event.getAction() == MotionEvent.ACTION_UP) + showNextPoint(); + if (mTouchDispatchDelegate != null) + return mTouchDispatchDelegate.sendTouchEvent(event); + else + return true; + } + + return super.dispatchTouchEvent(event); + } + /** + * Allow UiAutomator to know the text of the current point through content description. + * @param - position of the {@link LabeledPoint}. + */ + private void updateContentDescription(int position) { + String text = drawViewAdapter.getTextAt(position); + setContentDescription(text); + } + + private boolean isTouchHandler() { + return handlerType == 2 || isTouchDrivenAnimationHandler(); + } + + private boolean isTouchDrivenAnimationHandler() { + return handlerType == 3; + } + + private boolean isTouchEventInLabeledPoint(MotionEvent event, Drawable drawable) { + float x = event.getX(); + float y = event.getY(); + int center_x = drawable.getBounds().centerX(); + int center_y = drawable.getBounds().centerY(); + int radius = drawable.getIntrinsicWidth() / 2 - 3; + + //change < to <= to include points on circle + boolean result = Math.pow((x - center_x), 2) + Math.pow((y - center_y), 2) < Math.pow(radius, 2); + return result; + } + + private MotionEvent putTouchEventInLabledPoint(MotionEvent event, Drawable drawable) { + event.setLocation(drawable.getBounds().centerX(), drawable.getBounds().centerY()); + return event; + } + + public TouchDispatchDelegate getTouchDispatchDelegate() { + return mTouchDispatchDelegate; + } + + public void setTouchDispatchDelegate(TouchDispatchDelegate touchDispatchDelegate) { + if (!isTouchDrivenAnimationHandler()) + this.mTouchDispatchDelegate = touchDispatchDelegate; + } + + /** * Animate the point to draw on screen. * * @author sni @@ -323,7 +431,10 @@ private static final class AnimatorHandler extends Handler { private AnimatorHandler( DrawView drawView, long delayBetweenPoints ) { this.weakReference = new WeakReference< DrawView >( drawView ); this.delayBetweenPoints = delayBetweenPoints; - sendEmptyMessageDelayed( AnimatorHandler.ANIMATION_MESSAGE_ID, delayBetweenPoints ); + } + + public void sendEmptyMessageDelayed() { + sendEmptyMessageDelayed( AnimatorHandler.ANIMATION_MESSAGE_ID, delayBetweenPoints ); } @Override diff --git a/robodemo-lib/src/com/octo/android/robodemo/DrawViewAdapter.java b/robodemo-lib/src/com/octo/android/robodemo/DrawViewAdapter.java index dfef611..3afcdfa 100644 --- a/robodemo-lib/src/com/octo/android/robodemo/DrawViewAdapter.java +++ b/robodemo-lib/src/com/octo/android/robodemo/DrawViewAdapter.java @@ -50,5 +50,12 @@ public interface DrawViewAdapter { * @return the {@link Layout} to use when rendering the text of the {@link LabeledPoint} at a given position. */ public Layout getTextLayoutAt( int position ); + + /** + * Get the text of the {@link LabeledPoint} at position. + * @param position - the position of the {@link LabeledPoint} to render. + * @return the text of the {@link LabeledPoint}. + */ + public String getTextAt(int position); } \ No newline at end of file diff --git a/robodemo-lib/src/com/octo/android/robodemo/LabeledPoint.java b/robodemo-lib/src/com/octo/android/robodemo/LabeledPoint.java index 5b34347..73c01da 100644 --- a/robodemo-lib/src/com/octo/android/robodemo/LabeledPoint.java +++ b/robodemo-lib/src/com/octo/android/robodemo/LabeledPoint.java @@ -1,5 +1,7 @@ package com.octo.android.robodemo; +import android.annotation.SuppressLint; +import android.annotation.TargetApi; import android.app.Activity; import android.graphics.Point; import android.graphics.drawable.Drawable; @@ -8,6 +10,7 @@ import android.os.Parcelable; import android.view.Display; import android.view.View; +import android.view.View.OnLayoutChangeListener; /** * A pojo class that wraps all information needed to display a point on screen. {@link LabeledPoint} embed a position @@ -16,17 +19,25 @@ * Activity to illustrate and the {@link DemoActivity}. * * @author sni - * + * @author ericharlow */ public class LabeledPoint extends Point implements Parcelable { /** The text associated to this point. */ private String text; + + //experimental + private boolean usePreferredSize = false; + private int preferredWidth; + private int preferredHeight; /** * Creates an empty {@link LabeledPoint}. */ public LabeledPoint() { + text=""; + x = -100; + y = -100; } /** @@ -87,7 +98,7 @@ public LabeledPoint( Point src, String text ) { * the view on which to center the point. */ public LabeledPoint( View v ) { - this( v, 50, 50, null ); + this( v, 50, 50, null, true); } /** @@ -99,7 +110,7 @@ public LabeledPoint( View v ) { * the new text of the point. */ public LabeledPoint( View v, String text ) { - this( v, 50, 0, text ); + this( v, 50, 50, text, true); } /** @@ -115,7 +126,7 @@ public LabeledPoint( View v, String text ) { * */ public LabeledPoint( View v, float widthPercent, float heightPercent ) { - this( v, widthPercent, heightPercent, null ); + this( v, widthPercent, heightPercent, null, true); } /** @@ -131,14 +142,47 @@ public LabeledPoint( View v, float widthPercent, float heightPercent ) { * * @param text * the new text of the point. + * + * @param preferredSize + * whether to use the size of the view for the drawable size. */ - public LabeledPoint( View v, float widthPercent, float heightPercent, String text ) { - int[] location = new int[ 2 ]; - v.getLocationOnScreen( location ); - this.x = location[ 0 ] + Math.round( widthPercent * v.getMeasuredWidth() / 100 ); - this.y = location[ 1 ] + Math.round( heightPercent * v.getMeasuredHeight() / 100 ); + public LabeledPoint( View v, final float widthPercent, final float heightPercent, String text, boolean preferredSize) { + if (v != null) { + usePreferredSize = preferredSize; + setMeasuredLocation(widthPercent, heightPercent, v); + + v.addOnLayoutChangeListener(new OnLayoutChangeListener() { + + @Override + public void onLayoutChange(View v, int left, int top, int right, + int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) { + setMeasuredLocation(widthPercent, heightPercent, v); + if (usePreferredSize) + setPreferredSize(v.getMeasuredWidth(), v.getMeasuredHeight()); + v.removeOnLayoutChangeListener(this); + } + }); + } setText( text ); } + + protected void setPreferredSize(int measuredWidth, int measuredHeight) { + // use min because of DrawView.doUseClearPorterDuffXfermode uses canvas.drawCircle + int min = Math.min(measuredWidth, measuredHeight); + preferredWidth = min; + preferredHeight = min; + } + + /** + * Creates a {@link LabeledPoint} positioned relatively to a given activity, with a given text. + * @param activity - the view on which to center the point. + * @param widthPercent - the percent of the view width at which to place the new point. + * @param heightPercent - the percent of the view height at which to place the new point. + * @param stringID - reference to the new text of the point. + */ + public LabeledPoint(Activity activity, float widthPercent, float heightPercent, int stringID) { + this(activity, widthPercent, heightPercent, activity.getString(stringID)); + } /** * Creates a {@link LabeledPoint} positioned relatively to a given activity, with a given text. @@ -154,21 +198,12 @@ public LabeledPoint( View v, float widthPercent, float heightPercent, String tex * @param text * the new text of the point. */ - @SuppressWarnings("deprecation") public LabeledPoint( Activity activity, float widthPercent, float heightPercent, String text ) { Display display = activity.getWindowManager().getDefaultDisplay(); - int screenWidth = 0; - int screenHeight = 0; - if ( Build.VERSION.SDK_INT > Build.VERSION_CODES.HONEYCOMB_MR2 ) { - Point outSize = new Point(); - display.getSize( outSize ); - screenHeight = outSize.y; - screenWidth = outSize.x; - } else { - screenWidth = display.getWidth(); - screenHeight = display.getHeight(); - } + int screenWidth = getScreenWidth(display); + int screenHeight = getScreenHeight(display); + x = (int) ( screenWidth * widthPercent ); y = (int) ( screenHeight * heightPercent ); setText( text ); @@ -189,6 +224,53 @@ public LabeledPoint( Activity activity, float widthPercent, float heightPercent, public LabeledPoint( Activity activity, float widthPercent, float heightPercent ) { this( activity, widthPercent, heightPercent, null ); } + + /** + * Creates a {@link LabeledPoint} located at the center of a given view, with a given text. + * @param activity - the context for the view. + * @param referenceID - the resource id for the view. + * @param stringID - the resource id for the string. + */ + public LabeledPoint(Activity activity, int referenceID, int stringID) { + this(activity.findViewById(referenceID), activity.getString(stringID)); + } + + /** + * Creates a {@link LabeledPoint} at a given location, with a null text. + * + * @param activity - the view on which to center the point. + * @param x - the new x coordinate of the point. + * @param y - the new y coordinate of the point. + * @param stringID - reference to the new text of the point. + */ + public LabeledPoint(Activity activity, int x, int y, int stringID ) { + this( x, y, activity.getString(stringID) ); + } + + /** + * Creates a {@link LabeledPoint} positioned relatively to a given view, with a given text. + * @param activity - the context for the view. + * @param widthPercent - the percent of the view width at which to place the new point. + * @param heightPercent - the percent of the view height at which to place the new point. + * @param referenceID - the resource id for the view. + * @param stringID - the resource id for the string. + */ + public LabeledPoint(Activity activity, float widthPercent, float heightPercent, int referenceID, int stringID) { + this(activity.findViewById(referenceID), widthPercent, heightPercent, activity.getString(stringID), true); + } + + /** + * Creates a {@link LabeledPoint} positioned relatively to a given view, with a given text. + * @param activity - the context for the view. + * @param widthPercent - the percent of the view width at which to place the new point. + * @param heightPercent - the percent of the view height at which to place the new point. + * @param referenceID - the resource id for the view. + * @param stringID - the resource id for the string. + * @param preferredSize - use the size from the view for the size of the drawable. + */ + public LabeledPoint(Activity activity, float widthPercent, float heightPercent, int referenceID, int stringID, boolean preferredSize) { + this(activity.findViewById(referenceID), widthPercent, heightPercent, activity.getString(stringID), preferredSize); + } public String getText() { return text; @@ -208,6 +290,9 @@ public void setText( String text ) { public void writeToParcel( Parcel out, int flags ) { out.writeInt( x ); out.writeInt( y ); + out.writeInt(preferredHeight); + out.writeInt(preferredWidth); + out.writeByte((byte) (usePreferredSize ? 1 : 0)); out.writeString( text ); } @@ -218,7 +303,8 @@ public void writeToParcel( Parcel out, int flags ) { /** * Return a new point from the data in the specified parcel. */ - @Override + @SuppressLint("NewApi") + @Override public LabeledPoint createFromParcel( Parcel in ) { LabeledPoint r = new LabeledPoint(); r.readFromParcel( in ); @@ -241,10 +327,73 @@ public LabeledPoint[] newArray( int size ) { * @param in * The parcel to read the point's coordinates from */ - @Override public void readFromParcel( Parcel in ) { x = in.readInt(); y = in.readInt(); + preferredHeight = in.readInt(); + preferredWidth = in.readInt(); + usePreferredSize = in.readByte() != 0; text = in.readString(); } + + private void setMeasuredLocation(final float widthPercent, + final float heightPercent, View v) { + int[] location = new int[ 2 ]; + v.getLocationOnScreen( location ); + x = location[ 0 ] + Math.round( widthPercent * v.getMeasuredWidth() / 100 ); + y = location[ 1 ] + Math.round( heightPercent * v.getMeasuredHeight() / 100 ); + } + + @TargetApi(Build.VERSION_CODES.HONEYCOMB_MR2) + @SuppressWarnings("deprecation") + private int getScreenWidth(Display display) { + int screenWidth; + if ( Build.VERSION.SDK_INT > Build.VERSION_CODES.HONEYCOMB_MR2 ) { + Point outSize = new Point(); + display.getSize( outSize ); + screenWidth = outSize.x; + } else { + screenWidth = display.getWidth(); + } + return screenWidth; + } + + @TargetApi(Build.VERSION_CODES.HONEYCOMB_MR2) + @SuppressWarnings("deprecation") + private int getScreenHeight(Display display) { + int screenHeight; + if ( Build.VERSION.SDK_INT > Build.VERSION_CODES.HONEYCOMB_MR2 ) { + Point outSize = new Point(); + display.getSize( outSize ); + screenHeight = outSize.y; + } else { + screenHeight = display.getHeight(); + } + return screenHeight; + } + + //experimental + public int getPreferredWidth() { + return preferredWidth; + } + + public void setPreferredWidth(int preferedWidth) { + this.preferredWidth = preferedWidth; + } + + public int getPreferredHeight() { + return preferredHeight; + } + + public void setPreferredHeight(int preferedHeight) { + this.preferredHeight = preferedHeight; + } + + public boolean doUsePreferredSize() { + return usePreferredSize; + } + + public void setUsePreferredSize(boolean usePreferredSize) { + this.usePreferredSize = usePreferredSize; + } } \ No newline at end of file diff --git a/robodemo-lib/src/com/octo/android/robodemo/RoboDemo.java b/robodemo-lib/src/com/octo/android/robodemo/RoboDemo.java index f18109c..bc28ba8 100644 --- a/robodemo-lib/src/com/octo/android/robodemo/RoboDemo.java +++ b/robodemo-lib/src/com/octo/android/robodemo/RoboDemo.java @@ -3,8 +3,11 @@ import java.util.ArrayList; import android.app.Activity; +import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; +import android.content.SharedPreferences.Editor; +import android.preference.PreferenceManager; public class RoboDemo { @@ -52,4 +55,39 @@ public static boolean isNeverShowAgain( Activity caller, String demoActivityId ) public static boolean showAgain( Activity caller, String demoActivityId ) { return caller.getSharedPreferences( SHARED_PREFERENCE_NAME, Activity.MODE_PRIVATE ).edit().remove( demoActivityId ).commit(); } + + /** + * Should the Demo be shown. + * @param caller - the activity that is calling the demo. It will be used internally to get a {@link SharedPreferences}. + * @param demoActivityId - the id that will be used to store the information about showing the demo. + * @return true if the demo should be shown. + */ + public static boolean shouldShowDemo(Activity caller, String demoActivityId) { + SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(caller.getApplicationContext()); + return settings.getBoolean( demoActivityId, false ); + } + + /** + * Set whether the demo should be shown. + * @param caller - the activity that is calling the demo. It will be used internally to get a {@link SharedPreferences}. + * @param demoActivityId - the id that will be used to store the information about showing the demo. + * @param value - the value for whether the demo should be shown. + */ + public static void setShowDemo(Activity caller, String demoActivityId, boolean value) { + SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(caller.getApplicationContext()); + settings.edit().putBoolean(demoActivityId, value).commit(); + } + + /** + * + * @param caller - the activity that is calling the {@link DemoActivity}. Its {@link SharedPreferences} will be used + * internally by the {@link DemoActivity}. + * @param demoActivityId - the id that will be used to store the information about the 'never show again' checkbox. + * @param value + */ + public static void setNeverShowAgain(Activity caller, String demoActivityId, boolean value) { + Editor editor = caller.getSharedPreferences( SHARED_PREFERENCE_NAME, Context.MODE_PRIVATE ).edit(); + editor = editor.putBoolean( demoActivityId, value ); + editor.commit(); + } } diff --git a/robodemo-lib/src/com/octo/android/robodemo/TouchDispatchDelegate.java b/robodemo-lib/src/com/octo/android/robodemo/TouchDispatchDelegate.java new file mode 100644 index 0000000..7f7cc02 --- /dev/null +++ b/robodemo-lib/src/com/octo/android/robodemo/TouchDispatchDelegate.java @@ -0,0 +1,18 @@ +package com.octo.android.robodemo; + +import android.view.MotionEvent; + +/** + * Provide a way for Touch Events to be passed to other views. + * @author ericharlow + */ +public interface TouchDispatchDelegate { + + /** + * Send MotionEvents to a delegate. + * Intend to send from a Fragment's custom semitranslucent view to the Activity beneath the Fragment. + * @param event - The motion event to be dispatched. + * @return True if the event was handled by the view, false otherwise. + */ + public boolean sendTouchEvent(MotionEvent event); +} diff --git a/robodemo-sample/AndroidManifest.xml b/robodemo-sample/AndroidManifest.xml index 57abe71..886f879 100644 --- a/robodemo-sample/AndroidManifest.xml +++ b/robodemo-sample/AndroidManifest.xml @@ -4,7 +4,7 @@ android:versionName="1.0" >named arrayListPoints. + * + * DemoFragment f = DemoFragment.newInstance(arrayListPoints); + * f.show(getFragmentManager(), DemoFragment.TAG); + * } + *