/* * Copyright 2015 Google Inc. * * 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. */ package io.plaidapp.ui.widget; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Rect; import android.support.annotation.ColorInt; import android.support.annotation.FloatRange; import android.util.AttributeSet; import android.util.Property; import io.plaidapp.R; import io.plaidapp.util.AnimUtils; import io.plaidapp.util.ColorUtils; /** * An image view which supports parallax scrolling and applying a scrim onto it's content. Get it. * <p> * It also has a custom pinned state, for use via state lists. */ public class ParallaxScrimageView extends FourThreeImageView { private static final int[] STATE_PINNED = { R.attr.state_pinned }; private final Paint scrimPaint; private int imageOffset; private int minOffset; private Rect clipBounds = new Rect(); private float scrimAlpha = 0f; private float maxScrimAlpha = 1f; private int scrimColor = Color.TRANSPARENT; private float parallaxFactor = -0.5f; private boolean isPinned = false; private boolean immediatePin = false; public static final Property<ParallaxScrimageView, Integer> OFFSET = AnimUtils.createIntProperty(new AnimUtils.IntProp<ParallaxScrimageView>("offset") { @Override public void set(ParallaxScrimageView parallaxScrimageView, int offset) { parallaxScrimageView.setOffset(offset); } @Override public int get(ParallaxScrimageView parallaxScrimageView) { return parallaxScrimageView.getOffset(); } }); public ParallaxScrimageView(Context context, AttributeSet attrs) { super(context, attrs); final TypedArray a = getContext().obtainStyledAttributes( attrs, R.styleable.ParallaxScrimageView); if (a.hasValue(R.styleable.ParallaxScrimageView_scrimColor)) { scrimColor = a.getColor(R.styleable.ParallaxScrimageView_scrimColor, scrimColor); } if (a.hasValue(R.styleable.ParallaxScrimageView_scrimAlpha)) { scrimAlpha = a.getFloat(R.styleable.ParallaxScrimageView_scrimAlpha, scrimAlpha); } if (a.hasValue(R.styleable.ParallaxScrimageView_maxScrimAlpha)) { maxScrimAlpha = a.getFloat(R.styleable.ParallaxScrimageView_maxScrimAlpha, maxScrimAlpha); } if (a.hasValue(R.styleable.ParallaxScrimageView_parallaxFactor)) { parallaxFactor = a.getFloat(R.styleable.ParallaxScrimageView_parallaxFactor, parallaxFactor); } a.recycle(); scrimPaint = new Paint(); scrimPaint.setColor(ColorUtils.modifyAlpha(scrimColor, scrimAlpha)); } public int getOffset() { return (int) getTranslationY(); } public void setOffset(int offset) { offset = Math.max(minOffset, offset); if (offset != getTranslationY()) { setTranslationY(offset); imageOffset = (int) (offset * parallaxFactor); clipBounds.set(0, -offset, getWidth(), getHeight()); setClipBounds(clipBounds); setScrimAlpha(Math.min( ((float) -offset / getMinimumHeight()) * maxScrimAlpha, maxScrimAlpha)); postInvalidateOnAnimation(); } setPinned(offset == minOffset); } public void setScrimColor(@ColorInt int scrimColor) { if (this.scrimColor != scrimColor) { this.scrimColor = scrimColor; postInvalidateOnAnimation(); } } public void setScrimAlpha(@FloatRange(from = 0f, to = 1f) float alpha) { if (scrimAlpha != alpha) { scrimAlpha = alpha; scrimPaint.setColor(ColorUtils.modifyAlpha(scrimColor, scrimAlpha)); postInvalidateOnAnimation(); } } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); if (h > getMinimumHeight()) { minOffset = getMinimumHeight() - h; } } @Override protected void onDraw(Canvas canvas) { if (imageOffset != 0) { final int saveCount = canvas.save(); canvas.translate(0f, imageOffset); super.onDraw(canvas); canvas.drawRect(0, 0, canvas.getWidth(), canvas.getHeight(), scrimPaint); canvas.restoreToCount(saveCount); } else { super.onDraw(canvas); canvas.drawRect(0, 0, canvas.getWidth(), canvas.getHeight(), scrimPaint); } } @Override public int[] onCreateDrawableState(int extraSpace) { final int[] drawableState = super.onCreateDrawableState(extraSpace + 1); if (isPinned) { mergeDrawableStates(drawableState, STATE_PINNED); } return drawableState; } public boolean isPinned() { return isPinned; } public void setPinned(boolean isPinned) { if (this.isPinned != isPinned) { this.isPinned = isPinned; refreshDrawableState(); if (isPinned && immediatePin) { jumpDrawablesToCurrentState(); } } } public boolean isImmediatePin() { return immediatePin; } /** * As the pinned state is designed to work with a {@see StateListAnimator}, we may want to short * circuit this animation in certain situations e.g. when flinging a list. */ public void setImmediatePin(boolean immediatePin) { this.immediatePin = immediatePin; } }