package com.yalantis.phoenix.refresh_view; import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.Matrix; import android.graphics.Rect; import android.graphics.drawable.Animatable; import android.view.animation.Animation; import android.view.animation.Interpolator; import android.view.animation.LinearInterpolator; import android.view.animation.Transformation; import com.desmond.libs.R; import com.yalantis.phoenix.PullToRefreshView; import com.yalantis.phoenix.util.Utils; /** * Created by Oleksii Shliama on 22/12/2014. * https://dribbble.com/shots/1650317-Pull-to-Refresh-Rentals */ public class SunRefreshView extends BaseRefreshView implements Animatable { private static final float SCALE_START_PERCENT = 0.5f; private static final int ANIMATION_DURATION = 1000; private final static float SKY_RATIO = 0.65f; private static final float SKY_INITIAL_SCALE = 1.05f; private final static float TOWN_RATIO = 0.22f; private static final float TOWN_INITIAL_SCALE = 1.20f; private static final float TOWN_FINAL_SCALE = 1.30f; private static final float SUN_FINAL_SCALE = 0.75f; private static final float SUN_INITIAL_ROTATE_GROWTH = 1.2f; private static final float SUN_FINAL_ROTATE_GROWTH = 1.5f; private static final Interpolator LINEAR_INTERPOLATOR = new LinearInterpolator(); private PullToRefreshView mParent; private Matrix mMatrix; private Animation mAnimation; private int mTop; private int mScreenWidth; private int mSkyHeight; private float mSkyTopOffset; private float mSkyMoveOffset; private int mTownHeight; private float mTownInitialTopOffset; private float mTownFinalTopOffset; private float mTownMoveOffset; private int mSunSize = 100; private float mSunLeftOffset; private float mSunTopOffset; private float mPercent = 0.0f; private float mRotate = 0.0f; private Bitmap mSky; private Bitmap mSun; private Bitmap mTown; private boolean isRefreshing = false; public SunRefreshView(Context context, final PullToRefreshView parent) { super(context, parent); mParent = parent; mMatrix = new Matrix(); setupAnimations(); parent.post(new Runnable() { @Override public void run() { initiateDimens(parent.getWidth()); } }); } public void initiateDimens(int viewWidth) { if (viewWidth <= 0 || viewWidth == mScreenWidth) return; mScreenWidth = viewWidth; mSkyHeight = (int) (SKY_RATIO * mScreenWidth); mSkyTopOffset = (mSkyHeight * 0.38f); mSkyMoveOffset = Utils.convertDpToPixel(getContext(), 15); mTownHeight = (int) (TOWN_RATIO * mScreenWidth); mTownInitialTopOffset = (mParent.getTotalDragDistance() - mTownHeight * TOWN_INITIAL_SCALE); mTownFinalTopOffset = (mParent.getTotalDragDistance() - mTownHeight * TOWN_FINAL_SCALE); mTownMoveOffset = Utils.convertDpToPixel(getContext(), 10); mSunLeftOffset = 0.3f * (float) mScreenWidth; mSunTopOffset = (mParent.getTotalDragDistance() * 0.1f); mTop = -mParent.getTotalDragDistance(); createBitmaps(); } private void createBitmaps() { final BitmapFactory.Options options = new BitmapFactory.Options(); options.inPreferredConfig = Bitmap.Config.RGB_565; mSky = BitmapFactory.decodeResource(getContext().getResources(), R.drawable.sky, options); mSky = Bitmap.createScaledBitmap(mSky, mScreenWidth, mSkyHeight, true); mTown = BitmapFactory.decodeResource(getContext().getResources(), R.drawable.buildings, options); mTown = Bitmap.createScaledBitmap(mTown, mScreenWidth, (int) (mScreenWidth * TOWN_RATIO), true); mSun = BitmapFactory.decodeResource(getContext().getResources(), R.drawable.sun, options); mSun = Bitmap.createScaledBitmap(mSun, mSunSize, mSunSize, true); } @Override public void setPercent(float percent, boolean invalidate) { setPercent(percent); if (invalidate) setRotate(percent); } @Override public void offsetTopAndBottom(int offset) { mTop += offset; invalidateSelf(); } @Override public void draw(Canvas canvas) { if (mScreenWidth <= 0) return; final int saveCount = canvas.save(); canvas.translate(0, mTop); canvas.clipRect(0, -mTop, mScreenWidth, mParent.getTotalDragDistance()); drawSky(canvas); drawSun(canvas); drawTown(canvas); canvas.restoreToCount(saveCount); } private void drawSky(Canvas canvas) { Matrix matrix = mMatrix; matrix.reset(); float dragPercent = Math.min(1f, Math.abs(mPercent)); float skyScale; float scalePercentDelta = dragPercent - SCALE_START_PERCENT; if (scalePercentDelta > 0) { /** Change skyScale between {@link #SKY_INITIAL_SCALE} and 1.0f depending on {@link #mPercent} */ float scalePercent = scalePercentDelta / (1.0f - SCALE_START_PERCENT); skyScale = SKY_INITIAL_SCALE - (SKY_INITIAL_SCALE - 1.0f) * scalePercent; } else { skyScale = SKY_INITIAL_SCALE; } float offsetX = -(mScreenWidth * skyScale - mScreenWidth) / 2.0f; float offsetY = (1.0f - dragPercent) * mParent.getTotalDragDistance() - mSkyTopOffset // Offset canvas moving - mSkyHeight * (skyScale - 1.0f) / 2 // Offset sky scaling + mSkyMoveOffset * dragPercent; // Give it a little move top -> bottom matrix.postScale(skyScale, skyScale); matrix.postTranslate(offsetX, offsetY); canvas.drawBitmap(mSky, matrix, null); } private void drawTown(Canvas canvas) { Matrix matrix = mMatrix; matrix.reset(); float dragPercent = Math.min(1f, Math.abs(mPercent)); float townScale; float townTopOffset; float townMoveOffset; float scalePercentDelta = dragPercent - SCALE_START_PERCENT; if (scalePercentDelta > 0) { /** * Change townScale between {@link #TOWN_INITIAL_SCALE} and {@link #TOWN_FINAL_SCALE} depending on {@link #mPercent} * Change townTopOffset between {@link #mTownInitialTopOffset} and {@link #mTownFinalTopOffset} depending on {@link #mPercent} */ float scalePercent = scalePercentDelta / (1.0f - SCALE_START_PERCENT); townScale = TOWN_INITIAL_SCALE + (TOWN_FINAL_SCALE - TOWN_INITIAL_SCALE) * scalePercent; townTopOffset = mTownInitialTopOffset - (mTownFinalTopOffset - mTownInitialTopOffset) * scalePercent; townMoveOffset = mTownMoveOffset * (1.0f - scalePercent); } else { float scalePercent = dragPercent / SCALE_START_PERCENT; townScale = TOWN_INITIAL_SCALE; townTopOffset = mTownInitialTopOffset; townMoveOffset = mTownMoveOffset * scalePercent; } float offsetX = -(mScreenWidth * townScale - mScreenWidth) / 2.0f; float offsetY = (1.0f - dragPercent) * mParent.getTotalDragDistance() // Offset canvas moving + townTopOffset - mTownHeight * (townScale - 1.0f) / 2 // Offset town scaling + townMoveOffset; // Give it a little move matrix.postScale(townScale, townScale); matrix.postTranslate(offsetX, offsetY); canvas.drawBitmap(mTown, matrix, null); } private void drawSun(Canvas canvas) { Matrix matrix = mMatrix; matrix.reset(); float dragPercent = mPercent; if (dragPercent > 1.0f) { // Slow down if pulling over set height dragPercent = (dragPercent + 9.0f) / 10; } float sunRadius = (float) mSunSize / 2.0f; float sunRotateGrowth = SUN_INITIAL_ROTATE_GROWTH; float offsetX = mSunLeftOffset; float offsetY = mSunTopOffset + (mParent.getTotalDragDistance() / 2) * (1.0f - dragPercent) // Move the sun up - mTop; // Depending on Canvas position float scalePercentDelta = dragPercent - SCALE_START_PERCENT; if (scalePercentDelta > 0) { float scalePercent = scalePercentDelta / (1.0f - SCALE_START_PERCENT); float sunScale = 1.0f - (1.0f - SUN_FINAL_SCALE) * scalePercent; sunRotateGrowth += (SUN_FINAL_ROTATE_GROWTH - SUN_INITIAL_ROTATE_GROWTH) * scalePercent; matrix.preTranslate(offsetX + (sunRadius - sunRadius * sunScale), offsetY * (2.0f - sunScale)); matrix.preScale(sunScale, sunScale); offsetX += sunRadius; offsetY = offsetY * (2.0f - sunScale) + sunRadius * sunScale; } else { matrix.postTranslate(offsetX, offsetY); offsetX += sunRadius; offsetY += sunRadius; } matrix.postRotate( (isRefreshing ? -360 : 360) * mRotate * (isRefreshing ? 1 : sunRotateGrowth), offsetX, offsetY); canvas.drawBitmap(mSun, matrix, null); } public void setPercent(float percent) { mPercent = percent; } public void setRotate(float rotate) { mRotate = rotate; invalidateSelf(); } public void resetOriginals() { setPercent(0); setRotate(0); } @Override protected void onBoundsChange(Rect bounds) { super.onBoundsChange(bounds); } @Override public void setBounds(int left, int top, int right, int bottom) { super.setBounds(left, top, right, mSkyHeight + top); } @Override public boolean isRunning() { return false; } @Override public void start() { mAnimation.reset(); isRefreshing = true; mParent.startAnimation(mAnimation); } @Override public void stop() { mParent.clearAnimation(); isRefreshing = false; resetOriginals(); } private void setupAnimations() { mAnimation = new Animation() { @Override public void applyTransformation(float interpolatedTime, Transformation t) { setRotate(interpolatedTime); } }; mAnimation.setRepeatCount(Animation.INFINITE); mAnimation.setRepeatMode(Animation.RESTART); mAnimation.setInterpolator(LINEAR_INTERPOLATOR); mAnimation.setDuration(ANIMATION_DURATION); } }