Skip to content

Commit

Permalink
Merge pull request #31 from ninovanhooff/feature/vector-and-framerate…
Browse files Browse the repository at this point in the history
…-independence

Feature/vector and framerate independence
  • Loading branch information
tsuijten committed Jan 29, 2021
2 parents bc6f68e + 65f046c commit 18f9cd8
Show file tree
Hide file tree
Showing 7 changed files with 106 additions and 61 deletions.
24 changes: 14 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,15 +36,19 @@ In your Android layout file add:
android:id="@+id/scrolling_background"
android:layout_width="match_parent"
android:layout_height="wrap_content"
scrolling_image_view:speed="1dp"
scrolling_image_view:src="@drawable/scrolling_background" />
scrolling_image_view:speed="60dp"
scrolling_image_view:contiguous="false"
scrolling_image_view:source="@drawable/scrolling_background" />
```

There are two attributes for the `ScrollingImageView` namely `speed` and `src`.
* `speed` is the number of `dp`'s to move the bitmap on each animation frame (may be a negative number)
* `src` is the drawable to paint (**must be a bitmap!**)
There are three attributes for the `ScrollingImageView`:
* `speed` is the number of `dp`'s to move the drawable per second (may be a negative number)
* `source` is the drawable to paint. May refer to an array of drawables
* `contiguous` When source is an array of drawables, `contiguous` determines their ordering.

Don't forget to add the namespace to your root XLM element
false (default) for random ordering, true for the same order as in the array

Don't forget to add the namespace to your root XML element
```xml
xmlns:scrolling_image_view="http://schemas.android.com/apk/res-auto"
```
Expand All @@ -67,14 +71,14 @@ In order to achieve a parallax effect, you can stack multiple `ScrollingImageVie
android:id="@+id/scrolling_background"
android:layout_width="match_parent"
android:layout_height="wrap_content"
scrolling_image_view:speed="1dp"
scrolling_image_view:src="@drawable/scrolling_background" />
scrolling_image_view:speed="60dp"
scrolling_image_view:source="@drawable/scrolling_background" />

<com.q42.android.scrollingimageview.ScrollingImageView
android:id="@+id/scrolling_foreground"
android:layout_width="match_parent"
android:layout_height="wrap_content"
scrolling_image_view:speed="2.5dp"
scrolling_image_view:src="@drawable/scrolling_foreground" />
scrolling_image_view:speed="150dp"
scrolling_image_view:source="@drawable/scrolling_foreground" />
</FrameLayout>
```
2 changes: 1 addition & 1 deletion library/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ android {
buildToolsVersion "27.0.3"

defaultConfig {
minSdkVersion 16
minSdkVersion 21
targetSdkVersion 27
versionCode 1
versionName "1.0"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.View;
Expand All @@ -21,42 +23,66 @@
/**
* Created by thijs on 08-06-15.
*/
@SuppressWarnings("FieldCanBeLocal") // Declaring fields outside of onDraw improves performance
public class ScrollingImageView extends View {
public static ScrollingImageViewBitmapLoader BITMAP_LOADER = new ScrollingImageViewBitmapLoader() {
@Override
public Bitmap loadBitmap(Context context, int resourceId) {
return BitmapFactory.decodeResource(context.getResources(), resourceId);
public Bitmap loadDrawable(Context context, int resourceId) {
Drawable drawable = context.getResources().getDrawable(resourceId, context.getTheme());
if (drawable instanceof BitmapDrawable) {
return ((BitmapDrawable) drawable).getBitmap();
}

// Render any other kind of drawable to a bitmap
Bitmap bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(),
drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
drawable.draw(canvas);

return bitmap;
}
};

public Paint paint = null;

private List<Bitmap> bitmaps;
private float speed;
/** Pixels per second */
private final double speed;
private int[] scene;
private int arrayIndex = 0;
private int maxBitmapHeight = 0;

private Rect clipBounds = new Rect();
private final Rect clipBounds = new Rect();
private float offset = 0;

private static final double NANOS_PER_SECOND = 1e9;
/** Moment when the last call to onDraw() started */
private long lastFrameInstant = -1;
private long frameTimeNanos = -1;

private boolean isStarted;

public ScrollingImageView(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.ParallaxView, 0, 0);
TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.ScrollingImageView, 0, 0);
int initialState = 0;
try {
initialState = ta.getInt(R.styleable.ParallaxView_initialState, 0);
speed = ta.getDimension(R.styleable.ParallaxView_speed, 10);
int sceneLength = ta.getInt(R.styleable.ParallaxView_sceneLength, 1000);
final int randomnessResourceId = ta.getResourceId(R.styleable.ParallaxView_randomness, 0);
initialState = ta.getInt(R.styleable.ScrollingImageView_initialState, 0);
speed = ta.getDimension(R.styleable.ScrollingImageView_speed, 60);
int sceneLength = ta.getInt(R.styleable.ScrollingImageView_sceneLength, 1000);
final int randomnessResourceId = ta.getResourceId(R.styleable.ScrollingImageView_randomness, 0);
// When true, randomness is ignored and bitmaps are loaded in the order as they appear in the src array */
final boolean contiguous = ta.getBoolean(R.styleable.ScrollingImageView_contiguous, false);

int[] randomness = new int[0];
if (randomnessResourceId > 0) {
randomness = getResources().getIntArray(randomnessResourceId);
}

int type = isInEditMode() ? TypedValue.TYPE_STRING : ta.peekValue(R.styleable.ParallaxView_src).type;
int type = isInEditMode() ? TypedValue.TYPE_STRING : ta.peekValue(R.styleable.ScrollingImageView_source).type;
if (type == TypedValue.TYPE_REFERENCE) {
int resourceId = ta.getResourceId(R.styleable.ParallaxView_src, 0);
int resourceId = ta.getResourceId(R.styleable.ScrollingImageView_source, 0);
TypedArray typedArray = getResources().obtainTypedArray(resourceId);
try {
int bitmapsSize = 0;
Expand All @@ -72,7 +98,7 @@ public ScrollingImageView(Context context, AttributeSet attrs) {
multiplier = Math.max(1, randomness[i]);
}

Bitmap bitmap = BITMAP_LOADER.loadBitmap(getContext(), typedArray.getResourceId(i, 0));
Bitmap bitmap = BITMAP_LOADER.loadDrawable(getContext(), typedArray.getResourceId(i, 0));
for (int m = 0; m < multiplier; m++) {
bitmaps.add(bitmap);
}
Expand All @@ -83,13 +109,17 @@ public ScrollingImageView(Context context, AttributeSet attrs) {
Random random = new Random();
this.scene = new int[sceneLength];
for (int i = 0; i < this.scene.length; i++) {
this.scene[i] = random.nextInt(bitmaps.size());
if (contiguous){
this.scene[i] = i % bitmaps.size();
} else {
this.scene[i] = random.nextInt(bitmaps.size());
}
}
} finally {
typedArray.recycle();
}
} else if (type == TypedValue.TYPE_STRING) {
final Bitmap bitmap = BITMAP_LOADER.loadBitmap(getContext(), ta.getResourceId(R.styleable.ParallaxView_src, 0));
final Bitmap bitmap = BITMAP_LOADER.loadDrawable(getContext(), ta.getResourceId(R.styleable.ScrollingImageView_source, 0));
if (bitmap != null) {
bitmaps = singletonList(bitmap);
scene = new int[]{0};
Expand All @@ -116,6 +146,12 @@ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
@Override
public void onDraw(Canvas canvas) {
if(!isInEditMode()) {
if (lastFrameInstant == -1){
lastFrameInstant = System.nanoTime();
}
frameTimeNanos = System.nanoTime() - lastFrameInstant;
lastFrameInstant = System.nanoTime();

super.onDraw(canvas);
if (canvas == null || bitmaps.isEmpty()) {
return;
Expand All @@ -132,12 +168,13 @@ public void onDraw(Canvas canvas) {
for (int i = 0; left < clipBounds.width(); i++) {
Bitmap bitmap = getBitmap((arrayIndex + i) % scene.length);
int width = bitmap.getWidth();
canvas.drawBitmap(bitmap, getBitmapLeft(width, left), 0, null);
canvas.drawBitmap(bitmap, getBitmapLeft(width, left), 0, paint);
left += width;
}

if (isStarted && speed != 0) {
offset -= abs(speed);

offset -= (abs(speed) / NANOS_PER_SECOND) * frameTimeNanos;
postInvalidateOnAnimation();
}
}
Expand All @@ -161,24 +198,20 @@ private float getBitmapLeft(float layerWidth, float left) {
public void start() {
if (!isStarted) {
isStarted = true;
lastFrameInstant = -1;
postInvalidateOnAnimation();
}
}

/**
* Stop the animation
*/
@SuppressWarnings("unused")
public void stop() {
if (isStarted) {
isStarted = false;
lastFrameInstant = -1;
invalidate();
}
}

public void setSpeed(float speed) {
this.speed = speed;
if (isStarted) {
postInvalidateOnAnimation();
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,5 @@
* Created by thijs on 22-03-16.
*/
public interface ScrollingImageViewBitmapLoader {
Bitmap loadBitmap(Context context, int resourceId);
Bitmap loadDrawable(Context context, int resourceId);
}
9 changes: 7 additions & 2 deletions library/src/main/res/values/attr.xml
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="ParallaxView">
<declare-styleable name="ScrollingImageView">
<!-- dp per second -->
<attr name="speed" format="dimension" />
<attr name="src" format="reference" />
<!-- Using src would clash with attribute android:src in multi-module apps -->
<attr name="source" format="reference" />
<attr name="sceneLength" format="integer" />
<attr name="randomness" format="reference" />
<!-- When true, slices are stitched in order. When false, slices are stitched in random order
default: false -->
<attr name="contiguous" format="boolean" />
<attr name="initialState" format="enum">
<enum name="started" value="0" />
<enum name="stopped" value="1" />
Expand Down
2 changes: 1 addition & 1 deletion sampleapp/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ android {

defaultConfig {
applicationId "scrollingimageview.android.q42.com.sampleapp"
minSdkVersion 16
minSdkVersion 21
targetSdkVersion 27
versionCode 1
versionName "1.0"
Expand Down
45 changes: 24 additions & 21 deletions sampleapp/src/main/res/layout/activity_main.xml
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
scrolling_image_view:speed="3dp"
scrolling_image_view:src="@array/random_imgs"
scrolling_image_view:speed="180dp"
scrolling_image_view:source="@array/random_imgs"
scrolling_image_view:randomness="@array/randomness" />

<ImageView
Expand All @@ -55,15 +55,16 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
scrolling_image_view:speed="1dp"
scrolling_image_view:src="@drawable/scrolling_background" />
scrolling_image_view:speed="60dp"
scrolling_image_view:source="@drawable/scrolling_background"
scrolling_image_view:initialState="started"/>

<com.q42.android.scrollingimageview.ScrollingImageView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
scrolling_image_view:speed="1.9dp"
scrolling_image_view:src="@drawable/scrolling_foreground" />
scrolling_image_view:speed="114dp"
scrolling_image_view:source="@drawable/scrolling_foreground" />

<ImageView
android:layout_width="wrap_content"
Expand All @@ -76,7 +77,7 @@
style="@style/Base.TextAppearance.AppCompat.Title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Left to right scrolling background"
android:text="Left to right, fixed tile order"
android:layout_marginTop="30dp"/>

<FrameLayout
Expand All @@ -88,15 +89,17 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
scrolling_image_view:speed="-1dp"
scrolling_image_view:src="@drawable/scrolling_background" />
scrolling_image_view:speed="-60dp"
scrolling_image_view:contiguous="true"
scrolling_image_view:source="@drawable/scrolling_background" />

<com.q42.android.scrollingimageview.ScrollingImageView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
scrolling_image_view:speed="-1.9dp"
scrolling_image_view:src="@drawable/scrolling_foreground" />
scrolling_image_view:speed="-114dp"
scrolling_image_view:contiguous="true"
scrolling_image_view:source="@drawable/scrolling_foreground" />
</FrameLayout>

<TextView
Expand Down Expand Up @@ -128,36 +131,36 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
scrolling_image_view:speed=".3dp"
scrolling_image_view:src="@drawable/layer_3" />
scrolling_image_view:speed="18dp"
scrolling_image_view:source="@drawable/layer_3" />

<com.q42.android.scrollingimageview.ScrollingImageView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
scrolling_image_view:speed=".6dp"
scrolling_image_view:src="@drawable/layer_4" />
scrolling_image_view:speed="36dp"
scrolling_image_view:source="@drawable/layer_4" />

<com.q42.android.scrollingimageview.ScrollingImageView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
scrolling_image_view:speed=".9dp"
scrolling_image_view:src="@drawable/layer_5" />
scrolling_image_view:speed="54dp"
scrolling_image_view:source="@drawable/layer_5" />

<com.q42.android.scrollingimageview.ScrollingImageView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
scrolling_image_view:speed="1.5dp"
scrolling_image_view:src="@drawable/layer_6" />
scrolling_image_view:speed="90dp"
scrolling_image_view:source="@drawable/layer_6" />

<com.q42.android.scrollingimageview.ScrollingImageView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
scrolling_image_view:speed="1.7dp"
scrolling_image_view:src="@drawable/layer_7" />
scrolling_image_view:speed="102dp"
scrolling_image_view:source="@drawable/layer_7" />
</FrameLayout>
</LinearLayout>
</ScrollView>

0 comments on commit 18f9cd8

Please sign in to comment.