Skip to content

Instantly share code, notes, and snippets.

@AnderWeb
Created November 28, 2013 13:19
Show Gist options
  • Select an option

  • Save AnderWeb/7691721 to your computer and use it in GitHub Desktop.

Select an option

Save AnderWeb/7691721 to your computer and use it in GitHub Desktop.

Revisions

  1. AnderWeb created this gist Nov 28, 2013.
    255 changes: 255 additions & 0 deletions ScrollDrawable.java
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,255 @@
    /*
    * 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);
    }
    }