From 74e22f11bc15c74e9b38d599c82a6e99410929da Mon Sep 17 00:00:00 2001 From: parkin1 Date: Sun, 19 Jan 2014 14:09:40 -0500 Subject: [PATCH 1/3] added ability to put a shadow below the stuck view. Updated the README and the sample to reflect this. --- README.md | 24 ++++++- library/project.properties | 2 +- library/res/values/attrs.xml | 10 +++ .../StickyScrollView.java | 62 ++++++++++++++++++- sample/project.properties | 4 +- sample/res/drawable/sticky_shadow_default.xml | 12 ++++ sample/res/layout/main.xml | 4 +- .../samples/TestActivity.java | 11 +++- 8 files changed, 123 insertions(+), 6 deletions(-) create mode 100644 library/res/values/attrs.xml create mode 100644 sample/res/drawable/sticky_shadow_default.xml diff --git a/README.md b/README.md index 65f526f..6c0a0e2 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,8 @@ So you go from this: to this: ```xml + android:layout_height="match_parent" android:layout_width="match_parent" + android:id="@+id/sticky_scroll"> ``` @@ -32,6 +33,7 @@ to this: As with a regular `ScrollView` you are only allowed one child. But that child can contain any number of children. It is these children or any of their children that can be tagged as a sticky view. If you want t view to stick to the top when you scroll passed it add a `sticky` tag with the `android:tag` attribute to it like this: ```xml + + +``` + +These shadow height and drawable can also be set programatically. Note that, unlike the xml attribute, `setShadowHeight(pixels)` only takes the values in pixels. +```java +StickyScrollView stickyScroll = (StickyScrollView) findViewById(R.id.sticky_scroll); +stickyScroll.setShadowDrawable(getResources().getDrawable( + R.drawable.shadow_drawable)); +stickyScroll.setShadowHeight(50); // in pixels +``` diff --git a/library/project.properties b/library/project.properties index 36f1594..91d2b02 100644 --- a/library/project.properties +++ b/library/project.properties @@ -11,5 +11,5 @@ #proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt # Project target. -target=android-15 +target=android-19 android.library=true diff --git a/library/res/values/attrs.xml b/library/res/values/attrs.xml new file mode 100644 index 0000000..217be86 --- /dev/null +++ b/library/res/values/attrs.xml @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/library/src/com/emilsjolander/components/StickyScrollViewItems/StickyScrollView.java b/library/src/com/emilsjolander/components/StickyScrollViewItems/StickyScrollView.java index b23ca12..2330117 100644 --- a/library/src/com/emilsjolander/components/StickyScrollViewItems/StickyScrollView.java +++ b/library/src/com/emilsjolander/components/StickyScrollViewItems/StickyScrollView.java @@ -3,7 +3,9 @@ import java.util.ArrayList; import android.content.Context; +import android.content.res.TypedArray; import android.graphics.Canvas; +import android.graphics.drawable.Drawable; import android.os.Build; import android.util.AttributeSet; import android.view.MotionEvent; @@ -33,14 +35,23 @@ public class StickyScrollView extends ScrollView { * Flag for views that have aren't fully opaque */ public static final String FLAG_HASTRANSPARANCY = "-hastransparancy"; + + /** + * Default height of the shadow peeking out below the stuck view. + */ + private static final int DEFAULT_SHADOW_HEIGHT = 10; // dp; private ArrayList stickyViews; private View currentlyStickingView; private float stickyViewTopOffset; + private float stickyViewLeftOffset; private boolean redirectTouchesToStickyView; private boolean clippingToPadding; private boolean clipToPaddingHasBeenSet; + private int mShadowHeight; + private Drawable mShadowDrawable; + private final Runnable invalidateRunnable = new Runnable() { @Override @@ -67,7 +78,40 @@ public StickyScrollView(Context context, AttributeSet attrs) { public StickyScrollView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); setup(); + + + + TypedArray a = context.obtainStyledAttributes(attrs, + R.styleable.StickyScrollView, defStyle, 0); + + final float density = context.getResources().getDisplayMetrics().density; + int defaultShadowHeightInPix = (int) (DEFAULT_SHADOW_HEIGHT * density + 0.5f); + + mShadowHeight = a.getDimensionPixelSize( + R.styleable.StickyScrollView_stuckShadowHeight, + defaultShadowHeightInPix); + + int shadowDrawableRes = a.getResourceId( + R.styleable.StickyScrollView_stuckShadowDrawable, -1); + + if (shadowDrawableRes != -1) { + mShadowDrawable = context.getResources().getDrawable( + shadowDrawableRes); + } + + a.recycle(); + + } + + /** + * Sets the height of the shadow drawable in pixels. + * + * @param height + */ + public void setShadowHeight(int height) { + mShadowHeight = height; } + public void setup(){ stickyViews = new ArrayList(); @@ -160,7 +204,21 @@ protected void dispatchDraw(Canvas canvas) { super.dispatchDraw(canvas); if(currentlyStickingView != null){ canvas.save(); - canvas.translate(getPaddingLeft(), getScrollY() + stickyViewTopOffset + (clippingToPadding ? getPaddingTop() : 0)); + canvas.translate(getPaddingLeft() + stickyViewLeftOffset, getScrollY() + stickyViewTopOffset + (clippingToPadding ? getPaddingTop() : 0)); + + canvas.clipRect(0, (clippingToPadding ? -stickyViewTopOffset : 0), + getWidth() - stickyViewLeftOffset, + currentlyStickingView.getHeight() + mShadowHeight + 1); + + if (mShadowDrawable != null) { + int left = 0; + int right = currentlyStickingView.getWidth(); + int top = currentlyStickingView.getHeight(); + int bottom = currentlyStickingView.getHeight() + mShadowHeight; + mShadowDrawable.setBounds(left, top, right, bottom); + mShadowDrawable.draw(canvas); + } + canvas.clipRect(0, (clippingToPadding ? -stickyViewTopOffset : 0), getWidth(), currentlyStickingView.getHeight()); if(getStringTagForView(currentlyStickingView).contains(FLAG_HASTRANSPARANCY)){ showView(currentlyStickingView); @@ -249,6 +307,8 @@ private void doTheStickyThing() { if(currentlyStickingView!=null){ stopStickingCurrentlyStickingView(); } + // only compute left offset when we start sticking. + stickyViewLeftOffset = getLeftForViewRelativeOnlyChild(viewThatShouldStick); startStickingView(viewThatShouldStick); } }else if(currentlyStickingView!=null){ diff --git a/sample/project.properties b/sample/project.properties index 22d0dca..87b33ac 100644 --- a/sample/project.properties +++ b/sample/project.properties @@ -11,4 +11,6 @@ #proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt # Project target. -target=android-7 +target=android-19 +android.library=false +android.library.reference.1=../library diff --git a/sample/res/drawable/sticky_shadow_default.xml b/sample/res/drawable/sticky_shadow_default.xml new file mode 100644 index 0000000..461be66 --- /dev/null +++ b/sample/res/drawable/sticky_shadow_default.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/sample/res/layout/main.xml b/sample/res/layout/main.xml index 7401359..607279f 100644 --- a/sample/res/layout/main.xml +++ b/sample/res/layout/main.xml @@ -1,7 +1,9 @@ - \ No newline at end of file + diff --git a/sample/src/com/emilsjolander/components/StickyScrollViewItems/samples/TestActivity.java b/sample/src/com/emilsjolander/components/StickyScrollViewItems/samples/TestActivity.java index 9946be2..7da3959 100644 --- a/sample/src/com/emilsjolander/components/StickyScrollViewItems/samples/TestActivity.java +++ b/sample/src/com/emilsjolander/components/StickyScrollViewItems/samples/TestActivity.java @@ -26,5 +26,14 @@ public void onClick(View v) { Toast.makeText(getApplicationContext(), "hej", Toast.LENGTH_SHORT).show(); } }); + + /** + * Below shows setting the scroll view shadow properties programatically. + */ + // StickyScrollView scrollView = (StickyScrollView) + // findViewById(R.id.ScrollView); + // scrollView.setShadowDrawable(getResources().getDrawable( + // R.drawable.sticky_shadow_default)); + // scrollView.setShadowHeight(height); } -} \ No newline at end of file +} From a602d5ae956c4f51d3ad4804687be98797f1da1a Mon Sep 17 00:00:00 2001 From: parkin1 Date: Sun, 19 Jan 2014 17:04:20 -0500 Subject: [PATCH 2/3] made the left offset an Integer --- .../components/StickyScrollViewItems/StickyScrollView.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/src/com/emilsjolander/components/StickyScrollViewItems/StickyScrollView.java b/library/src/com/emilsjolander/components/StickyScrollViewItems/StickyScrollView.java index 2330117..eee1b64 100644 --- a/library/src/com/emilsjolander/components/StickyScrollViewItems/StickyScrollView.java +++ b/library/src/com/emilsjolander/components/StickyScrollViewItems/StickyScrollView.java @@ -44,7 +44,7 @@ public class StickyScrollView extends ScrollView { private ArrayList stickyViews; private View currentlyStickingView; private float stickyViewTopOffset; - private float stickyViewLeftOffset; + private int stickyViewLeftOffset; private boolean redirectTouchesToStickyView; private boolean clippingToPadding; private boolean clipToPaddingHasBeenSet; From e074d56beac5e82534c14c87f4f733faa69dac1d Mon Sep 17 00:00:00 2001 From: parkin1 Date: Sun, 19 Jan 2014 14:09:40 -0500 Subject: [PATCH 3/3] added ability to put a shadow below the stuck view. Updated the README and the sample to reflect this. --- README.md | 24 +++++++- library/res/values/attrs.xml | 10 ++++ .../StickyScrollView.java | 60 ++++++++++++++++++- sample/project.properties | 4 +- sample/res/drawable/sticky_shadow_default.xml | 12 ++++ sample/res/layout/main.xml | 4 +- .../samples/TestActivity.java | 11 +++- 7 files changed, 119 insertions(+), 6 deletions(-) create mode 100644 library/res/values/attrs.xml create mode 100644 sample/res/drawable/sticky_shadow_default.xml diff --git a/README.md b/README.md index 65f526f..6c0a0e2 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,8 @@ So you go from this: to this: ```xml + android:layout_height="match_parent" android:layout_width="match_parent" + android:id="@+id/sticky_scroll"> ``` @@ -32,6 +33,7 @@ to this: As with a regular `ScrollView` you are only allowed one child. But that child can contain any number of children. It is these children or any of their children that can be tagged as a sticky view. If you want t view to stick to the top when you scroll passed it add a `sticky` tag with the `android:tag` attribute to it like this: ```xml + + +``` + +These shadow height and drawable can also be set programatically. Note that, unlike the xml attribute, `setShadowHeight(pixels)` only takes the values in pixels. +```java +StickyScrollView stickyScroll = (StickyScrollView) findViewById(R.id.sticky_scroll); +stickyScroll.setShadowDrawable(getResources().getDrawable( + R.drawable.shadow_drawable)); +stickyScroll.setShadowHeight(50); // in pixels +``` diff --git a/library/res/values/attrs.xml b/library/res/values/attrs.xml new file mode 100644 index 0000000..217be86 --- /dev/null +++ b/library/res/values/attrs.xml @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/library/src/com/emilsjolander/components/StickyScrollViewItems/StickyScrollView.java b/library/src/com/emilsjolander/components/StickyScrollViewItems/StickyScrollView.java index d9a9289..6b7984d 100644 --- a/library/src/com/emilsjolander/components/StickyScrollViewItems/StickyScrollView.java +++ b/library/src/com/emilsjolander/components/StickyScrollViewItems/StickyScrollView.java @@ -3,7 +3,9 @@ import java.util.ArrayList; import android.content.Context; +import android.content.res.TypedArray; import android.graphics.Canvas; +import android.graphics.drawable.Drawable; import android.os.Build; import android.util.AttributeSet; import android.view.MotionEvent; @@ -33,6 +35,11 @@ public class StickyScrollView extends ScrollView { * Flag for views that have aren't fully opaque */ public static final String FLAG_HASTRANSPARANCY = "-hastransparancy"; + + /** + * Default height of the shadow peeking out below the stuck view. + */ + private static final int DEFAULT_SHADOW_HEIGHT = 10; // dp; private ArrayList stickyViews; private View currentlyStickingView; @@ -42,6 +49,9 @@ public class StickyScrollView extends ScrollView { private boolean clippingToPadding; private boolean clipToPaddingHasBeenSet; + private int mShadowHeight; + private Drawable mShadowDrawable; + private final Runnable invalidateRunnable = new Runnable() { @Override @@ -68,7 +78,40 @@ public StickyScrollView(Context context, AttributeSet attrs) { public StickyScrollView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); setup(); + + + + TypedArray a = context.obtainStyledAttributes(attrs, + R.styleable.StickyScrollView, defStyle, 0); + + final float density = context.getResources().getDisplayMetrics().density; + int defaultShadowHeightInPix = (int) (DEFAULT_SHADOW_HEIGHT * density + 0.5f); + + mShadowHeight = a.getDimensionPixelSize( + R.styleable.StickyScrollView_stuckShadowHeight, + defaultShadowHeightInPix); + + int shadowDrawableRes = a.getResourceId( + R.styleable.StickyScrollView_stuckShadowDrawable, -1); + + if (shadowDrawableRes != -1) { + mShadowDrawable = context.getResources().getDrawable( + shadowDrawableRes); + } + + a.recycle(); + + } + + /** + * Sets the height of the shadow drawable in pixels. + * + * @param height + */ + public void setShadowHeight(int height) { + mShadowHeight = height; } + public void setup(){ stickyViews = new ArrayList(); @@ -161,8 +204,21 @@ protected void dispatchDraw(Canvas canvas) { super.dispatchDraw(canvas); if(currentlyStickingView != null){ canvas.save(); - canvas.translate(getPaddingLeft() + stickyViewLeftOffset, - getScrollY() + stickyViewTopOffset + (clippingToPadding ? getPaddingTop() : 0)); + canvas.translate(getPaddingLeft() + stickyViewLeftOffset, getScrollY() + stickyViewTopOffset + (clippingToPadding ? getPaddingTop() : 0)); + + canvas.clipRect(0, (clippingToPadding ? -stickyViewTopOffset : 0), + getWidth() - stickyViewLeftOffset, + currentlyStickingView.getHeight() + mShadowHeight + 1); + + if (mShadowDrawable != null) { + int left = 0; + int right = currentlyStickingView.getWidth(); + int top = currentlyStickingView.getHeight(); + int bottom = currentlyStickingView.getHeight() + mShadowHeight; + mShadowDrawable.setBounds(left, top, right, bottom); + mShadowDrawable.draw(canvas); + } + canvas.clipRect(0, (clippingToPadding ? -stickyViewTopOffset : 0), getWidth(), currentlyStickingView.getHeight()); if(getStringTagForView(currentlyStickingView).contains(FLAG_HASTRANSPARANCY)){ showView(currentlyStickingView); diff --git a/sample/project.properties b/sample/project.properties index 22d0dca..87b33ac 100644 --- a/sample/project.properties +++ b/sample/project.properties @@ -11,4 +11,6 @@ #proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt # Project target. -target=android-7 +target=android-19 +android.library=false +android.library.reference.1=../library diff --git a/sample/res/drawable/sticky_shadow_default.xml b/sample/res/drawable/sticky_shadow_default.xml new file mode 100644 index 0000000..461be66 --- /dev/null +++ b/sample/res/drawable/sticky_shadow_default.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/sample/res/layout/main.xml b/sample/res/layout/main.xml index 7401359..607279f 100644 --- a/sample/res/layout/main.xml +++ b/sample/res/layout/main.xml @@ -1,7 +1,9 @@ - \ No newline at end of file + diff --git a/sample/src/com/emilsjolander/components/StickyScrollViewItems/samples/TestActivity.java b/sample/src/com/emilsjolander/components/StickyScrollViewItems/samples/TestActivity.java index 9946be2..7da3959 100644 --- a/sample/src/com/emilsjolander/components/StickyScrollViewItems/samples/TestActivity.java +++ b/sample/src/com/emilsjolander/components/StickyScrollViewItems/samples/TestActivity.java @@ -26,5 +26,14 @@ public void onClick(View v) { Toast.makeText(getApplicationContext(), "hej", Toast.LENGTH_SHORT).show(); } }); + + /** + * Below shows setting the scroll view shadow properties programatically. + */ + // StickyScrollView scrollView = (StickyScrollView) + // findViewById(R.id.ScrollView); + // scrollView.setShadowDrawable(getResources().getDrawable( + // R.drawable.sticky_shadow_default)); + // scrollView.setShadowHeight(height); } -} \ No newline at end of file +}