Skip to content

Instantly share code, notes, and snippets.

@AnderWeb
Created November 28, 2013 13:19
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save AnderWeb/7691721 to your computer and use it in GitHub Desktop.
Save AnderWeb/7691721 to your computer and use it in GitHub Desktop.
A little handy drawable to automatically animate another drawable moving up and down...
/*
* Copyright (C) 2013 AnderWeb (Gustavo Claramunt)
*
* Inspiration from http://cyrilmottier.com/2012/11/27/actionbar-on-the-move/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
public class ScrollDrawable extends Drawable implements Animatable {
public static class Builder {
private Drawable drawable;
private Interpolator interpolator;
private long duration;
private float scale;
public Builder(Drawable wrapped) {
this.drawable = wrapped;
}
public Builder setDuration(long durationMillis) {
this.duration = durationMillis;
return this;
}
public Builder setInterpolator(Interpolator interpolator) {
this.interpolator = interpolator;
return this;
}
/**
* This is where you set the difference between the drawable height and the total scroll height.
* So if this has a height of 300 and the scale is 1.5f, you'll get a 450px image scrolling up and down
*
* Note: this doesn't take proportions into consideration, so images may result stretched and/or ugly :)
*
* @param scrollScale the difference between this drawable height and the scrollable height.
* Values <1f are ignored.
* @return the instance of this Builder.
*/
public Builder setScrollScale(float scrollScale) {
this.scale = scrollScale;
return this;
}
public ScrollDrawable build() {
return new ScrollDrawable(drawable, interpolator, duration, scale);
}
}
private static final long FRAME_DURATION = 1000 / 60;
private float mScrollOffset;
private float mScrollScale;
private long mScrollDuration;
private Interpolator mInterpolator;
private Drawable mWrapped;
private final Rect mBounds = new Rect();
private int mScrollHeight;
private long mStartTime;
private boolean mReverse = false;
private boolean mRunning = false;
private ScrollDrawable(Drawable wrapped, Interpolator interpolator, long duration, float scale) {
mWrapped = wrapped;
mInterpolator = interpolator != null ? interpolator : new AccelerateDecelerateInterpolator();
mScrollDuration = duration > 0 ? duration : 4000;
mScrollScale = scale > 1f ? scale : 1.5f;
}
@Override
public void draw(Canvas canvas) {
canvas.clipRect(mBounds);
float currentScroll = mScrollOffset * mScrollHeight;
canvas.translate(0, -currentScroll);
mWrapped.draw(canvas);
canvas.translate(0, currentScroll);
}
@Override
protected void onBoundsChange(Rect bounds) {
super.onBoundsChange(bounds);
mBounds.set(bounds);
int scrollSize = (int) (bounds.height() * mScrollScale);
mScrollHeight = scrollSize - bounds.height();
mWrapped.setBounds(bounds.left, bounds.top, bounds.right, bounds.top + scrollSize);
}
private final Runnable mUpdater = new Runnable() {
@Override
public void run() {
long currentTime = SystemClock.uptimeMillis();
long diff = currentTime - mStartTime;
if (diff < mScrollDuration) {
float interpolation = mInterpolator.getInterpolation((float) diff / (float) mScrollDuration);
if (mReverse) {
interpolation = 1f - interpolation;
}
mScrollOffset = interpolation;
} else {
//Reverse!
mReverse = !mReverse;
mStartTime = currentTime;
}
scheduleSelf(mUpdater, currentTime + FRAME_DURATION);
invalidateSelf();
}
};
@Override
public void start() {
if (isRunning()) return;
mRunning = true;
mStartTime = SystemClock.uptimeMillis();
scheduleSelf(mUpdater, mStartTime + FRAME_DURATION);
invalidateSelf();
}
@Override
public void stop() {
if (!isRunning()) return;
unscheduleSelf(mUpdater);
mRunning = false;
}
@Override
public boolean isRunning() {
return mRunning;
}
//Rest of Drawable methods redirect to the wrapped drawable
@Override
public int getIntrinsicWidth() {
return mWrapped.getIntrinsicWidth();
}
@Override
public int getIntrinsicHeight() {
return mWrapped.getIntrinsicHeight();
}
@Override
public int getMinimumWidth() {
return mWrapped.getMinimumWidth();
}
@Override
public int getMinimumHeight() {
return mWrapped.getMinimumHeight();
}
@Override
public boolean getPadding(Rect padding) {
return mWrapped.getPadding(padding);
}
@Override
public ConstantState getConstantState() {
return super.getConstantState();
}
@Override
public void setChangingConfigurations(int configs) {
mWrapped.setChangingConfigurations(configs);
}
@Override
public int getChangingConfigurations() {
return mWrapped.getChangingConfigurations();
}
@Override
public void setDither(boolean dither) {
mWrapped.setDither(dither);
}
@Override
public void setFilterBitmap(boolean filter) {
mWrapped.setFilterBitmap(filter);
}
@Override
public void setAlpha(int alpha) {
mWrapped.setAlpha(alpha);
}
@Override
public void setColorFilter(ColorFilter cf) {
mWrapped.setColorFilter(cf);
}
@Override
public void setColorFilter(int color, PorterDuff.Mode mode) {
mWrapped.setColorFilter(color, mode);
}
@Override
public void clearColorFilter() {
mWrapped.clearColorFilter();
}
@Override
public boolean isStateful() {
return mWrapped.isStateful();
}
@Override
public boolean setState(int[] stateSet) {
return mWrapped.setState(stateSet);
}
@Override
public int[] getState() {
return mWrapped.getState();
}
@Override
public Drawable getCurrent() {
return mWrapped.getCurrent();
}
@Override
public boolean setVisible(boolean visible, boolean restart) {
return super.setVisible(visible, restart);
}
@Override
public int getOpacity() {
return mWrapped.getOpacity();
}
@Override
public Region getTransparentRegion() {
return mWrapped.getTransparentRegion();
}
@Override
protected boolean onStateChange(int[] state) {
mWrapped.setState(state);
return super.onStateChange(state);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment