Android - Liquid swipe to Refresh

Liquid swipe to Refresh

Dalam tutorial kali ini kita akan membuat fungsi swipe to refresh di Android. Untuk melakukannya, gunakan widget SwipeRefreshLayout. Android SwipeRefreshLayout adalah ViewGroup yang hanya dapat memuat satu anak gulir. Itu bisa ScrollView, ListView atau RecyclerView. Persyaratan dasar untuk SwipeRefreshLayout adalah mengizinkan pengguna menyegarkan layar secara manual. Ini cukup umum seperti pada layar pesan Facebook, Gmail, dan Twitter.
Lihat video di bawah ini contoh nya:


Sebelum tata letak ini tersedia di pustaka dukungan, kita harus menggunakan build kustom dan deteksi gulir ke bawah untuk memperbarui ListView, misalnya. Kelas ini terdiri dari pendengar penting, OnRefreshListener. Saat ditekan, pendengar ini diaktifkan dan metode OnRefresh() dipanggil. Kita dapat mengganti metode ini sesuai dengan kebutuhan kita. Dalam tutorial ini kita akan mengembangkan sebuah aplikasi yang terdiri dari ListView yang, ketika diseret ke bawah, memperbarui layar dan mengacak baris daftar.

Di dalam file activity_main.xml, terapkan widget SwipeRefreshLayout dan tambahkan ListView ke dalam SwipeRefreshLayout seperti yang saya susun di bawah ini.


<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/main_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity">
 <View
      android:layout_width="match_parent"
      android:layout_height="25dip"/>
  
	<RelativeLayout
		android:layout_width="match_parent"
		android:layout_height="0dp"
		android:layout_weight="1">
		<com.happycodx.app.RefreshLayout
			android:layout_width="match_parent"
			android:layout_height="match_parent"
			android:id="@+id/swipeRefresh"
			android:layout_below="@+id/toolbar">
			<ListView
				android:id="@+id/listview"
				android:layout_width="match_parent"
				android:layout_height="match_parent"
			/>
		</com.happycodx.app.RefreshLayout>
	</RelativeLayout>

</LinearLayout>

Buat file RefreshLayout.java


package com.happycodx.app;
import android.animation.ValueAnimator;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Color;
import android.support.annotation.IdRes;
import android.support.annotation.NonNull;
import android.support.v4.view.MotionEventCompat;
import android.support.v4.view.ViewCompat;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.view.animation.Animation;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.Transformation;
import android.widget.AbsListView;
/**
 * @author happycodx
 */
public class RefreshLayout extends ViewGroup implements ViewTreeObserver.OnPreDrawListener {
  private enum VERTICAL_DRAG_THRESHOLD {
    FIRST(0.1f), SECOND(0.16f + FIRST.val), THIRD(0.5f + FIRST.val);
    final float val;
    VERTICAL_DRAG_THRESHOLD(float val) {
      this.val = val;
    }
  }
  private enum STATE {
    REFRESHING, PENDING;
  }
  private enum EVENT_PHASE {
    WAITING, BEGINNING, APPEARING, EXPANDING, DROPPING;
  }
  private static final float DECELERATE_INTERPOLATION_FACTOR = 2f;
  private static final int INVALID_POINTER = -1;
  private static final float DRAGGING_WEIGHT = 0.5f;
  private static final float MAX_PROGRESS_ROTATION_RATE = 0.8f;
  private static final int SCALE_DOWN_DURATION = 200;
  private static final int ANIMATE_TO_TRIGGER_DURATION = 200;
  private static final int DEFAULT_CIRCLE_TARGET = 64;
  private View mTarget;
  private OnRefreshListener mListener;
  private STATE mState = STATE.PENDING;
  private EVENT_PHASE mEventPhase = EVENT_PHASE.WAITING;
  private final DecelerateInterpolator mDecelerateInterpolator;
  private ProgressAnimationImageView mCircleView;
  private WaveView mWaveView;
  private boolean mNotify;
  private boolean mIsManualRefresh = false;
  private float mFirstTouchDownPointY;
  private boolean mIsBeingDropped;
  private int mActivePointerId = INVALID_POINTER;
  private int mTopOffset;
  public RefreshLayout(Context context) {
    this(context, null);
  }
	public RefreshLayout(Context context, AttributeSet attrs) {
    this(context, attrs, 0);
  }
	public RefreshLayout(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
    getViewTreeObserver().addOnPreDrawListener(this);
    setWillNotDraw(false);
    mDecelerateInterpolator = new DecelerateInterpolator(DECELERATE_INTERPOLATION_FACTOR);
    ViewCompat.setChildrenDrawingOrderEnabled(this, true);
    createProgressView();
    createWaveView();
  }
  private void createProgressView() {
    addView(mCircleView = new ProgressAnimationImageView(getContext()));
  }
  private void createWaveView() {
    mWaveView = new WaveView(getContext());
    addView(mWaveView, 0);
  }
  @Override public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    ensureTarget();
    mTarget.measure(
        makeMeasureSpecExactly(getMeasuredWidth() - (getPaddingLeft() + getPaddingRight())),
        makeMeasureSpecExactly(getMeasuredHeight() - (getPaddingTop() + getPaddingBottom())));
    mWaveView.measure(widthMeasureSpec, heightMeasureSpec);
    mCircleView.measure();
  }
  @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
    if (getChildCount() == 0) {
      return;
    }
    ensureTarget();
    final int thisWidth = getMeasuredWidth();
    final int thisHeight = getMeasuredHeight();
    final int childRight = thisWidth - getPaddingRight();
    final int childBottom = thisHeight - getPaddingBottom();
    mTarget.layout(getPaddingLeft(), getPaddingTop(), childRight, childBottom);
    layoutWaveView();
  }
  private void layoutWaveView() {
    if (mWaveView == null) {
      return;
    }
    final int thisWidth = getMeasuredWidth();
    final int thisHeight = getMeasuredHeight();
    final int circleWidth = mCircleView.getMeasuredWidth();
    final int circleHeight = mCircleView.getMeasuredHeight();
    mCircleView.layout((thisWidth - circleWidth) / 2, -circleHeight + mTopOffset,
        (thisWidth + circleWidth) / 2, mTopOffset);
    final int childRight = thisWidth - getPaddingRight();
    final int childBottom = thisHeight - getPaddingBottom();
    mWaveView.layout(getPaddingLeft(), mTopOffset + getPaddingTop(), childRight, childBottom);
  }
  public void setTopOffsetOfWave(int topOffset) {
    if (topOffset < 0) {
      return;
    }
    mTopOffset = topOffset;
    layoutWaveView();
  }
  @Override public boolean onPreDraw() {
    getViewTreeObserver().removeOnPreDrawListener(this);
    mWaveView.bringToFront();
    mCircleView.bringToFront();
    if (mIsManualRefresh) {
      mIsManualRefresh = false;
      mWaveView.manualRefresh();
      reInitCircleView();
      mCircleView.setBackgroundColor(Color.TRANSPARENT);
      mCircleView.setTranslationY(
          mWaveView.getCurrentCircleCenterY() + mCircleView.getHeight() / 2);
      animateOffsetToCorrectPosition();
    }
    return false;
  }
  @Override public boolean onInterceptTouchEvent(@NonNull MotionEvent event) {
    ensureTarget();

    if (!isEnabled() || canChildScrollUp() || isRefreshing()) {
      return false;
    }
    final int action = MotionEventCompat.getActionMasked(event);
    switch (action) {
      case MotionEvent.ACTION_DOWN:
        mActivePointerId = MotionEventCompat.getPointerId(event, 0);
        mFirstTouchDownPointY = getMotionEventY(event, mActivePointerId);
        break;
      case MotionEvent.ACTION_MOVE:
        if (mActivePointerId == INVALID_POINTER) {
          return false;
        }
        final float currentY = getMotionEventY(event, mActivePointerId);
        if (currentY == -1) {
          return false;
        }
        if (mFirstTouchDownPointY == -1) {
          mFirstTouchDownPointY = currentY;
        }
        final float yDiff = currentY - mFirstTouchDownPointY;
        if (yDiff > ViewConfiguration.get(getContext()).getScaledTouchSlop() && !isRefreshing()) {
          mCircleView.makeProgressTransparent();
          return true;
        }
        break;
      case MotionEvent.ACTION_UP:
      case MotionEvent.ACTION_CANCEL:
        mActivePointerId = INVALID_POINTER;
        break;
    }
    return false;
  }
  @Override public void requestDisallowInterceptTouchEvent(boolean b) {
  }
  private void reInitCircleView() {
    if (mCircleView.getVisibility() != View.VISIBLE) {
      mCircleView.setVisibility(View.VISIBLE);
    }
    mCircleView.scaleWithKeepingAspectRatio(1f);
    mCircleView.makeProgressTransparent();
  }
  private boolean onMoveTouchEvent(@NonNull MotionEvent event, int pointerIndex) {
    if (mIsBeingDropped) {
      return false;
    }
    final float y = MotionEventCompat.getY(event, pointerIndex);
    final float diffY = y - mFirstTouchDownPointY;
    final float overScrollTop = diffY * DRAGGING_WEIGHT;
    if (overScrollTop < 0) {
      mCircleView.showArrow(false);
      return false;
    }
    final DisplayMetrics metrics = getResources().getDisplayMetrics();
    float originalDragPercent = overScrollTop / (DEFAULT_CIRCLE_TARGET * metrics.density);
    float dragPercent = Math.min(1f, originalDragPercent);
    float adjustedPercent = (float) Math.max(dragPercent - .4, 0) * 5 / 3;
    float tensionSlingshotPercent =
        (originalDragPercent > 3f) ? 2f : (originalDragPercent > 1f) ? originalDragPercent - 1f : 0;
    float tensionPercent = (4f - tensionSlingshotPercent) * tensionSlingshotPercent / 8f;
    mCircleView.showArrow(true);
    reInitCircleView();
    if (originalDragPercent < 1f) {
      float strokeStart = adjustedPercent * .8f;
      mCircleView.setProgressStartEndTrim(0f, Math.min(MAX_PROGRESS_ROTATION_RATE, strokeStart));
      mCircleView.setArrowScale(Math.min(1f, adjustedPercent));
    }
    float rotation = (-0.25f + .4f * adjustedPercent + tensionPercent * 2) * .5f;
    mCircleView.setProgressRotation(rotation);
    mCircleView.setTranslationY(mWaveView.getCurrentCircleCenterY());
    float seed = diffY / Math.min(getMeasuredWidth(), getMeasuredHeight());
    float firstBounds = seed * (5f - 2 * seed) / 3.5f;
    float secondBounds = firstBounds - VERTICAL_DRAG_THRESHOLD.FIRST.val;
    float finalBounds = (firstBounds - VERTICAL_DRAG_THRESHOLD.SECOND.val) / 5;
    if (firstBounds < VERTICAL_DRAG_THRESHOLD.FIRST.val) {
      onBeginPhase(firstBounds);
    } else if (firstBounds < VERTICAL_DRAG_THRESHOLD.SECOND.val) {
      onAppearPhase(firstBounds, secondBounds);
    } else if (firstBounds < VERTICAL_DRAG_THRESHOLD.THIRD.val) {
      onExpandPhase(firstBounds, secondBounds, finalBounds);
    } else {
      onDropPhase();
    }
    return !mIsBeingDropped;
  }
  private void onBeginPhase(float move1) {
    mWaveView.beginPhase(move1);
    setEventPhase(EVENT_PHASE.BEGINNING);
  }
  private void onAppearPhase(float move1, float move2) {
    mWaveView.appearPhase(move1, move2);
    setEventPhase(EVENT_PHASE.APPEARING);
  }
  private void onExpandPhase(float move1, float move2, float move3) {
    mWaveView.expandPhase(move1, move2, move3);
    setEventPhase(EVENT_PHASE.EXPANDING);
  }
  private void onDropPhase() {
    mWaveView.animationDropCircle();
    ValueAnimator animator = ValueAnimator.ofFloat(0, 0);
    animator.setDuration(500);
    animator.setInterpolator(new AccelerateDecelerateInterpolator());
    animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
      @Override public void onAnimationUpdate(ValueAnimator valueAnimator) {
        mCircleView.setTranslationY(
            mWaveView.getCurrentCircleCenterY() + mCircleView.getHeight() / 2.f);
      }
    });
    animator.start();
    setRefreshing(true, true);
    mIsBeingDropped = true;
    setEventPhase(EVENT_PHASE.DROPPING);
    setEnabled(false);
  }
  @Override public boolean onTouchEvent(@NonNull MotionEvent event) {
    if (!isEnabled() || canChildScrollUp()) {
      return false;
    }
    mIsBeingDropped = mWaveView.isDisappearCircleAnimatorRunning();
    final int action = MotionEventCompat.getActionMasked(event);
    switch (action) {
      case MotionEvent.ACTION_DOWN:
        break;
      case MotionEvent.ACTION_MOVE:
        final int pointerIndex = MotionEventCompat.findPointerIndex(event, mActivePointerId);
        return pointerIndex >= 0 && onMoveTouchEvent(event, pointerIndex);
      case MotionEvent.ACTION_UP:
        if (mIsBeingDropped) {
          mIsBeingDropped = false;
          return false;
        }
        final float diffY = event.getY() - mFirstTouchDownPointY;
        final float waveHeightThreshold =
            diffY * (5f - 2 * diffY / Math.min(getMeasuredWidth(), getMeasuredHeight())) / 1000f;
        mWaveView.startWaveAnimation(waveHeightThreshold);
      case MotionEvent.ACTION_CANCEL:
        if (mActivePointerId == INVALID_POINTER) {
          return false;
        }
        if (!isRefreshing()) {
          mCircleView.setProgressStartEndTrim(0f, 0f);
          mCircleView.showArrow(false);
          mCircleView.setVisibility(GONE);
        }
        mActivePointerId = INVALID_POINTER;
        return false;
    }
    return true;
  }
  private float getMotionEventY(@NonNull MotionEvent ev, int activePointerId) {
    final int index = MotionEventCompat.findPointerIndex(ev, activePointerId);
    if (index < 0) {
      return -1;
    }
    return MotionEventCompat.getY(ev, index);
  }
  private void animateOffsetToCorrectPosition() {
    mAnimateToCorrectPosition.reset();
    mAnimateToCorrectPosition.setDuration(ANIMATE_TO_TRIGGER_DURATION);
    mAnimateToCorrectPosition.setInterpolator(mDecelerateInterpolator);
    mCircleView.setAnimationListener(mRefreshListener);
    mCircleView.clearAnimation();
    mCircleView.startAnimation(mAnimateToCorrectPosition);
  }
  private final Animation mAnimateToCorrectPosition = new Animation() {
    @Override public void applyTransformation(float interpolatedTime, @NonNull Transformation t) {
    }
  };
  public void setMaxDropHeight(int dropHeight) {
    mWaveView.setMaxDropHeight(dropHeight);
  }
  private Animation.AnimationListener mRefreshListener = new Animation.AnimationListener() {
    @Override public void onAnimationStart(Animation animation) {
    }

    @Override public void onAnimationRepeat(Animation animation) {
    }
    @Override public void onAnimationEnd(Animation animation) {
      if (isRefreshing()) {
        mCircleView.makeProgressTransparent();
        mCircleView.startProgress();
        if (mNotify) {
          if (mListener != null) {
            mListener.onRefresh();
          }
        }
      } else {
        mCircleView.stopProgress();
        mCircleView.setVisibility(View.GONE);
        mCircleView.makeProgressTransparent();
        mWaveView.startDisappearCircleAnimation();
      }
    }
  };
  private void ensureTarget() {
    if (mTarget == null) {
      for (int i = 0; i < getChildCount(); i++) {
        View child = getChildAt(i);
        if (!child.equals(mCircleView) && !child.equals(mWaveView)) {
          mTarget = child;
          break;
        }
      }
    }
    if (mTarget == null) {
      throw new IllegalStateException("Tampilan ini harus berisi minimal satu Listview");
    }
  }
  private void setRefreshing(boolean refreshing, final boolean notify) {
    if (isRefreshing() != refreshing) {
      mNotify = notify;
      ensureTarget();
      setState(refreshing);
      if (isRefreshing()) {
        animateOffsetToCorrectPosition();
      } else {
        startScaleDownAnimation(mRefreshListener);
      }
    }
  }
  private void setEventPhase(EVENT_PHASE eventPhase) {
    mEventPhase = eventPhase;
  }
  private void setState(STATE state) {
    mState = state;
    setEnabled(true);
    if (!isRefreshing()) {
      setEventPhase(EVENT_PHASE.WAITING);
    }
  }
  private void setState(boolean doRefresh) {
    setState((doRefresh) ? STATE.REFRESHING : STATE.PENDING);
  }
  private void startScaleDownAnimation(Animation.AnimationListener listener) {
    Animation scaleDownAnimation = new Animation() {
      @Override public void applyTransformation(float interpolatedTime, Transformation t) {
        mCircleView.scaleWithKeepingAspectRatio(1 - interpolatedTime);
      }
    };
    scaleDownAnimation.setDuration(SCALE_DOWN_DURATION);
    mCircleView.setAnimationListener(listener);
    mCircleView.clearAnimation();
    mCircleView.startAnimation(scaleDownAnimation);
  }
  public void setColorSchemeResources(@IdRes int... colorResIds) {
    mCircleView.setProgressColorSchemeColorsFromResource(colorResIds);
  }
  public void setColorSchemeColors(int... colors) {
    ensureTarget();
    mCircleView.setProgressColorSchemeColors(colors);
  }
  
  public boolean isRefreshing() {
    return mState == STATE.REFRESHING;
  }
  private boolean isBeginning() {
    return mEventPhase == EVENT_PHASE.BEGINNING;
  }
  private boolean isExpanding() {
    return mEventPhase == EVENT_PHASE.EXPANDING;
  }
  private boolean isDropping() {
    return mEventPhase == EVENT_PHASE.DROPPING;
  }
  private boolean isAppearing() {
    return mEventPhase == EVENT_PHASE.APPEARING;
  }
  private boolean isWaiting() {
    return mEventPhase == EVENT_PHASE.WAITING;
  }
  public void setRefreshing(boolean refreshing) {
    if (refreshing && !isRefreshing()) {
      setState(true);
      mNotify = false;

      mIsManualRefresh = true;
      if (mWaveView.getCurrentCircleCenterY() == 0) {
        return;
      }
      mWaveView.manualRefresh();
      reInitCircleView();
      mCircleView.setTranslationY(
          mWaveView.getCurrentCircleCenterY() + mCircleView.getHeight() / 2);
      animateOffsetToCorrectPosition();
    } else {
      setRefreshing(refreshing, false);
    }
  }
  public boolean canChildScrollUp() {
    if (mTarget == null) {
      return false;
    }
    if (android.os.Build.VERSION.SDK_INT < 14) {
      if (mTarget instanceof AbsListView) {
        final AbsListView absListView = (AbsListView) mTarget;
        return absListView.getChildCount() > 0 && (absListView.getFirstVisiblePosition() > 0
            || absListView.getChildAt(0).getTop() < absListView.getPaddingTop());
      } else {
        return mTarget.getScrollY() > 0;
      }
    } else {
      return ViewCompat.canScrollVertically(mTarget, -1);
    }
  }
  public void setShadowRadius(int radius) {
    radius = Math.max(0, radius); 
    mWaveView.setShadowRadius(radius);
  }
  public void setWaveColor(int argbColor) {
    int alpha = 0xFF & (argbColor >> 24);
    int red = 0xFF & ( argbColor >> 16);
    int blue = 0xFF & (argbColor >> 0 );
    int green = 0xFF & (argbColor >> 8 );
    setWaveARGBColor(alpha, red, green, blue);
  }
  public void setWaveRGBColor(int r, int g, int b) {
    mWaveView.setWaveColor(Color.argb(0xFF, r, g, b));
  }
  public void setWaveARGBColor(int a, int r, int g, int b) {
    setWaveRGBColor(r, g, b);
    if (a == 0xFF) {
      return;
    }
    mWaveView.setAlpha((float) a / 255f);
  }
  private static int makeMeasureSpecExactly(int length) {
    return MeasureSpec.makeMeasureSpec(length, MeasureSpec.EXACTLY);
  }
  public void setOnRefreshListener(OnRefreshListener listener) {
    mListener = listener;
  }
  public interface OnRefreshListener {
    void onRefresh();
  }
  private class ProgressAnimationImageView extends AnimasiImage {
		private final MProgressDrawable mProgress;
    public ProgressAnimationImageView(Context context) {
      super(context);
      mProgress = new MProgressDrawable(context, RefreshLayout.this);

      if (Utils.isOver600dp(getContext())) { 
        mProgress.updateSizes(MProgressDrawable.LARGE);
      }
      initialize();
    }
    private void initialize() {
      setImageDrawable(null);

      mProgress.setBackgroundColor(Color.TRANSPARENT);

      setImageDrawable(mProgress);
      setVisibility(View.GONE);
    }
    public void measure() {
      final int circleDiameter = mProgress.getIntrinsicWidth();

      measure(makeMeasureSpecExactly(circleDiameter), makeMeasureSpecExactly(circleDiameter));
    }
    public void makeProgressTransparent() {
      mProgress.setAlpha(0xff);
    }
    public void showArrow(boolean show) {
      mProgress.showArrow(show);
    }
    public void setArrowScale(float scale) {
      mProgress.setArrowScale(scale);
    }
    public void setProgressAlpha(int alpha) {
      mProgress.setAlpha(alpha);
    }
    public void setProgressStartEndTrim(float startAngle, float endAngle) {
      mProgress.setStartEndTrim(startAngle, endAngle);
    }
    public void setProgressRotation(float rotation) {
      mProgress.setProgressRotation(rotation);
    }
    public void startProgress() {
      mProgress.start();
    }
    public void stopProgress() {
      mProgress.stop();
    }
    public void setProgressColorSchemeColors(@NonNull int... colors) {
      mProgress.setColorSchemeColors(colors);
    }
    public void setProgressColorSchemeColorsFromResource(@IdRes int... resources) {
      final Resources res = getResources();
      final int[] colorRes = new int[resources.length];

      for (int i = 0; i < resources.length; i++) {
        colorRes[i] = res.getColor(resources[i]);
      }
      setColorSchemeColors(colorRes);
    }
    public void scaleWithKeepingAspectRatio(float scale) {
      ViewCompat.setScaleX(this, scale);
      ViewCompat.setScaleY(this, scale);
    }
  }
}


Buat file WaveView.java


package com.happycodx.app;

import android.animation.Animator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.RectF;
import android.support.v4.view.ViewCompat;
import android.util.Log;
import android.view.View;
import android.view.ViewTreeObserver;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.view.animation.BounceInterpolator;

/**
 * @author happycodx
 */
public class WaveView extends View implements ViewTreeObserver.OnPreDrawListener {
	private static final long DROP_CIRCLE_ANIMATOR_DURATION = 500;
	private static final long DROP_VERTEX_ANIMATION_DURATION = 500;
	private static final long DROP_BOUNCE_ANIMATOR_DURATION = 500;
	private static final int DROP_REMOVE_ANIMATOR_DURATION = 200;
	private static final int WAVE_ANIMATOR_DURATION = 1000;
	private static final float MAX_WAVE_HEIGHT = 0.2f;
	private static final int SHADOW_COLOR = 0xFF000000;
	private float mDropCircleRadius = 100;
	private Paint mPaint;
	private Path mWavePath;
	private Path mDropTangentPath;
	private Path mDropCirclePath;
	private Paint mShadowPaint;
	private Path mShadowPath;
	private RectF mDropRect;
	private int mWidth;
	private float mCurrentCircleCenterY;
	private int mMaxDropHeight;
	private boolean mIsManualRefreshing = false;
	private boolean mDropHeightUpdated = false;
	private int mUpdateMaxDropHeight;
	private ValueAnimator mDropVertexAnimator;
	private ValueAnimator mDropBounceVerticalAnimator;
	private ValueAnimator mDropBounceHorizontalAnimator;
	private ValueAnimator mDropCircleAnimator;
	private ValueAnimator mDisappearCircleAnimator;
	private ValueAnimator mWaveReverseAnimator;
	private static final float[][] BEGIN_PHASE_POINTS = {
		{ 0.1655f, 0 },         
		{ 0.4188f, -0.0109f },  
		{ 0.4606f, -0.0049f },  
		{ 0.4893f, 0.f },    
		{ 0.4893f, 0.f },     
		{ 0.5f, 0.f }           
	};
	private static final float[][] APPEAR_PHASE_POINTS = {
		{ 0.1655f, 0.f },  
		{ 0.5237f, 0.0553f },  
		{ 0.4557f, 0.0936f },  
		{ 0.3908f, 0.1302f },    
		{ 0.4303f, 0.2173f },   
		{ 0.5f, 0.2173f }    
	};
	private static final float[][] EXPAND_PHASE_POINTS = {
		{ 0.1655f, 0.f },     
		{ 0.5909f, 0.0000f },    
		{ 0.4557f, 0.1642f },
		{ 0.3941f, 0.2061f },    
		{ 0.4303f, 0.2889f },    
		{ 0.5f, 0.2889f }     
	};
	private ValueAnimator.AnimatorUpdateListener mAnimatorUpdateListener =
	new ValueAnimator.AnimatorUpdateListener() {
        @Override public void onAnimationUpdate(ValueAnimator valueAnimator) {
			postInvalidate();
        }
	};
	public WaveView(Context context) {
		super(context);
		getViewTreeObserver().addOnPreDrawListener(this);
		initView();
	}
	@Override protected void onSizeChanged(int w, int h, int oldw, int oldh) {
		mWidth = w;
		mDropCircleRadius = w / 14.4f;
		updateMaxDropHeight((int) Math.min(Math.min(w, h), getHeight() - mDropCircleRadius));
		super.onSizeChanged(w, h, oldw, oldh);
	}
	@Override public boolean onPreDraw() {
		getViewTreeObserver().removeOnPreDrawListener(this);
		if (mDropHeightUpdated) {
			updateMaxDropHeight(mUpdateMaxDropHeight);
		}
		return false;
	}
	@Override protected void onDraw(Canvas canvas) {
		canvas.drawPath(mWavePath, mShadowPaint);
		canvas.drawPath(mWavePath, mPaint);
		mWavePath.rewind();
		mDropTangentPath.rewind();
		mDropCirclePath.rewind();
		float circleCenterY = (Float) mDropCircleAnimator.getAnimatedValue();
		float circleCenterX = mWidth / 2.f;
		mDropRect.setEmpty();
		float scale = (Float) mDisappearCircleAnimator.getAnimatedValue();
		float vertical = (Float) mDropBounceVerticalAnimator.getAnimatedValue();
		float horizontal = (Float) mDropBounceHorizontalAnimator.getAnimatedValue();
		mDropRect.set(circleCenterX - mDropCircleRadius * (1 + vertical) * scale
					  + mDropCircleRadius * horizontal / 2,
					  circleCenterY + mDropCircleRadius * (1 + horizontal) * scale
					  - mDropCircleRadius * vertical / 2,
					  circleCenterX + mDropCircleRadius * (1 + vertical) * scale
					  - mDropCircleRadius * horizontal / 2,
					  circleCenterY - mDropCircleRadius * (1 + horizontal) * scale
					  + mDropCircleRadius * vertical / 2);
		float vertex = (Float) mDropVertexAnimator.getAnimatedValue();
		mDropTangentPath.moveTo(circleCenterX, vertex);
		double q =
			(Math.pow(mDropCircleRadius, 2) + circleCenterY * vertex - Math.pow(circleCenterY, 2)) / (
            vertex - circleCenterY);
		double b = -2.0 * mWidth / 2;
		double c =
		Math.pow(q - circleCenterY, 2) + Math.pow(circleCenterX, 2) - Math.pow(mDropCircleRadius,2);
		double p1 = (-b + Math.sqrt(b * b - 4 * c)) / 2;
		double p2 = (-b - Math.sqrt(b * b - 4 * c)) / 2;
		mDropTangentPath.lineTo((float) p1, (float) q);
		mDropTangentPath.lineTo((float) p2, (float) q);
		mDropTangentPath.close();
		mShadowPath.set(mDropTangentPath);
		mShadowPath.addOval(mDropRect, Path.Direction.CCW);
		mDropCirclePath.addOval(mDropRect, Path.Direction.CCW);
		if (mDropVertexAnimator.isRunning()) {
			canvas.drawPath(mShadowPath, mShadowPaint);
		} else {
			canvas.drawPath(mDropCirclePath, mShadowPaint);
		}
		canvas.drawPath(mDropTangentPath, mPaint);
		canvas.drawPath(mDropCirclePath, mPaint);
	}
	@Override protected void onDetachedFromWindow() {
		if (mDisappearCircleAnimator != null) {
			mDisappearCircleAnimator.end();
			mDisappearCircleAnimator.removeAllUpdateListeners();
		}
		if (mDropCircleAnimator != null) {
			mDropCircleAnimator.end();
			mDropCircleAnimator.removeAllUpdateListeners();
		}
		if (mDropVertexAnimator != null) {
			mDropVertexAnimator.end();
			mDropVertexAnimator.removeAllUpdateListeners();
		}
		if (mWaveReverseAnimator != null) {
			mWaveReverseAnimator.end();
			mWaveReverseAnimator.removeAllUpdateListeners();
		}
		if (mDropBounceHorizontalAnimator != null) {
			mDropBounceHorizontalAnimator.end();
			mDropBounceHorizontalAnimator.removeAllUpdateListeners();
		}
		if (mDropBounceVerticalAnimator != null) {
			mDropBounceVerticalAnimator.end();
			mDropBounceVerticalAnimator.removeAllUpdateListeners();
		}
		super.onDetachedFromWindow();
	}
	private void initView() {
		setUpPaint();
		setUpPath();
		resetAnimator();
		mDropRect = new RectF();
		setLayerType(View.LAYER_TYPE_SOFTWARE, null);
	}
	private void setUpPaint() {
		mPaint = new Paint();
		mPaint.setColor(0xff2196F3);
		mPaint.setAntiAlias(true);
		mPaint.setStyle(Paint.Style.FILL);
		mShadowPaint = new Paint();
		mShadowPaint.setShadowLayer(10.0f, 0.0f, 2.0f, SHADOW_COLOR);
	}
	private void setUpPath() {
		mWavePath = new Path();
		mDropTangentPath = new Path();
		mDropCirclePath = new Path();
		mShadowPath = new Path();
	}
	private void resetAnimator() {
		mDropVertexAnimator = ValueAnimator.ofFloat(0.f, 0.f);
		mDropBounceVerticalAnimator = ValueAnimator.ofFloat(0.f, 0.f);
		mDropBounceHorizontalAnimator = ValueAnimator.ofFloat(0.f, 0.f);
		mDropCircleAnimator = ValueAnimator.ofFloat(-1000.f, -1000.f);
		mDropCircleAnimator.start();
		mDisappearCircleAnimator = ValueAnimator.ofFloat(1.f, 1.f);
		mDisappearCircleAnimator.setDuration(1);
		mDisappearCircleAnimator.start();
	}
	private void onPreDragWave() {
		if (mWaveReverseAnimator != null) {
			if (mWaveReverseAnimator.isRunning()) {
				mWaveReverseAnimator.cancel();
			}
		}
	}
	public void manualRefresh() {
		if (mIsManualRefreshing) {
			return;
		}
		mIsManualRefreshing = true;
		mDropCircleAnimator = ValueAnimator.ofFloat(mMaxDropHeight, mMaxDropHeight);
		mDropCircleAnimator.start();
		mDropVertexAnimator = ValueAnimator.ofFloat(mMaxDropHeight - mDropCircleRadius,
													mMaxDropHeight - mDropCircleRadius);
		mDropVertexAnimator.start();
		mCurrentCircleCenterY = mMaxDropHeight;
		postInvalidate();
	}

	public void beginPhase(float move1) {
		onPreDragWave();
		mWavePath.moveTo(0, 0);
		mWavePath.cubicTo(mWidth * BEGIN_PHASE_POINTS[0][0], BEGIN_PHASE_POINTS[0][1],
						  mWidth * BEGIN_PHASE_POINTS[1][0], mWidth * (BEGIN_PHASE_POINTS[1][1] + move1),
						  mWidth * BEGIN_PHASE_POINTS[2][0], mWidth * (BEGIN_PHASE_POINTS[2][1] + move1));
		mWavePath.cubicTo(mWidth * BEGIN_PHASE_POINTS[3][0],
						  mWidth * (BEGIN_PHASE_POINTS[3][1] + move1), mWidth * BEGIN_PHASE_POINTS[4][0],
						  mWidth * (BEGIN_PHASE_POINTS[4][1] + move1), mWidth * BEGIN_PHASE_POINTS[5][0],
						  mWidth * (BEGIN_PHASE_POINTS[5][1] + move1));
		mWavePath.cubicTo(mWidth - mWidth * BEGIN_PHASE_POINTS[4][0],
						  mWidth * (BEGIN_PHASE_POINTS[4][1] + move1), mWidth - mWidth * BEGIN_PHASE_POINTS[3][0],
						  mWidth * (BEGIN_PHASE_POINTS[3][1] + move1), mWidth - mWidth * BEGIN_PHASE_POINTS[2][0],
						  mWidth * (BEGIN_PHASE_POINTS[2][1] + move1));
		mWavePath.cubicTo(mWidth - mWidth * BEGIN_PHASE_POINTS[1][0],
						  mWidth * (BEGIN_PHASE_POINTS[1][1] + move1), mWidth - mWidth * BEGIN_PHASE_POINTS[0][0],
						  BEGIN_PHASE_POINTS[0][1], mWidth, 0);
		ViewCompat.postInvalidateOnAnimation(this);
	}

	public void appearPhase(float move1, float move2) {
		onPreDragWave();
		mWavePath.moveTo(0, 0);
		mWavePath.cubicTo(mWidth * APPEAR_PHASE_POINTS[0][0], mWidth * APPEAR_PHASE_POINTS[0][1],
						  mWidth * Math.min(BEGIN_PHASE_POINTS[1][0] + move2, APPEAR_PHASE_POINTS[1][0]),
						  mWidth * Math.max(BEGIN_PHASE_POINTS[1][1] + move1 - move2, APPEAR_PHASE_POINTS[1][1]),
						  mWidth * Math.max(BEGIN_PHASE_POINTS[2][0] - move2, APPEAR_PHASE_POINTS[2][0]),
						  mWidth * Math.max(BEGIN_PHASE_POINTS[2][1] + move1 - move2, APPEAR_PHASE_POINTS[2][1]));
		mWavePath.cubicTo(
			mWidth * Math.max(BEGIN_PHASE_POINTS[3][0] - move2, APPEAR_PHASE_POINTS[3][0]),
			mWidth * Math.min(BEGIN_PHASE_POINTS[3][1] + move1 + move2, APPEAR_PHASE_POINTS[3][1]),
			mWidth * Math.max(BEGIN_PHASE_POINTS[4][0] - move2, APPEAR_PHASE_POINTS[4][0]),
			mWidth * Math.min(BEGIN_PHASE_POINTS[4][1] + move1 + move2, APPEAR_PHASE_POINTS[4][1]),
			mWidth * APPEAR_PHASE_POINTS[5][0],
			mWidth * Math.min(BEGIN_PHASE_POINTS[0][1] + move1 + move2, APPEAR_PHASE_POINTS[5][1]));
		mWavePath.cubicTo(
			mWidth - mWidth * Math.max(BEGIN_PHASE_POINTS[4][0] - move2, APPEAR_PHASE_POINTS[4][0]),
			mWidth * Math.min(BEGIN_PHASE_POINTS[4][1] + move1 + move2, APPEAR_PHASE_POINTS[4][1]),
			mWidth - mWidth * Math.max(BEGIN_PHASE_POINTS[3][0] - move2, APPEAR_PHASE_POINTS[3][0]),
			mWidth * Math.min(BEGIN_PHASE_POINTS[3][1] + move1 + move2, APPEAR_PHASE_POINTS[3][1]),
			mWidth - mWidth * Math.max(BEGIN_PHASE_POINTS[2][0] - move2, APPEAR_PHASE_POINTS[2][0]),
			mWidth * Math.max(BEGIN_PHASE_POINTS[2][1] + move1 - move2, APPEAR_PHASE_POINTS[2][1]));
		mWavePath.cubicTo(
			mWidth - mWidth * Math.min(BEGIN_PHASE_POINTS[1][0] + move2, APPEAR_PHASE_POINTS[1][0]),
			mWidth * Math.max(BEGIN_PHASE_POINTS[1][1] + move1 - move2, APPEAR_PHASE_POINTS[1][1]),
			mWidth - mWidth * APPEAR_PHASE_POINTS[0][0], mWidth * APPEAR_PHASE_POINTS[0][1], mWidth, 0);
		mCurrentCircleCenterY =
			mWidth * Math.min(BEGIN_PHASE_POINTS[3][1] + move1 + move2, APPEAR_PHASE_POINTS[3][1])
            + mDropCircleRadius;
		ViewCompat.postInvalidateOnAnimation(this);
	}

	public void expandPhase(float move1, float move2, float move3) {
		onPreDragWave();
		mWavePath.moveTo(0, 0);
		mWavePath.cubicTo(mWidth * EXPAND_PHASE_POINTS[0][0], mWidth * EXPAND_PHASE_POINTS[0][1],
						  mWidth * Math.min(
							  Math.min(BEGIN_PHASE_POINTS[1][0] + move2, APPEAR_PHASE_POINTS[1][0]) + move3,
							  EXPAND_PHASE_POINTS[1][0]), mWidth * Math.max(
							  Math.max(BEGIN_PHASE_POINTS[1][1] + move1 - move2, APPEAR_PHASE_POINTS[1][1]) - move3,
							  EXPAND_PHASE_POINTS[1][1]),
						  mWidth * Math.max(BEGIN_PHASE_POINTS[2][0] - move2, EXPAND_PHASE_POINTS[2][0]),
						  mWidth * Math.min(
							  Math.max(BEGIN_PHASE_POINTS[2][1] + move1 - move2, APPEAR_PHASE_POINTS[2][1]) + move3,
							  EXPAND_PHASE_POINTS[2][1]));
		mWavePath.cubicTo(mWidth * Math.min(
							  Math.max(BEGIN_PHASE_POINTS[3][0] - move2, APPEAR_PHASE_POINTS[3][0]) + move3,
							  EXPAND_PHASE_POINTS[3][0]), mWidth * Math.min(
							  Math.min(BEGIN_PHASE_POINTS[3][1] + move1 + move2, APPEAR_PHASE_POINTS[3][1]) + move3,
							  EXPAND_PHASE_POINTS[3][1]),
						  mWidth * Math.max(BEGIN_PHASE_POINTS[4][0] - move2, EXPAND_PHASE_POINTS[4][0]),
						  mWidth * Math.min(
							  Math.min(BEGIN_PHASE_POINTS[4][1] + move1 + move2, APPEAR_PHASE_POINTS[4][1]) + move3,
							  EXPAND_PHASE_POINTS[4][1]), mWidth * EXPAND_PHASE_POINTS[5][0], mWidth * Math.min(
							  Math.min(BEGIN_PHASE_POINTS[0][1] + move1 + move2, APPEAR_PHASE_POINTS[5][1]) + move3,
							  EXPAND_PHASE_POINTS[5][1]));
		mWavePath.cubicTo(
			mWidth - mWidth * Math.max(BEGIN_PHASE_POINTS[4][0] - move2, EXPAND_PHASE_POINTS[4][0]),
			mWidth * Math.min(
				Math.min(BEGIN_PHASE_POINTS[4][1] + move1 + move2, APPEAR_PHASE_POINTS[4][1]) + move3,
				EXPAND_PHASE_POINTS[4][1]), mWidth - mWidth * Math.min(
				Math.max(BEGIN_PHASE_POINTS[3][0] - move2, APPEAR_PHASE_POINTS[3][0]) + move3,
				EXPAND_PHASE_POINTS[3][0]), mWidth * Math.min(
				Math.min(BEGIN_PHASE_POINTS[3][1] + move1 + move2, APPEAR_PHASE_POINTS[3][1]) + move3,
				EXPAND_PHASE_POINTS[3][1]),
			mWidth - mWidth * Math.max(BEGIN_PHASE_POINTS[2][0] - move2, EXPAND_PHASE_POINTS[2][0]),
			mWidth * Math.min(
				Math.max(BEGIN_PHASE_POINTS[2][1] + move1 - move2, APPEAR_PHASE_POINTS[2][1]) + move3,
				EXPAND_PHASE_POINTS[2][1]));
		mWavePath.cubicTo(mWidth - mWidth * Math.min(
							  Math.min(BEGIN_PHASE_POINTS[1][0] + move2, APPEAR_PHASE_POINTS[1][0]) + move3,
							  EXPAND_PHASE_POINTS[1][0]), mWidth * Math.max(
							  Math.max(BEGIN_PHASE_POINTS[1][1] + move1 - move2, APPEAR_PHASE_POINTS[1][1]) - move3,
							  EXPAND_PHASE_POINTS[1][1]), mWidth - mWidth * EXPAND_PHASE_POINTS[0][0],
						  mWidth * EXPAND_PHASE_POINTS[0][1], mWidth, 0);
		mCurrentCircleCenterY = mWidth * Math.min(
			Math.min(BEGIN_PHASE_POINTS[3][1] + move1 + move2, APPEAR_PHASE_POINTS[3][1]) + move3,
			EXPAND_PHASE_POINTS[3][1]) + mDropCircleRadius;
		ViewCompat.postInvalidateOnAnimation(this);
	}
	private void updateMaxDropHeight(int height) {
		if (500 * (mWidth / 1440.f) > height) {
			Log.w("WaveView", "DropHeight lebih dari " + 500 * (mWidth / 1440.f));
			return;
		}
		mMaxDropHeight = (int) Math.min(height, getHeight() - mDropCircleRadius);
		if (mIsManualRefreshing) {
			mIsManualRefreshing = false;
			manualRefresh();
		}
	}

	public void startDropAnimation() {
		mDisappearCircleAnimator = ValueAnimator.ofFloat(1.f, 1.f);
		mDisappearCircleAnimator.setDuration(1);
		mDisappearCircleAnimator.start();
		mDropCircleAnimator = ValueAnimator.ofFloat(500 * (mWidth / 1440.f), mMaxDropHeight);
		mDropCircleAnimator.setDuration(DROP_CIRCLE_ANIMATOR_DURATION);
		mDropCircleAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
				@Override public void onAnimationUpdate(ValueAnimator animation) {
					mCurrentCircleCenterY = (float) animation.getAnimatedValue();
					ViewCompat.postInvalidateOnAnimation(WaveView.this);
				}
			});
		mDropCircleAnimator.setInterpolator(new AccelerateDecelerateInterpolator());
		mDropCircleAnimator.start();
		mDropVertexAnimator = ValueAnimator.ofFloat(0.f, mMaxDropHeight - mDropCircleRadius);
		mDropVertexAnimator.setDuration(DROP_VERTEX_ANIMATION_DURATION);
		mDropVertexAnimator.addUpdateListener(mAnimatorUpdateListener);
		mDropVertexAnimator.start();
		mDropBounceVerticalAnimator = ValueAnimator.ofFloat(0.f, 1.f);
		mDropBounceVerticalAnimator.setDuration(DROP_BOUNCE_ANIMATOR_DURATION);
		mDropBounceVerticalAnimator.addUpdateListener(mAnimatorUpdateListener);
		mDropBounceVerticalAnimator.setInterpolator(new BounceInterpolate());
		mDropBounceVerticalAnimator.setStartDelay(DROP_VERTEX_ANIMATION_DURATION);
		mDropBounceVerticalAnimator.start();
		mDropBounceHorizontalAnimator = ValueAnimator.ofFloat(0.f, 1.f);
		mDropBounceHorizontalAnimator.setDuration(DROP_BOUNCE_ANIMATOR_DURATION);
		mDropBounceHorizontalAnimator.addUpdateListener(mAnimatorUpdateListener);
		mDropBounceHorizontalAnimator.setInterpolator(new BounceInterpolate());
		mDropBounceHorizontalAnimator.setStartDelay(
			(long) (DROP_VERTEX_ANIMATION_DURATION + DROP_BOUNCE_ANIMATOR_DURATION * 0.25));
		mDropBounceHorizontalAnimator.start();
	}
	public void startDisappearCircleAnimation() {
		mDisappearCircleAnimator = ValueAnimator.ofFloat(1.f, 0.f);
		mDisappearCircleAnimator.addUpdateListener(mAnimatorUpdateListener);
		mDisappearCircleAnimator.setDuration(DROP_REMOVE_ANIMATOR_DURATION);
		mDisappearCircleAnimator.addListener(new Animator.AnimatorListener() {
				@Override public void onAnimationStart(Animator animator) {
				}
				@Override public void onAnimationEnd(Animator animator) {
					resetAnimator();
					mIsManualRefreshing = false;
				}
				@Override public void onAnimationCancel(Animator animator) {
				}
				@Override public void onAnimationRepeat(Animator animator) {
				}
			});
		mDisappearCircleAnimator.start();
	}
	public void startWaveAnimation(float h) {
		h = Math.min(h, MAX_WAVE_HEIGHT) * mWidth;
		mWaveReverseAnimator = ValueAnimator.ofFloat(h, 0.f);
		mWaveReverseAnimator.setDuration(WAVE_ANIMATOR_DURATION);
		mWaveReverseAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
				@Override public void onAnimationUpdate(ValueAnimator valueAnimator) {
					float h = (Float) valueAnimator.getAnimatedValue();
					mWavePath.moveTo(0, 0);
					mWavePath.quadTo(0.25f * mWidth, 0, 0.333f * mWidth, h * 0.5f);
					mWavePath.quadTo(mWidth * 0.5f, h * 1.4f, 0.666f * mWidth, h * 0.5f);
					mWavePath.quadTo(0.75f * mWidth, 0, mWidth, 0);
					postInvalidate();
				}
			});
		mWaveReverseAnimator.setInterpolator(new BounceInterpolator());
		mWaveReverseAnimator.start();
	}
	public void animationDropCircle() {
		if (mDisappearCircleAnimator.isRunning()) {
			return;
		}
		startDropAnimation();
		startWaveAnimation(0.1f);
	}
	public float getCurrentCircleCenterY() {
		return mCurrentCircleCenterY;
	}
	public void setMaxDropHeight(int maxDropHeight) {
		if (mDropHeightUpdated) {
			updateMaxDropHeight(maxDropHeight);
		} else {
			mUpdateMaxDropHeight = maxDropHeight;
			mDropHeightUpdated = true;
			if (getViewTreeObserver().isAlive()) {
				getViewTreeObserver().removeOnPreDrawListener(this);
				getViewTreeObserver().addOnPreDrawListener(this);
			}
		}
	}
	public boolean isDisappearCircleAnimatorRunning() {
		return mDisappearCircleAnimator.isRunning();
	}
	public void setShadowRadius(int radius) {
		mShadowPaint.setShadowLayer(radius, 0.0f, 2.0f, SHADOW_COLOR);
	}
	public void setWaveColor(int color) {
		mPaint.setColor(color);
		invalidate();
	}
	public void setWaveARGBColor(int a, int r, int g, int b) {
		mPaint.setARGB(a, r, g, b);
		invalidate();
	}
}

Buat file AnimasiImage.java


package com.happycodx.app;
import android.content.Context;
import android.view.animation.Animation;
import android.widget.ImageView;
/**
 * @author happycodx
 */
class AnimasiImage extends ImageView {
	private Animation.AnimationListener mListener;
	public AnimasiImage(Context context) {
		super(context);
	}
	public void setAnimationListener(Animation.AnimationListener listener) {
		mListener = listener;
	}
	@Override public void onAnimationStart() {
		super.onAnimationStart();
		if (mListener != null) {
			mListener.onAnimationStart(getAnimation());
		}
	}
	@Override public void onAnimationEnd() {
		super.onAnimationEnd();
		if (mListener != null) {
			mListener.onAnimationEnd(getAnimation());
		}
	}
}

Buat file BounceInterpolate.java


package com.happycodx.app;
import android.content.Context;
import android.util.AttributeSet;
import android.view.animation.Interpolator;
public class BounceInterpolate implements Interpolator {
    public BounceInterpolate() {
    }
    @SuppressWarnings({"UnusedDeclaration"})
    public BounceInterpolate(Context context, AttributeSet attrs) {
    }
    @Override
    public float getInterpolation(float v) {
        //y = -19 * (x - 0.125)^2 + 1.3     (0 <= x < 0.25)
        //y = -6.5 * (x - 0.625)^2 + 1.1    (0.5 <= x < 0.75)
        //y = 0                             (0.25 <= x < 0.5 && 0.75 <= x <=1)

        if (v < 0.25f) {
            return -38.4f * (float) Math.pow(v - 0.125, 2) + 0.6f;
        } else if (v >= 0.5 && v < 0.75) {
            return -19.2f * (float) Math.pow(v - 0.625, 2) + 0.3f;
        } else {
            return 0;
        }
    }
}


Buat file MProgressDrawable.java


package com.happycodx.app;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.ColorFilter;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.drawable.Animatable;
import android.graphics.drawable.Drawable;
import android.support.annotation.IntDef;
import android.support.annotation.NonNull;
import android.util.DisplayMetrics;
import android.view.View;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.view.animation.Animation;
import android.view.animation.Interpolator;
import android.view.animation.LinearInterpolator;
import android.view.animation.Transformation;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
public class MProgressDrawable extends Drawable implements Animatable {
    private static final Interpolator LINEAR_INTERPOLATOR = new LinearInterpolator();
    private static final Interpolator END_CURVE_INTERPOLATOR = new EndCurveInterpolator();
    private static final Interpolator START_CURVE_INTERPOLATOR = new StartCurveInterpolator();
    private static final Interpolator EASE_INTERPOLATOR = new AccelerateDecelerateInterpolator();
    @Retention(RetentionPolicy.CLASS)
    @IntDef({LARGE, DEFAULT})
    public @interface ProgressDrawableSize {}
    // Maps ke ProgressBar.Large style
    static final int LARGE = 0;
    // Maps ke ProgressBar default style
    static final int DEFAULT = 1;
    // Maps ke ProgressBar default style
    private static final int CIRCLE_DIAMETER = 40;
    private static final float CENTER_RADIUS = 8.75f; // harus di tambah hingga 10 jika + stroke_width
    private static final float STROKE_WIDTH = 2.5f;
    // Maps ke ProgressBar.Large style
    private static final int CIRCLE_DIAMETER_LARGE = 56;
    private static final float CENTER_RADIUS_LARGE = 12.5f;
    private static final float STROKE_WIDTH_LARGE = 3f;
    private final int[] COLORS = new int[] {
		Color.BLACK
    };
    /** Durasi untuk sekali putaran dengan milliseconds. */
    private static final int ANIMATION_DURATION = 1000 * 80 / 60;
    private static final float NUM_POINTS = 5f;
    private final ArrayList mAnimators = new ArrayList();
    private final Ring mRing;
    private float mRotation;
    private static final int ARROW_WIDTH = 10;
    private static final int ARROW_HEIGHT = 5;
    private static final float ARROW_OFFSET_ANGLE = 5;
	private static final int ARROW_WIDTH_LARGE = 12;
    private static final int ARROW_HEIGHT_LARGE = 6;
    private static final float MAX_PROGRESS_ARC = .8f;
    private Resources mResources;
    private View mParent;
    private Animation mAnimation;
    private float mRotationCount;
    private double mWidth;
    private double mHeight;
    boolean mFinishing;
    public MProgressDrawable(Context context, View parent) {
        mParent = parent;
        mResources = context.getResources();
        mRing = new Ring(mCallback);
        mRing.setColors(COLORS);
        updateSizes(DEFAULT);
        setupAnimators();
    }
    private void setSizeParameters(double progressCircleWidth, double progressCircleHeight,
        double centerRadius, double strokeWidth, float arrowWidth, float arrowHeight) {
        final Ring ring = mRing;
        final DisplayMetrics metrics = mResources.getDisplayMetrics();
        final float screenDensity = metrics.density;
        mWidth = progressCircleWidth * screenDensity;
        mHeight = progressCircleHeight * screenDensity;
        ring.setStrokeWidth((float) strokeWidth * screenDensity);
        ring.setCenterRadius(centerRadius * screenDensity);
        ring.setColorIndex(0);
        ring.setArrowDimensions(arrowWidth * screenDensity, arrowHeight * screenDensity);
        ring.setInsets((int) mWidth, (int) mHeight);
    }
    public void updateSizes(@ProgressDrawableSize int size) {
        if (size == LARGE) {
            setSizeParameters(CIRCLE_DIAMETER_LARGE, CIRCLE_DIAMETER_LARGE, CENTER_RADIUS_LARGE,
							  STROKE_WIDTH_LARGE, ARROW_WIDTH_LARGE, ARROW_HEIGHT_LARGE);
        } else {
            setSizeParameters(CIRCLE_DIAMETER, CIRCLE_DIAMETER, CENTER_RADIUS, STROKE_WIDTH,
							  ARROW_WIDTH, ARROW_HEIGHT);
        }
    }
    public void showArrow(boolean show) {
        mRing.setShowArrow(show);
    }
    public void setArrowScale(float scale) {
        mRing.setArrowScale(scale);
    }
    public void setStartEndTrim(float startAngle, float endAngle) {
        mRing.setStartTrim(startAngle);
        mRing.setEndTrim(endAngle);
    }
    public void setProgressRotation(float rotation) {
        mRing.setRotation(rotation);
    }
    public void setBackgroundColor(int color) {
        mRing.setBackgroundColor(color);
    }
    public void setColorSchemeColors(int... colors) {
        mRing.setColors(colors);
        mRing.setColorIndex(0);
    }
    @Override
    public int getIntrinsicHeight() {
        return (int) mHeight;
    }
    @Override
    public int getIntrinsicWidth() {
        return (int) mWidth;
    }
    @Override
    public void draw(Canvas c) {
        final Rect bounds = getBounds();
        final int saveCount = c.save();
        c.rotate(mRotation, bounds.exactCenterX(), bounds.exactCenterY());
        mRing.draw(c, bounds);
        c.restoreToCount(saveCount);
    }
    @Override
    public void setAlpha(int alpha) {
        mRing.setAlpha(alpha);
    }
    public int getAlpha() {
        return mRing.getAlpha();
    }
    @Override
    public void setColorFilter(ColorFilter colorFilter) {
        mRing.setColorFilter(colorFilter);
    }
    @SuppressWarnings("unused")
    void setRotation(float rotation) {
        mRotation = rotation;
        invalidateSelf();
    }
    @SuppressWarnings("unused")
    private float getRotation() {
        return mRotation;
    }
    @Override
    public int getOpacity() {
        return PixelFormat.TRANSLUCENT;
    }
    @Override
    public boolean isRunning() {
        final ArrayList animators = mAnimators;
        final int N = animators.size();
        for (int i = 0; i < N; i++) {
            final Animation animator = animators.get(i);
            if (animator.hasStarted() && !animator.hasEnded()) {
                return true;
            }
        }
        return false;
    }
    @Override
    public void start() {
        mAnimation.reset();
        mRing.storeOriginals();
        if (mRing.getEndTrim() != mRing.getStartTrim()) {
            mFinishing = true;
            mAnimation.setDuration(ANIMATION_DURATION/2);
            mParent.startAnimation(mAnimation);
        } else {
            mRing.setColorIndex(0);
            mRing.resetOriginals();
            mAnimation.setDuration(ANIMATION_DURATION);
            mParent.startAnimation(mAnimation);
        }
    }
    @Override
    public void stop() {
        mParent.clearAnimation();
        setRotation(0);
        mRing.setShowArrow(false);
        mRing.setColorIndex(0);
        mRing.resetOriginals();
    }
    private void applyFinishTranslation(float interpolatedTime, Ring ring) {
        float targetRotation = (float) (Math.floor(ring.getStartingRotation() / MAX_PROGRESS_ARC)
			+ 1f);
        final float startTrim = ring.getStartingStartTrim()
			+ (ring.getStartingEndTrim() - ring.getStartingStartTrim()) * interpolatedTime;
        ring.setStartTrim(startTrim);
        final float rotation = ring.getStartingRotation()
			+ ((targetRotation - ring.getStartingRotation()) * interpolatedTime);
        ring.setRotation(rotation);
    }
    private void setupAnimators() {
        final Ring ring = mRing;
        final Animation animation = new Animation() {
            @Override
            public void applyTransformation(float interpolatedTime, Transformation t) {
                if (mFinishing) {
                    applyFinishTranslation(interpolatedTime, ring);
                } else {
                    final float minProgressArc = (float) Math.toRadians(
						ring.getStrokeWidth() / (2 * Math.PI * ring.getCenterRadius()));
                    final float startingEndTrim = ring.getStartingEndTrim();
                    final float startingTrim = ring.getStartingStartTrim();
                    final float startingRotation = ring.getStartingRotation();
                    final float minArc = MAX_PROGRESS_ARC - minProgressArc;
                    final float endTrim = startingEndTrim + (minArc
						* START_CURVE_INTERPOLATOR.getInterpolation(interpolatedTime));
                    ring.setEndTrim(endTrim);
                    final float startTrim = startingTrim + (MAX_PROGRESS_ARC
						* END_CURVE_INTERPOLATOR.getInterpolation(interpolatedTime));
                    ring.setStartTrim(startTrim);
                    final float rotation = startingRotation + (0.25f * interpolatedTime);
                    ring.setRotation(rotation);
                    float groupRotation = ((720.0f / NUM_POINTS) * interpolatedTime)
						+ (720.0f * (mRotationCount / NUM_POINTS));
                    setRotation(groupRotation);
                }
            }
        };
        animation.setRepeatCount(Animation.INFINITE);
        animation.setRepeatMode(Animation.RESTART);
        animation.setInterpolator(LINEAR_INTERPOLATOR);
        animation.setAnimationListener(new Animation.AnimationListener() {
				@Override
				public void onAnimationStart(Animation animation) {
					mRotationCount = 0;
				}
				@Override
				public void onAnimationEnd(Animation animation) {
				}
				@Override
				public void onAnimationRepeat(Animation animation) {
					ring.storeOriginals();
					ring.goToNextColor();
					ring.setStartTrim(ring.getEndTrim());
					if (mFinishing) {
						mFinishing = false;
						animation.setDuration(ANIMATION_DURATION);
						ring.setShowArrow(false);
					} else {
						mRotationCount = (mRotationCount + 1) % (NUM_POINTS);
					}
				}
			});
        mAnimation = animation;
    }
    private final Callback mCallback = new Callback() {
        @Override
        public void invalidateDrawable(Drawable d) {
            invalidateSelf();
        }
        @Override
        public void scheduleDrawable(Drawable d, Runnable what, long when) {
            scheduleSelf(what, when);
        }
        @Override
        public void unscheduleDrawable(Drawable d, Runnable what) {
            unscheduleSelf(what);
        }
    };
    private static class Ring {
        private final RectF mTempBounds = new RectF();
        private final Paint mPaint = new Paint();
        private final Paint mArrowPaint = new Paint();
        private final Callback mCallback;
        private float mStartTrim = 0.0f;
        private float mEndTrim = 0.0f;
        private float mRotation = 0.0f;
        private float mStrokeWidth = 5.0f;
        private float mStrokeInset = 2.5f;
        private int[] mColors;
        private int mColorIndex;
        private float mStartingStartTrim;
        private float mStartingEndTrim;
        private float mStartingRotation;
        private boolean mShowArrow;
        private Path mArrow;
        private float mArrowScale;
        private double mRingCenterRadius;
        private int mArrowWidth;
        private int mArrowHeight;
        private int mAlpha;
        private final Paint mCirclePaint = new Paint();
        private int mBackgroundColor;
        public Ring(Callback callback) {
            mCallback = callback;
            mPaint.setStrokeCap(Paint.Cap.SQUARE);
            mPaint.setAntiAlias(true);
            mPaint.setStyle(Paint.Style.STROKE);
            mArrowPaint.setStyle(Paint.Style.FILL);
            mArrowPaint.setAntiAlias(true);
        }
        public void setBackgroundColor(int color) {
            mBackgroundColor = color;
        }
        public void setArrowDimensions(float width, float height) {
            mArrowWidth = (int) width;
            mArrowHeight = (int) height;
        }
        public void draw(Canvas c, Rect bounds) {
            final RectF arcBounds = mTempBounds;
            arcBounds.set(bounds);
            arcBounds.inset(mStrokeInset, mStrokeInset);
            final float startAngle = (mStartTrim + mRotation) * 360;
            final float endAngle = (mEndTrim + mRotation) * 360;
            float sweepAngle = endAngle - startAngle;
            mPaint.setColor(mColors[mColorIndex]);
            c.drawArc(arcBounds, startAngle, sweepAngle, false, mPaint);
            drawTriangle(c, startAngle, sweepAngle, bounds);
            if (mAlpha < 255) {
                mCirclePaint.setColor(mBackgroundColor);
                mCirclePaint.setAlpha(255 - mAlpha);
                c.drawCircle(bounds.exactCenterX(), bounds.exactCenterY(), bounds.width() / 2,
							 mCirclePaint);
            }
        }
        private void drawTriangle(Canvas c, float startAngle, float sweepAngle, Rect bounds) {
            if (mShowArrow) {
                if (mArrow == null) {
                    mArrow = new android.graphics.Path();
                    mArrow.setFillType(android.graphics.Path.FillType.EVEN_ODD);
                } else {
                    mArrow.reset();
                }
                float inset = (int) mStrokeInset / 2 * mArrowScale;
                float x = (float) (mRingCenterRadius * Math.cos(0) + bounds.exactCenterX());
                float y = (float) (mRingCenterRadius * Math.sin(0) + bounds.exactCenterY());
                mArrow.moveTo(0, 0);
                mArrow.lineTo(mArrowWidth * mArrowScale, 0);
                mArrow.lineTo((mArrowWidth * mArrowScale / 2), (mArrowHeight
							  * mArrowScale));
                mArrow.offset(x - inset, y);
                mArrow.close();
                // draw a triangle
                mArrowPaint.setColor(mColors[mColorIndex]);
                c.rotate(startAngle + sweepAngle - ARROW_OFFSET_ANGLE, bounds.exactCenterX(),
						 bounds.exactCenterY());
                c.drawPath(mArrow, mArrowPaint);
            }
        }
        public void setColors(@NonNull int[] colors) {
            mColors = colors;
            setColorIndex(0);
        }
        public void setColorIndex(int index) {
            mColorIndex = index;
        }
        public void goToNextColor() {
            mColorIndex = (mColorIndex + 1) % (mColors.length);
        }

        public void setColorFilter(ColorFilter filter) {
            mPaint.setColorFilter(filter);
            invalidateSelf();
        }
        public void setAlpha(int alpha) {
            mAlpha = alpha;
        }
        public int getAlpha() {
            return mAlpha;
        }
        public void setStrokeWidth(float strokeWidth) {
            mStrokeWidth = strokeWidth;
            mPaint.setStrokeWidth(strokeWidth);
            invalidateSelf();
        }
        @SuppressWarnings("unused")
        public float getStrokeWidth() {
            return mStrokeWidth;
        }
        @SuppressWarnings("unused")
        public void setStartTrim(float startTrim) {
            mStartTrim = startTrim;
            invalidateSelf();
        }
        @SuppressWarnings("unused")
        public float getStartTrim() {
            return mStartTrim;
        }
        public float getStartingStartTrim() {
            return mStartingStartTrim;
        }
        public float getStartingEndTrim() {
            return mStartingEndTrim;
        }
        @SuppressWarnings("unused")
        public void setEndTrim(float endTrim) {
            mEndTrim = endTrim;
            invalidateSelf();
        }
        @SuppressWarnings("unused")
        public float getEndTrim() {
            return mEndTrim;
        }
        @SuppressWarnings("unused")
        public void setRotation(float rotation) {
            mRotation = rotation;
            invalidateSelf();
        }
        @SuppressWarnings("unused")
        public float getRotation() {
            return mRotation;
        }
        public void setInsets(int width, int height) {
            final float minEdge = (float) Math.min(width, height);
            float insets;
            if (mRingCenterRadius <= 0 || minEdge < 0) {
                insets = (float) Math.ceil(mStrokeWidth / 2.0f);
            } else {
                insets = (float) (minEdge / 2.0f - mRingCenterRadius);
            }
            mStrokeInset = insets;
        }
        @SuppressWarnings("unused")
        public float getInsets() {
            return mStrokeInset;
        }
        public void setCenterRadius(double centerRadius) {
            mRingCenterRadius = centerRadius;
        }

        public double getCenterRadius() {
            return mRingCenterRadius;
        }
        public void setShowArrow(boolean show) {
            if (mShowArrow != show) {
                mShowArrow = show;
                invalidateSelf();
            }
        }
        public void setArrowScale(float scale) {
            if (scale != mArrowScale) {
                mArrowScale = scale;
                invalidateSelf();
            }
        }
        public float getStartingRotation() {
            return mStartingRotation;
        }
        public void storeOriginals() {
            mStartingStartTrim = mStartTrim;
            mStartingEndTrim = mEndTrim;
            mStartingRotation = mRotation;
        }
        public void resetOriginals() {
            mStartingStartTrim = 0;
            mStartingEndTrim = 0;
            mStartingRotation = 0;
            setStartTrim(0);
            setEndTrim(0);
            setRotation(0);
        }

        private void invalidateSelf() {
            mCallback.invalidateDrawable(null);
        }
    }
    private static class EndCurveInterpolator extends AccelerateDecelerateInterpolator {
        @Override
        public float getInterpolation(float input) {
            return super.getInterpolation(Math.max(0, (input - 0.5f) * 2.0f));
        }
    }
    private static class StartCurveInterpolator extends AccelerateDecelerateInterpolator {
        @Override
        public float getInterpolation(float input) {
            return super.getInterpolation(Math.min(1, input * 2.0f));
        }
    }
}


Buat file Utils.java



package com.happycodx.app;
import android.content.Context;
import android.content.res.Resources;
import android.util.DisplayMetrics;
/**
 * @author happycodx
 */
public class Utils {
    private Utils(){}
    public static boolean isOver600dp(Context context) {
        Resources resources = context.getResources();
        DisplayMetrics displayMetrics = resources.getDisplayMetrics();
        return displayMetrics.widthPixels / displayMetrics.density >= 600;
    }
}

Tahap terakhir dalam file MainActivity.java kita buat ArrayList string dan menambahkan nya ke ArrayAdapter yang nantinya kita terapkan ke dalam listview.


package com.happycodx.app;
import android.graphics.Color;
import android.os.Bundle;
import android.os.Handler;
import android.support.v7.app.AppCompatActivity;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.SeekBar;
import java.util.ArrayList;
import java.util.Random;
public class MainActivity extends AppCompatActivity implements RefreshLayout.OnRefreshListener {
	private RefreshLayout mRefresh;
	private ListView listview;
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    requestWindowFeature(Window.FEATURE_NO_TITLE);
    getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
	listview = (ListView) findViewById(R.id.listview);
    initView();
	setData();
  }
  private void initView() {
	  mRefresh = (RefreshLayout) findViewById(R.id.swipeRefresh);
	  mRefresh.setColorSchemeColors(Color.WHITE, Color.WHITE);
	  mRefresh.setOnRefreshListener(this);
	  mRefresh.setWaveColor(getResources().getColor(R.color.colorAccent));
  }
  private void setData() {
		ArrayList sampleArrayStr = new ArrayList<>();
		for (int i = 0; i < 60; i++) {
			sampleArrayStr.add("" );
		}
		ArrayAdapter adapter = new ArrayAdapter<>(getApplicationContext(), android.R.layout.simple_list_item_1, sampleArrayStr);
		listview.setAdapter(adapter);
}
  private void refresh(){
    new Handler().postDelayed(new Runnable() {
      @Override
      public void run() {
		  mRefresh.setRefreshing(false);
      }
    }, 3000);
  }
  @Override
  protected void onResume() {
    refresh();
    super.onResume();
  }
  @Override
  public void onRefresh() {
    refresh();
  
  }
}

Baca Juga
Posting Komentar (0)
Lebih baru Lebih lama