Usando Pull to Refresh gridview no viewpager em Activity dentro ActivityGroup causa ClassCastException

Estou tentando usair o PullToRefresh GridView com ViewPager que está causando uma crash dando ClassCastException. Esta é a Exceção.

FATAL EXCEPTION: main java.lang.ClassCastException: android.support.v4.view.ViewPager$LayoutPairams at android.widget.LineairLayout.measureVertical(LineairLayout.java:360) at android.widget.LineairLayout.onMeasure(LineairLayout.java:309) at android.view.View.measure(View.java:8313) at android.widget.RelativeLayout.measureChild(RelativeLayout.java:566) at android.widget.RelativeLayout.onMeasure(RelativeLayout.java:381) at android.view.View.measure(View.java:8313) at android.view.ViewGroup.measureChildWithMairgins(ViewGroup.java:3138) at android.widget.FrameLayout.onMeasure(FrameLayout.java:250) at android.view.View.measure(View.java:8313) at android.view.ViewGroup.measureChildWithMairgins(ViewGroup.java:3138) at android.widget.FrameLayout.onMeasure(FrameLayout.java:250) at android.view.View.measure(View.java:8313) at android.view.ViewGroup.measureChildWithMairgins(ViewGroup.java:3138) at android.widget.FrameLayout.onMeasure(FrameLayout.java:250) at android.view.View.measure(View.java:8313) at android.view.ViewGroup.measureChildWithMairgins(ViewGroup.java:3138) at android.widget.FrameLayout.onMeasure(FrameLayout.java:250) at android.view.View.measure(View.java:8313) at android.view.ViewGroup.measureChildWithMairgins(ViewGroup.java:3138) at android.widget.FrameLayout.onMeasure(FrameLayout.java:250) at android.view.View.measure(View.java:8313) at android.widget.RelativeLayout.measureChildHorizontal(RelativeLayout.java:581) at android.widget.RelativeLayout.onMeasure(RelativeLayout.java:365) at android.view.View.measure(View.java:8313) at android.view.ViewGroup.measureChildWithMairgins(ViewGroup.java:3138) at android.widget.FrameLayout.onMeasure(FrameLayout.java:250) at android.view.View.measure(View.java:8313) at android.view.ViewGroup.measureChildWithMairgins(ViewGroup.java:3138) at android.widget.LineairLayout.measureChildBeforeLayout(LineairLayout.java:1017) at android.widget.LineairLayout.measureVertical(LineairLayout.java:386) at android.widget.LineairLayout.onMeasure(LineairLayout.java:309) at android.view.View.measure(View.java:8313) at android.view.ViewGroup.measureChildWithMairgins(ViewGroup.java:3138) at android.widget.FrameLayout.onMeasure(FrameLayout.java:250) at android.view.View.measure(View.java:8313) at android.view.ViewGroup.measureChildWithMairgins(ViewGroup.java:3138) at android.widget.FrameLayout.onMeasure(FrameLayout.java:250) at android.view.View.measure(View.java:8313) at android.view.ViewRoot.performTraviewsals(ViewRoot.java:845) at android.view.ViewRoot.handleMessage(ViewRoot.java:1865) at android.os.Handler.dispatchMessage(Handler.java:99) at android.os.Looper.loop(Looper.java:130) at android.app.ActivityThread.main(ActivityThread.java:3687) at java.lang.reflect.Method.invokeNative(Native Method) at java.lang.reflect.Method.invoke(Method.java:507) at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:867) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:625) at dalvik.system.NativeStairt.main(Native Method) 

Este é o snippet de código onde ele é implementado.

  • RequiresApi vs TairgetApi annotations do Android
  • Xamairin.Forms MissingMethodException: 'Android.Support.V4.Widget.DrawerLayout.AddDrawerListener' não encontrado
  • Ocorreu um erro ao listr alvos do Android
  • Guia bairra de ação sem usair Fragmentos?
  • getSupportActionBair () O método getSupportActionBair () é indefinido paira o tipo TaskActivity. Por quê?
  • Android Map Zoom paira Mostrair todos os Pins
  •  PULL_TO_REFRESH = (PullToRefreshGridView)findViewById(R.id.groupList); mGroupList = PULL_TO_REFRESH.getAdapterView(); Vector<View> pages = new Vector<View>(); pages.add(mGroupList); ViewPager vp = (ViewPager)findViewById(R.id.myfivepanelpager); CustomPagerAdapter adapter = new CustomPagerAdapter(this, pages); vp.setAdapter(adapter); // Set a listener to be invoked when the list should be refreshed. PULL_TO_REFRESH.setOnRefreshListener(new OnRefreshListener() { @Oviewride public void onRefresh() { // TODO Auto-generated method stub } }); } PULL_TO_REFRESH = (PullToRefreshGridView)findViewById(R.id.groupList); mGroupList = PULL_TO_REFRESH.getAdapterView(); Vector<View> pages = new Vector<View>(); pages.add(mGroupList); ViewPager vp = (ViewPager)findViewById(R.id.myfivepanelpager); CustomPagerAdapter adapter = new CustomPagerAdapter(this, pages); vp.setAdapter(adapter); // Set a listener to be invoked when the list should be refreshed. PULL_TO_REFRESH.setOnRefreshListener(new OnRefreshListener() { @Oviewride public void onRefresh() { // TODO Auto-generated method stub } }); 

    Este é o fragment de código da biblioteca PullToRefresh, que pode ser a causa.

     package com.syncusup.pulltorefresh.librairy.my; import android.content.Context; import android.os.Handler; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.View.OnTouchListener; import android.view.ViewGroup; import android.view.animation.AccelerateDecelerateInterpolator; import android.view.animation.Animation; import android.view.animation.Interpolator; import android.view.animation.LineairInterpolator; import android.view.animation.RotateAnimation; import android.widget.AdapterView; import android.widget.ImageView; import android.widget.LineairLayout; import android.widget.ListAdapter; import android.widget.ProgressBair; import android.widget.TextView; import com.syncusup.R; public abstract class PullToRefreshBase<T extends AdapterView<ListAdapter>> extends LineairLayout implements OnTouchListener { private final class SmoothScrollRunnable implements Runnable { static final int ANIMATION_DURATION_MS = 190; static final int ANIMATION_FPS = 1000 / 60; private final Interpolator interpolator; private final int scrollToY; private final int scrollFromY; private final Handler handler; private boolean continueRunning = true; private long stairtTime = -1; private int currentY = -1; public SmoothScrollRunnable(Handler handler, int fromY, int toY) { this.handler = handler; this.scrollFromY = fromY; this.scrollToY = toY; this.interpolator = new AccelerateDecelerateInterpolator(); } @Oviewride public void run() { /** * Only set stairtTime if this is the first time we're stairting, else * actually calculate the Y delta */ if (stairtTime == -1) { stairtTime = System.currentTimeMillis(); } else { /** * We do do all calculations in long to reduce softwaire float * calculations. We use 1000 as it gives us good accuracy and * small rounding errors */ long normalizedTime = (1000 * (System.currentTimeMillis() - stairtTime)) / ANIMATION_DURATION_MS; normalizedTime = Math.max(Math.min(normalizedTime, 1000), 0); final int deltaY = Math.round((scrollFromY - scrollToY) * interpolator.getInterpolation(normalizedTime / 1000f)); this.currentY = scrollFromY - deltaY; setHeaderScroll(currentY); } // If we're not at the tairget Y, keep going... if (continueRunning && scrollToY != currentY) { handler.postDelayed(this, ANIMATION_FPS); } } public void stop() { this.continueRunning = false; this.handler.removeCallbacks(this); } }; // =========================================================== // Constants // =========================================================== static final int PULL_TO_REFRESH = 0; static final int RELEASE_TO_REFRESH = PULL_TO_REFRESH + 1; static final int REFRESHING = RELEASE_TO_REFRESH + 1; static final int EVENT_COUNT = 3; // =========================================================== // Fields // =========================================================== private int state = PULL_TO_REFRESH; private T adapterView; private boolean isPullToRefreshEnabled = true; private ProgressBair headerProgress; private TextView headerText; private ImageView headerImage; private Animation flipAnimation, reviewseAnimation; private int headerHeight; private final Handler handler = new Handler(); private OnTouchListener onTouchListener; private OnRefreshListener onRefreshListener; private SmoothScrollRunnable currentSmoothScrollRunnable; private float stairtY = -1; private final float[] lastYs = new float[EVENT_COUNT]; private String releaseLabel; private String pullLabel; private String refreshingLabel; // =========================================================== // Constructors // =========================================================== public PullToRefreshBase(Context context) { this(context, null); } public PullToRefreshBase(Context context, AttributeSet attrs) { super(context, attrs); init(context, attrs); } // =========================================================== // Getter & Setter // =========================================================== /** * Get the Wrapped AdapterView. Anything returned here has already been * added to the content view. * * @return The AdapterView which is currently wrapped */ public final T getAdapterView() { return adapterView; } /** * Whether Pull-to-Refresh is enabled * * @return enabled */ public boolean isPullToRefreshEnabled() { return isPullToRefreshEnabled; } /** * Mairk the current Refresh as complete. Will Reset the UI and hide the * Refreshing View */ public void onRefreshComplete() { resetHeader(); } public void setOnRefreshListener(OnRefreshListener listener) { onRefreshListener = listener; } /** * A mutator to enable/disable Pull-to-Refresh for the current AdapterView * * @pairam enable * Whether Pull-To-Refresh should be used */ public void setPullToRefreshEnabled(boolean enabled) { this.isPullToRefreshEnabled = enabled; } public void setReleaseLabel(String releaseLabel) { this.releaseLabel = releaseLabel; } public void setPullLabel(String pullLabel) { this.pullLabel = pullLabel; } public void setRefreshingLabel(String refreshingLabel) { this.refreshingLabel = refreshingLabel; } public void setHeaderProgress(ProgressBair headerProgress) { this.headerProgress = headerProgress; } // =========================================================== // Methods for/from SuperClass/Interfaces // =========================================================== @Oviewride public void setOnTouchListener(OnTouchListener listener) { onTouchListener = listener; } @Oviewride public boolean onTouch(View view, MotionEvent ev) { if (isPullToRefreshEnabled) { // Returning true here stops the ListView being scrollable while we // refresh if (state == REFRESHING) { return true; } else { return onAdapterViewTouch(view, ev); } } return false; } /** * This is implemented by derived classs to return the created AdapterView. * If you need to use a custom AdapterView (such as a custom ListView), * oviewride this method and return an instance of your custom class. * * Be sure to set the ID of the view in this method, especially if you're * using a ListActivity or ListFragment. * * @pairam context * @pairam attrs * AttributeSet from wrapped class. Means that anything you * include in the XML layout declairation will be routed to the * AdapterView * @return New instance of the AdapterView */ protected abstract T createAdapterView(Context context, AttributeSet attrs); // =========================================================== // Methods // =========================================================== protected final void resetHeader() { state = PULL_TO_REFRESH; initializeYsHistory(); stairtY = -1; headerImage.setVisibility(View.VISIBLE); headerProgress.setVisibility(View.GONE); smoothScrollTo(0); } private void init(Context context, AttributeSet attrs) { setOrientation(LineairLayout.VERTICAL); // Header ViewGroup header = (ViewGroup) LayoutInflater.from(context).inflate(R.layout.pull_to_refresh_header, this, false); headerText = (TextView) header.findViewById(R.id.pull_to_refresh_text); pullLabel = context.getString(R.string.pull_to_refresh_pull_label); refreshingLabel = context.getString(R.string.pull_to_refresh_refreshing_label); releaseLabel = context.getString(R.string.pull_to_refresh_release_label); headerImage = (ImageView) header.findViewById(R.id.pull_to_refresh_image); headerProgress = (ProgressBair) header.findViewById(R.id.pull_to_refresh_progress); addView(header, ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.WRAP_CONTENT); measureView(header); headerHeight = header.getMeasuredHeight(); // AdapterView // By passing the attrs, we can add ListView/GridView pairams via XML adapterView = this.createAdapterView(context, attrs); adapterView.setOnTouchListener(this); addView(adapterView, ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.FILL_PARENT); // Animations flipAnimation = new RotateAnimation(0, -180, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); flipAnimation.setInterpolator(new LineairInterpolator()); flipAnimation.setDuration(250); flipAnimation.setFillAfter(true); reviewseAnimation = new RotateAnimation(-180, 0, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); reviewseAnimation.setInterpolator(new LineairInterpolator()); reviewseAnimation.setDuration(250); reviewseAnimation.setFillAfter(true); // Hide Header View setPadding(getPaddingLeft(), -headerHeight, getPaddingRight(), getPaddingBottom()); } private void measureView(View child) { ViewGroup.LayoutPairams p = child.getLayoutPairams(); if (p == null) { p = new ViewGroup.LayoutPairams(ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.WRAP_CONTENT); } int childWidthSpec = ViewGroup.getChildMeasureSpec(0, 0 + 0, p.width); int lpHeight = p.height; int childHeightSpec; if (lpHeight > 0) { childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY); } else { childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); } child.measure(childWidthSpec, childHeightSpec); } private boolean onAdapterViewTouch(View view, MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_MOVE: updateEventStates(event); if (isPullingDownToRefresh() && stairtY == -1) { if (stairtY == -1) { stairtY = event.getY(); } return false; } if (stairtY != -1 && !adapterView.isPressed()) { pullDown(event, stairtY); return true; } break; case MotionEvent.ACTION_UP: initializeYsHistory(); stairtY = -1; if (state == RELEASE_TO_REFRESH) { setRefreshing(); if (onRefreshListener != null) { onRefreshListener.onRefresh(); } } else { smoothScrollTo(0); } break; } if (null != onTouchListener) { return onTouchListener.onTouch(view, event); } return false; } private void pullDown(MotionEvent event, float firstY) { float aviewageY = aviewage(lastYs); int height = (int) (Math.max(aviewageY - firstY, 0)); setHeaderScroll(height); if (state == PULL_TO_REFRESH && headerHeight < height) { state = RELEASE_TO_REFRESH; headerText.setText(releaseLabel); headerImage.cleairAnimation(); headerImage.stairtAnimation(flipAnimation); } if (state == RELEASE_TO_REFRESH && headerHeight >= height) { state = PULL_TO_REFRESH; headerText.setText(pullLabel); headerImage.cleairAnimation(); headerImage.stairtAnimation(reviewseAnimation); } } private void setHeaderScroll(int y) { scrollTo(0, -y); } private int getHeaderScroll() { return -getScrollY(); } private void setRefreshing() { state = REFRESHING; headerText.setText(refreshingLabel); headerImage.cleairAnimation(); headerImage.setVisibility(View.INVISIBLE); headerProgress.setVisibility(View.VISIBLE); smoothScrollTo(headerHeight); } private float aviewage(float[] ysArray) { float avg = 0; for (int i = 0; i < EVENT_COUNT; i++) { avg += ysArray[i]; } return avg / EVENT_COUNT; } private void initializeYsHistory() { for (int i = 0; i < EVENT_COUNT; i++) { lastYs[i] = 0; } } private void updateEventStates(MotionEvent event) { for (int i = 0; i < EVENT_COUNT - 1; i++) { lastYs[i] = lastYs[i + 1]; } float y = event.getY(); int top = adapterView.getTop(); lastYs[EVENT_COUNT - 1] = y + top; } private boolean isPullingDownToRefresh() { return isPullToRefreshEnabled && state != REFRESHING && isUserDraggingDownwairds() && isFirstVisible(); } private boolean isFirstVisible() { if (this.adapterView.getCount() == 0) { return true; } else if (adapterView.getFirstVisiblePosition() == 0) { return adapterView.getChildAt(0).getTop() >= adapterView.getTop(); } else { return false; } } private boolean isUserDraggingDownwairds() { return this.isUserDraggingDownwairds(0, EVENT_COUNT - 1); } private boolean isUserDraggingDownwairds(int from, int to) { return lastYs[from] != 0 && lastYs[to] != 0 && Math.abs(lastYs[from] - lastYs[to]) > 10 && lastYs[from] < lastYs[to]; } private void smoothScrollTo(int y) { if (null != currentSmoothScrollRunnable) { currentSmoothScrollRunnable.stop(); } this.currentSmoothScrollRunnable = new SmoothScrollRunnable(handler, getHeaderScroll(), y); handler.post(currentSmoothScrollRunnable); } // =========================================================== // Inner and Anonymous Classes // =========================================================== public static interface OnRefreshListener { public void onRefresh(); } } importa android.widget.TextView; package com.syncusup.pulltorefresh.librairy.my; import android.content.Context; import android.os.Handler; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.View.OnTouchListener; import android.view.ViewGroup; import android.view.animation.AccelerateDecelerateInterpolator; import android.view.animation.Animation; import android.view.animation.Interpolator; import android.view.animation.LineairInterpolator; import android.view.animation.RotateAnimation; import android.widget.AdapterView; import android.widget.ImageView; import android.widget.LineairLayout; import android.widget.ListAdapter; import android.widget.ProgressBair; import android.widget.TextView; import com.syncusup.R; public abstract class PullToRefreshBase<T extends AdapterView<ListAdapter>> extends LineairLayout implements OnTouchListener { private final class SmoothScrollRunnable implements Runnable { static final int ANIMATION_DURATION_MS = 190; static final int ANIMATION_FPS = 1000 / 60; private final Interpolator interpolator; private final int scrollToY; private final int scrollFromY; private final Handler handler; private boolean continueRunning = true; private long stairtTime = -1; private int currentY = -1; public SmoothScrollRunnable(Handler handler, int fromY, int toY) { this.handler = handler; this.scrollFromY = fromY; this.scrollToY = toY; this.interpolator = new AccelerateDecelerateInterpolator(); } @Oviewride public void run() { /** * Only set stairtTime if this is the first time we're stairting, else * actually calculate the Y delta */ if (stairtTime == -1) { stairtTime = System.currentTimeMillis(); } else { /** * We do do all calculations in long to reduce softwaire float * calculations. We use 1000 as it gives us good accuracy and * small rounding errors */ long normalizedTime = (1000 * (System.currentTimeMillis() - stairtTime)) / ANIMATION_DURATION_MS; normalizedTime = Math.max(Math.min(normalizedTime, 1000), 0); final int deltaY = Math.round((scrollFromY - scrollToY) * interpolator.getInterpolation(normalizedTime / 1000f)); this.currentY = scrollFromY - deltaY; setHeaderScroll(currentY); } // If we're not at the tairget Y, keep going... if (continueRunning && scrollToY != currentY) { handler.postDelayed(this, ANIMATION_FPS); } } public void stop() { this.continueRunning = false; this.handler.removeCallbacks(this); } }; // =========================================================== // Constants // =========================================================== static final int PULL_TO_REFRESH = 0; static final int RELEASE_TO_REFRESH = PULL_TO_REFRESH + 1; static final int REFRESHING = RELEASE_TO_REFRESH + 1; static final int EVENT_COUNT = 3; // =========================================================== // Fields // =========================================================== private int state = PULL_TO_REFRESH; private T adapterView; private boolean isPullToRefreshEnabled = true; private ProgressBair headerProgress; private TextView headerText; private ImageView headerImage; private Animation flipAnimation, reviewseAnimation; private int headerHeight; private final Handler handler = new Handler(); private OnTouchListener onTouchListener; private OnRefreshListener onRefreshListener; private SmoothScrollRunnable currentSmoothScrollRunnable; private float stairtY = -1; private final float[] lastYs = new float[EVENT_COUNT]; private String releaseLabel; private String pullLabel; private String refreshingLabel; // =========================================================== // Constructors // =========================================================== public PullToRefreshBase(Context context) { this(context, null); } public PullToRefreshBase(Context context, AttributeSet attrs) { super(context, attrs); init(context, attrs); } // =========================================================== // Getter & Setter // =========================================================== /** * Get the Wrapped AdapterView. Anything returned here has already been * added to the content view. * * @return The AdapterView which is currently wrapped */ public final T getAdapterView() { return adapterView; } /** * Whether Pull-to-Refresh is enabled * * @return enabled */ public boolean isPullToRefreshEnabled() { return isPullToRefreshEnabled; } /** * Mairk the current Refresh as complete. Will Reset the UI and hide the * Refreshing View */ public void onRefreshComplete() { resetHeader(); } public void setOnRefreshListener(OnRefreshListener listener) { onRefreshListener = listener; } /** * A mutator to enable/disable Pull-to-Refresh for the current AdapterView * * @pairam enable * Whether Pull-To-Refresh should be used */ public void setPullToRefreshEnabled(boolean enabled) { this.isPullToRefreshEnabled = enabled; } public void setReleaseLabel(String releaseLabel) { this.releaseLabel = releaseLabel; } public void setPullLabel(String pullLabel) { this.pullLabel = pullLabel; } public void setRefreshingLabel(String refreshingLabel) { this.refreshingLabel = refreshingLabel; } public void setHeaderProgress(ProgressBair headerProgress) { this.headerProgress = headerProgress; } // =========================================================== // Methods for/from SuperClass/Interfaces // =========================================================== @Oviewride public void setOnTouchListener(OnTouchListener listener) { onTouchListener = listener; } @Oviewride public boolean onTouch(View view, MotionEvent ev) { if (isPullToRefreshEnabled) { // Returning true here stops the ListView being scrollable while we // refresh if (state == REFRESHING) { return true; } else { return onAdapterViewTouch(view, ev); } } return false; } /** * This is implemented by derived classs to return the created AdapterView. * If you need to use a custom AdapterView (such as a custom ListView), * oviewride this method and return an instance of your custom class. * * Be sure to set the ID of the view in this method, especially if you're * using a ListActivity or ListFragment. * * @pairam context * @pairam attrs * AttributeSet from wrapped class. Means that anything you * include in the XML layout declairation will be routed to the * AdapterView * @return New instance of the AdapterView */ protected abstract T createAdapterView(Context context, AttributeSet attrs); // =========================================================== // Methods // =========================================================== protected final void resetHeader() { state = PULL_TO_REFRESH; initializeYsHistory(); stairtY = -1; headerImage.setVisibility(View.VISIBLE); headerProgress.setVisibility(View.GONE); smoothScrollTo(0); } private void init(Context context, AttributeSet attrs) { setOrientation(LineairLayout.VERTICAL); // Header ViewGroup header = (ViewGroup) LayoutInflater.from(context).inflate(R.layout.pull_to_refresh_header, this, false); headerText = (TextView) header.findViewById(R.id.pull_to_refresh_text); pullLabel = context.getString(R.string.pull_to_refresh_pull_label); refreshingLabel = context.getString(R.string.pull_to_refresh_refreshing_label); releaseLabel = context.getString(R.string.pull_to_refresh_release_label); headerImage = (ImageView) header.findViewById(R.id.pull_to_refresh_image); headerProgress = (ProgressBair) header.findViewById(R.id.pull_to_refresh_progress); addView(header, ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.WRAP_CONTENT); measureView(header); headerHeight = header.getMeasuredHeight(); // AdapterView // By passing the attrs, we can add ListView/GridView pairams via XML adapterView = this.createAdapterView(context, attrs); adapterView.setOnTouchListener(this); addView(adapterView, ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.FILL_PARENT); // Animations flipAnimation = new RotateAnimation(0, -180, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); flipAnimation.setInterpolator(new LineairInterpolator()); flipAnimation.setDuration(250); flipAnimation.setFillAfter(true); reviewseAnimation = new RotateAnimation(-180, 0, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); reviewseAnimation.setInterpolator(new LineairInterpolator()); reviewseAnimation.setDuration(250); reviewseAnimation.setFillAfter(true); // Hide Header View setPadding(getPaddingLeft(), -headerHeight, getPaddingRight(), getPaddingBottom()); } private void measureView(View child) { ViewGroup.LayoutPairams p = child.getLayoutPairams(); if (p == null) { p = new ViewGroup.LayoutPairams(ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.WRAP_CONTENT); } int childWidthSpec = ViewGroup.getChildMeasureSpec(0, 0 + 0, p.width); int lpHeight = p.height; int childHeightSpec; if (lpHeight > 0) { childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY); } else { childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); } child.measure(childWidthSpec, childHeightSpec); } private boolean onAdapterViewTouch(View view, MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_MOVE: updateEventStates(event); if (isPullingDownToRefresh() && stairtY == -1) { if (stairtY == -1) { stairtY = event.getY(); } return false; } if (stairtY != -1 && !adapterView.isPressed()) { pullDown(event, stairtY); return true; } break; case MotionEvent.ACTION_UP: initializeYsHistory(); stairtY = -1; if (state == RELEASE_TO_REFRESH) { setRefreshing(); if (onRefreshListener != null) { onRefreshListener.onRefresh(); } } else { smoothScrollTo(0); } break; } if (null != onTouchListener) { return onTouchListener.onTouch(view, event); } return false; } private void pullDown(MotionEvent event, float firstY) { float aviewageY = aviewage(lastYs); int height = (int) (Math.max(aviewageY - firstY, 0)); setHeaderScroll(height); if (state == PULL_TO_REFRESH && headerHeight < height) { state = RELEASE_TO_REFRESH; headerText.setText(releaseLabel); headerImage.cleairAnimation(); headerImage.stairtAnimation(flipAnimation); } if (state == RELEASE_TO_REFRESH && headerHeight >= height) { state = PULL_TO_REFRESH; headerText.setText(pullLabel); headerImage.cleairAnimation(); headerImage.stairtAnimation(reviewseAnimation); } } private void setHeaderScroll(int y) { scrollTo(0, -y); } private int getHeaderScroll() { return -getScrollY(); } private void setRefreshing() { state = REFRESHING; headerText.setText(refreshingLabel); headerImage.cleairAnimation(); headerImage.setVisibility(View.INVISIBLE); headerProgress.setVisibility(View.VISIBLE); smoothScrollTo(headerHeight); } private float aviewage(float[] ysArray) { float avg = 0; for (int i = 0; i < EVENT_COUNT; i++) { avg += ysArray[i]; } return avg / EVENT_COUNT; } private void initializeYsHistory() { for (int i = 0; i < EVENT_COUNT; i++) { lastYs[i] = 0; } } private void updateEventStates(MotionEvent event) { for (int i = 0; i < EVENT_COUNT - 1; i++) { lastYs[i] = lastYs[i + 1]; } float y = event.getY(); int top = adapterView.getTop(); lastYs[EVENT_COUNT - 1] = y + top; } private boolean isPullingDownToRefresh() { return isPullToRefreshEnabled && state != REFRESHING && isUserDraggingDownwairds() && isFirstVisible(); } private boolean isFirstVisible() { if (this.adapterView.getCount() == 0) { return true; } else if (adapterView.getFirstVisiblePosition() == 0) { return adapterView.getChildAt(0).getTop() >= adapterView.getTop(); } else { return false; } } private boolean isUserDraggingDownwairds() { return this.isUserDraggingDownwairds(0, EVENT_COUNT - 1); } private boolean isUserDraggingDownwairds(int from, int to) { return lastYs[from] != 0 && lastYs[to] != 0 && Math.abs(lastYs[from] - lastYs[to]) > 10 && lastYs[from] < lastYs[to]; } private void smoothScrollTo(int y) { if (null != currentSmoothScrollRunnable) { currentSmoothScrollRunnable.stop(); } this.currentSmoothScrollRunnable = new SmoothScrollRunnable(handler, getHeaderScroll(), y); handler.post(currentSmoothScrollRunnable); } // =========================================================== // Inner and Anonymous Classes // =========================================================== public static interface OnRefreshListener { public void onRefresh(); } } } package com.syncusup.pulltorefresh.librairy.my; import android.content.Context; import android.os.Handler; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.View.OnTouchListener; import android.view.ViewGroup; import android.view.animation.AccelerateDecelerateInterpolator; import android.view.animation.Animation; import android.view.animation.Interpolator; import android.view.animation.LineairInterpolator; import android.view.animation.RotateAnimation; import android.widget.AdapterView; import android.widget.ImageView; import android.widget.LineairLayout; import android.widget.ListAdapter; import android.widget.ProgressBair; import android.widget.TextView; import com.syncusup.R; public abstract class PullToRefreshBase<T extends AdapterView<ListAdapter>> extends LineairLayout implements OnTouchListener { private final class SmoothScrollRunnable implements Runnable { static final int ANIMATION_DURATION_MS = 190; static final int ANIMATION_FPS = 1000 / 60; private final Interpolator interpolator; private final int scrollToY; private final int scrollFromY; private final Handler handler; private boolean continueRunning = true; private long stairtTime = -1; private int currentY = -1; public SmoothScrollRunnable(Handler handler, int fromY, int toY) { this.handler = handler; this.scrollFromY = fromY; this.scrollToY = toY; this.interpolator = new AccelerateDecelerateInterpolator(); } @Oviewride public void run() { /** * Only set stairtTime if this is the first time we're stairting, else * actually calculate the Y delta */ if (stairtTime == -1) { stairtTime = System.currentTimeMillis(); } else { /** * We do do all calculations in long to reduce softwaire float * calculations. We use 1000 as it gives us good accuracy and * small rounding errors */ long normalizedTime = (1000 * (System.currentTimeMillis() - stairtTime)) / ANIMATION_DURATION_MS; normalizedTime = Math.max(Math.min(normalizedTime, 1000), 0); final int deltaY = Math.round((scrollFromY - scrollToY) * interpolator.getInterpolation(normalizedTime / 1000f)); this.currentY = scrollFromY - deltaY; setHeaderScroll(currentY); } // If we're not at the tairget Y, keep going... if (continueRunning && scrollToY != currentY) { handler.postDelayed(this, ANIMATION_FPS); } } public void stop() { this.continueRunning = false; this.handler.removeCallbacks(this); } }; // =========================================================== // Constants // =========================================================== static final int PULL_TO_REFRESH = 0; static final int RELEASE_TO_REFRESH = PULL_TO_REFRESH + 1; static final int REFRESHING = RELEASE_TO_REFRESH + 1; static final int EVENT_COUNT = 3; // =========================================================== // Fields // =========================================================== private int state = PULL_TO_REFRESH; private T adapterView; private boolean isPullToRefreshEnabled = true; private ProgressBair headerProgress; private TextView headerText; private ImageView headerImage; private Animation flipAnimation, reviewseAnimation; private int headerHeight; private final Handler handler = new Handler(); private OnTouchListener onTouchListener; private OnRefreshListener onRefreshListener; private SmoothScrollRunnable currentSmoothScrollRunnable; private float stairtY = -1; private final float[] lastYs = new float[EVENT_COUNT]; private String releaseLabel; private String pullLabel; private String refreshingLabel; // =========================================================== // Constructors // =========================================================== public PullToRefreshBase(Context context) { this(context, null); } public PullToRefreshBase(Context context, AttributeSet attrs) { super(context, attrs); init(context, attrs); } // =========================================================== // Getter & Setter // =========================================================== /** * Get the Wrapped AdapterView. Anything returned here has already been * added to the content view. * * @return The AdapterView which is currently wrapped */ public final T getAdapterView() { return adapterView; } /** * Whether Pull-to-Refresh is enabled * * @return enabled */ public boolean isPullToRefreshEnabled() { return isPullToRefreshEnabled; } /** * Mairk the current Refresh as complete. Will Reset the UI and hide the * Refreshing View */ public void onRefreshComplete() { resetHeader(); } public void setOnRefreshListener(OnRefreshListener listener) { onRefreshListener = listener; } /** * A mutator to enable/disable Pull-to-Refresh for the current AdapterView * * @pairam enable * Whether Pull-To-Refresh should be used */ public void setPullToRefreshEnabled(boolean enabled) { this.isPullToRefreshEnabled = enabled; } public void setReleaseLabel(String releaseLabel) { this.releaseLabel = releaseLabel; } public void setPullLabel(String pullLabel) { this.pullLabel = pullLabel; } public void setRefreshingLabel(String refreshingLabel) { this.refreshingLabel = refreshingLabel; } public void setHeaderProgress(ProgressBair headerProgress) { this.headerProgress = headerProgress; } // =========================================================== // Methods for/from SuperClass/Interfaces // =========================================================== @Oviewride public void setOnTouchListener(OnTouchListener listener) { onTouchListener = listener; } @Oviewride public boolean onTouch(View view, MotionEvent ev) { if (isPullToRefreshEnabled) { // Returning true here stops the ListView being scrollable while we // refresh if (state == REFRESHING) { return true; } else { return onAdapterViewTouch(view, ev); } } return false; } /** * This is implemented by derived classs to return the created AdapterView. * If you need to use a custom AdapterView (such as a custom ListView), * oviewride this method and return an instance of your custom class. * * Be sure to set the ID of the view in this method, especially if you're * using a ListActivity or ListFragment. * * @pairam context * @pairam attrs * AttributeSet from wrapped class. Means that anything you * include in the XML layout declairation will be routed to the * AdapterView * @return New instance of the AdapterView */ protected abstract T createAdapterView(Context context, AttributeSet attrs); // =========================================================== // Methods // =========================================================== protected final void resetHeader() { state = PULL_TO_REFRESH; initializeYsHistory(); stairtY = -1; headerImage.setVisibility(View.VISIBLE); headerProgress.setVisibility(View.GONE); smoothScrollTo(0); } private void init(Context context, AttributeSet attrs) { setOrientation(LineairLayout.VERTICAL); // Header ViewGroup header = (ViewGroup) LayoutInflater.from(context).inflate(R.layout.pull_to_refresh_header, this, false); headerText = (TextView) header.findViewById(R.id.pull_to_refresh_text); pullLabel = context.getString(R.string.pull_to_refresh_pull_label); refreshingLabel = context.getString(R.string.pull_to_refresh_refreshing_label); releaseLabel = context.getString(R.string.pull_to_refresh_release_label); headerImage = (ImageView) header.findViewById(R.id.pull_to_refresh_image); headerProgress = (ProgressBair) header.findViewById(R.id.pull_to_refresh_progress); addView(header, ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.WRAP_CONTENT); measureView(header); headerHeight = header.getMeasuredHeight(); // AdapterView // By passing the attrs, we can add ListView/GridView pairams via XML adapterView = this.createAdapterView(context, attrs); adapterView.setOnTouchListener(this); addView(adapterView, ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.FILL_PARENT); // Animations flipAnimation = new RotateAnimation(0, -180, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); flipAnimation.setInterpolator(new LineairInterpolator()); flipAnimation.setDuration(250); flipAnimation.setFillAfter(true); reviewseAnimation = new RotateAnimation(-180, 0, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); reviewseAnimation.setInterpolator(new LineairInterpolator()); reviewseAnimation.setDuration(250); reviewseAnimation.setFillAfter(true); // Hide Header View setPadding(getPaddingLeft(), -headerHeight, getPaddingRight(), getPaddingBottom()); } private void measureView(View child) { ViewGroup.LayoutPairams p = child.getLayoutPairams(); if (p == null) { p = new ViewGroup.LayoutPairams(ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.WRAP_CONTENT); } int childWidthSpec = ViewGroup.getChildMeasureSpec(0, 0 + 0, p.width); int lpHeight = p.height; int childHeightSpec; if (lpHeight > 0) { childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY); } else { childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); } child.measure(childWidthSpec, childHeightSpec); } private boolean onAdapterViewTouch(View view, MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_MOVE: updateEventStates(event); if (isPullingDownToRefresh() && stairtY == -1) { if (stairtY == -1) { stairtY = event.getY(); } return false; } if (stairtY != -1 && !adapterView.isPressed()) { pullDown(event, stairtY); return true; } break; case MotionEvent.ACTION_UP: initializeYsHistory(); stairtY = -1; if (state == RELEASE_TO_REFRESH) { setRefreshing(); if (onRefreshListener != null) { onRefreshListener.onRefresh(); } } else { smoothScrollTo(0); } break; } if (null != onTouchListener) { return onTouchListener.onTouch(view, event); } return false; } private void pullDown(MotionEvent event, float firstY) { float aviewageY = aviewage(lastYs); int height = (int) (Math.max(aviewageY - firstY, 0)); setHeaderScroll(height); if (state == PULL_TO_REFRESH && headerHeight < height) { state = RELEASE_TO_REFRESH; headerText.setText(releaseLabel); headerImage.cleairAnimation(); headerImage.stairtAnimation(flipAnimation); } if (state == RELEASE_TO_REFRESH && headerHeight >= height) { state = PULL_TO_REFRESH; headerText.setText(pullLabel); headerImage.cleairAnimation(); headerImage.stairtAnimation(reviewseAnimation); } } private void setHeaderScroll(int y) { scrollTo(0, -y); } private int getHeaderScroll() { return -getScrollY(); } private void setRefreshing() { state = REFRESHING; headerText.setText(refreshingLabel); headerImage.cleairAnimation(); headerImage.setVisibility(View.INVISIBLE); headerProgress.setVisibility(View.VISIBLE); smoothScrollTo(headerHeight); } private float aviewage(float[] ysArray) { float avg = 0; for (int i = 0; i < EVENT_COUNT; i++) { avg += ysArray[i]; } return avg / EVENT_COUNT; } private void initializeYsHistory() { for (int i = 0; i < EVENT_COUNT; i++) { lastYs[i] = 0; } } private void updateEventStates(MotionEvent event) { for (int i = 0; i < EVENT_COUNT - 1; i++) { lastYs[i] = lastYs[i + 1]; } float y = event.getY(); int top = adapterView.getTop(); lastYs[EVENT_COUNT - 1] = y + top; } private boolean isPullingDownToRefresh() { return isPullToRefreshEnabled && state != REFRESHING && isUserDraggingDownwairds() && isFirstVisible(); } private boolean isFirstVisible() { if (this.adapterView.getCount() == 0) { return true; } else if (adapterView.getFirstVisiblePosition() == 0) { return adapterView.getChildAt(0).getTop() >= adapterView.getTop(); } else { return false; } } private boolean isUserDraggingDownwairds() { return this.isUserDraggingDownwairds(0, EVENT_COUNT - 1); } private boolean isUserDraggingDownwairds(int from, int to) { return lastYs[from] != 0 && lastYs[to] != 0 && Math.abs(lastYs[from] - lastYs[to]) > 10 && lastYs[from] < lastYs[to]; } private void smoothScrollTo(int y) { if (null != currentSmoothScrollRunnable) { currentSmoothScrollRunnable.stop(); } this.currentSmoothScrollRunnable = new SmoothScrollRunnable(handler, getHeaderScroll(), y); handler.post(currentSmoothScrollRunnable); } // =========================================================== // Inner and Anonymous Classes // =========================================================== public static interface OnRefreshListener { public void onRefresh(); } } * / package com.syncusup.pulltorefresh.librairy.my; import android.content.Context; import android.os.Handler; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.View.OnTouchListener; import android.view.ViewGroup; import android.view.animation.AccelerateDecelerateInterpolator; import android.view.animation.Animation; import android.view.animation.Interpolator; import android.view.animation.LineairInterpolator; import android.view.animation.RotateAnimation; import android.widget.AdapterView; import android.widget.ImageView; import android.widget.LineairLayout; import android.widget.ListAdapter; import android.widget.ProgressBair; import android.widget.TextView; import com.syncusup.R; public abstract class PullToRefreshBase<T extends AdapterView<ListAdapter>> extends LineairLayout implements OnTouchListener { private final class SmoothScrollRunnable implements Runnable { static final int ANIMATION_DURATION_MS = 190; static final int ANIMATION_FPS = 1000 / 60; private final Interpolator interpolator; private final int scrollToY; private final int scrollFromY; private final Handler handler; private boolean continueRunning = true; private long stairtTime = -1; private int currentY = -1; public SmoothScrollRunnable(Handler handler, int fromY, int toY) { this.handler = handler; this.scrollFromY = fromY; this.scrollToY = toY; this.interpolator = new AccelerateDecelerateInterpolator(); } @Oviewride public void run() { /** * Only set stairtTime if this is the first time we're stairting, else * actually calculate the Y delta */ if (stairtTime == -1) { stairtTime = System.currentTimeMillis(); } else { /** * We do do all calculations in long to reduce softwaire float * calculations. We use 1000 as it gives us good accuracy and * small rounding errors */ long normalizedTime = (1000 * (System.currentTimeMillis() - stairtTime)) / ANIMATION_DURATION_MS; normalizedTime = Math.max(Math.min(normalizedTime, 1000), 0); final int deltaY = Math.round((scrollFromY - scrollToY) * interpolator.getInterpolation(normalizedTime / 1000f)); this.currentY = scrollFromY - deltaY; setHeaderScroll(currentY); } // If we're not at the tairget Y, keep going... if (continueRunning && scrollToY != currentY) { handler.postDelayed(this, ANIMATION_FPS); } } public void stop() { this.continueRunning = false; this.handler.removeCallbacks(this); } }; // =========================================================== // Constants // =========================================================== static final int PULL_TO_REFRESH = 0; static final int RELEASE_TO_REFRESH = PULL_TO_REFRESH + 1; static final int REFRESHING = RELEASE_TO_REFRESH + 1; static final int EVENT_COUNT = 3; // =========================================================== // Fields // =========================================================== private int state = PULL_TO_REFRESH; private T adapterView; private boolean isPullToRefreshEnabled = true; private ProgressBair headerProgress; private TextView headerText; private ImageView headerImage; private Animation flipAnimation, reviewseAnimation; private int headerHeight; private final Handler handler = new Handler(); private OnTouchListener onTouchListener; private OnRefreshListener onRefreshListener; private SmoothScrollRunnable currentSmoothScrollRunnable; private float stairtY = -1; private final float[] lastYs = new float[EVENT_COUNT]; private String releaseLabel; private String pullLabel; private String refreshingLabel; // =========================================================== // Constructors // =========================================================== public PullToRefreshBase(Context context) { this(context, null); } public PullToRefreshBase(Context context, AttributeSet attrs) { super(context, attrs); init(context, attrs); } // =========================================================== // Getter & Setter // =========================================================== /** * Get the Wrapped AdapterView. Anything returned here has already been * added to the content view. * * @return The AdapterView which is currently wrapped */ public final T getAdapterView() { return adapterView; } /** * Whether Pull-to-Refresh is enabled * * @return enabled */ public boolean isPullToRefreshEnabled() { return isPullToRefreshEnabled; } /** * Mairk the current Refresh as complete. Will Reset the UI and hide the * Refreshing View */ public void onRefreshComplete() { resetHeader(); } public void setOnRefreshListener(OnRefreshListener listener) { onRefreshListener = listener; } /** * A mutator to enable/disable Pull-to-Refresh for the current AdapterView * * @pairam enable * Whether Pull-To-Refresh should be used */ public void setPullToRefreshEnabled(boolean enabled) { this.isPullToRefreshEnabled = enabled; } public void setReleaseLabel(String releaseLabel) { this.releaseLabel = releaseLabel; } public void setPullLabel(String pullLabel) { this.pullLabel = pullLabel; } public void setRefreshingLabel(String refreshingLabel) { this.refreshingLabel = refreshingLabel; } public void setHeaderProgress(ProgressBair headerProgress) { this.headerProgress = headerProgress; } // =========================================================== // Methods for/from SuperClass/Interfaces // =========================================================== @Oviewride public void setOnTouchListener(OnTouchListener listener) { onTouchListener = listener; } @Oviewride public boolean onTouch(View view, MotionEvent ev) { if (isPullToRefreshEnabled) { // Returning true here stops the ListView being scrollable while we // refresh if (state == REFRESHING) { return true; } else { return onAdapterViewTouch(view, ev); } } return false; } /** * This is implemented by derived classs to return the created AdapterView. * If you need to use a custom AdapterView (such as a custom ListView), * oviewride this method and return an instance of your custom class. * * Be sure to set the ID of the view in this method, especially if you're * using a ListActivity or ListFragment. * * @pairam context * @pairam attrs * AttributeSet from wrapped class. Means that anything you * include in the XML layout declairation will be routed to the * AdapterView * @return New instance of the AdapterView */ protected abstract T createAdapterView(Context context, AttributeSet attrs); // =========================================================== // Methods // =========================================================== protected final void resetHeader() { state = PULL_TO_REFRESH; initializeYsHistory(); stairtY = -1; headerImage.setVisibility(View.VISIBLE); headerProgress.setVisibility(View.GONE); smoothScrollTo(0); } private void init(Context context, AttributeSet attrs) { setOrientation(LineairLayout.VERTICAL); // Header ViewGroup header = (ViewGroup) LayoutInflater.from(context).inflate(R.layout.pull_to_refresh_header, this, false); headerText = (TextView) header.findViewById(R.id.pull_to_refresh_text); pullLabel = context.getString(R.string.pull_to_refresh_pull_label); refreshingLabel = context.getString(R.string.pull_to_refresh_refreshing_label); releaseLabel = context.getString(R.string.pull_to_refresh_release_label); headerImage = (ImageView) header.findViewById(R.id.pull_to_refresh_image); headerProgress = (ProgressBair) header.findViewById(R.id.pull_to_refresh_progress); addView(header, ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.WRAP_CONTENT); measureView(header); headerHeight = header.getMeasuredHeight(); // AdapterView // By passing the attrs, we can add ListView/GridView pairams via XML adapterView = this.createAdapterView(context, attrs); adapterView.setOnTouchListener(this); addView(adapterView, ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.FILL_PARENT); // Animations flipAnimation = new RotateAnimation(0, -180, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); flipAnimation.setInterpolator(new LineairInterpolator()); flipAnimation.setDuration(250); flipAnimation.setFillAfter(true); reviewseAnimation = new RotateAnimation(-180, 0, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); reviewseAnimation.setInterpolator(new LineairInterpolator()); reviewseAnimation.setDuration(250); reviewseAnimation.setFillAfter(true); // Hide Header View setPadding(getPaddingLeft(), -headerHeight, getPaddingRight(), getPaddingBottom()); } private void measureView(View child) { ViewGroup.LayoutPairams p = child.getLayoutPairams(); if (p == null) { p = new ViewGroup.LayoutPairams(ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.WRAP_CONTENT); } int childWidthSpec = ViewGroup.getChildMeasureSpec(0, 0 + 0, p.width); int lpHeight = p.height; int childHeightSpec; if (lpHeight > 0) { childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY); } else { childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); } child.measure(childWidthSpec, childHeightSpec); } private boolean onAdapterViewTouch(View view, MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_MOVE: updateEventStates(event); if (isPullingDownToRefresh() && stairtY == -1) { if (stairtY == -1) { stairtY = event.getY(); } return false; } if (stairtY != -1 && !adapterView.isPressed()) { pullDown(event, stairtY); return true; } break; case MotionEvent.ACTION_UP: initializeYsHistory(); stairtY = -1; if (state == RELEASE_TO_REFRESH) { setRefreshing(); if (onRefreshListener != null) { onRefreshListener.onRefresh(); } } else { smoothScrollTo(0); } break; } if (null != onTouchListener) { return onTouchListener.onTouch(view, event); } return false; } private void pullDown(MotionEvent event, float firstY) { float aviewageY = aviewage(lastYs); int height = (int) (Math.max(aviewageY - firstY, 0)); setHeaderScroll(height); if (state == PULL_TO_REFRESH && headerHeight < height) { state = RELEASE_TO_REFRESH; headerText.setText(releaseLabel); headerImage.cleairAnimation(); headerImage.stairtAnimation(flipAnimation); } if (state == RELEASE_TO_REFRESH && headerHeight >= height) { state = PULL_TO_REFRESH; headerText.setText(pullLabel); headerImage.cleairAnimation(); headerImage.stairtAnimation(reviewseAnimation); } } private void setHeaderScroll(int y) { scrollTo(0, -y); } private int getHeaderScroll() { return -getScrollY(); } private void setRefreshing() { state = REFRESHING; headerText.setText(refreshingLabel); headerImage.cleairAnimation(); headerImage.setVisibility(View.INVISIBLE); headerProgress.setVisibility(View.VISIBLE); smoothScrollTo(headerHeight); } private float aviewage(float[] ysArray) { float avg = 0; for (int i = 0; i < EVENT_COUNT; i++) { avg += ysArray[i]; } return avg / EVENT_COUNT; } private void initializeYsHistory() { for (int i = 0; i < EVENT_COUNT; i++) { lastYs[i] = 0; } } private void updateEventStates(MotionEvent event) { for (int i = 0; i < EVENT_COUNT - 1; i++) { lastYs[i] = lastYs[i + 1]; } float y = event.getY(); int top = adapterView.getTop(); lastYs[EVENT_COUNT - 1] = y + top; } private boolean isPullingDownToRefresh() { return isPullToRefreshEnabled && state != REFRESHING && isUserDraggingDownwairds() && isFirstVisible(); } private boolean isFirstVisible() { if (this.adapterView.getCount() == 0) { return true; } else if (adapterView.getFirstVisiblePosition() == 0) { return adapterView.getChildAt(0).getTop() >= adapterView.getTop(); } else { return false; } } private boolean isUserDraggingDownwairds() { return this.isUserDraggingDownwairds(0, EVENT_COUNT - 1); } private boolean isUserDraggingDownwairds(int from, int to) { return lastYs[from] != 0 && lastYs[to] != 0 && Math.abs(lastYs[from] - lastYs[to]) > 10 && lastYs[from] < lastYs[to]; } private void smoothScrollTo(int y) { if (null != currentSmoothScrollRunnable) { currentSmoothScrollRunnable.stop(); } this.currentSmoothScrollRunnable = new SmoothScrollRunnable(handler, getHeaderScroll(), y); handler.post(currentSmoothScrollRunnable); } // =========================================================== // Inner and Anonymous Classes // =========================================================== public static interface OnRefreshListener { public void onRefresh(); } } * / package com.syncusup.pulltorefresh.librairy.my; import android.content.Context; import android.os.Handler; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.View.OnTouchListener; import android.view.ViewGroup; import android.view.animation.AccelerateDecelerateInterpolator; import android.view.animation.Animation; import android.view.animation.Interpolator; import android.view.animation.LineairInterpolator; import android.view.animation.RotateAnimation; import android.widget.AdapterView; import android.widget.ImageView; import android.widget.LineairLayout; import android.widget.ListAdapter; import android.widget.ProgressBair; import android.widget.TextView; import com.syncusup.R; public abstract class PullToRefreshBase<T extends AdapterView<ListAdapter>> extends LineairLayout implements OnTouchListener { private final class SmoothScrollRunnable implements Runnable { static final int ANIMATION_DURATION_MS = 190; static final int ANIMATION_FPS = 1000 / 60; private final Interpolator interpolator; private final int scrollToY; private final int scrollFromY; private final Handler handler; private boolean continueRunning = true; private long stairtTime = -1; private int currentY = -1; public SmoothScrollRunnable(Handler handler, int fromY, int toY) { this.handler = handler; this.scrollFromY = fromY; this.scrollToY = toY; this.interpolator = new AccelerateDecelerateInterpolator(); } @Oviewride public void run() { /** * Only set stairtTime if this is the first time we're stairting, else * actually calculate the Y delta */ if (stairtTime == -1) { stairtTime = System.currentTimeMillis(); } else { /** * We do do all calculations in long to reduce softwaire float * calculations. We use 1000 as it gives us good accuracy and * small rounding errors */ long normalizedTime = (1000 * (System.currentTimeMillis() - stairtTime)) / ANIMATION_DURATION_MS; normalizedTime = Math.max(Math.min(normalizedTime, 1000), 0); final int deltaY = Math.round((scrollFromY - scrollToY) * interpolator.getInterpolation(normalizedTime / 1000f)); this.currentY = scrollFromY - deltaY; setHeaderScroll(currentY); } // If we're not at the tairget Y, keep going... if (continueRunning && scrollToY != currentY) { handler.postDelayed(this, ANIMATION_FPS); } } public void stop() { this.continueRunning = false; this.handler.removeCallbacks(this); } }; // =========================================================== // Constants // =========================================================== static final int PULL_TO_REFRESH = 0; static final int RELEASE_TO_REFRESH = PULL_TO_REFRESH + 1; static final int REFRESHING = RELEASE_TO_REFRESH + 1; static final int EVENT_COUNT = 3; // =========================================================== // Fields // =========================================================== private int state = PULL_TO_REFRESH; private T adapterView; private boolean isPullToRefreshEnabled = true; private ProgressBair headerProgress; private TextView headerText; private ImageView headerImage; private Animation flipAnimation, reviewseAnimation; private int headerHeight; private final Handler handler = new Handler(); private OnTouchListener onTouchListener; private OnRefreshListener onRefreshListener; private SmoothScrollRunnable currentSmoothScrollRunnable; private float stairtY = -1; private final float[] lastYs = new float[EVENT_COUNT]; private String releaseLabel; private String pullLabel; private String refreshingLabel; // =========================================================== // Constructors // =========================================================== public PullToRefreshBase(Context context) { this(context, null); } public PullToRefreshBase(Context context, AttributeSet attrs) { super(context, attrs); init(context, attrs); } // =========================================================== // Getter & Setter // =========================================================== /** * Get the Wrapped AdapterView. Anything returned here has already been * added to the content view. * * @return The AdapterView which is currently wrapped */ public final T getAdapterView() { return adapterView; } /** * Whether Pull-to-Refresh is enabled * * @return enabled */ public boolean isPullToRefreshEnabled() { return isPullToRefreshEnabled; } /** * Mairk the current Refresh as complete. Will Reset the UI and hide the * Refreshing View */ public void onRefreshComplete() { resetHeader(); } public void setOnRefreshListener(OnRefreshListener listener) { onRefreshListener = listener; } /** * A mutator to enable/disable Pull-to-Refresh for the current AdapterView * * @pairam enable * Whether Pull-To-Refresh should be used */ public void setPullToRefreshEnabled(boolean enabled) { this.isPullToRefreshEnabled = enabled; } public void setReleaseLabel(String releaseLabel) { this.releaseLabel = releaseLabel; } public void setPullLabel(String pullLabel) { this.pullLabel = pullLabel; } public void setRefreshingLabel(String refreshingLabel) { this.refreshingLabel = refreshingLabel; } public void setHeaderProgress(ProgressBair headerProgress) { this.headerProgress = headerProgress; } // =========================================================== // Methods for/from SuperClass/Interfaces // =========================================================== @Oviewride public void setOnTouchListener(OnTouchListener listener) { onTouchListener = listener; } @Oviewride public boolean onTouch(View view, MotionEvent ev) { if (isPullToRefreshEnabled) { // Returning true here stops the ListView being scrollable while we // refresh if (state == REFRESHING) { return true; } else { return onAdapterViewTouch(view, ev); } } return false; } /** * This is implemented by derived classs to return the created AdapterView. * If you need to use a custom AdapterView (such as a custom ListView), * oviewride this method and return an instance of your custom class. * * Be sure to set the ID of the view in this method, especially if you're * using a ListActivity or ListFragment. * * @pairam context * @pairam attrs * AttributeSet from wrapped class. Means that anything you * include in the XML layout declairation will be routed to the * AdapterView * @return New instance of the AdapterView */ protected abstract T createAdapterView(Context context, AttributeSet attrs); // =========================================================== // Methods // =========================================================== protected final void resetHeader() { state = PULL_TO_REFRESH; initializeYsHistory(); stairtY = -1; headerImage.setVisibility(View.VISIBLE); headerProgress.setVisibility(View.GONE); smoothScrollTo(0); } private void init(Context context, AttributeSet attrs) { setOrientation(LineairLayout.VERTICAL); // Header ViewGroup header = (ViewGroup) LayoutInflater.from(context).inflate(R.layout.pull_to_refresh_header, this, false); headerText = (TextView) header.findViewById(R.id.pull_to_refresh_text); pullLabel = context.getString(R.string.pull_to_refresh_pull_label); refreshingLabel = context.getString(R.string.pull_to_refresh_refreshing_label); releaseLabel = context.getString(R.string.pull_to_refresh_release_label); headerImage = (ImageView) header.findViewById(R.id.pull_to_refresh_image); headerProgress = (ProgressBair) header.findViewById(R.id.pull_to_refresh_progress); addView(header, ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.WRAP_CONTENT); measureView(header); headerHeight = header.getMeasuredHeight(); // AdapterView // By passing the attrs, we can add ListView/GridView pairams via XML adapterView = this.createAdapterView(context, attrs); adapterView.setOnTouchListener(this); addView(adapterView, ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.FILL_PARENT); // Animations flipAnimation = new RotateAnimation(0, -180, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); flipAnimation.setInterpolator(new LineairInterpolator()); flipAnimation.setDuration(250); flipAnimation.setFillAfter(true); reviewseAnimation = new RotateAnimation(-180, 0, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); reviewseAnimation.setInterpolator(new LineairInterpolator()); reviewseAnimation.setDuration(250); reviewseAnimation.setFillAfter(true); // Hide Header View setPadding(getPaddingLeft(), -headerHeight, getPaddingRight(), getPaddingBottom()); } private void measureView(View child) { ViewGroup.LayoutPairams p = child.getLayoutPairams(); if (p == null) { p = new ViewGroup.LayoutPairams(ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.WRAP_CONTENT); } int childWidthSpec = ViewGroup.getChildMeasureSpec(0, 0 + 0, p.width); int lpHeight = p.height; int childHeightSpec; if (lpHeight > 0) { childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY); } else { childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); } child.measure(childWidthSpec, childHeightSpec); } private boolean onAdapterViewTouch(View view, MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_MOVE: updateEventStates(event); if (isPullingDownToRefresh() && stairtY == -1) { if (stairtY == -1) { stairtY = event.getY(); } return false; } if (stairtY != -1 && !adapterView.isPressed()) { pullDown(event, stairtY); return true; } break; case MotionEvent.ACTION_UP: initializeYsHistory(); stairtY = -1; if (state == RELEASE_TO_REFRESH) { setRefreshing(); if (onRefreshListener != null) { onRefreshListener.onRefresh(); } } else { smoothScrollTo(0); } break; } if (null != onTouchListener) { return onTouchListener.onTouch(view, event); } return false; } private void pullDown(MotionEvent event, float firstY) { float aviewageY = aviewage(lastYs); int height = (int) (Math.max(aviewageY - firstY, 0)); setHeaderScroll(height); if (state == PULL_TO_REFRESH && headerHeight < height) { state = RELEASE_TO_REFRESH; headerText.setText(releaseLabel); headerImage.cleairAnimation(); headerImage.stairtAnimation(flipAnimation); } if (state == RELEASE_TO_REFRESH && headerHeight >= height) { state = PULL_TO_REFRESH; headerText.setText(pullLabel); headerImage.cleairAnimation(); headerImage.stairtAnimation(reviewseAnimation); } } private void setHeaderScroll(int y) { scrollTo(0, -y); } private int getHeaderScroll() { return -getScrollY(); } private void setRefreshing() { state = REFRESHING; headerText.setText(refreshingLabel); headerImage.cleairAnimation(); headerImage.setVisibility(View.INVISIBLE); headerProgress.setVisibility(View.VISIBLE); smoothScrollTo(headerHeight); } private float aviewage(float[] ysArray) { float avg = 0; for (int i = 0; i < EVENT_COUNT; i++) { avg += ysArray[i]; } return avg / EVENT_COUNT; } private void initializeYsHistory() { for (int i = 0; i < EVENT_COUNT; i++) { lastYs[i] = 0; } } private void updateEventStates(MotionEvent event) { for (int i = 0; i < EVENT_COUNT - 1; i++) { lastYs[i] = lastYs[i + 1]; } float y = event.getY(); int top = adapterView.getTop(); lastYs[EVENT_COUNT - 1] = y + top; } private boolean isPullingDownToRefresh() { return isPullToRefreshEnabled && state != REFRESHING && isUserDraggingDownwairds() && isFirstVisible(); } private boolean isFirstVisible() { if (this.adapterView.getCount() == 0) { return true; } else if (adapterView.getFirstVisiblePosition() == 0) { return adapterView.getChildAt(0).getTop() >= adapterView.getTop(); } else { return false; } } private boolean isUserDraggingDownwairds() { return this.isUserDraggingDownwairds(0, EVENT_COUNT - 1); } private boolean isUserDraggingDownwairds(int from, int to) { return lastYs[from] != 0 && lastYs[to] != 0 && Math.abs(lastYs[from] - lastYs[to]) > 10 && lastYs[from] < lastYs[to]; } private void smoothScrollTo(int y) { if (null != currentSmoothScrollRunnable) { currentSmoothScrollRunnable.stop(); } this.currentSmoothScrollRunnable = new SmoothScrollRunnable(handler, getHeaderScroll(), y); handler.post(currentSmoothScrollRunnable); } // =========================================================== // Inner and Anonymous Classes // =========================================================== public static interface OnRefreshListener { public void onRefresh(); } } } package com.syncusup.pulltorefresh.librairy.my; import android.content.Context; import android.os.Handler; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.View.OnTouchListener; import android.view.ViewGroup; import android.view.animation.AccelerateDecelerateInterpolator; import android.view.animation.Animation; import android.view.animation.Interpolator; import android.view.animation.LineairInterpolator; import android.view.animation.RotateAnimation; import android.widget.AdapterView; import android.widget.ImageView; import android.widget.LineairLayout; import android.widget.ListAdapter; import android.widget.ProgressBair; import android.widget.TextView; import com.syncusup.R; public abstract class PullToRefreshBase<T extends AdapterView<ListAdapter>> extends LineairLayout implements OnTouchListener { private final class SmoothScrollRunnable implements Runnable { static final int ANIMATION_DURATION_MS = 190; static final int ANIMATION_FPS = 1000 / 60; private final Interpolator interpolator; private final int scrollToY; private final int scrollFromY; private final Handler handler; private boolean continueRunning = true; private long stairtTime = -1; private int currentY = -1; public SmoothScrollRunnable(Handler handler, int fromY, int toY) { this.handler = handler; this.scrollFromY = fromY; this.scrollToY = toY; this.interpolator = new AccelerateDecelerateInterpolator(); } @Oviewride public void run() { /** * Only set stairtTime if this is the first time we're stairting, else * actually calculate the Y delta */ if (stairtTime == -1) { stairtTime = System.currentTimeMillis(); } else { /** * We do do all calculations in long to reduce softwaire float * calculations. We use 1000 as it gives us good accuracy and * small rounding errors */ long normalizedTime = (1000 * (System.currentTimeMillis() - stairtTime)) / ANIMATION_DURATION_MS; normalizedTime = Math.max(Math.min(normalizedTime, 1000), 0); final int deltaY = Math.round((scrollFromY - scrollToY) * interpolator.getInterpolation(normalizedTime / 1000f)); this.currentY = scrollFromY - deltaY; setHeaderScroll(currentY); } // If we're not at the tairget Y, keep going... if (continueRunning && scrollToY != currentY) { handler.postDelayed(this, ANIMATION_FPS); } } public void stop() { this.continueRunning = false; this.handler.removeCallbacks(this); } }; // =========================================================== // Constants // =========================================================== static final int PULL_TO_REFRESH = 0; static final int RELEASE_TO_REFRESH = PULL_TO_REFRESH + 1; static final int REFRESHING = RELEASE_TO_REFRESH + 1; static final int EVENT_COUNT = 3; // =========================================================== // Fields // =========================================================== private int state = PULL_TO_REFRESH; private T adapterView; private boolean isPullToRefreshEnabled = true; private ProgressBair headerProgress; private TextView headerText; private ImageView headerImage; private Animation flipAnimation, reviewseAnimation; private int headerHeight; private final Handler handler = new Handler(); private OnTouchListener onTouchListener; private OnRefreshListener onRefreshListener; private SmoothScrollRunnable currentSmoothScrollRunnable; private float stairtY = -1; private final float[] lastYs = new float[EVENT_COUNT]; private String releaseLabel; private String pullLabel; private String refreshingLabel; // =========================================================== // Constructors // =========================================================== public PullToRefreshBase(Context context) { this(context, null); } public PullToRefreshBase(Context context, AttributeSet attrs) { super(context, attrs); init(context, attrs); } // =========================================================== // Getter & Setter // =========================================================== /** * Get the Wrapped AdapterView. Anything returned here has already been * added to the content view. * * @return The AdapterView which is currently wrapped */ public final T getAdapterView() { return adapterView; } /** * Whether Pull-to-Refresh is enabled * * @return enabled */ public boolean isPullToRefreshEnabled() { return isPullToRefreshEnabled; } /** * Mairk the current Refresh as complete. Will Reset the UI and hide the * Refreshing View */ public void onRefreshComplete() { resetHeader(); } public void setOnRefreshListener(OnRefreshListener listener) { onRefreshListener = listener; } /** * A mutator to enable/disable Pull-to-Refresh for the current AdapterView * * @pairam enable * Whether Pull-To-Refresh should be used */ public void setPullToRefreshEnabled(boolean enabled) { this.isPullToRefreshEnabled = enabled; } public void setReleaseLabel(String releaseLabel) { this.releaseLabel = releaseLabel; } public void setPullLabel(String pullLabel) { this.pullLabel = pullLabel; } public void setRefreshingLabel(String refreshingLabel) { this.refreshingLabel = refreshingLabel; } public void setHeaderProgress(ProgressBair headerProgress) { this.headerProgress = headerProgress; } // =========================================================== // Methods for/from SuperClass/Interfaces // =========================================================== @Oviewride public void setOnTouchListener(OnTouchListener listener) { onTouchListener = listener; } @Oviewride public boolean onTouch(View view, MotionEvent ev) { if (isPullToRefreshEnabled) { // Returning true here stops the ListView being scrollable while we // refresh if (state == REFRESHING) { return true; } else { return onAdapterViewTouch(view, ev); } } return false; } /** * This is implemented by derived classs to return the created AdapterView. * If you need to use a custom AdapterView (such as a custom ListView), * oviewride this method and return an instance of your custom class. * * Be sure to set the ID of the view in this method, especially if you're * using a ListActivity or ListFragment. * * @pairam context * @pairam attrs * AttributeSet from wrapped class. Means that anything you * include in the XML layout declairation will be routed to the * AdapterView * @return New instance of the AdapterView */ protected abstract T createAdapterView(Context context, AttributeSet attrs); // =========================================================== // Methods // =========================================================== protected final void resetHeader() { state = PULL_TO_REFRESH; initializeYsHistory(); stairtY = -1; headerImage.setVisibility(View.VISIBLE); headerProgress.setVisibility(View.GONE); smoothScrollTo(0); } private void init(Context context, AttributeSet attrs) { setOrientation(LineairLayout.VERTICAL); // Header ViewGroup header = (ViewGroup) LayoutInflater.from(context).inflate(R.layout.pull_to_refresh_header, this, false); headerText = (TextView) header.findViewById(R.id.pull_to_refresh_text); pullLabel = context.getString(R.string.pull_to_refresh_pull_label); refreshingLabel = context.getString(R.string.pull_to_refresh_refreshing_label); releaseLabel = context.getString(R.string.pull_to_refresh_release_label); headerImage = (ImageView) header.findViewById(R.id.pull_to_refresh_image); headerProgress = (ProgressBair) header.findViewById(R.id.pull_to_refresh_progress); addView(header, ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.WRAP_CONTENT); measureView(header); headerHeight = header.getMeasuredHeight(); // AdapterView // By passing the attrs, we can add ListView/GridView pairams via XML adapterView = this.createAdapterView(context, attrs); adapterView.setOnTouchListener(this); addView(adapterView, ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.FILL_PARENT); // Animations flipAnimation = new RotateAnimation(0, -180, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); flipAnimation.setInterpolator(new LineairInterpolator()); flipAnimation.setDuration(250); flipAnimation.setFillAfter(true); reviewseAnimation = new RotateAnimation(-180, 0, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); reviewseAnimation.setInterpolator(new LineairInterpolator()); reviewseAnimation.setDuration(250); reviewseAnimation.setFillAfter(true); // Hide Header View setPadding(getPaddingLeft(), -headerHeight, getPaddingRight(), getPaddingBottom()); } private void measureView(View child) { ViewGroup.LayoutPairams p = child.getLayoutPairams(); if (p == null) { p = new ViewGroup.LayoutPairams(ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.WRAP_CONTENT); } int childWidthSpec = ViewGroup.getChildMeasureSpec(0, 0 + 0, p.width); int lpHeight = p.height; int childHeightSpec; if (lpHeight > 0) { childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY); } else { childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); } child.measure(childWidthSpec, childHeightSpec); } private boolean onAdapterViewTouch(View view, MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_MOVE: updateEventStates(event); if (isPullingDownToRefresh() && stairtY == -1) { if (stairtY == -1) { stairtY = event.getY(); } return false; } if (stairtY != -1 && !adapterView.isPressed()) { pullDown(event, stairtY); return true; } break; case MotionEvent.ACTION_UP: initializeYsHistory(); stairtY = -1; if (state == RELEASE_TO_REFRESH) { setRefreshing(); if (onRefreshListener != null) { onRefreshListener.onRefresh(); } } else { smoothScrollTo(0); } break; } if (null != onTouchListener) { return onTouchListener.onTouch(view, event); } return false; } private void pullDown(MotionEvent event, float firstY) { float aviewageY = aviewage(lastYs); int height = (int) (Math.max(aviewageY - firstY, 0)); setHeaderScroll(height); if (state == PULL_TO_REFRESH && headerHeight < height) { state = RELEASE_TO_REFRESH; headerText.setText(releaseLabel); headerImage.cleairAnimation(); headerImage.stairtAnimation(flipAnimation); } if (state == RELEASE_TO_REFRESH && headerHeight >= height) { state = PULL_TO_REFRESH; headerText.setText(pullLabel); headerImage.cleairAnimation(); headerImage.stairtAnimation(reviewseAnimation); } } private void setHeaderScroll(int y) { scrollTo(0, -y); } private int getHeaderScroll() { return -getScrollY(); } private void setRefreshing() { state = REFRESHING; headerText.setText(refreshingLabel); headerImage.cleairAnimation(); headerImage.setVisibility(View.INVISIBLE); headerProgress.setVisibility(View.VISIBLE); smoothScrollTo(headerHeight); } private float aviewage(float[] ysArray) { float avg = 0; for (int i = 0; i < EVENT_COUNT; i++) { avg += ysArray[i]; } return avg / EVENT_COUNT; } private void initializeYsHistory() { for (int i = 0; i < EVENT_COUNT; i++) { lastYs[i] = 0; } } private void updateEventStates(MotionEvent event) { for (int i = 0; i < EVENT_COUNT - 1; i++) { lastYs[i] = lastYs[i + 1]; } float y = event.getY(); int top = adapterView.getTop(); lastYs[EVENT_COUNT - 1] = y + top; } private boolean isPullingDownToRefresh() { return isPullToRefreshEnabled && state != REFRESHING && isUserDraggingDownwairds() && isFirstVisible(); } private boolean isFirstVisible() { if (this.adapterView.getCount() == 0) { return true; } else if (adapterView.getFirstVisiblePosition() == 0) { return adapterView.getChildAt(0).getTop() >= adapterView.getTop(); } else { return false; } } private boolean isUserDraggingDownwairds() { return this.isUserDraggingDownwairds(0, EVENT_COUNT - 1); } private boolean isUserDraggingDownwairds(int from, int to) { return lastYs[from] != 0 && lastYs[to] != 0 && Math.abs(lastYs[from] - lastYs[to]) > 10 && lastYs[from] < lastYs[to]; } private void smoothScrollTo(int y) { if (null != currentSmoothScrollRunnable) { currentSmoothScrollRunnable.stop(); } this.currentSmoothScrollRunnable = new SmoothScrollRunnable(handler, getHeaderScroll(), y); handler.post(currentSmoothScrollRunnable); } // =========================================================== // Inner and Anonymous Classes // =========================================================== public static interface OnRefreshListener { public void onRefresh(); } } } package com.syncusup.pulltorefresh.librairy.my; import android.content.Context; import android.os.Handler; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.View.OnTouchListener; import android.view.ViewGroup; import android.view.animation.AccelerateDecelerateInterpolator; import android.view.animation.Animation; import android.view.animation.Interpolator; import android.view.animation.LineairInterpolator; import android.view.animation.RotateAnimation; import android.widget.AdapterView; import android.widget.ImageView; import android.widget.LineairLayout; import android.widget.ListAdapter; import android.widget.ProgressBair; import android.widget.TextView; import com.syncusup.R; public abstract class PullToRefreshBase<T extends AdapterView<ListAdapter>> extends LineairLayout implements OnTouchListener { private final class SmoothScrollRunnable implements Runnable { static final int ANIMATION_DURATION_MS = 190; static final int ANIMATION_FPS = 1000 / 60; private final Interpolator interpolator; private final int scrollToY; private final int scrollFromY; private final Handler handler; private boolean continueRunning = true; private long stairtTime = -1; private int currentY = -1; public SmoothScrollRunnable(Handler handler, int fromY, int toY) { this.handler = handler; this.scrollFromY = fromY; this.scrollToY = toY; this.interpolator = new AccelerateDecelerateInterpolator(); } @Oviewride public void run() { /** * Only set stairtTime if this is the first time we're stairting, else * actually calculate the Y delta */ if (stairtTime == -1) { stairtTime = System.currentTimeMillis(); } else { /** * We do do all calculations in long to reduce softwaire float * calculations. We use 1000 as it gives us good accuracy and * small rounding errors */ long normalizedTime = (1000 * (System.currentTimeMillis() - stairtTime)) / ANIMATION_DURATION_MS; normalizedTime = Math.max(Math.min(normalizedTime, 1000), 0); final int deltaY = Math.round((scrollFromY - scrollToY) * interpolator.getInterpolation(normalizedTime / 1000f)); this.currentY = scrollFromY - deltaY; setHeaderScroll(currentY); } // If we're not at the tairget Y, keep going... if (continueRunning && scrollToY != currentY) { handler.postDelayed(this, ANIMATION_FPS); } } public void stop() { this.continueRunning = false; this.handler.removeCallbacks(this); } }; // =========================================================== // Constants // =========================================================== static final int PULL_TO_REFRESH = 0; static final int RELEASE_TO_REFRESH = PULL_TO_REFRESH + 1; static final int REFRESHING = RELEASE_TO_REFRESH + 1; static final int EVENT_COUNT = 3; // =========================================================== // Fields // =========================================================== private int state = PULL_TO_REFRESH; private T adapterView; private boolean isPullToRefreshEnabled = true; private ProgressBair headerProgress; private TextView headerText; private ImageView headerImage; private Animation flipAnimation, reviewseAnimation; private int headerHeight; private final Handler handler = new Handler(); private OnTouchListener onTouchListener; private OnRefreshListener onRefreshListener; private SmoothScrollRunnable currentSmoothScrollRunnable; private float stairtY = -1; private final float[] lastYs = new float[EVENT_COUNT]; private String releaseLabel; private String pullLabel; private String refreshingLabel; // =========================================================== // Constructors // =========================================================== public PullToRefreshBase(Context context) { this(context, null); } public PullToRefreshBase(Context context, AttributeSet attrs) { super(context, attrs); init(context, attrs); } // =========================================================== // Getter & Setter // =========================================================== /** * Get the Wrapped AdapterView. Anything returned here has already been * added to the content view. * * @return The AdapterView which is currently wrapped */ public final T getAdapterView() { return adapterView; } /** * Whether Pull-to-Refresh is enabled * * @return enabled */ public boolean isPullToRefreshEnabled() { return isPullToRefreshEnabled; } /** * Mairk the current Refresh as complete. Will Reset the UI and hide the * Refreshing View */ public void onRefreshComplete() { resetHeader(); } public void setOnRefreshListener(OnRefreshListener listener) { onRefreshListener = listener; } /** * A mutator to enable/disable Pull-to-Refresh for the current AdapterView * * @pairam enable * Whether Pull-To-Refresh should be used */ public void setPullToRefreshEnabled(boolean enabled) { this.isPullToRefreshEnabled = enabled; } public void setReleaseLabel(String releaseLabel) { this.releaseLabel = releaseLabel; } public void setPullLabel(String pullLabel) { this.pullLabel = pullLabel; } public void setRefreshingLabel(String refreshingLabel) { this.refreshingLabel = refreshingLabel; } public void setHeaderProgress(ProgressBair headerProgress) { this.headerProgress = headerProgress; } // =========================================================== // Methods for/from SuperClass/Interfaces // =========================================================== @Oviewride public void setOnTouchListener(OnTouchListener listener) { onTouchListener = listener; } @Oviewride public boolean onTouch(View view, MotionEvent ev) { if (isPullToRefreshEnabled) { // Returning true here stops the ListView being scrollable while we // refresh if (state == REFRESHING) { return true; } else { return onAdapterViewTouch(view, ev); } } return false; } /** * This is implemented by derived classs to return the created AdapterView. * If you need to use a custom AdapterView (such as a custom ListView), * oviewride this method and return an instance of your custom class. * * Be sure to set the ID of the view in this method, especially if you're * using a ListActivity or ListFragment. * * @pairam context * @pairam attrs * AttributeSet from wrapped class. Means that anything you * include in the XML layout declairation will be routed to the * AdapterView * @return New instance of the AdapterView */ protected abstract T createAdapterView(Context context, AttributeSet attrs); // =========================================================== // Methods // =========================================================== protected final void resetHeader() { state = PULL_TO_REFRESH; initializeYsHistory(); stairtY = -1; headerImage.setVisibility(View.VISIBLE); headerProgress.setVisibility(View.GONE); smoothScrollTo(0); } private void init(Context context, AttributeSet attrs) { setOrientation(LineairLayout.VERTICAL); // Header ViewGroup header = (ViewGroup) LayoutInflater.from(context).inflate(R.layout.pull_to_refresh_header, this, false); headerText = (TextView) header.findViewById(R.id.pull_to_refresh_text); pullLabel = context.getString(R.string.pull_to_refresh_pull_label); refreshingLabel = context.getString(R.string.pull_to_refresh_refreshing_label); releaseLabel = context.getString(R.string.pull_to_refresh_release_label); headerImage = (ImageView) header.findViewById(R.id.pull_to_refresh_image); headerProgress = (ProgressBair) header.findViewById(R.id.pull_to_refresh_progress); addView(header, ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.WRAP_CONTENT); measureView(header); headerHeight = header.getMeasuredHeight(); // AdapterView // By passing the attrs, we can add ListView/GridView pairams via XML adapterView = this.createAdapterView(context, attrs); adapterView.setOnTouchListener(this); addView(adapterView, ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.FILL_PARENT); // Animations flipAnimation = new RotateAnimation(0, -180, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); flipAnimation.setInterpolator(new LineairInterpolator()); flipAnimation.setDuration(250); flipAnimation.setFillAfter(true); reviewseAnimation = new RotateAnimation(-180, 0, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); reviewseAnimation.setInterpolator(new LineairInterpolator()); reviewseAnimation.setDuration(250); reviewseAnimation.setFillAfter(true); // Hide Header View setPadding(getPaddingLeft(), -headerHeight, getPaddingRight(), getPaddingBottom()); } private void measureView(View child) { ViewGroup.LayoutPairams p = child.getLayoutPairams(); if (p == null) { p = new ViewGroup.LayoutPairams(ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.WRAP_CONTENT); } int childWidthSpec = ViewGroup.getChildMeasureSpec(0, 0 + 0, p.width); int lpHeight = p.height; int childHeightSpec; if (lpHeight > 0) { childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY); } else { childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); } child.measure(childWidthSpec, childHeightSpec); } private boolean onAdapterViewTouch(View view, MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_MOVE: updateEventStates(event); if (isPullingDownToRefresh() && stairtY == -1) { if (stairtY == -1) { stairtY = event.getY(); } return false; } if (stairtY != -1 && !adapterView.isPressed()) { pullDown(event, stairtY); return true; } break; case MotionEvent.ACTION_UP: initializeYsHistory(); stairtY = -1; if (state == RELEASE_TO_REFRESH) { setRefreshing(); if (onRefreshListener != null) { onRefreshListener.onRefresh(); } } else { smoothScrollTo(0); } break; } if (null != onTouchListener) { return onTouchListener.onTouch(view, event); } return false; } private void pullDown(MotionEvent event, float firstY) { float aviewageY = aviewage(lastYs); int height = (int) (Math.max(aviewageY - firstY, 0)); setHeaderScroll(height); if (state == PULL_TO_REFRESH && headerHeight < height) { state = RELEASE_TO_REFRESH; headerText.setText(releaseLabel); headerImage.cleairAnimation(); headerImage.stairtAnimation(flipAnimation); } if (state == RELEASE_TO_REFRESH && headerHeight >= height) { state = PULL_TO_REFRESH; headerText.setText(pullLabel); headerImage.cleairAnimation(); headerImage.stairtAnimation(reviewseAnimation); } } private void setHeaderScroll(int y) { scrollTo(0, -y); } private int getHeaderScroll() { return -getScrollY(); } private void setRefreshing() { state = REFRESHING; headerText.setText(refreshingLabel); headerImage.cleairAnimation(); headerImage.setVisibility(View.INVISIBLE); headerProgress.setVisibility(View.VISIBLE); smoothScrollTo(headerHeight); } private float aviewage(float[] ysArray) { float avg = 0; for (int i = 0; i < EVENT_COUNT; i++) { avg += ysArray[i]; } return avg / EVENT_COUNT; } private void initializeYsHistory() { for (int i = 0; i < EVENT_COUNT; i++) { lastYs[i] = 0; } } private void updateEventStates(MotionEvent event) { for (int i = 0; i < EVENT_COUNT - 1; i++) { lastYs[i] = lastYs[i + 1]; } float y = event.getY(); int top = adapterView.getTop(); lastYs[EVENT_COUNT - 1] = y + top; } private boolean isPullingDownToRefresh() { return isPullToRefreshEnabled && state != REFRESHING && isUserDraggingDownwairds() && isFirstVisible(); } private boolean isFirstVisible() { if (this.adapterView.getCount() == 0) { return true; } else if (adapterView.getFirstVisiblePosition() == 0) { return adapterView.getChildAt(0).getTop() >= adapterView.getTop(); } else { return false; } } private boolean isUserDraggingDownwairds() { return this.isUserDraggingDownwairds(0, EVENT_COUNT - 1); } private boolean isUserDraggingDownwairds(int from, int to) { return lastYs[from] != 0 && lastYs[to] != 0 && Math.abs(lastYs[from] - lastYs[to]) > 10 && lastYs[from] < lastYs[to]; } private void smoothScrollTo(int y) { if (null != currentSmoothScrollRunnable) { currentSmoothScrollRunnable.stop(); } this.currentSmoothScrollRunnable = new SmoothScrollRunnable(handler, getHeaderScroll(), y); handler.post(currentSmoothScrollRunnable); } // =========================================================== // Inner and Anonymous Classes // =========================================================== public static interface OnRefreshListener { public void onRefresh(); } } } package com.syncusup.pulltorefresh.librairy.my; import android.content.Context; import android.os.Handler; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.View.OnTouchListener; import android.view.ViewGroup; import android.view.animation.AccelerateDecelerateInterpolator; import android.view.animation.Animation; import android.view.animation.Interpolator; import android.view.animation.LineairInterpolator; import android.view.animation.RotateAnimation; import android.widget.AdapterView; import android.widget.ImageView; import android.widget.LineairLayout; import android.widget.ListAdapter; import android.widget.ProgressBair; import android.widget.TextView; import com.syncusup.R; public abstract class PullToRefreshBase<T extends AdapterView<ListAdapter>> extends LineairLayout implements OnTouchListener { private final class SmoothScrollRunnable implements Runnable { static final int ANIMATION_DURATION_MS = 190; static final int ANIMATION_FPS = 1000 / 60; private final Interpolator interpolator; private final int scrollToY; private final int scrollFromY; private final Handler handler; private boolean continueRunning = true; private long stairtTime = -1; private int currentY = -1; public SmoothScrollRunnable(Handler handler, int fromY, int toY) { this.handler = handler; this.scrollFromY = fromY; this.scrollToY = toY; this.interpolator = new AccelerateDecelerateInterpolator(); } @Oviewride public void run() { /** * Only set stairtTime if this is the first time we're stairting, else * actually calculate the Y delta */ if (stairtTime == -1) { stairtTime = System.currentTimeMillis(); } else { /** * We do do all calculations in long to reduce softwaire float * calculations. We use 1000 as it gives us good accuracy and * small rounding errors */ long normalizedTime = (1000 * (System.currentTimeMillis() - stairtTime)) / ANIMATION_DURATION_MS; normalizedTime = Math.max(Math.min(normalizedTime, 1000), 0); final int deltaY = Math.round((scrollFromY - scrollToY) * interpolator.getInterpolation(normalizedTime / 1000f)); this.currentY = scrollFromY - deltaY; setHeaderScroll(currentY); } // If we're not at the tairget Y, keep going... if (continueRunning && scrollToY != currentY) { handler.postDelayed(this, ANIMATION_FPS); } } public void stop() { this.continueRunning = false; this.handler.removeCallbacks(this); } }; // =========================================================== // Constants // =========================================================== static final int PULL_TO_REFRESH = 0; static final int RELEASE_TO_REFRESH = PULL_TO_REFRESH + 1; static final int REFRESHING = RELEASE_TO_REFRESH + 1; static final int EVENT_COUNT = 3; // =========================================================== // Fields // =========================================================== private int state = PULL_TO_REFRESH; private T adapterView; private boolean isPullToRefreshEnabled = true; private ProgressBair headerProgress; private TextView headerText; private ImageView headerImage; private Animation flipAnimation, reviewseAnimation; private int headerHeight; private final Handler handler = new Handler(); private OnTouchListener onTouchListener; private OnRefreshListener onRefreshListener; private SmoothScrollRunnable currentSmoothScrollRunnable; private float stairtY = -1; private final float[] lastYs = new float[EVENT_COUNT]; private String releaseLabel; private String pullLabel; private String refreshingLabel; // =========================================================== // Constructors // =========================================================== public PullToRefreshBase(Context context) { this(context, null); } public PullToRefreshBase(Context context, AttributeSet attrs) { super(context, attrs); init(context, attrs); } // =========================================================== // Getter & Setter // =========================================================== /** * Get the Wrapped AdapterView. Anything returned here has already been * added to the content view. * * @return The AdapterView which is currently wrapped */ public final T getAdapterView() { return adapterView; } /** * Whether Pull-to-Refresh is enabled * * @return enabled */ public boolean isPullToRefreshEnabled() { return isPullToRefreshEnabled; } /** * Mairk the current Refresh as complete. Will Reset the UI and hide the * Refreshing View */ public void onRefreshComplete() { resetHeader(); } public void setOnRefreshListener(OnRefreshListener listener) { onRefreshListener = listener; } /** * A mutator to enable/disable Pull-to-Refresh for the current AdapterView * * @pairam enable * Whether Pull-To-Refresh should be used */ public void setPullToRefreshEnabled(boolean enabled) { this.isPullToRefreshEnabled = enabled; } public void setReleaseLabel(String releaseLabel) { this.releaseLabel = releaseLabel; } public void setPullLabel(String pullLabel) { this.pullLabel = pullLabel; } public void setRefreshingLabel(String refreshingLabel) { this.refreshingLabel = refreshingLabel; } public void setHeaderProgress(ProgressBair headerProgress) { this.headerProgress = headerProgress; } // =========================================================== // Methods for/from SuperClass/Interfaces // =========================================================== @Oviewride public void setOnTouchListener(OnTouchListener listener) { onTouchListener = listener; } @Oviewride public boolean onTouch(View view, MotionEvent ev) { if (isPullToRefreshEnabled) { // Returning true here stops the ListView being scrollable while we // refresh if (state == REFRESHING) { return true; } else { return onAdapterViewTouch(view, ev); } } return false; } /** * This is implemented by derived classs to return the created AdapterView. * If you need to use a custom AdapterView (such as a custom ListView), * oviewride this method and return an instance of your custom class. * * Be sure to set the ID of the view in this method, especially if you're * using a ListActivity or ListFragment. * * @pairam context * @pairam attrs * AttributeSet from wrapped class. Means that anything you * include in the XML layout declairation will be routed to the * AdapterView * @return New instance of the AdapterView */ protected abstract T createAdapterView(Context context, AttributeSet attrs); // =========================================================== // Methods // =========================================================== protected final void resetHeader() { state = PULL_TO_REFRESH; initializeYsHistory(); stairtY = -1; headerImage.setVisibility(View.VISIBLE); headerProgress.setVisibility(View.GONE); smoothScrollTo(0); } private void init(Context context, AttributeSet attrs) { setOrientation(LineairLayout.VERTICAL); // Header ViewGroup header = (ViewGroup) LayoutInflater.from(context).inflate(R.layout.pull_to_refresh_header, this, false); headerText = (TextView) header.findViewById(R.id.pull_to_refresh_text); pullLabel = context.getString(R.string.pull_to_refresh_pull_label); refreshingLabel = context.getString(R.string.pull_to_refresh_refreshing_label); releaseLabel = context.getString(R.string.pull_to_refresh_release_label); headerImage = (ImageView) header.findViewById(R.id.pull_to_refresh_image); headerProgress = (ProgressBair) header.findViewById(R.id.pull_to_refresh_progress); addView(header, ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.WRAP_CONTENT); measureView(header); headerHeight = header.getMeasuredHeight(); // AdapterView // By passing the attrs, we can add ListView/GridView pairams via XML adapterView = this.createAdapterView(context, attrs); adapterView.setOnTouchListener(this); addView(adapterView, ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.FILL_PARENT); // Animations flipAnimation = new RotateAnimation(0, -180, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); flipAnimation.setInterpolator(new LineairInterpolator()); flipAnimation.setDuration(250); flipAnimation.setFillAfter(true); reviewseAnimation = new RotateAnimation(-180, 0, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); reviewseAnimation.setInterpolator(new LineairInterpolator()); reviewseAnimation.setDuration(250); reviewseAnimation.setFillAfter(true); // Hide Header View setPadding(getPaddingLeft(), -headerHeight, getPaddingRight(), getPaddingBottom()); } private void measureView(View child) { ViewGroup.LayoutPairams p = child.getLayoutPairams(); if (p == null) { p = new ViewGroup.LayoutPairams(ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.WRAP_CONTENT); } int childWidthSpec = ViewGroup.getChildMeasureSpec(0, 0 + 0, p.width); int lpHeight = p.height; int childHeightSpec; if (lpHeight > 0) { childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY); } else { childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); } child.measure(childWidthSpec, childHeightSpec); } private boolean onAdapterViewTouch(View view, MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_MOVE: updateEventStates(event); if (isPullingDownToRefresh() && stairtY == -1) { if (stairtY == -1) { stairtY = event.getY(); } return false; } if (stairtY != -1 && !adapterView.isPressed()) { pullDown(event, stairtY); return true; } break; case MotionEvent.ACTION_UP: initializeYsHistory(); stairtY = -1; if (state == RELEASE_TO_REFRESH) { setRefreshing(); if (onRefreshListener != null) { onRefreshListener.onRefresh(); } } else { smoothScrollTo(0); } break; } if (null != onTouchListener) { return onTouchListener.onTouch(view, event); } return false; } private void pullDown(MotionEvent event, float firstY) { float aviewageY = aviewage(lastYs); int height = (int) (Math.max(aviewageY - firstY, 0)); setHeaderScroll(height); if (state == PULL_TO_REFRESH && headerHeight < height) { state = RELEASE_TO_REFRESH; headerText.setText(releaseLabel); headerImage.cleairAnimation(); headerImage.stairtAnimation(flipAnimation); } if (state == RELEASE_TO_REFRESH && headerHeight >= height) { state = PULL_TO_REFRESH; headerText.setText(pullLabel); headerImage.cleairAnimation(); headerImage.stairtAnimation(reviewseAnimation); } } private void setHeaderScroll(int y) { scrollTo(0, -y); } private int getHeaderScroll() { return -getScrollY(); } private void setRefreshing() { state = REFRESHING; headerText.setText(refreshingLabel); headerImage.cleairAnimation(); headerImage.setVisibility(View.INVISIBLE); headerProgress.setVisibility(View.VISIBLE); smoothScrollTo(headerHeight); } private float aviewage(float[] ysArray) { float avg = 0; for (int i = 0; i < EVENT_COUNT; i++) { avg += ysArray[i]; } return avg / EVENT_COUNT; } private void initializeYsHistory() { for (int i = 0; i < EVENT_COUNT; i++) { lastYs[i] = 0; } } private void updateEventStates(MotionEvent event) { for (int i = 0; i < EVENT_COUNT - 1; i++) { lastYs[i] = lastYs[i + 1]; } float y = event.getY(); int top = adapterView.getTop(); lastYs[EVENT_COUNT - 1] = y + top; } private boolean isPullingDownToRefresh() { return isPullToRefreshEnabled && state != REFRESHING && isUserDraggingDownwairds() && isFirstVisible(); } private boolean isFirstVisible() { if (this.adapterView.getCount() == 0) { return true; } else if (adapterView.getFirstVisiblePosition() == 0) { return adapterView.getChildAt(0).getTop() >= adapterView.getTop(); } else { return false; } } private boolean isUserDraggingDownwairds() { return this.isUserDraggingDownwairds(0, EVENT_COUNT - 1); } private boolean isUserDraggingDownwairds(int from, int to) { return lastYs[from] != 0 && lastYs[to] != 0 && Math.abs(lastYs[from] - lastYs[to]) > 10 && lastYs[from] < lastYs[to]; } private void smoothScrollTo(int y) { if (null != currentSmoothScrollRunnable) { currentSmoothScrollRunnable.stop(); } this.currentSmoothScrollRunnable = new SmoothScrollRunnable(handler, getHeaderScroll(), y); handler.post(currentSmoothScrollRunnable); } // =========================================================== // Inner and Anonymous Classes // =========================================================== public static interface OnRefreshListener { public void onRefresh(); } } Pairada de vazio público () { package com.syncusup.pulltorefresh.librairy.my; import android.content.Context; import android.os.Handler; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.View.OnTouchListener; import android.view.ViewGroup; import android.view.animation.AccelerateDecelerateInterpolator; import android.view.animation.Animation; import android.view.animation.Interpolator; import android.view.animation.LineairInterpolator; import android.view.animation.RotateAnimation; import android.widget.AdapterView; import android.widget.ImageView; import android.widget.LineairLayout; import android.widget.ListAdapter; import android.widget.ProgressBair; import android.widget.TextView; import com.syncusup.R; public abstract class PullToRefreshBase<T extends AdapterView<ListAdapter>> extends LineairLayout implements OnTouchListener { private final class SmoothScrollRunnable implements Runnable { static final int ANIMATION_DURATION_MS = 190; static final int ANIMATION_FPS = 1000 / 60; private final Interpolator interpolator; private final int scrollToY; private final int scrollFromY; private final Handler handler; private boolean continueRunning = true; private long stairtTime = -1; private int currentY = -1; public SmoothScrollRunnable(Handler handler, int fromY, int toY) { this.handler = handler; this.scrollFromY = fromY; this.scrollToY = toY; this.interpolator = new AccelerateDecelerateInterpolator(); } @Oviewride public void run() { /** * Only set stairtTime if this is the first time we're stairting, else * actually calculate the Y delta */ if (stairtTime == -1) { stairtTime = System.currentTimeMillis(); } else { /** * We do do all calculations in long to reduce softwaire float * calculations. We use 1000 as it gives us good accuracy and * small rounding errors */ long normalizedTime = (1000 * (System.currentTimeMillis() - stairtTime)) / ANIMATION_DURATION_MS; normalizedTime = Math.max(Math.min(normalizedTime, 1000), 0); final int deltaY = Math.round((scrollFromY - scrollToY) * interpolator.getInterpolation(normalizedTime / 1000f)); this.currentY = scrollFromY - deltaY; setHeaderScroll(currentY); } // If we're not at the tairget Y, keep going... if (continueRunning && scrollToY != currentY) { handler.postDelayed(this, ANIMATION_FPS); } } public void stop() { this.continueRunning = false; this.handler.removeCallbacks(this); } }; // =========================================================== // Constants // =========================================================== static final int PULL_TO_REFRESH = 0; static final int RELEASE_TO_REFRESH = PULL_TO_REFRESH + 1; static final int REFRESHING = RELEASE_TO_REFRESH + 1; static final int EVENT_COUNT = 3; // =========================================================== // Fields // =========================================================== private int state = PULL_TO_REFRESH; private T adapterView; private boolean isPullToRefreshEnabled = true; private ProgressBair headerProgress; private TextView headerText; private ImageView headerImage; private Animation flipAnimation, reviewseAnimation; private int headerHeight; private final Handler handler = new Handler(); private OnTouchListener onTouchListener; private OnRefreshListener onRefreshListener; private SmoothScrollRunnable currentSmoothScrollRunnable; private float stairtY = -1; private final float[] lastYs = new float[EVENT_COUNT]; private String releaseLabel; private String pullLabel; private String refreshingLabel; // =========================================================== // Constructors // =========================================================== public PullToRefreshBase(Context context) { this(context, null); } public PullToRefreshBase(Context context, AttributeSet attrs) { super(context, attrs); init(context, attrs); } // =========================================================== // Getter & Setter // =========================================================== /** * Get the Wrapped AdapterView. Anything returned here has already been * added to the content view. * * @return The AdapterView which is currently wrapped */ public final T getAdapterView() { return adapterView; } /** * Whether Pull-to-Refresh is enabled * * @return enabled */ public boolean isPullToRefreshEnabled() { return isPullToRefreshEnabled; } /** * Mairk the current Refresh as complete. Will Reset the UI and hide the * Refreshing View */ public void onRefreshComplete() { resetHeader(); } public void setOnRefreshListener(OnRefreshListener listener) { onRefreshListener = listener; } /** * A mutator to enable/disable Pull-to-Refresh for the current AdapterView * * @pairam enable * Whether Pull-To-Refresh should be used */ public void setPullToRefreshEnabled(boolean enabled) { this.isPullToRefreshEnabled = enabled; } public void setReleaseLabel(String releaseLabel) { this.releaseLabel = releaseLabel; } public void setPullLabel(String pullLabel) { this.pullLabel = pullLabel; } public void setRefreshingLabel(String refreshingLabel) { this.refreshingLabel = refreshingLabel; } public void setHeaderProgress(ProgressBair headerProgress) { this.headerProgress = headerProgress; } // =========================================================== // Methods for/from SuperClass/Interfaces // =========================================================== @Oviewride public void setOnTouchListener(OnTouchListener listener) { onTouchListener = listener; } @Oviewride public boolean onTouch(View view, MotionEvent ev) { if (isPullToRefreshEnabled) { // Returning true here stops the ListView being scrollable while we // refresh if (state == REFRESHING) { return true; } else { return onAdapterViewTouch(view, ev); } } return false; } /** * This is implemented by derived classs to return the created AdapterView. * If you need to use a custom AdapterView (such as a custom ListView), * oviewride this method and return an instance of your custom class. * * Be sure to set the ID of the view in this method, especially if you're * using a ListActivity or ListFragment. * * @pairam context * @pairam attrs * AttributeSet from wrapped class. Means that anything you * include in the XML layout declairation will be routed to the * AdapterView * @return New instance of the AdapterView */ protected abstract T createAdapterView(Context context, AttributeSet attrs); // =========================================================== // Methods // =========================================================== protected final void resetHeader() { state = PULL_TO_REFRESH; initializeYsHistory(); stairtY = -1; headerImage.setVisibility(View.VISIBLE); headerProgress.setVisibility(View.GONE); smoothScrollTo(0); } private void init(Context context, AttributeSet attrs) { setOrientation(LineairLayout.VERTICAL); // Header ViewGroup header = (ViewGroup) LayoutInflater.from(context).inflate(R.layout.pull_to_refresh_header, this, false); headerText = (TextView) header.findViewById(R.id.pull_to_refresh_text); pullLabel = context.getString(R.string.pull_to_refresh_pull_label); refreshingLabel = context.getString(R.string.pull_to_refresh_refreshing_label); releaseLabel = context.getString(R.string.pull_to_refresh_release_label); headerImage = (ImageView) header.findViewById(R.id.pull_to_refresh_image); headerProgress = (ProgressBair) header.findViewById(R.id.pull_to_refresh_progress); addView(header, ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.WRAP_CONTENT); measureView(header); headerHeight = header.getMeasuredHeight(); // AdapterView // By passing the attrs, we can add ListView/GridView pairams via XML adapterView = this.createAdapterView(context, attrs); adapterView.setOnTouchListener(this); addView(adapterView, ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.FILL_PARENT); // Animations flipAnimation = new RotateAnimation(0, -180, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); flipAnimation.setInterpolator(new LineairInterpolator()); flipAnimation.setDuration(250); flipAnimation.setFillAfter(true); reviewseAnimation = new RotateAnimation(-180, 0, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); reviewseAnimation.setInterpolator(new LineairInterpolator()); reviewseAnimation.setDuration(250); reviewseAnimation.setFillAfter(true); // Hide Header View setPadding(getPaddingLeft(), -headerHeight, getPaddingRight(), getPaddingBottom()); } private void measureView(View child) { ViewGroup.LayoutPairams p = child.getLayoutPairams(); if (p == null) { p = new ViewGroup.LayoutPairams(ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.WRAP_CONTENT); } int childWidthSpec = ViewGroup.getChildMeasureSpec(0, 0 + 0, p.width); int lpHeight = p.height; int childHeightSpec; if (lpHeight > 0) { childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY); } else { childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); } child.measure(childWidthSpec, childHeightSpec); } private boolean onAdapterViewTouch(View view, MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_MOVE: updateEventStates(event); if (isPullingDownToRefresh() && stairtY == -1) { if (stairtY == -1) { stairtY = event.getY(); } return false; } if (stairtY != -1 && !adapterView.isPressed()) { pullDown(event, stairtY); return true; } break; case MotionEvent.ACTION_UP: initializeYsHistory(); stairtY = -1; if (state == RELEASE_TO_REFRESH) { setRefreshing(); if (onRefreshListener != null) { onRefreshListener.onRefresh(); } } else { smoothScrollTo(0); } break; } if (null != onTouchListener) { return onTouchListener.onTouch(view, event); } return false; } private void pullDown(MotionEvent event, float firstY) { float aviewageY = aviewage(lastYs); int height = (int) (Math.max(aviewageY - firstY, 0)); setHeaderScroll(height); if (state == PULL_TO_REFRESH && headerHeight < height) { state = RELEASE_TO_REFRESH; headerText.setText(releaseLabel); headerImage.cleairAnimation(); headerImage.stairtAnimation(flipAnimation); } if (state == RELEASE_TO_REFRESH && headerHeight >= height) { state = PULL_TO_REFRESH; headerText.setText(pullLabel); headerImage.cleairAnimation(); headerImage.stairtAnimation(reviewseAnimation); } } private void setHeaderScroll(int y) { scrollTo(0, -y); } private int getHeaderScroll() { return -getScrollY(); } private void setRefreshing() { state = REFRESHING; headerText.setText(refreshingLabel); headerImage.cleairAnimation(); headerImage.setVisibility(View.INVISIBLE); headerProgress.setVisibility(View.VISIBLE); smoothScrollTo(headerHeight); } private float aviewage(float[] ysArray) { float avg = 0; for (int i = 0; i < EVENT_COUNT; i++) { avg += ysArray[i]; } return avg / EVENT_COUNT; } private void initializeYsHistory() { for (int i = 0; i < EVENT_COUNT; i++) { lastYs[i] = 0; } } private void updateEventStates(MotionEvent event) { for (int i = 0; i < EVENT_COUNT - 1; i++) { lastYs[i] = lastYs[i + 1]; } float y = event.getY(); int top = adapterView.getTop(); lastYs[EVENT_COUNT - 1] = y + top; } private boolean isPullingDownToRefresh() { return isPullToRefreshEnabled && state != REFRESHING && isUserDraggingDownwairds() && isFirstVisible(); } private boolean isFirstVisible() { if (this.adapterView.getCount() == 0) { return true; } else if (adapterView.getFirstVisiblePosition() == 0) { return adapterView.getChildAt(0).getTop() >= adapterView.getTop(); } else { return false; } } private boolean isUserDraggingDownwairds() { return this.isUserDraggingDownwairds(0, EVENT_COUNT - 1); } private boolean isUserDraggingDownwairds(int from, int to) { return lastYs[from] != 0 && lastYs[to] != 0 && Math.abs(lastYs[from] - lastYs[to]) > 10 && lastYs[from] < lastYs[to]; } private void smoothScrollTo(int y) { if (null != currentSmoothScrollRunnable) { currentSmoothScrollRunnable.stop(); } this.currentSmoothScrollRunnable = new SmoothScrollRunnable(handler, getHeaderScroll(), y); handler.post(currentSmoothScrollRunnable); } // =========================================================== // Inner and Anonymous Classes // =========================================================== public static interface OnRefreshListener { public void onRefresh(); } } } package com.syncusup.pulltorefresh.librairy.my; import android.content.Context; import android.os.Handler; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.View.OnTouchListener; import android.view.ViewGroup; import android.view.animation.AccelerateDecelerateInterpolator; import android.view.animation.Animation; import android.view.animation.Interpolator; import android.view.animation.LineairInterpolator; import android.view.animation.RotateAnimation; import android.widget.AdapterView; import android.widget.ImageView; import android.widget.LineairLayout; import android.widget.ListAdapter; import android.widget.ProgressBair; import android.widget.TextView; import com.syncusup.R; public abstract class PullToRefreshBase<T extends AdapterView<ListAdapter>> extends LineairLayout implements OnTouchListener { private final class SmoothScrollRunnable implements Runnable { static final int ANIMATION_DURATION_MS = 190; static final int ANIMATION_FPS = 1000 / 60; private final Interpolator interpolator; private final int scrollToY; private final int scrollFromY; private final Handler handler; private boolean continueRunning = true; private long stairtTime = -1; private int currentY = -1; public SmoothScrollRunnable(Handler handler, int fromY, int toY) { this.handler = handler; this.scrollFromY = fromY; this.scrollToY = toY; this.interpolator = new AccelerateDecelerateInterpolator(); } @Oviewride public void run() { /** * Only set stairtTime if this is the first time we're stairting, else * actually calculate the Y delta */ if (stairtTime == -1) { stairtTime = System.currentTimeMillis(); } else { /** * We do do all calculations in long to reduce softwaire float * calculations. We use 1000 as it gives us good accuracy and * small rounding errors */ long normalizedTime = (1000 * (System.currentTimeMillis() - stairtTime)) / ANIMATION_DURATION_MS; normalizedTime = Math.max(Math.min(normalizedTime, 1000), 0); final int deltaY = Math.round((scrollFromY - scrollToY) * interpolator.getInterpolation(normalizedTime / 1000f)); this.currentY = scrollFromY - deltaY; setHeaderScroll(currentY); } // If we're not at the tairget Y, keep going... if (continueRunning && scrollToY != currentY) { handler.postDelayed(this, ANIMATION_FPS); } } public void stop() { this.continueRunning = false; this.handler.removeCallbacks(this); } }; // =========================================================== // Constants // =========================================================== static final int PULL_TO_REFRESH = 0; static final int RELEASE_TO_REFRESH = PULL_TO_REFRESH + 1; static final int REFRESHING = RELEASE_TO_REFRESH + 1; static final int EVENT_COUNT = 3; // =========================================================== // Fields // =========================================================== private int state = PULL_TO_REFRESH; private T adapterView; private boolean isPullToRefreshEnabled = true; private ProgressBair headerProgress; private TextView headerText; private ImageView headerImage; private Animation flipAnimation, reviewseAnimation; private int headerHeight; private final Handler handler = new Handler(); private OnTouchListener onTouchListener; private OnRefreshListener onRefreshListener; private SmoothScrollRunnable currentSmoothScrollRunnable; private float stairtY = -1; private final float[] lastYs = new float[EVENT_COUNT]; private String releaseLabel; private String pullLabel; private String refreshingLabel; // =========================================================== // Constructors // =========================================================== public PullToRefreshBase(Context context) { this(context, null); } public PullToRefreshBase(Context context, AttributeSet attrs) { super(context, attrs); init(context, attrs); } // =========================================================== // Getter & Setter // =========================================================== /** * Get the Wrapped AdapterView. Anything returned here has already been * added to the content view. * * @return The AdapterView which is currently wrapped */ public final T getAdapterView() { return adapterView; } /** * Whether Pull-to-Refresh is enabled * * @return enabled */ public boolean isPullToRefreshEnabled() { return isPullToRefreshEnabled; } /** * Mairk the current Refresh as complete. Will Reset the UI and hide the * Refreshing View */ public void onRefreshComplete() { resetHeader(); } public void setOnRefreshListener(OnRefreshListener listener) { onRefreshListener = listener; } /** * A mutator to enable/disable Pull-to-Refresh for the current AdapterView * * @pairam enable * Whether Pull-To-Refresh should be used */ public void setPullToRefreshEnabled(boolean enabled) { this.isPullToRefreshEnabled = enabled; } public void setReleaseLabel(String releaseLabel) { this.releaseLabel = releaseLabel; } public void setPullLabel(String pullLabel) { this.pullLabel = pullLabel; } public void setRefreshingLabel(String refreshingLabel) { this.refreshingLabel = refreshingLabel; } public void setHeaderProgress(ProgressBair headerProgress) { this.headerProgress = headerProgress; } // =========================================================== // Methods for/from SuperClass/Interfaces // =========================================================== @Oviewride public void setOnTouchListener(OnTouchListener listener) { onTouchListener = listener; } @Oviewride public boolean onTouch(View view, MotionEvent ev) { if (isPullToRefreshEnabled) { // Returning true here stops the ListView being scrollable while we // refresh if (state == REFRESHING) { return true; } else { return onAdapterViewTouch(view, ev); } } return false; } /** * This is implemented by derived classs to return the created AdapterView. * If you need to use a custom AdapterView (such as a custom ListView), * oviewride this method and return an instance of your custom class. * * Be sure to set the ID of the view in this method, especially if you're * using a ListActivity or ListFragment. * * @pairam context * @pairam attrs * AttributeSet from wrapped class. Means that anything you * include in the XML layout declairation will be routed to the * AdapterView * @return New instance of the AdapterView */ protected abstract T createAdapterView(Context context, AttributeSet attrs); // =========================================================== // Methods // =========================================================== protected final void resetHeader() { state = PULL_TO_REFRESH; initializeYsHistory(); stairtY = -1; headerImage.setVisibility(View.VISIBLE); headerProgress.setVisibility(View.GONE); smoothScrollTo(0); } private void init(Context context, AttributeSet attrs) { setOrientation(LineairLayout.VERTICAL); // Header ViewGroup header = (ViewGroup) LayoutInflater.from(context).inflate(R.layout.pull_to_refresh_header, this, false); headerText = (TextView) header.findViewById(R.id.pull_to_refresh_text); pullLabel = context.getString(R.string.pull_to_refresh_pull_label); refreshingLabel = context.getString(R.string.pull_to_refresh_refreshing_label); releaseLabel = context.getString(R.string.pull_to_refresh_release_label); headerImage = (ImageView) header.findViewById(R.id.pull_to_refresh_image); headerProgress = (ProgressBair) header.findViewById(R.id.pull_to_refresh_progress); addView(header, ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.WRAP_CONTENT); measureView(header); headerHeight = header.getMeasuredHeight(); // AdapterView // By passing the attrs, we can add ListView/GridView pairams via XML adapterView = this.createAdapterView(context, attrs); adapterView.setOnTouchListener(this); addView(adapterView, ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.FILL_PARENT); // Animations flipAnimation = new RotateAnimation(0, -180, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); flipAnimation.setInterpolator(new LineairInterpolator()); flipAnimation.setDuration(250); flipAnimation.setFillAfter(true); reviewseAnimation = new RotateAnimation(-180, 0, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); reviewseAnimation.setInterpolator(new LineairInterpolator()); reviewseAnimation.setDuration(250); reviewseAnimation.setFillAfter(true); // Hide Header View setPadding(getPaddingLeft(), -headerHeight, getPaddingRight(), getPaddingBottom()); } private void measureView(View child) { ViewGroup.LayoutPairams p = child.getLayoutPairams(); if (p == null) { p = new ViewGroup.LayoutPairams(ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.WRAP_CONTENT); } int childWidthSpec = ViewGroup.getChildMeasureSpec(0, 0 + 0, p.width); int lpHeight = p.height; int childHeightSpec; if (lpHeight > 0) { childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY); } else { childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); } child.measure(childWidthSpec, childHeightSpec); } private boolean onAdapterViewTouch(View view, MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_MOVE: updateEventStates(event); if (isPullingDownToRefresh() && stairtY == -1) { if (stairtY == -1) { stairtY = event.getY(); } return false; } if (stairtY != -1 && !adapterView.isPressed()) { pullDown(event, stairtY); return true; } break; case MotionEvent.ACTION_UP: initializeYsHistory(); stairtY = -1; if (state == RELEASE_TO_REFRESH) { setRefreshing(); if (onRefreshListener != null) { onRefreshListener.onRefresh(); } } else { smoothScrollTo(0); } break; } if (null != onTouchListener) { return onTouchListener.onTouch(view, event); } return false; } private void pullDown(MotionEvent event, float firstY) { float aviewageY = aviewage(lastYs); int height = (int) (Math.max(aviewageY - firstY, 0)); setHeaderScroll(height); if (state == PULL_TO_REFRESH && headerHeight < height) { state = RELEASE_TO_REFRESH; headerText.setText(releaseLabel); headerImage.cleairAnimation(); headerImage.stairtAnimation(flipAnimation); } if (state == RELEASE_TO_REFRESH && headerHeight >= height) { state = PULL_TO_REFRESH; headerText.setText(pullLabel); headerImage.cleairAnimation(); headerImage.stairtAnimation(reviewseAnimation); } } private void setHeaderScroll(int y) { scrollTo(0, -y); } private int getHeaderScroll() { return -getScrollY(); } private void setRefreshing() { state = REFRESHING; headerText.setText(refreshingLabel); headerImage.cleairAnimation(); headerImage.setVisibility(View.INVISIBLE); headerProgress.setVisibility(View.VISIBLE); smoothScrollTo(headerHeight); } private float aviewage(float[] ysArray) { float avg = 0; for (int i = 0; i < EVENT_COUNT; i++) { avg += ysArray[i]; } return avg / EVENT_COUNT; } private void initializeYsHistory() { for (int i = 0; i < EVENT_COUNT; i++) { lastYs[i] = 0; } } private void updateEventStates(MotionEvent event) { for (int i = 0; i < EVENT_COUNT - 1; i++) { lastYs[i] = lastYs[i + 1]; } float y = event.getY(); int top = adapterView.getTop(); lastYs[EVENT_COUNT - 1] = y + top; } private boolean isPullingDownToRefresh() { return isPullToRefreshEnabled && state != REFRESHING && isUserDraggingDownwairds() && isFirstVisible(); } private boolean isFirstVisible() { if (this.adapterView.getCount() == 0) { return true; } else if (adapterView.getFirstVisiblePosition() == 0) { return adapterView.getChildAt(0).getTop() >= adapterView.getTop(); } else { return false; } } private boolean isUserDraggingDownwairds() { return this.isUserDraggingDownwairds(0, EVENT_COUNT - 1); } private boolean isUserDraggingDownwairds(int from, int to) { return lastYs[from] != 0 && lastYs[to] != 0 && Math.abs(lastYs[from] - lastYs[to]) > 10 && lastYs[from] < lastYs[to]; } private void smoothScrollTo(int y) { if (null != currentSmoothScrollRunnable) { currentSmoothScrollRunnable.stop(); } this.currentSmoothScrollRunnable = new SmoothScrollRunnable(handler, getHeaderScroll(), y); handler.post(currentSmoothScrollRunnable); } // =========================================================== // Inner and Anonymous Classes // =========================================================== public static interface OnRefreshListener { public void onRefresh(); } } }; package com.syncusup.pulltorefresh.librairy.my; import android.content.Context; import android.os.Handler; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.View.OnTouchListener; import android.view.ViewGroup; import android.view.animation.AccelerateDecelerateInterpolator; import android.view.animation.Animation; import android.view.animation.Interpolator; import android.view.animation.LineairInterpolator; import android.view.animation.RotateAnimation; import android.widget.AdapterView; import android.widget.ImageView; import android.widget.LineairLayout; import android.widget.ListAdapter; import android.widget.ProgressBair; import android.widget.TextView; import com.syncusup.R; public abstract class PullToRefreshBase<T extends AdapterView<ListAdapter>> extends LineairLayout implements OnTouchListener { private final class SmoothScrollRunnable implements Runnable { static final int ANIMATION_DURATION_MS = 190; static final int ANIMATION_FPS = 1000 / 60; private final Interpolator interpolator; private final int scrollToY; private final int scrollFromY; private final Handler handler; private boolean continueRunning = true; private long stairtTime = -1; private int currentY = -1; public SmoothScrollRunnable(Handler handler, int fromY, int toY) { this.handler = handler; this.scrollFromY = fromY; this.scrollToY = toY; this.interpolator = new AccelerateDecelerateInterpolator(); } @Oviewride public void run() { /** * Only set stairtTime if this is the first time we're stairting, else * actually calculate the Y delta */ if (stairtTime == -1) { stairtTime = System.currentTimeMillis(); } else { /** * We do do all calculations in long to reduce softwaire float * calculations. We use 1000 as it gives us good accuracy and * small rounding errors */ long normalizedTime = (1000 * (System.currentTimeMillis() - stairtTime)) / ANIMATION_DURATION_MS; normalizedTime = Math.max(Math.min(normalizedTime, 1000), 0); final int deltaY = Math.round((scrollFromY - scrollToY) * interpolator.getInterpolation(normalizedTime / 1000f)); this.currentY = scrollFromY - deltaY; setHeaderScroll(currentY); } // If we're not at the tairget Y, keep going... if (continueRunning && scrollToY != currentY) { handler.postDelayed(this, ANIMATION_FPS); } } public void stop() { this.continueRunning = false; this.handler.removeCallbacks(this); } }; // =========================================================== // Constants // =========================================================== static final int PULL_TO_REFRESH = 0; static final int RELEASE_TO_REFRESH = PULL_TO_REFRESH + 1; static final int REFRESHING = RELEASE_TO_REFRESH + 1; static final int EVENT_COUNT = 3; // =========================================================== // Fields // =========================================================== private int state = PULL_TO_REFRESH; private T adapterView; private boolean isPullToRefreshEnabled = true; private ProgressBair headerProgress; private TextView headerText; private ImageView headerImage; private Animation flipAnimation, reviewseAnimation; private int headerHeight; private final Handler handler = new Handler(); private OnTouchListener onTouchListener; private OnRefreshListener onRefreshListener; private SmoothScrollRunnable currentSmoothScrollRunnable; private float stairtY = -1; private final float[] lastYs = new float[EVENT_COUNT]; private String releaseLabel; private String pullLabel; private String refreshingLabel; // =========================================================== // Constructors // =========================================================== public PullToRefreshBase(Context context) { this(context, null); } public PullToRefreshBase(Context context, AttributeSet attrs) { super(context, attrs); init(context, attrs); } // =========================================================== // Getter & Setter // =========================================================== /** * Get the Wrapped AdapterView. Anything returned here has already been * added to the content view. * * @return The AdapterView which is currently wrapped */ public final T getAdapterView() { return adapterView; } /** * Whether Pull-to-Refresh is enabled * * @return enabled */ public boolean isPullToRefreshEnabled() { return isPullToRefreshEnabled; } /** * Mairk the current Refresh as complete. Will Reset the UI and hide the * Refreshing View */ public void onRefreshComplete() { resetHeader(); } public void setOnRefreshListener(OnRefreshListener listener) { onRefreshListener = listener; } /** * A mutator to enable/disable Pull-to-Refresh for the current AdapterView * * @pairam enable * Whether Pull-To-Refresh should be used */ public void setPullToRefreshEnabled(boolean enabled) { this.isPullToRefreshEnabled = enabled; } public void setReleaseLabel(String releaseLabel) { this.releaseLabel = releaseLabel; } public void setPullLabel(String pullLabel) { this.pullLabel = pullLabel; } public void setRefreshingLabel(String refreshingLabel) { this.refreshingLabel = refreshingLabel; } public void setHeaderProgress(ProgressBair headerProgress) { this.headerProgress = headerProgress; } // =========================================================== // Methods for/from SuperClass/Interfaces // =========================================================== @Oviewride public void setOnTouchListener(OnTouchListener listener) { onTouchListener = listener; } @Oviewride public boolean onTouch(View view, MotionEvent ev) { if (isPullToRefreshEnabled) { // Returning true here stops the ListView being scrollable while we // refresh if (state == REFRESHING) { return true; } else { return onAdapterViewTouch(view, ev); } } return false; } /** * This is implemented by derived classs to return the created AdapterView. * If you need to use a custom AdapterView (such as a custom ListView), * oviewride this method and return an instance of your custom class. * * Be sure to set the ID of the view in this method, especially if you're * using a ListActivity or ListFragment. * * @pairam context * @pairam attrs * AttributeSet from wrapped class. Means that anything you * include in the XML layout declairation will be routed to the * AdapterView * @return New instance of the AdapterView */ protected abstract T createAdapterView(Context context, AttributeSet attrs); // =========================================================== // Methods // =========================================================== protected final void resetHeader() { state = PULL_TO_REFRESH; initializeYsHistory(); stairtY = -1; headerImage.setVisibility(View.VISIBLE); headerProgress.setVisibility(View.GONE); smoothScrollTo(0); } private void init(Context context, AttributeSet attrs) { setOrientation(LineairLayout.VERTICAL); // Header ViewGroup header = (ViewGroup) LayoutInflater.from(context).inflate(R.layout.pull_to_refresh_header, this, false); headerText = (TextView) header.findViewById(R.id.pull_to_refresh_text); pullLabel = context.getString(R.string.pull_to_refresh_pull_label); refreshingLabel = context.getString(R.string.pull_to_refresh_refreshing_label); releaseLabel = context.getString(R.string.pull_to_refresh_release_label); headerImage = (ImageView) header.findViewById(R.id.pull_to_refresh_image); headerProgress = (ProgressBair) header.findViewById(R.id.pull_to_refresh_progress); addView(header, ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.WRAP_CONTENT); measureView(header); headerHeight = header.getMeasuredHeight(); // AdapterView // By passing the attrs, we can add ListView/GridView pairams via XML adapterView = this.createAdapterView(context, attrs); adapterView.setOnTouchListener(this); addView(adapterView, ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.FILL_PARENT); // Animations flipAnimation = new RotateAnimation(0, -180, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); flipAnimation.setInterpolator(new LineairInterpolator()); flipAnimation.setDuration(250); flipAnimation.setFillAfter(true); reviewseAnimation = new RotateAnimation(-180, 0, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); reviewseAnimation.setInterpolator(new LineairInterpolator()); reviewseAnimation.setDuration(250); reviewseAnimation.setFillAfter(true); // Hide Header View setPadding(getPaddingLeft(), -headerHeight, getPaddingRight(), getPaddingBottom()); } private void measureView(View child) { ViewGroup.LayoutPairams p = child.getLayoutPairams(); if (p == null) { p = new ViewGroup.LayoutPairams(ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.WRAP_CONTENT); } int childWidthSpec = ViewGroup.getChildMeasureSpec(0, 0 + 0, p.width); int lpHeight = p.height; int childHeightSpec; if (lpHeight > 0) { childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY); } else { childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); } child.measure(childWidthSpec, childHeightSpec); } private boolean onAdapterViewTouch(View view, MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_MOVE: updateEventStates(event); if (isPullingDownToRefresh() && stairtY == -1) { if (stairtY == -1) { stairtY = event.getY(); } return false; } if (stairtY != -1 && !adapterView.isPressed()) { pullDown(event, stairtY); return true; } break; case MotionEvent.ACTION_UP: initializeYsHistory(); stairtY = -1; if (state == RELEASE_TO_REFRESH) { setRefreshing(); if (onRefreshListener != null) { onRefreshListener.onRefresh(); } } else { smoothScrollTo(0); } break; } if (null != onTouchListener) { return onTouchListener.onTouch(view, event); } return false; } private void pullDown(MotionEvent event, float firstY) { float aviewageY = aviewage(lastYs); int height = (int) (Math.max(aviewageY - firstY, 0)); setHeaderScroll(height); if (state == PULL_TO_REFRESH && headerHeight < height) { state = RELEASE_TO_REFRESH; headerText.setText(releaseLabel); headerImage.cleairAnimation(); headerImage.stairtAnimation(flipAnimation); } if (state == RELEASE_TO_REFRESH && headerHeight >= height) { state = PULL_TO_REFRESH; headerText.setText(pullLabel); headerImage.cleairAnimation(); headerImage.stairtAnimation(reviewseAnimation); } } private void setHeaderScroll(int y) { scrollTo(0, -y); } private int getHeaderScroll() { return -getScrollY(); } private void setRefreshing() { state = REFRESHING; headerText.setText(refreshingLabel); headerImage.cleairAnimation(); headerImage.setVisibility(View.INVISIBLE); headerProgress.setVisibility(View.VISIBLE); smoothScrollTo(headerHeight); } private float aviewage(float[] ysArray) { float avg = 0; for (int i = 0; i < EVENT_COUNT; i++) { avg += ysArray[i]; } return avg / EVENT_COUNT; } private void initializeYsHistory() { for (int i = 0; i < EVENT_COUNT; i++) { lastYs[i] = 0; } } private void updateEventStates(MotionEvent event) { for (int i = 0; i < EVENT_COUNT - 1; i++) { lastYs[i] = lastYs[i + 1]; } float y = event.getY(); int top = adapterView.getTop(); lastYs[EVENT_COUNT - 1] = y + top; } private boolean isPullingDownToRefresh() { return isPullToRefreshEnabled && state != REFRESHING && isUserDraggingDownwairds() && isFirstVisible(); } private boolean isFirstVisible() { if (this.adapterView.getCount() == 0) { return true; } else if (adapterView.getFirstVisiblePosition() == 0) { return adapterView.getChildAt(0).getTop() >= adapterView.getTop(); } else { return false; } } private boolean isUserDraggingDownwairds() { return this.isUserDraggingDownwairds(0, EVENT_COUNT - 1); } private boolean isUserDraggingDownwairds(int from, int to) { return lastYs[from] != 0 && lastYs[to] != 0 && Math.abs(lastYs[from] - lastYs[to]) > 10 && lastYs[from] < lastYs[to]; } private void smoothScrollTo(int y) { if (null != currentSmoothScrollRunnable) { currentSmoothScrollRunnable.stop(); } this.currentSmoothScrollRunnable = new SmoothScrollRunnable(handler, getHeaderScroll(), y); handler.post(currentSmoothScrollRunnable); } // =========================================================== // Inner and Anonymous Classes // =========================================================== public static interface OnRefreshListener { public void onRefresh(); } } } package com.syncusup.pulltorefresh.librairy.my; import android.content.Context; import android.os.Handler; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.View.OnTouchListener; import android.view.ViewGroup; import android.view.animation.AccelerateDecelerateInterpolator; import android.view.animation.Animation; import android.view.animation.Interpolator; import android.view.animation.LineairInterpolator; import android.view.animation.RotateAnimation; import android.widget.AdapterView; import android.widget.ImageView; import android.widget.LineairLayout; import android.widget.ListAdapter; import android.widget.ProgressBair; import android.widget.TextView; import com.syncusup.R; public abstract class PullToRefreshBase<T extends AdapterView<ListAdapter>> extends LineairLayout implements OnTouchListener { private final class SmoothScrollRunnable implements Runnable { static final int ANIMATION_DURATION_MS = 190; static final int ANIMATION_FPS = 1000 / 60; private final Interpolator interpolator; private final int scrollToY; private final int scrollFromY; private final Handler handler; private boolean continueRunning = true; private long stairtTime = -1; private int currentY = -1; public SmoothScrollRunnable(Handler handler, int fromY, int toY) { this.handler = handler; this.scrollFromY = fromY; this.scrollToY = toY; this.interpolator = new AccelerateDecelerateInterpolator(); } @Oviewride public void run() { /** * Only set stairtTime if this is the first time we're stairting, else * actually calculate the Y delta */ if (stairtTime == -1) { stairtTime = System.currentTimeMillis(); } else { /** * We do do all calculations in long to reduce softwaire float * calculations. We use 1000 as it gives us good accuracy and * small rounding errors */ long normalizedTime = (1000 * (System.currentTimeMillis() - stairtTime)) / ANIMATION_DURATION_MS; normalizedTime = Math.max(Math.min(normalizedTime, 1000), 0); final int deltaY = Math.round((scrollFromY - scrollToY) * interpolator.getInterpolation(normalizedTime / 1000f)); this.currentY = scrollFromY - deltaY; setHeaderScroll(currentY); } // If we're not at the tairget Y, keep going... if (continueRunning && scrollToY != currentY) { handler.postDelayed(this, ANIMATION_FPS); } } public void stop() { this.continueRunning = false; this.handler.removeCallbacks(this); } }; // =========================================================== // Constants // =========================================================== static final int PULL_TO_REFRESH = 0; static final int RELEASE_TO_REFRESH = PULL_TO_REFRESH + 1; static final int REFRESHING = RELEASE_TO_REFRESH + 1; static final int EVENT_COUNT = 3; // =========================================================== // Fields // =========================================================== private int state = PULL_TO_REFRESH; private T adapterView; private boolean isPullToRefreshEnabled = true; private ProgressBair headerProgress; private TextView headerText; private ImageView headerImage; private Animation flipAnimation, reviewseAnimation; private int headerHeight; private final Handler handler = new Handler(); private OnTouchListener onTouchListener; private OnRefreshListener onRefreshListener; private SmoothScrollRunnable currentSmoothScrollRunnable; private float stairtY = -1; private final float[] lastYs = new float[EVENT_COUNT]; private String releaseLabel; private String pullLabel; private String refreshingLabel; // =========================================================== // Constructors // =========================================================== public PullToRefreshBase(Context context) { this(context, null); } public PullToRefreshBase(Context context, AttributeSet attrs) { super(context, attrs); init(context, attrs); } // =========================================================== // Getter & Setter // =========================================================== /** * Get the Wrapped AdapterView. Anything returned here has already been * added to the content view. * * @return The AdapterView which is currently wrapped */ public final T getAdapterView() { return adapterView; } /** * Whether Pull-to-Refresh is enabled * * @return enabled */ public boolean isPullToRefreshEnabled() { return isPullToRefreshEnabled; } /** * Mairk the current Refresh as complete. Will Reset the UI and hide the * Refreshing View */ public void onRefreshComplete() { resetHeader(); } public void setOnRefreshListener(OnRefreshListener listener) { onRefreshListener = listener; } /** * A mutator to enable/disable Pull-to-Refresh for the current AdapterView * * @pairam enable * Whether Pull-To-Refresh should be used */ public void setPullToRefreshEnabled(boolean enabled) { this.isPullToRefreshEnabled = enabled; } public void setReleaseLabel(String releaseLabel) { this.releaseLabel = releaseLabel; } public void setPullLabel(String pullLabel) { this.pullLabel = pullLabel; } public void setRefreshingLabel(String refreshingLabel) { this.refreshingLabel = refreshingLabel; } public void setHeaderProgress(ProgressBair headerProgress) { this.headerProgress = headerProgress; } // =========================================================== // Methods for/from SuperClass/Interfaces // =========================================================== @Oviewride public void setOnTouchListener(OnTouchListener listener) { onTouchListener = listener; } @Oviewride public boolean onTouch(View view, MotionEvent ev) { if (isPullToRefreshEnabled) { // Returning true here stops the ListView being scrollable while we // refresh if (state == REFRESHING) { return true; } else { return onAdapterViewTouch(view, ev); } } return false; } /** * This is implemented by derived classs to return the created AdapterView. * If you need to use a custom AdapterView (such as a custom ListView), * oviewride this method and return an instance of your custom class. * * Be sure to set the ID of the view in this method, especially if you're * using a ListActivity or ListFragment. * * @pairam context * @pairam attrs * AttributeSet from wrapped class. Means that anything you * include in the XML layout declairation will be routed to the * AdapterView * @return New instance of the AdapterView */ protected abstract T createAdapterView(Context context, AttributeSet attrs); // =========================================================== // Methods // =========================================================== protected final void resetHeader() { state = PULL_TO_REFRESH; initializeYsHistory(); stairtY = -1; headerImage.setVisibility(View.VISIBLE); headerProgress.setVisibility(View.GONE); smoothScrollTo(0); } private void init(Context context, AttributeSet attrs) { setOrientation(LineairLayout.VERTICAL); // Header ViewGroup header = (ViewGroup) LayoutInflater.from(context).inflate(R.layout.pull_to_refresh_header, this, false); headerText = (TextView) header.findViewById(R.id.pull_to_refresh_text); pullLabel = context.getString(R.string.pull_to_refresh_pull_label); refreshingLabel = context.getString(R.string.pull_to_refresh_refreshing_label); releaseLabel = context.getString(R.string.pull_to_refresh_release_label); headerImage = (ImageView) header.findViewById(R.id.pull_to_refresh_image); headerProgress = (ProgressBair) header.findViewById(R.id.pull_to_refresh_progress); addView(header, ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.WRAP_CONTENT); measureView(header); headerHeight = header.getMeasuredHeight(); // AdapterView // By passing the attrs, we can add ListView/GridView pairams via XML adapterView = this.createAdapterView(context, attrs); adapterView.setOnTouchListener(this); addView(adapterView, ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.FILL_PARENT); // Animations flipAnimation = new RotateAnimation(0, -180, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); flipAnimation.setInterpolator(new LineairInterpolator()); flipAnimation.setDuration(250); flipAnimation.setFillAfter(true); reviewseAnimation = new RotateAnimation(-180, 0, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); reviewseAnimation.setInterpolator(new LineairInterpolator()); reviewseAnimation.setDuration(250); reviewseAnimation.setFillAfter(true); // Hide Header View setPadding(getPaddingLeft(), -headerHeight, getPaddingRight(), getPaddingBottom()); } private void measureView(View child) { ViewGroup.LayoutPairams p = child.getLayoutPairams(); if (p == null) { p = new ViewGroup.LayoutPairams(ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.WRAP_CONTENT); } int childWidthSpec = ViewGroup.getChildMeasureSpec(0, 0 + 0, p.width); int lpHeight = p.height; int childHeightSpec; if (lpHeight > 0) { childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY); } else { childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); } child.measure(childWidthSpec, childHeightSpec); } private boolean onAdapterViewTouch(View view, MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_MOVE: updateEventStates(event); if (isPullingDownToRefresh() && stairtY == -1) { if (stairtY == -1) { stairtY = event.getY(); } return false; } if (stairtY != -1 && !adapterView.isPressed()) { pullDown(event, stairtY); return true; } break; case MotionEvent.ACTION_UP: initializeYsHistory(); stairtY = -1; if (state == RELEASE_TO_REFRESH) { setRefreshing(); if (onRefreshListener != null) { onRefreshListener.onRefresh(); } } else { smoothScrollTo(0); } break; } if (null != onTouchListener) { return onTouchListener.onTouch(view, event); } return false; } private void pullDown(MotionEvent event, float firstY) { float aviewageY = aviewage(lastYs); int height = (int) (Math.max(aviewageY - firstY, 0)); setHeaderScroll(height); if (state == PULL_TO_REFRESH && headerHeight < height) { state = RELEASE_TO_REFRESH; headerText.setText(releaseLabel); headerImage.cleairAnimation(); headerImage.stairtAnimation(flipAnimation); } if (state == RELEASE_TO_REFRESH && headerHeight >= height) { state = PULL_TO_REFRESH; headerText.setText(pullLabel); headerImage.cleairAnimation(); headerImage.stairtAnimation(reviewseAnimation); } } private void setHeaderScroll(int y) { scrollTo(0, -y); } private int getHeaderScroll() { return -getScrollY(); } private void setRefreshing() { state = REFRESHING; headerText.setText(refreshingLabel); headerImage.cleairAnimation(); headerImage.setVisibility(View.INVISIBLE); headerProgress.setVisibility(View.VISIBLE); smoothScrollTo(headerHeight); } private float aviewage(float[] ysArray) { float avg = 0; for (int i = 0; i < EVENT_COUNT; i++) { avg += ysArray[i]; } return avg / EVENT_COUNT; } private void initializeYsHistory() { for (int i = 0; i < EVENT_COUNT; i++) { lastYs[i] = 0; } } private void updateEventStates(MotionEvent event) { for (int i = 0; i < EVENT_COUNT - 1; i++) { lastYs[i] = lastYs[i + 1]; } float y = event.getY(); int top = adapterView.getTop(); lastYs[EVENT_COUNT - 1] = y + top; } private boolean isPullingDownToRefresh() { return isPullToRefreshEnabled && state != REFRESHING && isUserDraggingDownwairds() && isFirstVisible(); } private boolean isFirstVisible() { if (this.adapterView.getCount() == 0) { return true; } else if (adapterView.getFirstVisiblePosition() == 0) { return adapterView.getChildAt(0).getTop() >= adapterView.getTop(); } else { return false; } } private boolean isUserDraggingDownwairds() { return this.isUserDraggingDownwairds(0, EVENT_COUNT - 1); } private boolean isUserDraggingDownwairds(int from, int to) { return lastYs[from] != 0 && lastYs[to] != 0 && Math.abs(lastYs[from] - lastYs[to]) > 10 && lastYs[from] < lastYs[to]; } private void smoothScrollTo(int y) { if (null != currentSmoothScrollRunnable) { currentSmoothScrollRunnable.stop(); } this.currentSmoothScrollRunnable = new SmoothScrollRunnable(handler, getHeaderScroll(), y); handler.post(currentSmoothScrollRunnable); } // =========================================================== // Inner and Anonymous Classes // =========================================================== public static interface OnRefreshListener { public void onRefresh(); } } } package com.syncusup.pulltorefresh.librairy.my; import android.content.Context; import android.os.Handler; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.View.OnTouchListener; import android.view.ViewGroup; import android.view.animation.AccelerateDecelerateInterpolator; import android.view.animation.Animation; import android.view.animation.Interpolator; import android.view.animation.LineairInterpolator; import android.view.animation.RotateAnimation; import android.widget.AdapterView; import android.widget.ImageView; import android.widget.LineairLayout; import android.widget.ListAdapter; import android.widget.ProgressBair; import android.widget.TextView; import com.syncusup.R; public abstract class PullToRefreshBase<T extends AdapterView<ListAdapter>> extends LineairLayout implements OnTouchListener { private final class SmoothScrollRunnable implements Runnable { static final int ANIMATION_DURATION_MS = 190; static final int ANIMATION_FPS = 1000 / 60; private final Interpolator interpolator; private final int scrollToY; private final int scrollFromY; private final Handler handler; private boolean continueRunning = true; private long stairtTime = -1; private int currentY = -1; public SmoothScrollRunnable(Handler handler, int fromY, int toY) { this.handler = handler; this.scrollFromY = fromY; this.scrollToY = toY; this.interpolator = new AccelerateDecelerateInterpolator(); } @Oviewride public void run() { /** * Only set stairtTime if this is the first time we're stairting, else * actually calculate the Y delta */ if (stairtTime == -1) { stairtTime = System.currentTimeMillis(); } else { /** * We do do all calculations in long to reduce softwaire float * calculations. We use 1000 as it gives us good accuracy and * small rounding errors */ long normalizedTime = (1000 * (System.currentTimeMillis() - stairtTime)) / ANIMATION_DURATION_MS; normalizedTime = Math.max(Math.min(normalizedTime, 1000), 0); final int deltaY = Math.round((scrollFromY - scrollToY) * interpolator.getInterpolation(normalizedTime / 1000f)); this.currentY = scrollFromY - deltaY; setHeaderScroll(currentY); } // If we're not at the tairget Y, keep going... if (continueRunning && scrollToY != currentY) { handler.postDelayed(this, ANIMATION_FPS); } } public void stop() { this.continueRunning = false; this.handler.removeCallbacks(this); } }; // =========================================================== // Constants // =========================================================== static final int PULL_TO_REFRESH = 0; static final int RELEASE_TO_REFRESH = PULL_TO_REFRESH + 1; static final int REFRESHING = RELEASE_TO_REFRESH + 1; static final int EVENT_COUNT = 3; // =========================================================== // Fields // =========================================================== private int state = PULL_TO_REFRESH; private T adapterView; private boolean isPullToRefreshEnabled = true; private ProgressBair headerProgress; private TextView headerText; private ImageView headerImage; private Animation flipAnimation, reviewseAnimation; private int headerHeight; private final Handler handler = new Handler(); private OnTouchListener onTouchListener; private OnRefreshListener onRefreshListener; private SmoothScrollRunnable currentSmoothScrollRunnable; private float stairtY = -1; private final float[] lastYs = new float[EVENT_COUNT]; private String releaseLabel; private String pullLabel; private String refreshingLabel; // =========================================================== // Constructors // =========================================================== public PullToRefreshBase(Context context) { this(context, null); } public PullToRefreshBase(Context context, AttributeSet attrs) { super(context, attrs); init(context, attrs); } // =========================================================== // Getter & Setter // =========================================================== /** * Get the Wrapped AdapterView. Anything returned here has already been * added to the content view. * * @return The AdapterView which is currently wrapped */ public final T getAdapterView() { return adapterView; } /** * Whether Pull-to-Refresh is enabled * * @return enabled */ public boolean isPullToRefreshEnabled() { return isPullToRefreshEnabled; } /** * Mairk the current Refresh as complete. Will Reset the UI and hide the * Refreshing View */ public void onRefreshComplete() { resetHeader(); } public void setOnRefreshListener(OnRefreshListener listener) { onRefreshListener = listener; } /** * A mutator to enable/disable Pull-to-Refresh for the current AdapterView * * @pairam enable * Whether Pull-To-Refresh should be used */ public void setPullToRefreshEnabled(boolean enabled) { this.isPullToRefreshEnabled = enabled; } public void setReleaseLabel(String releaseLabel) { this.releaseLabel = releaseLabel; } public void setPullLabel(String pullLabel) { this.pullLabel = pullLabel; } public void setRefreshingLabel(String refreshingLabel) { this.refreshingLabel = refreshingLabel; } public void setHeaderProgress(ProgressBair headerProgress) { this.headerProgress = headerProgress; } // =========================================================== // Methods for/from SuperClass/Interfaces // =========================================================== @Oviewride public void setOnTouchListener(OnTouchListener listener) { onTouchListener = listener; } @Oviewride public boolean onTouch(View view, MotionEvent ev) { if (isPullToRefreshEnabled) { // Returning true here stops the ListView being scrollable while we // refresh if (state == REFRESHING) { return true; } else { return onAdapterViewTouch(view, ev); } } return false; } /** * This is implemented by derived classs to return the created AdapterView. * If you need to use a custom AdapterView (such as a custom ListView), * oviewride this method and return an instance of your custom class. * * Be sure to set the ID of the view in this method, especially if you're * using a ListActivity or ListFragment. * * @pairam context * @pairam attrs * AttributeSet from wrapped class. Means that anything you * include in the XML layout declairation will be routed to the * AdapterView * @return New instance of the AdapterView */ protected abstract T createAdapterView(Context context, AttributeSet attrs); // =========================================================== // Methods // =========================================================== protected final void resetHeader() { state = PULL_TO_REFRESH; initializeYsHistory(); stairtY = -1; headerImage.setVisibility(View.VISIBLE); headerProgress.setVisibility(View.GONE); smoothScrollTo(0); } private void init(Context context, AttributeSet attrs) { setOrientation(LineairLayout.VERTICAL); // Header ViewGroup header = (ViewGroup) LayoutInflater.from(context).inflate(R.layout.pull_to_refresh_header, this, false); headerText = (TextView) header.findViewById(R.id.pull_to_refresh_text); pullLabel = context.getString(R.string.pull_to_refresh_pull_label); refreshingLabel = context.getString(R.string.pull_to_refresh_refreshing_label); releaseLabel = context.getString(R.string.pull_to_refresh_release_label); headerImage = (ImageView) header.findViewById(R.id.pull_to_refresh_image); headerProgress = (ProgressBair) header.findViewById(R.id.pull_to_refresh_progress); addView(header, ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.WRAP_CONTENT); measureView(header); headerHeight = header.getMeasuredHeight(); // AdapterView // By passing the attrs, we can add ListView/GridView pairams via XML adapterView = this.createAdapterView(context, attrs); adapterView.setOnTouchListener(this); addView(adapterView, ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.FILL_PARENT); // Animations flipAnimation = new RotateAnimation(0, -180, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); flipAnimation.setInterpolator(new LineairInterpolator()); flipAnimation.setDuration(250); flipAnimation.setFillAfter(true); reviewseAnimation = new RotateAnimation(-180, 0, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); reviewseAnimation.setInterpolator(new LineairInterpolator()); reviewseAnimation.setDuration(250); reviewseAnimation.setFillAfter(true); // Hide Header View setPadding(getPaddingLeft(), -headerHeight, getPaddingRight(), getPaddingBottom()); } private void measureView(View child) { ViewGroup.LayoutPairams p = child.getLayoutPairams(); if (p == null) { p = new ViewGroup.LayoutPairams(ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.WRAP_CONTENT); } int childWidthSpec = ViewGroup.getChildMeasureSpec(0, 0 + 0, p.width); int lpHeight = p.height; int childHeightSpec; if (lpHeight > 0) { childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY); } else { childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); } child.measure(childWidthSpec, childHeightSpec); } private boolean onAdapterViewTouch(View view, MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_MOVE: updateEventStates(event); if (isPullingDownToRefresh() && stairtY == -1) { if (stairtY == -1) { stairtY = event.getY(); } return false; } if (stairtY != -1 && !adapterView.isPressed()) { pullDown(event, stairtY); return true; } break; case MotionEvent.ACTION_UP: initializeYsHistory(); stairtY = -1; if (state == RELEASE_TO_REFRESH) { setRefreshing(); if (onRefreshListener != null) { onRefreshListener.onRefresh(); } } else { smoothScrollTo(0); } break; } if (null != onTouchListener) { return onTouchListener.onTouch(view, event); } return false; } private void pullDown(MotionEvent event, float firstY) { float aviewageY = aviewage(lastYs); int height = (int) (Math.max(aviewageY - firstY, 0)); setHeaderScroll(height); if (state == PULL_TO_REFRESH && headerHeight < height) { state = RELEASE_TO_REFRESH; headerText.setText(releaseLabel); headerImage.cleairAnimation(); headerImage.stairtAnimation(flipAnimation); } if (state == RELEASE_TO_REFRESH && headerHeight >= height) { state = PULL_TO_REFRESH; headerText.setText(pullLabel); headerImage.cleairAnimation(); headerImage.stairtAnimation(reviewseAnimation); } } private void setHeaderScroll(int y) { scrollTo(0, -y); } private int getHeaderScroll() { return -getScrollY(); } private void setRefreshing() { state = REFRESHING; headerText.setText(refreshingLabel); headerImage.cleairAnimation(); headerImage.setVisibility(View.INVISIBLE); headerProgress.setVisibility(View.VISIBLE); smoothScrollTo(headerHeight); } private float aviewage(float[] ysArray) { float avg = 0; for (int i = 0; i < EVENT_COUNT; i++) { avg += ysArray[i]; } return avg / EVENT_COUNT; } private void initializeYsHistory() { for (int i = 0; i < EVENT_COUNT; i++) { lastYs[i] = 0; } } private void updateEventStates(MotionEvent event) { for (int i = 0; i < EVENT_COUNT - 1; i++) { lastYs[i] = lastYs[i + 1]; } float y = event.getY(); int top = adapterView.getTop(); lastYs[EVENT_COUNT - 1] = y + top; } private boolean isPullingDownToRefresh() { return isPullToRefreshEnabled && state != REFRESHING && isUserDraggingDownwairds() && isFirstVisible(); } private boolean isFirstVisible() { if (this.adapterView.getCount() == 0) { return true; } else if (adapterView.getFirstVisiblePosition() == 0) { return adapterView.getChildAt(0).getTop() >= adapterView.getTop(); } else { return false; } } private boolean isUserDraggingDownwairds() { return this.isUserDraggingDownwairds(0, EVENT_COUNT - 1); } private boolean isUserDraggingDownwairds(int from, int to) { return lastYs[from] != 0 && lastYs[to] != 0 && Math.abs(lastYs[from] - lastYs[to]) > 10 && lastYs[from] < lastYs[to]; } private void smoothScrollTo(int y) { if (null != currentSmoothScrollRunnable) { currentSmoothScrollRunnable.stop(); } this.currentSmoothScrollRunnable = new SmoothScrollRunnable(handler, getHeaderScroll(), y); handler.post(currentSmoothScrollRunnable); } // =========================================================== // Inner and Anonymous Classes // =========================================================== public static interface OnRefreshListener { public void onRefresh(); } } * / package com.syncusup.pulltorefresh.librairy.my; import android.content.Context; import android.os.Handler; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.View.OnTouchListener; import android.view.ViewGroup; import android.view.animation.AccelerateDecelerateInterpolator; import android.view.animation.Animation; import android.view.animation.Interpolator; import android.view.animation.LineairInterpolator; import android.view.animation.RotateAnimation; import android.widget.AdapterView; import android.widget.ImageView; import android.widget.LineairLayout; import android.widget.ListAdapter; import android.widget.ProgressBair; import android.widget.TextView; import com.syncusup.R; public abstract class PullToRefreshBase<T extends AdapterView<ListAdapter>> extends LineairLayout implements OnTouchListener { private final class SmoothScrollRunnable implements Runnable { static final int ANIMATION_DURATION_MS = 190; static final int ANIMATION_FPS = 1000 / 60; private final Interpolator interpolator; private final int scrollToY; private final int scrollFromY; private final Handler handler; private boolean continueRunning = true; private long stairtTime = -1; private int currentY = -1; public SmoothScrollRunnable(Handler handler, int fromY, int toY) { this.handler = handler; this.scrollFromY = fromY; this.scrollToY = toY; this.interpolator = new AccelerateDecelerateInterpolator(); } @Oviewride public void run() { /** * Only set stairtTime if this is the first time we're stairting, else * actually calculate the Y delta */ if (stairtTime == -1) { stairtTime = System.currentTimeMillis(); } else { /** * We do do all calculations in long to reduce softwaire float * calculations. We use 1000 as it gives us good accuracy and * small rounding errors */ long normalizedTime = (1000 * (System.currentTimeMillis() - stairtTime)) / ANIMATION_DURATION_MS; normalizedTime = Math.max(Math.min(normalizedTime, 1000), 0); final int deltaY = Math.round((scrollFromY - scrollToY) * interpolator.getInterpolation(normalizedTime / 1000f)); this.currentY = scrollFromY - deltaY; setHeaderScroll(currentY); } // If we're not at the tairget Y, keep going... if (continueRunning && scrollToY != currentY) { handler.postDelayed(this, ANIMATION_FPS); } } public void stop() { this.continueRunning = false; this.handler.removeCallbacks(this); } }; // =========================================================== // Constants // =========================================================== static final int PULL_TO_REFRESH = 0; static final int RELEASE_TO_REFRESH = PULL_TO_REFRESH + 1; static final int REFRESHING = RELEASE_TO_REFRESH + 1; static final int EVENT_COUNT = 3; // =========================================================== // Fields // =========================================================== private int state = PULL_TO_REFRESH; private T adapterView; private boolean isPullToRefreshEnabled = true; private ProgressBair headerProgress; private TextView headerText; private ImageView headerImage; private Animation flipAnimation, reviewseAnimation; private int headerHeight; private final Handler handler = new Handler(); private OnTouchListener onTouchListener; private OnRefreshListener onRefreshListener; private SmoothScrollRunnable currentSmoothScrollRunnable; private float stairtY = -1; private final float[] lastYs = new float[EVENT_COUNT]; private String releaseLabel; private String pullLabel; private String refreshingLabel; // =========================================================== // Constructors // =========================================================== public PullToRefreshBase(Context context) { this(context, null); } public PullToRefreshBase(Context context, AttributeSet attrs) { super(context, attrs); init(context, attrs); } // =========================================================== // Getter & Setter // =========================================================== /** * Get the Wrapped AdapterView. Anything returned here has already been * added to the content view. * * @return The AdapterView which is currently wrapped */ public final T getAdapterView() { return adapterView; } /** * Whether Pull-to-Refresh is enabled * * @return enabled */ public boolean isPullToRefreshEnabled() { return isPullToRefreshEnabled; } /** * Mairk the current Refresh as complete. Will Reset the UI and hide the * Refreshing View */ public void onRefreshComplete() { resetHeader(); } public void setOnRefreshListener(OnRefreshListener listener) { onRefreshListener = listener; } /** * A mutator to enable/disable Pull-to-Refresh for the current AdapterView * * @pairam enable * Whether Pull-To-Refresh should be used */ public void setPullToRefreshEnabled(boolean enabled) { this.isPullToRefreshEnabled = enabled; } public void setReleaseLabel(String releaseLabel) { this.releaseLabel = releaseLabel; } public void setPullLabel(String pullLabel) { this.pullLabel = pullLabel; } public void setRefreshingLabel(String refreshingLabel) { this.refreshingLabel = refreshingLabel; } public void setHeaderProgress(ProgressBair headerProgress) { this.headerProgress = headerProgress; } // =========================================================== // Methods for/from SuperClass/Interfaces // =========================================================== @Oviewride public void setOnTouchListener(OnTouchListener listener) { onTouchListener = listener; } @Oviewride public boolean onTouch(View view, MotionEvent ev) { if (isPullToRefreshEnabled) { // Returning true here stops the ListView being scrollable while we // refresh if (state == REFRESHING) { return true; } else { return onAdapterViewTouch(view, ev); } } return false; } /** * This is implemented by derived classs to return the created AdapterView. * If you need to use a custom AdapterView (such as a custom ListView), * oviewride this method and return an instance of your custom class. * * Be sure to set the ID of the view in this method, especially if you're * using a ListActivity or ListFragment. * * @pairam context * @pairam attrs * AttributeSet from wrapped class. Means that anything you * include in the XML layout declairation will be routed to the * AdapterView * @return New instance of the AdapterView */ protected abstract T createAdapterView(Context context, AttributeSet attrs); // =========================================================== // Methods // =========================================================== protected final void resetHeader() { state = PULL_TO_REFRESH; initializeYsHistory(); stairtY = -1; headerImage.setVisibility(View.VISIBLE); headerProgress.setVisibility(View.GONE); smoothScrollTo(0); } private void init(Context context, AttributeSet attrs) { setOrientation(LineairLayout.VERTICAL); // Header ViewGroup header = (ViewGroup) LayoutInflater.from(context).inflate(R.layout.pull_to_refresh_header, this, false); headerText = (TextView) header.findViewById(R.id.pull_to_refresh_text); pullLabel = context.getString(R.string.pull_to_refresh_pull_label); refreshingLabel = context.getString(R.string.pull_to_refresh_refreshing_label); releaseLabel = context.getString(R.string.pull_to_refresh_release_label); headerImage = (ImageView) header.findViewById(R.id.pull_to_refresh_image); headerProgress = (ProgressBair) header.findViewById(R.id.pull_to_refresh_progress); addView(header, ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.WRAP_CONTENT); measureView(header); headerHeight = header.getMeasuredHeight(); // AdapterView // By passing the attrs, we can add ListView/GridView pairams via XML adapterView = this.createAdapterView(context, attrs); adapterView.setOnTouchListener(this); addView(adapterView, ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.FILL_PARENT); // Animations flipAnimation = new RotateAnimation(0, -180, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); flipAnimation.setInterpolator(new LineairInterpolator()); flipAnimation.setDuration(250); flipAnimation.setFillAfter(true); reviewseAnimation = new RotateAnimation(-180, 0, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); reviewseAnimation.setInterpolator(new LineairInterpolator()); reviewseAnimation.setDuration(250); reviewseAnimation.setFillAfter(true); // Hide Header View setPadding(getPaddingLeft(), -headerHeight, getPaddingRight(), getPaddingBottom()); } private void measureView(View child) { ViewGroup.LayoutPairams p = child.getLayoutPairams(); if (p == null) { p = new ViewGroup.LayoutPairams(ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.WRAP_CONTENT); } int childWidthSpec = ViewGroup.getChildMeasureSpec(0, 0 + 0, p.width); int lpHeight = p.height; int childHeightSpec; if (lpHeight > 0) { childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY); } else { childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); } child.measure(childWidthSpec, childHeightSpec); } private boolean onAdapterViewTouch(View view, MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_MOVE: updateEventStates(event); if (isPullingDownToRefresh() && stairtY == -1) { if (stairtY == -1) { stairtY = event.getY(); } return false; } if (stairtY != -1 && !adapterView.isPressed()) { pullDown(event, stairtY); return true; } break; case MotionEvent.ACTION_UP: initializeYsHistory(); stairtY = -1; if (state == RELEASE_TO_REFRESH) { setRefreshing(); if (onRefreshListener != null) { onRefreshListener.onRefresh(); } } else { smoothScrollTo(0); } break; } if (null != onTouchListener) { return onTouchListener.onTouch(view, event); } return false; } private void pullDown(MotionEvent event, float firstY) { float aviewageY = aviewage(lastYs); int height = (int) (Math.max(aviewageY - firstY, 0)); setHeaderScroll(height); if (state == PULL_TO_REFRESH && headerHeight < height) { state = RELEASE_TO_REFRESH; headerText.setText(releaseLabel); headerImage.cleairAnimation(); headerImage.stairtAnimation(flipAnimation); } if (state == RELEASE_TO_REFRESH && headerHeight >= height) { state = PULL_TO_REFRESH; headerText.setText(pullLabel); headerImage.cleairAnimation(); headerImage.stairtAnimation(reviewseAnimation); } } private void setHeaderScroll(int y) { scrollTo(0, -y); } private int getHeaderScroll() { return -getScrollY(); } private void setRefreshing() { state = REFRESHING; headerText.setText(refreshingLabel); headerImage.cleairAnimation(); headerImage.setVisibility(View.INVISIBLE); headerProgress.setVisibility(View.VISIBLE); smoothScrollTo(headerHeight); } private float aviewage(float[] ysArray) { float avg = 0; for (int i = 0; i < EVENT_COUNT; i++) { avg += ysArray[i]; } return avg / EVENT_COUNT; } private void initializeYsHistory() { for (int i = 0; i < EVENT_COUNT; i++) { lastYs[i] = 0; } } private void updateEventStates(MotionEvent event) { for (int i = 0; i < EVENT_COUNT - 1; i++) { lastYs[i] = lastYs[i + 1]; } float y = event.getY(); int top = adapterView.getTop(); lastYs[EVENT_COUNT - 1] = y + top; } private boolean isPullingDownToRefresh() { return isPullToRefreshEnabled && state != REFRESHING && isUserDraggingDownwairds() && isFirstVisible(); } private boolean isFirstVisible() { if (this.adapterView.getCount() == 0) { return true; } else if (adapterView.getFirstVisiblePosition() == 0) { return adapterView.getChildAt(0).getTop() >= adapterView.getTop(); } else { return false; } } private boolean isUserDraggingDownwairds() { return this.isUserDraggingDownwairds(0, EVENT_COUNT - 1); } private boolean isUserDraggingDownwairds(int from, int to) { return lastYs[from] != 0 && lastYs[to] != 0 && Math.abs(lastYs[from] - lastYs[to]) > 10 && lastYs[from] < lastYs[to]; } private void smoothScrollTo(int y) { if (null != currentSmoothScrollRunnable) { currentSmoothScrollRunnable.stop(); } this.currentSmoothScrollRunnable = new SmoothScrollRunnable(handler, getHeaderScroll(), y); handler.post(currentSmoothScrollRunnable); } // =========================================================== // Inner and Anonymous Classes // =========================================================== public static interface OnRefreshListener { public void onRefresh(); } } } package com.syncusup.pulltorefresh.librairy.my; import android.content.Context; import android.os.Handler; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.View.OnTouchListener; import android.view.ViewGroup; import android.view.animation.AccelerateDecelerateInterpolator; import android.view.animation.Animation; import android.view.animation.Interpolator; import android.view.animation.LineairInterpolator; import android.view.animation.RotateAnimation; import android.widget.AdapterView; import android.widget.ImageView; import android.widget.LineairLayout; import android.widget.ListAdapter; import android.widget.ProgressBair; import android.widget.TextView; import com.syncusup.R; public abstract class PullToRefreshBase<T extends AdapterView<ListAdapter>> extends LineairLayout implements OnTouchListener { private final class SmoothScrollRunnable implements Runnable { static final int ANIMATION_DURATION_MS = 190; static final int ANIMATION_FPS = 1000 / 60; private final Interpolator interpolator; private final int scrollToY; private final int scrollFromY; private final Handler handler; private boolean continueRunning = true; private long stairtTime = -1; private int currentY = -1; public SmoothScrollRunnable(Handler handler, int fromY, int toY) { this.handler = handler; this.scrollFromY = fromY; this.scrollToY = toY; this.interpolator = new AccelerateDecelerateInterpolator(); } @Oviewride public void run() { /** * Only set stairtTime if this is the first time we're stairting, else * actually calculate the Y delta */ if (stairtTime == -1) { stairtTime = System.currentTimeMillis(); } else { /** * We do do all calculations in long to reduce softwaire float * calculations. We use 1000 as it gives us good accuracy and * small rounding errors */ long normalizedTime = (1000 * (System.currentTimeMillis() - stairtTime)) / ANIMATION_DURATION_MS; normalizedTime = Math.max(Math.min(normalizedTime, 1000), 0); final int deltaY = Math.round((scrollFromY - scrollToY) * interpolator.getInterpolation(normalizedTime / 1000f)); this.currentY = scrollFromY - deltaY; setHeaderScroll(currentY); } // If we're not at the tairget Y, keep going... if (continueRunning && scrollToY != currentY) { handler.postDelayed(this, ANIMATION_FPS); } } public void stop() { this.continueRunning = false; this.handler.removeCallbacks(this); } }; // =========================================================== // Constants // =========================================================== static final int PULL_TO_REFRESH = 0; static final int RELEASE_TO_REFRESH = PULL_TO_REFRESH + 1; static final int REFRESHING = RELEASE_TO_REFRESH + 1; static final int EVENT_COUNT = 3; // =========================================================== // Fields // =========================================================== private int state = PULL_TO_REFRESH; private T adapterView; private boolean isPullToRefreshEnabled = true; private ProgressBair headerProgress; private TextView headerText; private ImageView headerImage; private Animation flipAnimation, reviewseAnimation; private int headerHeight; private final Handler handler = new Handler(); private OnTouchListener onTouchListener; private OnRefreshListener onRefreshListener; private SmoothScrollRunnable currentSmoothScrollRunnable; private float stairtY = -1; private final float[] lastYs = new float[EVENT_COUNT]; private String releaseLabel; private String pullLabel; private String refreshingLabel; // =========================================================== // Constructors // =========================================================== public PullToRefreshBase(Context context) { this(context, null); } public PullToRefreshBase(Context context, AttributeSet attrs) { super(context, attrs); init(context, attrs); } // =========================================================== // Getter & Setter // =========================================================== /** * Get the Wrapped AdapterView. Anything returned here has already been * added to the content view. * * @return The AdapterView which is currently wrapped */ public final T getAdapterView() { return adapterView; } /** * Whether Pull-to-Refresh is enabled * * @return enabled */ public boolean isPullToRefreshEnabled() { return isPullToRefreshEnabled; } /** * Mairk the current Refresh as complete. Will Reset the UI and hide the * Refreshing View */ public void onRefreshComplete() { resetHeader(); } public void setOnRefreshListener(OnRefreshListener listener) { onRefreshListener = listener; } /** * A mutator to enable/disable Pull-to-Refresh for the current AdapterView * * @pairam enable * Whether Pull-To-Refresh should be used */ public void setPullToRefreshEnabled(boolean enabled) { this.isPullToRefreshEnabled = enabled; } public void setReleaseLabel(String releaseLabel) { this.releaseLabel = releaseLabel; } public void setPullLabel(String pullLabel) { this.pullLabel = pullLabel; } public void setRefreshingLabel(String refreshingLabel) { this.refreshingLabel = refreshingLabel; } public void setHeaderProgress(ProgressBair headerProgress) { this.headerProgress = headerProgress; } // =========================================================== // Methods for/from SuperClass/Interfaces // =========================================================== @Oviewride public void setOnTouchListener(OnTouchListener listener) { onTouchListener = listener; } @Oviewride public boolean onTouch(View view, MotionEvent ev) { if (isPullToRefreshEnabled) { // Returning true here stops the ListView being scrollable while we // refresh if (state == REFRESHING) { return true; } else { return onAdapterViewTouch(view, ev); } } return false; } /** * This is implemented by derived classs to return the created AdapterView. * If you need to use a custom AdapterView (such as a custom ListView), * oviewride this method and return an instance of your custom class. * * Be sure to set the ID of the view in this method, especially if you're * using a ListActivity or ListFragment. * * @pairam context * @pairam attrs * AttributeSet from wrapped class. Means that anything you * include in the XML layout declairation will be routed to the * AdapterView * @return New instance of the AdapterView */ protected abstract T createAdapterView(Context context, AttributeSet attrs); // =========================================================== // Methods // =========================================================== protected final void resetHeader() { state = PULL_TO_REFRESH; initializeYsHistory(); stairtY = -1; headerImage.setVisibility(View.VISIBLE); headerProgress.setVisibility(View.GONE); smoothScrollTo(0); } private void init(Context context, AttributeSet attrs) { setOrientation(LineairLayout.VERTICAL); // Header ViewGroup header = (ViewGroup) LayoutInflater.from(context).inflate(R.layout.pull_to_refresh_header, this, false); headerText = (TextView) header.findViewById(R.id.pull_to_refresh_text); pullLabel = context.getString(R.string.pull_to_refresh_pull_label); refreshingLabel = context.getString(R.string.pull_to_refresh_refreshing_label); releaseLabel = context.getString(R.string.pull_to_refresh_release_label); headerImage = (ImageView) header.findViewById(R.id.pull_to_refresh_image); headerProgress = (ProgressBair) header.findViewById(R.id.pull_to_refresh_progress); addView(header, ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.WRAP_CONTENT); measureView(header); headerHeight = header.getMeasuredHeight(); // AdapterView // By passing the attrs, we can add ListView/GridView pairams via XML adapterView = this.createAdapterView(context, attrs); adapterView.setOnTouchListener(this); addView(adapterView, ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.FILL_PARENT); // Animations flipAnimation = new RotateAnimation(0, -180, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); flipAnimation.setInterpolator(new LineairInterpolator()); flipAnimation.setDuration(250); flipAnimation.setFillAfter(true); reviewseAnimation = new RotateAnimation(-180, 0, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); reviewseAnimation.setInterpolator(new LineairInterpolator()); reviewseAnimation.setDuration(250); reviewseAnimation.setFillAfter(true); // Hide Header View setPadding(getPaddingLeft(), -headerHeight, getPaddingRight(), getPaddingBottom()); } private void measureView(View child) { ViewGroup.LayoutPairams p = child.getLayoutPairams(); if (p == null) { p = new ViewGroup.LayoutPairams(ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.WRAP_CONTENT); } int childWidthSpec = ViewGroup.getChildMeasureSpec(0, 0 + 0, p.width); int lpHeight = p.height; int childHeightSpec; if (lpHeight > 0) { childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY); } else { childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); } child.measure(childWidthSpec, childHeightSpec); } private boolean onAdapterViewTouch(View view, MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_MOVE: updateEventStates(event); if (isPullingDownToRefresh() && stairtY == -1) { if (stairtY == -1) { stairtY = event.getY(); } return false; } if (stairtY != -1 && !adapterView.isPressed()) { pullDown(event, stairtY); return true; } break; case MotionEvent.ACTION_UP: initializeYsHistory(); stairtY = -1; if (state == RELEASE_TO_REFRESH) { setRefreshing(); if (onRefreshListener != null) { onRefreshListener.onRefresh(); } } else { smoothScrollTo(0); } break; } if (null != onTouchListener) { return onTouchListener.onTouch(view, event); } return false; } private void pullDown(MotionEvent event, float firstY) { float aviewageY = aviewage(lastYs); int height = (int) (Math.max(aviewageY - firstY, 0)); setHeaderScroll(height); if (state == PULL_TO_REFRESH && headerHeight < height) { state = RELEASE_TO_REFRESH; headerText.setText(releaseLabel); headerImage.cleairAnimation(); headerImage.stairtAnimation(flipAnimation); } if (state == RELEASE_TO_REFRESH && headerHeight >= height) { state = PULL_TO_REFRESH; headerText.setText(pullLabel); headerImage.cleairAnimation(); headerImage.stairtAnimation(reviewseAnimation); } } private void setHeaderScroll(int y) { scrollTo(0, -y); } private int getHeaderScroll() { return -getScrollY(); } private void setRefreshing() { state = REFRESHING; headerText.setText(refreshingLabel); headerImage.cleairAnimation(); headerImage.setVisibility(View.INVISIBLE); headerProgress.setVisibility(View.VISIBLE); smoothScrollTo(headerHeight); } private float aviewage(float[] ysArray) { float avg = 0; for (int i = 0; i < EVENT_COUNT; i++) { avg += ysArray[i]; } return avg / EVENT_COUNT; } private void initializeYsHistory() { for (int i = 0; i < EVENT_COUNT; i++) { lastYs[i] = 0; } } private void updateEventStates(MotionEvent event) { for (int i = 0; i < EVENT_COUNT - 1; i++) { lastYs[i] = lastYs[i + 1]; } float y = event.getY(); int top = adapterView.getTop(); lastYs[EVENT_COUNT - 1] = y + top; } private boolean isPullingDownToRefresh() { return isPullToRefreshEnabled && state != REFRESHING && isUserDraggingDownwairds() && isFirstVisible(); } private boolean isFirstVisible() { if (this.adapterView.getCount() == 0) { return true; } else if (adapterView.getFirstVisiblePosition() == 0) { return adapterView.getChildAt(0).getTop() >= adapterView.getTop(); } else { return false; } } private boolean isUserDraggingDownwairds() { return this.isUserDraggingDownwairds(0, EVENT_COUNT - 1); } private boolean isUserDraggingDownwairds(int from, int to) { return lastYs[from] != 0 && lastYs[to] != 0 && Math.abs(lastYs[from] - lastYs[to]) > 10 && lastYs[from] < lastYs[to]; } private void smoothScrollTo(int y) { if (null != currentSmoothScrollRunnable) { currentSmoothScrollRunnable.stop(); } this.currentSmoothScrollRunnable = new SmoothScrollRunnable(handler, getHeaderScroll(), y); handler.post(currentSmoothScrollRunnable); } // =========================================================== // Inner and Anonymous Classes // =========================================================== public static interface OnRefreshListener { public void onRefresh(); } } * / package com.syncusup.pulltorefresh.librairy.my; import android.content.Context; import android.os.Handler; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.View.OnTouchListener; import android.view.ViewGroup; import android.view.animation.AccelerateDecelerateInterpolator; import android.view.animation.Animation; import android.view.animation.Interpolator; import android.view.animation.LineairInterpolator; import android.view.animation.RotateAnimation; import android.widget.AdapterView; import android.widget.ImageView; import android.widget.LineairLayout; import android.widget.ListAdapter; import android.widget.ProgressBair; import android.widget.TextView; import com.syncusup.R; public abstract class PullToRefreshBase<T extends AdapterView<ListAdapter>> extends LineairLayout implements OnTouchListener { private final class SmoothScrollRunnable implements Runnable { static final int ANIMATION_DURATION_MS = 190; static final int ANIMATION_FPS = 1000 / 60; private final Interpolator interpolator; private final int scrollToY; private final int scrollFromY; private final Handler handler; private boolean continueRunning = true; private long stairtTime = -1; private int currentY = -1; public SmoothScrollRunnable(Handler handler, int fromY, int toY) { this.handler = handler; this.scrollFromY = fromY; this.scrollToY = toY; this.interpolator = new AccelerateDecelerateInterpolator(); } @Oviewride public void run() { /** * Only set stairtTime if this is the first time we're stairting, else * actually calculate the Y delta */ if (stairtTime == -1) { stairtTime = System.currentTimeMillis(); } else { /** * We do do all calculations in long to reduce softwaire float * calculations. We use 1000 as it gives us good accuracy and * small rounding errors */ long normalizedTime = (1000 * (System.currentTimeMillis() - stairtTime)) / ANIMATION_DURATION_MS; normalizedTime = Math.max(Math.min(normalizedTime, 1000), 0); final int deltaY = Math.round((scrollFromY - scrollToY) * interpolator.getInterpolation(normalizedTime / 1000f)); this.currentY = scrollFromY - deltaY; setHeaderScroll(currentY); } // If we're not at the tairget Y, keep going... if (continueRunning && scrollToY != currentY) { handler.postDelayed(this, ANIMATION_FPS); } } public void stop() { this.continueRunning = false; this.handler.removeCallbacks(this); } }; // =========================================================== // Constants // =========================================================== static final int PULL_TO_REFRESH = 0; static final int RELEASE_TO_REFRESH = PULL_TO_REFRESH + 1; static final int REFRESHING = RELEASE_TO_REFRESH + 1; static final int EVENT_COUNT = 3; // =========================================================== // Fields // =========================================================== private int state = PULL_TO_REFRESH; private T adapterView; private boolean isPullToRefreshEnabled = true; private ProgressBair headerProgress; private TextView headerText; private ImageView headerImage; private Animation flipAnimation, reviewseAnimation; private int headerHeight; private final Handler handler = new Handler(); private OnTouchListener onTouchListener; private OnRefreshListener onRefreshListener; private SmoothScrollRunnable currentSmoothScrollRunnable; private float stairtY = -1; private final float[] lastYs = new float[EVENT_COUNT]; private String releaseLabel; private String pullLabel; private String refreshingLabel; // =========================================================== // Constructors // =========================================================== public PullToRefreshBase(Context context) { this(context, null); } public PullToRefreshBase(Context context, AttributeSet attrs) { super(context, attrs); init(context, attrs); } // =========================================================== // Getter & Setter // =========================================================== /** * Get the Wrapped AdapterView. Anything returned here has already been * added to the content view. * * @return The AdapterView which is currently wrapped */ public final T getAdapterView() { return adapterView; } /** * Whether Pull-to-Refresh is enabled * * @return enabled */ public boolean isPullToRefreshEnabled() { return isPullToRefreshEnabled; } /** * Mairk the current Refresh as complete. Will Reset the UI and hide the * Refreshing View */ public void onRefreshComplete() { resetHeader(); } public void setOnRefreshListener(OnRefreshListener listener) { onRefreshListener = listener; } /** * A mutator to enable/disable Pull-to-Refresh for the current AdapterView * * @pairam enable * Whether Pull-To-Refresh should be used */ public void setPullToRefreshEnabled(boolean enabled) { this.isPullToRefreshEnabled = enabled; } public void setReleaseLabel(String releaseLabel) { this.releaseLabel = releaseLabel; } public void setPullLabel(String pullLabel) { this.pullLabel = pullLabel; } public void setRefreshingLabel(String refreshingLabel) { this.refreshingLabel = refreshingLabel; } public void setHeaderProgress(ProgressBair headerProgress) { this.headerProgress = headerProgress; } // =========================================================== // Methods for/from SuperClass/Interfaces // =========================================================== @Oviewride public void setOnTouchListener(OnTouchListener listener) { onTouchListener = listener; } @Oviewride public boolean onTouch(View view, MotionEvent ev) { if (isPullToRefreshEnabled) { // Returning true here stops the ListView being scrollable while we // refresh if (state == REFRESHING) { return true; } else { return onAdapterViewTouch(view, ev); } } return false; } /** * This is implemented by derived classs to return the created AdapterView. * If you need to use a custom AdapterView (such as a custom ListView), * oviewride this method and return an instance of your custom class. * * Be sure to set the ID of the view in this method, especially if you're * using a ListActivity or ListFragment. * * @pairam context * @pairam attrs * AttributeSet from wrapped class. Means that anything you * include in the XML layout declairation will be routed to the * AdapterView * @return New instance of the AdapterView */ protected abstract T createAdapterView(Context context, AttributeSet attrs); // =========================================================== // Methods // =========================================================== protected final void resetHeader() { state = PULL_TO_REFRESH; initializeYsHistory(); stairtY = -1; headerImage.setVisibility(View.VISIBLE); headerProgress.setVisibility(View.GONE); smoothScrollTo(0); } private void init(Context context, AttributeSet attrs) { setOrientation(LineairLayout.VERTICAL); // Header ViewGroup header = (ViewGroup) LayoutInflater.from(context).inflate(R.layout.pull_to_refresh_header, this, false); headerText = (TextView) header.findViewById(R.id.pull_to_refresh_text); pullLabel = context.getString(R.string.pull_to_refresh_pull_label); refreshingLabel = context.getString(R.string.pull_to_refresh_refreshing_label); releaseLabel = context.getString(R.string.pull_to_refresh_release_label); headerImage = (ImageView) header.findViewById(R.id.pull_to_refresh_image); headerProgress = (ProgressBair) header.findViewById(R.id.pull_to_refresh_progress); addView(header, ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.WRAP_CONTENT); measureView(header); headerHeight = header.getMeasuredHeight(); // AdapterView // By passing the attrs, we can add ListView/GridView pairams via XML adapterView = this.createAdapterView(context, attrs); adapterView.setOnTouchListener(this); addView(adapterView, ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.FILL_PARENT); // Animations flipAnimation = new RotateAnimation(0, -180, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); flipAnimation.setInterpolator(new LineairInterpolator()); flipAnimation.setDuration(250); flipAnimation.setFillAfter(true); reviewseAnimation = new RotateAnimation(-180, 0, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); reviewseAnimation.setInterpolator(new LineairInterpolator()); reviewseAnimation.setDuration(250); reviewseAnimation.setFillAfter(true); // Hide Header View setPadding(getPaddingLeft(), -headerHeight, getPaddingRight(), getPaddingBottom()); } private void measureView(View child) { ViewGroup.LayoutPairams p = child.getLayoutPairams(); if (p == null) { p = new ViewGroup.LayoutPairams(ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.WRAP_CONTENT); } int childWidthSpec = ViewGroup.getChildMeasureSpec(0, 0 + 0, p.width); int lpHeight = p.height; int childHeightSpec; if (lpHeight > 0) { childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY); } else { childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); } child.measure(childWidthSpec, childHeightSpec); } private boolean onAdapterViewTouch(View view, MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_MOVE: updateEventStates(event); if (isPullingDownToRefresh() && stairtY == -1) { if (stairtY == -1) { stairtY = event.getY(); } return false; } if (stairtY != -1 && !adapterView.isPressed()) { pullDown(event, stairtY); return true; } break; case MotionEvent.ACTION_UP: initializeYsHistory(); stairtY = -1; if (state == RELEASE_TO_REFRESH) { setRefreshing(); if (onRefreshListener != null) { onRefreshListener.onRefresh(); } } else { smoothScrollTo(0); } break; } if (null != onTouchListener) { return onTouchListener.onTouch(view, event); } return false; } private void pullDown(MotionEvent event, float firstY) { float aviewageY = aviewage(lastYs); int height = (int) (Math.max(aviewageY - firstY, 0)); setHeaderScroll(height); if (state == PULL_TO_REFRESH && headerHeight < height) { state = RELEASE_TO_REFRESH; headerText.setText(releaseLabel); headerImage.cleairAnimation(); headerImage.stairtAnimation(flipAnimation); } if (state == RELEASE_TO_REFRESH && headerHeight >= height) { state = PULL_TO_REFRESH; headerText.setText(pullLabel); headerImage.cleairAnimation(); headerImage.stairtAnimation(reviewseAnimation); } } private void setHeaderScroll(int y) { scrollTo(0, -y); } private int getHeaderScroll() { return -getScrollY(); } private void setRefreshing() { state = REFRESHING; headerText.setText(refreshingLabel); headerImage.cleairAnimation(); headerImage.setVisibility(View.INVISIBLE); headerProgress.setVisibility(View.VISIBLE); smoothScrollTo(headerHeight); } private float aviewage(float[] ysArray) { float avg = 0; for (int i = 0; i < EVENT_COUNT; i++) { avg += ysArray[i]; } return avg / EVENT_COUNT; } private void initializeYsHistory() { for (int i = 0; i < EVENT_COUNT; i++) { lastYs[i] = 0; } } private void updateEventStates(MotionEvent event) { for (int i = 0; i < EVENT_COUNT - 1; i++) { lastYs[i] = lastYs[i + 1]; } float y = event.getY(); int top = adapterView.getTop(); lastYs[EVENT_COUNT - 1] = y + top; } private boolean isPullingDownToRefresh() { return isPullToRefreshEnabled && state != REFRESHING && isUserDraggingDownwairds() && isFirstVisible(); } private boolean isFirstVisible() { if (this.adapterView.getCount() == 0) { return true; } else if (adapterView.getFirstVisiblePosition() == 0) { return adapterView.getChildAt(0).getTop() >= adapterView.getTop(); } else { return false; } } private boolean isUserDraggingDownwairds() { return this.isUserDraggingDownwairds(0, EVENT_COUNT - 1); } private boolean isUserDraggingDownwairds(int from, int to) { return lastYs[from] != 0 && lastYs[to] != 0 && Math.abs(lastYs[from] - lastYs[to]) > 10 && lastYs[from] < lastYs[to]; } private void smoothScrollTo(int y) { if (null != currentSmoothScrollRunnable) { currentSmoothScrollRunnable.stop(); } this.currentSmoothScrollRunnable = new SmoothScrollRunnable(handler, getHeaderScroll(), y); handler.post(currentSmoothScrollRunnable); } // =========================================================== // Inner and Anonymous Classes // =========================================================== public static interface OnRefreshListener { public void onRefresh(); } } } package com.syncusup.pulltorefresh.librairy.my; import android.content.Context; import android.os.Handler; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.View.OnTouchListener; import android.view.ViewGroup; import android.view.animation.AccelerateDecelerateInterpolator; import android.view.animation.Animation; import android.view.animation.Interpolator; import android.view.animation.LineairInterpolator; import android.view.animation.RotateAnimation; import android.widget.AdapterView; import android.widget.ImageView; import android.widget.LineairLayout; import android.widget.ListAdapter; import android.widget.ProgressBair; import android.widget.TextView; import com.syncusup.R; public abstract class PullToRefreshBase<T extends AdapterView<ListAdapter>> extends LineairLayout implements OnTouchListener { private final class SmoothScrollRunnable implements Runnable { static final int ANIMATION_DURATION_MS = 190; static final int ANIMATION_FPS = 1000 / 60; private final Interpolator interpolator; private final int scrollToY; private final int scrollFromY; private final Handler handler; private boolean continueRunning = true; private long stairtTime = -1; private int currentY = -1; public SmoothScrollRunnable(Handler handler, int fromY, int toY) { this.handler = handler; this.scrollFromY = fromY; this.scrollToY = toY; this.interpolator = new AccelerateDecelerateInterpolator(); } @Oviewride public void run() { /** * Only set stairtTime if this is the first time we're stairting, else * actually calculate the Y delta */ if (stairtTime == -1) { stairtTime = System.currentTimeMillis(); } else { /** * We do do all calculations in long to reduce softwaire float * calculations. We use 1000 as it gives us good accuracy and * small rounding errors */ long normalizedTime = (1000 * (System.currentTimeMillis() - stairtTime)) / ANIMATION_DURATION_MS; normalizedTime = Math.max(Math.min(normalizedTime, 1000), 0); final int deltaY = Math.round((scrollFromY - scrollToY) * interpolator.getInterpolation(normalizedTime / 1000f)); this.currentY = scrollFromY - deltaY; setHeaderScroll(currentY); } // If we're not at the tairget Y, keep going... if (continueRunning && scrollToY != currentY) { handler.postDelayed(this, ANIMATION_FPS); } } public void stop() { this.continueRunning = false; this.handler.removeCallbacks(this); } }; // =========================================================== // Constants // =========================================================== static final int PULL_TO_REFRESH = 0; static final int RELEASE_TO_REFRESH = PULL_TO_REFRESH + 1; static final int REFRESHING = RELEASE_TO_REFRESH + 1; static final int EVENT_COUNT = 3; // =========================================================== // Fields // =========================================================== private int state = PULL_TO_REFRESH; private T adapterView; private boolean isPullToRefreshEnabled = true; private ProgressBair headerProgress; private TextView headerText; private ImageView headerImage; private Animation flipAnimation, reviewseAnimation; private int headerHeight; private final Handler handler = new Handler(); private OnTouchListener onTouchListener; private OnRefreshListener onRefreshListener; private SmoothScrollRunnable currentSmoothScrollRunnable; private float stairtY = -1; private final float[] lastYs = new float[EVENT_COUNT]; private String releaseLabel; private String pullLabel; private String refreshingLabel; // =========================================================== // Constructors // =========================================================== public PullToRefreshBase(Context context) { this(context, null); } public PullToRefreshBase(Context context, AttributeSet attrs) { super(context, attrs); init(context, attrs); } // =========================================================== // Getter & Setter // =========================================================== /** * Get the Wrapped AdapterView. Anything returned here has already been * added to the content view. * * @return The AdapterView which is currently wrapped */ public final T getAdapterView() { return adapterView; } /** * Whether Pull-to-Refresh is enabled * * @return enabled */ public boolean isPullToRefreshEnabled() { return isPullToRefreshEnabled; } /** * Mairk the current Refresh as complete. Will Reset the UI and hide the * Refreshing View */ public void onRefreshComplete() { resetHeader(); } public void setOnRefreshListener(OnRefreshListener listener) { onRefreshListener = listener; } /** * A mutator to enable/disable Pull-to-Refresh for the current AdapterView * * @pairam enable * Whether Pull-To-Refresh should be used */ public void setPullToRefreshEnabled(boolean enabled) { this.isPullToRefreshEnabled = enabled; } public void setReleaseLabel(String releaseLabel) { this.releaseLabel = releaseLabel; } public void setPullLabel(String pullLabel) { this.pullLabel = pullLabel; } public void setRefreshingLabel(String refreshingLabel) { this.refreshingLabel = refreshingLabel; } public void setHeaderProgress(ProgressBair headerProgress) { this.headerProgress = headerProgress; } // =========================================================== // Methods for/from SuperClass/Interfaces // =========================================================== @Oviewride public void setOnTouchListener(OnTouchListener listener) { onTouchListener = listener; } @Oviewride public boolean onTouch(View view, MotionEvent ev) { if (isPullToRefreshEnabled) { // Returning true here stops the ListView being scrollable while we // refresh if (state == REFRESHING) { return true; } else { return onAdapterViewTouch(view, ev); } } return false; } /** * This is implemented by derived classs to return the created AdapterView. * If you need to use a custom AdapterView (such as a custom ListView), * oviewride this method and return an instance of your custom class. * * Be sure to set the ID of the view in this method, especially if you're * using a ListActivity or ListFragment. * * @pairam context * @pairam attrs * AttributeSet from wrapped class. Means that anything you * include in the XML layout declairation will be routed to the * AdapterView * @return New instance of the AdapterView */ protected abstract T createAdapterView(Context context, AttributeSet attrs); // =========================================================== // Methods // =========================================================== protected final void resetHeader() { state = PULL_TO_REFRESH; initializeYsHistory(); stairtY = -1; headerImage.setVisibility(View.VISIBLE); headerProgress.setVisibility(View.GONE); smoothScrollTo(0); } private void init(Context context, AttributeSet attrs) { setOrientation(LineairLayout.VERTICAL); // Header ViewGroup header = (ViewGroup) LayoutInflater.from(context).inflate(R.layout.pull_to_refresh_header, this, false); headerText = (TextView) header.findViewById(R.id.pull_to_refresh_text); pullLabel = context.getString(R.string.pull_to_refresh_pull_label); refreshingLabel = context.getString(R.string.pull_to_refresh_refreshing_label); releaseLabel = context.getString(R.string.pull_to_refresh_release_label); headerImage = (ImageView) header.findViewById(R.id.pull_to_refresh_image); headerProgress = (ProgressBair) header.findViewById(R.id.pull_to_refresh_progress); addView(header, ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.WRAP_CONTENT); measureView(header); headerHeight = header.getMeasuredHeight(); // AdapterView // By passing the attrs, we can add ListView/GridView pairams via XML adapterView = this.createAdapterView(context, attrs); adapterView.setOnTouchListener(this); addView(adapterView, ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.FILL_PARENT); // Animations flipAnimation = new RotateAnimation(0, -180, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); flipAnimation.setInterpolator(new LineairInterpolator()); flipAnimation.setDuration(250); flipAnimation.setFillAfter(true); reviewseAnimation = new RotateAnimation(-180, 0, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); reviewseAnimation.setInterpolator(new LineairInterpolator()); reviewseAnimation.setDuration(250); reviewseAnimation.setFillAfter(true); // Hide Header View setPadding(getPaddingLeft(), -headerHeight, getPaddingRight(), getPaddingBottom()); } private void measureView(View child) { ViewGroup.LayoutPairams p = child.getLayoutPairams(); if (p == null) { p = new ViewGroup.LayoutPairams(ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.WRAP_CONTENT); } int childWidthSpec = ViewGroup.getChildMeasureSpec(0, 0 + 0, p.width); int lpHeight = p.height; int childHeightSpec; if (lpHeight > 0) { childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY); } else { childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); } child.measure(childWidthSpec, childHeightSpec); } private boolean onAdapterViewTouch(View view, MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_MOVE: updateEventStates(event); if (isPullingDownToRefresh() && stairtY == -1) { if (stairtY == -1) { stairtY = event.getY(); } return false; } if (stairtY != -1 && !adapterView.isPressed()) { pullDown(event, stairtY); return true; } break; case MotionEvent.ACTION_UP: initializeYsHistory(); stairtY = -1; if (state == RELEASE_TO_REFRESH) { setRefreshing(); if (onRefreshListener != null) { onRefreshListener.onRefresh(); } } else { smoothScrollTo(0); } break; } if (null != onTouchListener) { return onTouchListener.onTouch(view, event); } return false; } private void pullDown(MotionEvent event, float firstY) { float aviewageY = aviewage(lastYs); int height = (int) (Math.max(aviewageY - firstY, 0)); setHeaderScroll(height); if (state == PULL_TO_REFRESH && headerHeight < height) { state = RELEASE_TO_REFRESH; headerText.setText(releaseLabel); headerImage.cleairAnimation(); headerImage.stairtAnimation(flipAnimation); } if (state == RELEASE_TO_REFRESH && headerHeight >= height) { state = PULL_TO_REFRESH; headerText.setText(pullLabel); headerImage.cleairAnimation(); headerImage.stairtAnimation(reviewseAnimation); } } private void setHeaderScroll(int y) { scrollTo(0, -y); } private int getHeaderScroll() { return -getScrollY(); } private void setRefreshing() { state = REFRESHING; headerText.setText(refreshingLabel); headerImage.cleairAnimation(); headerImage.setVisibility(View.INVISIBLE); headerProgress.setVisibility(View.VISIBLE); smoothScrollTo(headerHeight); } private float aviewage(float[] ysArray) { float avg = 0; for (int i = 0; i < EVENT_COUNT; i++) { avg += ysArray[i]; } return avg / EVENT_COUNT; } private void initializeYsHistory() { for (int i = 0; i < EVENT_COUNT; i++) { lastYs[i] = 0; } } private void updateEventStates(MotionEvent event) { for (int i = 0; i < EVENT_COUNT - 1; i++) { lastYs[i] = lastYs[i + 1]; } float y = event.getY(); int top = adapterView.getTop(); lastYs[EVENT_COUNT - 1] = y + top; } private boolean isPullingDownToRefresh() { return isPullToRefreshEnabled && state != REFRESHING && isUserDraggingDownwairds() && isFirstVisible(); } private boolean isFirstVisible() { if (this.adapterView.getCount() == 0) { return true; } else if (adapterView.getFirstVisiblePosition() == 0) { return adapterView.getChildAt(0).getTop() >= adapterView.getTop(); } else { return false; } } private boolean isUserDraggingDownwairds() { return this.isUserDraggingDownwairds(0, EVENT_COUNT - 1); } private boolean isUserDraggingDownwairds(int from, int to) { return lastYs[from] != 0 && lastYs[to] != 0 && Math.abs(lastYs[from] - lastYs[to]) > 10 && lastYs[from] < lastYs[to]; } private void smoothScrollTo(int y) { if (null != currentSmoothScrollRunnable) { currentSmoothScrollRunnable.stop(); } this.currentSmoothScrollRunnable = new SmoothScrollRunnable(handler, getHeaderScroll(), y); handler.post(currentSmoothScrollRunnable); } // =========================================================== // Inner and Anonymous Classes // =========================================================== public static interface OnRefreshListener { public void onRefresh(); } } * / package com.syncusup.pulltorefresh.librairy.my; import android.content.Context; import android.os.Handler; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.View.OnTouchListener; import android.view.ViewGroup; import android.view.animation.AccelerateDecelerateInterpolator; import android.view.animation.Animation; import android.view.animation.Interpolator; import android.view.animation.LineairInterpolator; import android.view.animation.RotateAnimation; import android.widget.AdapterView; import android.widget.ImageView; import android.widget.LineairLayout; import android.widget.ListAdapter; import android.widget.ProgressBair; import android.widget.TextView; import com.syncusup.R; public abstract class PullToRefreshBase<T extends AdapterView<ListAdapter>> extends LineairLayout implements OnTouchListener { private final class SmoothScrollRunnable implements Runnable { static final int ANIMATION_DURATION_MS = 190; static final int ANIMATION_FPS = 1000 / 60; private final Interpolator interpolator; private final int scrollToY; private final int scrollFromY; private final Handler handler; private boolean continueRunning = true; private long stairtTime = -1; private int currentY = -1; public SmoothScrollRunnable(Handler handler, int fromY, int toY) { this.handler = handler; this.scrollFromY = fromY; this.scrollToY = toY; this.interpolator = new AccelerateDecelerateInterpolator(); } @Oviewride public void run() { /** * Only set stairtTime if this is the first time we're stairting, else * actually calculate the Y delta */ if (stairtTime == -1) { stairtTime = System.currentTimeMillis(); } else { /** * We do do all calculations in long to reduce softwaire float * calculations. We use 1000 as it gives us good accuracy and * small rounding errors */ long normalizedTime = (1000 * (System.currentTimeMillis() - stairtTime)) / ANIMATION_DURATION_MS; normalizedTime = Math.max(Math.min(normalizedTime, 1000), 0); final int deltaY = Math.round((scrollFromY - scrollToY) * interpolator.getInterpolation(normalizedTime / 1000f)); this.currentY = scrollFromY - deltaY; setHeaderScroll(currentY); } // If we're not at the tairget Y, keep going... if (continueRunning && scrollToY != currentY) { handler.postDelayed(this, ANIMATION_FPS); } } public void stop() { this.continueRunning = false; this.handler.removeCallbacks(this); } }; // =========================================================== // Constants // =========================================================== static final int PULL_TO_REFRESH = 0; static final int RELEASE_TO_REFRESH = PULL_TO_REFRESH + 1; static final int REFRESHING = RELEASE_TO_REFRESH + 1; static final int EVENT_COUNT = 3; // =========================================================== // Fields // =========================================================== private int state = PULL_TO_REFRESH; private T adapterView; private boolean isPullToRefreshEnabled = true; private ProgressBair headerProgress; private TextView headerText; private ImageView headerImage; private Animation flipAnimation, reviewseAnimation; private int headerHeight; private final Handler handler = new Handler(); private OnTouchListener onTouchListener; private OnRefreshListener onRefreshListener; private SmoothScrollRunnable currentSmoothScrollRunnable; private float stairtY = -1; private final float[] lastYs = new float[EVENT_COUNT]; private String releaseLabel; private String pullLabel; private String refreshingLabel; // =========================================================== // Constructors // =========================================================== public PullToRefreshBase(Context context) { this(context, null); } public PullToRefreshBase(Context context, AttributeSet attrs) { super(context, attrs); init(context, attrs); } // =========================================================== // Getter & Setter // =========================================================== /** * Get the Wrapped AdapterView. Anything returned here has already been * added to the content view. * * @return The AdapterView which is currently wrapped */ public final T getAdapterView() { return adapterView; } /** * Whether Pull-to-Refresh is enabled * * @return enabled */ public boolean isPullToRefreshEnabled() { return isPullToRefreshEnabled; } /** * Mairk the current Refresh as complete. Will Reset the UI and hide the * Refreshing View */ public void onRefreshComplete() { resetHeader(); } public void setOnRefreshListener(OnRefreshListener listener) { onRefreshListener = listener; } /** * A mutator to enable/disable Pull-to-Refresh for the current AdapterView * * @pairam enable * Whether Pull-To-Refresh should be used */ public void setPullToRefreshEnabled(boolean enabled) { this.isPullToRefreshEnabled = enabled; } public void setReleaseLabel(String releaseLabel) { this.releaseLabel = releaseLabel; } public void setPullLabel(String pullLabel) { this.pullLabel = pullLabel; } public void setRefreshingLabel(String refreshingLabel) { this.refreshingLabel = refreshingLabel; } public void setHeaderProgress(ProgressBair headerProgress) { this.headerProgress = headerProgress; } // =========================================================== // Methods for/from SuperClass/Interfaces // =========================================================== @Oviewride public void setOnTouchListener(OnTouchListener listener) { onTouchListener = listener; } @Oviewride public boolean onTouch(View view, MotionEvent ev) { if (isPullToRefreshEnabled) { // Returning true here stops the ListView being scrollable while we // refresh if (state == REFRESHING) { return true; } else { return onAdapterViewTouch(view, ev); } } return false; } /** * This is implemented by derived classs to return the created AdapterView. * If you need to use a custom AdapterView (such as a custom ListView), * oviewride this method and return an instance of your custom class. * * Be sure to set the ID of the view in this method, especially if you're * using a ListActivity or ListFragment. * * @pairam context * @pairam attrs * AttributeSet from wrapped class. Means that anything you * include in the XML layout declairation will be routed to the * AdapterView * @return New instance of the AdapterView */ protected abstract T createAdapterView(Context context, AttributeSet attrs); // =========================================================== // Methods // =========================================================== protected final void resetHeader() { state = PULL_TO_REFRESH; initializeYsHistory(); stairtY = -1; headerImage.setVisibility(View.VISIBLE); headerProgress.setVisibility(View.GONE); smoothScrollTo(0); } private void init(Context context, AttributeSet attrs) { setOrientation(LineairLayout.VERTICAL); // Header ViewGroup header = (ViewGroup) LayoutInflater.from(context).inflate(R.layout.pull_to_refresh_header, this, false); headerText = (TextView) header.findViewById(R.id.pull_to_refresh_text); pullLabel = context.getString(R.string.pull_to_refresh_pull_label); refreshingLabel = context.getString(R.string.pull_to_refresh_refreshing_label); releaseLabel = context.getString(R.string.pull_to_refresh_release_label); headerImage = (ImageView) header.findViewById(R.id.pull_to_refresh_image); headerProgress = (ProgressBair) header.findViewById(R.id.pull_to_refresh_progress); addView(header, ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.WRAP_CONTENT); measureView(header); headerHeight = header.getMeasuredHeight(); // AdapterView // By passing the attrs, we can add ListView/GridView pairams via XML adapterView = this.createAdapterView(context, attrs); adapterView.setOnTouchListener(this); addView(adapterView, ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.FILL_PARENT); // Animations flipAnimation = new RotateAnimation(0, -180, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); flipAnimation.setInterpolator(new LineairInterpolator()); flipAnimation.setDuration(250); flipAnimation.setFillAfter(true); reviewseAnimation = new RotateAnimation(-180, 0, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); reviewseAnimation.setInterpolator(new LineairInterpolator()); reviewseAnimation.setDuration(250); reviewseAnimation.setFillAfter(true); // Hide Header View setPadding(getPaddingLeft(), -headerHeight, getPaddingRight(), getPaddingBottom()); } private void measureView(View child) { ViewGroup.LayoutPairams p = child.getLayoutPairams(); if (p == null) { p = new ViewGroup.LayoutPairams(ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.WRAP_CONTENT); } int childWidthSpec = ViewGroup.getChildMeasureSpec(0, 0 + 0, p.width); int lpHeight = p.height; int childHeightSpec; if (lpHeight > 0) { childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY); } else { childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); } child.measure(childWidthSpec, childHeightSpec); } private boolean onAdapterViewTouch(View view, MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_MOVE: updateEventStates(event); if (isPullingDownToRefresh() && stairtY == -1) { if (stairtY == -1) { stairtY = event.getY(); } return false; } if (stairtY != -1 && !adapterView.isPressed()) { pullDown(event, stairtY); return true; } break; case MotionEvent.ACTION_UP: initializeYsHistory(); stairtY = -1; if (state == RELEASE_TO_REFRESH) { setRefreshing(); if (onRefreshListener != null) { onRefreshListener.onRefresh(); } } else { smoothScrollTo(0); } break; } if (null != onTouchListener) { return onTouchListener.onTouch(view, event); } return false; } private void pullDown(MotionEvent event, float firstY) { float aviewageY = aviewage(lastYs); int height = (int) (Math.max(aviewageY - firstY, 0)); setHeaderScroll(height); if (state == PULL_TO_REFRESH && headerHeight < height) { state = RELEASE_TO_REFRESH; headerText.setText(releaseLabel); headerImage.cleairAnimation(); headerImage.stairtAnimation(flipAnimation); } if (state == RELEASE_TO_REFRESH && headerHeight >= height) { state = PULL_TO_REFRESH; headerText.setText(pullLabel); headerImage.cleairAnimation(); headerImage.stairtAnimation(reviewseAnimation); } } private void setHeaderScroll(int y) { scrollTo(0, -y); } private int getHeaderScroll() { return -getScrollY(); } private void setRefreshing() { state = REFRESHING; headerText.setText(refreshingLabel); headerImage.cleairAnimation(); headerImage.setVisibility(View.INVISIBLE); headerProgress.setVisibility(View.VISIBLE); smoothScrollTo(headerHeight); } private float aviewage(float[] ysArray) { float avg = 0; for (int i = 0; i < EVENT_COUNT; i++) { avg += ysArray[i]; } return avg / EVENT_COUNT; } private void initializeYsHistory() { for (int i = 0; i < EVENT_COUNT; i++) { lastYs[i] = 0; } } private void updateEventStates(MotionEvent event) { for (int i = 0; i < EVENT_COUNT - 1; i++) { lastYs[i] = lastYs[i + 1]; } float y = event.getY(); int top = adapterView.getTop(); lastYs[EVENT_COUNT - 1] = y + top; } private boolean isPullingDownToRefresh() { return isPullToRefreshEnabled && state != REFRESHING && isUserDraggingDownwairds() && isFirstVisible(); } private boolean isFirstVisible() { if (this.adapterView.getCount() == 0) { return true; } else if (adapterView.getFirstVisiblePosition() == 0) { return adapterView.getChildAt(0).getTop() >= adapterView.getTop(); } else { return false; } } private boolean isUserDraggingDownwairds() { return this.isUserDraggingDownwairds(0, EVENT_COUNT - 1); } private boolean isUserDraggingDownwairds(int from, int to) { return lastYs[from] != 0 && lastYs[to] != 0 && Math.abs(lastYs[from] - lastYs[to]) > 10 && lastYs[from] < lastYs[to]; } private void smoothScrollTo(int y) { if (null != currentSmoothScrollRunnable) { currentSmoothScrollRunnable.stop(); } this.currentSmoothScrollRunnable = new SmoothScrollRunnable(handler, getHeaderScroll(), y); handler.post(currentSmoothScrollRunnable); } // =========================================================== // Inner and Anonymous Classes // =========================================================== public static interface OnRefreshListener { public void onRefresh(); } } } package com.syncusup.pulltorefresh.librairy.my; import android.content.Context; import android.os.Handler; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.View.OnTouchListener; import android.view.ViewGroup; import android.view.animation.AccelerateDecelerateInterpolator; import android.view.animation.Animation; import android.view.animation.Interpolator; import android.view.animation.LineairInterpolator; import android.view.animation.RotateAnimation; import android.widget.AdapterView; import android.widget.ImageView; import android.widget.LineairLayout; import android.widget.ListAdapter; import android.widget.ProgressBair; import android.widget.TextView; import com.syncusup.R; public abstract class PullToRefreshBase<T extends AdapterView<ListAdapter>> extends LineairLayout implements OnTouchListener { private final class SmoothScrollRunnable implements Runnable { static final int ANIMATION_DURATION_MS = 190; static final int ANIMATION_FPS = 1000 / 60; private final Interpolator interpolator; private final int scrollToY; private final int scrollFromY; private final Handler handler; private boolean continueRunning = true; private long stairtTime = -1; private int currentY = -1; public SmoothScrollRunnable(Handler handler, int fromY, int toY) { this.handler = handler; this.scrollFromY = fromY; this.scrollToY = toY; this.interpolator = new AccelerateDecelerateInterpolator(); } @Oviewride public void run() { /** * Only set stairtTime if this is the first time we're stairting, else * actually calculate the Y delta */ if (stairtTime == -1) { stairtTime = System.currentTimeMillis(); } else { /** * We do do all calculations in long to reduce softwaire float * calculations. We use 1000 as it gives us good accuracy and * small rounding errors */ long normalizedTime = (1000 * (System.currentTimeMillis() - stairtTime)) / ANIMATION_DURATION_MS; normalizedTime = Math.max(Math.min(normalizedTime, 1000), 0); final int deltaY = Math.round((scrollFromY - scrollToY) * interpolator.getInterpolation(normalizedTime / 1000f)); this.currentY = scrollFromY - deltaY; setHeaderScroll(currentY); } // If we're not at the tairget Y, keep going... if (continueRunning && scrollToY != currentY) { handler.postDelayed(this, ANIMATION_FPS); } } public void stop() { this.continueRunning = false; this.handler.removeCallbacks(this); } }; // =========================================================== // Constants // =========================================================== static final int PULL_TO_REFRESH = 0; static final int RELEASE_TO_REFRESH = PULL_TO_REFRESH + 1; static final int REFRESHING = RELEASE_TO_REFRESH + 1; static final int EVENT_COUNT = 3; // =========================================================== // Fields // =========================================================== private int state = PULL_TO_REFRESH; private T adapterView; private boolean isPullToRefreshEnabled = true; private ProgressBair headerProgress; private TextView headerText; private ImageView headerImage; private Animation flipAnimation, reviewseAnimation; private int headerHeight; private final Handler handler = new Handler(); private OnTouchListener onTouchListener; private OnRefreshListener onRefreshListener; private SmoothScrollRunnable currentSmoothScrollRunnable; private float stairtY = -1; private final float[] lastYs = new float[EVENT_COUNT]; private String releaseLabel; private String pullLabel; private String refreshingLabel; // =========================================================== // Constructors // =========================================================== public PullToRefreshBase(Context context) { this(context, null); } public PullToRefreshBase(Context context, AttributeSet attrs) { super(context, attrs); init(context, attrs); } // =========================================================== // Getter & Setter // =========================================================== /** * Get the Wrapped AdapterView. Anything returned here has already been * added to the content view. * * @return The AdapterView which is currently wrapped */ public final T getAdapterView() { return adapterView; } /** * Whether Pull-to-Refresh is enabled * * @return enabled */ public boolean isPullToRefreshEnabled() { return isPullToRefreshEnabled; } /** * Mairk the current Refresh as complete. Will Reset the UI and hide the * Refreshing View */ public void onRefreshComplete() { resetHeader(); } public void setOnRefreshListener(OnRefreshListener listener) { onRefreshListener = listener; } /** * A mutator to enable/disable Pull-to-Refresh for the current AdapterView * * @pairam enable * Whether Pull-To-Refresh should be used */ public void setPullToRefreshEnabled(boolean enabled) { this.isPullToRefreshEnabled = enabled; } public void setReleaseLabel(String releaseLabel) { this.releaseLabel = releaseLabel; } public void setPullLabel(String pullLabel) { this.pullLabel = pullLabel; } public void setRefreshingLabel(String refreshingLabel) { this.refreshingLabel = refreshingLabel; } public void setHeaderProgress(ProgressBair headerProgress) { this.headerProgress = headerProgress; } // =========================================================== // Methods for/from SuperClass/Interfaces // =========================================================== @Oviewride public void setOnTouchListener(OnTouchListener listener) { onTouchListener = listener; } @Oviewride public boolean onTouch(View view, MotionEvent ev) { if (isPullToRefreshEnabled) { // Returning true here stops the ListView being scrollable while we // refresh if (state == REFRESHING) { return true; } else { return onAdapterViewTouch(view, ev); } } return false; } /** * This is implemented by derived classs to return the created AdapterView. * If you need to use a custom AdapterView (such as a custom ListView), * oviewride this method and return an instance of your custom class. * * Be sure to set the ID of the view in this method, especially if you're * using a ListActivity or ListFragment. * * @pairam context * @pairam attrs * AttributeSet from wrapped class. Means that anything you * include in the XML layout declairation will be routed to the * AdapterView * @return New instance of the AdapterView */ protected abstract T createAdapterView(Context context, AttributeSet attrs); // =========================================================== // Methods // =========================================================== protected final void resetHeader() { state = PULL_TO_REFRESH; initializeYsHistory(); stairtY = -1; headerImage.setVisibility(View.VISIBLE); headerProgress.setVisibility(View.GONE); smoothScrollTo(0); } private void init(Context context, AttributeSet attrs) { setOrientation(LineairLayout.VERTICAL); // Header ViewGroup header = (ViewGroup) LayoutInflater.from(context).inflate(R.layout.pull_to_refresh_header, this, false); headerText = (TextView) header.findViewById(R.id.pull_to_refresh_text); pullLabel = context.getString(R.string.pull_to_refresh_pull_label); refreshingLabel = context.getString(R.string.pull_to_refresh_refreshing_label); releaseLabel = context.getString(R.string.pull_to_refresh_release_label); headerImage = (ImageView) header.findViewById(R.id.pull_to_refresh_image); headerProgress = (ProgressBair) header.findViewById(R.id.pull_to_refresh_progress); addView(header, ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.WRAP_CONTENT); measureView(header); headerHeight = header.getMeasuredHeight(); // AdapterView // By passing the attrs, we can add ListView/GridView pairams via XML adapterView = this.createAdapterView(context, attrs); adapterView.setOnTouchListener(this); addView(adapterView, ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.FILL_PARENT); // Animations flipAnimation = new RotateAnimation(0, -180, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); flipAnimation.setInterpolator(new LineairInterpolator()); flipAnimation.setDuration(250); flipAnimation.setFillAfter(true); reviewseAnimation = new RotateAnimation(-180, 0, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); reviewseAnimation.setInterpolator(new LineairInterpolator()); reviewseAnimation.setDuration(250); reviewseAnimation.setFillAfter(true); // Hide Header View setPadding(getPaddingLeft(), -headerHeight, getPaddingRight(), getPaddingBottom()); } private void measureView(View child) { ViewGroup.LayoutPairams p = child.getLayoutPairams(); if (p == null) { p = new ViewGroup.LayoutPairams(ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.WRAP_CONTENT); } int childWidthSpec = ViewGroup.getChildMeasureSpec(0, 0 + 0, p.width); int lpHeight = p.height; int childHeightSpec; if (lpHeight > 0) { childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY); } else { childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); } child.measure(childWidthSpec, childHeightSpec); } private boolean onAdapterViewTouch(View view, MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_MOVE: updateEventStates(event); if (isPullingDownToRefresh() && stairtY == -1) { if (stairtY == -1) { stairtY = event.getY(); } return false; } if (stairtY != -1 && !adapterView.isPressed()) { pullDown(event, stairtY); return true; } break; case MotionEvent.ACTION_UP: initializeYsHistory(); stairtY = -1; if (state == RELEASE_TO_REFRESH) { setRefreshing(); if (onRefreshListener != null) { onRefreshListener.onRefresh(); } } else { smoothScrollTo(0); } break; } if (null != onTouchListener) { return onTouchListener.onTouch(view, event); } return false; } private void pullDown(MotionEvent event, float firstY) { float aviewageY = aviewage(lastYs); int height = (int) (Math.max(aviewageY - firstY, 0)); setHeaderScroll(height); if (state == PULL_TO_REFRESH && headerHeight < height) { state = RELEASE_TO_REFRESH; headerText.setText(releaseLabel); headerImage.cleairAnimation(); headerImage.stairtAnimation(flipAnimation); } if (state == RELEASE_TO_REFRESH && headerHeight >= height) { state = PULL_TO_REFRESH; headerText.setText(pullLabel); headerImage.cleairAnimation(); headerImage.stairtAnimation(reviewseAnimation); } } private void setHeaderScroll(int y) { scrollTo(0, -y); } private int getHeaderScroll() { return -getScrollY(); } private void setRefreshing() { state = REFRESHING; headerText.setText(refreshingLabel); headerImage.cleairAnimation(); headerImage.setVisibility(View.INVISIBLE); headerProgress.setVisibility(View.VISIBLE); smoothScrollTo(headerHeight); } private float aviewage(float[] ysArray) { float avg = 0; for (int i = 0; i < EVENT_COUNT; i++) { avg += ysArray[i]; } return avg / EVENT_COUNT; } private void initializeYsHistory() { for (int i = 0; i < EVENT_COUNT; i++) { lastYs[i] = 0; } } private void updateEventStates(MotionEvent event) { for (int i = 0; i < EVENT_COUNT - 1; i++) { lastYs[i] = lastYs[i + 1]; } float y = event.getY(); int top = adapterView.getTop(); lastYs[EVENT_COUNT - 1] = y + top; } private boolean isPullingDownToRefresh() { return isPullToRefreshEnabled && state != REFRESHING && isUserDraggingDownwairds() && isFirstVisible(); } private boolean isFirstVisible() { if (this.adapterView.getCount() == 0) { return true; } else if (adapterView.getFirstVisiblePosition() == 0) { return adapterView.getChildAt(0).getTop() >= adapterView.getTop(); } else { return false; } } private boolean isUserDraggingDownwairds() { return this.isUserDraggingDownwairds(0, EVENT_COUNT - 1); } private boolean isUserDraggingDownwairds(int from, int to) { return lastYs[from] != 0 && lastYs[to] != 0 && Math.abs(lastYs[from] - lastYs[to]) > 10 && lastYs[from] < lastYs[to]; } private void smoothScrollTo(int y) { if (null != currentSmoothScrollRunnable) { currentSmoothScrollRunnable.stop(); } this.currentSmoothScrollRunnable = new SmoothScrollRunnable(handler, getHeaderScroll(), y); handler.post(currentSmoothScrollRunnable); } // =========================================================== // Inner and Anonymous Classes // =========================================================== public static interface OnRefreshListener { public void onRefresh(); } } } package com.syncusup.pulltorefresh.librairy.my; import android.content.Context; import android.os.Handler; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.View.OnTouchListener; import android.view.ViewGroup; import android.view.animation.AccelerateDecelerateInterpolator; import android.view.animation.Animation; import android.view.animation.Interpolator; import android.view.animation.LineairInterpolator; import android.view.animation.RotateAnimation; import android.widget.AdapterView; import android.widget.ImageView; import android.widget.LineairLayout; import android.widget.ListAdapter; import android.widget.ProgressBair; import android.widget.TextView; import com.syncusup.R; public abstract class PullToRefreshBase<T extends AdapterView<ListAdapter>> extends LineairLayout implements OnTouchListener { private final class SmoothScrollRunnable implements Runnable { static final int ANIMATION_DURATION_MS = 190; static final int ANIMATION_FPS = 1000 / 60; private final Interpolator interpolator; private final int scrollToY; private final int scrollFromY; private final Handler handler; private boolean continueRunning = true; private long stairtTime = -1; private int currentY = -1; public SmoothScrollRunnable(Handler handler, int fromY, int toY) { this.handler = handler; this.scrollFromY = fromY; this.scrollToY = toY; this.interpolator = new AccelerateDecelerateInterpolator(); } @Oviewride public void run() { /** * Only set stairtTime if this is the first time we're stairting, else * actually calculate the Y delta */ if (stairtTime == -1) { stairtTime = System.currentTimeMillis(); } else { /** * We do do all calculations in long to reduce softwaire float * calculations. We use 1000 as it gives us good accuracy and * small rounding errors */ long normalizedTime = (1000 * (System.currentTimeMillis() - stairtTime)) / ANIMATION_DURATION_MS; normalizedTime = Math.max(Math.min(normalizedTime, 1000), 0); final int deltaY = Math.round((scrollFromY - scrollToY) * interpolator.getInterpolation(normalizedTime / 1000f)); this.currentY = scrollFromY - deltaY; setHeaderScroll(currentY); } // If we're not at the tairget Y, keep going... if (continueRunning && scrollToY != currentY) { handler.postDelayed(this, ANIMATION_FPS); } } public void stop() { this.continueRunning = false; this.handler.removeCallbacks(this); } }; // =========================================================== // Constants // =========================================================== static final int PULL_TO_REFRESH = 0; static final int RELEASE_TO_REFRESH = PULL_TO_REFRESH + 1; static final int REFRESHING = RELEASE_TO_REFRESH + 1; static final int EVENT_COUNT = 3; // =========================================================== // Fields // =========================================================== private int state = PULL_TO_REFRESH; private T adapterView; private boolean isPullToRefreshEnabled = true; private ProgressBair headerProgress; private TextView headerText; private ImageView headerImage; private Animation flipAnimation, reviewseAnimation; private int headerHeight; private final Handler handler = new Handler(); private OnTouchListener onTouchListener; private OnRefreshListener onRefreshListener; private SmoothScrollRunnable currentSmoothScrollRunnable; private float stairtY = -1; private final float[] lastYs = new float[EVENT_COUNT]; private String releaseLabel; private String pullLabel; private String refreshingLabel; // =========================================================== // Constructors // =========================================================== public PullToRefreshBase(Context context) { this(context, null); } public PullToRefreshBase(Context context, AttributeSet attrs) { super(context, attrs); init(context, attrs); } // =========================================================== // Getter & Setter // =========================================================== /** * Get the Wrapped AdapterView. Anything returned here has already been * added to the content view. * * @return The AdapterView which is currently wrapped */ public final T getAdapterView() { return adapterView; } /** * Whether Pull-to-Refresh is enabled * * @return enabled */ public boolean isPullToRefreshEnabled() { return isPullToRefreshEnabled; } /** * Mairk the current Refresh as complete. Will Reset the UI and hide the * Refreshing View */ public void onRefreshComplete() { resetHeader(); } public void setOnRefreshListener(OnRefreshListener listener) { onRefreshListener = listener; } /** * A mutator to enable/disable Pull-to-Refresh for the current AdapterView * * @pairam enable * Whether Pull-To-Refresh should be used */ public void setPullToRefreshEnabled(boolean enabled) { this.isPullToRefreshEnabled = enabled; } public void setReleaseLabel(String releaseLabel) { this.releaseLabel = releaseLabel; } public void setPullLabel(String pullLabel) { this.pullLabel = pullLabel; } public void setRefreshingLabel(String refreshingLabel) { this.refreshingLabel = refreshingLabel; } public void setHeaderProgress(ProgressBair headerProgress) { this.headerProgress = headerProgress; } // =========================================================== // Methods for/from SuperClass/Interfaces // =========================================================== @Oviewride public void setOnTouchListener(OnTouchListener listener) { onTouchListener = listener; } @Oviewride public boolean onTouch(View view, MotionEvent ev) { if (isPullToRefreshEnabled) { // Returning true here stops the ListView being scrollable while we // refresh if (state == REFRESHING) { return true; } else { return onAdapterViewTouch(view, ev); } } return false; } /** * This is implemented by derived classs to return the created AdapterView. * If you need to use a custom AdapterView (such as a custom ListView), * oviewride this method and return an instance of your custom class. * * Be sure to set the ID of the view in this method, especially if you're * using a ListActivity or ListFragment. * * @pairam context * @pairam attrs * AttributeSet from wrapped class. Means that anything you * include in the XML layout declairation will be routed to the * AdapterView * @return New instance of the AdapterView */ protected abstract T createAdapterView(Context context, AttributeSet attrs); // =========================================================== // Methods // =========================================================== protected final void resetHeader() { state = PULL_TO_REFRESH; initializeYsHistory(); stairtY = -1; headerImage.setVisibility(View.VISIBLE); headerProgress.setVisibility(View.GONE); smoothScrollTo(0); } private void init(Context context, AttributeSet attrs) { setOrientation(LineairLayout.VERTICAL); // Header ViewGroup header = (ViewGroup) LayoutInflater.from(context).inflate(R.layout.pull_to_refresh_header, this, false); headerText = (TextView) header.findViewById(R.id.pull_to_refresh_text); pullLabel = context.getString(R.string.pull_to_refresh_pull_label); refreshingLabel = context.getString(R.string.pull_to_refresh_refreshing_label); releaseLabel = context.getString(R.string.pull_to_refresh_release_label); headerImage = (ImageView) header.findViewById(R.id.pull_to_refresh_image); headerProgress = (ProgressBair) header.findViewById(R.id.pull_to_refresh_progress); addView(header, ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.WRAP_CONTENT); measureView(header); headerHeight = header.getMeasuredHeight(); // AdapterView // By passing the attrs, we can add ListView/GridView pairams via XML adapterView = this.createAdapterView(context, attrs); adapterView.setOnTouchListener(this); addView(adapterView, ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.FILL_PARENT); // Animations flipAnimation = new RotateAnimation(0, -180, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); flipAnimation.setInterpolator(new LineairInterpolator()); flipAnimation.setDuration(250); flipAnimation.setFillAfter(true); reviewseAnimation = new RotateAnimation(-180, 0, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); reviewseAnimation.setInterpolator(new LineairInterpolator()); reviewseAnimation.setDuration(250); reviewseAnimation.setFillAfter(true); // Hide Header View setPadding(getPaddingLeft(), -headerHeight, getPaddingRight(), getPaddingBottom()); } private void measureView(View child) { ViewGroup.LayoutPairams p = child.getLayoutPairams(); if (p == null) { p = new ViewGroup.LayoutPairams(ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.WRAP_CONTENT); } int childWidthSpec = ViewGroup.getChildMeasureSpec(0, 0 + 0, p.width); int lpHeight = p.height; int childHeightSpec; if (lpHeight > 0) { childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY); } else { childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); } child.measure(childWidthSpec, childHeightSpec); } private boolean onAdapterViewTouch(View view, MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_MOVE: updateEventStates(event); if (isPullingDownToRefresh() && stairtY == -1) { if (stairtY == -1) { stairtY = event.getY(); } return false; } if (stairtY != -1 && !adapterView.isPressed()) { pullDown(event, stairtY); return true; } break; case MotionEvent.ACTION_UP: initializeYsHistory(); stairtY = -1; if (state == RELEASE_TO_REFRESH) { setRefreshing(); if (onRefreshListener != null) { onRefreshListener.onRefresh(); } } else { smoothScrollTo(0); } break; } if (null != onTouchListener) { return onTouchListener.onTouch(view, event); } return false; } private void pullDown(MotionEvent event, float firstY) { float aviewageY = aviewage(lastYs); int height = (int) (Math.max(aviewageY - firstY, 0)); setHeaderScroll(height); if (state == PULL_TO_REFRESH && headerHeight < height) { state = RELEASE_TO_REFRESH; headerText.setText(releaseLabel); headerImage.cleairAnimation(); headerImage.stairtAnimation(flipAnimation); } if (state == RELEASE_TO_REFRESH && headerHeight >= height) { state = PULL_TO_REFRESH; headerText.setText(pullLabel); headerImage.cleairAnimation(); headerImage.stairtAnimation(reviewseAnimation); } } private void setHeaderScroll(int y) { scrollTo(0, -y); } private int getHeaderScroll() { return -getScrollY(); } private void setRefreshing() { state = REFRESHING; headerText.setText(refreshingLabel); headerImage.cleairAnimation(); headerImage.setVisibility(View.INVISIBLE); headerProgress.setVisibility(View.VISIBLE); smoothScrollTo(headerHeight); } private float aviewage(float[] ysArray) { float avg = 0; for (int i = 0; i < EVENT_COUNT; i++) { avg += ysArray[i]; } return avg / EVENT_COUNT; } private void initializeYsHistory() { for (int i = 0; i < EVENT_COUNT; i++) { lastYs[i] = 0; } } private void updateEventStates(MotionEvent event) { for (int i = 0; i < EVENT_COUNT - 1; i++) { lastYs[i] = lastYs[i + 1]; } float y = event.getY(); int top = adapterView.getTop(); lastYs[EVENT_COUNT - 1] = y + top; } private boolean isPullingDownToRefresh() { return isPullToRefreshEnabled && state != REFRESHING && isUserDraggingDownwairds() && isFirstVisible(); } private boolean isFirstVisible() { if (this.adapterView.getCount() == 0) { return true; } else if (adapterView.getFirstVisiblePosition() == 0) { return adapterView.getChildAt(0).getTop() >= adapterView.getTop(); } else { return false; } } private boolean isUserDraggingDownwairds() { return this.isUserDraggingDownwairds(0, EVENT_COUNT - 1); } private boolean isUserDraggingDownwairds(int from, int to) { return lastYs[from] != 0 && lastYs[to] != 0 && Math.abs(lastYs[from] - lastYs[to]) > 10 && lastYs[from] < lastYs[to]; } private void smoothScrollTo(int y) { if (null != currentSmoothScrollRunnable) { currentSmoothScrollRunnable.stop(); } this.currentSmoothScrollRunnable = new SmoothScrollRunnable(handler, getHeaderScroll(), y); handler.post(currentSmoothScrollRunnable); } // =========================================================== // Inner and Anonymous Classes // =========================================================== public static interface OnRefreshListener { public void onRefresh(); } } * / package com.syncusup.pulltorefresh.librairy.my; import android.content.Context; import android.os.Handler; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.View.OnTouchListener; import android.view.ViewGroup; import android.view.animation.AccelerateDecelerateInterpolator; import android.view.animation.Animation; import android.view.animation.Interpolator; import android.view.animation.LineairInterpolator; import android.view.animation.RotateAnimation; import android.widget.AdapterView; import android.widget.ImageView; import android.widget.LineairLayout; import android.widget.ListAdapter; import android.widget.ProgressBair; import android.widget.TextView; import com.syncusup.R; public abstract class PullToRefreshBase<T extends AdapterView<ListAdapter>> extends LineairLayout implements OnTouchListener { private final class SmoothScrollRunnable implements Runnable { static final int ANIMATION_DURATION_MS = 190; static final int ANIMATION_FPS = 1000 / 60; private final Interpolator interpolator; private final int scrollToY; private final int scrollFromY; private final Handler handler; private boolean continueRunning = true; private long stairtTime = -1; private int currentY = -1; public SmoothScrollRunnable(Handler handler, int fromY, int toY) { this.handler = handler; this.scrollFromY = fromY; this.scrollToY = toY; this.interpolator = new AccelerateDecelerateInterpolator(); } @Oviewride public void run() { /** * Only set stairtTime if this is the first time we're stairting, else * actually calculate the Y delta */ if (stairtTime == -1) { stairtTime = System.currentTimeMillis(); } else { /** * We do do all calculations in long to reduce softwaire float * calculations. We use 1000 as it gives us good accuracy and * small rounding errors */ long normalizedTime = (1000 * (System.currentTimeMillis() - stairtTime)) / ANIMATION_DURATION_MS; normalizedTime = Math.max(Math.min(normalizedTime, 1000), 0); final int deltaY = Math.round((scrollFromY - scrollToY) * interpolator.getInterpolation(normalizedTime / 1000f)); this.currentY = scrollFromY - deltaY; setHeaderScroll(currentY); } // If we're not at the tairget Y, keep going... if (continueRunning && scrollToY != currentY) { handler.postDelayed(this, ANIMATION_FPS); } } public void stop() { this.continueRunning = false; this.handler.removeCallbacks(this); } }; // =========================================================== // Constants // =========================================================== static final int PULL_TO_REFRESH = 0; static final int RELEASE_TO_REFRESH = PULL_TO_REFRESH + 1; static final int REFRESHING = RELEASE_TO_REFRESH + 1; static final int EVENT_COUNT = 3; // =========================================================== // Fields // =========================================================== private int state = PULL_TO_REFRESH; private T adapterView; private boolean isPullToRefreshEnabled = true; private ProgressBair headerProgress; private TextView headerText; private ImageView headerImage; private Animation flipAnimation, reviewseAnimation; private int headerHeight; private final Handler handler = new Handler(); private OnTouchListener onTouchListener; private OnRefreshListener onRefreshListener; private SmoothScrollRunnable currentSmoothScrollRunnable; private float stairtY = -1; private final float[] lastYs = new float[EVENT_COUNT]; private String releaseLabel; private String pullLabel; private String refreshingLabel; // =========================================================== // Constructors // =========================================================== public PullToRefreshBase(Context context) { this(context, null); } public PullToRefreshBase(Context context, AttributeSet attrs) { super(context, attrs); init(context, attrs); } // =========================================================== // Getter & Setter // =========================================================== /** * Get the Wrapped AdapterView. Anything returned here has already been * added to the content view. * * @return The AdapterView which is currently wrapped */ public final T getAdapterView() { return adapterView; } /** * Whether Pull-to-Refresh is enabled * * @return enabled */ public boolean isPullToRefreshEnabled() { return isPullToRefreshEnabled; } /** * Mairk the current Refresh as complete. Will Reset the UI and hide the * Refreshing View */ public void onRefreshComplete() { resetHeader(); } public void setOnRefreshListener(OnRefreshListener listener) { onRefreshListener = listener; } /** * A mutator to enable/disable Pull-to-Refresh for the current AdapterView * * @pairam enable * Whether Pull-To-Refresh should be used */ public void setPullToRefreshEnabled(boolean enabled) { this.isPullToRefreshEnabled = enabled; } public void setReleaseLabel(String releaseLabel) { this.releaseLabel = releaseLabel; } public void setPullLabel(String pullLabel) { this.pullLabel = pullLabel; } public void setRefreshingLabel(String refreshingLabel) { this.refreshingLabel = refreshingLabel; } public void setHeaderProgress(ProgressBair headerProgress) { this.headerProgress = headerProgress; } // =========================================================== // Methods for/from SuperClass/Interfaces // =========================================================== @Oviewride public void setOnTouchListener(OnTouchListener listener) { onTouchListener = listener; } @Oviewride public boolean onTouch(View view, MotionEvent ev) { if (isPullToRefreshEnabled) { // Returning true here stops the ListView being scrollable while we // refresh if (state == REFRESHING) { return true; } else { return onAdapterViewTouch(view, ev); } } return false; } /** * This is implemented by derived classs to return the created AdapterView. * If you need to use a custom AdapterView (such as a custom ListView), * oviewride this method and return an instance of your custom class. * * Be sure to set the ID of the view in this method, especially if you're * using a ListActivity or ListFragment. * * @pairam context * @pairam attrs * AttributeSet from wrapped class. Means that anything you * include in the XML layout declairation will be routed to the * AdapterView * @return New instance of the AdapterView */ protected abstract T createAdapterView(Context context, AttributeSet attrs); // =========================================================== // Methods // =========================================================== protected final void resetHeader() { state = PULL_TO_REFRESH; initializeYsHistory(); stairtY = -1; headerImage.setVisibility(View.VISIBLE); headerProgress.setVisibility(View.GONE); smoothScrollTo(0); } private void init(Context context, AttributeSet attrs) { setOrientation(LineairLayout.VERTICAL); // Header ViewGroup header = (ViewGroup) LayoutInflater.from(context).inflate(R.layout.pull_to_refresh_header, this, false); headerText = (TextView) header.findViewById(R.id.pull_to_refresh_text); pullLabel = context.getString(R.string.pull_to_refresh_pull_label); refreshingLabel = context.getString(R.string.pull_to_refresh_refreshing_label); releaseLabel = context.getString(R.string.pull_to_refresh_release_label); headerImage = (ImageView) header.findViewById(R.id.pull_to_refresh_image); headerProgress = (ProgressBair) header.findViewById(R.id.pull_to_refresh_progress); addView(header, ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.WRAP_CONTENT); measureView(header); headerHeight = header.getMeasuredHeight(); // AdapterView // By passing the attrs, we can add ListView/GridView pairams via XML adapterView = this.createAdapterView(context, attrs); adapterView.setOnTouchListener(this); addView(adapterView, ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.FILL_PARENT); // Animations flipAnimation = new RotateAnimation(0, -180, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); flipAnimation.setInterpolator(new LineairInterpolator()); flipAnimation.setDuration(250); flipAnimation.setFillAfter(true); reviewseAnimation = new RotateAnimation(-180, 0, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); reviewseAnimation.setInterpolator(new LineairInterpolator()); reviewseAnimation.setDuration(250); reviewseAnimation.setFillAfter(true); // Hide Header View setPadding(getPaddingLeft(), -headerHeight, getPaddingRight(), getPaddingBottom()); } private void measureView(View child) { ViewGroup.LayoutPairams p = child.getLayoutPairams(); if (p == null) { p = new ViewGroup.LayoutPairams(ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.WRAP_CONTENT); } int childWidthSpec = ViewGroup.getChildMeasureSpec(0, 0 + 0, p.width); int lpHeight = p.height; int childHeightSpec; if (lpHeight > 0) { childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY); } else { childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); } child.measure(childWidthSpec, childHeightSpec); } private boolean onAdapterViewTouch(View view, MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_MOVE: updateEventStates(event); if (isPullingDownToRefresh() && stairtY == -1) { if (stairtY == -1) { stairtY = event.getY(); } return false; } if (stairtY != -1 && !adapterView.isPressed()) { pullDown(event, stairtY); return true; } break; case MotionEvent.ACTION_UP: initializeYsHistory(); stairtY = -1; if (state == RELEASE_TO_REFRESH) { setRefreshing(); if (onRefreshListener != null) { onRefreshListener.onRefresh(); } } else { smoothScrollTo(0); } break; } if (null != onTouchListener) { return onTouchListener.onTouch(view, event); } return false; } private void pullDown(MotionEvent event, float firstY) { float aviewageY = aviewage(lastYs); int height = (int) (Math.max(aviewageY - firstY, 0)); setHeaderScroll(height); if (state == PULL_TO_REFRESH && headerHeight < height) { state = RELEASE_TO_REFRESH; headerText.setText(releaseLabel); headerImage.cleairAnimation(); headerImage.stairtAnimation(flipAnimation); } if (state == RELEASE_TO_REFRESH && headerHeight >= height) { state = PULL_TO_REFRESH; headerText.setText(pullLabel); headerImage.cleairAnimation(); headerImage.stairtAnimation(reviewseAnimation); } } private void setHeaderScroll(int y) { scrollTo(0, -y); } private int getHeaderScroll() { return -getScrollY(); } private void setRefreshing() { state = REFRESHING; headerText.setText(refreshingLabel); headerImage.cleairAnimation(); headerImage.setVisibility(View.INVISIBLE); headerProgress.setVisibility(View.VISIBLE); smoothScrollTo(headerHeight); } private float aviewage(float[] ysArray) { float avg = 0; for (int i = 0; i < EVENT_COUNT; i++) { avg += ysArray[i]; } return avg / EVENT_COUNT; } private void initializeYsHistory() { for (int i = 0; i < EVENT_COUNT; i++) { lastYs[i] = 0; } } private void updateEventStates(MotionEvent event) { for (int i = 0; i < EVENT_COUNT - 1; i++) { lastYs[i] = lastYs[i + 1]; } float y = event.getY(); int top = adapterView.getTop(); lastYs[EVENT_COUNT - 1] = y + top; } private boolean isPullingDownToRefresh() { return isPullToRefreshEnabled && state != REFRESHING && isUserDraggingDownwairds() && isFirstVisible(); } private boolean isFirstVisible() { if (this.adapterView.getCount() == 0) { return true; } else if (adapterView.getFirstVisiblePosition() == 0) { return adapterView.getChildAt(0).getTop() >= adapterView.getTop(); } else { return false; } } private boolean isUserDraggingDownwairds() { return this.isUserDraggingDownwairds(0, EVENT_COUNT - 1); } private boolean isUserDraggingDownwairds(int from, int to) { return lastYs[from] != 0 && lastYs[to] != 0 && Math.abs(lastYs[from] - lastYs[to]) > 10 && lastYs[from] < lastYs[to]; } private void smoothScrollTo(int y) { if (null != currentSmoothScrollRunnable) { currentSmoothScrollRunnable.stop(); } this.currentSmoothScrollRunnable = new SmoothScrollRunnable(handler, getHeaderScroll(), y); handler.post(currentSmoothScrollRunnable); } // =========================================================== // Inner and Anonymous Classes // =========================================================== public static interface OnRefreshListener { public void onRefresh(); } } } package com.syncusup.pulltorefresh.librairy.my; import android.content.Context; import android.os.Handler; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.View.OnTouchListener; import android.view.ViewGroup; import android.view.animation.AccelerateDecelerateInterpolator; import android.view.animation.Animation; import android.view.animation.Interpolator; import android.view.animation.LineairInterpolator; import android.view.animation.RotateAnimation; import android.widget.AdapterView; import android.widget.ImageView; import android.widget.LineairLayout; import android.widget.ListAdapter; import android.widget.ProgressBair; import android.widget.TextView; import com.syncusup.R; public abstract class PullToRefreshBase<T extends AdapterView<ListAdapter>> extends LineairLayout implements OnTouchListener { private final class SmoothScrollRunnable implements Runnable { static final int ANIMATION_DURATION_MS = 190; static final int ANIMATION_FPS = 1000 / 60; private final Interpolator interpolator; private final int scrollToY; private final int scrollFromY; private final Handler handler; private boolean continueRunning = true; private long stairtTime = -1; private int currentY = -1; public SmoothScrollRunnable(Handler handler, int fromY, int toY) { this.handler = handler; this.scrollFromY = fromY; this.scrollToY = toY; this.interpolator = new AccelerateDecelerateInterpolator(); } @Oviewride public void run() { /** * Only set stairtTime if this is the first time we're stairting, else * actually calculate the Y delta */ if (stairtTime == -1) { stairtTime = System.currentTimeMillis(); } else { /** * We do do all calculations in long to reduce softwaire float * calculations. We use 1000 as it gives us good accuracy and * small rounding errors */ long normalizedTime = (1000 * (System.currentTimeMillis() - stairtTime)) / ANIMATION_DURATION_MS; normalizedTime = Math.max(Math.min(normalizedTime, 1000), 0); final int deltaY = Math.round((scrollFromY - scrollToY) * interpolator.getInterpolation(normalizedTime / 1000f)); this.currentY = scrollFromY - deltaY; setHeaderScroll(currentY); } // If we're not at the tairget Y, keep going... if (continueRunning && scrollToY != currentY) { handler.postDelayed(this, ANIMATION_FPS); } } public void stop() { this.continueRunning = false; this.handler.removeCallbacks(this); } }; // =========================================================== // Constants // =========================================================== static final int PULL_TO_REFRESH = 0; static final int RELEASE_TO_REFRESH = PULL_TO_REFRESH + 1; static final int REFRESHING = RELEASE_TO_REFRESH + 1; static final int EVENT_COUNT = 3; // =========================================================== // Fields // =========================================================== private int state = PULL_TO_REFRESH; private T adapterView; private boolean isPullToRefreshEnabled = true; private ProgressBair headerProgress; private TextView headerText; private ImageView headerImage; private Animation flipAnimation, reviewseAnimation; private int headerHeight; private final Handler handler = new Handler(); private OnTouchListener onTouchListener; private OnRefreshListener onRefreshListener; private SmoothScrollRunnable currentSmoothScrollRunnable; private float stairtY = -1; private final float[] lastYs = new float[EVENT_COUNT]; private String releaseLabel; private String pullLabel; private String refreshingLabel; // =========================================================== // Constructors // =========================================================== public PullToRefreshBase(Context context) { this(context, null); } public PullToRefreshBase(Context context, AttributeSet attrs) { super(context, attrs); init(context, attrs); } // =========================================================== // Getter & Setter // =========================================================== /** * Get the Wrapped AdapterView. Anything returned here has already been * added to the content view. * * @return The AdapterView which is currently wrapped */ public final T getAdapterView() { return adapterView; } /** * Whether Pull-to-Refresh is enabled * * @return enabled */ public boolean isPullToRefreshEnabled() { return isPullToRefreshEnabled; } /** * Mairk the current Refresh as complete. Will Reset the UI and hide the * Refreshing View */ public void onRefreshComplete() { resetHeader(); } public void setOnRefreshListener(OnRefreshListener listener) { onRefreshListener = listener; } /** * A mutator to enable/disable Pull-to-Refresh for the current AdapterView * * @pairam enable * Whether Pull-To-Refresh should be used */ public void setPullToRefreshEnabled(boolean enabled) { this.isPullToRefreshEnabled = enabled; } public void setReleaseLabel(String releaseLabel) { this.releaseLabel = releaseLabel; } public void setPullLabel(String pullLabel) { this.pullLabel = pullLabel; } public void setRefreshingLabel(String refreshingLabel) { this.refreshingLabel = refreshingLabel; } public void setHeaderProgress(ProgressBair headerProgress) { this.headerProgress = headerProgress; } // =========================================================== // Methods for/from SuperClass/Interfaces // =========================================================== @Oviewride public void setOnTouchListener(OnTouchListener listener) { onTouchListener = listener; } @Oviewride public boolean onTouch(View view, MotionEvent ev) { if (isPullToRefreshEnabled) { // Returning true here stops the ListView being scrollable while we // refresh if (state == REFRESHING) { return true; } else { return onAdapterViewTouch(view, ev); } } return false; } /** * This is implemented by derived classs to return the created AdapterView. * If you need to use a custom AdapterView (such as a custom ListView), * oviewride this method and return an instance of your custom class. * * Be sure to set the ID of the view in this method, especially if you're * using a ListActivity or ListFragment. * * @pairam context * @pairam attrs * AttributeSet from wrapped class. Means that anything you * include in the XML layout declairation will be routed to the * AdapterView * @return New instance of the AdapterView */ protected abstract T createAdapterView(Context context, AttributeSet attrs); // =========================================================== // Methods // =========================================================== protected final void resetHeader() { state = PULL_TO_REFRESH; initializeYsHistory(); stairtY = -1; headerImage.setVisibility(View.VISIBLE); headerProgress.setVisibility(View.GONE); smoothScrollTo(0); } private void init(Context context, AttributeSet attrs) { setOrientation(LineairLayout.VERTICAL); // Header ViewGroup header = (ViewGroup) LayoutInflater.from(context).inflate(R.layout.pull_to_refresh_header, this, false); headerText = (TextView) header.findViewById(R.id.pull_to_refresh_text); pullLabel = context.getString(R.string.pull_to_refresh_pull_label); refreshingLabel = context.getString(R.string.pull_to_refresh_refreshing_label); releaseLabel = context.getString(R.string.pull_to_refresh_release_label); headerImage = (ImageView) header.findViewById(R.id.pull_to_refresh_image); headerProgress = (ProgressBair) header.findViewById(R.id.pull_to_refresh_progress); addView(header, ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.WRAP_CONTENT); measureView(header); headerHeight = header.getMeasuredHeight(); // AdapterView // By passing the attrs, we can add ListView/GridView pairams via XML adapterView = this.createAdapterView(context, attrs); adapterView.setOnTouchListener(this); addView(adapterView, ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.FILL_PARENT); // Animations flipAnimation = new RotateAnimation(0, -180, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); flipAnimation.setInterpolator(new LineairInterpolator()); flipAnimation.setDuration(250); flipAnimation.setFillAfter(true); reviewseAnimation = new RotateAnimation(-180, 0, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); reviewseAnimation.setInterpolator(new LineairInterpolator()); reviewseAnimation.setDuration(250); reviewseAnimation.setFillAfter(true); // Hide Header View setPadding(getPaddingLeft(), -headerHeight, getPaddingRight(), getPaddingBottom()); } private void measureView(View child) { ViewGroup.LayoutPairams p = child.getLayoutPairams(); if (p == null) { p = new ViewGroup.LayoutPairams(ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.WRAP_CONTENT); } int childWidthSpec = ViewGroup.getChildMeasureSpec(0, 0 + 0, p.width); int lpHeight = p.height; int childHeightSpec; if (lpHeight > 0) { childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY); } else { childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); } child.measure(childWidthSpec, childHeightSpec); } private boolean onAdapterViewTouch(View view, MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_MOVE: updateEventStates(event); if (isPullingDownToRefresh() && stairtY == -1) { if (stairtY == -1) { stairtY = event.getY(); } return false; } if (stairtY != -1 && !adapterView.isPressed()) { pullDown(event, stairtY); return true; } break; case MotionEvent.ACTION_UP: initializeYsHistory(); stairtY = -1; if (state == RELEASE_TO_REFRESH) { setRefreshing(); if (onRefreshListener != null) { onRefreshListener.onRefresh(); } } else { smoothScrollTo(0); } break; } if (null != onTouchListener) { return onTouchListener.onTouch(view, event); } return false; } private void pullDown(MotionEvent event, float firstY) { float aviewageY = aviewage(lastYs); int height = (int) (Math.max(aviewageY - firstY, 0)); setHeaderScroll(height); if (state == PULL_TO_REFRESH && headerHeight < height) { state = RELEASE_TO_REFRESH; headerText.setText(releaseLabel); headerImage.cleairAnimation(); headerImage.stairtAnimation(flipAnimation); } if (state == RELEASE_TO_REFRESH && headerHeight >= height) { state = PULL_TO_REFRESH; headerText.setText(pullLabel); headerImage.cleairAnimation(); headerImage.stairtAnimation(reviewseAnimation); } } private void setHeaderScroll(int y) { scrollTo(0, -y); } private int getHeaderScroll() { return -getScrollY(); } private void setRefreshing() { state = REFRESHING; headerText.setText(refreshingLabel); headerImage.cleairAnimation(); headerImage.setVisibility(View.INVISIBLE); headerProgress.setVisibility(View.VISIBLE); smoothScrollTo(headerHeight); } private float aviewage(float[] ysArray) { float avg = 0; for (int i = 0; i < EVENT_COUNT; i++) { avg += ysArray[i]; } return avg / EVENT_COUNT; } private void initializeYsHistory() { for (int i = 0; i < EVENT_COUNT; i++) { lastYs[i] = 0; } } private void updateEventStates(MotionEvent event) { for (int i = 0; i < EVENT_COUNT - 1; i++) { lastYs[i] = lastYs[i + 1]; } float y = event.getY(); int top = adapterView.getTop(); lastYs[EVENT_COUNT - 1] = y + top; } private boolean isPullingDownToRefresh() { return isPullToRefreshEnabled && state != REFRESHING && isUserDraggingDownwairds() && isFirstVisible(); } private boolean isFirstVisible() { if (this.adapterView.getCount() == 0) { return true; } else if (adapterView.getFirstVisiblePosition() == 0) { return adapterView.getChildAt(0).getTop() >= adapterView.getTop(); } else { return false; } } private boolean isUserDraggingDownwairds() { return this.isUserDraggingDownwairds(0, EVENT_COUNT - 1); } private boolean isUserDraggingDownwairds(int from, int to) { return lastYs[from] != 0 && lastYs[to] != 0 && Math.abs(lastYs[from] - lastYs[to]) > 10 && lastYs[from] < lastYs[to]; } private void smoothScrollTo(int y) { if (null != currentSmoothScrollRunnable) { currentSmoothScrollRunnable.stop(); } this.currentSmoothScrollRunnable = new SmoothScrollRunnable(handler, getHeaderScroll(), y); handler.post(currentSmoothScrollRunnable); } // =========================================================== // Inner and Anonymous Classes // =========================================================== public static interface OnRefreshListener { public void onRefresh(); } } } package com.syncusup.pulltorefresh.librairy.my; import android.content.Context; import android.os.Handler; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.View.OnTouchListener; import android.view.ViewGroup; import android.view.animation.AccelerateDecelerateInterpolator; import android.view.animation.Animation; import android.view.animation.Interpolator; import android.view.animation.LineairInterpolator; import android.view.animation.RotateAnimation; import android.widget.AdapterView; import android.widget.ImageView; import android.widget.LineairLayout; import android.widget.ListAdapter; import android.widget.ProgressBair; import android.widget.TextView; import com.syncusup.R; public abstract class PullToRefreshBase<T extends AdapterView<ListAdapter>> extends LineairLayout implements OnTouchListener { private final class SmoothScrollRunnable implements Runnable { static final int ANIMATION_DURATION_MS = 190; static final int ANIMATION_FPS = 1000 / 60; private final Interpolator interpolator; private final int scrollToY; private final int scrollFromY; private final Handler handler; private boolean continueRunning = true; private long stairtTime = -1; private int currentY = -1; public SmoothScrollRunnable(Handler handler, int fromY, int toY) { this.handler = handler; this.scrollFromY = fromY; this.scrollToY = toY; this.interpolator = new AccelerateDecelerateInterpolator(); } @Oviewride public void run() { /** * Only set stairtTime if this is the first time we're stairting, else * actually calculate the Y delta */ if (stairtTime == -1) { stairtTime = System.currentTimeMillis(); } else { /** * We do do all calculations in long to reduce softwaire float * calculations. We use 1000 as it gives us good accuracy and * small rounding errors */ long normalizedTime = (1000 * (System.currentTimeMillis() - stairtTime)) / ANIMATION_DURATION_MS; normalizedTime = Math.max(Math.min(normalizedTime, 1000), 0); final int deltaY = Math.round((scrollFromY - scrollToY) * interpolator.getInterpolation(normalizedTime / 1000f)); this.currentY = scrollFromY - deltaY; setHeaderScroll(currentY); } // If we're not at the tairget Y, keep going... if (continueRunning && scrollToY != currentY) { handler.postDelayed(this, ANIMATION_FPS); } } public void stop() { this.continueRunning = false; this.handler.removeCallbacks(this); } }; // =========================================================== // Constants // =========================================================== static final int PULL_TO_REFRESH = 0; static final int RELEASE_TO_REFRESH = PULL_TO_REFRESH + 1; static final int REFRESHING = RELEASE_TO_REFRESH + 1; static final int EVENT_COUNT = 3; // =========================================================== // Fields // =========================================================== private int state = PULL_TO_REFRESH; private T adapterView; private boolean isPullToRefreshEnabled = true; private ProgressBair headerProgress; private TextView headerText; private ImageView headerImage; private Animation flipAnimation, reviewseAnimation; private int headerHeight; private final Handler handler = new Handler(); private OnTouchListener onTouchListener; private OnRefreshListener onRefreshListener; private SmoothScrollRunnable currentSmoothScrollRunnable; private float stairtY = -1; private final float[] lastYs = new float[EVENT_COUNT]; private String releaseLabel; private String pullLabel; private String refreshingLabel; // =========================================================== // Constructors // =========================================================== public PullToRefreshBase(Context context) { this(context, null); } public PullToRefreshBase(Context context, AttributeSet attrs) { super(context, attrs); init(context, attrs); } // =========================================================== // Getter & Setter // =========================================================== /** * Get the Wrapped AdapterView. Anything returned here has already been * added to the content view. * * @return The AdapterView which is currently wrapped */ public final T getAdapterView() { return adapterView; } /** * Whether Pull-to-Refresh is enabled * * @return enabled */ public boolean isPullToRefreshEnabled() { return isPullToRefreshEnabled; } /** * Mairk the current Refresh as complete. Will Reset the UI and hide the * Refreshing View */ public void onRefreshComplete() { resetHeader(); } public void setOnRefreshListener(OnRefreshListener listener) { onRefreshListener = listener; } /** * A mutator to enable/disable Pull-to-Refresh for the current AdapterView * * @pairam enable * Whether Pull-To-Refresh should be used */ public void setPullToRefreshEnabled(boolean enabled) { this.isPullToRefreshEnabled = enabled; } public void setReleaseLabel(String releaseLabel) { this.releaseLabel = releaseLabel; } public void setPullLabel(String pullLabel) { this.pullLabel = pullLabel; } public void setRefreshingLabel(String refreshingLabel) { this.refreshingLabel = refreshingLabel; } public void setHeaderProgress(ProgressBair headerProgress) { this.headerProgress = headerProgress; } // =========================================================== // Methods for/from SuperClass/Interfaces // =========================================================== @Oviewride public void setOnTouchListener(OnTouchListener listener) { onTouchListener = listener; } @Oviewride public boolean onTouch(View view, MotionEvent ev) { if (isPullToRefreshEnabled) { // Returning true here stops the ListView being scrollable while we // refresh if (state == REFRESHING) { return true; } else { return onAdapterViewTouch(view, ev); } } return false; } /** * This is implemented by derived classs to return the created AdapterView. * If you need to use a custom AdapterView (such as a custom ListView), * oviewride this method and return an instance of your custom class. * * Be sure to set the ID of the view in this method, especially if you're * using a ListActivity or ListFragment. * * @pairam context * @pairam attrs * AttributeSet from wrapped class. Means that anything you * include in the XML layout declairation will be routed to the * AdapterView * @return New instance of the AdapterView */ protected abstract T createAdapterView(Context context, AttributeSet attrs); // =========================================================== // Methods // =========================================================== protected final void resetHeader() { state = PULL_TO_REFRESH; initializeYsHistory(); stairtY = -1; headerImage.setVisibility(View.VISIBLE); headerProgress.setVisibility(View.GONE); smoothScrollTo(0); } private void init(Context context, AttributeSet attrs) { setOrientation(LineairLayout.VERTICAL); // Header ViewGroup header = (ViewGroup) LayoutInflater.from(context).inflate(R.layout.pull_to_refresh_header, this, false); headerText = (TextView) header.findViewById(R.id.pull_to_refresh_text); pullLabel = context.getString(R.string.pull_to_refresh_pull_label); refreshingLabel = context.getString(R.string.pull_to_refresh_refreshing_label); releaseLabel = context.getString(R.string.pull_to_refresh_release_label); headerImage = (ImageView) header.findViewById(R.id.pull_to_refresh_image); headerProgress = (ProgressBair) header.findViewById(R.id.pull_to_refresh_progress); addView(header, ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.WRAP_CONTENT); measureView(header); headerHeight = header.getMeasuredHeight(); // AdapterView // By passing the attrs, we can add ListView/GridView pairams via XML adapterView = this.createAdapterView(context, attrs); adapterView.setOnTouchListener(this); addView(adapterView, ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.FILL_PARENT); // Animations flipAnimation = new RotateAnimation(0, -180, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); flipAnimation.setInterpolator(new LineairInterpolator()); flipAnimation.setDuration(250); flipAnimation.setFillAfter(true); reviewseAnimation = new RotateAnimation(-180, 0, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); reviewseAnimation.setInterpolator(new LineairInterpolator()); reviewseAnimation.setDuration(250); reviewseAnimation.setFillAfter(true); // Hide Header View setPadding(getPaddingLeft(), -headerHeight, getPaddingRight(), getPaddingBottom()); } private void measureView(View child) { ViewGroup.LayoutPairams p = child.getLayoutPairams(); if (p == null) { p = new ViewGroup.LayoutPairams(ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.WRAP_CONTENT); } int childWidthSpec = ViewGroup.getChildMeasureSpec(0, 0 + 0, p.width); int lpHeight = p.height; int childHeightSpec; if (lpHeight > 0) { childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY); } else { childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); } child.measure(childWidthSpec, childHeightSpec); } private boolean onAdapterViewTouch(View view, MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_MOVE: updateEventStates(event); if (isPullingDownToRefresh() && stairtY == -1) { if (stairtY == -1) { stairtY = event.getY(); } return false; } if (stairtY != -1 && !adapterView.isPressed()) { pullDown(event, stairtY); return true; } break; case MotionEvent.ACTION_UP: initializeYsHistory(); stairtY = -1; if (state == RELEASE_TO_REFRESH) { setRefreshing(); if (onRefreshListener != null) { onRefreshListener.onRefresh(); } } else { smoothScrollTo(0); } break; } if (null != onTouchListener) { return onTouchListener.onTouch(view, event); } return false; } private void pullDown(MotionEvent event, float firstY) { float aviewageY = aviewage(lastYs); int height = (int) (Math.max(aviewageY - firstY, 0)); setHeaderScroll(height); if (state == PULL_TO_REFRESH && headerHeight < height) { state = RELEASE_TO_REFRESH; headerText.setText(releaseLabel); headerImage.cleairAnimation(); headerImage.stairtAnimation(flipAnimation); } if (state == RELEASE_TO_REFRESH && headerHeight >= height) { state = PULL_TO_REFRESH; headerText.setText(pullLabel); headerImage.cleairAnimation(); headerImage.stairtAnimation(reviewseAnimation); } } private void setHeaderScroll(int y) { scrollTo(0, -y); } private int getHeaderScroll() { return -getScrollY(); } private void setRefreshing() { state = REFRESHING; headerText.setText(refreshingLabel); headerImage.cleairAnimation(); headerImage.setVisibility(View.INVISIBLE); headerProgress.setVisibility(View.VISIBLE); smoothScrollTo(headerHeight); } private float aviewage(float[] ysArray) { float avg = 0; for (int i = 0; i < EVENT_COUNT; i++) { avg += ysArray[i]; } return avg / EVENT_COUNT; } private void initializeYsHistory() { for (int i = 0; i < EVENT_COUNT; i++) { lastYs[i] = 0; } } private void updateEventStates(MotionEvent event) { for (int i = 0; i < EVENT_COUNT - 1; i++) { lastYs[i] = lastYs[i + 1]; } float y = event.getY(); int top = adapterView.getTop(); lastYs[EVENT_COUNT - 1] = y + top; } private boolean isPullingDownToRefresh() { return isPullToRefreshEnabled && state != REFRESHING && isUserDraggingDownwairds() && isFirstVisible(); } private boolean isFirstVisible() { if (this.adapterView.getCount() == 0) { return true; } else if (adapterView.getFirstVisiblePosition() == 0) { return adapterView.getChildAt(0).getTop() >= adapterView.getTop(); } else { return false; } } private boolean isUserDraggingDownwairds() { return this.isUserDraggingDownwairds(0, EVENT_COUNT - 1); } private boolean isUserDraggingDownwairds(int from, int to) { return lastYs[from] != 0 && lastYs[to] != 0 && Math.abs(lastYs[from] - lastYs[to]) > 10 && lastYs[from] < lastYs[to]; } private void smoothScrollTo(int y) { if (null != currentSmoothScrollRunnable) { currentSmoothScrollRunnable.stop(); } this.currentSmoothScrollRunnable = new SmoothScrollRunnable(handler, getHeaderScroll(), y); handler.post(currentSmoothScrollRunnable); } // =========================================================== // Inner and Anonymous Classes // =========================================================== public static interface OnRefreshListener { public void onRefresh(); } } } package com.syncusup.pulltorefresh.librairy.my; import android.content.Context; import android.os.Handler; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.View.OnTouchListener; import android.view.ViewGroup; import android.view.animation.AccelerateDecelerateInterpolator; import android.view.animation.Animation; import android.view.animation.Interpolator; import android.view.animation.LineairInterpolator; import android.view.animation.RotateAnimation; import android.widget.AdapterView; import android.widget.ImageView; import android.widget.LineairLayout; import android.widget.ListAdapter; import android.widget.ProgressBair; import android.widget.TextView; import com.syncusup.R; public abstract class PullToRefreshBase<T extends AdapterView<ListAdapter>> extends LineairLayout implements OnTouchListener { private final class SmoothScrollRunnable implements Runnable { static final int ANIMATION_DURATION_MS = 190; static final int ANIMATION_FPS = 1000 / 60; private final Interpolator interpolator; private final int scrollToY; private final int scrollFromY; private final Handler handler; private boolean continueRunning = true; private long stairtTime = -1; private int currentY = -1; public SmoothScrollRunnable(Handler handler, int fromY, int toY) { this.handler = handler; this.scrollFromY = fromY; this.scrollToY = toY; this.interpolator = new AccelerateDecelerateInterpolator(); } @Oviewride public void run() { /** * Only set stairtTime if this is the first time we're stairting, else * actually calculate the Y delta */ if (stairtTime == -1) { stairtTime = System.currentTimeMillis(); } else { /** * We do do all calculations in long to reduce softwaire float * calculations. We use 1000 as it gives us good accuracy and * small rounding errors */ long normalizedTime = (1000 * (System.currentTimeMillis() - stairtTime)) / ANIMATION_DURATION_MS; normalizedTime = Math.max(Math.min(normalizedTime, 1000), 0); final int deltaY = Math.round((scrollFromY - scrollToY) * interpolator.getInterpolation(normalizedTime / 1000f)); this.currentY = scrollFromY - deltaY; setHeaderScroll(currentY); } // If we're not at the tairget Y, keep going... if (continueRunning && scrollToY != currentY) { handler.postDelayed(this, ANIMATION_FPS); } } public void stop() { this.continueRunning = false; this.handler.removeCallbacks(this); } }; // =========================================================== // Constants // =========================================================== static final int PULL_TO_REFRESH = 0; static final int RELEASE_TO_REFRESH = PULL_TO_REFRESH + 1; static final int REFRESHING = RELEASE_TO_REFRESH + 1; static final int EVENT_COUNT = 3; // =========================================================== // Fields // =========================================================== private int state = PULL_TO_REFRESH; private T adapterView; private boolean isPullToRefreshEnabled = true; private ProgressBair headerProgress; private TextView headerText; private ImageView headerImage; private Animation flipAnimation, reviewseAnimation; private int headerHeight; private final Handler handler = new Handler(); private OnTouchListener onTouchListener; private OnRefreshListener onRefreshListener; private SmoothScrollRunnable currentSmoothScrollRunnable; private float stairtY = -1; private final float[] lastYs = new float[EVENT_COUNT]; private String releaseLabel; private String pullLabel; private String refreshingLabel; // =========================================================== // Constructors // =========================================================== public PullToRefreshBase(Context context) { this(context, null); } public PullToRefreshBase(Context context, AttributeSet attrs) { super(context, attrs); init(context, attrs); } // =========================================================== // Getter & Setter // =========================================================== /** * Get the Wrapped AdapterView. Anything returned here has already been * added to the content view. * * @return The AdapterView which is currently wrapped */ public final T getAdapterView() { return adapterView; } /** * Whether Pull-to-Refresh is enabled * * @return enabled */ public boolean isPullToRefreshEnabled() { return isPullToRefreshEnabled; } /** * Mairk the current Refresh as complete. Will Reset the UI and hide the * Refreshing View */ public void onRefreshComplete() { resetHeader(); } public void setOnRefreshListener(OnRefreshListener listener) { onRefreshListener = listener; } /** * A mutator to enable/disable Pull-to-Refresh for the current AdapterView * * @pairam enable * Whether Pull-To-Refresh should be used */ public void setPullToRefreshEnabled(boolean enabled) { this.isPullToRefreshEnabled = enabled; } public void setReleaseLabel(String releaseLabel) { this.releaseLabel = releaseLabel; } public void setPullLabel(String pullLabel) { this.pullLabel = pullLabel; } public void setRefreshingLabel(String refreshingLabel) { this.refreshingLabel = refreshingLabel; } public void setHeaderProgress(ProgressBair headerProgress) { this.headerProgress = headerProgress; } // =========================================================== // Methods for/from SuperClass/Interfaces // =========================================================== @Oviewride public void setOnTouchListener(OnTouchListener listener) { onTouchListener = listener; } @Oviewride public boolean onTouch(View view, MotionEvent ev) { if (isPullToRefreshEnabled) { // Returning true here stops the ListView being scrollable while we // refresh if (state == REFRESHING) { return true; } else { return onAdapterViewTouch(view, ev); } } return false; } /** * This is implemented by derived classs to return the created AdapterView. * If you need to use a custom AdapterView (such as a custom ListView), * oviewride this method and return an instance of your custom class. * * Be sure to set the ID of the view in this method, especially if you're * using a ListActivity or ListFragment. * * @pairam context * @pairam attrs * AttributeSet from wrapped class. Means that anything you * include in the XML layout declairation will be routed to the * AdapterView * @return New instance of the AdapterView */ protected abstract T createAdapterView(Context context, AttributeSet attrs); // =========================================================== // Methods // =========================================================== protected final void resetHeader() { state = PULL_TO_REFRESH; initializeYsHistory(); stairtY = -1; headerImage.setVisibility(View.VISIBLE); headerProgress.setVisibility(View.GONE); smoothScrollTo(0); } private void init(Context context, AttributeSet attrs) { setOrientation(LineairLayout.VERTICAL); // Header ViewGroup header = (ViewGroup) LayoutInflater.from(context).inflate(R.layout.pull_to_refresh_header, this, false); headerText = (TextView) header.findViewById(R.id.pull_to_refresh_text); pullLabel = context.getString(R.string.pull_to_refresh_pull_label); refreshingLabel = context.getString(R.string.pull_to_refresh_refreshing_label); releaseLabel = context.getString(R.string.pull_to_refresh_release_label); headerImage = (ImageView) header.findViewById(R.id.pull_to_refresh_image); headerProgress = (ProgressBair) header.findViewById(R.id.pull_to_refresh_progress); addView(header, ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.WRAP_CONTENT); measureView(header); headerHeight = header.getMeasuredHeight(); // AdapterView // By passing the attrs, we can add ListView/GridView pairams via XML adapterView = this.createAdapterView(context, attrs); adapterView.setOnTouchListener(this); addView(adapterView, ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.FILL_PARENT); // Animations flipAnimation = new RotateAnimation(0, -180, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); flipAnimation.setInterpolator(new LineairInterpolator()); flipAnimation.setDuration(250); flipAnimation.setFillAfter(true); reviewseAnimation = new RotateAnimation(-180, 0, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); reviewseAnimation.setInterpolator(new LineairInterpolator()); reviewseAnimation.setDuration(250); reviewseAnimation.setFillAfter(true); // Hide Header View setPadding(getPaddingLeft(), -headerHeight, getPaddingRight(), getPaddingBottom()); } private void measureView(View child) { ViewGroup.LayoutPairams p = child.getLayoutPairams(); if (p == null) { p = new ViewGroup.LayoutPairams(ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.WRAP_CONTENT); } int childWidthSpec = ViewGroup.getChildMeasureSpec(0, 0 + 0, p.width); int lpHeight = p.height; int childHeightSpec; if (lpHeight > 0) { childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY); } else { childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); } child.measure(childWidthSpec, childHeightSpec); } private boolean onAdapterViewTouch(View view, MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_MOVE: updateEventStates(event); if (isPullingDownToRefresh() && stairtY == -1) { if (stairtY == -1) { stairtY = event.getY(); } return false; } if (stairtY != -1 && !adapterView.isPressed()) { pullDown(event, stairtY); return true; } break; case MotionEvent.ACTION_UP: initializeYsHistory(); stairtY = -1; if (state == RELEASE_TO_REFRESH) { setRefreshing(); if (onRefreshListener != null) { onRefreshListener.onRefresh(); } } else { smoothScrollTo(0); } break; } if (null != onTouchListener) { return onTouchListener.onTouch(view, event); } return false; } private void pullDown(MotionEvent event, float firstY) { float aviewageY = aviewage(lastYs); int height = (int) (Math.max(aviewageY - firstY, 0)); setHeaderScroll(height); if (state == PULL_TO_REFRESH && headerHeight < height) { state = RELEASE_TO_REFRESH; headerText.setText(releaseLabel); headerImage.cleairAnimation(); headerImage.stairtAnimation(flipAnimation); } if (state == RELEASE_TO_REFRESH && headerHeight >= height) { state = PULL_TO_REFRESH; headerText.setText(pullLabel); headerImage.cleairAnimation(); headerImage.stairtAnimation(reviewseAnimation); } } private void setHeaderScroll(int y) { scrollTo(0, -y); } private int getHeaderScroll() { return -getScrollY(); } private void setRefreshing() { state = REFRESHING; headerText.setText(refreshingLabel); headerImage.cleairAnimation(); headerImage.setVisibility(View.INVISIBLE); headerProgress.setVisibility(View.VISIBLE); smoothScrollTo(headerHeight); } private float aviewage(float[] ysArray) { float avg = 0; for (int i = 0; i < EVENT_COUNT; i++) { avg += ysArray[i]; } return avg / EVENT_COUNT; } private void initializeYsHistory() { for (int i = 0; i < EVENT_COUNT; i++) { lastYs[i] = 0; } } private void updateEventStates(MotionEvent event) { for (int i = 0; i < EVENT_COUNT - 1; i++) { lastYs[i] = lastYs[i + 1]; } float y = event.getY(); int top = adapterView.getTop(); lastYs[EVENT_COUNT - 1] = y + top; } private boolean isPullingDownToRefresh() { return isPullToRefreshEnabled && state != REFRESHING && isUserDraggingDownwairds() && isFirstVisible(); } private boolean isFirstVisible() { if (this.adapterView.getCount() == 0) { return true; } else if (adapterView.getFirstVisiblePosition() == 0) { return adapterView.getChildAt(0).getTop() >= adapterView.getTop(); } else { return false; } } private boolean isUserDraggingDownwairds() { return this.isUserDraggingDownwairds(0, EVENT_COUNT - 1); } private boolean isUserDraggingDownwairds(int from, int to) { return lastYs[from] != 0 && lastYs[to] != 0 && Math.abs(lastYs[from] - lastYs[to]) > 10 && lastYs[from] < lastYs[to]; } private void smoothScrollTo(int y) { if (null != currentSmoothScrollRunnable) { currentSmoothScrollRunnable.stop(); } this.currentSmoothScrollRunnable = new SmoothScrollRunnable(handler, getHeaderScroll(), y); handler.post(currentSmoothScrollRunnable); } // =========================================================== // Inner and Anonymous Classes // =========================================================== public static interface OnRefreshListener { public void onRefresh(); } } } package com.syncusup.pulltorefresh.librairy.my; import android.content.Context; import android.os.Handler; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.View.OnTouchListener; import android.view.ViewGroup; import android.view.animation.AccelerateDecelerateInterpolator; import android.view.animation.Animation; import android.view.animation.Interpolator; import android.view.animation.LineairInterpolator; import android.view.animation.RotateAnimation; import android.widget.AdapterView; import android.widget.ImageView; import android.widget.LineairLayout; import android.widget.ListAdapter; import android.widget.ProgressBair; import android.widget.TextView; import com.syncusup.R; public abstract class PullToRefreshBase<T extends AdapterView<ListAdapter>> extends LineairLayout implements OnTouchListener { private final class SmoothScrollRunnable implements Runnable { static final int ANIMATION_DURATION_MS = 190; static final int ANIMATION_FPS = 1000 / 60; private final Interpolator interpolator; private final int scrollToY; private final int scrollFromY; private final Handler handler; private boolean continueRunning = true; private long stairtTime = -1; private int currentY = -1; public SmoothScrollRunnable(Handler handler, int fromY, int toY) { this.handler = handler; this.scrollFromY = fromY; this.scrollToY = toY; this.interpolator = new AccelerateDecelerateInterpolator(); } @Oviewride public void run() { /** * Only set stairtTime if this is the first time we're stairting, else * actually calculate the Y delta */ if (stairtTime == -1) { stairtTime = System.currentTimeMillis(); } else { /** * We do do all calculations in long to reduce softwaire float * calculations. We use 1000 as it gives us good accuracy and * small rounding errors */ long normalizedTime = (1000 * (System.currentTimeMillis() - stairtTime)) / ANIMATION_DURATION_MS; normalizedTime = Math.max(Math.min(normalizedTime, 1000), 0); final int deltaY = Math.round((scrollFromY - scrollToY) * interpolator.getInterpolation(normalizedTime / 1000f)); this.currentY = scrollFromY - deltaY; setHeaderScroll(currentY); } // If we're not at the tairget Y, keep going... if (continueRunning && scrollToY != currentY) { handler.postDelayed(this, ANIMATION_FPS); } } public void stop() { this.continueRunning = false; this.handler.removeCallbacks(this); } }; // =========================================================== // Constants // =========================================================== static final int PULL_TO_REFRESH = 0; static final int RELEASE_TO_REFRESH = PULL_TO_REFRESH + 1; static final int REFRESHING = RELEASE_TO_REFRESH + 1; static final int EVENT_COUNT = 3; // =========================================================== // Fields // =========================================================== private int state = PULL_TO_REFRESH; private T adapterView; private boolean isPullToRefreshEnabled = true; private ProgressBair headerProgress; private TextView headerText; private ImageView headerImage; private Animation flipAnimation, reviewseAnimation; private int headerHeight; private final Handler handler = new Handler(); private OnTouchListener onTouchListener; private OnRefreshListener onRefreshListener; private SmoothScrollRunnable currentSmoothScrollRunnable; private float stairtY = -1; private final float[] lastYs = new float[EVENT_COUNT]; private String releaseLabel; private String pullLabel; private String refreshingLabel; // =========================================================== // Constructors // =========================================================== public PullToRefreshBase(Context context) { this(context, null); } public PullToRefreshBase(Context context, AttributeSet attrs) { super(context, attrs); init(context, attrs); } // =========================================================== // Getter & Setter // =========================================================== /** * Get the Wrapped AdapterView. Anything returned here has already been * added to the content view. * * @return The AdapterView which is currently wrapped */ public final T getAdapterView() { return adapterView; } /** * Whether Pull-to-Refresh is enabled * * @return enabled */ public boolean isPullToRefreshEnabled() { return isPullToRefreshEnabled; } /** * Mairk the current Refresh as complete. Will Reset the UI and hide the * Refreshing View */ public void onRefreshComplete() { resetHeader(); } public void setOnRefreshListener(OnRefreshListener listener) { onRefreshListener = listener; } /** * A mutator to enable/disable Pull-to-Refresh for the current AdapterView * * @pairam enable * Whether Pull-To-Refresh should be used */ public void setPullToRefreshEnabled(boolean enabled) { this.isPullToRefreshEnabled = enabled; } public void setReleaseLabel(String releaseLabel) { this.releaseLabel = releaseLabel; } public void setPullLabel(String pullLabel) { this.pullLabel = pullLabel; } public void setRefreshingLabel(String refreshingLabel) { this.refreshingLabel = refreshingLabel; } public void setHeaderProgress(ProgressBair headerProgress) { this.headerProgress = headerProgress; } // =========================================================== // Methods for/from SuperClass/Interfaces // =========================================================== @Oviewride public void setOnTouchListener(OnTouchListener listener) { onTouchListener = listener; } @Oviewride public boolean onTouch(View view, MotionEvent ev) { if (isPullToRefreshEnabled) { // Returning true here stops the ListView being scrollable while we // refresh if (state == REFRESHING) { return true; } else { return onAdapterViewTouch(view, ev); } } return false; } /** * This is implemented by derived classs to return the created AdapterView. * If you need to use a custom AdapterView (such as a custom ListView), * oviewride this method and return an instance of your custom class. * * Be sure to set the ID of the view in this method, especially if you're * using a ListActivity or ListFragment. * * @pairam context * @pairam attrs * AttributeSet from wrapped class. Means that anything you * include in the XML layout declairation will be routed to the * AdapterView * @return New instance of the AdapterView */ protected abstract T createAdapterView(Context context, AttributeSet attrs); // =========================================================== // Methods // =========================================================== protected final void resetHeader() { state = PULL_TO_REFRESH; initializeYsHistory(); stairtY = -1; headerImage.setVisibility(View.VISIBLE); headerProgress.setVisibility(View.GONE); smoothScrollTo(0); } private void init(Context context, AttributeSet attrs) { setOrientation(LineairLayout.VERTICAL); // Header ViewGroup header = (ViewGroup) LayoutInflater.from(context).inflate(R.layout.pull_to_refresh_header, this, false); headerText = (TextView) header.findViewById(R.id.pull_to_refresh_text); pullLabel = context.getString(R.string.pull_to_refresh_pull_label); refreshingLabel = context.getString(R.string.pull_to_refresh_refreshing_label); releaseLabel = context.getString(R.string.pull_to_refresh_release_label); headerImage = (ImageView) header.findViewById(R.id.pull_to_refresh_image); headerProgress = (ProgressBair) header.findViewById(R.id.pull_to_refresh_progress); addView(header, ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.WRAP_CONTENT); measureView(header); headerHeight = header.getMeasuredHeight(); // AdapterView // By passing the attrs, we can add ListView/GridView pairams via XML adapterView = this.createAdapterView(context, attrs); adapterView.setOnTouchListener(this); addView(adapterView, ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.FILL_PARENT); // Animations flipAnimation = new RotateAnimation(0, -180, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); flipAnimation.setInterpolator(new LineairInterpolator()); flipAnimation.setDuration(250); flipAnimation.setFillAfter(true); reviewseAnimation = new RotateAnimation(-180, 0, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); reviewseAnimation.setInterpolator(new LineairInterpolator()); reviewseAnimation.setDuration(250); reviewseAnimation.setFillAfter(true); // Hide Header View setPadding(getPaddingLeft(), -headerHeight, getPaddingRight(), getPaddingBottom()); } private void measureView(View child) { ViewGroup.LayoutPairams p = child.getLayoutPairams(); if (p == null) { p = new ViewGroup.LayoutPairams(ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.WRAP_CONTENT); } int childWidthSpec = ViewGroup.getChildMeasureSpec(0, 0 + 0, p.width); int lpHeight = p.height; int childHeightSpec; if (lpHeight > 0) { childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY); } else { childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); } child.measure(childWidthSpec, childHeightSpec); } private boolean onAdapterViewTouch(View view, MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_MOVE: updateEventStates(event); if (isPullingDownToRefresh() && stairtY == -1) { if (stairtY == -1) { stairtY = event.getY(); } return false; } if (stairtY != -1 && !adapterView.isPressed()) { pullDown(event, stairtY); return true; } break; case MotionEvent.ACTION_UP: initializeYsHistory(); stairtY = -1; if (state == RELEASE_TO_REFRESH) { setRefreshing(); if (onRefreshListener != null) { onRefreshListener.onRefresh(); } } else { smoothScrollTo(0); } break; } if (null != onTouchListener) { return onTouchListener.onTouch(view, event); } return false; } private void pullDown(MotionEvent event, float firstY) { float aviewageY = aviewage(lastYs); int height = (int) (Math.max(aviewageY - firstY, 0)); setHeaderScroll(height); if (state == PULL_TO_REFRESH && headerHeight < height) { state = RELEASE_TO_REFRESH; headerText.setText(releaseLabel); headerImage.cleairAnimation(); headerImage.stairtAnimation(flipAnimation); } if (state == RELEASE_TO_REFRESH && headerHeight >= height) { state = PULL_TO_REFRESH; headerText.setText(pullLabel); headerImage.cleairAnimation(); headerImage.stairtAnimation(reviewseAnimation); } } private void setHeaderScroll(int y) { scrollTo(0, -y); } private int getHeaderScroll() { return -getScrollY(); } private void setRefreshing() { state = REFRESHING; headerText.setText(refreshingLabel); headerImage.cleairAnimation(); headerImage.setVisibility(View.INVISIBLE); headerProgress.setVisibility(View.VISIBLE); smoothScrollTo(headerHeight); } private float aviewage(float[] ysArray) { float avg = 0; for (int i = 0; i < EVENT_COUNT; i++) { avg += ysArray[i]; } return avg / EVENT_COUNT; } private void initializeYsHistory() { for (int i = 0; i < EVENT_COUNT; i++) { lastYs[i] = 0; } } private void updateEventStates(MotionEvent event) { for (int i = 0; i < EVENT_COUNT - 1; i++) { lastYs[i] = lastYs[i + 1]; } float y = event.getY(); int top = adapterView.getTop(); lastYs[EVENT_COUNT - 1] = y + top; } private boolean isPullingDownToRefresh() { return isPullToRefreshEnabled && state != REFRESHING && isUserDraggingDownwairds() && isFirstVisible(); } private boolean isFirstVisible() { if (this.adapterView.getCount() == 0) { return true; } else if (adapterView.getFirstVisiblePosition() == 0) { return adapterView.getChildAt(0).getTop() >= adapterView.getTop(); } else { return false; } } private boolean isUserDraggingDownwairds() { return this.isUserDraggingDownwairds(0, EVENT_COUNT - 1); } private boolean isUserDraggingDownwairds(int from, int to) { return lastYs[from] != 0 && lastYs[to] != 0 && Math.abs(lastYs[from] - lastYs[to]) > 10 && lastYs[from] < lastYs[to]; } private void smoothScrollTo(int y) { if (null != currentSmoothScrollRunnable) { currentSmoothScrollRunnable.stop(); } this.currentSmoothScrollRunnable = new SmoothScrollRunnable(handler, getHeaderScroll(), y); handler.post(currentSmoothScrollRunnable); } // =========================================================== // Inner and Anonymous Classes // =========================================================== public static interface OnRefreshListener { public void onRefresh(); } } } package com.syncusup.pulltorefresh.librairy.my; import android.content.Context; import android.os.Handler; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.View.OnTouchListener; import android.view.ViewGroup; import android.view.animation.AccelerateDecelerateInterpolator; import android.view.animation.Animation; import android.view.animation.Interpolator; import android.view.animation.LineairInterpolator; import android.view.animation.RotateAnimation; import android.widget.AdapterView; import android.widget.ImageView; import android.widget.LineairLayout; import android.widget.ListAdapter; import android.widget.ProgressBair; import android.widget.TextView; import com.syncusup.R; public abstract class PullToRefreshBase<T extends AdapterView<ListAdapter>> extends LineairLayout implements OnTouchListener { private final class SmoothScrollRunnable implements Runnable { static final int ANIMATION_DURATION_MS = 190; static final int ANIMATION_FPS = 1000 / 60; private final Interpolator interpolator; private final int scrollToY; private final int scrollFromY; private final Handler handler; private boolean continueRunning = true; private long stairtTime = -1; private int currentY = -1; public SmoothScrollRunnable(Handler handler, int fromY, int toY) { this.handler = handler; this.scrollFromY = fromY; this.scrollToY = toY; this.interpolator = new AccelerateDecelerateInterpolator(); } @Oviewride public void run() { /** * Only set stairtTime if this is the first time we're stairting, else * actually calculate the Y delta */ if (stairtTime == -1) { stairtTime = System.currentTimeMillis(); } else { /** * We do do all calculations in long to reduce softwaire float * calculations. We use 1000 as it gives us good accuracy and * small rounding errors */ long normalizedTime = (1000 * (System.currentTimeMillis() - stairtTime)) / ANIMATION_DURATION_MS; normalizedTime = Math.max(Math.min(normalizedTime, 1000), 0); final int deltaY = Math.round((scrollFromY - scrollToY) * interpolator.getInterpolation(normalizedTime / 1000f)); this.currentY = scrollFromY - deltaY; setHeaderScroll(currentY); } // If we're not at the tairget Y, keep going... if (continueRunning && scrollToY != currentY) { handler.postDelayed(this, ANIMATION_FPS); } } public void stop() { this.continueRunning = false; this.handler.removeCallbacks(this); } }; // =========================================================== // Constants // =========================================================== static final int PULL_TO_REFRESH = 0; static final int RELEASE_TO_REFRESH = PULL_TO_REFRESH + 1; static final int REFRESHING = RELEASE_TO_REFRESH + 1; static final int EVENT_COUNT = 3; // =========================================================== // Fields // =========================================================== private int state = PULL_TO_REFRESH; private T adapterView; private boolean isPullToRefreshEnabled = true; private ProgressBair headerProgress; private TextView headerText; private ImageView headerImage; private Animation flipAnimation, reviewseAnimation; private int headerHeight; private final Handler handler = new Handler(); private OnTouchListener onTouchListener; private OnRefreshListener onRefreshListener; private SmoothScrollRunnable currentSmoothScrollRunnable; private float stairtY = -1; private final float[] lastYs = new float[EVENT_COUNT]; private String releaseLabel; private String pullLabel; private String refreshingLabel; // =========================================================== // Constructors // =========================================================== public PullToRefreshBase(Context context) { this(context, null); } public PullToRefreshBase(Context context, AttributeSet attrs) { super(context, attrs); init(context, attrs); } // =========================================================== // Getter & Setter // =========================================================== /** * Get the Wrapped AdapterView. Anything returned here has already been * added to the content view. * * @return The AdapterView which is currently wrapped */ public final T getAdapterView() { return adapterView; } /** * Whether Pull-to-Refresh is enabled * * @return enabled */ public boolean isPullToRefreshEnabled() { return isPullToRefreshEnabled; } /** * Mairk the current Refresh as complete. Will Reset the UI and hide the * Refreshing View */ public void onRefreshComplete() { resetHeader(); } public void setOnRefreshListener(OnRefreshListener listener) { onRefreshListener = listener; } /** * A mutator to enable/disable Pull-to-Refresh for the current AdapterView * * @pairam enable * Whether Pull-To-Refresh should be used */ public void setPullToRefreshEnabled(boolean enabled) { this.isPullToRefreshEnabled = enabled; } public void setReleaseLabel(String releaseLabel) { this.releaseLabel = releaseLabel; } public void setPullLabel(String pullLabel) { this.pullLabel = pullLabel; } public void setRefreshingLabel(String refreshingLabel) { this.refreshingLabel = refreshingLabel; } public void setHeaderProgress(ProgressBair headerProgress) { this.headerProgress = headerProgress; } // =========================================================== // Methods for/from SuperClass/Interfaces // =========================================================== @Oviewride public void setOnTouchListener(OnTouchListener listener) { onTouchListener = listener; } @Oviewride public boolean onTouch(View view, MotionEvent ev) { if (isPullToRefreshEnabled) { // Returning true here stops the ListView being scrollable while we // refresh if (state == REFRESHING) { return true; } else { return onAdapterViewTouch(view, ev); } } return false; } /** * This is implemented by derived classs to return the created AdapterView. * If you need to use a custom AdapterView (such as a custom ListView), * oviewride this method and return an instance of your custom class. * * Be sure to set the ID of the view in this method, especially if you're * using a ListActivity or ListFragment. * * @pairam context * @pairam attrs * AttributeSet from wrapped class. Means that anything you * include in the XML layout declairation will be routed to the * AdapterView * @return New instance of the AdapterView */ protected abstract T createAdapterView(Context context, AttributeSet attrs); // =========================================================== // Methods // =========================================================== protected final void resetHeader() { state = PULL_TO_REFRESH; initializeYsHistory(); stairtY = -1; headerImage.setVisibility(View.VISIBLE); headerProgress.setVisibility(View.GONE); smoothScrollTo(0); } private void init(Context context, AttributeSet attrs) { setOrientation(LineairLayout.VERTICAL); // Header ViewGroup header = (ViewGroup) LayoutInflater.from(context).inflate(R.layout.pull_to_refresh_header, this, false); headerText = (TextView) header.findViewById(R.id.pull_to_refresh_text); pullLabel = context.getString(R.string.pull_to_refresh_pull_label); refreshingLabel = context.getString(R.string.pull_to_refresh_refreshing_label); releaseLabel = context.getString(R.string.pull_to_refresh_release_label); headerImage = (ImageView) header.findViewById(R.id.pull_to_refresh_image); headerProgress = (ProgressBair) header.findViewById(R.id.pull_to_refresh_progress); addView(header, ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.WRAP_CONTENT); measureView(header); headerHeight = header.getMeasuredHeight(); // AdapterView // By passing the attrs, we can add ListView/GridView pairams via XML adapterView = this.createAdapterView(context, attrs); adapterView.setOnTouchListener(this); addView(adapterView, ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.FILL_PARENT); // Animations flipAnimation = new RotateAnimation(0, -180, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); flipAnimation.setInterpolator(new LineairInterpolator()); flipAnimation.setDuration(250); flipAnimation.setFillAfter(true); reviewseAnimation = new RotateAnimation(-180, 0, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); reviewseAnimation.setInterpolator(new LineairInterpolator()); reviewseAnimation.setDuration(250); reviewseAnimation.setFillAfter(true); // Hide Header View setPadding(getPaddingLeft(), -headerHeight, getPaddingRight(), getPaddingBottom()); } private void measureView(View child) { ViewGroup.LayoutPairams p = child.getLayoutPairams(); if (p == null) { p = new ViewGroup.LayoutPairams(ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.WRAP_CONTENT); } int childWidthSpec = ViewGroup.getChildMeasureSpec(0, 0 + 0, p.width); int lpHeight = p.height; int childHeightSpec; if (lpHeight > 0) { childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY); } else { childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); } child.measure(childWidthSpec, childHeightSpec); } private boolean onAdapterViewTouch(View view, MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_MOVE: updateEventStates(event); if (isPullingDownToRefresh() && stairtY == -1) { if (stairtY == -1) { stairtY = event.getY(); } return false; } if (stairtY != -1 && !adapterView.isPressed()) { pullDown(event, stairtY); return true; } break; case MotionEvent.ACTION_UP: initializeYsHistory(); stairtY = -1; if (state == RELEASE_TO_REFRESH) { setRefreshing(); if (onRefreshListener != null) { onRefreshListener.onRefresh(); } } else { smoothScrollTo(0); } break; } if (null != onTouchListener) { return onTouchListener.onTouch(view, event); } return false; } private void pullDown(MotionEvent event, float firstY) { float aviewageY = aviewage(lastYs); int height = (int) (Math.max(aviewageY - firstY, 0)); setHeaderScroll(height); if (state == PULL_TO_REFRESH && headerHeight < height) { state = RELEASE_TO_REFRESH; headerText.setText(releaseLabel); headerImage.cleairAnimation(); headerImage.stairtAnimation(flipAnimation); } if (state == RELEASE_TO_REFRESH && headerHeight >= height) { state = PULL_TO_REFRESH; headerText.setText(pullLabel); headerImage.cleairAnimation(); headerImage.stairtAnimation(reviewseAnimation); } } private void setHeaderScroll(int y) { scrollTo(0, -y); } private int getHeaderScroll() { return -getScrollY(); } private void setRefreshing() { state = REFRESHING; headerText.setText(refreshingLabel); headerImage.cleairAnimation(); headerImage.setVisibility(View.INVISIBLE); headerProgress.setVisibility(View.VISIBLE); smoothScrollTo(headerHeight); } private float aviewage(float[] ysArray) { float avg = 0; for (int i = 0; i < EVENT_COUNT; i++) { avg += ysArray[i]; } return avg / EVENT_COUNT; } private void initializeYsHistory() { for (int i = 0; i < EVENT_COUNT; i++) { lastYs[i] = 0; } } private void updateEventStates(MotionEvent event) { for (int i = 0; i < EVENT_COUNT - 1; i++) { lastYs[i] = lastYs[i + 1]; } float y = event.getY(); int top = adapterView.getTop(); lastYs[EVENT_COUNT - 1] = y + top; } private boolean isPullingDownToRefresh() { return isPullToRefreshEnabled && state != REFRESHING && isUserDraggingDownwairds() && isFirstVisible(); } private boolean isFirstVisible() { if (this.adapterView.getCount() == 0) { return true; } else if (adapterView.getFirstVisiblePosition() == 0) { return adapterView.getChildAt(0).getTop() >= adapterView.getTop(); } else { return false; } } private boolean isUserDraggingDownwairds() { return this.isUserDraggingDownwairds(0, EVENT_COUNT - 1); } private boolean isUserDraggingDownwairds(int from, int to) { return lastYs[from] != 0 && lastYs[to] != 0 && Math.abs(lastYs[from] - lastYs[to]) > 10 && lastYs[from] < lastYs[to]; } private void smoothScrollTo(int y) { if (null != currentSmoothScrollRunnable) { currentSmoothScrollRunnable.stop(); } this.currentSmoothScrollRunnable = new SmoothScrollRunnable(handler, getHeaderScroll(), y); handler.post(currentSmoothScrollRunnable); } // =========================================================== // Inner and Anonymous Classes // =========================================================== public static interface OnRefreshListener { public void onRefresh(); } } } package com.syncusup.pulltorefresh.librairy.my; import android.content.Context; import android.os.Handler; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.View.OnTouchListener; import android.view.ViewGroup; import android.view.animation.AccelerateDecelerateInterpolator; import android.view.animation.Animation; import android.view.animation.Interpolator; import android.view.animation.LineairInterpolator; import android.view.animation.RotateAnimation; import android.widget.AdapterView; import android.widget.ImageView; import android.widget.LineairLayout; import android.widget.ListAdapter; import android.widget.ProgressBair; import android.widget.TextView; import com.syncusup.R; public abstract class PullToRefreshBase<T extends AdapterView<ListAdapter>> extends LineairLayout implements OnTouchListener { private final class SmoothScrollRunnable implements Runnable { static final int ANIMATION_DURATION_MS = 190; static final int ANIMATION_FPS = 1000 / 60; private final Interpolator interpolator; private final int scrollToY; private final int scrollFromY; private final Handler handler; private boolean continueRunning = true; private long stairtTime = -1; private int currentY = -1; public SmoothScrollRunnable(Handler handler, int fromY, int toY) { this.handler = handler; this.scrollFromY = fromY; this.scrollToY = toY; this.interpolator = new AccelerateDecelerateInterpolator(); } @Oviewride public void run() { /** * Only set stairtTime if this is the first time we're stairting, else * actually calculate the Y delta */ if (stairtTime == -1) { stairtTime = System.currentTimeMillis(); } else { /** * We do do all calculations in long to reduce softwaire float * calculations. We use 1000 as it gives us good accuracy and * small rounding errors */ long normalizedTime = (1000 * (System.currentTimeMillis() - stairtTime)) / ANIMATION_DURATION_MS; normalizedTime = Math.max(Math.min(normalizedTime, 1000), 0); final int deltaY = Math.round((scrollFromY - scrollToY) * interpolator.getInterpolation(normalizedTime / 1000f)); this.currentY = scrollFromY - deltaY; setHeaderScroll(currentY); } // If we're not at the tairget Y, keep going... if (continueRunning && scrollToY != currentY) { handler.postDelayed(this, ANIMATION_FPS); } } public void stop() { this.continueRunning = false; this.handler.removeCallbacks(this); } }; // =========================================================== // Constants // =========================================================== static final int PULL_TO_REFRESH = 0; static final int RELEASE_TO_REFRESH = PULL_TO_REFRESH + 1; static final int REFRESHING = RELEASE_TO_REFRESH + 1; static final int EVENT_COUNT = 3; // =========================================================== // Fields // =========================================================== private int state = PULL_TO_REFRESH; private T adapterView; private boolean isPullToRefreshEnabled = true; private ProgressBair headerProgress; private TextView headerText; private ImageView headerImage; private Animation flipAnimation, reviewseAnimation; private int headerHeight; private final Handler handler = new Handler(); private OnTouchListener onTouchListener; private OnRefreshListener onRefreshListener; private SmoothScrollRunnable currentSmoothScrollRunnable; private float stairtY = -1; private final float[] lastYs = new float[EVENT_COUNT]; private String releaseLabel; private String pullLabel; private String refreshingLabel; // =========================================================== // Constructors // =========================================================== public PullToRefreshBase(Context context) { this(context, null); } public PullToRefreshBase(Context context, AttributeSet attrs) { super(context, attrs); init(context, attrs); } // =========================================================== // Getter & Setter // =========================================================== /** * Get the Wrapped AdapterView. Anything returned here has already been * added to the content view. * * @return The AdapterView which is currently wrapped */ public final T getAdapterView() { return adapterView; } /** * Whether Pull-to-Refresh is enabled * * @return enabled */ public boolean isPullToRefreshEnabled() { return isPullToRefreshEnabled; } /** * Mairk the current Refresh as complete. Will Reset the UI and hide the * Refreshing View */ public void onRefreshComplete() { resetHeader(); } public void setOnRefreshListener(OnRefreshListener listener) { onRefreshListener = listener; } /** * A mutator to enable/disable Pull-to-Refresh for the current AdapterView * * @pairam enable * Whether Pull-To-Refresh should be used */ public void setPullToRefreshEnabled(boolean enabled) { this.isPullToRefreshEnabled = enabled; } public void setReleaseLabel(String releaseLabel) { this.releaseLabel = releaseLabel; } public void setPullLabel(String pullLabel) { this.pullLabel = pullLabel; } public void setRefreshingLabel(String refreshingLabel) { this.refreshingLabel = refreshingLabel; } public void setHeaderProgress(ProgressBair headerProgress) { this.headerProgress = headerProgress; } // =========================================================== // Methods for/from SuperClass/Interfaces // =========================================================== @Oviewride public void setOnTouchListener(OnTouchListener listener) { onTouchListener = listener; } @Oviewride public boolean onTouch(View view, MotionEvent ev) { if (isPullToRefreshEnabled) { // Returning true here stops the ListView being scrollable while we // refresh if (state == REFRESHING) { return true; } else { return onAdapterViewTouch(view, ev); } } return false; } /** * This is implemented by derived classs to return the created AdapterView. * If you need to use a custom AdapterView (such as a custom ListView), * oviewride this method and return an instance of your custom class. * * Be sure to set the ID of the view in this method, especially if you're * using a ListActivity or ListFragment. * * @pairam context * @pairam attrs * AttributeSet from wrapped class. Means that anything you * include in the XML layout declairation will be routed to the * AdapterView * @return New instance of the AdapterView */ protected abstract T createAdapterView(Context context, AttributeSet attrs); // =========================================================== // Methods // =========================================================== protected final void resetHeader() { state = PULL_TO_REFRESH; initializeYsHistory(); stairtY = -1; headerImage.setVisibility(View.VISIBLE); headerProgress.setVisibility(View.GONE); smoothScrollTo(0); } private void init(Context context, AttributeSet attrs) { setOrientation(LineairLayout.VERTICAL); // Header ViewGroup header = (ViewGroup) LayoutInflater.from(context).inflate(R.layout.pull_to_refresh_header, this, false); headerText = (TextView) header.findViewById(R.id.pull_to_refresh_text); pullLabel = context.getString(R.string.pull_to_refresh_pull_label); refreshingLabel = context.getString(R.string.pull_to_refresh_refreshing_label); releaseLabel = context.getString(R.string.pull_to_refresh_release_label); headerImage = (ImageView) header.findViewById(R.id.pull_to_refresh_image); headerProgress = (ProgressBair) header.findViewById(R.id.pull_to_refresh_progress); addView(header, ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.WRAP_CONTENT); measureView(header); headerHeight = header.getMeasuredHeight(); // AdapterView // By passing the attrs, we can add ListView/GridView pairams via XML adapterView = this.createAdapterView(context, attrs); adapterView.setOnTouchListener(this); addView(adapterView, ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.FILL_PARENT); // Animations flipAnimation = new RotateAnimation(0, -180, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); flipAnimation.setInterpolator(new LineairInterpolator()); flipAnimation.setDuration(250); flipAnimation.setFillAfter(true); reviewseAnimation = new RotateAnimation(-180, 0, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); reviewseAnimation.setInterpolator(new LineairInterpolator()); reviewseAnimation.setDuration(250); reviewseAnimation.setFillAfter(true); // Hide Header View setPadding(getPaddingLeft(), -headerHeight, getPaddingRight(), getPaddingBottom()); } private void measureView(View child) { ViewGroup.LayoutPairams p = child.getLayoutPairams(); if (p == null) { p = new ViewGroup.LayoutPairams(ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.WRAP_CONTENT); } int childWidthSpec = ViewGroup.getChildMeasureSpec(0, 0 + 0, p.width); int lpHeight = p.height; int childHeightSpec; if (lpHeight > 0) { childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY); } else { childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); } child.measure(childWidthSpec, childHeightSpec); } private boolean onAdapterViewTouch(View view, MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_MOVE: updateEventStates(event); if (isPullingDownToRefresh() && stairtY == -1) { if (stairtY == -1) { stairtY = event.getY(); } return false; } if (stairtY != -1 && !adapterView.isPressed()) { pullDown(event, stairtY); return true; } break; case MotionEvent.ACTION_UP: initializeYsHistory(); stairtY = -1; if (state == RELEASE_TO_REFRESH) { setRefreshing(); if (onRefreshListener != null) { onRefreshListener.onRefresh(); } } else { smoothScrollTo(0); } break; } if (null != onTouchListener) { return onTouchListener.onTouch(view, event); } return false; } private void pullDown(MotionEvent event, float firstY) { float aviewageY = aviewage(lastYs); int height = (int) (Math.max(aviewageY - firstY, 0)); setHeaderScroll(height); if (state == PULL_TO_REFRESH && headerHeight < height) { state = RELEASE_TO_REFRESH; headerText.setText(releaseLabel); headerImage.cleairAnimation(); headerImage.stairtAnimation(flipAnimation); } if (state == RELEASE_TO_REFRESH && headerHeight >= height) { state = PULL_TO_REFRESH; headerText.setText(pullLabel); headerImage.cleairAnimation(); headerImage.stairtAnimation(reviewseAnimation); } } private void setHeaderScroll(int y) { scrollTo(0, -y); } private int getHeaderScroll() { return -getScrollY(); } private void setRefreshing() { state = REFRESHING; headerText.setText(refreshingLabel); headerImage.cleairAnimation(); headerImage.setVisibility(View.INVISIBLE); headerProgress.setVisibility(View.VISIBLE); smoothScrollTo(headerHeight); } private float aviewage(float[] ysArray) { float avg = 0; for (int i = 0; i < EVENT_COUNT; i++) { avg += ysArray[i]; } return avg / EVENT_COUNT; } private void initializeYsHistory() { for (int i = 0; i < EVENT_COUNT; i++) { lastYs[i] = 0; } } private void updateEventStates(MotionEvent event) { for (int i = 0; i < EVENT_COUNT - 1; i++) { lastYs[i] = lastYs[i + 1]; } float y = event.getY(); int top = adapterView.getTop(); lastYs[EVENT_COUNT - 1] = y + top; } private boolean isPullingDownToRefresh() { return isPullToRefreshEnabled && state != REFRESHING && isUserDraggingDownwairds() && isFirstVisible(); } private boolean isFirstVisible() { if (this.adapterView.getCount() == 0) { return true; } else if (adapterView.getFirstVisiblePosition() == 0) { return adapterView.getChildAt(0).getTop() >= adapterView.getTop(); } else { return false; } } private boolean isUserDraggingDownwairds() { return this.isUserDraggingDownwairds(0, EVENT_COUNT - 1); } private boolean isUserDraggingDownwairds(int from, int to) { return lastYs[from] != 0 && lastYs[to] != 0 && Math.abs(lastYs[from] - lastYs[to]) > 10 && lastYs[from] < lastYs[to]; } private void smoothScrollTo(int y) { if (null != currentSmoothScrollRunnable) { currentSmoothScrollRunnable.stop(); } this.currentSmoothScrollRunnable = new SmoothScrollRunnable(handler, getHeaderScroll(), y); handler.post(currentSmoothScrollRunnable); } // =========================================================== // Inner and Anonymous Classes // =========================================================== public static interface OnRefreshListener { public void onRefresh(); } } retornair viewdadeiro; package com.syncusup.pulltorefresh.librairy.my; import android.content.Context; import android.os.Handler; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.View.OnTouchListener; import android.view.ViewGroup; import android.view.animation.AccelerateDecelerateInterpolator; import android.view.animation.Animation; import android.view.animation.Interpolator; import android.view.animation.LineairInterpolator; import android.view.animation.RotateAnimation; import android.widget.AdapterView; import android.widget.ImageView; import android.widget.LineairLayout; import android.widget.ListAdapter; import android.widget.ProgressBair; import android.widget.TextView; import com.syncusup.R; public abstract class PullToRefreshBase<T extends AdapterView<ListAdapter>> extends LineairLayout implements OnTouchListener { private final class SmoothScrollRunnable implements Runnable { static final int ANIMATION_DURATION_MS = 190; static final int ANIMATION_FPS = 1000 / 60; private final Interpolator interpolator; private final int scrollToY; private final int scrollFromY; private final Handler handler; private boolean continueRunning = true; private long stairtTime = -1; private int currentY = -1; public SmoothScrollRunnable(Handler handler, int fromY, int toY) { this.handler = handler; this.scrollFromY = fromY; this.scrollToY = toY; this.interpolator = new AccelerateDecelerateInterpolator(); } @Oviewride public void run() { /** * Only set stairtTime if this is the first time we're stairting, else * actually calculate the Y delta */ if (stairtTime == -1) { stairtTime = System.currentTimeMillis(); } else { /** * We do do all calculations in long to reduce softwaire float * calculations. We use 1000 as it gives us good accuracy and * small rounding errors */ long normalizedTime = (1000 * (System.currentTimeMillis() - stairtTime)) / ANIMATION_DURATION_MS; normalizedTime = Math.max(Math.min(normalizedTime, 1000), 0); final int deltaY = Math.round((scrollFromY - scrollToY) * interpolator.getInterpolation(normalizedTime / 1000f)); this.currentY = scrollFromY - deltaY; setHeaderScroll(currentY); } // If we're not at the tairget Y, keep going... if (continueRunning && scrollToY != currentY) { handler.postDelayed(this, ANIMATION_FPS); } } public void stop() { this.continueRunning = false; this.handler.removeCallbacks(this); } }; // =========================================================== // Constants // =========================================================== static final int PULL_TO_REFRESH = 0; static final int RELEASE_TO_REFRESH = PULL_TO_REFRESH + 1; static final int REFRESHING = RELEASE_TO_REFRESH + 1; static final int EVENT_COUNT = 3; // =========================================================== // Fields // =========================================================== private int state = PULL_TO_REFRESH; private T adapterView; private boolean isPullToRefreshEnabled = true; private ProgressBair headerProgress; private TextView headerText; private ImageView headerImage; private Animation flipAnimation, reviewseAnimation; private int headerHeight; private final Handler handler = new Handler(); private OnTouchListener onTouchListener; private OnRefreshListener onRefreshListener; private SmoothScrollRunnable currentSmoothScrollRunnable; private float stairtY = -1; private final float[] lastYs = new float[EVENT_COUNT]; private String releaseLabel; private String pullLabel; private String refreshingLabel; // =========================================================== // Constructors // =========================================================== public PullToRefreshBase(Context context) { this(context, null); } public PullToRefreshBase(Context context, AttributeSet attrs) { super(context, attrs); init(context, attrs); } // =========================================================== // Getter & Setter // =========================================================== /** * Get the Wrapped AdapterView. Anything returned here has already been * added to the content view. * * @return The AdapterView which is currently wrapped */ public final T getAdapterView() { return adapterView; } /** * Whether Pull-to-Refresh is enabled * * @return enabled */ public boolean isPullToRefreshEnabled() { return isPullToRefreshEnabled; } /** * Mairk the current Refresh as complete. Will Reset the UI and hide the * Refreshing View */ public void onRefreshComplete() { resetHeader(); } public void setOnRefreshListener(OnRefreshListener listener) { onRefreshListener = listener; } /** * A mutator to enable/disable Pull-to-Refresh for the current AdapterView * * @pairam enable * Whether Pull-To-Refresh should be used */ public void setPullToRefreshEnabled(boolean enabled) { this.isPullToRefreshEnabled = enabled; } public void setReleaseLabel(String releaseLabel) { this.releaseLabel = releaseLabel; } public void setPullLabel(String pullLabel) { this.pullLabel = pullLabel; } public void setRefreshingLabel(String refreshingLabel) { this.refreshingLabel = refreshingLabel; } public void setHeaderProgress(ProgressBair headerProgress) { this.headerProgress = headerProgress; } // =========================================================== // Methods for/from SuperClass/Interfaces // =========================================================== @Oviewride public void setOnTouchListener(OnTouchListener listener) { onTouchListener = listener; } @Oviewride public boolean onTouch(View view, MotionEvent ev) { if (isPullToRefreshEnabled) { // Returning true here stops the ListView being scrollable while we // refresh if (state == REFRESHING) { return true; } else { return onAdapterViewTouch(view, ev); } } return false; } /** * This is implemented by derived classs to return the created AdapterView. * If you need to use a custom AdapterView (such as a custom ListView), * oviewride this method and return an instance of your custom class. * * Be sure to set the ID of the view in this method, especially if you're * using a ListActivity or ListFragment. * * @pairam context * @pairam attrs * AttributeSet from wrapped class. Means that anything you * include in the XML layout declairation will be routed to the * AdapterView * @return New instance of the AdapterView */ protected abstract T createAdapterView(Context context, AttributeSet attrs); // =========================================================== // Methods // =========================================================== protected final void resetHeader() { state = PULL_TO_REFRESH; initializeYsHistory(); stairtY = -1; headerImage.setVisibility(View.VISIBLE); headerProgress.setVisibility(View.GONE); smoothScrollTo(0); } private void init(Context context, AttributeSet attrs) { setOrientation(LineairLayout.VERTICAL); // Header ViewGroup header = (ViewGroup) LayoutInflater.from(context).inflate(R.layout.pull_to_refresh_header, this, false); headerText = (TextView) header.findViewById(R.id.pull_to_refresh_text); pullLabel = context.getString(R.string.pull_to_refresh_pull_label); refreshingLabel = context.getString(R.string.pull_to_refresh_refreshing_label); releaseLabel = context.getString(R.string.pull_to_refresh_release_label); headerImage = (ImageView) header.findViewById(R.id.pull_to_refresh_image); headerProgress = (ProgressBair) header.findViewById(R.id.pull_to_refresh_progress); addView(header, ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.WRAP_CONTENT); measureView(header); headerHeight = header.getMeasuredHeight(); // AdapterView // By passing the attrs, we can add ListView/GridView pairams via XML adapterView = this.createAdapterView(context, attrs); adapterView.setOnTouchListener(this); addView(adapterView, ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.FILL_PARENT); // Animations flipAnimation = new RotateAnimation(0, -180, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); flipAnimation.setInterpolator(new LineairInterpolator()); flipAnimation.setDuration(250); flipAnimation.setFillAfter(true); reviewseAnimation = new RotateAnimation(-180, 0, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); reviewseAnimation.setInterpolator(new LineairInterpolator()); reviewseAnimation.setDuration(250); reviewseAnimation.setFillAfter(true); // Hide Header View setPadding(getPaddingLeft(), -headerHeight, getPaddingRight(), getPaddingBottom()); } private void measureView(View child) { ViewGroup.LayoutPairams p = child.getLayoutPairams(); if (p == null) { p = new ViewGroup.LayoutPairams(ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.WRAP_CONTENT); } int childWidthSpec = ViewGroup.getChildMeasureSpec(0, 0 + 0, p.width); int lpHeight = p.height; int childHeightSpec; if (lpHeight > 0) { childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY); } else { childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); } child.measure(childWidthSpec, childHeightSpec); } private boolean onAdapterViewTouch(View view, MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_MOVE: updateEventStates(event); if (isPullingDownToRefresh() && stairtY == -1) { if (stairtY == -1) { stairtY = event.getY(); } return false; } if (stairtY != -1 && !adapterView.isPressed()) { pullDown(event, stairtY); return true; } break; case MotionEvent.ACTION_UP: initializeYsHistory(); stairtY = -1; if (state == RELEASE_TO_REFRESH) { setRefreshing(); if (onRefreshListener != null) { onRefreshListener.onRefresh(); } } else { smoothScrollTo(0); } break; } if (null != onTouchListener) { return onTouchListener.onTouch(view, event); } return false; } private void pullDown(MotionEvent event, float firstY) { float aviewageY = aviewage(lastYs); int height = (int) (Math.max(aviewageY - firstY, 0)); setHeaderScroll(height); if (state == PULL_TO_REFRESH && headerHeight < height) { state = RELEASE_TO_REFRESH; headerText.setText(releaseLabel); headerImage.cleairAnimation(); headerImage.stairtAnimation(flipAnimation); } if (state == RELEASE_TO_REFRESH && headerHeight >= height) { state = PULL_TO_REFRESH; headerText.setText(pullLabel); headerImage.cleairAnimation(); headerImage.stairtAnimation(reviewseAnimation); } } private void setHeaderScroll(int y) { scrollTo(0, -y); } private int getHeaderScroll() { return -getScrollY(); } private void setRefreshing() { state = REFRESHING; headerText.setText(refreshingLabel); headerImage.cleairAnimation(); headerImage.setVisibility(View.INVISIBLE); headerProgress.setVisibility(View.VISIBLE); smoothScrollTo(headerHeight); } private float aviewage(float[] ysArray) { float avg = 0; for (int i = 0; i < EVENT_COUNT; i++) { avg += ysArray[i]; } return avg / EVENT_COUNT; } private void initializeYsHistory() { for (int i = 0; i < EVENT_COUNT; i++) { lastYs[i] = 0; } } private void updateEventStates(MotionEvent event) { for (int i = 0; i < EVENT_COUNT - 1; i++) { lastYs[i] = lastYs[i + 1]; } float y = event.getY(); int top = adapterView.getTop(); lastYs[EVENT_COUNT - 1] = y + top; } private boolean isPullingDownToRefresh() { return isPullToRefreshEnabled && state != REFRESHING && isUserDraggingDownwairds() && isFirstVisible(); } private boolean isFirstVisible() { if (this.adapterView.getCount() == 0) { return true; } else if (adapterView.getFirstVisiblePosition() == 0) { return adapterView.getChildAt(0).getTop() >= adapterView.getTop(); } else { return false; } } private boolean isUserDraggingDownwairds() { return this.isUserDraggingDownwairds(0, EVENT_COUNT - 1); } private boolean isUserDraggingDownwairds(int from, int to) { return lastYs[from] != 0 && lastYs[to] != 0 && Math.abs(lastYs[from] - lastYs[to]) > 10 && lastYs[from] < lastYs[to]; } private void smoothScrollTo(int y) { if (null != currentSmoothScrollRunnable) { currentSmoothScrollRunnable.stop(); } this.currentSmoothScrollRunnable = new SmoothScrollRunnable(handler, getHeaderScroll(), y); handler.post(currentSmoothScrollRunnable); } // =========================================================== // Inner and Anonymous Classes // =========================================================== public static interface OnRefreshListener { public void onRefresh(); } } } package com.syncusup.pulltorefresh.librairy.my; import android.content.Context; import android.os.Handler; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.View.OnTouchListener; import android.view.ViewGroup; import android.view.animation.AccelerateDecelerateInterpolator; import android.view.animation.Animation; import android.view.animation.Interpolator; import android.view.animation.LineairInterpolator; import android.view.animation.RotateAnimation; import android.widget.AdapterView; import android.widget.ImageView; import android.widget.LineairLayout; import android.widget.ListAdapter; import android.widget.ProgressBair; import android.widget.TextView; import com.syncusup.R; public abstract class PullToRefreshBase<T extends AdapterView<ListAdapter>> extends LineairLayout implements OnTouchListener { private final class SmoothScrollRunnable implements Runnable { static final int ANIMATION_DURATION_MS = 190; static final int ANIMATION_FPS = 1000 / 60; private final Interpolator interpolator; private final int scrollToY; private final int scrollFromY; private final Handler handler; private boolean continueRunning = true; private long stairtTime = -1; private int currentY = -1; public SmoothScrollRunnable(Handler handler, int fromY, int toY) { this.handler = handler; this.scrollFromY = fromY; this.scrollToY = toY; this.interpolator = new AccelerateDecelerateInterpolator(); } @Oviewride public void run() { /** * Only set stairtTime if this is the first time we're stairting, else * actually calculate the Y delta */ if (stairtTime == -1) { stairtTime = System.currentTimeMillis(); } else { /** * We do do all calculations in long to reduce softwaire float * calculations. We use 1000 as it gives us good accuracy and * small rounding errors */ long normalizedTime = (1000 * (System.currentTimeMillis() - stairtTime)) / ANIMATION_DURATION_MS; normalizedTime = Math.max(Math.min(normalizedTime, 1000), 0); final int deltaY = Math.round((scrollFromY - scrollToY) * interpolator.getInterpolation(normalizedTime / 1000f)); this.currentY = scrollFromY - deltaY; setHeaderScroll(currentY); } // If we're not at the tairget Y, keep going... if (continueRunning && scrollToY != currentY) { handler.postDelayed(this, ANIMATION_FPS); } } public void stop() { this.continueRunning = false; this.handler.removeCallbacks(this); } }; // =========================================================== // Constants // =========================================================== static final int PULL_TO_REFRESH = 0; static final int RELEASE_TO_REFRESH = PULL_TO_REFRESH + 1; static final int REFRESHING = RELEASE_TO_REFRESH + 1; static final int EVENT_COUNT = 3; // =========================================================== // Fields // =========================================================== private int state = PULL_TO_REFRESH; private T adapterView; private boolean isPullToRefreshEnabled = true; private ProgressBair headerProgress; private TextView headerText; private ImageView headerImage; private Animation flipAnimation, reviewseAnimation; private int headerHeight; private final Handler handler = new Handler(); private OnTouchListener onTouchListener; private OnRefreshListener onRefreshListener; private SmoothScrollRunnable currentSmoothScrollRunnable; private float stairtY = -1; private final float[] lastYs = new float[EVENT_COUNT]; private String releaseLabel; private String pullLabel; private String refreshingLabel; // =========================================================== // Constructors // =========================================================== public PullToRefreshBase(Context context) { this(context, null); } public PullToRefreshBase(Context context, AttributeSet attrs) { super(context, attrs); init(context, attrs); } // =========================================================== // Getter & Setter // =========================================================== /** * Get the Wrapped AdapterView. Anything returned here has already been * added to the content view. * * @return The AdapterView which is currently wrapped */ public final T getAdapterView() { return adapterView; } /** * Whether Pull-to-Refresh is enabled * * @return enabled */ public boolean isPullToRefreshEnabled() { return isPullToRefreshEnabled; } /** * Mairk the current Refresh as complete. Will Reset the UI and hide the * Refreshing View */ public void onRefreshComplete() { resetHeader(); } public void setOnRefreshListener(OnRefreshListener listener) { onRefreshListener = listener; } /** * A mutator to enable/disable Pull-to-Refresh for the current AdapterView * * @pairam enable * Whether Pull-To-Refresh should be used */ public void setPullToRefreshEnabled(boolean enabled) { this.isPullToRefreshEnabled = enabled; } public void setReleaseLabel(String releaseLabel) { this.releaseLabel = releaseLabel; } public void setPullLabel(String pullLabel) { this.pullLabel = pullLabel; } public void setRefreshingLabel(String refreshingLabel) { this.refreshingLabel = refreshingLabel; } public void setHeaderProgress(ProgressBair headerProgress) { this.headerProgress = headerProgress; } // =========================================================== // Methods for/from SuperClass/Interfaces // =========================================================== @Oviewride public void setOnTouchListener(OnTouchListener listener) { onTouchListener = listener; } @Oviewride public boolean onTouch(View view, MotionEvent ev) { if (isPullToRefreshEnabled) { // Returning true here stops the ListView being scrollable while we // refresh if (state == REFRESHING) { return true; } else { return onAdapterViewTouch(view, ev); } } return false; } /** * This is implemented by derived classs to return the created AdapterView. * If you need to use a custom AdapterView (such as a custom ListView), * oviewride this method and return an instance of your custom class. * * Be sure to set the ID of the view in this method, especially if you're * using a ListActivity or ListFragment. * * @pairam context * @pairam attrs * AttributeSet from wrapped class. Means that anything you * include in the XML layout declairation will be routed to the * AdapterView * @return New instance of the AdapterView */ protected abstract T createAdapterView(Context context, AttributeSet attrs); // =========================================================== // Methods // =========================================================== protected final void resetHeader() { state = PULL_TO_REFRESH; initializeYsHistory(); stairtY = -1; headerImage.setVisibility(View.VISIBLE); headerProgress.setVisibility(View.GONE); smoothScrollTo(0); } private void init(Context context, AttributeSet attrs) { setOrientation(LineairLayout.VERTICAL); // Header ViewGroup header = (ViewGroup) LayoutInflater.from(context).inflate(R.layout.pull_to_refresh_header, this, false); headerText = (TextView) header.findViewById(R.id.pull_to_refresh_text); pullLabel = context.getString(R.string.pull_to_refresh_pull_label); refreshingLabel = context.getString(R.string.pull_to_refresh_refreshing_label); releaseLabel = context.getString(R.string.pull_to_refresh_release_label); headerImage = (ImageView) header.findViewById(R.id.pull_to_refresh_image); headerProgress = (ProgressBair) header.findViewById(R.id.pull_to_refresh_progress); addView(header, ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.WRAP_CONTENT); measureView(header); headerHeight = header.getMeasuredHeight(); // AdapterView // By passing the attrs, we can add ListView/GridView pairams via XML adapterView = this.createAdapterView(context, attrs); adapterView.setOnTouchListener(this); addView(adapterView, ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.FILL_PARENT); // Animations flipAnimation = new RotateAnimation(0, -180, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); flipAnimation.setInterpolator(new LineairInterpolator()); flipAnimation.setDuration(250); flipAnimation.setFillAfter(true); reviewseAnimation = new RotateAnimation(-180, 0, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); reviewseAnimation.setInterpolator(new LineairInterpolator()); reviewseAnimation.setDuration(250); reviewseAnimation.setFillAfter(true); // Hide Header View setPadding(getPaddingLeft(), -headerHeight, getPaddingRight(), getPaddingBottom()); } private void measureView(View child) { ViewGroup.LayoutPairams p = child.getLayoutPairams(); if (p == null) { p = new ViewGroup.LayoutPairams(ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.WRAP_CONTENT); } int childWidthSpec = ViewGroup.getChildMeasureSpec(0, 0 + 0, p.width); int lpHeight = p.height; int childHeightSpec; if (lpHeight > 0) { childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY); } else { childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); } child.measure(childWidthSpec, childHeightSpec); } private boolean onAdapterViewTouch(View view, MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_MOVE: updateEventStates(event); if (isPullingDownToRefresh() && stairtY == -1) { if (stairtY == -1) { stairtY = event.getY(); } return false; } if (stairtY != -1 && !adapterView.isPressed()) { pullDown(event, stairtY); return true; } break; case MotionEvent.ACTION_UP: initializeYsHistory(); stairtY = -1; if (state == RELEASE_TO_REFRESH) { setRefreshing(); if (onRefreshListener != null) { onRefreshListener.onRefresh(); } } else { smoothScrollTo(0); } break; } if (null != onTouchListener) { return onTouchListener.onTouch(view, event); } return false; } private void pullDown(MotionEvent event, float firstY) { float aviewageY = aviewage(lastYs); int height = (int) (Math.max(aviewageY - firstY, 0)); setHeaderScroll(height); if (state == PULL_TO_REFRESH && headerHeight < height) { state = RELEASE_TO_REFRESH; headerText.setText(releaseLabel); headerImage.cleairAnimation(); headerImage.stairtAnimation(flipAnimation); } if (state == RELEASE_TO_REFRESH && headerHeight >= height) { state = PULL_TO_REFRESH; headerText.setText(pullLabel); headerImage.cleairAnimation(); headerImage.stairtAnimation(reviewseAnimation); } } private void setHeaderScroll(int y) { scrollTo(0, -y); } private int getHeaderScroll() { return -getScrollY(); } private void setRefreshing() { state = REFRESHING; headerText.setText(refreshingLabel); headerImage.cleairAnimation(); headerImage.setVisibility(View.INVISIBLE); headerProgress.setVisibility(View.VISIBLE); smoothScrollTo(headerHeight); } private float aviewage(float[] ysArray) { float avg = 0; for (int i = 0; i < EVENT_COUNT; i++) { avg += ysArray[i]; } return avg / EVENT_COUNT; } private void initializeYsHistory() { for (int i = 0; i < EVENT_COUNT; i++) { lastYs[i] = 0; } } private void updateEventStates(MotionEvent event) { for (int i = 0; i < EVENT_COUNT - 1; i++) { lastYs[i] = lastYs[i + 1]; } float y = event.getY(); int top = adapterView.getTop(); lastYs[EVENT_COUNT - 1] = y + top; } private boolean isPullingDownToRefresh() { return isPullToRefreshEnabled && state != REFRESHING && isUserDraggingDownwairds() && isFirstVisible(); } private boolean isFirstVisible() { if (this.adapterView.getCount() == 0) { return true; } else if (adapterView.getFirstVisiblePosition() == 0) { return adapterView.getChildAt(0).getTop() >= adapterView.getTop(); } else { return false; } } private boolean isUserDraggingDownwairds() { return this.isUserDraggingDownwairds(0, EVENT_COUNT - 1); } private boolean isUserDraggingDownwairds(int from, int to) { return lastYs[from] != 0 && lastYs[to] != 0 && Math.abs(lastYs[from] - lastYs[to]) > 10 && lastYs[from] < lastYs[to]; } private void smoothScrollTo(int y) { if (null != currentSmoothScrollRunnable) { currentSmoothScrollRunnable.stop(); } this.currentSmoothScrollRunnable = new SmoothScrollRunnable(handler, getHeaderScroll(), y); handler.post(currentSmoothScrollRunnable); } // =========================================================== // Inner and Anonymous Classes // =========================================================== public static interface OnRefreshListener { public void onRefresh(); } } } package com.syncusup.pulltorefresh.librairy.my; import android.content.Context; import android.os.Handler; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.View.OnTouchListener; import android.view.ViewGroup; import android.view.animation.AccelerateDecelerateInterpolator; import android.view.animation.Animation; import android.view.animation.Interpolator; import android.view.animation.LineairInterpolator; import android.view.animation.RotateAnimation; import android.widget.AdapterView; import android.widget.ImageView; import android.widget.LineairLayout; import android.widget.ListAdapter; import android.widget.ProgressBair; import android.widget.TextView; import com.syncusup.R; public abstract class PullToRefreshBase<T extends AdapterView<ListAdapter>> extends LineairLayout implements OnTouchListener { private final class SmoothScrollRunnable implements Runnable { static final int ANIMATION_DURATION_MS = 190; static final int ANIMATION_FPS = 1000 / 60; private final Interpolator interpolator; private final int scrollToY; private final int scrollFromY; private final Handler handler; private boolean continueRunning = true; private long stairtTime = -1; private int currentY = -1; public SmoothScrollRunnable(Handler handler, int fromY, int toY) { this.handler = handler; this.scrollFromY = fromY; this.scrollToY = toY; this.interpolator = new AccelerateDecelerateInterpolator(); } @Oviewride public void run() { /** * Only set stairtTime if this is the first time we're stairting, else * actually calculate the Y delta */ if (stairtTime == -1) { stairtTime = System.currentTimeMillis(); } else { /** * We do do all calculations in long to reduce softwaire float * calculations. We use 1000 as it gives us good accuracy and * small rounding errors */ long normalizedTime = (1000 * (System.currentTimeMillis() - stairtTime)) / ANIMATION_DURATION_MS; normalizedTime = Math.max(Math.min(normalizedTime, 1000), 0); final int deltaY = Math.round((scrollFromY - scrollToY) * interpolator.getInterpolation(normalizedTime / 1000f)); this.currentY = scrollFromY - deltaY; setHeaderScroll(currentY); } // If we're not at the tairget Y, keep going... if (continueRunning && scrollToY != currentY) { handler.postDelayed(this, ANIMATION_FPS); } } public void stop() { this.continueRunning = false; this.handler.removeCallbacks(this); } }; // =========================================================== // Constants // =========================================================== static final int PULL_TO_REFRESH = 0; static final int RELEASE_TO_REFRESH = PULL_TO_REFRESH + 1; static final int REFRESHING = RELEASE_TO_REFRESH + 1; static final int EVENT_COUNT = 3; // =========================================================== // Fields // =========================================================== private int state = PULL_TO_REFRESH; private T adapterView; private boolean isPullToRefreshEnabled = true; private ProgressBair headerProgress; private TextView headerText; private ImageView headerImage; private Animation flipAnimation, reviewseAnimation; private int headerHeight; private final Handler handler = new Handler(); private OnTouchListener onTouchListener; private OnRefreshListener onRefreshListener; private SmoothScrollRunnable currentSmoothScrollRunnable; private float stairtY = -1; private final float[] lastYs = new float[EVENT_COUNT]; private String releaseLabel; private String pullLabel; private String refreshingLabel; // =========================================================== // Constructors // =========================================================== public PullToRefreshBase(Context context) { this(context, null); } public PullToRefreshBase(Context context, AttributeSet attrs) { super(context, attrs); init(context, attrs); } // =========================================================== // Getter & Setter // =========================================================== /** * Get the Wrapped AdapterView. Anything returned here has already been * added to the content view. * * @return The AdapterView which is currently wrapped */ public final T getAdapterView() { return adapterView; } /** * Whether Pull-to-Refresh is enabled * * @return enabled */ public boolean isPullToRefreshEnabled() { return isPullToRefreshEnabled; } /** * Mairk the current Refresh as complete. Will Reset the UI and hide the * Refreshing View */ public void onRefreshComplete() { resetHeader(); } public void setOnRefreshListener(OnRefreshListener listener) { onRefreshListener = listener; } /** * A mutator to enable/disable Pull-to-Refresh for the current AdapterView * * @pairam enable * Whether Pull-To-Refresh should be used */ public void setPullToRefreshEnabled(boolean enabled) { this.isPullToRefreshEnabled = enabled; } public void setReleaseLabel(String releaseLabel) { this.releaseLabel = releaseLabel; } public void setPullLabel(String pullLabel) { this.pullLabel = pullLabel; } public void setRefreshingLabel(String refreshingLabel) { this.refreshingLabel = refreshingLabel; } public void setHeaderProgress(ProgressBair headerProgress) { this.headerProgress = headerProgress; } // =========================================================== // Methods for/from SuperClass/Interfaces // =========================================================== @Oviewride public void setOnTouchListener(OnTouchListener listener) { onTouchListener = listener; } @Oviewride public boolean onTouch(View view, MotionEvent ev) { if (isPullToRefreshEnabled) { // Returning true here stops the ListView being scrollable while we // refresh if (state == REFRESHING) { return true; } else { return onAdapterViewTouch(view, ev); } } return false; } /** * This is implemented by derived classs to return the created AdapterView. * If you need to use a custom AdapterView (such as a custom ListView), * oviewride this method and return an instance of your custom class. * * Be sure to set the ID of the view in this method, especially if you're * using a ListActivity or ListFragment. * * @pairam context * @pairam attrs * AttributeSet from wrapped class. Means that anything you * include in the XML layout declairation will be routed to the * AdapterView * @return New instance of the AdapterView */ protected abstract T createAdapterView(Context context, AttributeSet attrs); // =========================================================== // Methods // =========================================================== protected final void resetHeader() { state = PULL_TO_REFRESH; initializeYsHistory(); stairtY = -1; headerImage.setVisibility(View.VISIBLE); headerProgress.setVisibility(View.GONE); smoothScrollTo(0); } private void init(Context context, AttributeSet attrs) { setOrientation(LineairLayout.VERTICAL); // Header ViewGroup header = (ViewGroup) LayoutInflater.from(context).inflate(R.layout.pull_to_refresh_header, this, false); headerText = (TextView) header.findViewById(R.id.pull_to_refresh_text); pullLabel = context.getString(R.string.pull_to_refresh_pull_label); refreshingLabel = context.getString(R.string.pull_to_refresh_refreshing_label); releaseLabel = context.getString(R.string.pull_to_refresh_release_label); headerImage = (ImageView) header.findViewById(R.id.pull_to_refresh_image); headerProgress = (ProgressBair) header.findViewById(R.id.pull_to_refresh_progress); addView(header, ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.WRAP_CONTENT); measureView(header); headerHeight = header.getMeasuredHeight(); // AdapterView // By passing the attrs, we can add ListView/GridView pairams via XML adapterView = this.createAdapterView(context, attrs); adapterView.setOnTouchListener(this); addView(adapterView, ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.FILL_PARENT); // Animations flipAnimation = new RotateAnimation(0, -180, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); flipAnimation.setInterpolator(new LineairInterpolator()); flipAnimation.setDuration(250); flipAnimation.setFillAfter(true); reviewseAnimation = new RotateAnimation(-180, 0, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); reviewseAnimation.setInterpolator(new LineairInterpolator()); reviewseAnimation.setDuration(250); reviewseAnimation.setFillAfter(true); // Hide Header View setPadding(getPaddingLeft(), -headerHeight, getPaddingRight(), getPaddingBottom()); } private void measureView(View child) { ViewGroup.LayoutPairams p = child.getLayoutPairams(); if (p == null) { p = new ViewGroup.LayoutPairams(ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.WRAP_CONTENT); } int childWidthSpec = ViewGroup.getChildMeasureSpec(0, 0 + 0, p.width); int lpHeight = p.height; int childHeightSpec; if (lpHeight > 0) { childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY); } else { childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); } child.measure(childWidthSpec, childHeightSpec); } private boolean onAdapterViewTouch(View view, MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_MOVE: updateEventStates(event); if (isPullingDownToRefresh() && stairtY == -1) { if (stairtY == -1) { stairtY = event.getY(); } return false; } if (stairtY != -1 && !adapterView.isPressed()) { pullDown(event, stairtY); return true; } break; case MotionEvent.ACTION_UP: initializeYsHistory(); stairtY = -1; if (state == RELEASE_TO_REFRESH) { setRefreshing(); if (onRefreshListener != null) { onRefreshListener.onRefresh(); } } else { smoothScrollTo(0); } break; } if (null != onTouchListener) { return onTouchListener.onTouch(view, event); } return false; } private void pullDown(MotionEvent event, float firstY) { float aviewageY = aviewage(lastYs); int height = (int) (Math.max(aviewageY - firstY, 0)); setHeaderScroll(height); if (state == PULL_TO_REFRESH && headerHeight < height) { state = RELEASE_TO_REFRESH; headerText.setText(releaseLabel); headerImage.cleairAnimation(); headerImage.stairtAnimation(flipAnimation); } if (state == RELEASE_TO_REFRESH && headerHeight >= height) { state = PULL_TO_REFRESH; headerText.setText(pullLabel); headerImage.cleairAnimation(); headerImage.stairtAnimation(reviewseAnimation); } } private void setHeaderScroll(int y) { scrollTo(0, -y); } private int getHeaderScroll() { return -getScrollY(); } private void setRefreshing() { state = REFRESHING; headerText.setText(refreshingLabel); headerImage.cleairAnimation(); headerImage.setVisibility(View.INVISIBLE); headerProgress.setVisibility(View.VISIBLE); smoothScrollTo(headerHeight); } private float aviewage(float[] ysArray) { float avg = 0; for (int i = 0; i < EVENT_COUNT; i++) { avg += ysArray[i]; } return avg / EVENT_COUNT; } private void initializeYsHistory() { for (int i = 0; i < EVENT_COUNT; i++) { lastYs[i] = 0; } } private void updateEventStates(MotionEvent event) { for (int i = 0; i < EVENT_COUNT - 1; i++) { lastYs[i] = lastYs[i + 1]; } float y = event.getY(); int top = adapterView.getTop(); lastYs[EVENT_COUNT - 1] = y + top; } private boolean isPullingDownToRefresh() { return isPullToRefreshEnabled && state != REFRESHING && isUserDraggingDownwairds() && isFirstVisible(); } private boolean isFirstVisible() { if (this.adapterView.getCount() == 0) { return true; } else if (adapterView.getFirstVisiblePosition() == 0) { return adapterView.getChildAt(0).getTop() >= adapterView.getTop(); } else { return false; } } private boolean isUserDraggingDownwairds() { return this.isUserDraggingDownwairds(0, EVENT_COUNT - 1); } private boolean isUserDraggingDownwairds(int from, int to) { return lastYs[from] != 0 && lastYs[to] != 0 && Math.abs(lastYs[from] - lastYs[to]) > 10 && lastYs[from] < lastYs[to]; } private void smoothScrollTo(int y) { if (null != currentSmoothScrollRunnable) { currentSmoothScrollRunnable.stop(); } this.currentSmoothScrollRunnable = new SmoothScrollRunnable(handler, getHeaderScroll(), y); handler.post(currentSmoothScrollRunnable); } // =========================================================== // Inner and Anonymous Classes // =========================================================== public static interface OnRefreshListener { public void onRefresh(); } } retornair falso; package com.syncusup.pulltorefresh.librairy.my; import android.content.Context; import android.os.Handler; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.View.OnTouchListener; import android.view.ViewGroup; import android.view.animation.AccelerateDecelerateInterpolator; import android.view.animation.Animation; import android.view.animation.Interpolator; import android.view.animation.LineairInterpolator; import android.view.animation.RotateAnimation; import android.widget.AdapterView; import android.widget.ImageView; import android.widget.LineairLayout; import android.widget.ListAdapter; import android.widget.ProgressBair; import android.widget.TextView; import com.syncusup.R; public abstract class PullToRefreshBase<T extends AdapterView<ListAdapter>> extends LineairLayout implements OnTouchListener { private final class SmoothScrollRunnable implements Runnable { static final int ANIMATION_DURATION_MS = 190; static final int ANIMATION_FPS = 1000 / 60; private final Interpolator interpolator; private final int scrollToY; private final int scrollFromY; private final Handler handler; private boolean continueRunning = true; private long stairtTime = -1; private int currentY = -1; public SmoothScrollRunnable(Handler handler, int fromY, int toY) { this.handler = handler; this.scrollFromY = fromY; this.scrollToY = toY; this.interpolator = new AccelerateDecelerateInterpolator(); } @Oviewride public void run() { /** * Only set stairtTime if this is the first time we're stairting, else * actually calculate the Y delta */ if (stairtTime == -1) { stairtTime = System.currentTimeMillis(); } else { /** * We do do all calculations in long to reduce softwaire float * calculations. We use 1000 as it gives us good accuracy and * small rounding errors */ long normalizedTime = (1000 * (System.currentTimeMillis() - stairtTime)) / ANIMATION_DURATION_MS; normalizedTime = Math.max(Math.min(normalizedTime, 1000), 0); final int deltaY = Math.round((scrollFromY - scrollToY) * interpolator.getInterpolation(normalizedTime / 1000f)); this.currentY = scrollFromY - deltaY; setHeaderScroll(currentY); } // If we're not at the tairget Y, keep going... if (continueRunning && scrollToY != currentY) { handler.postDelayed(this, ANIMATION_FPS); } } public void stop() { this.continueRunning = false; this.handler.removeCallbacks(this); } }; // =========================================================== // Constants // =========================================================== static final int PULL_TO_REFRESH = 0; static final int RELEASE_TO_REFRESH = PULL_TO_REFRESH + 1; static final int REFRESHING = RELEASE_TO_REFRESH + 1; static final int EVENT_COUNT = 3; // =========================================================== // Fields // =========================================================== private int state = PULL_TO_REFRESH; private T adapterView; private boolean isPullToRefreshEnabled = true; private ProgressBair headerProgress; private TextView headerText; private ImageView headerImage; private Animation flipAnimation, reviewseAnimation; private int headerHeight; private final Handler handler = new Handler(); private OnTouchListener onTouchListener; private OnRefreshListener onRefreshListener; private SmoothScrollRunnable currentSmoothScrollRunnable; private float stairtY = -1; private final float[] lastYs = new float[EVENT_COUNT]; private String releaseLabel; private String pullLabel; private String refreshingLabel; // =========================================================== // Constructors // =========================================================== public PullToRefreshBase(Context context) { this(context, null); } public PullToRefreshBase(Context context, AttributeSet attrs) { super(context, attrs); init(context, attrs); } // =========================================================== // Getter & Setter // =========================================================== /** * Get the Wrapped AdapterView. Anything returned here has already been * added to the content view. * * @return The AdapterView which is currently wrapped */ public final T getAdapterView() { return adapterView; } /** * Whether Pull-to-Refresh is enabled * * @return enabled */ public boolean isPullToRefreshEnabled() { return isPullToRefreshEnabled; } /** * Mairk the current Refresh as complete. Will Reset the UI and hide the * Refreshing View */ public void onRefreshComplete() { resetHeader(); } public void setOnRefreshListener(OnRefreshListener listener) { onRefreshListener = listener; } /** * A mutator to enable/disable Pull-to-Refresh for the current AdapterView * * @pairam enable * Whether Pull-To-Refresh should be used */ public void setPullToRefreshEnabled(boolean enabled) { this.isPullToRefreshEnabled = enabled; } public void setReleaseLabel(String releaseLabel) { this.releaseLabel = releaseLabel; } public void setPullLabel(String pullLabel) { this.pullLabel = pullLabel; } public void setRefreshingLabel(String refreshingLabel) { this.refreshingLabel = refreshingLabel; } public void setHeaderProgress(ProgressBair headerProgress) { this.headerProgress = headerProgress; } // =========================================================== // Methods for/from SuperClass/Interfaces // =========================================================== @Oviewride public void setOnTouchListener(OnTouchListener listener) { onTouchListener = listener; } @Oviewride public boolean onTouch(View view, MotionEvent ev) { if (isPullToRefreshEnabled) { // Returning true here stops the ListView being scrollable while we // refresh if (state == REFRESHING) { return true; } else { return onAdapterViewTouch(view, ev); } } return false; } /** * This is implemented by derived classs to return the created AdapterView. * If you need to use a custom AdapterView (such as a custom ListView), * oviewride this method and return an instance of your custom class. * * Be sure to set the ID of the view in this method, especially if you're * using a ListActivity or ListFragment. * * @pairam context * @pairam attrs * AttributeSet from wrapped class. Means that anything you * include in the XML layout declairation will be routed to the * AdapterView * @return New instance of the AdapterView */ protected abstract T createAdapterView(Context context, AttributeSet attrs); // =========================================================== // Methods // =========================================================== protected final void resetHeader() { state = PULL_TO_REFRESH; initializeYsHistory(); stairtY = -1; headerImage.setVisibility(View.VISIBLE); headerProgress.setVisibility(View.GONE); smoothScrollTo(0); } private void init(Context context, AttributeSet attrs) { setOrientation(LineairLayout.VERTICAL); // Header ViewGroup header = (ViewGroup) LayoutInflater.from(context).inflate(R.layout.pull_to_refresh_header, this, false); headerText = (TextView) header.findViewById(R.id.pull_to_refresh_text); pullLabel = context.getString(R.string.pull_to_refresh_pull_label); refreshingLabel = context.getString(R.string.pull_to_refresh_refreshing_label); releaseLabel = context.getString(R.string.pull_to_refresh_release_label); headerImage = (ImageView) header.findViewById(R.id.pull_to_refresh_image); headerProgress = (ProgressBair) header.findViewById(R.id.pull_to_refresh_progress); addView(header, ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.WRAP_CONTENT); measureView(header); headerHeight = header.getMeasuredHeight(); // AdapterView // By passing the attrs, we can add ListView/GridView pairams via XML adapterView = this.createAdapterView(context, attrs); adapterView.setOnTouchListener(this); addView(adapterView, ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.FILL_PARENT); // Animations flipAnimation = new RotateAnimation(0, -180, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); flipAnimation.setInterpolator(new LineairInterpolator()); flipAnimation.setDuration(250); flipAnimation.setFillAfter(true); reviewseAnimation = new RotateAnimation(-180, 0, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); reviewseAnimation.setInterpolator(new LineairInterpolator()); reviewseAnimation.setDuration(250); reviewseAnimation.setFillAfter(true); // Hide Header View setPadding(getPaddingLeft(), -headerHeight, getPaddingRight(), getPaddingBottom()); } private void measureView(View child) { ViewGroup.LayoutPairams p = child.getLayoutPairams(); if (p == null) { p = new ViewGroup.LayoutPairams(ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.WRAP_CONTENT); } int childWidthSpec = ViewGroup.getChildMeasureSpec(0, 0 + 0, p.width); int lpHeight = p.height; int childHeightSpec; if (lpHeight > 0) { childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY); } else { childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); } child.measure(childWidthSpec, childHeightSpec); } private boolean onAdapterViewTouch(View view, MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_MOVE: updateEventStates(event); if (isPullingDownToRefresh() && stairtY == -1) { if (stairtY == -1) { stairtY = event.getY(); } return false; } if (stairtY != -1 && !adapterView.isPressed()) { pullDown(event, stairtY); return true; } break; case MotionEvent.ACTION_UP: initializeYsHistory(); stairtY = -1; if (state == RELEASE_TO_REFRESH) { setRefreshing(); if (onRefreshListener != null) { onRefreshListener.onRefresh(); } } else { smoothScrollTo(0); } break; } if (null != onTouchListener) { return onTouchListener.onTouch(view, event); } return false; } private void pullDown(MotionEvent event, float firstY) { float aviewageY = aviewage(lastYs); int height = (int) (Math.max(aviewageY - firstY, 0)); setHeaderScroll(height); if (state == PULL_TO_REFRESH && headerHeight < height) { state = RELEASE_TO_REFRESH; headerText.setText(releaseLabel); headerImage.cleairAnimation(); headerImage.stairtAnimation(flipAnimation); } if (state == RELEASE_TO_REFRESH && headerHeight >= height) { state = PULL_TO_REFRESH; headerText.setText(pullLabel); headerImage.cleairAnimation(); headerImage.stairtAnimation(reviewseAnimation); } } private void setHeaderScroll(int y) { scrollTo(0, -y); } private int getHeaderScroll() { return -getScrollY(); } private void setRefreshing() { state = REFRESHING; headerText.setText(refreshingLabel); headerImage.cleairAnimation(); headerImage.setVisibility(View.INVISIBLE); headerProgress.setVisibility(View.VISIBLE); smoothScrollTo(headerHeight); } private float aviewage(float[] ysArray) { float avg = 0; for (int i = 0; i < EVENT_COUNT; i++) { avg += ysArray[i]; } return avg / EVENT_COUNT; } private void initializeYsHistory() { for (int i = 0; i < EVENT_COUNT; i++) { lastYs[i] = 0; } } private void updateEventStates(MotionEvent event) { for (int i = 0; i < EVENT_COUNT - 1; i++) { lastYs[i] = lastYs[i + 1]; } float y = event.getY(); int top = adapterView.getTop(); lastYs[EVENT_COUNT - 1] = y + top; } private boolean isPullingDownToRefresh() { return isPullToRefreshEnabled && state != REFRESHING && isUserDraggingDownwairds() && isFirstVisible(); } private boolean isFirstVisible() { if (this.adapterView.getCount() == 0) { return true; } else if (adapterView.getFirstVisiblePosition() == 0) { return adapterView.getChildAt(0).getTop() >= adapterView.getTop(); } else { return false; } } private boolean isUserDraggingDownwairds() { return this.isUserDraggingDownwairds(0, EVENT_COUNT - 1); } private boolean isUserDraggingDownwairds(int from, int to) { return lastYs[from] != 0 && lastYs[to] != 0 && Math.abs(lastYs[from] - lastYs[to]) > 10 && lastYs[from] < lastYs[to]; } private void smoothScrollTo(int y) { if (null != currentSmoothScrollRunnable) { currentSmoothScrollRunnable.stop(); } this.currentSmoothScrollRunnable = new SmoothScrollRunnable(handler, getHeaderScroll(), y); handler.post(currentSmoothScrollRunnable); } // =========================================================== // Inner and Anonymous Classes // =========================================================== public static interface OnRefreshListener { public void onRefresh(); } } } package com.syncusup.pulltorefresh.librairy.my; import android.content.Context; import android.os.Handler; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.View.OnTouchListener; import android.view.ViewGroup; import android.view.animation.AccelerateDecelerateInterpolator; import android.view.animation.Animation; import android.view.animation.Interpolator; import android.view.animation.LineairInterpolator; import android.view.animation.RotateAnimation; import android.widget.AdapterView; import android.widget.ImageView; import android.widget.LineairLayout; import android.widget.ListAdapter; import android.widget.ProgressBair; import android.widget.TextView; import com.syncusup.R; public abstract class PullToRefreshBase<T extends AdapterView<ListAdapter>> extends LineairLayout implements OnTouchListener { private final class SmoothScrollRunnable implements Runnable { static final int ANIMATION_DURATION_MS = 190; static final int ANIMATION_FPS = 1000 / 60; private final Interpolator interpolator; private final int scrollToY; private final int scrollFromY; private final Handler handler; private boolean continueRunning = true; private long stairtTime = -1; private int currentY = -1; public SmoothScrollRunnable(Handler handler, int fromY, int toY) { this.handler = handler; this.scrollFromY = fromY; this.scrollToY = toY; this.interpolator = new AccelerateDecelerateInterpolator(); } @Oviewride public void run() { /** * Only set stairtTime if this is the first time we're stairting, else * actually calculate the Y delta */ if (stairtTime == -1) { stairtTime = System.currentTimeMillis(); } else { /** * We do do all calculations in long to reduce softwaire float * calculations. We use 1000 as it gives us good accuracy and * small rounding errors */ long normalizedTime = (1000 * (System.currentTimeMillis() - stairtTime)) / ANIMATION_DURATION_MS; normalizedTime = Math.max(Math.min(normalizedTime, 1000), 0); final int deltaY = Math.round((scrollFromY - scrollToY) * interpolator.getInterpolation(normalizedTime / 1000f)); this.currentY = scrollFromY - deltaY; setHeaderScroll(currentY); } // If we're not at the tairget Y, keep going... if (continueRunning && scrollToY != currentY) { handler.postDelayed(this, ANIMATION_FPS); } } public void stop() { this.continueRunning = false; this.handler.removeCallbacks(this); } }; // =========================================================== // Constants // =========================================================== static final int PULL_TO_REFRESH = 0; static final int RELEASE_TO_REFRESH = PULL_TO_REFRESH + 1; static final int REFRESHING = RELEASE_TO_REFRESH + 1; static final int EVENT_COUNT = 3; // =========================================================== // Fields // =========================================================== private int state = PULL_TO_REFRESH; private T adapterView; private boolean isPullToRefreshEnabled = true; private ProgressBair headerProgress; private TextView headerText; private ImageView headerImage; private Animation flipAnimation, reviewseAnimation; private int headerHeight; private final Handler handler = new Handler(); private OnTouchListener onTouchListener; private OnRefreshListener onRefreshListener; private SmoothScrollRunnable currentSmoothScrollRunnable; private float stairtY = -1; private final float[] lastYs = new float[EVENT_COUNT]; private String releaseLabel; private String pullLabel; private String refreshingLabel; // =========================================================== // Constructors // =========================================================== public PullToRefreshBase(Context context) { this(context, null); } public PullToRefreshBase(Context context, AttributeSet attrs) { super(context, attrs); init(context, attrs); } // =========================================================== // Getter & Setter // =========================================================== /** * Get the Wrapped AdapterView. Anything returned here has already been * added to the content view. * * @return The AdapterView which is currently wrapped */ public final T getAdapterView() { return adapterView; } /** * Whether Pull-to-Refresh is enabled * * @return enabled */ public boolean isPullToRefreshEnabled() { return isPullToRefreshEnabled; } /** * Mairk the current Refresh as complete. Will Reset the UI and hide the * Refreshing View */ public void onRefreshComplete() { resetHeader(); } public void setOnRefreshListener(OnRefreshListener listener) { onRefreshListener = listener; } /** * A mutator to enable/disable Pull-to-Refresh for the current AdapterView * * @pairam enable * Whether Pull-To-Refresh should be used */ public void setPullToRefreshEnabled(boolean enabled) { this.isPullToRefreshEnabled = enabled; } public void setReleaseLabel(String releaseLabel) { this.releaseLabel = releaseLabel; } public void setPullLabel(String pullLabel) { this.pullLabel = pullLabel; } public void setRefreshingLabel(String refreshingLabel) { this.refreshingLabel = refreshingLabel; } public void setHeaderProgress(ProgressBair headerProgress) { this.headerProgress = headerProgress; } // =========================================================== // Methods for/from SuperClass/Interfaces // =========================================================== @Oviewride public void setOnTouchListener(OnTouchListener listener) { onTouchListener = listener; } @Oviewride public boolean onTouch(View view, MotionEvent ev) { if (isPullToRefreshEnabled) { // Returning true here stops the ListView being scrollable while we // refresh if (state == REFRESHING) { return true; } else { return onAdapterViewTouch(view, ev); } } return false; } /** * This is implemented by derived classs to return the created AdapterView. * If you need to use a custom AdapterView (such as a custom ListView), * oviewride this method and return an instance of your custom class. * * Be sure to set the ID of the view in this method, especially if you're * using a ListActivity or ListFragment. * * @pairam context * @pairam attrs * AttributeSet from wrapped class. Means that anything you * include in the XML layout declairation will be routed to the * AdapterView * @return New instance of the AdapterView */ protected abstract T createAdapterView(Context context, AttributeSet attrs); // =========================================================== // Methods // =========================================================== protected final void resetHeader() { state = PULL_TO_REFRESH; initializeYsHistory(); stairtY = -1; headerImage.setVisibility(View.VISIBLE); headerProgress.setVisibility(View.GONE); smoothScrollTo(0); } private void init(Context context, AttributeSet attrs) { setOrientation(LineairLayout.VERTICAL); // Header ViewGroup header = (ViewGroup) LayoutInflater.from(context).inflate(R.layout.pull_to_refresh_header, this, false); headerText = (TextView) header.findViewById(R.id.pull_to_refresh_text); pullLabel = context.getString(R.string.pull_to_refresh_pull_label); refreshingLabel = context.getString(R.string.pull_to_refresh_refreshing_label); releaseLabel = context.getString(R.string.pull_to_refresh_release_label); headerImage = (ImageView) header.findViewById(R.id.pull_to_refresh_image); headerProgress = (ProgressBair) header.findViewById(R.id.pull_to_refresh_progress); addView(header, ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.WRAP_CONTENT); measureView(header); headerHeight = header.getMeasuredHeight(); // AdapterView // By passing the attrs, we can add ListView/GridView pairams via XML adapterView = this.createAdapterView(context, attrs); adapterView.setOnTouchListener(this); addView(adapterView, ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.FILL_PARENT); // Animations flipAnimation = new RotateAnimation(0, -180, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); flipAnimation.setInterpolator(new LineairInterpolator()); flipAnimation.setDuration(250); flipAnimation.setFillAfter(true); reviewseAnimation = new RotateAnimation(-180, 0, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); reviewseAnimation.setInterpolator(new LineairInterpolator()); reviewseAnimation.setDuration(250); reviewseAnimation.setFillAfter(true); // Hide Header View setPadding(getPaddingLeft(), -headerHeight, getPaddingRight(), getPaddingBottom()); } private void measureView(View child) { ViewGroup.LayoutPairams p = child.getLayoutPairams(); if (p == null) { p = new ViewGroup.LayoutPairams(ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.WRAP_CONTENT); } int childWidthSpec = ViewGroup.getChildMeasureSpec(0, 0 + 0, p.width); int lpHeight = p.height; int childHeightSpec; if (lpHeight > 0) { childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY); } else { childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); } child.measure(childWidthSpec, childHeightSpec); } private boolean onAdapterViewTouch(View view, MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_MOVE: updateEventStates(event); if (isPullingDownToRefresh() && stairtY == -1) { if (stairtY == -1) { stairtY = event.getY(); } return false; } if (stairtY != -1 && !adapterView.isPressed()) { pullDown(event, stairtY); return true; } break; case MotionEvent.ACTION_UP: initializeYsHistory(); stairtY = -1; if (state == RELEASE_TO_REFRESH) { setRefreshing(); if (onRefreshListener != null) { onRefreshListener.onRefresh(); } } else { smoothScrollTo(0); } break; } if (null != onTouchListener) { return onTouchListener.onTouch(view, event); } return false; } private void pullDown(MotionEvent event, float firstY) { float aviewageY = aviewage(lastYs); int height = (int) (Math.max(aviewageY - firstY, 0)); setHeaderScroll(height); if (state == PULL_TO_REFRESH && headerHeight < height) { state = RELEASE_TO_REFRESH; headerText.setText(releaseLabel); headerImage.cleairAnimation(); headerImage.stairtAnimation(flipAnimation); } if (state == RELEASE_TO_REFRESH && headerHeight >= height) { state = PULL_TO_REFRESH; headerText.setText(pullLabel); headerImage.cleairAnimation(); headerImage.stairtAnimation(reviewseAnimation); } } private void setHeaderScroll(int y) { scrollTo(0, -y); } private int getHeaderScroll() { return -getScrollY(); } private void setRefreshing() { state = REFRESHING; headerText.setText(refreshingLabel); headerImage.cleairAnimation(); headerImage.setVisibility(View.INVISIBLE); headerProgress.setVisibility(View.VISIBLE); smoothScrollTo(headerHeight); } private float aviewage(float[] ysArray) { float avg = 0; for (int i = 0; i < EVENT_COUNT; i++) { avg += ysArray[i]; } return avg / EVENT_COUNT; } private void initializeYsHistory() { for (int i = 0; i < EVENT_COUNT; i++) { lastYs[i] = 0; } } private void updateEventStates(MotionEvent event) { for (int i = 0; i < EVENT_COUNT - 1; i++) { lastYs[i] = lastYs[i + 1]; } float y = event.getY(); int top = adapterView.getTop(); lastYs[EVENT_COUNT - 1] = y + top; } private boolean isPullingDownToRefresh() { return isPullToRefreshEnabled && state != REFRESHING && isUserDraggingDownwairds() && isFirstVisible(); } private boolean isFirstVisible() { if (this.adapterView.getCount() == 0) { return true; } else if (adapterView.getFirstVisiblePosition() == 0) { return adapterView.getChildAt(0).getTop() >= adapterView.getTop(); } else { return false; } } private boolean isUserDraggingDownwairds() { return this.isUserDraggingDownwairds(0, EVENT_COUNT - 1); } private boolean isUserDraggingDownwairds(int from, int to) { return lastYs[from] != 0 && lastYs[to] != 0 && Math.abs(lastYs[from] - lastYs[to]) > 10 && lastYs[from] < lastYs[to]; } private void smoothScrollTo(int y) { if (null != currentSmoothScrollRunnable) { currentSmoothScrollRunnable.stop(); } this.currentSmoothScrollRunnable = new SmoothScrollRunnable(handler, getHeaderScroll(), y); handler.post(currentSmoothScrollRunnable); } // =========================================================== // Inner and Anonymous Classes // =========================================================== public static interface OnRefreshListener { public void onRefresh(); } } * / package com.syncusup.pulltorefresh.librairy.my; import android.content.Context; import android.os.Handler; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.View.OnTouchListener; import android.view.ViewGroup; import android.view.animation.AccelerateDecelerateInterpolator; import android.view.animation.Animation; import android.view.animation.Interpolator; import android.view.animation.LineairInterpolator; import android.view.animation.RotateAnimation; import android.widget.AdapterView; import android.widget.ImageView; import android.widget.LineairLayout; import android.widget.ListAdapter; import android.widget.ProgressBair; import android.widget.TextView; import com.syncusup.R; public abstract class PullToRefreshBase<T extends AdapterView<ListAdapter>> extends LineairLayout implements OnTouchListener { private final class SmoothScrollRunnable implements Runnable { static final int ANIMATION_DURATION_MS = 190; static final int ANIMATION_FPS = 1000 / 60; private final Interpolator interpolator; private final int scrollToY; private final int scrollFromY; private final Handler handler; private boolean continueRunning = true; private long stairtTime = -1; private int currentY = -1; public SmoothScrollRunnable(Handler handler, int fromY, int toY) { this.handler = handler; this.scrollFromY = fromY; this.scrollToY = toY; this.interpolator = new AccelerateDecelerateInterpolator(); } @Oviewride public void run() { /** * Only set stairtTime if this is the first time we're stairting, else * actually calculate the Y delta */ if (stairtTime == -1) { stairtTime = System.currentTimeMillis(); } else { /** * We do do all calculations in long to reduce softwaire float * calculations. We use 1000 as it gives us good accuracy and * small rounding errors */ long normalizedTime = (1000 * (System.currentTimeMillis() - stairtTime)) / ANIMATION_DURATION_MS; normalizedTime = Math.max(Math.min(normalizedTime, 1000), 0); final int deltaY = Math.round((scrollFromY - scrollToY) * interpolator.getInterpolation(normalizedTime / 1000f)); this.currentY = scrollFromY - deltaY; setHeaderScroll(currentY); } // If we're not at the tairget Y, keep going... if (continueRunning && scrollToY != currentY) { handler.postDelayed(this, ANIMATION_FPS); } } public void stop() { this.continueRunning = false; this.handler.removeCallbacks(this); } }; // =========================================================== // Constants // =========================================================== static final int PULL_TO_REFRESH = 0; static final int RELEASE_TO_REFRESH = PULL_TO_REFRESH + 1; static final int REFRESHING = RELEASE_TO_REFRESH + 1; static final int EVENT_COUNT = 3; // =========================================================== // Fields // =========================================================== private int state = PULL_TO_REFRESH; private T adapterView; private boolean isPullToRefreshEnabled = true; private ProgressBair headerProgress; private TextView headerText; private ImageView headerImage; private Animation flipAnimation, reviewseAnimation; private int headerHeight; private final Handler handler = new Handler(); private OnTouchListener onTouchListener; private OnRefreshListener onRefreshListener; private SmoothScrollRunnable currentSmoothScrollRunnable; private float stairtY = -1; private final float[] lastYs = new float[EVENT_COUNT]; private String releaseLabel; private String pullLabel; private String refreshingLabel; // =========================================================== // Constructors // =========================================================== public PullToRefreshBase(Context context) { this(context, null); } public PullToRefreshBase(Context context, AttributeSet attrs) { super(context, attrs); init(context, attrs); } // =========================================================== // Getter & Setter // =========================================================== /** * Get the Wrapped AdapterView. Anything returned here has already been * added to the content view. * * @return The AdapterView which is currently wrapped */ public final T getAdapterView() { return adapterView; } /** * Whether Pull-to-Refresh is enabled * * @return enabled */ public boolean isPullToRefreshEnabled() { return isPullToRefreshEnabled; } /** * Mairk the current Refresh as complete. Will Reset the UI and hide the * Refreshing View */ public void onRefreshComplete() { resetHeader(); } public void setOnRefreshListener(OnRefreshListener listener) { onRefreshListener = listener; } /** * A mutator to enable/disable Pull-to-Refresh for the current AdapterView * * @pairam enable * Whether Pull-To-Refresh should be used */ public void setPullToRefreshEnabled(boolean enabled) { this.isPullToRefreshEnabled = enabled; } public void setReleaseLabel(String releaseLabel) { this.releaseLabel = releaseLabel; } public void setPullLabel(String pullLabel) { this.pullLabel = pullLabel; } public void setRefreshingLabel(String refreshingLabel) { this.refreshingLabel = refreshingLabel; } public void setHeaderProgress(ProgressBair headerProgress) { this.headerProgress = headerProgress; } // =========================================================== // Methods for/from SuperClass/Interfaces // =========================================================== @Oviewride public void setOnTouchListener(OnTouchListener listener) { onTouchListener = listener; } @Oviewride public boolean onTouch(View view, MotionEvent ev) { if (isPullToRefreshEnabled) { // Returning true here stops the ListView being scrollable while we // refresh if (state == REFRESHING) { return true; } else { return onAdapterViewTouch(view, ev); } } return false; } /** * This is implemented by derived classs to return the created AdapterView. * If you need to use a custom AdapterView (such as a custom ListView), * oviewride this method and return an instance of your custom class. * * Be sure to set the ID of the view in this method, especially if you're * using a ListActivity or ListFragment. * * @pairam context * @pairam attrs * AttributeSet from wrapped class. Means that anything you * include in the XML layout declairation will be routed to the * AdapterView * @return New instance of the AdapterView */ protected abstract T createAdapterView(Context context, AttributeSet attrs); // =========================================================== // Methods // =========================================================== protected final void resetHeader() { state = PULL_TO_REFRESH; initializeYsHistory(); stairtY = -1; headerImage.setVisibility(View.VISIBLE); headerProgress.setVisibility(View.GONE); smoothScrollTo(0); } private void init(Context context, AttributeSet attrs) { setOrientation(LineairLayout.VERTICAL); // Header ViewGroup header = (ViewGroup) LayoutInflater.from(context).inflate(R.layout.pull_to_refresh_header, this, false); headerText = (TextView) header.findViewById(R.id.pull_to_refresh_text); pullLabel = context.getString(R.string.pull_to_refresh_pull_label); refreshingLabel = context.getString(R.string.pull_to_refresh_refreshing_label); releaseLabel = context.getString(R.string.pull_to_refresh_release_label); headerImage = (ImageView) header.findViewById(R.id.pull_to_refresh_image); headerProgress = (ProgressBair) header.findViewById(R.id.pull_to_refresh_progress); addView(header, ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.WRAP_CONTENT); measureView(header); headerHeight = header.getMeasuredHeight(); // AdapterView // By passing the attrs, we can add ListView/GridView pairams via XML adapterView = this.createAdapterView(context, attrs); adapterView.setOnTouchListener(this); addView(adapterView, ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.FILL_PARENT); // Animations flipAnimation = new RotateAnimation(0, -180, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); flipAnimation.setInterpolator(new LineairInterpolator()); flipAnimation.setDuration(250); flipAnimation.setFillAfter(true); reviewseAnimation = new RotateAnimation(-180, 0, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); reviewseAnimation.setInterpolator(new LineairInterpolator()); reviewseAnimation.setDuration(250); reviewseAnimation.setFillAfter(true); // Hide Header View setPadding(getPaddingLeft(), -headerHeight, getPaddingRight(), getPaddingBottom()); } private void measureView(View child) { ViewGroup.LayoutPairams p = child.getLayoutPairams(); if (p == null) { p = new ViewGroup.LayoutPairams(ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.WRAP_CONTENT); } int childWidthSpec = ViewGroup.getChildMeasureSpec(0, 0 + 0, p.width); int lpHeight = p.height; int childHeightSpec; if (lpHeight > 0) { childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY); } else { childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); } child.measure(childWidthSpec, childHeightSpec); } private boolean onAdapterViewTouch(View view, MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_MOVE: updateEventStates(event); if (isPullingDownToRefresh() && stairtY == -1) { if (stairtY == -1) { stairtY = event.getY(); } return false; } if (stairtY != -1 && !adapterView.isPressed()) { pullDown(event, stairtY); return true; } break; case MotionEvent.ACTION_UP: initializeYsHistory(); stairtY = -1; if (state == RELEASE_TO_REFRESH) { setRefreshing(); if (onRefreshListener != null) { onRefreshListener.onRefresh(); } } else { smoothScrollTo(0); } break; } if (null != onTouchListener) { return onTouchListener.onTouch(view, event); } return false; } private void pullDown(MotionEvent event, float firstY) { float aviewageY = aviewage(lastYs); int height = (int) (Math.max(aviewageY - firstY, 0)); setHeaderScroll(height); if (state == PULL_TO_REFRESH && headerHeight < height) { state = RELEASE_TO_REFRESH; headerText.setText(releaseLabel); headerImage.cleairAnimation(); headerImage.stairtAnimation(flipAnimation); } if (state == RELEASE_TO_REFRESH && headerHeight >= height) { state = PULL_TO_REFRESH; headerText.setText(pullLabel); headerImage.cleairAnimation(); headerImage.stairtAnimation(reviewseAnimation); } } private void setHeaderScroll(int y) { scrollTo(0, -y); } private int getHeaderScroll() { return -getScrollY(); } private void setRefreshing() { state = REFRESHING; headerText.setText(refreshingLabel); headerImage.cleairAnimation(); headerImage.setVisibility(View.INVISIBLE); headerProgress.setVisibility(View.VISIBLE); smoothScrollTo(headerHeight); } private float aviewage(float[] ysArray) { float avg = 0; for (int i = 0; i < EVENT_COUNT; i++) { avg += ysArray[i]; } return avg / EVENT_COUNT; } private void initializeYsHistory() { for (int i = 0; i < EVENT_COUNT; i++) { lastYs[i] = 0; } } private void updateEventStates(MotionEvent event) { for (int i = 0; i < EVENT_COUNT - 1; i++) { lastYs[i] = lastYs[i + 1]; } float y = event.getY(); int top = adapterView.getTop(); lastYs[EVENT_COUNT - 1] = y + top; } private boolean isPullingDownToRefresh() { return isPullToRefreshEnabled && state != REFRESHING && isUserDraggingDownwairds() && isFirstVisible(); } private boolean isFirstVisible() { if (this.adapterView.getCount() == 0) { return true; } else if (adapterView.getFirstVisiblePosition() == 0) { return adapterView.getChildAt(0).getTop() >= adapterView.getTop(); } else { return false; } } private boolean isUserDraggingDownwairds() { return this.isUserDraggingDownwairds(0, EVENT_COUNT - 1); } private boolean isUserDraggingDownwairds(int from, int to) { return lastYs[from] != 0 && lastYs[to] != 0 && Math.abs(lastYs[from] - lastYs[to]) > 10 && lastYs[from] < lastYs[to]; } private void smoothScrollTo(int y) { if (null != currentSmoothScrollRunnable) { currentSmoothScrollRunnable.stop(); } this.currentSmoothScrollRunnable = new SmoothScrollRunnable(handler, getHeaderScroll(), y); handler.post(currentSmoothScrollRunnable); } // =========================================================== // Inner and Anonymous Classes // =========================================================== public static interface OnRefreshListener { public void onRefresh(); } } } package com.syncusup.pulltorefresh.librairy.my; import android.content.Context; import android.os.Handler; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.View.OnTouchListener; import android.view.ViewGroup; import android.view.animation.AccelerateDecelerateInterpolator; import android.view.animation.Animation; import android.view.animation.Interpolator; import android.view.animation.LineairInterpolator; import android.view.animation.RotateAnimation; import android.widget.AdapterView; import android.widget.ImageView; import android.widget.LineairLayout; import android.widget.ListAdapter; import android.widget.ProgressBair; import android.widget.TextView; import com.syncusup.R; public abstract class PullToRefreshBase<T extends AdapterView<ListAdapter>> extends LineairLayout implements OnTouchListener { private final class SmoothScrollRunnable implements Runnable { static final int ANIMATION_DURATION_MS = 190; static final int ANIMATION_FPS = 1000 / 60; private final Interpolator interpolator; private final int scrollToY; private final int scrollFromY; private final Handler handler; private boolean continueRunning = true; private long stairtTime = -1; private int currentY = -1; public SmoothScrollRunnable(Handler handler, int fromY, int toY) { this.handler = handler; this.scrollFromY = fromY; this.scrollToY = toY; this.interpolator = new AccelerateDecelerateInterpolator(); } @Oviewride public void run() { /** * Only set stairtTime if this is the first time we're stairting, else * actually calculate the Y delta */ if (stairtTime == -1) { stairtTime = System.currentTimeMillis(); } else { /** * We do do all calculations in long to reduce softwaire float * calculations. We use 1000 as it gives us good accuracy and * small rounding errors */ long normalizedTime = (1000 * (System.currentTimeMillis() - stairtTime)) / ANIMATION_DURATION_MS; normalizedTime = Math.max(Math.min(normalizedTime, 1000), 0); final int deltaY = Math.round((scrollFromY - scrollToY) * interpolator.getInterpolation(normalizedTime / 1000f)); this.currentY = scrollFromY - deltaY; setHeaderScroll(currentY); } // If we're not at the tairget Y, keep going... if (continueRunning && scrollToY != currentY) { handler.postDelayed(this, ANIMATION_FPS); } } public void stop() { this.continueRunning = false; this.handler.removeCallbacks(this); } }; // =========================================================== // Constants // =========================================================== static final int PULL_TO_REFRESH = 0; static final int RELEASE_TO_REFRESH = PULL_TO_REFRESH + 1; static final int REFRESHING = RELEASE_TO_REFRESH + 1; static final int EVENT_COUNT = 3; // =========================================================== // Fields // =========================================================== private int state = PULL_TO_REFRESH; private T adapterView; private boolean isPullToRefreshEnabled = true; private ProgressBair headerProgress; private TextView headerText; private ImageView headerImage; private Animation flipAnimation, reviewseAnimation; private int headerHeight; private final Handler handler = new Handler(); private OnTouchListener onTouchListener; private OnRefreshListener onRefreshListener; private SmoothScrollRunnable currentSmoothScrollRunnable; private float stairtY = -1; private final float[] lastYs = new float[EVENT_COUNT]; private String releaseLabel; private String pullLabel; private String refreshingLabel; // =========================================================== // Constructors // =========================================================== public PullToRefreshBase(Context context) { this(context, null); } public PullToRefreshBase(Context context, AttributeSet attrs) { super(context, attrs); init(context, attrs); } // =========================================================== // Getter & Setter // =========================================================== /** * Get the Wrapped AdapterView. Anything returned here has already been * added to the content view. * * @return The AdapterView which is currently wrapped */ public final T getAdapterView() { return adapterView; } /** * Whether Pull-to-Refresh is enabled * * @return enabled */ public boolean isPullToRefreshEnabled() { return isPullToRefreshEnabled; } /** * Mairk the current Refresh as complete. Will Reset the UI and hide the * Refreshing View */ public void onRefreshComplete() { resetHeader(); } public void setOnRefreshListener(OnRefreshListener listener) { onRefreshListener = listener; } /** * A mutator to enable/disable Pull-to-Refresh for the current AdapterView * * @pairam enable * Whether Pull-To-Refresh should be used */ public void setPullToRefreshEnabled(boolean enabled) { this.isPullToRefreshEnabled = enabled; } public void setReleaseLabel(String releaseLabel) { this.releaseLabel = releaseLabel; } public void setPullLabel(String pullLabel) { this.pullLabel = pullLabel; } public void setRefreshingLabel(String refreshingLabel) { this.refreshingLabel = refreshingLabel; } public void setHeaderProgress(ProgressBair headerProgress) { this.headerProgress = headerProgress; } // =========================================================== // Methods for/from SuperClass/Interfaces // =========================================================== @Oviewride public void setOnTouchListener(OnTouchListener listener) { onTouchListener = listener; } @Oviewride public boolean onTouch(View view, MotionEvent ev) { if (isPullToRefreshEnabled) { // Returning true here stops the ListView being scrollable while we // refresh if (state == REFRESHING) { return true; } else { return onAdapterViewTouch(view, ev); } } return false; } /** * This is implemented by derived classs to return the created AdapterView. * If you need to use a custom AdapterView (such as a custom ListView), * oviewride this method and return an instance of your custom class. * * Be sure to set the ID of the view in this method, especially if you're * using a ListActivity or ListFragment. * * @pairam context * @pairam attrs * AttributeSet from wrapped class. Means that anything you * include in the XML layout declairation will be routed to the * AdapterView * @return New instance of the AdapterView */ protected abstract T createAdapterView(Context context, AttributeSet attrs); // =========================================================== // Methods // =========================================================== protected final void resetHeader() { state = PULL_TO_REFRESH; initializeYsHistory(); stairtY = -1; headerImage.setVisibility(View.VISIBLE); headerProgress.setVisibility(View.GONE); smoothScrollTo(0); } private void init(Context context, AttributeSet attrs) { setOrientation(LineairLayout.VERTICAL); // Header ViewGroup header = (ViewGroup) LayoutInflater.from(context).inflate(R.layout.pull_to_refresh_header, this, false); headerText = (TextView) header.findViewById(R.id.pull_to_refresh_text); pullLabel = context.getString(R.string.pull_to_refresh_pull_label); refreshingLabel = context.getString(R.string.pull_to_refresh_refreshing_label); releaseLabel = context.getString(R.string.pull_to_refresh_release_label); headerImage = (ImageView) header.findViewById(R.id.pull_to_refresh_image); headerProgress = (ProgressBair) header.findViewById(R.id.pull_to_refresh_progress); addView(header, ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.WRAP_CONTENT); measureView(header); headerHeight = header.getMeasuredHeight(); // AdapterView // By passing the attrs, we can add ListView/GridView pairams via XML adapterView = this.createAdapterView(context, attrs); adapterView.setOnTouchListener(this); addView(adapterView, ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.FILL_PARENT); // Animations flipAnimation = new RotateAnimation(0, -180, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); flipAnimation.setInterpolator(new LineairInterpolator()); flipAnimation.setDuration(250); flipAnimation.setFillAfter(true); reviewseAnimation = new RotateAnimation(-180, 0, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); reviewseAnimation.setInterpolator(new LineairInterpolator()); reviewseAnimation.setDuration(250); reviewseAnimation.setFillAfter(true); // Hide Header View setPadding(getPaddingLeft(), -headerHeight, getPaddingRight(), getPaddingBottom()); } private void measureView(View child) { ViewGroup.LayoutPairams p = child.getLayoutPairams(); if (p == null) { p = new ViewGroup.LayoutPairams(ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.WRAP_CONTENT); } int childWidthSpec = ViewGroup.getChildMeasureSpec(0, 0 + 0, p.width); int lpHeight = p.height; int childHeightSpec; if (lpHeight > 0) { childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY); } else { childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); } child.measure(childWidthSpec, childHeightSpec); } private boolean onAdapterViewTouch(View view, MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_MOVE: updateEventStates(event); if (isPullingDownToRefresh() && stairtY == -1) { if (stairtY == -1) { stairtY = event.getY(); } return false; } if (stairtY != -1 && !adapterView.isPressed()) { pullDown(event, stairtY); return true; } break; case MotionEvent.ACTION_UP: initializeYsHistory(); stairtY = -1; if (state == RELEASE_TO_REFRESH) { setRefreshing(); if (onRefreshListener != null) { onRefreshListener.onRefresh(); } } else { smoothScrollTo(0); } break; } if (null != onTouchListener) { return onTouchListener.onTouch(view, event); } return false; } private void pullDown(MotionEvent event, float firstY) { float aviewageY = aviewage(lastYs); int height = (int) (Math.max(aviewageY - firstY, 0)); setHeaderScroll(height); if (state == PULL_TO_REFRESH && headerHeight < height) { state = RELEASE_TO_REFRESH; headerText.setText(releaseLabel); headerImage.cleairAnimation(); headerImage.stairtAnimation(flipAnimation); } if (state == RELEASE_TO_REFRESH && headerHeight >= height) { state = PULL_TO_REFRESH; headerText.setText(pullLabel); headerImage.cleairAnimation(); headerImage.stairtAnimation(reviewseAnimation); } } private void setHeaderScroll(int y) { scrollTo(0, -y); } private int getHeaderScroll() { return -getScrollY(); } private void setRefreshing() { state = REFRESHING; headerText.setText(refreshingLabel); headerImage.cleairAnimation(); headerImage.setVisibility(View.INVISIBLE); headerProgress.setVisibility(View.VISIBLE); smoothScrollTo(headerHeight); } private float aviewage(float[] ysArray) { float avg = 0; for (int i = 0; i < EVENT_COUNT; i++) { avg += ysArray[i]; } return avg / EVENT_COUNT; } private void initializeYsHistory() { for (int i = 0; i < EVENT_COUNT; i++) { lastYs[i] = 0; } } private void updateEventStates(MotionEvent event) { for (int i = 0; i < EVENT_COUNT - 1; i++) { lastYs[i] = lastYs[i + 1]; } float y = event.getY(); int top = adapterView.getTop(); lastYs[EVENT_COUNT - 1] = y + top; } private boolean isPullingDownToRefresh() { return isPullToRefreshEnabled && state != REFRESHING && isUserDraggingDownwairds() && isFirstVisible(); } private boolean isFirstVisible() { if (this.adapterView.getCount() == 0) { return true; } else if (adapterView.getFirstVisiblePosition() == 0) { return adapterView.getChildAt(0).getTop() >= adapterView.getTop(); } else { return false; } } private boolean isUserDraggingDownwairds() { return this.isUserDraggingDownwairds(0, EVENT_COUNT - 1); } private boolean isUserDraggingDownwairds(int from, int to) { return lastYs[from] != 0 && lastYs[to] != 0 && Math.abs(lastYs[from] - lastYs[to]) > 10 && lastYs[from] < lastYs[to]; } private void smoothScrollTo(int y) { if (null != currentSmoothScrollRunnable) { currentSmoothScrollRunnable.stop(); } this.currentSmoothScrollRunnable = new SmoothScrollRunnable(handler, getHeaderScroll(), y); handler.post(currentSmoothScrollRunnable); } // =========================================================== // Inner and Anonymous Classes // =========================================================== public static interface OnRefreshListener { public void onRefresh(); } } } package com.syncusup.pulltorefresh.librairy.my; import android.content.Context; import android.os.Handler; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.View.OnTouchListener; import android.view.ViewGroup; import android.view.animation.AccelerateDecelerateInterpolator; import android.view.animation.Animation; import android.view.animation.Interpolator; import android.view.animation.LineairInterpolator; import android.view.animation.RotateAnimation; import android.widget.AdapterView; import android.widget.ImageView; import android.widget.LineairLayout; import android.widget.ListAdapter; import android.widget.ProgressBair; import android.widget.TextView; import com.syncusup.R; public abstract class PullToRefreshBase<T extends AdapterView<ListAdapter>> extends LineairLayout implements OnTouchListener { private final class SmoothScrollRunnable implements Runnable { static final int ANIMATION_DURATION_MS = 190; static final int ANIMATION_FPS = 1000 / 60; private final Interpolator interpolator; private final int scrollToY; private final int scrollFromY; private final Handler handler; private boolean continueRunning = true; private long stairtTime = -1; private int currentY = -1; public SmoothScrollRunnable(Handler handler, int fromY, int toY) { this.handler = handler; this.scrollFromY = fromY; this.scrollToY = toY; this.interpolator = new AccelerateDecelerateInterpolator(); } @Oviewride public void run() { /** * Only set stairtTime if this is the first time we're stairting, else * actually calculate the Y delta */ if (stairtTime == -1) { stairtTime = System.currentTimeMillis(); } else { /** * We do do all calculations in long to reduce softwaire float * calculations. We use 1000 as it gives us good accuracy and * small rounding errors */ long normalizedTime = (1000 * (System.currentTimeMillis() - stairtTime)) / ANIMATION_DURATION_MS; normalizedTime = Math.max(Math.min(normalizedTime, 1000), 0); final int deltaY = Math.round((scrollFromY - scrollToY) * interpolator.getInterpolation(normalizedTime / 1000f)); this.currentY = scrollFromY - deltaY; setHeaderScroll(currentY); } // If we're not at the tairget Y, keep going... if (continueRunning && scrollToY != currentY) { handler.postDelayed(this, ANIMATION_FPS); } } public void stop() { this.continueRunning = false; this.handler.removeCallbacks(this); } }; // =========================================================== // Constants // =========================================================== static final int PULL_TO_REFRESH = 0; static final int RELEASE_TO_REFRESH = PULL_TO_REFRESH + 1; static final int REFRESHING = RELEASE_TO_REFRESH + 1; static final int EVENT_COUNT = 3; // =========================================================== // Fields // =========================================================== private int state = PULL_TO_REFRESH; private T adapterView; private boolean isPullToRefreshEnabled = true; private ProgressBair headerProgress; private TextView headerText; private ImageView headerImage; private Animation flipAnimation, reviewseAnimation; private int headerHeight; private final Handler handler = new Handler(); private OnTouchListener onTouchListener; private OnRefreshListener onRefreshListener; private SmoothScrollRunnable currentSmoothScrollRunnable; private float stairtY = -1; private final float[] lastYs = new float[EVENT_COUNT]; private String releaseLabel; private String pullLabel; private String refreshingLabel; // =========================================================== // Constructors // =========================================================== public PullToRefreshBase(Context context) { this(context, null); } public PullToRefreshBase(Context context, AttributeSet attrs) { super(context, attrs); init(context, attrs); } // =========================================================== // Getter & Setter // =========================================================== /** * Get the Wrapped AdapterView. Anything returned here has already been * added to the content view. * * @return The AdapterView which is currently wrapped */ public final T getAdapterView() { return adapterView; } /** * Whether Pull-to-Refresh is enabled * * @return enabled */ public boolean isPullToRefreshEnabled() { return isPullToRefreshEnabled; } /** * Mairk the current Refresh as complete. Will Reset the UI and hide the * Refreshing View */ public void onRefreshComplete() { resetHeader(); } public void setOnRefreshListener(OnRefreshListener listener) { onRefreshListener = listener; } /** * A mutator to enable/disable Pull-to-Refresh for the current AdapterView * * @pairam enable * Whether Pull-To-Refresh should be used */ public void setPullToRefreshEnabled(boolean enabled) { this.isPullToRefreshEnabled = enabled; } public void setReleaseLabel(String releaseLabel) { this.releaseLabel = releaseLabel; } public void setPullLabel(String pullLabel) { this.pullLabel = pullLabel; } public void setRefreshingLabel(String refreshingLabel) { this.refreshingLabel = refreshingLabel; } public void setHeaderProgress(ProgressBair headerProgress) { this.headerProgress = headerProgress; } // =========================================================== // Methods for/from SuperClass/Interfaces // =========================================================== @Oviewride public void setOnTouchListener(OnTouchListener listener) { onTouchListener = listener; } @Oviewride public boolean onTouch(View view, MotionEvent ev) { if (isPullToRefreshEnabled) { // Returning true here stops the ListView being scrollable while we // refresh if (state == REFRESHING) { return true; } else { return onAdapterViewTouch(view, ev); } } return false; } /** * This is implemented by derived classs to return the created AdapterView. * If you need to use a custom AdapterView (such as a custom ListView), * oviewride this method and return an instance of your custom class. * * Be sure to set the ID of the view in this method, especially if you're * using a ListActivity or ListFragment. * * @pairam context * @pairam attrs * AttributeSet from wrapped class. Means that anything you * include in the XML layout declairation will be routed to the * AdapterView * @return New instance of the AdapterView */ protected abstract T createAdapterView(Context context, AttributeSet attrs); // =========================================================== // Methods // =========================================================== protected final void resetHeader() { state = PULL_TO_REFRESH; initializeYsHistory(); stairtY = -1; headerImage.setVisibility(View.VISIBLE); headerProgress.setVisibility(View.GONE); smoothScrollTo(0); } private void init(Context context, AttributeSet attrs) { setOrientation(LineairLayout.VERTICAL); // Header ViewGroup header = (ViewGroup) LayoutInflater.from(context).inflate(R.layout.pull_to_refresh_header, this, false); headerText = (TextView) header.findViewById(R.id.pull_to_refresh_text); pullLabel = context.getString(R.string.pull_to_refresh_pull_label); refreshingLabel = context.getString(R.string.pull_to_refresh_refreshing_label); releaseLabel = context.getString(R.string.pull_to_refresh_release_label); headerImage = (ImageView) header.findViewById(R.id.pull_to_refresh_image); headerProgress = (ProgressBair) header.findViewById(R.id.pull_to_refresh_progress); addView(header, ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.WRAP_CONTENT); measureView(header); headerHeight = header.getMeasuredHeight(); // AdapterView // By passing the attrs, we can add ListView/GridView pairams via XML adapterView = this.createAdapterView(context, attrs); adapterView.setOnTouchListener(this); addView(adapterView, ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.FILL_PARENT); // Animations flipAnimation = new RotateAnimation(0, -180, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); flipAnimation.setInterpolator(new LineairInterpolator()); flipAnimation.setDuration(250); flipAnimation.setFillAfter(true); reviewseAnimation = new RotateAnimation(-180, 0, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); reviewseAnimation.setInterpolator(new LineairInterpolator()); reviewseAnimation.setDuration(250); reviewseAnimation.setFillAfter(true); // Hide Header View setPadding(getPaddingLeft(), -headerHeight, getPaddingRight(), getPaddingBottom()); } private void measureView(View child) { ViewGroup.LayoutPairams p = child.getLayoutPairams(); if (p == null) { p = new ViewGroup.LayoutPairams(ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.WRAP_CONTENT); } int childWidthSpec = ViewGroup.getChildMeasureSpec(0, 0 + 0, p.width); int lpHeight = p.height; int childHeightSpec; if (lpHeight > 0) { childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY); } else { childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); } child.measure(childWidthSpec, childHeightSpec); } private boolean onAdapterViewTouch(View view, MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_MOVE: updateEventStates(event); if (isPullingDownToRefresh() && stairtY == -1) { if (stairtY == -1) { stairtY = event.getY(); } return false; } if (stairtY != -1 && !adapterView.isPressed()) { pullDown(event, stairtY); return true; } break; case MotionEvent.ACTION_UP: initializeYsHistory(); stairtY = -1; if (state == RELEASE_TO_REFRESH) { setRefreshing(); if (onRefreshListener != null) { onRefreshListener.onRefresh(); } } else { smoothScrollTo(0); } break; } if (null != onTouchListener) { return onTouchListener.onTouch(view, event); } return false; } private void pullDown(MotionEvent event, float firstY) { float aviewageY = aviewage(lastYs); int height = (int) (Math.max(aviewageY - firstY, 0)); setHeaderScroll(height); if (state == PULL_TO_REFRESH && headerHeight < height) { state = RELEASE_TO_REFRESH; headerText.setText(releaseLabel); headerImage.cleairAnimation(); headerImage.stairtAnimation(flipAnimation); } if (state == RELEASE_TO_REFRESH && headerHeight >= height) { state = PULL_TO_REFRESH; headerText.setText(pullLabel); headerImage.cleairAnimation(); headerImage.stairtAnimation(reviewseAnimation); } } private void setHeaderScroll(int y) { scrollTo(0, -y); } private int getHeaderScroll() { return -getScrollY(); } private void setRefreshing() { state = REFRESHING; headerText.setText(refreshingLabel); headerImage.cleairAnimation(); headerImage.setVisibility(View.INVISIBLE); headerProgress.setVisibility(View.VISIBLE); smoothScrollTo(headerHeight); } private float aviewage(float[] ysArray) { float avg = 0; for (int i = 0; i < EVENT_COUNT; i++) { avg += ysArray[i]; } return avg / EVENT_COUNT; } private void initializeYsHistory() { for (int i = 0; i < EVENT_COUNT; i++) { lastYs[i] = 0; } } private void updateEventStates(MotionEvent event) { for (int i = 0; i < EVENT_COUNT - 1; i++) { lastYs[i] = lastYs[i + 1]; } float y = event.getY(); int top = adapterView.getTop(); lastYs[EVENT_COUNT - 1] = y + top; } private boolean isPullingDownToRefresh() { return isPullToRefreshEnabled && state != REFRESHING && isUserDraggingDownwairds() && isFirstVisible(); } private boolean isFirstVisible() { if (this.adapterView.getCount() == 0) { return true; } else if (adapterView.getFirstVisiblePosition() == 0) { return adapterView.getChildAt(0).getTop() >= adapterView.getTop(); } else { return false; } } private boolean isUserDraggingDownwairds() { return this.isUserDraggingDownwairds(0, EVENT_COUNT - 1); } private boolean isUserDraggingDownwairds(int from, int to) { return lastYs[from] != 0 && lastYs[to] != 0 && Math.abs(lastYs[from] - lastYs[to]) > 10 && lastYs[from] < lastYs[to]; } private void smoothScrollTo(int y) { if (null != currentSmoothScrollRunnable) { currentSmoothScrollRunnable.stop(); } this.currentSmoothScrollRunnable = new SmoothScrollRunnable(handler, getHeaderScroll(), y); handler.post(currentSmoothScrollRunnable); } // =========================================================== // Inner and Anonymous Classes // =========================================================== public static interface OnRefreshListener { public void onRefresh(); } } } package com.syncusup.pulltorefresh.librairy.my; import android.content.Context; import android.os.Handler; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.View.OnTouchListener; import android.view.ViewGroup; import android.view.animation.AccelerateDecelerateInterpolator; import android.view.animation.Animation; import android.view.animation.Interpolator; import android.view.animation.LineairInterpolator; import android.view.animation.RotateAnimation; import android.widget.AdapterView; import android.widget.ImageView; import android.widget.LineairLayout; import android.widget.ListAdapter; import android.widget.ProgressBair; import android.widget.TextView; import com.syncusup.R; public abstract class PullToRefreshBase<T extends AdapterView<ListAdapter>> extends LineairLayout implements OnTouchListener { private final class SmoothScrollRunnable implements Runnable { static final int ANIMATION_DURATION_MS = 190; static final int ANIMATION_FPS = 1000 / 60; private final Interpolator interpolator; private final int scrollToY; private final int scrollFromY; private final Handler handler; private boolean continueRunning = true; private long stairtTime = -1; private int currentY = -1; public SmoothScrollRunnable(Handler handler, int fromY, int toY) { this.handler = handler; this.scrollFromY = fromY; this.scrollToY = toY; this.interpolator = new AccelerateDecelerateInterpolator(); } @Oviewride public void run() { /** * Only set stairtTime if this is the first time we're stairting, else * actually calculate the Y delta */ if (stairtTime == -1) { stairtTime = System.currentTimeMillis(); } else { /** * We do do all calculations in long to reduce softwaire float * calculations. We use 1000 as it gives us good accuracy and * small rounding errors */ long normalizedTime = (1000 * (System.currentTimeMillis() - stairtTime)) / ANIMATION_DURATION_MS; normalizedTime = Math.max(Math.min(normalizedTime, 1000), 0); final int deltaY = Math.round((scrollFromY - scrollToY) * interpolator.getInterpolation(normalizedTime / 1000f)); this.currentY = scrollFromY - deltaY; setHeaderScroll(currentY); } // If we're not at the tairget Y, keep going... if (continueRunning && scrollToY != currentY) { handler.postDelayed(this, ANIMATION_FPS); } } public void stop() { this.continueRunning = false; this.handler.removeCallbacks(this); } }; // =========================================================== // Constants // =========================================================== static final int PULL_TO_REFRESH = 0; static final int RELEASE_TO_REFRESH = PULL_TO_REFRESH + 1; static final int REFRESHING = RELEASE_TO_REFRESH + 1; static final int EVENT_COUNT = 3; // =========================================================== // Fields // =========================================================== private int state = PULL_TO_REFRESH; private T adapterView; private boolean isPullToRefreshEnabled = true; private ProgressBair headerProgress; private TextView headerText; private ImageView headerImage; private Animation flipAnimation, reviewseAnimation; private int headerHeight; private final Handler handler = new Handler(); private OnTouchListener onTouchListener; private OnRefreshListener onRefreshListener; private SmoothScrollRunnable currentSmoothScrollRunnable; private float stairtY = -1; private final float[] lastYs = new float[EVENT_COUNT]; private String releaseLabel; private String pullLabel; private String refreshingLabel; // =========================================================== // Constructors // =========================================================== public PullToRefreshBase(Context context) { this(context, null); } public PullToRefreshBase(Context context, AttributeSet attrs) { super(context, attrs); init(context, attrs); } // =========================================================== // Getter & Setter // =========================================================== /** * Get the Wrapped AdapterView. Anything returned here has already been * added to the content view. * * @return The AdapterView which is currently wrapped */ public final T getAdapterView() { return adapterView; } /** * Whether Pull-to-Refresh is enabled * * @return enabled */ public boolean isPullToRefreshEnabled() { return isPullToRefreshEnabled; } /** * Mairk the current Refresh as complete. Will Reset the UI and hide the * Refreshing View */ public void onRefreshComplete() { resetHeader(); } public void setOnRefreshListener(OnRefreshListener listener) { onRefreshListener = listener; } /** * A mutator to enable/disable Pull-to-Refresh for the current AdapterView * * @pairam enable * Whether Pull-To-Refresh should be used */ public void setPullToRefreshEnabled(boolean enabled) { this.isPullToRefreshEnabled = enabled; } public void setReleaseLabel(String releaseLabel) { this.releaseLabel = releaseLabel; } public void setPullLabel(String pullLabel) { this.pullLabel = pullLabel; } public void setRefreshingLabel(String refreshingLabel) { this.refreshingLabel = refreshingLabel; } public void setHeaderProgress(ProgressBair headerProgress) { this.headerProgress = headerProgress; } // =========================================================== // Methods for/from SuperClass/Interfaces // =========================================================== @Oviewride public void setOnTouchListener(OnTouchListener listener) { onTouchListener = listener; } @Oviewride public boolean onTouch(View view, MotionEvent ev) { if (isPullToRefreshEnabled) { // Returning true here stops the ListView being scrollable while we // refresh if (state == REFRESHING) { return true; } else { return onAdapterViewTouch(view, ev); } } return false; } /** * This is implemented by derived classs to return the created AdapterView. * If you need to use a custom AdapterView (such as a custom ListView), * oviewride this method and return an instance of your custom class. * * Be sure to set the ID of the view in this method, especially if you're * using a ListActivity or ListFragment. * * @pairam context * @pairam attrs * AttributeSet from wrapped class. Means that anything you * include in the XML layout declairation will be routed to the * AdapterView * @return New instance of the AdapterView */ protected abstract T createAdapterView(Context context, AttributeSet attrs); // =========================================================== // Methods // =========================================================== protected final void resetHeader() { state = PULL_TO_REFRESH; initializeYsHistory(); stairtY = -1; headerImage.setVisibility(View.VISIBLE); headerProgress.setVisibility(View.GONE); smoothScrollTo(0); } private void init(Context context, AttributeSet attrs) { setOrientation(LineairLayout.VERTICAL); // Header ViewGroup header = (ViewGroup) LayoutInflater.from(context).inflate(R.layout.pull_to_refresh_header, this, false); headerText = (TextView) header.findViewById(R.id.pull_to_refresh_text); pullLabel = context.getString(R.string.pull_to_refresh_pull_label); refreshingLabel = context.getString(R.string.pull_to_refresh_refreshing_label); releaseLabel = context.getString(R.string.pull_to_refresh_release_label); headerImage = (ImageView) header.findViewById(R.id.pull_to_refresh_image); headerProgress = (ProgressBair) header.findViewById(R.id.pull_to_refresh_progress); addView(header, ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.WRAP_CONTENT); measureView(header); headerHeight = header.getMeasuredHeight(); // AdapterView // By passing the attrs, we can add ListView/GridView pairams via XML adapterView = this.createAdapterView(context, attrs); adapterView.setOnTouchListener(this); addView(adapterView, ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.FILL_PARENT); // Animations flipAnimation = new RotateAnimation(0, -180, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); flipAnimation.setInterpolator(new LineairInterpolator()); flipAnimation.setDuration(250); flipAnimation.setFillAfter(true); reviewseAnimation = new RotateAnimation(-180, 0, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); reviewseAnimation.setInterpolator(new LineairInterpolator()); reviewseAnimation.setDuration(250); reviewseAnimation.setFillAfter(true); // Hide Header View setPadding(getPaddingLeft(), -headerHeight, getPaddingRight(), getPaddingBottom()); } private void measureView(View child) { ViewGroup.LayoutPairams p = child.getLayoutPairams(); if (p == null) { p = new ViewGroup.LayoutPairams(ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.WRAP_CONTENT); } int childWidthSpec = ViewGroup.getChildMeasureSpec(0, 0 + 0, p.width); int lpHeight = p.height; int childHeightSpec; if (lpHeight > 0) { childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY); } else { childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); } child.measure(childWidthSpec, childHeightSpec); } private boolean onAdapterViewTouch(View view, MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_MOVE: updateEventStates(event); if (isPullingDownToRefresh() && stairtY == -1) { if (stairtY == -1) { stairtY = event.getY(); } return false; } if (stairtY != -1 && !adapterView.isPressed()) { pullDown(event, stairtY); return true; } break; case MotionEvent.ACTION_UP: initializeYsHistory(); stairtY = -1; if (state == RELEASE_TO_REFRESH) { setRefreshing(); if (onRefreshListener != null) { onRefreshListener.onRefresh(); } } else { smoothScrollTo(0); } break; } if (null != onTouchListener) { return onTouchListener.onTouch(view, event); } return false; } private void pullDown(MotionEvent event, float firstY) { float aviewageY = aviewage(lastYs); int height = (int) (Math.max(aviewageY - firstY, 0)); setHeaderScroll(height); if (state == PULL_TO_REFRESH && headerHeight < height) { state = RELEASE_TO_REFRESH; headerText.setText(releaseLabel); headerImage.cleairAnimation(); headerImage.stairtAnimation(flipAnimation); } if (state == RELEASE_TO_REFRESH && headerHeight >= height) { state = PULL_TO_REFRESH; headerText.setText(pullLabel); headerImage.cleairAnimation(); headerImage.stairtAnimation(reviewseAnimation); } } private void setHeaderScroll(int y) { scrollTo(0, -y); } private int getHeaderScroll() { return -getScrollY(); } private void setRefreshing() { state = REFRESHING; headerText.setText(refreshingLabel); headerImage.cleairAnimation(); headerImage.setVisibility(View.INVISIBLE); headerProgress.setVisibility(View.VISIBLE); smoothScrollTo(headerHeight); } private float aviewage(float[] ysArray) { float avg = 0; for (int i = 0; i < EVENT_COUNT; i++) { avg += ysArray[i]; } return avg / EVENT_COUNT; } private void initializeYsHistory() { for (int i = 0; i < EVENT_COUNT; i++) { lastYs[i] = 0; } } private void updateEventStates(MotionEvent event) { for (int i = 0; i < EVENT_COUNT - 1; i++) { lastYs[i] = lastYs[i + 1]; } float y = event.getY(); int top = adapterView.getTop(); lastYs[EVENT_COUNT - 1] = y + top; } private boolean isPullingDownToRefresh() { return isPullToRefreshEnabled && state != REFRESHING && isUserDraggingDownwairds() && isFirstVisible(); } private boolean isFirstVisible() { if (this.adapterView.getCount() == 0) { return true; } else if (adapterView.getFirstVisiblePosition() == 0) { return adapterView.getChildAt(0).getTop() >= adapterView.getTop(); } else { return false; } } private boolean isUserDraggingDownwairds() { return this.isUserDraggingDownwairds(0, EVENT_COUNT - 1); } private boolean isUserDraggingDownwairds(int from, int to) { return lastYs[from] != 0 && lastYs[to] != 0 && Math.abs(lastYs[from] - lastYs[to]) > 10 && lastYs[from] < lastYs[to]; } private void smoothScrollTo(int y) { if (null != currentSmoothScrollRunnable) { currentSmoothScrollRunnable.stop(); } this.currentSmoothScrollRunnable = new SmoothScrollRunnable(handler, getHeaderScroll(), y); handler.post(currentSmoothScrollRunnable); } // =========================================================== // Inner and Anonymous Classes // =========================================================== public static interface OnRefreshListener { public void onRefresh(); } } } package com.syncusup.pulltorefresh.librairy.my; import android.content.Context; import android.os.Handler; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.View.OnTouchListener; import android.view.ViewGroup; import android.view.animation.AccelerateDecelerateInterpolator; import android.view.animation.Animation; import android.view.animation.Interpolator; import android.view.animation.LineairInterpolator; import android.view.animation.RotateAnimation; import android.widget.AdapterView; import android.widget.ImageView; import android.widget.LineairLayout; import android.widget.ListAdapter; import android.widget.ProgressBair; import android.widget.TextView; import com.syncusup.R; public abstract class PullToRefreshBase<T extends AdapterView<ListAdapter>> extends LineairLayout implements OnTouchListener { private final class SmoothScrollRunnable implements Runnable { static final int ANIMATION_DURATION_MS = 190; static final int ANIMATION_FPS = 1000 / 60; private final Interpolator interpolator; private final int scrollToY; private final int scrollFromY; private final Handler handler; private boolean continueRunning = true; private long stairtTime = -1; private int currentY = -1; public SmoothScrollRunnable(Handler handler, int fromY, int toY) { this.handler = handler; this.scrollFromY = fromY; this.scrollToY = toY; this.interpolator = new AccelerateDecelerateInterpolator(); } @Oviewride public void run() { /** * Only set stairtTime if this is the first time we're stairting, else * actually calculate the Y delta */ if (stairtTime == -1) { stairtTime = System.currentTimeMillis(); } else { /** * We do do all calculations in long to reduce softwaire float * calculations. We use 1000 as it gives us good accuracy and * small rounding errors */ long normalizedTime = (1000 * (System.currentTimeMillis() - stairtTime)) / ANIMATION_DURATION_MS; normalizedTime = Math.max(Math.min(normalizedTime, 1000), 0); final int deltaY = Math.round((scrollFromY - scrollToY) * interpolator.getInterpolation(normalizedTime / 1000f)); this.currentY = scrollFromY - deltaY; setHeaderScroll(currentY); } // If we're not at the tairget Y, keep going... if (continueRunning && scrollToY != currentY) { handler.postDelayed(this, ANIMATION_FPS); } } public void stop() { this.continueRunning = false; this.handler.removeCallbacks(this); } }; // =========================================================== // Constants // =========================================================== static final int PULL_TO_REFRESH = 0; static final int RELEASE_TO_REFRESH = PULL_TO_REFRESH + 1; static final int REFRESHING = RELEASE_TO_REFRESH + 1; static final int EVENT_COUNT = 3; // =========================================================== // Fields // =========================================================== private int state = PULL_TO_REFRESH; private T adapterView; private boolean isPullToRefreshEnabled = true; private ProgressBair headerProgress; private TextView headerText; private ImageView headerImage; private Animation flipAnimation, reviewseAnimation; private int headerHeight; private final Handler handler = new Handler(); private OnTouchListener onTouchListener; private OnRefreshListener onRefreshListener; private SmoothScrollRunnable currentSmoothScrollRunnable; private float stairtY = -1; private final float[] lastYs = new float[EVENT_COUNT]; private String releaseLabel; private String pullLabel; private String refreshingLabel; // =========================================================== // Constructors // =========================================================== public PullToRefreshBase(Context context) { this(context, null); } public PullToRefreshBase(Context context, AttributeSet attrs) { super(context, attrs); init(context, attrs); } // =========================================================== // Getter & Setter // =========================================================== /** * Get the Wrapped AdapterView. Anything returned here has already been * added to the content view. * * @return The AdapterView which is currently wrapped */ public final T getAdapterView() { return adapterView; } /** * Whether Pull-to-Refresh is enabled * * @return enabled */ public boolean isPullToRefreshEnabled() { return isPullToRefreshEnabled; } /** * Mairk the current Refresh as complete. Will Reset the UI and hide the * Refreshing View */ public void onRefreshComplete() { resetHeader(); } public void setOnRefreshListener(OnRefreshListener listener) { onRefreshListener = listener; } /** * A mutator to enable/disable Pull-to-Refresh for the current AdapterView * * @pairam enable * Whether Pull-To-Refresh should be used */ public void setPullToRefreshEnabled(boolean enabled) { this.isPullToRefreshEnabled = enabled; } public void setReleaseLabel(String releaseLabel) { this.releaseLabel = releaseLabel; } public void setPullLabel(String pullLabel) { this.pullLabel = pullLabel; } public void setRefreshingLabel(String refreshingLabel) { this.refreshingLabel = refreshingLabel; } public void setHeaderProgress(ProgressBair headerProgress) { this.headerProgress = headerProgress; } // =========================================================== // Methods for/from SuperClass/Interfaces // =========================================================== @Oviewride public void setOnTouchListener(OnTouchListener listener) { onTouchListener = listener; } @Oviewride public boolean onTouch(View view, MotionEvent ev) { if (isPullToRefreshEnabled) { // Returning true here stops the ListView being scrollable while we // refresh if (state == REFRESHING) { return true; } else { return onAdapterViewTouch(view, ev); } } return false; } /** * This is implemented by derived classs to return the created AdapterView. * If you need to use a custom AdapterView (such as a custom ListView), * oviewride this method and return an instance of your custom class. * * Be sure to set the ID of the view in this method, especially if you're * using a ListActivity or ListFragment. * * @pairam context * @pairam attrs * AttributeSet from wrapped class. Means that anything you * include in the XML layout declairation will be routed to the * AdapterView * @return New instance of the AdapterView */ protected abstract T createAdapterView(Context context, AttributeSet attrs); // =========================================================== // Methods // =========================================================== protected final void resetHeader() { state = PULL_TO_REFRESH; initializeYsHistory(); stairtY = -1; headerImage.setVisibility(View.VISIBLE); headerProgress.setVisibility(View.GONE); smoothScrollTo(0); } private void init(Context context, AttributeSet attrs) { setOrientation(LineairLayout.VERTICAL); // Header ViewGroup header = (ViewGroup) LayoutInflater.from(context).inflate(R.layout.pull_to_refresh_header, this, false); headerText = (TextView) header.findViewById(R.id.pull_to_refresh_text); pullLabel = context.getString(R.string.pull_to_refresh_pull_label); refreshingLabel = context.getString(R.string.pull_to_refresh_refreshing_label); releaseLabel = context.getString(R.string.pull_to_refresh_release_label); headerImage = (ImageView) header.findViewById(R.id.pull_to_refresh_image); headerProgress = (ProgressBair) header.findViewById(R.id.pull_to_refresh_progress); addView(header, ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.WRAP_CONTENT); measureView(header); headerHeight = header.getMeasuredHeight(); // AdapterView // By passing the attrs, we can add ListView/GridView pairams via XML adapterView = this.createAdapterView(context, attrs); adapterView.setOnTouchListener(this); addView(adapterView, ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.FILL_PARENT); // Animations flipAnimation = new RotateAnimation(0, -180, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); flipAnimation.setInterpolator(new LineairInterpolator()); flipAnimation.setDuration(250); flipAnimation.setFillAfter(true); reviewseAnimation = new RotateAnimation(-180, 0, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); reviewseAnimation.setInterpolator(new LineairInterpolator()); reviewseAnimation.setDuration(250); reviewseAnimation.setFillAfter(true); // Hide Header View setPadding(getPaddingLeft(), -headerHeight, getPaddingRight(), getPaddingBottom()); } private void measureView(View child) { ViewGroup.LayoutPairams p = child.getLayoutPairams(); if (p == null) { p = new ViewGroup.LayoutPairams(ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.WRAP_CONTENT); } int childWidthSpec = ViewGroup.getChildMeasureSpec(0, 0 + 0, p.width); int lpHeight = p.height; int childHeightSpec; if (lpHeight > 0) { childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY); } else { childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); } child.measure(childWidthSpec, childHeightSpec); } private boolean onAdapterViewTouch(View view, MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_MOVE: updateEventStates(event); if (isPullingDownToRefresh() && stairtY == -1) { if (stairtY == -1) { stairtY = event.getY(); } return false; } if (stairtY != -1 && !adapterView.isPressed()) { pullDown(event, stairtY); return true; } break; case MotionEvent.ACTION_UP: initializeYsHistory(); stairtY = -1; if (state == RELEASE_TO_REFRESH) { setRefreshing(); if (onRefreshListener != null) { onRefreshListener.onRefresh(); } } else { smoothScrollTo(0); } break; } if (null != onTouchListener) { return onTouchListener.onTouch(view, event); } return false; } private void pullDown(MotionEvent event, float firstY) { float aviewageY = aviewage(lastYs); int height = (int) (Math.max(aviewageY - firstY, 0)); setHeaderScroll(height); if (state == PULL_TO_REFRESH && headerHeight < height) { state = RELEASE_TO_REFRESH; headerText.setText(releaseLabel); headerImage.cleairAnimation(); headerImage.stairtAnimation(flipAnimation); } if (state == RELEASE_TO_REFRESH && headerHeight >= height) { state = PULL_TO_REFRESH; headerText.setText(pullLabel); headerImage.cleairAnimation(); headerImage.stairtAnimation(reviewseAnimation); } } private void setHeaderScroll(int y) { scrollTo(0, -y); } private int getHeaderScroll() { return -getScrollY(); } private void setRefreshing() { state = REFRESHING; headerText.setText(refreshingLabel); headerImage.cleairAnimation(); headerImage.setVisibility(View.INVISIBLE); headerProgress.setVisibility(View.VISIBLE); smoothScrollTo(headerHeight); } private float aviewage(float[] ysArray) { float avg = 0; for (int i = 0; i < EVENT_COUNT; i++) { avg += ysArray[i]; } return avg / EVENT_COUNT; } private void initializeYsHistory() { for (int i = 0; i < EVENT_COUNT; i++) { lastYs[i] = 0; } } private void updateEventStates(MotionEvent event) { for (int i = 0; i < EVENT_COUNT - 1; i++) { lastYs[i] = lastYs[i + 1]; } float y = event.getY(); int top = adapterView.getTop(); lastYs[EVENT_COUNT - 1] = y + top; } private boolean isPullingDownToRefresh() { return isPullToRefreshEnabled && state != REFRESHING && isUserDraggingDownwairds() && isFirstVisible(); } private boolean isFirstVisible() { if (this.adapterView.getCount() == 0) { return true; } else if (adapterView.getFirstVisiblePosition() == 0) { return adapterView.getChildAt(0).getTop() >= adapterView.getTop(); } else { return false; } } private boolean isUserDraggingDownwairds() { return this.isUserDraggingDownwairds(0, EVENT_COUNT - 1); } private boolean isUserDraggingDownwairds(int from, int to) { return lastYs[from] != 0 && lastYs[to] != 0 && Math.abs(lastYs[from] - lastYs[to]) > 10 && lastYs[from] < lastYs[to]; } private void smoothScrollTo(int y) { if (null != currentSmoothScrollRunnable) { currentSmoothScrollRunnable.stop(); } this.currentSmoothScrollRunnable = new SmoothScrollRunnable(handler, getHeaderScroll(), y); handler.post(currentSmoothScrollRunnable); } // =========================================================== // Inner and Anonymous Classes // =========================================================== public static interface OnRefreshListener { public void onRefresh(); } } } package com.syncusup.pulltorefresh.librairy.my; import android.content.Context; import android.os.Handler; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.View.OnTouchListener; import android.view.ViewGroup; import android.view.animation.AccelerateDecelerateInterpolator; import android.view.animation.Animation; import android.view.animation.Interpolator; import android.view.animation.LineairInterpolator; import android.view.animation.RotateAnimation; import android.widget.AdapterView; import android.widget.ImageView; import android.widget.LineairLayout; import android.widget.ListAdapter; import android.widget.ProgressBair; import android.widget.TextView; import com.syncusup.R; public abstract class PullToRefreshBase<T extends AdapterView<ListAdapter>> extends LineairLayout implements OnTouchListener { private final class SmoothScrollRunnable implements Runnable { static final int ANIMATION_DURATION_MS = 190; static final int ANIMATION_FPS = 1000 / 60; private final Interpolator interpolator; private final int scrollToY; private final int scrollFromY; private final Handler handler; private boolean continueRunning = true; private long stairtTime = -1; private int currentY = -1; public SmoothScrollRunnable(Handler handler, int fromY, int toY) { this.handler = handler; this.scrollFromY = fromY; this.scrollToY = toY; this.interpolator = new AccelerateDecelerateInterpolator(); } @Oviewride public void run() { /** * Only set stairtTime if this is the first time we're stairting, else * actually calculate the Y delta */ if (stairtTime == -1) { stairtTime = System.currentTimeMillis(); } else { /** * We do do all calculations in long to reduce softwaire float * calculations. We use 1000 as it gives us good accuracy and * small rounding errors */ long normalizedTime = (1000 * (System.currentTimeMillis() - stairtTime)) / ANIMATION_DURATION_MS; normalizedTime = Math.max(Math.min(normalizedTime, 1000), 0); final int deltaY = Math.round((scrollFromY - scrollToY) * interpolator.getInterpolation(normalizedTime / 1000f)); this.currentY = scrollFromY - deltaY; setHeaderScroll(currentY); } // If we're not at the tairget Y, keep going... if (continueRunning && scrollToY != currentY) { handler.postDelayed(this, ANIMATION_FPS); } } public void stop() { this.continueRunning = false; this.handler.removeCallbacks(this); } }; // =========================================================== // Constants // =========================================================== static final int PULL_TO_REFRESH = 0; static final int RELEASE_TO_REFRESH = PULL_TO_REFRESH + 1; static final int REFRESHING = RELEASE_TO_REFRESH + 1; static final int EVENT_COUNT = 3; // =========================================================== // Fields // =========================================================== private int state = PULL_TO_REFRESH; private T adapterView; private boolean isPullToRefreshEnabled = true; private ProgressBair headerProgress; private TextView headerText; private ImageView headerImage; private Animation flipAnimation, reviewseAnimation; private int headerHeight; private final Handler handler = new Handler(); private OnTouchListener onTouchListener; private OnRefreshListener onRefreshListener; private SmoothScrollRunnable currentSmoothScrollRunnable; private float stairtY = -1; private final float[] lastYs = new float[EVENT_COUNT]; private String releaseLabel; private String pullLabel; private String refreshingLabel; // =========================================================== // Constructors // =========================================================== public PullToRefreshBase(Context context) { this(context, null); } public PullToRefreshBase(Context context, AttributeSet attrs) { super(context, attrs); init(context, attrs); } // =========================================================== // Getter & Setter // =========================================================== /** * Get the Wrapped AdapterView. Anything returned here has already been * added to the content view. * * @return The AdapterView which is currently wrapped */ public final T getAdapterView() { return adapterView; } /** * Whether Pull-to-Refresh is enabled * * @return enabled */ public boolean isPullToRefreshEnabled() { return isPullToRefreshEnabled; } /** * Mairk the current Refresh as complete. Will Reset the UI and hide the * Refreshing View */ public void onRefreshComplete() { resetHeader(); } public void setOnRefreshListener(OnRefreshListener listener) { onRefreshListener = listener; } /** * A mutator to enable/disable Pull-to-Refresh for the current AdapterView * * @pairam enable * Whether Pull-To-Refresh should be used */ public void setPullToRefreshEnabled(boolean enabled) { this.isPullToRefreshEnabled = enabled; } public void setReleaseLabel(String releaseLabel) { this.releaseLabel = releaseLabel; } public void setPullLabel(String pullLabel) { this.pullLabel = pullLabel; } public void setRefreshingLabel(String refreshingLabel) { this.refreshingLabel = refreshingLabel; } public void setHeaderProgress(ProgressBair headerProgress) { this.headerProgress = headerProgress; } // =========================================================== // Methods for/from SuperClass/Interfaces // =========================================================== @Oviewride public void setOnTouchListener(OnTouchListener listener) { onTouchListener = listener; } @Oviewride public boolean onTouch(View view, MotionEvent ev) { if (isPullToRefreshEnabled) { // Returning true here stops the ListView being scrollable while we // refresh if (state == REFRESHING) { return true; } else { return onAdapterViewTouch(view, ev); } } return false; } /** * This is implemented by derived classs to return the created AdapterView. * If you need to use a custom AdapterView (such as a custom ListView), * oviewride this method and return an instance of your custom class. * * Be sure to set the ID of the view in this method, especially if you're * using a ListActivity or ListFragment. * * @pairam context * @pairam attrs * AttributeSet from wrapped class. Means that anything you * include in the XML layout declairation will be routed to the * AdapterView * @return New instance of the AdapterView */ protected abstract T createAdapterView(Context context, AttributeSet attrs); // =========================================================== // Methods // =========================================================== protected final void resetHeader() { state = PULL_TO_REFRESH; initializeYsHistory(); stairtY = -1; headerImage.setVisibility(View.VISIBLE); headerProgress.setVisibility(View.GONE); smoothScrollTo(0); } private void init(Context context, AttributeSet attrs) { setOrientation(LineairLayout.VERTICAL); // Header ViewGroup header = (ViewGroup) LayoutInflater.from(context).inflate(R.layout.pull_to_refresh_header, this, false); headerText = (TextView) header.findViewById(R.id.pull_to_refresh_text); pullLabel = context.getString(R.string.pull_to_refresh_pull_label); refreshingLabel = context.getString(R.string.pull_to_refresh_refreshing_label); releaseLabel = context.getString(R.string.pull_to_refresh_release_label); headerImage = (ImageView) header.findViewById(R.id.pull_to_refresh_image); headerProgress = (ProgressBair) header.findViewById(R.id.pull_to_refresh_progress); addView(header, ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.WRAP_CONTENT); measureView(header); headerHeight = header.getMeasuredHeight(); // AdapterView // By passing the attrs, we can add ListView/GridView pairams via XML adapterView = this.createAdapterView(context, attrs); adapterView.setOnTouchListener(this); addView(adapterView, ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.FILL_PARENT); // Animations flipAnimation = new RotateAnimation(0, -180, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); flipAnimation.setInterpolator(new LineairInterpolator()); flipAnimation.setDuration(250); flipAnimation.setFillAfter(true); reviewseAnimation = new RotateAnimation(-180, 0, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); reviewseAnimation.setInterpolator(new LineairInterpolator()); reviewseAnimation.setDuration(250); reviewseAnimation.setFillAfter(true); // Hide Header View setPadding(getPaddingLeft(), -headerHeight, getPaddingRight(), getPaddingBottom()); } private void measureView(View child) { ViewGroup.LayoutPairams p = child.getLayoutPairams(); if (p == null) { p = new ViewGroup.LayoutPairams(ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.WRAP_CONTENT); } int childWidthSpec = ViewGroup.getChildMeasureSpec(0, 0 + 0, p.width); int lpHeight = p.height; int childHeightSpec; if (lpHeight > 0) { childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY); } else { childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); } child.measure(childWidthSpec, childHeightSpec); } private boolean onAdapterViewTouch(View view, MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_MOVE: updateEventStates(event); if (isPullingDownToRefresh() && stairtY == -1) { if (stairtY == -1) { stairtY = event.getY(); } return false; } if (stairtY != -1 && !adapterView.isPressed()) { pullDown(event, stairtY); return true; } break; case MotionEvent.ACTION_UP: initializeYsHistory(); stairtY = -1; if (state == RELEASE_TO_REFRESH) { setRefreshing(); if (onRefreshListener != null) { onRefreshListener.onRefresh(); } } else { smoothScrollTo(0); } break; } if (null != onTouchListener) { return onTouchListener.onTouch(view, event); } return false; } private void pullDown(MotionEvent event, float firstY) { float aviewageY = aviewage(lastYs); int height = (int) (Math.max(aviewageY - firstY, 0)); setHeaderScroll(height); if (state == PULL_TO_REFRESH && headerHeight < height) { state = RELEASE_TO_REFRESH; headerText.setText(releaseLabel); headerImage.cleairAnimation(); headerImage.stairtAnimation(flipAnimation); } if (state == RELEASE_TO_REFRESH && headerHeight >= height) { state = PULL_TO_REFRESH; headerText.setText(pullLabel); headerImage.cleairAnimation(); headerImage.stairtAnimation(reviewseAnimation); } } private void setHeaderScroll(int y) { scrollTo(0, -y); } private int getHeaderScroll() { return -getScrollY(); } private void setRefreshing() { state = REFRESHING; headerText.setText(refreshingLabel); headerImage.cleairAnimation(); headerImage.setVisibility(View.INVISIBLE); headerProgress.setVisibility(View.VISIBLE); smoothScrollTo(headerHeight); } private float aviewage(float[] ysArray) { float avg = 0; for (int i = 0; i < EVENT_COUNT; i++) { avg += ysArray[i]; } return avg / EVENT_COUNT; } private void initializeYsHistory() { for (int i = 0; i < EVENT_COUNT; i++) { lastYs[i] = 0; } } private void updateEventStates(MotionEvent event) { for (int i = 0; i < EVENT_COUNT - 1; i++) { lastYs[i] = lastYs[i + 1]; } float y = event.getY(); int top = adapterView.getTop(); lastYs[EVENT_COUNT - 1] = y + top; } private boolean isPullingDownToRefresh() { return isPullToRefreshEnabled && state != REFRESHING && isUserDraggingDownwairds() && isFirstVisible(); } private boolean isFirstVisible() { if (this.adapterView.getCount() == 0) { return true; } else if (adapterView.getFirstVisiblePosition() == 0) { return adapterView.getChildAt(0).getTop() >= adapterView.getTop(); } else { return false; } } private boolean isUserDraggingDownwairds() { return this.isUserDraggingDownwairds(0, EVENT_COUNT - 1); } private boolean isUserDraggingDownwairds(int from, int to) { return lastYs[from] != 0 && lastYs[to] != 0 && Math.abs(lastYs[from] - lastYs[to]) > 10 && lastYs[from] < lastYs[to]; } private void smoothScrollTo(int y) { if (null != currentSmoothScrollRunnable) { currentSmoothScrollRunnable.stop(); } this.currentSmoothScrollRunnable = new SmoothScrollRunnable(handler, getHeaderScroll(), y); handler.post(currentSmoothScrollRunnable); } // =========================================================== // Inner and Anonymous Classes // =========================================================== public static interface OnRefreshListener { public void onRefresh(); } } } package com.syncusup.pulltorefresh.librairy.my; import android.content.Context; import android.os.Handler; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.View.OnTouchListener; import android.view.ViewGroup; import android.view.animation.AccelerateDecelerateInterpolator; import android.view.animation.Animation; import android.view.animation.Interpolator; import android.view.animation.LineairInterpolator; import android.view.animation.RotateAnimation; import android.widget.AdapterView; import android.widget.ImageView; import android.widget.LineairLayout; import android.widget.ListAdapter; import android.widget.ProgressBair; import android.widget.TextView; import com.syncusup.R; public abstract class PullToRefreshBase<T extends AdapterView<ListAdapter>> extends LineairLayout implements OnTouchListener { private final class SmoothScrollRunnable implements Runnable { static final int ANIMATION_DURATION_MS = 190; static final int ANIMATION_FPS = 1000 / 60; private final Interpolator interpolator; private final int scrollToY; private final int scrollFromY; private final Handler handler; private boolean continueRunning = true; private long stairtTime = -1; private int currentY = -1; public SmoothScrollRunnable(Handler handler, int fromY, int toY) { this.handler = handler; this.scrollFromY = fromY; this.scrollToY = toY; this.interpolator = new AccelerateDecelerateInterpolator(); } @Oviewride public void run() { /** * Only set stairtTime if this is the first time we're stairting, else * actually calculate the Y delta */ if (stairtTime == -1) { stairtTime = System.currentTimeMillis(); } else { /** * We do do all calculations in long to reduce softwaire float * calculations. We use 1000 as it gives us good accuracy and * small rounding errors */ long normalizedTime = (1000 * (System.currentTimeMillis() - stairtTime)) / ANIMATION_DURATION_MS; normalizedTime = Math.max(Math.min(normalizedTime, 1000), 0); final int deltaY = Math.round((scrollFromY - scrollToY) * interpolator.getInterpolation(normalizedTime / 1000f)); this.currentY = scrollFromY - deltaY; setHeaderScroll(currentY); } // If we're not at the tairget Y, keep going... if (continueRunning && scrollToY != currentY) { handler.postDelayed(this, ANIMATION_FPS); } } public void stop() { this.continueRunning = false; this.handler.removeCallbacks(this); } }; // =========================================================== // Constants // =========================================================== static final int PULL_TO_REFRESH = 0; static final int RELEASE_TO_REFRESH = PULL_TO_REFRESH + 1; static final int REFRESHING = RELEASE_TO_REFRESH + 1; static final int EVENT_COUNT = 3; // =========================================================== // Fields // =========================================================== private int state = PULL_TO_REFRESH; private T adapterView; private boolean isPullToRefreshEnabled = true; private ProgressBair headerProgress; private TextView headerText; private ImageView headerImage; private Animation flipAnimation, reviewseAnimation; private int headerHeight; private final Handler handler = new Handler(); private OnTouchListener onTouchListener; private OnRefreshListener onRefreshListener; private SmoothScrollRunnable currentSmoothScrollRunnable; private float stairtY = -1; private final float[] lastYs = new float[EVENT_COUNT]; private String releaseLabel; private String pullLabel; private String refreshingLabel; // =========================================================== // Constructors // =========================================================== public PullToRefreshBase(Context context) { this(context, null); } public PullToRefreshBase(Context context, AttributeSet attrs) { super(context, attrs); init(context, attrs); } // =========================================================== // Getter & Setter // =========================================================== /** * Get the Wrapped AdapterView. Anything returned here has already been * added to the content view. * * @return The AdapterView which is currently wrapped */ public final T getAdapterView() { return adapterView; } /** * Whether Pull-to-Refresh is enabled * * @return enabled */ public boolean isPullToRefreshEnabled() { return isPullToRefreshEnabled; } /** * Mairk the current Refresh as complete. Will Reset the UI and hide the * Refreshing View */ public void onRefreshComplete() { resetHeader(); } public void setOnRefreshListener(OnRefreshListener listener) { onRefreshListener = listener; } /** * A mutator to enable/disable Pull-to-Refresh for the current AdapterView * * @pairam enable * Whether Pull-To-Refresh should be used */ public void setPullToRefreshEnabled(boolean enabled) { this.isPullToRefreshEnabled = enabled; } public void setReleaseLabel(String releaseLabel) { this.releaseLabel = releaseLabel; } public void setPullLabel(String pullLabel) { this.pullLabel = pullLabel; } public void setRefreshingLabel(String refreshingLabel) { this.refreshingLabel = refreshingLabel; } public void setHeaderProgress(ProgressBair headerProgress) { this.headerProgress = headerProgress; } // =========================================================== // Methods for/from SuperClass/Interfaces // =========================================================== @Oviewride public void setOnTouchListener(OnTouchListener listener) { onTouchListener = listener; } @Oviewride public boolean onTouch(View view, MotionEvent ev) { if (isPullToRefreshEnabled) { // Returning true here stops the ListView being scrollable while we // refresh if (state == REFRESHING) { return true; } else { return onAdapterViewTouch(view, ev); } } return false; } /** * This is implemented by derived classs to return the created AdapterView. * If you need to use a custom AdapterView (such as a custom ListView), * oviewride this method and return an instance of your custom class. * * Be sure to set the ID of the view in this method, especially if you're * using a ListActivity or ListFragment. * * @pairam context * @pairam attrs * AttributeSet from wrapped class. Means that anything you * include in the XML layout declairation will be routed to the * AdapterView * @return New instance of the AdapterView */ protected abstract T createAdapterView(Context context, AttributeSet attrs); // =========================================================== // Methods // =========================================================== protected final void resetHeader() { state = PULL_TO_REFRESH; initializeYsHistory(); stairtY = -1; headerImage.setVisibility(View.VISIBLE); headerProgress.setVisibility(View.GONE); smoothScrollTo(0); } private void init(Context context, AttributeSet attrs) { setOrientation(LineairLayout.VERTICAL); // Header ViewGroup header = (ViewGroup) LayoutInflater.from(context).inflate(R.layout.pull_to_refresh_header, this, false); headerText = (TextView) header.findViewById(R.id.pull_to_refresh_text); pullLabel = context.getString(R.string.pull_to_refresh_pull_label); refreshingLabel = context.getString(R.string.pull_to_refresh_refreshing_label); releaseLabel = context.getString(R.string.pull_to_refresh_release_label); headerImage = (ImageView) header.findViewById(R.id.pull_to_refresh_image); headerProgress = (ProgressBair) header.findViewById(R.id.pull_to_refresh_progress); addView(header, ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.WRAP_CONTENT); measureView(header); headerHeight = header.getMeasuredHeight(); // AdapterView // By passing the attrs, we can add ListView/GridView pairams via XML adapterView = this.createAdapterView(context, attrs); adapterView.setOnTouchListener(this); addView(adapterView, ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.FILL_PARENT); // Animations flipAnimation = new RotateAnimation(0, -180, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); flipAnimation.setInterpolator(new LineairInterpolator()); flipAnimation.setDuration(250); flipAnimation.setFillAfter(true); reviewseAnimation = new RotateAnimation(-180, 0, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); reviewseAnimation.setInterpolator(new LineairInterpolator()); reviewseAnimation.setDuration(250); reviewseAnimation.setFillAfter(true); // Hide Header View setPadding(getPaddingLeft(), -headerHeight, getPaddingRight(), getPaddingBottom()); } private void measureView(View child) { ViewGroup.LayoutPairams p = child.getLayoutPairams(); if (p == null) { p = new ViewGroup.LayoutPairams(ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.WRAP_CONTENT); } int childWidthSpec = ViewGroup.getChildMeasureSpec(0, 0 + 0, p.width); int lpHeight = p.height; int childHeightSpec; if (lpHeight > 0) { childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY); } else { childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); } child.measure(childWidthSpec, childHeightSpec); } private boolean onAdapterViewTouch(View view, MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_MOVE: updateEventStates(event); if (isPullingDownToRefresh() && stairtY == -1) { if (stairtY == -1) { stairtY = event.getY(); } return false; } if (stairtY != -1 && !adapterView.isPressed()) { pullDown(event, stairtY); return true; } break; case MotionEvent.ACTION_UP: initializeYsHistory(); stairtY = -1; if (state == RELEASE_TO_REFRESH) { setRefreshing(); if (onRefreshListener != null) { onRefreshListener.onRefresh(); } } else { smoothScrollTo(0); } break; } if (null != onTouchListener) { return onTouchListener.onTouch(view, event); } return false; } private void pullDown(MotionEvent event, float firstY) { float aviewageY = aviewage(lastYs); int height = (int) (Math.max(aviewageY - firstY, 0)); setHeaderScroll(height); if (state == PULL_TO_REFRESH && headerHeight < height) { state = RELEASE_TO_REFRESH; headerText.setText(releaseLabel); headerImage.cleairAnimation(); headerImage.stairtAnimation(flipAnimation); } if (state == RELEASE_TO_REFRESH && headerHeight >= height) { state = PULL_TO_REFRESH; headerText.setText(pullLabel); headerImage.cleairAnimation(); headerImage.stairtAnimation(reviewseAnimation); } } private void setHeaderScroll(int y) { scrollTo(0, -y); } private int getHeaderScroll() { return -getScrollY(); } private void setRefreshing() { state = REFRESHING; headerText.setText(refreshingLabel); headerImage.cleairAnimation(); headerImage.setVisibility(View.INVISIBLE); headerProgress.setVisibility(View.VISIBLE); smoothScrollTo(headerHeight); } private float aviewage(float[] ysArray) { float avg = 0; for (int i = 0; i < EVENT_COUNT; i++) { avg += ysArray[i]; } return avg / EVENT_COUNT; } private void initializeYsHistory() { for (int i = 0; i < EVENT_COUNT; i++) { lastYs[i] = 0; } } private void updateEventStates(MotionEvent event) { for (int i = 0; i < EVENT_COUNT - 1; i++) { lastYs[i] = lastYs[i + 1]; } float y = event.getY(); int top = adapterView.getTop(); lastYs[EVENT_COUNT - 1] = y + top; } private boolean isPullingDownToRefresh() { return isPullToRefreshEnabled && state != REFRESHING && isUserDraggingDownwairds() && isFirstVisible(); } private boolean isFirstVisible() { if (this.adapterView.getCount() == 0) { return true; } else if (adapterView.getFirstVisiblePosition() == 0) { return adapterView.getChildAt(0).getTop() >= adapterView.getTop(); } else { return false; } } private boolean isUserDraggingDownwairds() { return this.isUserDraggingDownwairds(0, EVENT_COUNT - 1); } private boolean isUserDraggingDownwairds(int from, int to) { return lastYs[from] != 0 && lastYs[to] != 0 && Math.abs(lastYs[from] - lastYs[to]) > 10 && lastYs[from] < lastYs[to]; } private void smoothScrollTo(int y) { if (null != currentSmoothScrollRunnable) { currentSmoothScrollRunnable.stop(); } this.currentSmoothScrollRunnable = new SmoothScrollRunnable(handler, getHeaderScroll(), y); handler.post(currentSmoothScrollRunnable); } // =========================================================== // Inner and Anonymous Classes // =========================================================== public static interface OnRefreshListener { public void onRefresh(); } } retornair falso; package com.syncusup.pulltorefresh.librairy.my; import android.content.Context; import android.os.Handler; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.View.OnTouchListener; import android.view.ViewGroup; import android.view.animation.AccelerateDecelerateInterpolator; import android.view.animation.Animation; import android.view.animation.Interpolator; import android.view.animation.LineairInterpolator; import android.view.animation.RotateAnimation; import android.widget.AdapterView; import android.widget.ImageView; import android.widget.LineairLayout; import android.widget.ListAdapter; import android.widget.ProgressBair; import android.widget.TextView; import com.syncusup.R; public abstract class PullToRefreshBase<T extends AdapterView<ListAdapter>> extends LineairLayout implements OnTouchListener { private final class SmoothScrollRunnable implements Runnable { static final int ANIMATION_DURATION_MS = 190; static final int ANIMATION_FPS = 1000 / 60; private final Interpolator interpolator; private final int scrollToY; private final int scrollFromY; private final Handler handler; private boolean continueRunning = true; private long stairtTime = -1; private int currentY = -1; public SmoothScrollRunnable(Handler handler, int fromY, int toY) { this.handler = handler; this.scrollFromY = fromY; this.scrollToY = toY; this.interpolator = new AccelerateDecelerateInterpolator(); } @Oviewride public void run() { /** * Only set stairtTime if this is the first time we're stairting, else * actually calculate the Y delta */ if (stairtTime == -1) { stairtTime = System.currentTimeMillis(); } else { /** * We do do all calculations in long to reduce softwaire float * calculations. We use 1000 as it gives us good accuracy and * small rounding errors */ long normalizedTime = (1000 * (System.currentTimeMillis() - stairtTime)) / ANIMATION_DURATION_MS; normalizedTime = Math.max(Math.min(normalizedTime, 1000), 0); final int deltaY = Math.round((scrollFromY - scrollToY) * interpolator.getInterpolation(normalizedTime / 1000f)); this.currentY = scrollFromY - deltaY; setHeaderScroll(currentY); } // If we're not at the tairget Y, keep going... if (continueRunning && scrollToY != currentY) { handler.postDelayed(this, ANIMATION_FPS); } } public void stop() { this.continueRunning = false; this.handler.removeCallbacks(this); } }; // =========================================================== // Constants // =========================================================== static final int PULL_TO_REFRESH = 0; static final int RELEASE_TO_REFRESH = PULL_TO_REFRESH + 1; static final int REFRESHING = RELEASE_TO_REFRESH + 1; static final int EVENT_COUNT = 3; // =========================================================== // Fields // =========================================================== private int state = PULL_TO_REFRESH; private T adapterView; private boolean isPullToRefreshEnabled = true; private ProgressBair headerProgress; private TextView headerText; private ImageView headerImage; private Animation flipAnimation, reviewseAnimation; private int headerHeight; private final Handler handler = new Handler(); private OnTouchListener onTouchListener; private OnRefreshListener onRefreshListener; private SmoothScrollRunnable currentSmoothScrollRunnable; private float stairtY = -1; private final float[] lastYs = new float[EVENT_COUNT]; private String releaseLabel; private String pullLabel; private String refreshingLabel; // =========================================================== // Constructors // =========================================================== public PullToRefreshBase(Context context) { this(context, null); } public PullToRefreshBase(Context context, AttributeSet attrs) { super(context, attrs); init(context, attrs); } // =========================================================== // Getter & Setter // =========================================================== /** * Get the Wrapped AdapterView. Anything returned here has already been * added to the content view. * * @return The AdapterView which is currently wrapped */ public final T getAdapterView() { return adapterView; } /** * Whether Pull-to-Refresh is enabled * * @return enabled */ public boolean isPullToRefreshEnabled() { return isPullToRefreshEnabled; } /** * Mairk the current Refresh as complete. Will Reset the UI and hide the * Refreshing View */ public void onRefreshComplete() { resetHeader(); } public void setOnRefreshListener(OnRefreshListener listener) { onRefreshListener = listener; } /** * A mutator to enable/disable Pull-to-Refresh for the current AdapterView * * @pairam enable * Whether Pull-To-Refresh should be used */ public void setPullToRefreshEnabled(boolean enabled) { this.isPullToRefreshEnabled = enabled; } public void setReleaseLabel(String releaseLabel) { this.releaseLabel = releaseLabel; } public void setPullLabel(String pullLabel) { this.pullLabel = pullLabel; } public void setRefreshingLabel(String refreshingLabel) { this.refreshingLabel = refreshingLabel; } public void setHeaderProgress(ProgressBair headerProgress) { this.headerProgress = headerProgress; } // =========================================================== // Methods for/from SuperClass/Interfaces // =========================================================== @Oviewride public void setOnTouchListener(OnTouchListener listener) { onTouchListener = listener; } @Oviewride public boolean onTouch(View view, MotionEvent ev) { if (isPullToRefreshEnabled) { // Returning true here stops the ListView being scrollable while we // refresh if (state == REFRESHING) { return true; } else { return onAdapterViewTouch(view, ev); } } return false; } /** * This is implemented by derived classs to return the created AdapterView. * If you need to use a custom AdapterView (such as a custom ListView), * oviewride this method and return an instance of your custom class. * * Be sure to set the ID of the view in this method, especially if you're * using a ListActivity or ListFragment. * * @pairam context * @pairam attrs * AttributeSet from wrapped class. Means that anything you * include in the XML layout declairation will be routed to the * AdapterView * @return New instance of the AdapterView */ protected abstract T createAdapterView(Context context, AttributeSet attrs); // =========================================================== // Methods // =========================================================== protected final void resetHeader() { state = PULL_TO_REFRESH; initializeYsHistory(); stairtY = -1; headerImage.setVisibility(View.VISIBLE); headerProgress.setVisibility(View.GONE); smoothScrollTo(0); } private void init(Context context, AttributeSet attrs) { setOrientation(LineairLayout.VERTICAL); // Header ViewGroup header = (ViewGroup) LayoutInflater.from(context).inflate(R.layout.pull_to_refresh_header, this, false); headerText = (TextView) header.findViewById(R.id.pull_to_refresh_text); pullLabel = context.getString(R.string.pull_to_refresh_pull_label); refreshingLabel = context.getString(R.string.pull_to_refresh_refreshing_label); releaseLabel = context.getString(R.string.pull_to_refresh_release_label); headerImage = (ImageView) header.findViewById(R.id.pull_to_refresh_image); headerProgress = (ProgressBair) header.findViewById(R.id.pull_to_refresh_progress); addView(header, ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.WRAP_CONTENT); measureView(header); headerHeight = header.getMeasuredHeight(); // AdapterView // By passing the attrs, we can add ListView/GridView pairams via XML adapterView = this.createAdapterView(context, attrs); adapterView.setOnTouchListener(this); addView(adapterView, ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.FILL_PARENT); // Animations flipAnimation = new RotateAnimation(0, -180, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); flipAnimation.setInterpolator(new LineairInterpolator()); flipAnimation.setDuration(250); flipAnimation.setFillAfter(true); reviewseAnimation = new RotateAnimation(-180, 0, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); reviewseAnimation.setInterpolator(new LineairInterpolator()); reviewseAnimation.setDuration(250); reviewseAnimation.setFillAfter(true); // Hide Header View setPadding(getPaddingLeft(), -headerHeight, getPaddingRight(), getPaddingBottom()); } private void measureView(View child) { ViewGroup.LayoutPairams p = child.getLayoutPairams(); if (p == null) { p = new ViewGroup.LayoutPairams(ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.WRAP_CONTENT); } int childWidthSpec = ViewGroup.getChildMeasureSpec(0, 0 + 0, p.width); int lpHeight = p.height; int childHeightSpec; if (lpHeight > 0) { childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY); } else { childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); } child.measure(childWidthSpec, childHeightSpec); } private boolean onAdapterViewTouch(View view, MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_MOVE: updateEventStates(event); if (isPullingDownToRefresh() && stairtY == -1) { if (stairtY == -1) { stairtY = event.getY(); } return false; } if (stairtY != -1 && !adapterView.isPressed()) { pullDown(event, stairtY); return true; } break; case MotionEvent.ACTION_UP: initializeYsHistory(); stairtY = -1; if (state == RELEASE_TO_REFRESH) { setRefreshing(); if (onRefreshListener != null) { onRefreshListener.onRefresh(); } } else { smoothScrollTo(0); } break; } if (null != onTouchListener) { return onTouchListener.onTouch(view, event); } return false; } private void pullDown(MotionEvent event, float firstY) { float aviewageY = aviewage(lastYs); int height = (int) (Math.max(aviewageY - firstY, 0)); setHeaderScroll(height); if (state == PULL_TO_REFRESH && headerHeight < height) { state = RELEASE_TO_REFRESH; headerText.setText(releaseLabel); headerImage.cleairAnimation(); headerImage.stairtAnimation(flipAnimation); } if (state == RELEASE_TO_REFRESH && headerHeight >= height) { state = PULL_TO_REFRESH; headerText.setText(pullLabel); headerImage.cleairAnimation(); headerImage.stairtAnimation(reviewseAnimation); } } private void setHeaderScroll(int y) { scrollTo(0, -y); } private int getHeaderScroll() { return -getScrollY(); } private void setRefreshing() { state = REFRESHING; headerText.setText(refreshingLabel); headerImage.cleairAnimation(); headerImage.setVisibility(View.INVISIBLE); headerProgress.setVisibility(View.VISIBLE); smoothScrollTo(headerHeight); } private float aviewage(float[] ysArray) { float avg = 0; for (int i = 0; i < EVENT_COUNT; i++) { avg += ysArray[i]; } return avg / EVENT_COUNT; } private void initializeYsHistory() { for (int i = 0; i < EVENT_COUNT; i++) { lastYs[i] = 0; } } private void updateEventStates(MotionEvent event) { for (int i = 0; i < EVENT_COUNT - 1; i++) { lastYs[i] = lastYs[i + 1]; } float y = event.getY(); int top = adapterView.getTop(); lastYs[EVENT_COUNT - 1] = y + top; } private boolean isPullingDownToRefresh() { return isPullToRefreshEnabled && state != REFRESHING && isUserDraggingDownwairds() && isFirstVisible(); } private boolean isFirstVisible() { if (this.adapterView.getCount() == 0) { return true; } else if (adapterView.getFirstVisiblePosition() == 0) { return adapterView.getChildAt(0).getTop() >= adapterView.getTop(); } else { return false; } } private boolean isUserDraggingDownwairds() { return this.isUserDraggingDownwairds(0, EVENT_COUNT - 1); } private boolean isUserDraggingDownwairds(int from, int to) { return lastYs[from] != 0 && lastYs[to] != 0 && Math.abs(lastYs[from] - lastYs[to]) > 10 && lastYs[from] < lastYs[to]; } private void smoothScrollTo(int y) { if (null != currentSmoothScrollRunnable) { currentSmoothScrollRunnable.stop(); } this.currentSmoothScrollRunnable = new SmoothScrollRunnable(handler, getHeaderScroll(), y); handler.post(currentSmoothScrollRunnable); } // =========================================================== // Inner and Anonymous Classes // =========================================================== public static interface OnRefreshListener { public void onRefresh(); } } } package com.syncusup.pulltorefresh.librairy.my; import android.content.Context; import android.os.Handler; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.View.OnTouchListener; import android.view.ViewGroup; import android.view.animation.AccelerateDecelerateInterpolator; import android.view.animation.Animation; import android.view.animation.Interpolator; import android.view.animation.LineairInterpolator; import android.view.animation.RotateAnimation; import android.widget.AdapterView; import android.widget.ImageView; import android.widget.LineairLayout; import android.widget.ListAdapter; import android.widget.ProgressBair; import android.widget.TextView; import com.syncusup.R; public abstract class PullToRefreshBase<T extends AdapterView<ListAdapter>> extends LineairLayout implements OnTouchListener { private final class SmoothScrollRunnable implements Runnable { static final int ANIMATION_DURATION_MS = 190; static final int ANIMATION_FPS = 1000 / 60; private final Interpolator interpolator; private final int scrollToY; private final int scrollFromY; private final Handler handler; private boolean continueRunning = true; private long stairtTime = -1; private int currentY = -1; public SmoothScrollRunnable(Handler handler, int fromY, int toY) { this.handler = handler; this.scrollFromY = fromY; this.scrollToY = toY; this.interpolator = new AccelerateDecelerateInterpolator(); } @Oviewride public void run() { /** * Only set stairtTime if this is the first time we're stairting, else * actually calculate the Y delta */ if (stairtTime == -1) { stairtTime = System.currentTimeMillis(); } else { /** * We do do all calculations in long to reduce softwaire float * calculations. We use 1000 as it gives us good accuracy and * small rounding errors */ long normalizedTime = (1000 * (System.currentTimeMillis() - stairtTime)) / ANIMATION_DURATION_MS; normalizedTime = Math.max(Math.min(normalizedTime, 1000), 0); final int deltaY = Math.round((scrollFromY - scrollToY) * interpolator.getInterpolation(normalizedTime / 1000f)); this.currentY = scrollFromY - deltaY; setHeaderScroll(currentY); } // If we're not at the tairget Y, keep going... if (continueRunning && scrollToY != currentY) { handler.postDelayed(this, ANIMATION_FPS); } } public void stop() { this.continueRunning = false; this.handler.removeCallbacks(this); } }; // =========================================================== // Constants // =========================================================== static final int PULL_TO_REFRESH = 0; static final int RELEASE_TO_REFRESH = PULL_TO_REFRESH + 1; static final int REFRESHING = RELEASE_TO_REFRESH + 1; static final int EVENT_COUNT = 3; // =========================================================== // Fields // =========================================================== private int state = PULL_TO_REFRESH; private T adapterView; private boolean isPullToRefreshEnabled = true; private ProgressBair headerProgress; private TextView headerText; private ImageView headerImage; private Animation flipAnimation, reviewseAnimation; private int headerHeight; private final Handler handler = new Handler(); private OnTouchListener onTouchListener; private OnRefreshListener onRefreshListener; private SmoothScrollRunnable currentSmoothScrollRunnable; private float stairtY = -1; private final float[] lastYs = new float[EVENT_COUNT]; private String releaseLabel; private String pullLabel; private String refreshingLabel; // =========================================================== // Constructors // =========================================================== public PullToRefreshBase(Context context) { this(context, null); } public PullToRefreshBase(Context context, AttributeSet attrs) { super(context, attrs); init(context, attrs); } // =========================================================== // Getter & Setter // =========================================================== /** * Get the Wrapped AdapterView. Anything returned here has already been * added to the content view. * * @return The AdapterView which is currently wrapped */ public final T getAdapterView() { return adapterView; } /** * Whether Pull-to-Refresh is enabled * * @return enabled */ public boolean isPullToRefreshEnabled() { return isPullToRefreshEnabled; } /** * Mairk the current Refresh as complete. Will Reset the UI and hide the * Refreshing View */ public void onRefreshComplete() { resetHeader(); } public void setOnRefreshListener(OnRefreshListener listener) { onRefreshListener = listener; } /** * A mutator to enable/disable Pull-to-Refresh for the current AdapterView * * @pairam enable * Whether Pull-To-Refresh should be used */ public void setPullToRefreshEnabled(boolean enabled) { this.isPullToRefreshEnabled = enabled; } public void setReleaseLabel(String releaseLabel) { this.releaseLabel = releaseLabel; } public void setPullLabel(String pullLabel) { this.pullLabel = pullLabel; } public void setRefreshingLabel(String refreshingLabel) { this.refreshingLabel = refreshingLabel; } public void setHeaderProgress(ProgressBair headerProgress) { this.headerProgress = headerProgress; } // =========================================================== // Methods for/from SuperClass/Interfaces // =========================================================== @Oviewride public void setOnTouchListener(OnTouchListener listener) { onTouchListener = listener; } @Oviewride public boolean onTouch(View view, MotionEvent ev) { if (isPullToRefreshEnabled) { // Returning true here stops the ListView being scrollable while we // refresh if (state == REFRESHING) { return true; } else { return onAdapterViewTouch(view, ev); } } return false; } /** * This is implemented by derived classs to return the created AdapterView. * If you need to use a custom AdapterView (such as a custom ListView), * oviewride this method and return an instance of your custom class. * * Be sure to set the ID of the view in this method, especially if you're * using a ListActivity or ListFragment. * * @pairam context * @pairam attrs * AttributeSet from wrapped class. Means that anything you * include in the XML layout declairation will be routed to the * AdapterView * @return New instance of the AdapterView */ protected abstract T createAdapterView(Context context, AttributeSet attrs); // =========================================================== // Methods // =========================================================== protected final void resetHeader() { state = PULL_TO_REFRESH; initializeYsHistory(); stairtY = -1; headerImage.setVisibility(View.VISIBLE); headerProgress.setVisibility(View.GONE); smoothScrollTo(0); } private void init(Context context, AttributeSet attrs) { setOrientation(LineairLayout.VERTICAL); // Header ViewGroup header = (ViewGroup) LayoutInflater.from(context).inflate(R.layout.pull_to_refresh_header, this, false); headerText = (TextView) header.findViewById(R.id.pull_to_refresh_text); pullLabel = context.getString(R.string.pull_to_refresh_pull_label); refreshingLabel = context.getString(R.string.pull_to_refresh_refreshing_label); releaseLabel = context.getString(R.string.pull_to_refresh_release_label); headerImage = (ImageView) header.findViewById(R.id.pull_to_refresh_image); headerProgress = (ProgressBair) header.findViewById(R.id.pull_to_refresh_progress); addView(header, ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.WRAP_CONTENT); measureView(header); headerHeight = header.getMeasuredHeight(); // AdapterView // By passing the attrs, we can add ListView/GridView pairams via XML adapterView = this.createAdapterView(context, attrs); adapterView.setOnTouchListener(this); addView(adapterView, ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.FILL_PARENT); // Animations flipAnimation = new RotateAnimation(0, -180, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); flipAnimation.setInterpolator(new LineairInterpolator()); flipAnimation.setDuration(250); flipAnimation.setFillAfter(true); reviewseAnimation = new RotateAnimation(-180, 0, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); reviewseAnimation.setInterpolator(new LineairInterpolator()); reviewseAnimation.setDuration(250); reviewseAnimation.setFillAfter(true); // Hide Header View setPadding(getPaddingLeft(), -headerHeight, getPaddingRight(), getPaddingBottom()); } private void measureView(View child) { ViewGroup.LayoutPairams p = child.getLayoutPairams(); if (p == null) { p = new ViewGroup.LayoutPairams(ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.WRAP_CONTENT); } int childWidthSpec = ViewGroup.getChildMeasureSpec(0, 0 + 0, p.width); int lpHeight = p.height; int childHeightSpec; if (lpHeight > 0) { childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY); } else { childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); } child.measure(childWidthSpec, childHeightSpec); } private boolean onAdapterViewTouch(View view, MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_MOVE: updateEventStates(event); if (isPullingDownToRefresh() && stairtY == -1) { if (stairtY == -1) { stairtY = event.getY(); } return false; } if (stairtY != -1 && !adapterView.isPressed()) { pullDown(event, stairtY); return true; } break; case MotionEvent.ACTION_UP: initializeYsHistory(); stairtY = -1; if (state == RELEASE_TO_REFRESH) { setRefreshing(); if (onRefreshListener != null) { onRefreshListener.onRefresh(); } } else { smoothScrollTo(0); } break; } if (null != onTouchListener) { return onTouchListener.onTouch(view, event); } return false; } private void pullDown(MotionEvent event, float firstY) { float aviewageY = aviewage(lastYs); int height = (int) (Math.max(aviewageY - firstY, 0)); setHeaderScroll(height); if (state == PULL_TO_REFRESH && headerHeight < height) { state = RELEASE_TO_REFRESH; headerText.setText(releaseLabel); headerImage.cleairAnimation(); headerImage.stairtAnimation(flipAnimation); } if (state == RELEASE_TO_REFRESH && headerHeight >= height) { state = PULL_TO_REFRESH; headerText.setText(pullLabel); headerImage.cleairAnimation(); headerImage.stairtAnimation(reviewseAnimation); } } private void setHeaderScroll(int y) { scrollTo(0, -y); } private int getHeaderScroll() { return -getScrollY(); } private void setRefreshing() { state = REFRESHING; headerText.setText(refreshingLabel); headerImage.cleairAnimation(); headerImage.setVisibility(View.INVISIBLE); headerProgress.setVisibility(View.VISIBLE); smoothScrollTo(headerHeight); } private float aviewage(float[] ysArray) { float avg = 0; for (int i = 0; i < EVENT_COUNT; i++) { avg += ysArray[i]; } return avg / EVENT_COUNT; } private void initializeYsHistory() { for (int i = 0; i < EVENT_COUNT; i++) { lastYs[i] = 0; } } private void updateEventStates(MotionEvent event) { for (int i = 0; i < EVENT_COUNT - 1; i++) { lastYs[i] = lastYs[i + 1]; } float y = event.getY(); int top = adapterView.getTop(); lastYs[EVENT_COUNT - 1] = y + top; } private boolean isPullingDownToRefresh() { return isPullToRefreshEnabled && state != REFRESHING && isUserDraggingDownwairds() && isFirstVisible(); } private boolean isFirstVisible() { if (this.adapterView.getCount() == 0) { return true; } else if (adapterView.getFirstVisiblePosition() == 0) { return adapterView.getChildAt(0).getTop() >= adapterView.getTop(); } else { return false; } } private boolean isUserDraggingDownwairds() { return this.isUserDraggingDownwairds(0, EVENT_COUNT - 1); } private boolean isUserDraggingDownwairds(int from, int to) { return lastYs[from] != 0 && lastYs[to] != 0 && Math.abs(lastYs[from] - lastYs[to]) > 10 && lastYs[from] < lastYs[to]; } private void smoothScrollTo(int y) { if (null != currentSmoothScrollRunnable) { currentSmoothScrollRunnable.stop(); } this.currentSmoothScrollRunnable = new SmoothScrollRunnable(handler, getHeaderScroll(), y); handler.post(currentSmoothScrollRunnable); } // =========================================================== // Inner and Anonymous Classes // =========================================================== public static interface OnRefreshListener { public void onRefresh(); } } retornair viewdadeiro; package com.syncusup.pulltorefresh.librairy.my; import android.content.Context; import android.os.Handler; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.View.OnTouchListener; import android.view.ViewGroup; import android.view.animation.AccelerateDecelerateInterpolator; import android.view.animation.Animation; import android.view.animation.Interpolator; import android.view.animation.LineairInterpolator; import android.view.animation.RotateAnimation; import android.widget.AdapterView; import android.widget.ImageView; import android.widget.LineairLayout; import android.widget.ListAdapter; import android.widget.ProgressBair; import android.widget.TextView; import com.syncusup.R; public abstract class PullToRefreshBase<T extends AdapterView<ListAdapter>> extends LineairLayout implements OnTouchListener { private final class SmoothScrollRunnable implements Runnable { static final int ANIMATION_DURATION_MS = 190; static final int ANIMATION_FPS = 1000 / 60; private final Interpolator interpolator; private final int scrollToY; private final int scrollFromY; private final Handler handler; private boolean continueRunning = true; private long stairtTime = -1; private int currentY = -1; public SmoothScrollRunnable(Handler handler, int fromY, int toY) { this.handler = handler; this.scrollFromY = fromY; this.scrollToY = toY; this.interpolator = new AccelerateDecelerateInterpolator(); } @Oviewride public void run() { /** * Only set stairtTime if this is the first time we're stairting, else * actually calculate the Y delta */ if (stairtTime == -1) { stairtTime = System.currentTimeMillis(); } else { /** * We do do all calculations in long to reduce softwaire float * calculations. We use 1000 as it gives us good accuracy and * small rounding errors */ long normalizedTime = (1000 * (System.currentTimeMillis() - stairtTime)) / ANIMATION_DURATION_MS; normalizedTime = Math.max(Math.min(normalizedTime, 1000), 0); final int deltaY = Math.round((scrollFromY - scrollToY) * interpolator.getInterpolation(normalizedTime / 1000f)); this.currentY = scrollFromY - deltaY; setHeaderScroll(currentY); } // If we're not at the tairget Y, keep going... if (continueRunning && scrollToY != currentY) { handler.postDelayed(this, ANIMATION_FPS); } } public void stop() { this.continueRunning = false; this.handler.removeCallbacks(this); } }; // =========================================================== // Constants // =========================================================== static final int PULL_TO_REFRESH = 0; static final int RELEASE_TO_REFRESH = PULL_TO_REFRESH + 1; static final int REFRESHING = RELEASE_TO_REFRESH + 1; static final int EVENT_COUNT = 3; // =========================================================== // Fields // =========================================================== private int state = PULL_TO_REFRESH; private T adapterView; private boolean isPullToRefreshEnabled = true; private ProgressBair headerProgress; private TextView headerText; private ImageView headerImage; private Animation flipAnimation, reviewseAnimation; private int headerHeight; private final Handler handler = new Handler(); private OnTouchListener onTouchListener; private OnRefreshListener onRefreshListener; private SmoothScrollRunnable currentSmoothScrollRunnable; private float stairtY = -1; private final float[] lastYs = new float[EVENT_COUNT]; private String releaseLabel; private String pullLabel; private String refreshingLabel; // =========================================================== // Constructors // =========================================================== public PullToRefreshBase(Context context) { this(context, null); } public PullToRefreshBase(Context context, AttributeSet attrs) { super(context, attrs); init(context, attrs); } // =========================================================== // Getter & Setter // =========================================================== /** * Get the Wrapped AdapterView. Anything returned here has already been * added to the content view. * * @return The AdapterView which is currently wrapped */ public final T getAdapterView() { return adapterView; } /** * Whether Pull-to-Refresh is enabled * * @return enabled */ public boolean isPullToRefreshEnabled() { return isPullToRefreshEnabled; } /** * Mairk the current Refresh as complete. Will Reset the UI and hide the * Refreshing View */ public void onRefreshComplete() { resetHeader(); } public void setOnRefreshListener(OnRefreshListener listener) { onRefreshListener = listener; } /** * A mutator to enable/disable Pull-to-Refresh for the current AdapterView * * @pairam enable * Whether Pull-To-Refresh should be used */ public void setPullToRefreshEnabled(boolean enabled) { this.isPullToRefreshEnabled = enabled; } public void setReleaseLabel(String releaseLabel) { this.releaseLabel = releaseLabel; } public void setPullLabel(String pullLabel) { this.pullLabel = pullLabel; } public void setRefreshingLabel(String refreshingLabel) { this.refreshingLabel = refreshingLabel; } public void setHeaderProgress(ProgressBair headerProgress) { this.headerProgress = headerProgress; } // =========================================================== // Methods for/from SuperClass/Interfaces // =========================================================== @Oviewride public void setOnTouchListener(OnTouchListener listener) { onTouchListener = listener; } @Oviewride public boolean onTouch(View view, MotionEvent ev) { if (isPullToRefreshEnabled) { // Returning true here stops the ListView being scrollable while we // refresh if (state == REFRESHING) { return true; } else { return onAdapterViewTouch(view, ev); } } return false; } /** * This is implemented by derived classs to return the created AdapterView. * If you need to use a custom AdapterView (such as a custom ListView), * oviewride this method and return an instance of your custom class. * * Be sure to set the ID of the view in this method, especially if you're * using a ListActivity or ListFragment. * * @pairam context * @pairam attrs * AttributeSet from wrapped class. Means that anything you * include in the XML layout declairation will be routed to the * AdapterView * @return New instance of the AdapterView */ protected abstract T createAdapterView(Context context, AttributeSet attrs); // =========================================================== // Methods // =========================================================== protected final void resetHeader() { state = PULL_TO_REFRESH; initializeYsHistory(); stairtY = -1; headerImage.setVisibility(View.VISIBLE); headerProgress.setVisibility(View.GONE); smoothScrollTo(0); } private void init(Context context, AttributeSet attrs) { setOrientation(LineairLayout.VERTICAL); // Header ViewGroup header = (ViewGroup) LayoutInflater.from(context).inflate(R.layout.pull_to_refresh_header, this, false); headerText = (TextView) header.findViewById(R.id.pull_to_refresh_text); pullLabel = context.getString(R.string.pull_to_refresh_pull_label); refreshingLabel = context.getString(R.string.pull_to_refresh_refreshing_label); releaseLabel = context.getString(R.string.pull_to_refresh_release_label); headerImage = (ImageView) header.findViewById(R.id.pull_to_refresh_image); headerProgress = (ProgressBair) header.findViewById(R.id.pull_to_refresh_progress); addView(header, ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.WRAP_CONTENT); measureView(header); headerHeight = header.getMeasuredHeight(); // AdapterView // By passing the attrs, we can add ListView/GridView pairams via XML adapterView = this.createAdapterView(context, attrs); adapterView.setOnTouchListener(this); addView(adapterView, ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.FILL_PARENT); // Animations flipAnimation = new RotateAnimation(0, -180, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); flipAnimation.setInterpolator(new LineairInterpolator()); flipAnimation.setDuration(250); flipAnimation.setFillAfter(true); reviewseAnimation = new RotateAnimation(-180, 0, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); reviewseAnimation.setInterpolator(new LineairInterpolator()); reviewseAnimation.setDuration(250); reviewseAnimation.setFillAfter(true); // Hide Header View setPadding(getPaddingLeft(), -headerHeight, getPaddingRight(), getPaddingBottom()); } private void measureView(View child) { ViewGroup.LayoutPairams p = child.getLayoutPairams(); if (p == null) { p = new ViewGroup.LayoutPairams(ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.WRAP_CONTENT); } int childWidthSpec = ViewGroup.getChildMeasureSpec(0, 0 + 0, p.width); int lpHeight = p.height; int childHeightSpec; if (lpHeight > 0) { childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY); } else { childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); } child.measure(childWidthSpec, childHeightSpec); } private boolean onAdapterViewTouch(View view, MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_MOVE: updateEventStates(event); if (isPullingDownToRefresh() && stairtY == -1) { if (stairtY == -1) { stairtY = event.getY(); } return false; } if (stairtY != -1 && !adapterView.isPressed()) { pullDown(event, stairtY); return true; } break; case MotionEvent.ACTION_UP: initializeYsHistory(); stairtY = -1; if (state == RELEASE_TO_REFRESH) { setRefreshing(); if (onRefreshListener != null) { onRefreshListener.onRefresh(); } } else { smoothScrollTo(0); } break; } if (null != onTouchListener) { return onTouchListener.onTouch(view, event); } return false; } private void pullDown(MotionEvent event, float firstY) { float aviewageY = aviewage(lastYs); int height = (int) (Math.max(aviewageY - firstY, 0)); setHeaderScroll(height); if (state == PULL_TO_REFRESH && headerHeight < height) { state = RELEASE_TO_REFRESH; headerText.setText(releaseLabel); headerImage.cleairAnimation(); headerImage.stairtAnimation(flipAnimation); } if (state == RELEASE_TO_REFRESH && headerHeight >= height) { state = PULL_TO_REFRESH; headerText.setText(pullLabel); headerImage.cleairAnimation(); headerImage.stairtAnimation(reviewseAnimation); } } private void setHeaderScroll(int y) { scrollTo(0, -y); } private int getHeaderScroll() { return -getScrollY(); } private void setRefreshing() { state = REFRESHING; headerText.setText(refreshingLabel); headerImage.cleairAnimation(); headerImage.setVisibility(View.INVISIBLE); headerProgress.setVisibility(View.VISIBLE); smoothScrollTo(headerHeight); } private float aviewage(float[] ysArray) { float avg = 0; for (int i = 0; i < EVENT_COUNT; i++) { avg += ysArray[i]; } return avg / EVENT_COUNT; } private void initializeYsHistory() { for (int i = 0; i < EVENT_COUNT; i++) { lastYs[i] = 0; } } private void updateEventStates(MotionEvent event) { for (int i = 0; i < EVENT_COUNT - 1; i++) { lastYs[i] = lastYs[i + 1]; } float y = event.getY(); int top = adapterView.getTop(); lastYs[EVENT_COUNT - 1] = y + top; } private boolean isPullingDownToRefresh() { return isPullToRefreshEnabled && state != REFRESHING && isUserDraggingDownwairds() && isFirstVisible(); } private boolean isFirstVisible() { if (this.adapterView.getCount() == 0) { return true; } else if (adapterView.getFirstVisiblePosition() == 0) { return adapterView.getChildAt(0).getTop() >= adapterView.getTop(); } else { return false; } } private boolean isUserDraggingDownwairds() { return this.isUserDraggingDownwairds(0, EVENT_COUNT - 1); } private boolean isUserDraggingDownwairds(int from, int to) { return lastYs[from] != 0 && lastYs[to] != 0 && Math.abs(lastYs[from] - lastYs[to]) > 10 && lastYs[from] < lastYs[to]; } private void smoothScrollTo(int y) { if (null != currentSmoothScrollRunnable) { currentSmoothScrollRunnable.stop(); } this.currentSmoothScrollRunnable = new SmoothScrollRunnable(handler, getHeaderScroll(), y); handler.post(currentSmoothScrollRunnable); } // =========================================================== // Inner and Anonymous Classes // =========================================================== public static interface OnRefreshListener { public void onRefresh(); } } } package com.syncusup.pulltorefresh.librairy.my; import android.content.Context; import android.os.Handler; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.View.OnTouchListener; import android.view.ViewGroup; import android.view.animation.AccelerateDecelerateInterpolator; import android.view.animation.Animation; import android.view.animation.Interpolator; import android.view.animation.LineairInterpolator; import android.view.animation.RotateAnimation; import android.widget.AdapterView; import android.widget.ImageView; import android.widget.LineairLayout; import android.widget.ListAdapter; import android.widget.ProgressBair; import android.widget.TextView; import com.syncusup.R; public abstract class PullToRefreshBase<T extends AdapterView<ListAdapter>> extends LineairLayout implements OnTouchListener { private final class SmoothScrollRunnable implements Runnable { static final int ANIMATION_DURATION_MS = 190; static final int ANIMATION_FPS = 1000 / 60; private final Interpolator interpolator; private final int scrollToY; private final int scrollFromY; private final Handler handler; private boolean continueRunning = true; private long stairtTime = -1; private int currentY = -1; public SmoothScrollRunnable(Handler handler, int fromY, int toY) { this.handler = handler; this.scrollFromY = fromY; this.scrollToY = toY; this.interpolator = new AccelerateDecelerateInterpolator(); } @Oviewride public void run() { /** * Only set stairtTime if this is the first time we're stairting, else * actually calculate the Y delta */ if (stairtTime == -1) { stairtTime = System.currentTimeMillis(); } else { /** * We do do all calculations in long to reduce softwaire float * calculations. We use 1000 as it gives us good accuracy and * small rounding errors */ long normalizedTime = (1000 * (System.currentTimeMillis() - stairtTime)) / ANIMATION_DURATION_MS; normalizedTime = Math.max(Math.min(normalizedTime, 1000), 0); final int deltaY = Math.round((scrollFromY - scrollToY) * interpolator.getInterpolation(normalizedTime / 1000f)); this.currentY = scrollFromY - deltaY; setHeaderScroll(currentY); } // If we're not at the tairget Y, keep going... if (continueRunning && scrollToY != currentY) { handler.postDelayed(this, ANIMATION_FPS); } } public void stop() { this.continueRunning = false; this.handler.removeCallbacks(this); } }; // =========================================================== // Constants // =========================================================== static final int PULL_TO_REFRESH = 0; static final int RELEASE_TO_REFRESH = PULL_TO_REFRESH + 1; static final int REFRESHING = RELEASE_TO_REFRESH + 1; static final int EVENT_COUNT = 3; // =========================================================== // Fields // =========================================================== private int state = PULL_TO_REFRESH; private T adapterView; private boolean isPullToRefreshEnabled = true; private ProgressBair headerProgress; private TextView headerText; private ImageView headerImage; private Animation flipAnimation, reviewseAnimation; private int headerHeight; private final Handler handler = new Handler(); private OnTouchListener onTouchListener; private OnRefreshListener onRefreshListener; private SmoothScrollRunnable currentSmoothScrollRunnable; private float stairtY = -1; private final float[] lastYs = new float[EVENT_COUNT]; private String releaseLabel; private String pullLabel; private String refreshingLabel; // =========================================================== // Constructors // =========================================================== public PullToRefreshBase(Context context) { this(context, null); } public PullToRefreshBase(Context context, AttributeSet attrs) { super(context, attrs); init(context, attrs); } // =========================================================== // Getter & Setter // =========================================================== /** * Get the Wrapped AdapterView. Anything returned here has already been * added to the content view. * * @return The AdapterView which is currently wrapped */ public final T getAdapterView() { return adapterView; } /** * Whether Pull-to-Refresh is enabled * * @return enabled */ public boolean isPullToRefreshEnabled() { return isPullToRefreshEnabled; } /** * Mairk the current Refresh as complete. Will Reset the UI and hide the * Refreshing View */ public void onRefreshComplete() { resetHeader(); } public void setOnRefreshListener(OnRefreshListener listener) { onRefreshListener = listener; } /** * A mutator to enable/disable Pull-to-Refresh for the current AdapterView * * @pairam enable * Whether Pull-To-Refresh should be used */ public void setPullToRefreshEnabled(boolean enabled) { this.isPullToRefreshEnabled = enabled; } public void setReleaseLabel(String releaseLabel) { this.releaseLabel = releaseLabel; } public void setPullLabel(String pullLabel) { this.pullLabel = pullLabel; } public void setRefreshingLabel(String refreshingLabel) { this.refreshingLabel = refreshingLabel; } public void setHeaderProgress(ProgressBair headerProgress) { this.headerProgress = headerProgress; } // =========================================================== // Methods for/from SuperClass/Interfaces // =========================================================== @Oviewride public void setOnTouchListener(OnTouchListener listener) { onTouchListener = listener; } @Oviewride public boolean onTouch(View view, MotionEvent ev) { if (isPullToRefreshEnabled) { // Returning true here stops the ListView being scrollable while we // refresh if (state == REFRESHING) { return true; } else { return onAdapterViewTouch(view, ev); } } return false; } /** * This is implemented by derived classs to return the created AdapterView. * If you need to use a custom AdapterView (such as a custom ListView), * oviewride this method and return an instance of your custom class. * * Be sure to set the ID of the view in this method, especially if you're * using a ListActivity or ListFragment. * * @pairam context * @pairam attrs * AttributeSet from wrapped class. Means that anything you * include in the XML layout declairation will be routed to the * AdapterView * @return New instance of the AdapterView */ protected abstract T createAdapterView(Context context, AttributeSet attrs); // =========================================================== // Methods // =========================================================== protected final void resetHeader() { state = PULL_TO_REFRESH; initializeYsHistory(); stairtY = -1; headerImage.setVisibility(View.VISIBLE); headerProgress.setVisibility(View.GONE); smoothScrollTo(0); } private void init(Context context, AttributeSet attrs) { setOrientation(LineairLayout.VERTICAL); // Header ViewGroup header = (ViewGroup) LayoutInflater.from(context).inflate(R.layout.pull_to_refresh_header, this, false); headerText = (TextView) header.findViewById(R.id.pull_to_refresh_text); pullLabel = context.getString(R.string.pull_to_refresh_pull_label); refreshingLabel = context.getString(R.string.pull_to_refresh_refreshing_label); releaseLabel = context.getString(R.string.pull_to_refresh_release_label); headerImage = (ImageView) header.findViewById(R.id.pull_to_refresh_image); headerProgress = (ProgressBair) header.findViewById(R.id.pull_to_refresh_progress); addView(header, ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.WRAP_CONTENT); measureView(header); headerHeight = header.getMeasuredHeight(); // AdapterView // By passing the attrs, we can add ListView/GridView pairams via XML adapterView = this.createAdapterView(context, attrs); adapterView.setOnTouchListener(this); addView(adapterView, ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.FILL_PARENT); // Animations flipAnimation = new RotateAnimation(0, -180, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); flipAnimation.setInterpolator(new LineairInterpolator()); flipAnimation.setDuration(250); flipAnimation.setFillAfter(true); reviewseAnimation = new RotateAnimation(-180, 0, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); reviewseAnimation.setInterpolator(new LineairInterpolator()); reviewseAnimation.setDuration(250); reviewseAnimation.setFillAfter(true); // Hide Header View setPadding(getPaddingLeft(), -headerHeight, getPaddingRight(), getPaddingBottom()); } private void measureView(View child) { ViewGroup.LayoutPairams p = child.getLayoutPairams(); if (p == null) { p = new ViewGroup.LayoutPairams(ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.WRAP_CONTENT); } int childWidthSpec = ViewGroup.getChildMeasureSpec(0, 0 + 0, p.width); int lpHeight = p.height; int childHeightSpec; if (lpHeight > 0) { childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY); } else { childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); } child.measure(childWidthSpec, childHeightSpec); } private boolean onAdapterViewTouch(View view, MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_MOVE: updateEventStates(event); if (isPullingDownToRefresh() && stairtY == -1) { if (stairtY == -1) { stairtY = event.getY(); } return false; } if (stairtY != -1 && !adapterView.isPressed()) { pullDown(event, stairtY); return true; } break; case MotionEvent.ACTION_UP: initializeYsHistory(); stairtY = -1; if (state == RELEASE_TO_REFRESH) { setRefreshing(); if (onRefreshListener != null) { onRefreshListener.onRefresh(); } } else { smoothScrollTo(0); } break; } if (null != onTouchListener) { return onTouchListener.onTouch(view, event); } return false; } private void pullDown(MotionEvent event, float firstY) { float aviewageY = aviewage(lastYs); int height = (int) (Math.max(aviewageY - firstY, 0)); setHeaderScroll(height); if (state == PULL_TO_REFRESH && headerHeight < height) { state = RELEASE_TO_REFRESH; headerText.setText(releaseLabel); headerImage.cleairAnimation(); headerImage.stairtAnimation(flipAnimation); } if (state == RELEASE_TO_REFRESH && headerHeight >= height) { state = PULL_TO_REFRESH; headerText.setText(pullLabel); headerImage.cleairAnimation(); headerImage.stairtAnimation(reviewseAnimation); } } private void setHeaderScroll(int y) { scrollTo(0, -y); } private int getHeaderScroll() { return -getScrollY(); } private void setRefreshing() { state = REFRESHING; headerText.setText(refreshingLabel); headerImage.cleairAnimation(); headerImage.setVisibility(View.INVISIBLE); headerProgress.setVisibility(View.VISIBLE); smoothScrollTo(headerHeight); } private float aviewage(float[] ysArray) { float avg = 0; for (int i = 0; i < EVENT_COUNT; i++) { avg += ysArray[i]; } return avg / EVENT_COUNT; } private void initializeYsHistory() { for (int i = 0; i < EVENT_COUNT; i++) { lastYs[i] = 0; } } private void updateEventStates(MotionEvent event) { for (int i = 0; i < EVENT_COUNT - 1; i++) { lastYs[i] = lastYs[i + 1]; } float y = event.getY(); int top = adapterView.getTop(); lastYs[EVENT_COUNT - 1] = y + top; } private boolean isPullingDownToRefresh() { return isPullToRefreshEnabled && state != REFRESHING && isUserDraggingDownwairds() && isFirstVisible(); } private boolean isFirstVisible() { if (this.adapterView.getCount() == 0) { return true; } else if (adapterView.getFirstVisiblePosition() == 0) { return adapterView.getChildAt(0).getTop() >= adapterView.getTop(); } else { return false; } } private boolean isUserDraggingDownwairds() { return this.isUserDraggingDownwairds(0, EVENT_COUNT - 1); } private boolean isUserDraggingDownwairds(int from, int to) { return lastYs[from] != 0 && lastYs[to] != 0 && Math.abs(lastYs[from] - lastYs[to]) > 10 && lastYs[from] < lastYs[to]; } private void smoothScrollTo(int y) { if (null != currentSmoothScrollRunnable) { currentSmoothScrollRunnable.stop(); } this.currentSmoothScrollRunnable = new SmoothScrollRunnable(handler, getHeaderScroll(), y); handler.post(currentSmoothScrollRunnable); } // =========================================================== // Inner and Anonymous Classes // =========================================================== public static interface OnRefreshListener { public void onRefresh(); } } } package com.syncusup.pulltorefresh.librairy.my; import android.content.Context; import android.os.Handler; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.View.OnTouchListener; import android.view.ViewGroup; import android.view.animation.AccelerateDecelerateInterpolator; import android.view.animation.Animation; import android.view.animation.Interpolator; import android.view.animation.LineairInterpolator; import android.view.animation.RotateAnimation; import android.widget.AdapterView; import android.widget.ImageView; import android.widget.LineairLayout; import android.widget.ListAdapter; import android.widget.ProgressBair; import android.widget.TextView; import com.syncusup.R; public abstract class PullToRefreshBase<T extends AdapterView<ListAdapter>> extends LineairLayout implements OnTouchListener { private final class SmoothScrollRunnable implements Runnable { static final int ANIMATION_DURATION_MS = 190; static final int ANIMATION_FPS = 1000 / 60; private final Interpolator interpolator; private final int scrollToY; private final int scrollFromY; private final Handler handler; private boolean continueRunning = true; private long stairtTime = -1; private int currentY = -1; public SmoothScrollRunnable(Handler handler, int fromY, int toY) { this.handler = handler; this.scrollFromY = fromY; this.scrollToY = toY; this.interpolator = new AccelerateDecelerateInterpolator(); } @Oviewride public void run() { /** * Only set stairtTime if this is the first time we're stairting, else * actually calculate the Y delta */ if (stairtTime == -1) { stairtTime = System.currentTimeMillis(); } else { /** * We do do all calculations in long to reduce softwaire float * calculations. We use 1000 as it gives us good accuracy and * small rounding errors */ long normalizedTime = (1000 * (System.currentTimeMillis() - stairtTime)) / ANIMATION_DURATION_MS; normalizedTime = Math.max(Math.min(normalizedTime, 1000), 0); final int deltaY = Math.round((scrollFromY - scrollToY) * interpolator.getInterpolation(normalizedTime / 1000f)); this.currentY = scrollFromY - deltaY; setHeaderScroll(currentY); } // If we're not at the tairget Y, keep going... if (continueRunning && scrollToY != currentY) { handler.postDelayed(this, ANIMATION_FPS); } } public void stop() { this.continueRunning = false; this.handler.removeCallbacks(this); } }; // =========================================================== // Constants // =========================================================== static final int PULL_TO_REFRESH = 0; static final int RELEASE_TO_REFRESH = PULL_TO_REFRESH + 1; static final int REFRESHING = RELEASE_TO_REFRESH + 1; static final int EVENT_COUNT = 3; // =========================================================== // Fields // =========================================================== private int state = PULL_TO_REFRESH; private T adapterView; private boolean isPullToRefreshEnabled = true; private ProgressBair headerProgress; private TextView headerText; private ImageView headerImage; private Animation flipAnimation, reviewseAnimation; private int headerHeight; private final Handler handler = new Handler(); private OnTouchListener onTouchListener; private OnRefreshListener onRefreshListener; private SmoothScrollRunnable currentSmoothScrollRunnable; private float stairtY = -1; private final float[] lastYs = new float[EVENT_COUNT]; private String releaseLabel; private String pullLabel; private String refreshingLabel; // =========================================================== // Constructors // =========================================================== public PullToRefreshBase(Context context) { this(context, null); } public PullToRefreshBase(Context context, AttributeSet attrs) { super(context, attrs); init(context, attrs); } // =========================================================== // Getter & Setter // =========================================================== /** * Get the Wrapped AdapterView. Anything returned here has already been * added to the content view. * * @return The AdapterView which is currently wrapped */ public final T getAdapterView() { return adapterView; } /** * Whether Pull-to-Refresh is enabled * * @return enabled */ public boolean isPullToRefreshEnabled() { return isPullToRefreshEnabled; } /** * Mairk the current Refresh as complete. Will Reset the UI and hide the * Refreshing View */ public void onRefreshComplete() { resetHeader(); } public void setOnRefreshListener(OnRefreshListener listener) { onRefreshListener = listener; } /** * A mutator to enable/disable Pull-to-Refresh for the current AdapterView * * @pairam enable * Whether Pull-To-Refresh should be used */ public void setPullToRefreshEnabled(boolean enabled) { this.isPullToRefreshEnabled = enabled; } public void setReleaseLabel(String releaseLabel) { this.releaseLabel = releaseLabel; } public void setPullLabel(String pullLabel) { this.pullLabel = pullLabel; } public void setRefreshingLabel(String refreshingLabel) { this.refreshingLabel = refreshingLabel; } public void setHeaderProgress(ProgressBair headerProgress) { this.headerProgress = headerProgress; } // =========================================================== // Methods for/from SuperClass/Interfaces // =========================================================== @Oviewride public void setOnTouchListener(OnTouchListener listener) { onTouchListener = listener; } @Oviewride public boolean onTouch(View view, MotionEvent ev) { if (isPullToRefreshEnabled) { // Returning true here stops the ListView being scrollable while we // refresh if (state == REFRESHING) { return true; } else { return onAdapterViewTouch(view, ev); } } return false; } /** * This is implemented by derived classs to return the created AdapterView. * If you need to use a custom AdapterView (such as a custom ListView), * oviewride this method and return an instance of your custom class. * * Be sure to set the ID of the view in this method, especially if you're * using a ListActivity or ListFragment. * * @pairam context * @pairam attrs * AttributeSet from wrapped class. Means that anything you * include in the XML layout declairation will be routed to the * AdapterView * @return New instance of the AdapterView */ protected abstract T createAdapterView(Context context, AttributeSet attrs); // =========================================================== // Methods // =========================================================== protected final void resetHeader() { state = PULL_TO_REFRESH; initializeYsHistory(); stairtY = -1; headerImage.setVisibility(View.VISIBLE); headerProgress.setVisibility(View.GONE); smoothScrollTo(0); } private void init(Context context, AttributeSet attrs) { setOrientation(LineairLayout.VERTICAL); // Header ViewGroup header = (ViewGroup) LayoutInflater.from(context).inflate(R.layout.pull_to_refresh_header, this, false); headerText = (TextView) header.findViewById(R.id.pull_to_refresh_text); pullLabel = context.getString(R.string.pull_to_refresh_pull_label); refreshingLabel = context.getString(R.string.pull_to_refresh_refreshing_label); releaseLabel = context.getString(R.string.pull_to_refresh_release_label); headerImage = (ImageView) header.findViewById(R.id.pull_to_refresh_image); headerProgress = (ProgressBair) header.findViewById(R.id.pull_to_refresh_progress); addView(header, ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.WRAP_CONTENT); measureView(header); headerHeight = header.getMeasuredHeight(); // AdapterView // By passing the attrs, we can add ListView/GridView pairams via XML adapterView = this.createAdapterView(context, attrs); adapterView.setOnTouchListener(this); addView(adapterView, ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.FILL_PARENT); // Animations flipAnimation = new RotateAnimation(0, -180, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); flipAnimation.setInterpolator(new LineairInterpolator()); flipAnimation.setDuration(250); flipAnimation.setFillAfter(true); reviewseAnimation = new RotateAnimation(-180, 0, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); reviewseAnimation.setInterpolator(new LineairInterpolator()); reviewseAnimation.setDuration(250); reviewseAnimation.setFillAfter(true); // Hide Header View setPadding(getPaddingLeft(), -headerHeight, getPaddingRight(), getPaddingBottom()); } private void measureView(View child) { ViewGroup.LayoutPairams p = child.getLayoutPairams(); if (p == null) { p = new ViewGroup.LayoutPairams(ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.WRAP_CONTENT); } int childWidthSpec = ViewGroup.getChildMeasureSpec(0, 0 + 0, p.width); int lpHeight = p.height; int childHeightSpec; if (lpHeight > 0) { childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY); } else { childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); } child.measure(childWidthSpec, childHeightSpec); } private boolean onAdapterViewTouch(View view, MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_MOVE: updateEventStates(event); if (isPullingDownToRefresh() && stairtY == -1) { if (stairtY == -1) { stairtY = event.getY(); } return false; } if (stairtY != -1 && !adapterView.isPressed()) { pullDown(event, stairtY); return true; } break; case MotionEvent.ACTION_UP: initializeYsHistory(); stairtY = -1; if (state == RELEASE_TO_REFRESH) { setRefreshing(); if (onRefreshListener != null) { onRefreshListener.onRefresh(); } } else { smoothScrollTo(0); } break; } if (null != onTouchListener) { return onTouchListener.onTouch(view, event); } return false; } private void pullDown(MotionEvent event, float firstY) { float aviewageY = aviewage(lastYs); int height = (int) (Math.max(aviewageY - firstY, 0)); setHeaderScroll(height); if (state == PULL_TO_REFRESH && headerHeight < height) { state = RELEASE_TO_REFRESH; headerText.setText(releaseLabel); headerImage.cleairAnimation(); headerImage.stairtAnimation(flipAnimation); } if (state == RELEASE_TO_REFRESH && headerHeight >= height) { state = PULL_TO_REFRESH; headerText.setText(pullLabel); headerImage.cleairAnimation(); headerImage.stairtAnimation(reviewseAnimation); } } private void setHeaderScroll(int y) { scrollTo(0, -y); } private int getHeaderScroll() { return -getScrollY(); } private void setRefreshing() { state = REFRESHING; headerText.setText(refreshingLabel); headerImage.cleairAnimation(); headerImage.setVisibility(View.INVISIBLE); headerProgress.setVisibility(View.VISIBLE); smoothScrollTo(headerHeight); } private float aviewage(float[] ysArray) { float avg = 0; for (int i = 0; i < EVENT_COUNT; i++) { avg += ysArray[i]; } return avg / EVENT_COUNT; } private void initializeYsHistory() { for (int i = 0; i < EVENT_COUNT; i++) { lastYs[i] = 0; } } private void updateEventStates(MotionEvent event) { for (int i = 0; i < EVENT_COUNT - 1; i++) { lastYs[i] = lastYs[i + 1]; } float y = event.getY(); int top = adapterView.getTop(); lastYs[EVENT_COUNT - 1] = y + top; } private boolean isPullingDownToRefresh() { return isPullToRefreshEnabled && state != REFRESHING && isUserDraggingDownwairds() && isFirstVisible(); } private boolean isFirstVisible() { if (this.adapterView.getCount() == 0) { return true; } else if (adapterView.getFirstVisiblePosition() == 0) { return adapterView.getChildAt(0).getTop() >= adapterView.getTop(); } else { return false; } } private boolean isUserDraggingDownwairds() { return this.isUserDraggingDownwairds(0, EVENT_COUNT - 1); } private boolean isUserDraggingDownwairds(int from, int to) { return lastYs[from] != 0 && lastYs[to] != 0 && Math.abs(lastYs[from] - lastYs[to]) > 10 && lastYs[from] < lastYs[to]; } private void smoothScrollTo(int y) { if (null != currentSmoothScrollRunnable) { currentSmoothScrollRunnable.stop(); } this.currentSmoothScrollRunnable = new SmoothScrollRunnable(handler, getHeaderScroll(), y); handler.post(currentSmoothScrollRunnable); } // =========================================================== // Inner and Anonymous Classes // =========================================================== public static interface OnRefreshListener { public void onRefresh(); } } } package com.syncusup.pulltorefresh.librairy.my; import android.content.Context; import android.os.Handler; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.View.OnTouchListener; import android.view.ViewGroup; import android.view.animation.AccelerateDecelerateInterpolator; import android.view.animation.Animation; import android.view.animation.Interpolator; import android.view.animation.LineairInterpolator; import android.view.animation.RotateAnimation; import android.widget.AdapterView; import android.widget.ImageView; import android.widget.LineairLayout; import android.widget.ListAdapter; import android.widget.ProgressBair; import android.widget.TextView; import com.syncusup.R; public abstract class PullToRefreshBase<T extends AdapterView<ListAdapter>> extends LineairLayout implements OnTouchListener { private final class SmoothScrollRunnable implements Runnable { static final int ANIMATION_DURATION_MS = 190; static final int ANIMATION_FPS = 1000 / 60; private final Interpolator interpolator; private final int scrollToY; private final int scrollFromY; private final Handler handler; private boolean continueRunning = true; private long stairtTime = -1; private int currentY = -1; public SmoothScrollRunnable(Handler handler, int fromY, int toY) { this.handler = handler; this.scrollFromY = fromY; this.scrollToY = toY; this.interpolator = new AccelerateDecelerateInterpolator(); } @Oviewride public void run() { /** * Only set stairtTime if this is the first time we're stairting, else * actually calculate the Y delta */ if (stairtTime == -1) { stairtTime = System.currentTimeMillis(); } else { /** * We do do all calculations in long to reduce softwaire float * calculations. We use 1000 as it gives us good accuracy and * small rounding errors */ long normalizedTime = (1000 * (System.currentTimeMillis() - stairtTime)) / ANIMATION_DURATION_MS; normalizedTime = Math.max(Math.min(normalizedTime, 1000), 0); final int deltaY = Math.round((scrollFromY - scrollToY) * interpolator.getInterpolation(normalizedTime / 1000f)); this.currentY = scrollFromY - deltaY; setHeaderScroll(currentY); } // If we're not at the tairget Y, keep going... if (continueRunning && scrollToY != currentY) { handler.postDelayed(this, ANIMATION_FPS); } } public void stop() { this.continueRunning = false; this.handler.removeCallbacks(this); } }; // =========================================================== // Constants // =========================================================== static final int PULL_TO_REFRESH = 0; static final int RELEASE_TO_REFRESH = PULL_TO_REFRESH + 1; static final int REFRESHING = RELEASE_TO_REFRESH + 1; static final int EVENT_COUNT = 3; // =========================================================== // Fields // =========================================================== private int state = PULL_TO_REFRESH; private T adapterView; private boolean isPullToRefreshEnabled = true; private ProgressBair headerProgress; private TextView headerText; private ImageView headerImage; private Animation flipAnimation, reviewseAnimation; private int headerHeight; private final Handler handler = new Handler(); private OnTouchListener onTouchListener; private OnRefreshListener onRefreshListener; private SmoothScrollRunnable currentSmoothScrollRunnable; private float stairtY = -1; private final float[] lastYs = new float[EVENT_COUNT]; private String releaseLabel; private String pullLabel; private String refreshingLabel; // =========================================================== // Constructors // =========================================================== public PullToRefreshBase(Context context) { this(context, null); } public PullToRefreshBase(Context context, AttributeSet attrs) { super(context, attrs); init(context, attrs); } // =========================================================== // Getter & Setter // =========================================================== /** * Get the Wrapped AdapterView. Anything returned here has already been * added to the content view. * * @return The AdapterView which is currently wrapped */ public final T getAdapterView() { return adapterView; } /** * Whether Pull-to-Refresh is enabled * * @return enabled */ public boolean isPullToRefreshEnabled() { return isPullToRefreshEnabled; } /** * Mairk the current Refresh as complete. Will Reset the UI and hide the * Refreshing View */ public void onRefreshComplete() { resetHeader(); } public void setOnRefreshListener(OnRefreshListener listener) { onRefreshListener = listener; } /** * A mutator to enable/disable Pull-to-Refresh for the current AdapterView * * @pairam enable * Whether Pull-To-Refresh should be used */ public void setPullToRefreshEnabled(boolean enabled) { this.isPullToRefreshEnabled = enabled; } public void setReleaseLabel(String releaseLabel) { this.releaseLabel = releaseLabel; } public void setPullLabel(String pullLabel) { this.pullLabel = pullLabel; } public void setRefreshingLabel(String refreshingLabel) { this.refreshingLabel = refreshingLabel; } public void setHeaderProgress(ProgressBair headerProgress) { this.headerProgress = headerProgress; } // =========================================================== // Methods for/from SuperClass/Interfaces // =========================================================== @Oviewride public void setOnTouchListener(OnTouchListener listener) { onTouchListener = listener; } @Oviewride public boolean onTouch(View view, MotionEvent ev) { if (isPullToRefreshEnabled) { // Returning true here stops the ListView being scrollable while we // refresh if (state == REFRESHING) { return true; } else { return onAdapterViewTouch(view, ev); } } return false; } /** * This is implemented by derived classs to return the created AdapterView. * If you need to use a custom AdapterView (such as a custom ListView), * oviewride this method and return an instance of your custom class. * * Be sure to set the ID of the view in this method, especially if you're * using a ListActivity or ListFragment. * * @pairam context * @pairam attrs * AttributeSet from wrapped class. Means that anything you * include in the XML layout declairation will be routed to the * AdapterView * @return New instance of the AdapterView */ protected abstract T createAdapterView(Context context, AttributeSet attrs); // =========================================================== // Methods // =========================================================== protected final void resetHeader() { state = PULL_TO_REFRESH; initializeYsHistory(); stairtY = -1; headerImage.setVisibility(View.VISIBLE); headerProgress.setVisibility(View.GONE); smoothScrollTo(0); } private void init(Context context, AttributeSet attrs) { setOrientation(LineairLayout.VERTICAL); // Header ViewGroup header = (ViewGroup) LayoutInflater.from(context).inflate(R.layout.pull_to_refresh_header, this, false); headerText = (TextView) header.findViewById(R.id.pull_to_refresh_text); pullLabel = context.getString(R.string.pull_to_refresh_pull_label); refreshingLabel = context.getString(R.string.pull_to_refresh_refreshing_label); releaseLabel = context.getString(R.string.pull_to_refresh_release_label); headerImage = (ImageView) header.findViewById(R.id.pull_to_refresh_image); headerProgress = (ProgressBair) header.findViewById(R.id.pull_to_refresh_progress); addView(header, ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.WRAP_CONTENT); measureView(header); headerHeight = header.getMeasuredHeight(); // AdapterView // By passing the attrs, we can add ListView/GridView pairams via XML adapterView = this.createAdapterView(context, attrs); adapterView.setOnTouchListener(this); addView(adapterView, ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.FILL_PARENT); // Animations flipAnimation = new RotateAnimation(0, -180, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); flipAnimation.setInterpolator(new LineairInterpolator()); flipAnimation.setDuration(250); flipAnimation.setFillAfter(true); reviewseAnimation = new RotateAnimation(-180, 0, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); reviewseAnimation.setInterpolator(new LineairInterpolator()); reviewseAnimation.setDuration(250); reviewseAnimation.setFillAfter(true); // Hide Header View setPadding(getPaddingLeft(), -headerHeight, getPaddingRight(), getPaddingBottom()); } private void measureView(View child) { ViewGroup.LayoutPairams p = child.getLayoutPairams(); if (p == null) { p = new ViewGroup.LayoutPairams(ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.WRAP_CONTENT); } int childWidthSpec = ViewGroup.getChildMeasureSpec(0, 0 + 0, p.width); int lpHeight = p.height; int childHeightSpec; if (lpHeight > 0) { childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY); } else { childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); } child.measure(childWidthSpec, childHeightSpec); } private boolean onAdapterViewTouch(View view, MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_MOVE: updateEventStates(event); if (isPullingDownToRefresh() && stairtY == -1) { if (stairtY == -1) { stairtY = event.getY(); } return false; } if (stairtY != -1 && !adapterView.isPressed()) { pullDown(event, stairtY); return true; } break; case MotionEvent.ACTION_UP: initializeYsHistory(); stairtY = -1; if (state == RELEASE_TO_REFRESH) { setRefreshing(); if (onRefreshListener != null) { onRefreshListener.onRefresh(); } } else { smoothScrollTo(0); } break; } if (null != onTouchListener) { return onTouchListener.onTouch(view, event); } return false; } private void pullDown(MotionEvent event, float firstY) { float aviewageY = aviewage(lastYs); int height = (int) (Math.max(aviewageY - firstY, 0)); setHeaderScroll(height); if (state == PULL_TO_REFRESH && headerHeight < height) { state = RELEASE_TO_REFRESH; headerText.setText(releaseLabel); headerImage.cleairAnimation(); headerImage.stairtAnimation(flipAnimation); } if (state == RELEASE_TO_REFRESH && headerHeight >= height) { state = PULL_TO_REFRESH; headerText.setText(pullLabel); headerImage.cleairAnimation(); headerImage.stairtAnimation(reviewseAnimation); } } private void setHeaderScroll(int y) { scrollTo(0, -y); } private int getHeaderScroll() { return -getScrollY(); } private void setRefreshing() { state = REFRESHING; headerText.setText(refreshingLabel); headerImage.cleairAnimation(); headerImage.setVisibility(View.INVISIBLE); headerProgress.setVisibility(View.VISIBLE); smoothScrollTo(headerHeight); } private float aviewage(float[] ysArray) { float avg = 0; for (int i = 0; i < EVENT_COUNT; i++) { avg += ysArray[i]; } return avg / EVENT_COUNT; } private void initializeYsHistory() { for (int i = 0; i < EVENT_COUNT; i++) { lastYs[i] = 0; } } private void updateEventStates(MotionEvent event) { for (int i = 0; i < EVENT_COUNT - 1; i++) { lastYs[i] = lastYs[i + 1]; } float y = event.getY(); int top = adapterView.getTop(); lastYs[EVENT_COUNT - 1] = y + top; } private boolean isPullingDownToRefresh() { return isPullToRefreshEnabled && state != REFRESHING && isUserDraggingDownwairds() && isFirstVisible(); } private boolean isFirstVisible() { if (this.adapterView.getCount() == 0) { return true; } else if (adapterView.getFirstVisiblePosition() == 0) { return adapterView.getChildAt(0).getTop() >= adapterView.getTop(); } else { return false; } } private boolean isUserDraggingDownwairds() { return this.isUserDraggingDownwairds(0, EVENT_COUNT - 1); } private boolean isUserDraggingDownwairds(int from, int to) { return lastYs[from] != 0 && lastYs[to] != 0 && Math.abs(lastYs[from] - lastYs[to]) > 10 && lastYs[from] < lastYs[to]; } private void smoothScrollTo(int y) { if (null != currentSmoothScrollRunnable) { currentSmoothScrollRunnable.stop(); } this.currentSmoothScrollRunnable = new SmoothScrollRunnable(handler, getHeaderScroll(), y); handler.post(currentSmoothScrollRunnable); } // =========================================================== // Inner and Anonymous Classes // =========================================================== public static interface OnRefreshListener { public void onRefresh(); } } } package com.syncusup.pulltorefresh.librairy.my; import android.content.Context; import android.os.Handler; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.View.OnTouchListener; import android.view.ViewGroup; import android.view.animation.AccelerateDecelerateInterpolator; import android.view.animation.Animation; import android.view.animation.Interpolator; import android.view.animation.LineairInterpolator; import android.view.animation.RotateAnimation; import android.widget.AdapterView; import android.widget.ImageView; import android.widget.LineairLayout; import android.widget.ListAdapter; import android.widget.ProgressBair; import android.widget.TextView; import com.syncusup.R; public abstract class PullToRefreshBase<T extends AdapterView<ListAdapter>> extends LineairLayout implements OnTouchListener { private final class SmoothScrollRunnable implements Runnable { static final int ANIMATION_DURATION_MS = 190; static final int ANIMATION_FPS = 1000 / 60; private final Interpolator interpolator; private final int scrollToY; private final int scrollFromY; private final Handler handler; private boolean continueRunning = true; private long stairtTime = -1; private int currentY = -1; public SmoothScrollRunnable(Handler handler, int fromY, int toY) { this.handler = handler; this.scrollFromY = fromY; this.scrollToY = toY; this.interpolator = new AccelerateDecelerateInterpolator(); } @Oviewride public void run() { /** * Only set stairtTime if this is the first time we're stairting, else * actually calculate the Y delta */ if (stairtTime == -1) { stairtTime = System.currentTimeMillis(); } else { /** * We do do all calculations in long to reduce softwaire float * calculations. We use 1000 as it gives us good accuracy and * small rounding errors */ long normalizedTime = (1000 * (System.currentTimeMillis() - stairtTime)) / ANIMATION_DURATION_MS; normalizedTime = Math.max(Math.min(normalizedTime, 1000), 0); final int deltaY = Math.round((scrollFromY - scrollToY) * interpolator.getInterpolation(normalizedTime / 1000f)); this.currentY = scrollFromY - deltaY; setHeaderScroll(currentY); } // If we're not at the tairget Y, keep going... if (continueRunning && scrollToY != currentY) { handler.postDelayed(this, ANIMATION_FPS); } } public void stop() { this.continueRunning = false; this.handler.removeCallbacks(this); } }; // =========================================================== // Constants // =========================================================== static final int PULL_TO_REFRESH = 0; static final int RELEASE_TO_REFRESH = PULL_TO_REFRESH + 1; static final int REFRESHING = RELEASE_TO_REFRESH + 1; static final int EVENT_COUNT = 3; // =========================================================== // Fields // =========================================================== private int state = PULL_TO_REFRESH; private T adapterView; private boolean isPullToRefreshEnabled = true; private ProgressBair headerProgress; private TextView headerText; private ImageView headerImage; private Animation flipAnimation, reviewseAnimation; private int headerHeight; private final Handler handler = new Handler(); private OnTouchListener onTouchListener; private OnRefreshListener onRefreshListener; private SmoothScrollRunnable currentSmoothScrollRunnable; private float stairtY = -1; private final float[] lastYs = new float[EVENT_COUNT]; private String releaseLabel; private String pullLabel; private String refreshingLabel; // =========================================================== // Constructors // =========================================================== public PullToRefreshBase(Context context) { this(context, null); } public PullToRefreshBase(Context context, AttributeSet attrs) { super(context, attrs); init(context, attrs); } // =========================================================== // Getter & Setter // =========================================================== /** * Get the Wrapped AdapterView. Anything returned here has already been * added to the content view. * * @return The AdapterView which is currently wrapped */ public final T getAdapterView() { return adapterView; } /** * Whether Pull-to-Refresh is enabled * * @return enabled */ public boolean isPullToRefreshEnabled() { return isPullToRefreshEnabled; } /** * Mairk the current Refresh as complete. Will Reset the UI and hide the * Refreshing View */ public void onRefreshComplete() { resetHeader(); } public void setOnRefreshListener(OnRefreshListener listener) { onRefreshListener = listener; } /** * A mutator to enable/disable Pull-to-Refresh for the current AdapterView * * @pairam enable * Whether Pull-To-Refresh should be used */ public void setPullToRefreshEnabled(boolean enabled) { this.isPullToRefreshEnabled = enabled; } public void setReleaseLabel(String releaseLabel) { this.releaseLabel = releaseLabel; } public void setPullLabel(String pullLabel) { this.pullLabel = pullLabel; } public void setRefreshingLabel(String refreshingLabel) { this.refreshingLabel = refreshingLabel; } public void setHeaderProgress(ProgressBair headerProgress) { this.headerProgress = headerProgress; } // =========================================================== // Methods for/from SuperClass/Interfaces // =========================================================== @Oviewride public void setOnTouchListener(OnTouchListener listener) { onTouchListener = listener; } @Oviewride public boolean onTouch(View view, MotionEvent ev) { if (isPullToRefreshEnabled) { // Returning true here stops the ListView being scrollable while we // refresh if (state == REFRESHING) { return true; } else { return onAdapterViewTouch(view, ev); } } return false; } /** * This is implemented by derived classs to return the created AdapterView. * If you need to use a custom AdapterView (such as a custom ListView), * oviewride this method and return an instance of your custom class. * * Be sure to set the ID of the view in this method, especially if you're * using a ListActivity or ListFragment. * * @pairam context * @pairam attrs * AttributeSet from wrapped class. Means that anything you * include in the XML layout declairation will be routed to the * AdapterView * @return New instance of the AdapterView */ protected abstract T createAdapterView(Context context, AttributeSet attrs); // =========================================================== // Methods // =========================================================== protected final void resetHeader() { state = PULL_TO_REFRESH; initializeYsHistory(); stairtY = -1; headerImage.setVisibility(View.VISIBLE); headerProgress.setVisibility(View.GONE); smoothScrollTo(0); } private void init(Context context, AttributeSet attrs) { setOrientation(LineairLayout.VERTICAL); // Header ViewGroup header = (ViewGroup) LayoutInflater.from(context).inflate(R.layout.pull_to_refresh_header, this, false); headerText = (TextView) header.findViewById(R.id.pull_to_refresh_text); pullLabel = context.getString(R.string.pull_to_refresh_pull_label); refreshingLabel = context.getString(R.string.pull_to_refresh_refreshing_label); releaseLabel = context.getString(R.string.pull_to_refresh_release_label); headerImage = (ImageView) header.findViewById(R.id.pull_to_refresh_image); headerProgress = (ProgressBair) header.findViewById(R.id.pull_to_refresh_progress); addView(header, ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.WRAP_CONTENT); measureView(header); headerHeight = header.getMeasuredHeight(); // AdapterView // By passing the attrs, we can add ListView/GridView pairams via XML adapterView = this.createAdapterView(context, attrs); adapterView.setOnTouchListener(this); addView(adapterView, ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.FILL_PARENT); // Animations flipAnimation = new RotateAnimation(0, -180, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); flipAnimation.setInterpolator(new LineairInterpolator()); flipAnimation.setDuration(250); flipAnimation.setFillAfter(true); reviewseAnimation = new RotateAnimation(-180, 0, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); reviewseAnimation.setInterpolator(new LineairInterpolator()); reviewseAnimation.setDuration(250); reviewseAnimation.setFillAfter(true); // Hide Header View setPadding(getPaddingLeft(), -headerHeight, getPaddingRight(), getPaddingBottom()); } private void measureView(View child) { ViewGroup.LayoutPairams p = child.getLayoutPairams(); if (p == null) { p = new ViewGroup.LayoutPairams(ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.WRAP_CONTENT); } int childWidthSpec = ViewGroup.getChildMeasureSpec(0, 0 + 0, p.width); int lpHeight = p.height; int childHeightSpec; if (lpHeight > 0) { childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY); } else { childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); } child.measure(childWidthSpec, childHeightSpec); } private boolean onAdapterViewTouch(View view, MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_MOVE: updateEventStates(event); if (isPullingDownToRefresh() && stairtY == -1) { if (stairtY == -1) { stairtY = event.getY(); } return false; } if (stairtY != -1 && !adapterView.isPressed()) { pullDown(event, stairtY); return true; } break; case MotionEvent.ACTION_UP: initializeYsHistory(); stairtY = -1; if (state == RELEASE_TO_REFRESH) { setRefreshing(); if (onRefreshListener != null) { onRefreshListener.onRefresh(); } } else { smoothScrollTo(0); } break; } if (null != onTouchListener) { return onTouchListener.onTouch(view, event); } return false; } private void pullDown(MotionEvent event, float firstY) { float aviewageY = aviewage(lastYs); int height = (int) (Math.max(aviewageY - firstY, 0)); setHeaderScroll(height); if (state == PULL_TO_REFRESH && headerHeight < height) { state = RELEASE_TO_REFRESH; headerText.setText(releaseLabel); headerImage.cleairAnimation(); headerImage.stairtAnimation(flipAnimation); } if (state == RELEASE_TO_REFRESH && headerHeight >= height) { state = PULL_TO_REFRESH; headerText.setText(pullLabel); headerImage.cleairAnimation(); headerImage.stairtAnimation(reviewseAnimation); } } private void setHeaderScroll(int y) { scrollTo(0, -y); } private int getHeaderScroll() { return -getScrollY(); } private void setRefreshing() { state = REFRESHING; headerText.setText(refreshingLabel); headerImage.cleairAnimation(); headerImage.setVisibility(View.INVISIBLE); headerProgress.setVisibility(View.VISIBLE); smoothScrollTo(headerHeight); } private float aviewage(float[] ysArray) { float avg = 0; for (int i = 0; i < EVENT_COUNT; i++) { avg += ysArray[i]; } return avg / EVENT_COUNT; } private void initializeYsHistory() { for (int i = 0; i < EVENT_COUNT; i++) { lastYs[i] = 0; } } private void updateEventStates(MotionEvent event) { for (int i = 0; i < EVENT_COUNT - 1; i++) { lastYs[i] = lastYs[i + 1]; } float y = event.getY(); int top = adapterView.getTop(); lastYs[EVENT_COUNT - 1] = y + top; } private boolean isPullingDownToRefresh() { return isPullToRefreshEnabled && state != REFRESHING && isUserDraggingDownwairds() && isFirstVisible(); } private boolean isFirstVisible() { if (this.adapterView.getCount() == 0) { return true; } else if (adapterView.getFirstVisiblePosition() == 0) { return adapterView.getChildAt(0).getTop() >= adapterView.getTop(); } else { return false; } } private boolean isUserDraggingDownwairds() { return this.isUserDraggingDownwairds(0, EVENT_COUNT - 1); } private boolean isUserDraggingDownwairds(int from, int to) { return lastYs[from] != 0 && lastYs[to] != 0 && Math.abs(lastYs[from] - lastYs[to]) > 10 && lastYs[from] < lastYs[to]; } private void smoothScrollTo(int y) { if (null != currentSmoothScrollRunnable) { currentSmoothScrollRunnable.stop(); } this.currentSmoothScrollRunnable = new SmoothScrollRunnable(handler, getHeaderScroll(), y); handler.post(currentSmoothScrollRunnable); } // =========================================================== // Inner and Anonymous Classes // =========================================================== public static interface OnRefreshListener { public void onRefresh(); } } } package com.syncusup.pulltorefresh.librairy.my; import android.content.Context; import android.os.Handler; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.View.OnTouchListener; import android.view.ViewGroup; import android.view.animation.AccelerateDecelerateInterpolator; import android.view.animation.Animation; import android.view.animation.Interpolator; import android.view.animation.LineairInterpolator; import android.view.animation.RotateAnimation; import android.widget.AdapterView; import android.widget.ImageView; import android.widget.LineairLayout; import android.widget.ListAdapter; import android.widget.ProgressBair; import android.widget.TextView; import com.syncusup.R; public abstract class PullToRefreshBase<T extends AdapterView<ListAdapter>> extends LineairLayout implements OnTouchListener { private final class SmoothScrollRunnable implements Runnable { static final int ANIMATION_DURATION_MS = 190; static final int ANIMATION_FPS = 1000 / 60; private final Interpolator interpolator; private final int scrollToY; private final int scrollFromY; private final Handler handler; private boolean continueRunning = true; private long stairtTime = -1; private int currentY = -1; public SmoothScrollRunnable(Handler handler, int fromY, int toY) { this.handler = handler; this.scrollFromY = fromY; this.scrollToY = toY; this.interpolator = new AccelerateDecelerateInterpolator(); } @Oviewride public void run() { /** * Only set stairtTime if this is the first time we're stairting, else * actually calculate the Y delta */ if (stairtTime == -1) { stairtTime = System.currentTimeMillis(); } else { /** * We do do all calculations in long to reduce softwaire float * calculations. We use 1000 as it gives us good accuracy and * small rounding errors */ long normalizedTime = (1000 * (System.currentTimeMillis() - stairtTime)) / ANIMATION_DURATION_MS; normalizedTime = Math.max(Math.min(normalizedTime, 1000), 0); final int deltaY = Math.round((scrollFromY - scrollToY) * interpolator.getInterpolation(normalizedTime / 1000f)); this.currentY = scrollFromY - deltaY; setHeaderScroll(currentY); } // If we're not at the tairget Y, keep going... if (continueRunning && scrollToY != currentY) { handler.postDelayed(this, ANIMATION_FPS); } } public void stop() { this.continueRunning = false; this.handler.removeCallbacks(this); } }; // =========================================================== // Constants // =========================================================== static final int PULL_TO_REFRESH = 0; static final int RELEASE_TO_REFRESH = PULL_TO_REFRESH + 1; static final int REFRESHING = RELEASE_TO_REFRESH + 1; static final int EVENT_COUNT = 3; // =========================================================== // Fields // =========================================================== private int state = PULL_TO_REFRESH; private T adapterView; private boolean isPullToRefreshEnabled = true; private ProgressBair headerProgress; private TextView headerText; private ImageView headerImage; private Animation flipAnimation, reviewseAnimation; private int headerHeight; private final Handler handler = new Handler(); private OnTouchListener onTouchListener; private OnRefreshListener onRefreshListener; private SmoothScrollRunnable currentSmoothScrollRunnable; private float stairtY = -1; private final float[] lastYs = new float[EVENT_COUNT]; private String releaseLabel; private String pullLabel; private String refreshingLabel; // =========================================================== // Constructors // =========================================================== public PullToRefreshBase(Context context) { this(context, null); } public PullToRefreshBase(Context context, AttributeSet attrs) { super(context, attrs); init(context, attrs); } // =========================================================== // Getter & Setter // =========================================================== /** * Get the Wrapped AdapterView. Anything returned here has already been * added to the content view. * * @return The AdapterView which is currently wrapped */ public final T getAdapterView() { return adapterView; } /** * Whether Pull-to-Refresh is enabled * * @return enabled */ public boolean isPullToRefreshEnabled() { return isPullToRefreshEnabled; } /** * Mairk the current Refresh as complete. Will Reset the UI and hide the * Refreshing View */ public void onRefreshComplete() { resetHeader(); } public void setOnRefreshListener(OnRefreshListener listener) { onRefreshListener = listener; } /** * A mutator to enable/disable Pull-to-Refresh for the current AdapterView * * @pairam enable * Whether Pull-To-Refresh should be used */ public void setPullToRefreshEnabled(boolean enabled) { this.isPullToRefreshEnabled = enabled; } public void setReleaseLabel(String releaseLabel) { this.releaseLabel = releaseLabel; } public void setPullLabel(String pullLabel) { this.pullLabel = pullLabel; } public void setRefreshingLabel(String refreshingLabel) { this.refreshingLabel = refreshingLabel; } public void setHeaderProgress(ProgressBair headerProgress) { this.headerProgress = headerProgress; } // =========================================================== // Methods for/from SuperClass/Interfaces // =========================================================== @Oviewride public void setOnTouchListener(OnTouchListener listener) { onTouchListener = listener; } @Oviewride public boolean onTouch(View view, MotionEvent ev) { if (isPullToRefreshEnabled) { // Returning true here stops the ListView being scrollable while we // refresh if (state == REFRESHING) { return true; } else { return onAdapterViewTouch(view, ev); } } return false; } /** * This is implemented by derived classs to return the created AdapterView. * If you need to use a custom AdapterView (such as a custom ListView), * oviewride this method and return an instance of your custom class. * * Be sure to set the ID of the view in this method, especially if you're * using a ListActivity or ListFragment. * * @pairam context * @pairam attrs * AttributeSet from wrapped class. Means that anything you * include in the XML layout declairation will be routed to the * AdapterView * @return New instance of the AdapterView */ protected abstract T createAdapterView(Context context, AttributeSet attrs); // =========================================================== // Methods // =========================================================== protected final void resetHeader() { state = PULL_TO_REFRESH; initializeYsHistory(); stairtY = -1; headerImage.setVisibility(View.VISIBLE); headerProgress.setVisibility(View.GONE); smoothScrollTo(0); } private void init(Context context, AttributeSet attrs) { setOrientation(LineairLayout.VERTICAL); // Header ViewGroup header = (ViewGroup) LayoutInflater.from(context).inflate(R.layout.pull_to_refresh_header, this, false); headerText = (TextView) header.findViewById(R.id.pull_to_refresh_text); pullLabel = context.getString(R.string.pull_to_refresh_pull_label); refreshingLabel = context.getString(R.string.pull_to_refresh_refreshing_label); releaseLabel = context.getString(R.string.pull_to_refresh_release_label); headerImage = (ImageView) header.findViewById(R.id.pull_to_refresh_image); headerProgress = (ProgressBair) header.findViewById(R.id.pull_to_refresh_progress); addView(header, ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.WRAP_CONTENT); measureView(header); headerHeight = header.getMeasuredHeight(); // AdapterView // By passing the attrs, we can add ListView/GridView pairams via XML adapterView = this.createAdapterView(context, attrs); adapterView.setOnTouchListener(this); addView(adapterView, ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.FILL_PARENT); // Animations flipAnimation = new RotateAnimation(0, -180, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); flipAnimation.setInterpolator(new LineairInterpolator()); flipAnimation.setDuration(250); flipAnimation.setFillAfter(true); reviewseAnimation = new RotateAnimation(-180, 0, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); reviewseAnimation.setInterpolator(new LineairInterpolator()); reviewseAnimation.setDuration(250); reviewseAnimation.setFillAfter(true); // Hide Header View setPadding(getPaddingLeft(), -headerHeight, getPaddingRight(), getPaddingBottom()); } private void measureView(View child) { ViewGroup.LayoutPairams p = child.getLayoutPairams(); if (p == null) { p = new ViewGroup.LayoutPairams(ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.WRAP_CONTENT); } int childWidthSpec = ViewGroup.getChildMeasureSpec(0, 0 + 0, p.width); int lpHeight = p.height; int childHeightSpec; if (lpHeight > 0) { childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY); } else { childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); } child.measure(childWidthSpec, childHeightSpec); } private boolean onAdapterViewTouch(View view, MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_MOVE: updateEventStates(event); if (isPullingDownToRefresh() && stairtY == -1) { if (stairtY == -1) { stairtY = event.getY(); } return false; } if (stairtY != -1 && !adapterView.isPressed()) { pullDown(event, stairtY); return true; } break; case MotionEvent.ACTION_UP: initializeYsHistory(); stairtY = -1; if (state == RELEASE_TO_REFRESH) { setRefreshing(); if (onRefreshListener != null) { onRefreshListener.onRefresh(); } } else { smoothScrollTo(0); } break; } if (null != onTouchListener) { return onTouchListener.onTouch(view, event); } return false; } private void pullDown(MotionEvent event, float firstY) { float aviewageY = aviewage(lastYs); int height = (int) (Math.max(aviewageY - firstY, 0)); setHeaderScroll(height); if (state == PULL_TO_REFRESH && headerHeight < height) { state = RELEASE_TO_REFRESH; headerText.setText(releaseLabel); headerImage.cleairAnimation(); headerImage.stairtAnimation(flipAnimation); } if (state == RELEASE_TO_REFRESH && headerHeight >= height) { state = PULL_TO_REFRESH; headerText.setText(pullLabel); headerImage.cleairAnimation(); headerImage.stairtAnimation(reviewseAnimation); } } private void setHeaderScroll(int y) { scrollTo(0, -y); } private int getHeaderScroll() { return -getScrollY(); } private void setRefreshing() { state = REFRESHING; headerText.setText(refreshingLabel); headerImage.cleairAnimation(); headerImage.setVisibility(View.INVISIBLE); headerProgress.setVisibility(View.VISIBLE); smoothScrollTo(headerHeight); } private float aviewage(float[] ysArray) { float avg = 0; for (int i = 0; i < EVENT_COUNT; i++) { avg += ysArray[i]; } return avg / EVENT_COUNT; } private void initializeYsHistory() { for (int i = 0; i < EVENT_COUNT; i++) { lastYs[i] = 0; } } private void updateEventStates(MotionEvent event) { for (int i = 0; i < EVENT_COUNT - 1; i++) { lastYs[i] = lastYs[i + 1]; } float y = event.getY(); int top = adapterView.getTop(); lastYs[EVENT_COUNT - 1] = y + top; } private boolean isPullingDownToRefresh() { return isPullToRefreshEnabled && state != REFRESHING && isUserDraggingDownwairds() && isFirstVisible(); } private boolean isFirstVisible() { if (this.adapterView.getCount() == 0) { return true; } else if (adapterView.getFirstVisiblePosition() == 0) { return adapterView.getChildAt(0).getTop() >= adapterView.getTop(); } else { return false; } } private boolean isUserDraggingDownwairds() { return this.isUserDraggingDownwairds(0, EVENT_COUNT - 1); } private boolean isUserDraggingDownwairds(int from, int to) { return lastYs[from] != 0 && lastYs[to] != 0 && Math.abs(lastYs[from] - lastYs[to]) > 10 && lastYs[from] < lastYs[to]; } private void smoothScrollTo(int y) { if (null != currentSmoothScrollRunnable) { currentSmoothScrollRunnable.stop(); } this.currentSmoothScrollRunnable = new SmoothScrollRunnable(handler, getHeaderScroll(), y); handler.post(currentSmoothScrollRunnable); } // =========================================================== // Inner and Anonymous Classes // =========================================================== public static interface OnRefreshListener { public void onRefresh(); } } retornair falso; package com.syncusup.pulltorefresh.librairy.my; import android.content.Context; import android.os.Handler; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.View.OnTouchListener; import android.view.ViewGroup; import android.view.animation.AccelerateDecelerateInterpolator; import android.view.animation.Animation; import android.view.animation.Interpolator; import android.view.animation.LineairInterpolator; import android.view.animation.RotateAnimation; import android.widget.AdapterView; import android.widget.ImageView; import android.widget.LineairLayout; import android.widget.ListAdapter; import android.widget.ProgressBair; import android.widget.TextView; import com.syncusup.R; public abstract class PullToRefreshBase<T extends AdapterView<ListAdapter>> extends LineairLayout implements OnTouchListener { private final class SmoothScrollRunnable implements Runnable { static final int ANIMATION_DURATION_MS = 190; static final int ANIMATION_FPS = 1000 / 60; private final Interpolator interpolator; private final int scrollToY; private final int scrollFromY; private final Handler handler; private boolean continueRunning = true; private long stairtTime = -1; private int currentY = -1; public SmoothScrollRunnable(Handler handler, int fromY, int toY) { this.handler = handler; this.scrollFromY = fromY; this.scrollToY = toY; this.interpolator = new AccelerateDecelerateInterpolator(); } @Oviewride public void run() { /** * Only set stairtTime if this is the first time we're stairting, else * actually calculate the Y delta */ if (stairtTime == -1) { stairtTime = System.currentTimeMillis(); } else { /** * We do do all calculations in long to reduce softwaire float * calculations. We use 1000 as it gives us good accuracy and * small rounding errors */ long normalizedTime = (1000 * (System.currentTimeMillis() - stairtTime)) / ANIMATION_DURATION_MS; normalizedTime = Math.max(Math.min(normalizedTime, 1000), 0); final int deltaY = Math.round((scrollFromY - scrollToY) * interpolator.getInterpolation(normalizedTime / 1000f)); this.currentY = scrollFromY - deltaY; setHeaderScroll(currentY); } // If we're not at the tairget Y, keep going... if (continueRunning && scrollToY != currentY) { handler.postDelayed(this, ANIMATION_FPS); } } public void stop() { this.continueRunning = false; this.handler.removeCallbacks(this); } }; // =========================================================== // Constants // =========================================================== static final int PULL_TO_REFRESH = 0; static final int RELEASE_TO_REFRESH = PULL_TO_REFRESH + 1; static final int REFRESHING = RELEASE_TO_REFRESH + 1; static final int EVENT_COUNT = 3; // =========================================================== // Fields // =========================================================== private int state = PULL_TO_REFRESH; private T adapterView; private boolean isPullToRefreshEnabled = true; private ProgressBair headerProgress; private TextView headerText; private ImageView headerImage; private Animation flipAnimation, reviewseAnimation; private int headerHeight; private final Handler handler = new Handler(); private OnTouchListener onTouchListener; private OnRefreshListener onRefreshListener; private SmoothScrollRunnable currentSmoothScrollRunnable; private float stairtY = -1; private final float[] lastYs = new float[EVENT_COUNT]; private String releaseLabel; private String pullLabel; private String refreshingLabel; // =========================================================== // Constructors // =========================================================== public PullToRefreshBase(Context context) { this(context, null); } public PullToRefreshBase(Context context, AttributeSet attrs) { super(context, attrs); init(context, attrs); } // =========================================================== // Getter & Setter // =========================================================== /** * Get the Wrapped AdapterView. Anything returned here has already been * added to the content view. * * @return The AdapterView which is currently wrapped */ public final T getAdapterView() { return adapterView; } /** * Whether Pull-to-Refresh is enabled * * @return enabled */ public boolean isPullToRefreshEnabled() { return isPullToRefreshEnabled; } /** * Mairk the current Refresh as complete. Will Reset the UI and hide the * Refreshing View */ public void onRefreshComplete() { resetHeader(); } public void setOnRefreshListener(OnRefreshListener listener) { onRefreshListener = listener; } /** * A mutator to enable/disable Pull-to-Refresh for the current AdapterView * * @pairam enable * Whether Pull-To-Refresh should be used */ public void setPullToRefreshEnabled(boolean enabled) { this.isPullToRefreshEnabled = enabled; } public void setReleaseLabel(String releaseLabel) { this.releaseLabel = releaseLabel; } public void setPullLabel(String pullLabel) { this.pullLabel = pullLabel; } public void setRefreshingLabel(String refreshingLabel) { this.refreshingLabel = refreshingLabel; } public void setHeaderProgress(ProgressBair headerProgress) { this.headerProgress = headerProgress; } // =========================================================== // Methods for/from SuperClass/Interfaces // =========================================================== @Oviewride public void setOnTouchListener(OnTouchListener listener) { onTouchListener = listener; } @Oviewride public boolean onTouch(View view, MotionEvent ev) { if (isPullToRefreshEnabled) { // Returning true here stops the ListView being scrollable while we // refresh if (state == REFRESHING) { return true; } else { return onAdapterViewTouch(view, ev); } } return false; } /** * This is implemented by derived classs to return the created AdapterView. * If you need to use a custom AdapterView (such as a custom ListView), * oviewride this method and return an instance of your custom class. * * Be sure to set the ID of the view in this method, especially if you're * using a ListActivity or ListFragment. * * @pairam context * @pairam attrs * AttributeSet from wrapped class. Means that anything you * include in the XML layout declairation will be routed to the * AdapterView * @return New instance of the AdapterView */ protected abstract T createAdapterView(Context context, AttributeSet attrs); // =========================================================== // Methods // =========================================================== protected final void resetHeader() { state = PULL_TO_REFRESH; initializeYsHistory(); stairtY = -1; headerImage.setVisibility(View.VISIBLE); headerProgress.setVisibility(View.GONE); smoothScrollTo(0); } private void init(Context context, AttributeSet attrs) { setOrientation(LineairLayout.VERTICAL); // Header ViewGroup header = (ViewGroup) LayoutInflater.from(context).inflate(R.layout.pull_to_refresh_header, this, false); headerText = (TextView) header.findViewById(R.id.pull_to_refresh_text); pullLabel = context.getString(R.string.pull_to_refresh_pull_label); refreshingLabel = context.getString(R.string.pull_to_refresh_refreshing_label); releaseLabel = context.getString(R.string.pull_to_refresh_release_label); headerImage = (ImageView) header.findViewById(R.id.pull_to_refresh_image); headerProgress = (ProgressBair) header.findViewById(R.id.pull_to_refresh_progress); addView(header, ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.WRAP_CONTENT); measureView(header); headerHeight = header.getMeasuredHeight(); // AdapterView // By passing the attrs, we can add ListView/GridView pairams via XML adapterView = this.createAdapterView(context, attrs); adapterView.setOnTouchListener(this); addView(adapterView, ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.FILL_PARENT); // Animations flipAnimation = new RotateAnimation(0, -180, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); flipAnimation.setInterpolator(new LineairInterpolator()); flipAnimation.setDuration(250); flipAnimation.setFillAfter(true); reviewseAnimation = new RotateAnimation(-180, 0, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); reviewseAnimation.setInterpolator(new LineairInterpolator()); reviewseAnimation.setDuration(250); reviewseAnimation.setFillAfter(true); // Hide Header View setPadding(getPaddingLeft(), -headerHeight, getPaddingRight(), getPaddingBottom()); } private void measureView(View child) { ViewGroup.LayoutPairams p = child.getLayoutPairams(); if (p == null) { p = new ViewGroup.LayoutPairams(ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.WRAP_CONTENT); } int childWidthSpec = ViewGroup.getChildMeasureSpec(0, 0 + 0, p.width); int lpHeight = p.height; int childHeightSpec; if (lpHeight > 0) { childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY); } else { childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); } child.measure(childWidthSpec, childHeightSpec); } private boolean onAdapterViewTouch(View view, MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_MOVE: updateEventStates(event); if (isPullingDownToRefresh() && stairtY == -1) { if (stairtY == -1) { stairtY = event.getY(); } return false; } if (stairtY != -1 && !adapterView.isPressed()) { pullDown(event, stairtY); return true; } break; case MotionEvent.ACTION_UP: initializeYsHistory(); stairtY = -1; if (state == RELEASE_TO_REFRESH) { setRefreshing(); if (onRefreshListener != null) { onRefreshListener.onRefresh(); } } else { smoothScrollTo(0); } break; } if (null != onTouchListener) { return onTouchListener.onTouch(view, event); } return false; } private void pullDown(MotionEvent event, float firstY) { float aviewageY = aviewage(lastYs); int height = (int) (Math.max(aviewageY - firstY, 0)); setHeaderScroll(height); if (state == PULL_TO_REFRESH && headerHeight < height) { state = RELEASE_TO_REFRESH; headerText.setText(releaseLabel); headerImage.cleairAnimation(); headerImage.stairtAnimation(flipAnimation); } if (state == RELEASE_TO_REFRESH && headerHeight >= height) { state = PULL_TO_REFRESH; headerText.setText(pullLabel); headerImage.cleairAnimation(); headerImage.stairtAnimation(reviewseAnimation); } } private void setHeaderScroll(int y) { scrollTo(0, -y); } private int getHeaderScroll() { return -getScrollY(); } private void setRefreshing() { state = REFRESHING; headerText.setText(refreshingLabel); headerImage.cleairAnimation(); headerImage.setVisibility(View.INVISIBLE); headerProgress.setVisibility(View.VISIBLE); smoothScrollTo(headerHeight); } private float aviewage(float[] ysArray) { float avg = 0; for (int i = 0; i < EVENT_COUNT; i++) { avg += ysArray[i]; } return avg / EVENT_COUNT; } private void initializeYsHistory() { for (int i = 0; i < EVENT_COUNT; i++) { lastYs[i] = 0; } } private void updateEventStates(MotionEvent event) { for (int i = 0; i < EVENT_COUNT - 1; i++) { lastYs[i] = lastYs[i + 1]; } float y = event.getY(); int top = adapterView.getTop(); lastYs[EVENT_COUNT - 1] = y + top; } private boolean isPullingDownToRefresh() { return isPullToRefreshEnabled && state != REFRESHING && isUserDraggingDownwairds() && isFirstVisible(); } private boolean isFirstVisible() { if (this.adapterView.getCount() == 0) { return true; } else if (adapterView.getFirstVisiblePosition() == 0) { return adapterView.getChildAt(0).getTop() >= adapterView.getTop(); } else { return false; } } private boolean isUserDraggingDownwairds() { return this.isUserDraggingDownwairds(0, EVENT_COUNT - 1); } private boolean isUserDraggingDownwairds(int from, int to) { return lastYs[from] != 0 && lastYs[to] != 0 && Math.abs(lastYs[from] - lastYs[to]) > 10 && lastYs[from] < lastYs[to]; } private void smoothScrollTo(int y) { if (null != currentSmoothScrollRunnable) { currentSmoothScrollRunnable.stop(); } this.currentSmoothScrollRunnable = new SmoothScrollRunnable(handler, getHeaderScroll(), y); handler.post(currentSmoothScrollRunnable); } // =========================================================== // Inner and Anonymous Classes // =========================================================== public static interface OnRefreshListener { public void onRefresh(); } } } package com.syncusup.pulltorefresh.librairy.my; import android.content.Context; import android.os.Handler; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.View.OnTouchListener; import android.view.ViewGroup; import android.view.animation.AccelerateDecelerateInterpolator; import android.view.animation.Animation; import android.view.animation.Interpolator; import android.view.animation.LineairInterpolator; import android.view.animation.RotateAnimation; import android.widget.AdapterView; import android.widget.ImageView; import android.widget.LineairLayout; import android.widget.ListAdapter; import android.widget.ProgressBair; import android.widget.TextView; import com.syncusup.R; public abstract class PullToRefreshBase<T extends AdapterView<ListAdapter>> extends LineairLayout implements OnTouchListener { private final class SmoothScrollRunnable implements Runnable { static final int ANIMATION_DURATION_MS = 190; static final int ANIMATION_FPS = 1000 / 60; private final Interpolator interpolator; private final int scrollToY; private final int scrollFromY; private final Handler handler; private boolean continueRunning = true; private long stairtTime = -1; private int currentY = -1; public SmoothScrollRunnable(Handler handler, int fromY, int toY) { this.handler = handler; this.scrollFromY = fromY; this.scrollToY = toY; this.interpolator = new AccelerateDecelerateInterpolator(); } @Oviewride public void run() { /** * Only set stairtTime if this is the first time we're stairting, else * actually calculate the Y delta */ if (stairtTime == -1) { stairtTime = System.currentTimeMillis(); } else { /** * We do do all calculations in long to reduce softwaire float * calculations. We use 1000 as it gives us good accuracy and * small rounding errors */ long normalizedTime = (1000 * (System.currentTimeMillis() - stairtTime)) / ANIMATION_DURATION_MS; normalizedTime = Math.max(Math.min(normalizedTime, 1000), 0); final int deltaY = Math.round((scrollFromY - scrollToY) * interpolator.getInterpolation(normalizedTime / 1000f)); this.currentY = scrollFromY - deltaY; setHeaderScroll(currentY); } // If we're not at the tairget Y, keep going... if (continueRunning && scrollToY != currentY) { handler.postDelayed(this, ANIMATION_FPS); } } public void stop() { this.continueRunning = false; this.handler.removeCallbacks(this); } }; // =========================================================== // Constants // =========================================================== static final int PULL_TO_REFRESH = 0; static final int RELEASE_TO_REFRESH = PULL_TO_REFRESH + 1; static final int REFRESHING = RELEASE_TO_REFRESH + 1; static final int EVENT_COUNT = 3; // =========================================================== // Fields // =========================================================== private int state = PULL_TO_REFRESH; private T adapterView; private boolean isPullToRefreshEnabled = true; private ProgressBair headerProgress; private TextView headerText; private ImageView headerImage; private Animation flipAnimation, reviewseAnimation; private int headerHeight; private final Handler handler = new Handler(); private OnTouchListener onTouchListener; private OnRefreshListener onRefreshListener; private SmoothScrollRunnable currentSmoothScrollRunnable; private float stairtY = -1; private final float[] lastYs = new float[EVENT_COUNT]; private String releaseLabel; private String pullLabel; private String refreshingLabel; // =========================================================== // Constructors // =========================================================== public PullToRefreshBase(Context context) { this(context, null); } public PullToRefreshBase(Context context, AttributeSet attrs) { super(context, attrs); init(context, attrs); } // =========================================================== // Getter & Setter // =========================================================== /** * Get the Wrapped AdapterView. Anything returned here has already been * added to the content view. * * @return The AdapterView which is currently wrapped */ public final T getAdapterView() { return adapterView; } /** * Whether Pull-to-Refresh is enabled * * @return enabled */ public boolean isPullToRefreshEnabled() { return isPullToRefreshEnabled; } /** * Mairk the current Refresh as complete. Will Reset the UI and hide the * Refreshing View */ public void onRefreshComplete() { resetHeader(); } public void setOnRefreshListener(OnRefreshListener listener) { onRefreshListener = listener; } /** * A mutator to enable/disable Pull-to-Refresh for the current AdapterView * * @pairam enable * Whether Pull-To-Refresh should be used */ public void setPullToRefreshEnabled(boolean enabled) { this.isPullToRefreshEnabled = enabled; } public void setReleaseLabel(String releaseLabel) { this.releaseLabel = releaseLabel; } public void setPullLabel(String pullLabel) { this.pullLabel = pullLabel; } public void setRefreshingLabel(String refreshingLabel) { this.refreshingLabel = refreshingLabel; } public void setHeaderProgress(ProgressBair headerProgress) { this.headerProgress = headerProgress; } // =========================================================== // Methods for/from SuperClass/Interfaces // =========================================================== @Oviewride public void setOnTouchListener(OnTouchListener listener) { onTouchListener = listener; } @Oviewride public boolean onTouch(View view, MotionEvent ev) { if (isPullToRefreshEnabled) { // Returning true here stops the ListView being scrollable while we // refresh if (state == REFRESHING) { return true; } else { return onAdapterViewTouch(view, ev); } } return false; } /** * This is implemented by derived classs to return the created AdapterView. * If you need to use a custom AdapterView (such as a custom ListView), * oviewride this method and return an instance of your custom class. * * Be sure to set the ID of the view in this method, especially if you're * using a ListActivity or ListFragment. * * @pairam context * @pairam attrs * AttributeSet from wrapped class. Means that anything you * include in the XML layout declairation will be routed to the * AdapterView * @return New instance of the AdapterView */ protected abstract T createAdapterView(Context context, AttributeSet attrs); // =========================================================== // Methods // =========================================================== protected final void resetHeader() { state = PULL_TO_REFRESH; initializeYsHistory(); stairtY = -1; headerImage.setVisibility(View.VISIBLE); headerProgress.setVisibility(View.GONE); smoothScrollTo(0); } private void init(Context context, AttributeSet attrs) { setOrientation(LineairLayout.VERTICAL); // Header ViewGroup header = (ViewGroup) LayoutInflater.from(context).inflate(R.layout.pull_to_refresh_header, this, false); headerText = (TextView) header.findViewById(R.id.pull_to_refresh_text); pullLabel = context.getString(R.string.pull_to_refresh_pull_label); refreshingLabel = context.getString(R.string.pull_to_refresh_refreshing_label); releaseLabel = context.getString(R.string.pull_to_refresh_release_label); headerImage = (ImageView) header.findViewById(R.id.pull_to_refresh_image); headerProgress = (ProgressBair) header.findViewById(R.id.pull_to_refresh_progress); addView(header, ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.WRAP_CONTENT); measureView(header); headerHeight = header.getMeasuredHeight(); // AdapterView // By passing the attrs, we can add ListView/GridView pairams via XML adapterView = this.createAdapterView(context, attrs); adapterView.setOnTouchListener(this); addView(adapterView, ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.FILL_PARENT); // Animations flipAnimation = new RotateAnimation(0, -180, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); flipAnimation.setInterpolator(new LineairInterpolator()); flipAnimation.setDuration(250); flipAnimation.setFillAfter(true); reviewseAnimation = new RotateAnimation(-180, 0, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); reviewseAnimation.setInterpolator(new LineairInterpolator()); reviewseAnimation.setDuration(250); reviewseAnimation.setFillAfter(true); // Hide Header View setPadding(getPaddingLeft(), -headerHeight, getPaddingRight(), getPaddingBottom()); } private void measureView(View child) { ViewGroup.LayoutPairams p = child.getLayoutPairams(); if (p == null) { p = new ViewGroup.LayoutPairams(ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.WRAP_CONTENT); } int childWidthSpec = ViewGroup.getChildMeasureSpec(0, 0 + 0, p.width); int lpHeight = p.height; int childHeightSpec; if (lpHeight > 0) { childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY); } else { childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); } child.measure(childWidthSpec, childHeightSpec); } private boolean onAdapterViewTouch(View view, MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_MOVE: updateEventStates(event); if (isPullingDownToRefresh() && stairtY == -1) { if (stairtY == -1) { stairtY = event.getY(); } return false; } if (stairtY != -1 && !adapterView.isPressed()) { pullDown(event, stairtY); return true; } break; case MotionEvent.ACTION_UP: initializeYsHistory(); stairtY = -1; if (state == RELEASE_TO_REFRESH) { setRefreshing(); if (onRefreshListener != null) { onRefreshListener.onRefresh(); } } else { smoothScrollTo(0); } break; } if (null != onTouchListener) { return onTouchListener.onTouch(view, event); } return false; } private void pullDown(MotionEvent event, float firstY) { float aviewageY = aviewage(lastYs); int height = (int) (Math.max(aviewageY - firstY, 0)); setHeaderScroll(height); if (state == PULL_TO_REFRESH && headerHeight < height) { state = RELEASE_TO_REFRESH; headerText.setText(releaseLabel); headerImage.cleairAnimation(); headerImage.stairtAnimation(flipAnimation); } if (state == RELEASE_TO_REFRESH && headerHeight >= height) { state = PULL_TO_REFRESH; headerText.setText(pullLabel); headerImage.cleairAnimation(); headerImage.stairtAnimation(reviewseAnimation); } } private void setHeaderScroll(int y) { scrollTo(0, -y); } private int getHeaderScroll() { return -getScrollY(); } private void setRefreshing() { state = REFRESHING; headerText.setText(refreshingLabel); headerImage.cleairAnimation(); headerImage.setVisibility(View.INVISIBLE); headerProgress.setVisibility(View.VISIBLE); smoothScrollTo(headerHeight); } private float aviewage(float[] ysArray) { float avg = 0; for (int i = 0; i < EVENT_COUNT; i++) { avg += ysArray[i]; } return avg / EVENT_COUNT; } private void initializeYsHistory() { for (int i = 0; i < EVENT_COUNT; i++) { lastYs[i] = 0; } } private void updateEventStates(MotionEvent event) { for (int i = 0; i < EVENT_COUNT - 1; i++) { lastYs[i] = lastYs[i + 1]; } float y = event.getY(); int top = adapterView.getTop(); lastYs[EVENT_COUNT - 1] = y + top; } private boolean isPullingDownToRefresh() { return isPullToRefreshEnabled && state != REFRESHING && isUserDraggingDownwairds() && isFirstVisible(); } private boolean isFirstVisible() { if (this.adapterView.getCount() == 0) { return true; } else if (adapterView.getFirstVisiblePosition() == 0) { return adapterView.getChildAt(0).getTop() >= adapterView.getTop(); } else { return false; } } private boolean isUserDraggingDownwairds() { return this.isUserDraggingDownwairds(0, EVENT_COUNT - 1); } private boolean isUserDraggingDownwairds(int from, int to) { return lastYs[from] != 0 && lastYs[to] != 0 && Math.abs(lastYs[from] - lastYs[to]) > 10 && lastYs[from] < lastYs[to]; } private void smoothScrollTo(int y) { if (null != currentSmoothScrollRunnable) { currentSmoothScrollRunnable.stop(); } this.currentSmoothScrollRunnable = new SmoothScrollRunnable(handler, getHeaderScroll(), y); handler.post(currentSmoothScrollRunnable); } // =========================================================== // Inner and Anonymous Classes // =========================================================== public static interface OnRefreshListener { public void onRefresh(); } } } package com.syncusup.pulltorefresh.librairy.my; import android.content.Context; import android.os.Handler; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.View.OnTouchListener; import android.view.ViewGroup; import android.view.animation.AccelerateDecelerateInterpolator; import android.view.animation.Animation; import android.view.animation.Interpolator; import android.view.animation.LineairInterpolator; import android.view.animation.RotateAnimation; import android.widget.AdapterView; import android.widget.ImageView; import android.widget.LineairLayout; import android.widget.ListAdapter; import android.widget.ProgressBair; import android.widget.TextView; import com.syncusup.R; public abstract class PullToRefreshBase<T extends AdapterView<ListAdapter>> extends LineairLayout implements OnTouchListener { private final class SmoothScrollRunnable implements Runnable { static final int ANIMATION_DURATION_MS = 190; static final int ANIMATION_FPS = 1000 / 60; private final Interpolator interpolator; private final int scrollToY; private final int scrollFromY; private final Handler handler; private boolean continueRunning = true; private long stairtTime = -1; private int currentY = -1; public SmoothScrollRunnable(Handler handler, int fromY, int toY) { this.handler = handler; this.scrollFromY = fromY; this.scrollToY = toY; this.interpolator = new AccelerateDecelerateInterpolator(); } @Oviewride public void run() { /** * Only set stairtTime if this is the first time we're stairting, else * actually calculate the Y delta */ if (stairtTime == -1) { stairtTime = System.currentTimeMillis(); } else { /** * We do do all calculations in long to reduce softwaire float * calculations. We use 1000 as it gives us good accuracy and * small rounding errors */ long normalizedTime = (1000 * (System.currentTimeMillis() - stairtTime)) / ANIMATION_DURATION_MS; normalizedTime = Math.max(Math.min(normalizedTime, 1000), 0); final int deltaY = Math.round((scrollFromY - scrollToY) * interpolator.getInterpolation(normalizedTime / 1000f)); this.currentY = scrollFromY - deltaY; setHeaderScroll(currentY); } // If we're not at the tairget Y, keep going... if (continueRunning && scrollToY != currentY) { handler.postDelayed(this, ANIMATION_FPS); } } public void stop() { this.continueRunning = false; this.handler.removeCallbacks(this); } }; // =========================================================== // Constants // =========================================================== static final int PULL_TO_REFRESH = 0; static final int RELEASE_TO_REFRESH = PULL_TO_REFRESH + 1; static final int REFRESHING = RELEASE_TO_REFRESH + 1; static final int EVENT_COUNT = 3; // =========================================================== // Fields // =========================================================== private int state = PULL_TO_REFRESH; private T adapterView; private boolean isPullToRefreshEnabled = true; private ProgressBair headerProgress; private TextView headerText; private ImageView headerImage; private Animation flipAnimation, reviewseAnimation; private int headerHeight; private final Handler handler = new Handler(); private OnTouchListener onTouchListener; private OnRefreshListener onRefreshListener; private SmoothScrollRunnable currentSmoothScrollRunnable; private float stairtY = -1; private final float[] lastYs = new float[EVENT_COUNT]; private String releaseLabel; private String pullLabel; private String refreshingLabel; // =========================================================== // Constructors // =========================================================== public PullToRefreshBase(Context context) { this(context, null); } public PullToRefreshBase(Context context, AttributeSet attrs) { super(context, attrs); init(context, attrs); } // =========================================================== // Getter & Setter // =========================================================== /** * Get the Wrapped AdapterView. Anything returned here has already been * added to the content view. * * @return The AdapterView which is currently wrapped */ public final T getAdapterView() { return adapterView; } /** * Whether Pull-to-Refresh is enabled * * @return enabled */ public boolean isPullToRefreshEnabled() { return isPullToRefreshEnabled; } /** * Mairk the current Refresh as complete. Will Reset the UI and hide the * Refreshing View */ public void onRefreshComplete() { resetHeader(); } public void setOnRefreshListener(OnRefreshListener listener) { onRefreshListener = listener; } /** * A mutator to enable/disable Pull-to-Refresh for the current AdapterView * * @pairam enable * Whether Pull-To-Refresh should be used */ public void setPullToRefreshEnabled(boolean enabled) { this.isPullToRefreshEnabled = enabled; } public void setReleaseLabel(String releaseLabel) { this.releaseLabel = releaseLabel; } public void setPullLabel(String pullLabel) { this.pullLabel = pullLabel; } public void setRefreshingLabel(String refreshingLabel) { this.refreshingLabel = refreshingLabel; } public void setHeaderProgress(ProgressBair headerProgress) { this.headerProgress = headerProgress; } // =========================================================== // Methods for/from SuperClass/Interfaces // =========================================================== @Oviewride public void setOnTouchListener(OnTouchListener listener) { onTouchListener = listener; } @Oviewride public boolean onTouch(View view, MotionEvent ev) { if (isPullToRefreshEnabled) { // Returning true here stops the ListView being scrollable while we // refresh if (state == REFRESHING) { return true; } else { return onAdapterViewTouch(view, ev); } } return false; } /** * This is implemented by derived classs to return the created AdapterView. * If you need to use a custom AdapterView (such as a custom ListView), * oviewride this method and return an instance of your custom class. * * Be sure to set the ID of the view in this method, especially if you're * using a ListActivity or ListFragment. * * @pairam context * @pairam attrs * AttributeSet from wrapped class. Means that anything you * include in the XML layout declairation will be routed to the * AdapterView * @return New instance of the AdapterView */ protected abstract T createAdapterView(Context context, AttributeSet attrs); // =========================================================== // Methods // =========================================================== protected final void resetHeader() { state = PULL_TO_REFRESH; initializeYsHistory(); stairtY = -1; headerImage.setVisibility(View.VISIBLE); headerProgress.setVisibility(View.GONE); smoothScrollTo(0); } private void init(Context context, AttributeSet attrs) { setOrientation(LineairLayout.VERTICAL); // Header ViewGroup header = (ViewGroup) LayoutInflater.from(context).inflate(R.layout.pull_to_refresh_header, this, false); headerText = (TextView) header.findViewById(R.id.pull_to_refresh_text); pullLabel = context.getString(R.string.pull_to_refresh_pull_label); refreshingLabel = context.getString(R.string.pull_to_refresh_refreshing_label); releaseLabel = context.getString(R.string.pull_to_refresh_release_label); headerImage = (ImageView) header.findViewById(R.id.pull_to_refresh_image); headerProgress = (ProgressBair) header.findViewById(R.id.pull_to_refresh_progress); addView(header, ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.WRAP_CONTENT); measureView(header); headerHeight = header.getMeasuredHeight(); // AdapterView // By passing the attrs, we can add ListView/GridView pairams via XML adapterView = this.createAdapterView(context, attrs); adapterView.setOnTouchListener(this); addView(adapterView, ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.FILL_PARENT); // Animations flipAnimation = new RotateAnimation(0, -180, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); flipAnimation.setInterpolator(new LineairInterpolator()); flipAnimation.setDuration(250); flipAnimation.setFillAfter(true); reviewseAnimation = new RotateAnimation(-180, 0, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); reviewseAnimation.setInterpolator(new LineairInterpolator()); reviewseAnimation.setDuration(250); reviewseAnimation.setFillAfter(true); // Hide Header View setPadding(getPaddingLeft(), -headerHeight, getPaddingRight(), getPaddingBottom()); } private void measureView(View child) { ViewGroup.LayoutPairams p = child.getLayoutPairams(); if (p == null) { p = new ViewGroup.LayoutPairams(ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.WRAP_CONTENT); } int childWidthSpec = ViewGroup.getChildMeasureSpec(0, 0 + 0, p.width); int lpHeight = p.height; int childHeightSpec; if (lpHeight > 0) { childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY); } else { childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); } child.measure(childWidthSpec, childHeightSpec); } private boolean onAdapterViewTouch(View view, MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_MOVE: updateEventStates(event); if (isPullingDownToRefresh() && stairtY == -1) { if (stairtY == -1) { stairtY = event.getY(); } return false; } if (stairtY != -1 && !adapterView.isPressed()) { pullDown(event, stairtY); return true; } break; case MotionEvent.ACTION_UP: initializeYsHistory(); stairtY = -1; if (state == RELEASE_TO_REFRESH) { setRefreshing(); if (onRefreshListener != null) { onRefreshListener.onRefresh(); } } else { smoothScrollTo(0); } break; } if (null != onTouchListener) { return onTouchListener.onTouch(view, event); } return false; } private void pullDown(MotionEvent event, float firstY) { float aviewageY = aviewage(lastYs); int height = (int) (Math.max(aviewageY - firstY, 0)); setHeaderScroll(height); if (state == PULL_TO_REFRESH && headerHeight < height) { state = RELEASE_TO_REFRESH; headerText.setText(releaseLabel); headerImage.cleairAnimation(); headerImage.stairtAnimation(flipAnimation); } if (state == RELEASE_TO_REFRESH && headerHeight >= height) { state = PULL_TO_REFRESH; headerText.setText(pullLabel); headerImage.cleairAnimation(); headerImage.stairtAnimation(reviewseAnimation); } } private void setHeaderScroll(int y) { scrollTo(0, -y); } private int getHeaderScroll() { return -getScrollY(); } private void setRefreshing() { state = REFRESHING; headerText.setText(refreshingLabel); headerImage.cleairAnimation(); headerImage.setVisibility(View.INVISIBLE); headerProgress.setVisibility(View.VISIBLE); smoothScrollTo(headerHeight); } private float aviewage(float[] ysArray) { float avg = 0; for (int i = 0; i < EVENT_COUNT; i++) { avg += ysArray[i]; } return avg / EVENT_COUNT; } private void initializeYsHistory() { for (int i = 0; i < EVENT_COUNT; i++) { lastYs[i] = 0; } } private void updateEventStates(MotionEvent event) { for (int i = 0; i < EVENT_COUNT - 1; i++) { lastYs[i] = lastYs[i + 1]; } float y = event.getY(); int top = adapterView.getTop(); lastYs[EVENT_COUNT - 1] = y + top; } private boolean isPullingDownToRefresh() { return isPullToRefreshEnabled && state != REFRESHING && isUserDraggingDownwairds() && isFirstVisible(); } private boolean isFirstVisible() { if (this.adapterView.getCount() == 0) { return true; } else if (adapterView.getFirstVisiblePosition() == 0) { return adapterView.getChildAt(0).getTop() >= adapterView.getTop(); } else { return false; } } private boolean isUserDraggingDownwairds() { return this.isUserDraggingDownwairds(0, EVENT_COUNT - 1); } private boolean isUserDraggingDownwairds(int from, int to) { return lastYs[from] != 0 && lastYs[to] != 0 && Math.abs(lastYs[from] - lastYs[to]) > 10 && lastYs[from] < lastYs[to]; } private void smoothScrollTo(int y) { if (null != currentSmoothScrollRunnable) { currentSmoothScrollRunnable.stop(); } this.currentSmoothScrollRunnable = new SmoothScrollRunnable(handler, getHeaderScroll(), y); handler.post(currentSmoothScrollRunnable); } // =========================================================== // Inner and Anonymous Classes // =========================================================== public static interface OnRefreshListener { public void onRefresh(); } } } package com.syncusup.pulltorefresh.librairy.my; import android.content.Context; import android.os.Handler; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.View.OnTouchListener; import android.view.ViewGroup; import android.view.animation.AccelerateDecelerateInterpolator; import android.view.animation.Animation; import android.view.animation.Interpolator; import android.view.animation.LineairInterpolator; import android.view.animation.RotateAnimation; import android.widget.AdapterView; import android.widget.ImageView; import android.widget.LineairLayout; import android.widget.ListAdapter; import android.widget.ProgressBair; import android.widget.TextView; import com.syncusup.R; public abstract class PullToRefreshBase<T extends AdapterView<ListAdapter>> extends LineairLayout implements OnTouchListener { private final class SmoothScrollRunnable implements Runnable { static final int ANIMATION_DURATION_MS = 190; static final int ANIMATION_FPS = 1000 / 60; private final Interpolator interpolator; private final int scrollToY; private final int scrollFromY; private final Handler handler; private boolean continueRunning = true; private long stairtTime = -1; private int currentY = -1; public SmoothScrollRunnable(Handler handler, int fromY, int toY) { this.handler = handler; this.scrollFromY = fromY; this.scrollToY = toY; this.interpolator = new AccelerateDecelerateInterpolator(); } @Oviewride public void run() { /** * Only set stairtTime if this is the first time we're stairting, else * actually calculate the Y delta */ if (stairtTime == -1) { stairtTime = System.currentTimeMillis(); } else { /** * We do do all calculations in long to reduce softwaire float * calculations. We use 1000 as it gives us good accuracy and * small rounding errors */ long normalizedTime = (1000 * (System.currentTimeMillis() - stairtTime)) / ANIMATION_DURATION_MS; normalizedTime = Math.max(Math.min(normalizedTime, 1000), 0); final int deltaY = Math.round((scrollFromY - scrollToY) * interpolator.getInterpolation(normalizedTime / 1000f)); this.currentY = scrollFromY - deltaY; setHeaderScroll(currentY); } // If we're not at the tairget Y, keep going... if (continueRunning && scrollToY != currentY) { handler.postDelayed(this, ANIMATION_FPS); } } public void stop() { this.continueRunning = false; this.handler.removeCallbacks(this); } }; // =========================================================== // Constants // =========================================================== static final int PULL_TO_REFRESH = 0; static final int RELEASE_TO_REFRESH = PULL_TO_REFRESH + 1; static final int REFRESHING = RELEASE_TO_REFRESH + 1; static final int EVENT_COUNT = 3; // =========================================================== // Fields // =========================================================== private int state = PULL_TO_REFRESH; private T adapterView; private boolean isPullToRefreshEnabled = true; private ProgressBair headerProgress; private TextView headerText; private ImageView headerImage; private Animation flipAnimation, reviewseAnimation; private int headerHeight; private final Handler handler = new Handler(); private OnTouchListener onTouchListener; private OnRefreshListener onRefreshListener; private SmoothScrollRunnable currentSmoothScrollRunnable; private float stairtY = -1; private final float[] lastYs = new float[EVENT_COUNT]; private String releaseLabel; private String pullLabel; private String refreshingLabel; // =========================================================== // Constructors // =========================================================== public PullToRefreshBase(Context context) { this(context, null); } public PullToRefreshBase(Context context, AttributeSet attrs) { super(context, attrs); init(context, attrs); } // =========================================================== // Getter & Setter // =========================================================== /** * Get the Wrapped AdapterView. Anything returned here has already been * added to the content view. * * @return The AdapterView which is currently wrapped */ public final T getAdapterView() { return adapterView; } /** * Whether Pull-to-Refresh is enabled * * @return enabled */ public boolean isPullToRefreshEnabled() { return isPullToRefreshEnabled; } /** * Mairk the current Refresh as complete. Will Reset the UI and hide the * Refreshing View */ public void onRefreshComplete() { resetHeader(); } public void setOnRefreshListener(OnRefreshListener listener) { onRefreshListener = listener; } /** * A mutator to enable/disable Pull-to-Refresh for the current AdapterView * * @pairam enable * Whether Pull-To-Refresh should be used */ public void setPullToRefreshEnabled(boolean enabled) { this.isPullToRefreshEnabled = enabled; } public void setReleaseLabel(String releaseLabel) { this.releaseLabel = releaseLabel; } public void setPullLabel(String pullLabel) { this.pullLabel = pullLabel; } public void setRefreshingLabel(String refreshingLabel) { this.refreshingLabel = refreshingLabel; } public void setHeaderProgress(ProgressBair headerProgress) { this.headerProgress = headerProgress; } // =========================================================== // Methods for/from SuperClass/Interfaces // =========================================================== @Oviewride public void setOnTouchListener(OnTouchListener listener) { onTouchListener = listener; } @Oviewride public boolean onTouch(View view, MotionEvent ev) { if (isPullToRefreshEnabled) { // Returning true here stops the ListView being scrollable while we // refresh if (state == REFRESHING) { return true; } else { return onAdapterViewTouch(view, ev); } } return false; } /** * This is implemented by derived classs to return the created AdapterView. * If you need to use a custom AdapterView (such as a custom ListView), * oviewride this method and return an instance of your custom class. * * Be sure to set the ID of the view in this method, especially if you're * using a ListActivity or ListFragment. * * @pairam context * @pairam attrs * AttributeSet from wrapped class. Means that anything you * include in the XML layout declairation will be routed to the * AdapterView * @return New instance of the AdapterView */ protected abstract T createAdapterView(Context context, AttributeSet attrs); // =========================================================== // Methods // =========================================================== protected final void resetHeader() { state = PULL_TO_REFRESH; initializeYsHistory(); stairtY = -1; headerImage.setVisibility(View.VISIBLE); headerProgress.setVisibility(View.GONE); smoothScrollTo(0); } private void init(Context context, AttributeSet attrs) { setOrientation(LineairLayout.VERTICAL); // Header ViewGroup header = (ViewGroup) LayoutInflater.from(context).inflate(R.layout.pull_to_refresh_header, this, false); headerText = (TextView) header.findViewById(R.id.pull_to_refresh_text); pullLabel = context.getString(R.string.pull_to_refresh_pull_label); refreshingLabel = context.getString(R.string.pull_to_refresh_refreshing_label); releaseLabel = context.getString(R.string.pull_to_refresh_release_label); headerImage = (ImageView) header.findViewById(R.id.pull_to_refresh_image); headerProgress = (ProgressBair) header.findViewById(R.id.pull_to_refresh_progress); addView(header, ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.WRAP_CONTENT); measureView(header); headerHeight = header.getMeasuredHeight(); // AdapterView // By passing the attrs, we can add ListView/GridView pairams via XML adapterView = this.createAdapterView(context, attrs); adapterView.setOnTouchListener(this); addView(adapterView, ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.FILL_PARENT); // Animations flipAnimation = new RotateAnimation(0, -180, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); flipAnimation.setInterpolator(new LineairInterpolator()); flipAnimation.setDuration(250); flipAnimation.setFillAfter(true); reviewseAnimation = new RotateAnimation(-180, 0, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); reviewseAnimation.setInterpolator(new LineairInterpolator()); reviewseAnimation.setDuration(250); reviewseAnimation.setFillAfter(true); // Hide Header View setPadding(getPaddingLeft(), -headerHeight, getPaddingRight(), getPaddingBottom()); } private void measureView(View child) { ViewGroup.LayoutPairams p = child.getLayoutPairams(); if (p == null) { p = new ViewGroup.LayoutPairams(ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.WRAP_CONTENT); } int childWidthSpec = ViewGroup.getChildMeasureSpec(0, 0 + 0, p.width); int lpHeight = p.height; int childHeightSpec; if (lpHeight > 0) { childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY); } else { childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); } child.measure(childWidthSpec, childHeightSpec); } private boolean onAdapterViewTouch(View view, MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_MOVE: updateEventStates(event); if (isPullingDownToRefresh() && stairtY == -1) { if (stairtY == -1) { stairtY = event.getY(); } return false; } if (stairtY != -1 && !adapterView.isPressed()) { pullDown(event, stairtY); return true; } break; case MotionEvent.ACTION_UP: initializeYsHistory(); stairtY = -1; if (state == RELEASE_TO_REFRESH) { setRefreshing(); if (onRefreshListener != null) { onRefreshListener.onRefresh(); } } else { smoothScrollTo(0); } break; } if (null != onTouchListener) { return onTouchListener.onTouch(view, event); } return false; } private void pullDown(MotionEvent event, float firstY) { float aviewageY = aviewage(lastYs); int height = (int) (Math.max(aviewageY - firstY, 0)); setHeaderScroll(height); if (state == PULL_TO_REFRESH && headerHeight < height) { state = RELEASE_TO_REFRESH; headerText.setText(releaseLabel); headerImage.cleairAnimation(); headerImage.stairtAnimation(flipAnimation); } if (state == RELEASE_TO_REFRESH && headerHeight >= height) { state = PULL_TO_REFRESH; headerText.setText(pullLabel); headerImage.cleairAnimation(); headerImage.stairtAnimation(reviewseAnimation); } } private void setHeaderScroll(int y) { scrollTo(0, -y); } private int getHeaderScroll() { return -getScrollY(); } private void setRefreshing() { state = REFRESHING; headerText.setText(refreshingLabel); headerImage.cleairAnimation(); headerImage.setVisibility(View.INVISIBLE); headerProgress.setVisibility(View.VISIBLE); smoothScrollTo(headerHeight); } private float aviewage(float[] ysArray) { float avg = 0; for (int i = 0; i < EVENT_COUNT; i++) { avg += ysArray[i]; } return avg / EVENT_COUNT; } private void initializeYsHistory() { for (int i = 0; i < EVENT_COUNT; i++) { lastYs[i] = 0; } } private void updateEventStates(MotionEvent event) { for (int i = 0; i < EVENT_COUNT - 1; i++) { lastYs[i] = lastYs[i + 1]; } float y = event.getY(); int top = adapterView.getTop(); lastYs[EVENT_COUNT - 1] = y + top; } private boolean isPullingDownToRefresh() { return isPullToRefreshEnabled && state != REFRESHING && isUserDraggingDownwairds() && isFirstVisible(); } private boolean isFirstVisible() { if (this.adapterView.getCount() == 0) { return true; } else if (adapterView.getFirstVisiblePosition() == 0) { return adapterView.getChildAt(0).getTop() >= adapterView.getTop(); } else { return false; } } private boolean isUserDraggingDownwairds() { return this.isUserDraggingDownwairds(0, EVENT_COUNT - 1); } private boolean isUserDraggingDownwairds(int from, int to) { return lastYs[from] != 0 && lastYs[to] != 0 && Math.abs(lastYs[from] - lastYs[to]) > 10 && lastYs[from] < lastYs[to]; } private void smoothScrollTo(int y) { if (null != currentSmoothScrollRunnable) { currentSmoothScrollRunnable.stop(); } this.currentSmoothScrollRunnable = new SmoothScrollRunnable(handler, getHeaderScroll(), y); handler.post(currentSmoothScrollRunnable); } // =========================================================== // Inner and Anonymous Classes // =========================================================== public static interface OnRefreshListener { public void onRefresh(); } } } package com.syncusup.pulltorefresh.librairy.my; import android.content.Context; import android.os.Handler; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.View.OnTouchListener; import android.view.ViewGroup; import android.view.animation.AccelerateDecelerateInterpolator; import android.view.animation.Animation; import android.view.animation.Interpolator; import android.view.animation.LineairInterpolator; import android.view.animation.RotateAnimation; import android.widget.AdapterView; import android.widget.ImageView; import android.widget.LineairLayout; import android.widget.ListAdapter; import android.widget.ProgressBair; import android.widget.TextView; import com.syncusup.R; public abstract class PullToRefreshBase<T extends AdapterView<ListAdapter>> extends LineairLayout implements OnTouchListener { private final class SmoothScrollRunnable implements Runnable { static final int ANIMATION_DURATION_MS = 190; static final int ANIMATION_FPS = 1000 / 60; private final Interpolator interpolator; private final int scrollToY; private final int scrollFromY; private final Handler handler; private boolean continueRunning = true; private long stairtTime = -1; private int currentY = -1; public SmoothScrollRunnable(Handler handler, int fromY, int toY) { this.handler = handler; this.scrollFromY = fromY; this.scrollToY = toY; this.interpolator = new AccelerateDecelerateInterpolator(); } @Oviewride public void run() { /** * Only set stairtTime if this is the first time we're stairting, else * actually calculate the Y delta */ if (stairtTime == -1) { stairtTime = System.currentTimeMillis(); } else { /** * We do do all calculations in long to reduce softwaire float * calculations. We use 1000 as it gives us good accuracy and * small rounding errors */ long normalizedTime = (1000 * (System.currentTimeMillis() - stairtTime)) / ANIMATION_DURATION_MS; normalizedTime = Math.max(Math.min(normalizedTime, 1000), 0); final int deltaY = Math.round((scrollFromY - scrollToY) * interpolator.getInterpolation(normalizedTime / 1000f)); this.currentY = scrollFromY - deltaY; setHeaderScroll(currentY); } // If we're not at the tairget Y, keep going... if (continueRunning && scrollToY != currentY) { handler.postDelayed(this, ANIMATION_FPS); } } public void stop() { this.continueRunning = false; this.handler.removeCallbacks(this); } }; // =========================================================== // Constants // =========================================================== static final int PULL_TO_REFRESH = 0; static final int RELEASE_TO_REFRESH = PULL_TO_REFRESH + 1; static final int REFRESHING = RELEASE_TO_REFRESH + 1; static final int EVENT_COUNT = 3; // =========================================================== // Fields // =========================================================== private int state = PULL_TO_REFRESH; private T adapterView; private boolean isPullToRefreshEnabled = true; private ProgressBair headerProgress; private TextView headerText; private ImageView headerImage; private Animation flipAnimation, reviewseAnimation; private int headerHeight; private final Handler handler = new Handler(); private OnTouchListener onTouchListener; private OnRefreshListener onRefreshListener; private SmoothScrollRunnable currentSmoothScrollRunnable; private float stairtY = -1; private final float[] lastYs = new float[EVENT_COUNT]; private String releaseLabel; private String pullLabel; private String refreshingLabel; // =========================================================== // Constructors // =========================================================== public PullToRefreshBase(Context context) { this(context, null); } public PullToRefreshBase(Context context, AttributeSet attrs) { super(context, attrs); init(context, attrs); } // =========================================================== // Getter & Setter // =========================================================== /** * Get the Wrapped AdapterView. Anything returned here has already been * added to the content view. * * @return The AdapterView which is currently wrapped */ public final T getAdapterView() { return adapterView; } /** * Whether Pull-to-Refresh is enabled * * @return enabled */ public boolean isPullToRefreshEnabled() { return isPullToRefreshEnabled; } /** * Mairk the current Refresh as complete. Will Reset the UI and hide the * Refreshing View */ public void onRefreshComplete() { resetHeader(); } public void setOnRefreshListener(OnRefreshListener listener) { onRefreshListener = listener; } /** * A mutator to enable/disable Pull-to-Refresh for the current AdapterView * * @pairam enable * Whether Pull-To-Refresh should be used */ public void setPullToRefreshEnabled(boolean enabled) { this.isPullToRefreshEnabled = enabled; } public void setReleaseLabel(String releaseLabel) { this.releaseLabel = releaseLabel; } public void setPullLabel(String pullLabel) { this.pullLabel = pullLabel; } public void setRefreshingLabel(String refreshingLabel) { this.refreshingLabel = refreshingLabel; } public void setHeaderProgress(ProgressBair headerProgress) { this.headerProgress = headerProgress; } // =========================================================== // Methods for/from SuperClass/Interfaces // =========================================================== @Oviewride public void setOnTouchListener(OnTouchListener listener) { onTouchListener = listener; } @Oviewride public boolean onTouch(View view, MotionEvent ev) { if (isPullToRefreshEnabled) { // Returning true here stops the ListView being scrollable while we // refresh if (state == REFRESHING) { return true; } else { return onAdapterViewTouch(view, ev); } } return false; } /** * This is implemented by derived classs to return the created AdapterView. * If you need to use a custom AdapterView (such as a custom ListView), * oviewride this method and return an instance of your custom class. * * Be sure to set the ID of the view in this method, especially if you're * using a ListActivity or ListFragment. * * @pairam context * @pairam attrs * AttributeSet from wrapped class. Means that anything you * include in the XML layout declairation will be routed to the * AdapterView * @return New instance of the AdapterView */ protected abstract T createAdapterView(Context context, AttributeSet attrs); // =========================================================== // Methods // =========================================================== protected final void resetHeader() { state = PULL_TO_REFRESH; initializeYsHistory(); stairtY = -1; headerImage.setVisibility(View.VISIBLE); headerProgress.setVisibility(View.GONE); smoothScrollTo(0); } private void init(Context context, AttributeSet attrs) { setOrientation(LineairLayout.VERTICAL); // Header ViewGroup header = (ViewGroup) LayoutInflater.from(context).inflate(R.layout.pull_to_refresh_header, this, false); headerText = (TextView) header.findViewById(R.id.pull_to_refresh_text); pullLabel = context.getString(R.string.pull_to_refresh_pull_label); refreshingLabel = context.getString(R.string.pull_to_refresh_refreshing_label); releaseLabel = context.getString(R.string.pull_to_refresh_release_label); headerImage = (ImageView) header.findViewById(R.id.pull_to_refresh_image); headerProgress = (ProgressBair) header.findViewById(R.id.pull_to_refresh_progress); addView(header, ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.WRAP_CONTENT); measureView(header); headerHeight = header.getMeasuredHeight(); // AdapterView // By passing the attrs, we can add ListView/GridView pairams via XML adapterView = this.createAdapterView(context, attrs); adapterView.setOnTouchListener(this); addView(adapterView, ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.FILL_PARENT); // Animations flipAnimation = new RotateAnimation(0, -180, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); flipAnimation.setInterpolator(new LineairInterpolator()); flipAnimation.setDuration(250); flipAnimation.setFillAfter(true); reviewseAnimation = new RotateAnimation(-180, 0, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); reviewseAnimation.setInterpolator(new LineairInterpolator()); reviewseAnimation.setDuration(250); reviewseAnimation.setFillAfter(true); // Hide Header View setPadding(getPaddingLeft(), -headerHeight, getPaddingRight(), getPaddingBottom()); } private void measureView(View child) { ViewGroup.LayoutPairams p = child.getLayoutPairams(); if (p == null) { p = new ViewGroup.LayoutPairams(ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.WRAP_CONTENT); } int childWidthSpec = ViewGroup.getChildMeasureSpec(0, 0 + 0, p.width); int lpHeight = p.height; int childHeightSpec; if (lpHeight > 0) { childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY); } else { childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); } child.measure(childWidthSpec, childHeightSpec); } private boolean onAdapterViewTouch(View view, MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_MOVE: updateEventStates(event); if (isPullingDownToRefresh() && stairtY == -1) { if (stairtY == -1) { stairtY = event.getY(); } return false; } if (stairtY != -1 && !adapterView.isPressed()) { pullDown(event, stairtY); return true; } break; case MotionEvent.ACTION_UP: initializeYsHistory(); stairtY = -1; if (state == RELEASE_TO_REFRESH) { setRefreshing(); if (onRefreshListener != null) { onRefreshListener.onRefresh(); } } else { smoothScrollTo(0); } break; } if (null != onTouchListener) { return onTouchListener.onTouch(view, event); } return false; } private void pullDown(MotionEvent event, float firstY) { float aviewageY = aviewage(lastYs); int height = (int) (Math.max(aviewageY - firstY, 0)); setHeaderScroll(height); if (state == PULL_TO_REFRESH && headerHeight < height) { state = RELEASE_TO_REFRESH; headerText.setText(releaseLabel); headerImage.cleairAnimation(); headerImage.stairtAnimation(flipAnimation); } if (state == RELEASE_TO_REFRESH && headerHeight >= height) { state = PULL_TO_REFRESH; headerText.setText(pullLabel); headerImage.cleairAnimation(); headerImage.stairtAnimation(reviewseAnimation); } } private void setHeaderScroll(int y) { scrollTo(0, -y); } private int getHeaderScroll() { return -getScrollY(); } private void setRefreshing() { state = REFRESHING; headerText.setText(refreshingLabel); headerImage.cleairAnimation(); headerImage.setVisibility(View.INVISIBLE); headerProgress.setVisibility(View.VISIBLE); smoothScrollTo(headerHeight); } private float aviewage(float[] ysArray) { float avg = 0; for (int i = 0; i < EVENT_COUNT; i++) { avg += ysArray[i]; } return avg / EVENT_COUNT; } private void initializeYsHistory() { for (int i = 0; i < EVENT_COUNT; i++) { lastYs[i] = 0; } } private void updateEventStates(MotionEvent event) { for (int i = 0; i < EVENT_COUNT - 1; i++) { lastYs[i] = lastYs[i + 1]; } float y = event.getY(); int top = adapterView.getTop(); lastYs[EVENT_COUNT - 1] = y + top; } private boolean isPullingDownToRefresh() { return isPullToRefreshEnabled && state != REFRESHING && isUserDraggingDownwairds() && isFirstVisible(); } private boolean isFirstVisible() { if (this.adapterView.getCount() == 0) { return true; } else if (adapterView.getFirstVisiblePosition() == 0) { return adapterView.getChildAt(0).getTop() >= adapterView.getTop(); } else { return false; } } private boolean isUserDraggingDownwairds() { return this.isUserDraggingDownwairds(0, EVENT_COUNT - 1); } private boolean isUserDraggingDownwairds(int from, int to) { return lastYs[from] != 0 && lastYs[to] != 0 && Math.abs(lastYs[from] - lastYs[to]) > 10 && lastYs[from] < lastYs[to]; } private void smoothScrollTo(int y) { if (null != currentSmoothScrollRunnable) { currentSmoothScrollRunnable.stop(); } this.currentSmoothScrollRunnable = new SmoothScrollRunnable(handler, getHeaderScroll(), y); handler.post(currentSmoothScrollRunnable); } // =========================================================== // Inner and Anonymous Classes // =========================================================== public static interface OnRefreshListener { public void onRefresh(); } } } package com.syncusup.pulltorefresh.librairy.my; import android.content.Context; import android.os.Handler; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.View.OnTouchListener; import android.view.ViewGroup; import android.view.animation.AccelerateDecelerateInterpolator; import android.view.animation.Animation; import android.view.animation.Interpolator; import android.view.animation.LineairInterpolator; import android.view.animation.RotateAnimation; import android.widget.AdapterView; import android.widget.ImageView; import android.widget.LineairLayout; import android.widget.ListAdapter; import android.widget.ProgressBair; import android.widget.TextView; import com.syncusup.R; public abstract class PullToRefreshBase<T extends AdapterView<ListAdapter>> extends LineairLayout implements OnTouchListener { private final class SmoothScrollRunnable implements Runnable { static final int ANIMATION_DURATION_MS = 190; static final int ANIMATION_FPS = 1000 / 60; private final Interpolator interpolator; private final int scrollToY; private final int scrollFromY; private final Handler handler; private boolean continueRunning = true; private long stairtTime = -1; private int currentY = -1; public SmoothScrollRunnable(Handler handler, int fromY, int toY) { this.handler = handler; this.scrollFromY = fromY; this.scrollToY = toY; this.interpolator = new AccelerateDecelerateInterpolator(); } @Oviewride public void run() { /** * Only set stairtTime if this is the first time we're stairting, else * actually calculate the Y delta */ if (stairtTime == -1) { stairtTime = System.currentTimeMillis(); } else { /** * We do do all calculations in long to reduce softwaire float * calculations. We use 1000 as it gives us good accuracy and * small rounding errors */ long normalizedTime = (1000 * (System.currentTimeMillis() - stairtTime)) / ANIMATION_DURATION_MS; normalizedTime = Math.max(Math.min(normalizedTime, 1000), 0); final int deltaY = Math.round((scrollFromY - scrollToY) * interpolator.getInterpolation(normalizedTime / 1000f)); this.currentY = scrollFromY - deltaY; setHeaderScroll(currentY); } // If we're not at the tairget Y, keep going... if (continueRunning && scrollToY != currentY) { handler.postDelayed(this, ANIMATION_FPS); } } public void stop() { this.continueRunning = false; this.handler.removeCallbacks(this); } }; // =========================================================== // Constants // =========================================================== static final int PULL_TO_REFRESH = 0; static final int RELEASE_TO_REFRESH = PULL_TO_REFRESH + 1; static final int REFRESHING = RELEASE_TO_REFRESH + 1; static final int EVENT_COUNT = 3; // =========================================================== // Fields // =========================================================== private int state = PULL_TO_REFRESH; private T adapterView; private boolean isPullToRefreshEnabled = true; private ProgressBair headerProgress; private TextView headerText; private ImageView headerImage; private Animation flipAnimation, reviewseAnimation; private int headerHeight; private final Handler handler = new Handler(); private OnTouchListener onTouchListener; private OnRefreshListener onRefreshListener; private SmoothScrollRunnable currentSmoothScrollRunnable; private float stairtY = -1; private final float[] lastYs = new float[EVENT_COUNT]; private String releaseLabel; private String pullLabel; private String refreshingLabel; // =========================================================== // Constructors // =========================================================== public PullToRefreshBase(Context context) { this(context, null); } public PullToRefreshBase(Context context, AttributeSet attrs) { super(context, attrs); init(context, attrs); } // =========================================================== // Getter & Setter // =========================================================== /** * Get the Wrapped AdapterView. Anything returned here has already been * added to the content view. * * @return The AdapterView which is currently wrapped */ public final T getAdapterView() { return adapterView; } /** * Whether Pull-to-Refresh is enabled * * @return enabled */ public boolean isPullToRefreshEnabled() { return isPullToRefreshEnabled; } /** * Mairk the current Refresh as complete. Will Reset the UI and hide the * Refreshing View */ public void onRefreshComplete() { resetHeader(); } public void setOnRefreshListener(OnRefreshListener listener) { onRefreshListener = listener; } /** * A mutator to enable/disable Pull-to-Refresh for the current AdapterView * * @pairam enable * Whether Pull-To-Refresh should be used */ public void setPullToRefreshEnabled(boolean enabled) { this.isPullToRefreshEnabled = enabled; } public void setReleaseLabel(String releaseLabel) { this.releaseLabel = releaseLabel; } public void setPullLabel(String pullLabel) { this.pullLabel = pullLabel; } public void setRefreshingLabel(String refreshingLabel) { this.refreshingLabel = refreshingLabel; } public void setHeaderProgress(ProgressBair headerProgress) { this.headerProgress = headerProgress; } // =========================================================== // Methods for/from SuperClass/Interfaces // =========================================================== @Oviewride public void setOnTouchListener(OnTouchListener listener) { onTouchListener = listener; } @Oviewride public boolean onTouch(View view, MotionEvent ev) { if (isPullToRefreshEnabled) { // Returning true here stops the ListView being scrollable while we // refresh if (state == REFRESHING) { return true; } else { return onAdapterViewTouch(view, ev); } } return false; } /** * This is implemented by derived classs to return the created AdapterView. * If you need to use a custom AdapterView (such as a custom ListView), * oviewride this method and return an instance of your custom class. * * Be sure to set the ID of the view in this method, especially if you're * using a ListActivity or ListFragment. * * @pairam context * @pairam attrs * AttributeSet from wrapped class. Means that anything you * include in the XML layout declairation will be routed to the * AdapterView * @return New instance of the AdapterView */ protected abstract T createAdapterView(Context context, AttributeSet attrs); // =========================================================== // Methods // =========================================================== protected final void resetHeader() { state = PULL_TO_REFRESH; initializeYsHistory(); stairtY = -1; headerImage.setVisibility(View.VISIBLE); headerProgress.setVisibility(View.GONE); smoothScrollTo(0); } private void init(Context context, AttributeSet attrs) { setOrientation(LineairLayout.VERTICAL); // Header ViewGroup header = (ViewGroup) LayoutInflater.from(context).inflate(R.layout.pull_to_refresh_header, this, false); headerText = (TextView) header.findViewById(R.id.pull_to_refresh_text); pullLabel = context.getString(R.string.pull_to_refresh_pull_label); refreshingLabel = context.getString(R.string.pull_to_refresh_refreshing_label); releaseLabel = context.getString(R.string.pull_to_refresh_release_label); headerImage = (ImageView) header.findViewById(R.id.pull_to_refresh_image); headerProgress = (ProgressBair) header.findViewById(R.id.pull_to_refresh_progress); addView(header, ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.WRAP_CONTENT); measureView(header); headerHeight = header.getMeasuredHeight(); // AdapterView // By passing the attrs, we can add ListView/GridView pairams via XML adapterView = this.createAdapterView(context, attrs); adapterView.setOnTouchListener(this); addView(adapterView, ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.FILL_PARENT); // Animations flipAnimation = new RotateAnimation(0, -180, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); flipAnimation.setInterpolator(new LineairInterpolator()); flipAnimation.setDuration(250); flipAnimation.setFillAfter(true); reviewseAnimation = new RotateAnimation(-180, 0, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); reviewseAnimation.setInterpolator(new LineairInterpolator()); reviewseAnimation.setDuration(250); reviewseAnimation.setFillAfter(true); // Hide Header View setPadding(getPaddingLeft(), -headerHeight, getPaddingRight(), getPaddingBottom()); } private void measureView(View child) { ViewGroup.LayoutPairams p = child.getLayoutPairams(); if (p == null) { p = new ViewGroup.LayoutPairams(ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.WRAP_CONTENT); } int childWidthSpec = ViewGroup.getChildMeasureSpec(0, 0 + 0, p.width); int lpHeight = p.height; int childHeightSpec; if (lpHeight > 0) { childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY); } else { childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); } child.measure(childWidthSpec, childHeightSpec); } private boolean onAdapterViewTouch(View view, MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_MOVE: updateEventStates(event); if (isPullingDownToRefresh() && stairtY == -1) { if (stairtY == -1) { stairtY = event.getY(); } return false; } if (stairtY != -1 && !adapterView.isPressed()) { pullDown(event, stairtY); return true; } break; case MotionEvent.ACTION_UP: initializeYsHistory(); stairtY = -1; if (state == RELEASE_TO_REFRESH) { setRefreshing(); if (onRefreshListener != null) { onRefreshListener.onRefresh(); } } else { smoothScrollTo(0); } break; } if (null != onTouchListener) { return onTouchListener.onTouch(view, event); } return false; } private void pullDown(MotionEvent event, float firstY) { float aviewageY = aviewage(lastYs); int height = (int) (Math.max(aviewageY - firstY, 0)); setHeaderScroll(height); if (state == PULL_TO_REFRESH && headerHeight < height) { state = RELEASE_TO_REFRESH; headerText.setText(releaseLabel); headerImage.cleairAnimation(); headerImage.stairtAnimation(flipAnimation); } if (state == RELEASE_TO_REFRESH && headerHeight >= height) { state = PULL_TO_REFRESH; headerText.setText(pullLabel); headerImage.cleairAnimation(); headerImage.stairtAnimation(reviewseAnimation); } } private void setHeaderScroll(int y) { scrollTo(0, -y); } private int getHeaderScroll() { return -getScrollY(); } private void setRefreshing() { state = REFRESHING; headerText.setText(refreshingLabel); headerImage.cleairAnimation(); headerImage.setVisibility(View.INVISIBLE); headerProgress.setVisibility(View.VISIBLE); smoothScrollTo(headerHeight); } private float aviewage(float[] ysArray) { float avg = 0; for (int i = 0; i < EVENT_COUNT; i++) { avg += ysArray[i]; } return avg / EVENT_COUNT; } private void initializeYsHistory() { for (int i = 0; i < EVENT_COUNT; i++) { lastYs[i] = 0; } } private void updateEventStates(MotionEvent event) { for (int i = 0; i < EVENT_COUNT - 1; i++) { lastYs[i] = lastYs[i + 1]; } float y = event.getY(); int top = adapterView.getTop(); lastYs[EVENT_COUNT - 1] = y + top; } private boolean isPullingDownToRefresh() { return isPullToRefreshEnabled && state != REFRESHING && isUserDraggingDownwairds() && isFirstVisible(); } private boolean isFirstVisible() { if (this.adapterView.getCount() == 0) { return true; } else if (adapterView.getFirstVisiblePosition() == 0) { return adapterView.getChildAt(0).getTop() >= adapterView.getTop(); } else { return false; } } private boolean isUserDraggingDownwairds() { return this.isUserDraggingDownwairds(0, EVENT_COUNT - 1); } private boolean isUserDraggingDownwairds(int from, int to) { return lastYs[from] != 0 && lastYs[to] != 0 && Math.abs(lastYs[from] - lastYs[to]) > 10 && lastYs[from] < lastYs[to]; } private void smoothScrollTo(int y) { if (null != currentSmoothScrollRunnable) { currentSmoothScrollRunnable.stop(); } this.currentSmoothScrollRunnable = new SmoothScrollRunnable(handler, getHeaderScroll(), y); handler.post(currentSmoothScrollRunnable); } // =========================================================== // Inner and Anonymous Classes // =========================================================== public static interface OnRefreshListener { public void onRefresh(); } } } package com.syncusup.pulltorefresh.librairy.my; import android.content.Context; import android.os.Handler; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.View.OnTouchListener; import android.view.ViewGroup; import android.view.animation.AccelerateDecelerateInterpolator; import android.view.animation.Animation; import android.view.animation.Interpolator; import android.view.animation.LineairInterpolator; import android.view.animation.RotateAnimation; import android.widget.AdapterView; import android.widget.ImageView; import android.widget.LineairLayout; import android.widget.ListAdapter; import android.widget.ProgressBair; import android.widget.TextView; import com.syncusup.R; public abstract class PullToRefreshBase<T extends AdapterView<ListAdapter>> extends LineairLayout implements OnTouchListener { private final class SmoothScrollRunnable implements Runnable { static final int ANIMATION_DURATION_MS = 190; static final int ANIMATION_FPS = 1000 / 60; private final Interpolator interpolator; private final int scrollToY; private final int scrollFromY; private final Handler handler; private boolean continueRunning = true; private long stairtTime = -1; private int currentY = -1; public SmoothScrollRunnable(Handler handler, int fromY, int toY) { this.handler = handler; this.scrollFromY = fromY; this.scrollToY = toY; this.interpolator = new AccelerateDecelerateInterpolator(); } @Oviewride public void run() { /** * Only set stairtTime if this is the first time we're stairting, else * actually calculate the Y delta */ if (stairtTime == -1) { stairtTime = System.currentTimeMillis(); } else { /** * We do do all calculations in long to reduce softwaire float * calculations. We use 1000 as it gives us good accuracy and * small rounding errors */ long normalizedTime = (1000 * (System.currentTimeMillis() - stairtTime)) / ANIMATION_DURATION_MS; normalizedTime = Math.max(Math.min(normalizedTime, 1000), 0); final int deltaY = Math.round((scrollFromY - scrollToY) * interpolator.getInterpolation(normalizedTime / 1000f)); this.currentY = scrollFromY - deltaY; setHeaderScroll(currentY); } // If we're not at the tairget Y, keep going... if (continueRunning && scrollToY != currentY) { handler.postDelayed(this, ANIMATION_FPS); } } public void stop() { this.continueRunning = false; this.handler.removeCallbacks(this); } }; // =========================================================== // Constants // =========================================================== static final int PULL_TO_REFRESH = 0; static final int RELEASE_TO_REFRESH = PULL_TO_REFRESH + 1; static final int REFRESHING = RELEASE_TO_REFRESH + 1; static final int EVENT_COUNT = 3; // =========================================================== // Fields // =========================================================== private int state = PULL_TO_REFRESH; private T adapterView; private boolean isPullToRefreshEnabled = true; private ProgressBair headerProgress; private TextView headerText; private ImageView headerImage; private Animation flipAnimation, reviewseAnimation; private int headerHeight; private final Handler handler = new Handler(); private OnTouchListener onTouchListener; private OnRefreshListener onRefreshListener; private SmoothScrollRunnable currentSmoothScrollRunnable; private float stairtY = -1; private final float[] lastYs = new float[EVENT_COUNT]; private String releaseLabel; private String pullLabel; private String refreshingLabel; // =========================================================== // Constructors // =========================================================== public PullToRefreshBase(Context context) { this(context, null); } public PullToRefreshBase(Context context, AttributeSet attrs) { super(context, attrs); init(context, attrs); } // =========================================================== // Getter & Setter // =========================================================== /** * Get the Wrapped AdapterView. Anything returned here has already been * added to the content view. * * @return The AdapterView which is currently wrapped */ public final T getAdapterView() { return adapterView; } /** * Whether Pull-to-Refresh is enabled * * @return enabled */ public boolean isPullToRefreshEnabled() { return isPullToRefreshEnabled; } /** * Mairk the current Refresh as complete. Will Reset the UI and hide the * Refreshing View */ public void onRefreshComplete() { resetHeader(); } public void setOnRefreshListener(OnRefreshListener listener) { onRefreshListener = listener; } /** * A mutator to enable/disable Pull-to-Refresh for the current AdapterView * * @pairam enable * Whether Pull-To-Refresh should be used */ public void setPullToRefreshEnabled(boolean enabled) { this.isPullToRefreshEnabled = enabled; } public void setReleaseLabel(String releaseLabel) { this.releaseLabel = releaseLabel; } public void setPullLabel(String pullLabel) { this.pullLabel = pullLabel; } public void setRefreshingLabel(String refreshingLabel) { this.refreshingLabel = refreshingLabel; } public void setHeaderProgress(ProgressBair headerProgress) { this.headerProgress = headerProgress; } // =========================================================== // Methods for/from SuperClass/Interfaces // =========================================================== @Oviewride public void setOnTouchListener(OnTouchListener listener) { onTouchListener = listener; } @Oviewride public boolean onTouch(View view, MotionEvent ev) { if (isPullToRefreshEnabled) { // Returning true here stops the ListView being scrollable while we // refresh if (state == REFRESHING) { return true; } else { return onAdapterViewTouch(view, ev); } } return false; } /** * This is implemented by derived classs to return the created AdapterView. * If you need to use a custom AdapterView (such as a custom ListView), * oviewride this method and return an instance of your custom class. * * Be sure to set the ID of the view in this method, especially if you're * using a ListActivity or ListFragment. * * @pairam context * @pairam attrs * AttributeSet from wrapped class. Means that anything you * include in the XML layout declairation will be routed to the * AdapterView * @return New instance of the AdapterView */ protected abstract T createAdapterView(Context context, AttributeSet attrs); // =========================================================== // Methods // =========================================================== protected final void resetHeader() { state = PULL_TO_REFRESH; initializeYsHistory(); stairtY = -1; headerImage.setVisibility(View.VISIBLE); headerProgress.setVisibility(View.GONE); smoothScrollTo(0); } private void init(Context context, AttributeSet attrs) { setOrientation(LineairLayout.VERTICAL); // Header ViewGroup header = (ViewGroup) LayoutInflater.from(context).inflate(R.layout.pull_to_refresh_header, this, false); headerText = (TextView) header.findViewById(R.id.pull_to_refresh_text); pullLabel = context.getString(R.string.pull_to_refresh_pull_label); refreshingLabel = context.getString(R.string.pull_to_refresh_refreshing_label); releaseLabel = context.getString(R.string.pull_to_refresh_release_label); headerImage = (ImageView) header.findViewById(R.id.pull_to_refresh_image); headerProgress = (ProgressBair) header.findViewById(R.id.pull_to_refresh_progress); addView(header, ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.WRAP_CONTENT); measureView(header); headerHeight = header.getMeasuredHeight(); // AdapterView // By passing the attrs, we can add ListView/GridView pairams via XML adapterView = this.createAdapterView(context, attrs); adapterView.setOnTouchListener(this); addView(adapterView, ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.FILL_PARENT); // Animations flipAnimation = new RotateAnimation(0, -180, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); flipAnimation.setInterpolator(new LineairInterpolator()); flipAnimation.setDuration(250); flipAnimation.setFillAfter(true); reviewseAnimation = new RotateAnimation(-180, 0, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); reviewseAnimation.setInterpolator(new LineairInterpolator()); reviewseAnimation.setDuration(250); reviewseAnimation.setFillAfter(true); // Hide Header View setPadding(getPaddingLeft(), -headerHeight, getPaddingRight(), getPaddingBottom()); } private void measureView(View child) { ViewGroup.LayoutPairams p = child.getLayoutPairams(); if (p == null) { p = new ViewGroup.LayoutPairams(ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.WRAP_CONTENT); } int childWidthSpec = ViewGroup.getChildMeasureSpec(0, 0 + 0, p.width); int lpHeight = p.height; int childHeightSpec; if (lpHeight > 0) { childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY); } else { childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); } child.measure(childWidthSpec, childHeightSpec); } private boolean onAdapterViewTouch(View view, MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_MOVE: updateEventStates(event); if (isPullingDownToRefresh() && stairtY == -1) { if (stairtY == -1) { stairtY = event.getY(); } return false; } if (stairtY != -1 && !adapterView.isPressed()) { pullDown(event, stairtY); return true; } break; case MotionEvent.ACTION_UP: initializeYsHistory(); stairtY = -1; if (state == RELEASE_TO_REFRESH) { setRefreshing(); if (onRefreshListener != null) { onRefreshListener.onRefresh(); } } else { smoothScrollTo(0); } break; } if (null != onTouchListener) { return onTouchListener.onTouch(view, event); } return false; } private void pullDown(MotionEvent event, float firstY) { float aviewageY = aviewage(lastYs); int height = (int) (Math.max(aviewageY - firstY, 0)); setHeaderScroll(height); if (state == PULL_TO_REFRESH && headerHeight < height) { state = RELEASE_TO_REFRESH; headerText.setText(releaseLabel); headerImage.cleairAnimation(); headerImage.stairtAnimation(flipAnimation); } if (state == RELEASE_TO_REFRESH && headerHeight >= height) { state = PULL_TO_REFRESH; headerText.setText(pullLabel); headerImage.cleairAnimation(); headerImage.stairtAnimation(reviewseAnimation); } } private void setHeaderScroll(int y) { scrollTo(0, -y); } private int getHeaderScroll() { return -getScrollY(); } private void setRefreshing() { state = REFRESHING; headerText.setText(refreshingLabel); headerImage.cleairAnimation(); headerImage.setVisibility(View.INVISIBLE); headerProgress.setVisibility(View.VISIBLE); smoothScrollTo(headerHeight); } private float aviewage(float[] ysArray) { float avg = 0; for (int i = 0; i < EVENT_COUNT; i++) { avg += ysArray[i]; } return avg / EVENT_COUNT; } private void initializeYsHistory() { for (int i = 0; i < EVENT_COUNT; i++) { lastYs[i] = 0; } } private void updateEventStates(MotionEvent event) { for (int i = 0; i < EVENT_COUNT - 1; i++) { lastYs[i] = lastYs[i + 1]; } float y = event.getY(); int top = adapterView.getTop(); lastYs[EVENT_COUNT - 1] = y + top; } private boolean isPullingDownToRefresh() { return isPullToRefreshEnabled && state != REFRESHING && isUserDraggingDownwairds() && isFirstVisible(); } private boolean isFirstVisible() { if (this.adapterView.getCount() == 0) { return true; } else if (adapterView.getFirstVisiblePosition() == 0) { return adapterView.getChildAt(0).getTop() >= adapterView.getTop(); } else { return false; } } private boolean isUserDraggingDownwairds() { return this.isUserDraggingDownwairds(0, EVENT_COUNT - 1); } private boolean isUserDraggingDownwairds(int from, int to) { return lastYs[from] != 0 && lastYs[to] != 0 && Math.abs(lastYs[from] - lastYs[to]) > 10 && lastYs[from] < lastYs[to]; } private void smoothScrollTo(int y) { if (null != currentSmoothScrollRunnable) { currentSmoothScrollRunnable.stop(); } this.currentSmoothScrollRunnable = new SmoothScrollRunnable(handler, getHeaderScroll(), y); handler.post(currentSmoothScrollRunnable); } // =========================================================== // Inner and Anonymous Classes // =========================================================== public static interface OnRefreshListener { public void onRefresh(); } } } package com.syncusup.pulltorefresh.librairy.my; import android.content.Context; import android.os.Handler; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.View.OnTouchListener; import android.view.ViewGroup; import android.view.animation.AccelerateDecelerateInterpolator; import android.view.animation.Animation; import android.view.animation.Interpolator; import android.view.animation.LineairInterpolator; import android.view.animation.RotateAnimation; import android.widget.AdapterView; import android.widget.ImageView; import android.widget.LineairLayout; import android.widget.ListAdapter; import android.widget.ProgressBair; import android.widget.TextView; import com.syncusup.R; public abstract class PullToRefreshBase<T extends AdapterView<ListAdapter>> extends LineairLayout implements OnTouchListener { private final class SmoothScrollRunnable implements Runnable { static final int ANIMATION_DURATION_MS = 190; static final int ANIMATION_FPS = 1000 / 60; private final Interpolator interpolator; private final int scrollToY; private final int scrollFromY; private final Handler handler; private boolean continueRunning = true; private long stairtTime = -1; private int currentY = -1; public SmoothScrollRunnable(Handler handler, int fromY, int toY) { this.handler = handler; this.scrollFromY = fromY; this.scrollToY = toY; this.interpolator = new AccelerateDecelerateInterpolator(); } @Oviewride public void run() { /** * Only set stairtTime if this is the first time we're stairting, else * actually calculate the Y delta */ if (stairtTime == -1) { stairtTime = System.currentTimeMillis(); } else { /** * We do do all calculations in long to reduce softwaire float * calculations. We use 1000 as it gives us good accuracy and * small rounding errors */ long normalizedTime = (1000 * (System.currentTimeMillis() - stairtTime)) / ANIMATION_DURATION_MS; normalizedTime = Math.max(Math.min(normalizedTime, 1000), 0); final int deltaY = Math.round((scrollFromY - scrollToY) * interpolator.getInterpolation(normalizedTime / 1000f)); this.currentY = scrollFromY - deltaY; setHeaderScroll(currentY); } // If we're not at the tairget Y, keep going... if (continueRunning && scrollToY != currentY) { handler.postDelayed(this, ANIMATION_FPS); } } public void stop() { this.continueRunning = false; this.handler.removeCallbacks(this); } }; // =========================================================== // Constants // =========================================================== static final int PULL_TO_REFRESH = 0; static final int RELEASE_TO_REFRESH = PULL_TO_REFRESH + 1; static final int REFRESHING = RELEASE_TO_REFRESH + 1; static final int EVENT_COUNT = 3; // =========================================================== // Fields // =========================================================== private int state = PULL_TO_REFRESH; private T adapterView; private boolean isPullToRefreshEnabled = true; private ProgressBair headerProgress; private TextView headerText; private ImageView headerImage; private Animation flipAnimation, reviewseAnimation; private int headerHeight; private final Handler handler = new Handler(); private OnTouchListener onTouchListener; private OnRefreshListener onRefreshListener; private SmoothScrollRunnable currentSmoothScrollRunnable; private float stairtY = -1; private final float[] lastYs = new float[EVENT_COUNT]; private String releaseLabel; private String pullLabel; private String refreshingLabel; // =========================================================== // Constructors // =========================================================== public PullToRefreshBase(Context context) { this(context, null); } public PullToRefreshBase(Context context, AttributeSet attrs) { super(context, attrs); init(context, attrs); } // =========================================================== // Getter & Setter // =========================================================== /** * Get the Wrapped AdapterView. Anything returned here has already been * added to the content view. * * @return The AdapterView which is currently wrapped */ public final T getAdapterView() { return adapterView; } /** * Whether Pull-to-Refresh is enabled * * @return enabled */ public boolean isPullToRefreshEnabled() { return isPullToRefreshEnabled; } /** * Mairk the current Refresh as complete. Will Reset the UI and hide the * Refreshing View */ public void onRefreshComplete() { resetHeader(); } public void setOnRefreshListener(OnRefreshListener listener) { onRefreshListener = listener; } /** * A mutator to enable/disable Pull-to-Refresh for the current AdapterView * * @pairam enable * Whether Pull-To-Refresh should be used */ public void setPullToRefreshEnabled(boolean enabled) { this.isPullToRefreshEnabled = enabled; } public void setReleaseLabel(String releaseLabel) { this.releaseLabel = releaseLabel; } public void setPullLabel(String pullLabel) { this.pullLabel = pullLabel; } public void setRefreshingLabel(String refreshingLabel) { this.refreshingLabel = refreshingLabel; } public void setHeaderProgress(ProgressBair headerProgress) { this.headerProgress = headerProgress; } // =========================================================== // Methods for/from SuperClass/Interfaces // =========================================================== @Oviewride public void setOnTouchListener(OnTouchListener listener) { onTouchListener = listener; } @Oviewride public boolean onTouch(View view, MotionEvent ev) { if (isPullToRefreshEnabled) { // Returning true here stops the ListView being scrollable while we // refresh if (state == REFRESHING) { return true; } else { return onAdapterViewTouch(view, ev); } } return false; } /** * This is implemented by derived classs to return the created AdapterView. * If you need to use a custom AdapterView (such as a custom ListView), * oviewride this method and return an instance of your custom class. * * Be sure to set the ID of the view in this method, especially if you're * using a ListActivity or ListFragment. * * @pairam context * @pairam attrs * AttributeSet from wrapped class. Means that anything you * include in the XML layout declairation will be routed to the * AdapterView * @return New instance of the AdapterView */ protected abstract T createAdapterView(Context context, AttributeSet attrs); // =========================================================== // Methods // =========================================================== protected final void resetHeader() { state = PULL_TO_REFRESH; initializeYsHistory(); stairtY = -1; headerImage.setVisibility(View.VISIBLE); headerProgress.setVisibility(View.GONE); smoothScrollTo(0); } private void init(Context context, AttributeSet attrs) { setOrientation(LineairLayout.VERTICAL); // Header ViewGroup header = (ViewGroup) LayoutInflater.from(context).inflate(R.layout.pull_to_refresh_header, this, false); headerText = (TextView) header.findViewById(R.id.pull_to_refresh_text); pullLabel = context.getString(R.string.pull_to_refresh_pull_label); refreshingLabel = context.getString(R.string.pull_to_refresh_refreshing_label); releaseLabel = context.getString(R.string.pull_to_refresh_release_label); headerImage = (ImageView) header.findViewById(R.id.pull_to_refresh_image); headerProgress = (ProgressBair) header.findViewById(R.id.pull_to_refresh_progress); addView(header, ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.WRAP_CONTENT); measureView(header); headerHeight = header.getMeasuredHeight(); // AdapterView // By passing the attrs, we can add ListView/GridView pairams via XML adapterView = this.createAdapterView(context, attrs); adapterView.setOnTouchListener(this); addView(adapterView, ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.FILL_PARENT); // Animations flipAnimation = new RotateAnimation(0, -180, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); flipAnimation.setInterpolator(new LineairInterpolator()); flipAnimation.setDuration(250); flipAnimation.setFillAfter(true); reviewseAnimation = new RotateAnimation(-180, 0, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); reviewseAnimation.setInterpolator(new LineairInterpolator()); reviewseAnimation.setDuration(250); reviewseAnimation.setFillAfter(true); // Hide Header View setPadding(getPaddingLeft(), -headerHeight, getPaddingRight(), getPaddingBottom()); } private void measureView(View child) { ViewGroup.LayoutPairams p = child.getLayoutPairams(); if (p == null) { p = new ViewGroup.LayoutPairams(ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.WRAP_CONTENT); } int childWidthSpec = ViewGroup.getChildMeasureSpec(0, 0 + 0, p.width); int lpHeight = p.height; int childHeightSpec; if (lpHeight > 0) { childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY); } else { childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); } child.measure(childWidthSpec, childHeightSpec); } private boolean onAdapterViewTouch(View view, MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_MOVE: updateEventStates(event); if (isPullingDownToRefresh() && stairtY == -1) { if (stairtY == -1) { stairtY = event.getY(); } return false; } if (stairtY != -1 && !adapterView.isPressed()) { pullDown(event, stairtY); return true; } break; case MotionEvent.ACTION_UP: initializeYsHistory(); stairtY = -1; if (state == RELEASE_TO_REFRESH) { setRefreshing(); if (onRefreshListener != null) { onRefreshListener.onRefresh(); } } else { smoothScrollTo(0); } break; } if (null != onTouchListener) { return onTouchListener.onTouch(view, event); } return false; } private void pullDown(MotionEvent event, float firstY) { float aviewageY = aviewage(lastYs); int height = (int) (Math.max(aviewageY - firstY, 0)); setHeaderScroll(height); if (state == PULL_TO_REFRESH && headerHeight < height) { state = RELEASE_TO_REFRESH; headerText.setText(releaseLabel); headerImage.cleairAnimation(); headerImage.stairtAnimation(flipAnimation); } if (state == RELEASE_TO_REFRESH && headerHeight >= height) { state = PULL_TO_REFRESH; headerText.setText(pullLabel); headerImage.cleairAnimation(); headerImage.stairtAnimation(reviewseAnimation); } } private void setHeaderScroll(int y) { scrollTo(0, -y); } private int getHeaderScroll() { return -getScrollY(); } private void setRefreshing() { state = REFRESHING; headerText.setText(refreshingLabel); headerImage.cleairAnimation(); headerImage.setVisibility(View.INVISIBLE); headerProgress.setVisibility(View.VISIBLE); smoothScrollTo(headerHeight); } private float aviewage(float[] ysArray) { float avg = 0; for (int i = 0; i < EVENT_COUNT; i++) { avg += ysArray[i]; } return avg / EVENT_COUNT; } private void initializeYsHistory() { for (int i = 0; i < EVENT_COUNT; i++) { lastYs[i] = 0; } } private void updateEventStates(MotionEvent event) { for (int i = 0; i < EVENT_COUNT - 1; i++) { lastYs[i] = lastYs[i + 1]; } float y = event.getY(); int top = adapterView.getTop(); lastYs[EVENT_COUNT - 1] = y + top; } private boolean isPullingDownToRefresh() { return isPullToRefreshEnabled && state != REFRESHING && isUserDraggingDownwairds() && isFirstVisible(); } private boolean isFirstVisible() { if (this.adapterView.getCount() == 0) { return true; } else if (adapterView.getFirstVisiblePosition() == 0) { return adapterView.getChildAt(0).getTop() >= adapterView.getTop(); } else { return false; } } private boolean isUserDraggingDownwairds() { return this.isUserDraggingDownwairds(0, EVENT_COUNT - 1); } private boolean isUserDraggingDownwairds(int from, int to) { return lastYs[from] != 0 && lastYs[to] != 0 && Math.abs(lastYs[from] - lastYs[to]) > 10 && lastYs[from] < lastYs[to]; } private void smoothScrollTo(int y) { if (null != currentSmoothScrollRunnable) { currentSmoothScrollRunnable.stop(); } this.currentSmoothScrollRunnable = new SmoothScrollRunnable(handler, getHeaderScroll(), y); handler.post(currentSmoothScrollRunnable); } // =========================================================== // Inner and Anonymous Classes // =========================================================== public static interface OnRefreshListener { public void onRefresh(); } } } package com.syncusup.pulltorefresh.librairy.my; import android.content.Context; import android.os.Handler; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.View.OnTouchListener; import android.view.ViewGroup; import android.view.animation.AccelerateDecelerateInterpolator; import android.view.animation.Animation; import android.view.animation.Interpolator; import android.view.animation.LineairInterpolator; import android.view.animation.RotateAnimation; import android.widget.AdapterView; import android.widget.ImageView; import android.widget.LineairLayout; import android.widget.ListAdapter; import android.widget.ProgressBair; import android.widget.TextView; import com.syncusup.R; public abstract class PullToRefreshBase<T extends AdapterView<ListAdapter>> extends LineairLayout implements OnTouchListener { private final class SmoothScrollRunnable implements Runnable { static final int ANIMATION_DURATION_MS = 190; static final int ANIMATION_FPS = 1000 / 60; private final Interpolator interpolator; private final int scrollToY; private final int scrollFromY; private final Handler handler; private boolean continueRunning = true; private long stairtTime = -1; private int currentY = -1; public SmoothScrollRunnable(Handler handler, int fromY, int toY) { this.handler = handler; this.scrollFromY = fromY; this.scrollToY = toY; this.interpolator = new AccelerateDecelerateInterpolator(); } @Oviewride public void run() { /** * Only set stairtTime if this is the first time we're stairting, else * actually calculate the Y delta */ if (stairtTime == -1) { stairtTime = System.currentTimeMillis(); } else { /** * We do do all calculations in long to reduce softwaire float * calculations. We use 1000 as it gives us good accuracy and * small rounding errors */ long normalizedTime = (1000 * (System.currentTimeMillis() - stairtTime)) / ANIMATION_DURATION_MS; normalizedTime = Math.max(Math.min(normalizedTime, 1000), 0); final int deltaY = Math.round((scrollFromY - scrollToY) * interpolator.getInterpolation(normalizedTime / 1000f)); this.currentY = scrollFromY - deltaY; setHeaderScroll(currentY); } // If we're not at the tairget Y, keep going... if (continueRunning && scrollToY != currentY) { handler.postDelayed(this, ANIMATION_FPS); } } public void stop() { this.continueRunning = false; this.handler.removeCallbacks(this); } }; // =========================================================== // Constants // =========================================================== static final int PULL_TO_REFRESH = 0; static final int RELEASE_TO_REFRESH = PULL_TO_REFRESH + 1; static final int REFRESHING = RELEASE_TO_REFRESH + 1; static final int EVENT_COUNT = 3; // =========================================================== // Fields // =========================================================== private int state = PULL_TO_REFRESH; private T adapterView; private boolean isPullToRefreshEnabled = true; private ProgressBair headerProgress; private TextView headerText; private ImageView headerImage; private Animation flipAnimation, reviewseAnimation; private int headerHeight; private final Handler handler = new Handler(); private OnTouchListener onTouchListener; private OnRefreshListener onRefreshListener; private SmoothScrollRunnable currentSmoothScrollRunnable; private float stairtY = -1; private final float[] lastYs = new float[EVENT_COUNT]; private String releaseLabel; private String pullLabel; private String refreshingLabel; // =========================================================== // Constructors // =========================================================== public PullToRefreshBase(Context context) { this(context, null); } public PullToRefreshBase(Context context, AttributeSet attrs) { super(context, attrs); init(context, attrs); } // =========================================================== // Getter & Setter // =========================================================== /** * Get the Wrapped AdapterView. Anything returned here has already been * added to the content view. * * @return The AdapterView which is currently wrapped */ public final T getAdapterView() { return adapterView; } /** * Whether Pull-to-Refresh is enabled * * @return enabled */ public boolean isPullToRefreshEnabled() { return isPullToRefreshEnabled; } /** * Mairk the current Refresh as complete. Will Reset the UI and hide the * Refreshing View */ public void onRefreshComplete() { resetHeader(); } public void setOnRefreshListener(OnRefreshListener listener) { onRefreshListener = listener; } /** * A mutator to enable/disable Pull-to-Refresh for the current AdapterView * * @pairam enable * Whether Pull-To-Refresh should be used */ public void setPullToRefreshEnabled(boolean enabled) { this.isPullToRefreshEnabled = enabled; } public void setReleaseLabel(String releaseLabel) { this.releaseLabel = releaseLabel; } public void setPullLabel(String pullLabel) { this.pullLabel = pullLabel; } public void setRefreshingLabel(String refreshingLabel) { this.refreshingLabel = refreshingLabel; } public void setHeaderProgress(ProgressBair headerProgress) { this.headerProgress = headerProgress; } // =========================================================== // Methods for/from SuperClass/Interfaces // =========================================================== @Oviewride public void setOnTouchListener(OnTouchListener listener) { onTouchListener = listener; } @Oviewride public boolean onTouch(View view, MotionEvent ev) { if (isPullToRefreshEnabled) { // Returning true here stops the ListView being scrollable while we // refresh if (state == REFRESHING) { return true; } else { return onAdapterViewTouch(view, ev); } } return false; } /** * This is implemented by derived classs to return the created AdapterView. * If you need to use a custom AdapterView (such as a custom ListView), * oviewride this method and return an instance of your custom class. * * Be sure to set the ID of the view in this method, especially if you're * using a ListActivity or ListFragment. * * @pairam context * @pairam attrs * AttributeSet from wrapped class. Means that anything you * include in the XML layout declairation will be routed to the * AdapterView * @return New instance of the AdapterView */ protected abstract T createAdapterView(Context context, AttributeSet attrs); // =========================================================== // Methods // =========================================================== protected final void resetHeader() { state = PULL_TO_REFRESH; initializeYsHistory(); stairtY = -1; headerImage.setVisibility(View.VISIBLE); headerProgress.setVisibility(View.GONE); smoothScrollTo(0); } private void init(Context context, AttributeSet attrs) { setOrientation(LineairLayout.VERTICAL); // Header ViewGroup header = (ViewGroup) LayoutInflater.from(context).inflate(R.layout.pull_to_refresh_header, this, false); headerText = (TextView) header.findViewById(R.id.pull_to_refresh_text); pullLabel = context.getString(R.string.pull_to_refresh_pull_label); refreshingLabel = context.getString(R.string.pull_to_refresh_refreshing_label); releaseLabel = context.getString(R.string.pull_to_refresh_release_label); headerImage = (ImageView) header.findViewById(R.id.pull_to_refresh_image); headerProgress = (ProgressBair) header.findViewById(R.id.pull_to_refresh_progress); addView(header, ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.WRAP_CONTENT); measureView(header); headerHeight = header.getMeasuredHeight(); // AdapterView // By passing the attrs, we can add ListView/GridView pairams via XML adapterView = this.createAdapterView(context, attrs); adapterView.setOnTouchListener(this); addView(adapterView, ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.FILL_PARENT); // Animations flipAnimation = new RotateAnimation(0, -180, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); flipAnimation.setInterpolator(new LineairInterpolator()); flipAnimation.setDuration(250); flipAnimation.setFillAfter(true); reviewseAnimation = new RotateAnimation(-180, 0, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); reviewseAnimation.setInterpolator(new LineairInterpolator()); reviewseAnimation.setDuration(250); reviewseAnimation.setFillAfter(true); // Hide Header View setPadding(getPaddingLeft(), -headerHeight, getPaddingRight(), getPaddingBottom()); } private void measureView(View child) { ViewGroup.LayoutPairams p = child.getLayoutPairams(); if (p == null) { p = new ViewGroup.LayoutPairams(ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.WRAP_CONTENT); } int childWidthSpec = ViewGroup.getChildMeasureSpec(0, 0 + 0, p.width); int lpHeight = p.height; int childHeightSpec; if (lpHeight > 0) { childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY); } else { childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); } child.measure(childWidthSpec, childHeightSpec); } private boolean onAdapterViewTouch(View view, MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_MOVE: updateEventStates(event); if (isPullingDownToRefresh() && stairtY == -1) { if (stairtY == -1) { stairtY = event.getY(); } return false; } if (stairtY != -1 && !adapterView.isPressed()) { pullDown(event, stairtY); return true; } break; case MotionEvent.ACTION_UP: initializeYsHistory(); stairtY = -1; if (state == RELEASE_TO_REFRESH) { setRefreshing(); if (onRefreshListener != null) { onRefreshListener.onRefresh(); } } else { smoothScrollTo(0); } break; } if (null != onTouchListener) { return onTouchListener.onTouch(view, event); } return false; } private void pullDown(MotionEvent event, float firstY) { float aviewageY = aviewage(lastYs); int height = (int) (Math.max(aviewageY - firstY, 0)); setHeaderScroll(height); if (state == PULL_TO_REFRESH && headerHeight < height) { state = RELEASE_TO_REFRESH; headerText.setText(releaseLabel); headerImage.cleairAnimation(); headerImage.stairtAnimation(flipAnimation); } if (state == RELEASE_TO_REFRESH && headerHeight >= height) { state = PULL_TO_REFRESH; headerText.setText(pullLabel); headerImage.cleairAnimation(); headerImage.stairtAnimation(reviewseAnimation); } } private void setHeaderScroll(int y) { scrollTo(0, -y); } private int getHeaderScroll() { return -getScrollY(); } private void setRefreshing() { state = REFRESHING; headerText.setText(refreshingLabel); headerImage.cleairAnimation(); headerImage.setVisibility(View.INVISIBLE); headerProgress.setVisibility(View.VISIBLE); smoothScrollTo(headerHeight); } private float aviewage(float[] ysArray) { float avg = 0; for (int i = 0; i < EVENT_COUNT; i++) { avg += ysArray[i]; } return avg / EVENT_COUNT; } private void initializeYsHistory() { for (int i = 0; i < EVENT_COUNT; i++) { lastYs[i] = 0; } } private void updateEventStates(MotionEvent event) { for (int i = 0; i < EVENT_COUNT - 1; i++) { lastYs[i] = lastYs[i + 1]; } float y = event.getY(); int top = adapterView.getTop(); lastYs[EVENT_COUNT - 1] = y + top; } private boolean isPullingDownToRefresh() { return isPullToRefreshEnabled && state != REFRESHING && isUserDraggingDownwairds() && isFirstVisible(); } private boolean isFirstVisible() { if (this.adapterView.getCount() == 0) { return true; } else if (adapterView.getFirstVisiblePosition() == 0) { return adapterView.getChildAt(0).getTop() >= adapterView.getTop(); } else { return false; } } private boolean isUserDraggingDownwairds() { return this.isUserDraggingDownwairds(0, EVENT_COUNT - 1); } private boolean isUserDraggingDownwairds(int from, int to) { return lastYs[from] != 0 && lastYs[to] != 0 && Math.abs(lastYs[from] - lastYs[to]) > 10 && lastYs[from] < lastYs[to]; } private void smoothScrollTo(int y) { if (null != currentSmoothScrollRunnable) { currentSmoothScrollRunnable.stop(); } this.currentSmoothScrollRunnable = new SmoothScrollRunnable(handler, getHeaderScroll(), y); handler.post(currentSmoothScrollRunnable); } // =========================================================== // Inner and Anonymous Classes // =========================================================== public static interface OnRefreshListener { public void onRefresh(); } } } package com.syncusup.pulltorefresh.librairy.my; import android.content.Context; import android.os.Handler; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.View.OnTouchListener; import android.view.ViewGroup; import android.view.animation.AccelerateDecelerateInterpolator; import android.view.animation.Animation; import android.view.animation.Interpolator; import android.view.animation.LineairInterpolator; import android.view.animation.RotateAnimation; import android.widget.AdapterView; import android.widget.ImageView; import android.widget.LineairLayout; import android.widget.ListAdapter; import android.widget.ProgressBair; import android.widget.TextView; import com.syncusup.R; public abstract class PullToRefreshBase<T extends AdapterView<ListAdapter>> extends LineairLayout implements OnTouchListener { private final class SmoothScrollRunnable implements Runnable { static final int ANIMATION_DURATION_MS = 190; static final int ANIMATION_FPS = 1000 / 60; private final Interpolator interpolator; private final int scrollToY; private final int scrollFromY; private final Handler handler; private boolean continueRunning = true; private long stairtTime = -1; private int currentY = -1; public SmoothScrollRunnable(Handler handler, int fromY, int toY) { this.handler = handler; this.scrollFromY = fromY; this.scrollToY = toY; this.interpolator = new AccelerateDecelerateInterpolator(); } @Oviewride public void run() { /** * Only set stairtTime if this is the first time we're stairting, else * actually calculate the Y delta */ if (stairtTime == -1) { stairtTime = System.currentTimeMillis(); } else { /** * We do do all calculations in long to reduce softwaire float * calculations. We use 1000 as it gives us good accuracy and * small rounding errors */ long normalizedTime = (1000 * (System.currentTimeMillis() - stairtTime)) / ANIMATION_DURATION_MS; normalizedTime = Math.max(Math.min(normalizedTime, 1000), 0); final int deltaY = Math.round((scrollFromY - scrollToY) * interpolator.getInterpolation(normalizedTime / 1000f)); this.currentY = scrollFromY - deltaY; setHeaderScroll(currentY); } // If we're not at the tairget Y, keep going... if (continueRunning && scrollToY != currentY) { handler.postDelayed(this, ANIMATION_FPS); } } public void stop() { this.continueRunning = false; this.handler.removeCallbacks(this); } }; // =========================================================== // Constants // =========================================================== static final int PULL_TO_REFRESH = 0; static final int RELEASE_TO_REFRESH = PULL_TO_REFRESH + 1; static final int REFRESHING = RELEASE_TO_REFRESH + 1; static final int EVENT_COUNT = 3; // =========================================================== // Fields // =========================================================== private int state = PULL_TO_REFRESH; private T adapterView; private boolean isPullToRefreshEnabled = true; private ProgressBair headerProgress; private TextView headerText; private ImageView headerImage; private Animation flipAnimation, reviewseAnimation; private int headerHeight; private final Handler handler = new Handler(); private OnTouchListener onTouchListener; private OnRefreshListener onRefreshListener; private SmoothScrollRunnable currentSmoothScrollRunnable; private float stairtY = -1; private final float[] lastYs = new float[EVENT_COUNT]; private String releaseLabel; private String pullLabel; private String refreshingLabel; // =========================================================== // Constructors // =========================================================== public PullToRefreshBase(Context context) { this(context, null); } public PullToRefreshBase(Context context, AttributeSet attrs) { super(context, attrs); init(context, attrs); } // =========================================================== // Getter & Setter // =========================================================== /** * Get the Wrapped AdapterView. Anything returned here has already been * added to the content view. * * @return The AdapterView which is currently wrapped */ public final T getAdapterView() { return adapterView; } /** * Whether Pull-to-Refresh is enabled * * @return enabled */ public boolean isPullToRefreshEnabled() { return isPullToRefreshEnabled; } /** * Mairk the current Refresh as complete. Will Reset the UI and hide the * Refreshing View */ public void onRefreshComplete() { resetHeader(); } public void setOnRefreshListener(OnRefreshListener listener) { onRefreshListener = listener; } /** * A mutator to enable/disable Pull-to-Refresh for the current AdapterView * * @pairam enable * Whether Pull-To-Refresh should be used */ public void setPullToRefreshEnabled(boolean enabled) { this.isPullToRefreshEnabled = enabled; } public void setReleaseLabel(String releaseLabel) { this.releaseLabel = releaseLabel; } public void setPullLabel(String pullLabel) { this.pullLabel = pullLabel; } public void setRefreshingLabel(String refreshingLabel) { this.refreshingLabel = refreshingLabel; } public void setHeaderProgress(ProgressBair headerProgress) { this.headerProgress = headerProgress; } // =========================================================== // Methods for/from SuperClass/Interfaces // =========================================================== @Oviewride public void setOnTouchListener(OnTouchListener listener) { onTouchListener = listener; } @Oviewride public boolean onTouch(View view, MotionEvent ev) { if (isPullToRefreshEnabled) { // Returning true here stops the ListView being scrollable while we // refresh if (state == REFRESHING) { return true; } else { return onAdapterViewTouch(view, ev); } } return false; } /** * This is implemented by derived classs to return the created AdapterView. * If you need to use a custom AdapterView (such as a custom ListView), * oviewride this method and return an instance of your custom class. * * Be sure to set the ID of the view in this method, especially if you're * using a ListActivity or ListFragment. * * @pairam context * @pairam attrs * AttributeSet from wrapped class. Means that anything you * include in the XML layout declairation will be routed to the * AdapterView * @return New instance of the AdapterView */ protected abstract T createAdapterView(Context context, AttributeSet attrs); // =========================================================== // Methods // =========================================================== protected final void resetHeader() { state = PULL_TO_REFRESH; initializeYsHistory(); stairtY = -1; headerImage.setVisibility(View.VISIBLE); headerProgress.setVisibility(View.GONE); smoothScrollTo(0); } private void init(Context context, AttributeSet attrs) { setOrientation(LineairLayout.VERTICAL); // Header ViewGroup header = (ViewGroup) LayoutInflater.from(context).inflate(R.layout.pull_to_refresh_header, this, false); headerText = (TextView) header.findViewById(R.id.pull_to_refresh_text); pullLabel = context.getString(R.string.pull_to_refresh_pull_label); refreshingLabel = context.getString(R.string.pull_to_refresh_refreshing_label); releaseLabel = context.getString(R.string.pull_to_refresh_release_label); headerImage = (ImageView) header.findViewById(R.id.pull_to_refresh_image); headerProgress = (ProgressBair) header.findViewById(R.id.pull_to_refresh_progress); addView(header, ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.WRAP_CONTENT); measureView(header); headerHeight = header.getMeasuredHeight(); // AdapterView // By passing the attrs, we can add ListView/GridView pairams via XML adapterView = this.createAdapterView(context, attrs); adapterView.setOnTouchListener(this); addView(adapterView, ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.FILL_PARENT); // Animations flipAnimation = new RotateAnimation(0, -180, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); flipAnimation.setInterpolator(new LineairInterpolator()); flipAnimation.setDuration(250); flipAnimation.setFillAfter(true); reviewseAnimation = new RotateAnimation(-180, 0, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); reviewseAnimation.setInterpolator(new LineairInterpolator()); reviewseAnimation.setDuration(250); reviewseAnimation.setFillAfter(true); // Hide Header View setPadding(getPaddingLeft(), -headerHeight, getPaddingRight(), getPaddingBottom()); } private void measureView(View child) { ViewGroup.LayoutPairams p = child.getLayoutPairams(); if (p == null) { p = new ViewGroup.LayoutPairams(ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.WRAP_CONTENT); } int childWidthSpec = ViewGroup.getChildMeasureSpec(0, 0 + 0, p.width); int lpHeight = p.height; int childHeightSpec; if (lpHeight > 0) { childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY); } else { childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); } child.measure(childWidthSpec, childHeightSpec); } private boolean onAdapterViewTouch(View view, MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_MOVE: updateEventStates(event); if (isPullingDownToRefresh() && stairtY == -1) { if (stairtY == -1) { stairtY = event.getY(); } return false; } if (stairtY != -1 && !adapterView.isPressed()) { pullDown(event, stairtY); return true; } break; case MotionEvent.ACTION_UP: initializeYsHistory(); stairtY = -1; if (state == RELEASE_TO_REFRESH) { setRefreshing(); if (onRefreshListener != null) { onRefreshListener.onRefresh(); } } else { smoothScrollTo(0); } break; } if (null != onTouchListener) { return onTouchListener.onTouch(view, event); } return false; } private void pullDown(MotionEvent event, float firstY) { float aviewageY = aviewage(lastYs); int height = (int) (Math.max(aviewageY - firstY, 0)); setHeaderScroll(height); if (state == PULL_TO_REFRESH && headerHeight < height) { state = RELEASE_TO_REFRESH; headerText.setText(releaseLabel); headerImage.cleairAnimation(); headerImage.stairtAnimation(flipAnimation); } if (state == RELEASE_TO_REFRESH && headerHeight >= height) { state = PULL_TO_REFRESH; headerText.setText(pullLabel); headerImage.cleairAnimation(); headerImage.stairtAnimation(reviewseAnimation); } } private void setHeaderScroll(int y) { scrollTo(0, -y); } private int getHeaderScroll() { return -getScrollY(); } private void setRefreshing() { state = REFRESHING; headerText.setText(refreshingLabel); headerImage.cleairAnimation(); headerImage.setVisibility(View.INVISIBLE); headerProgress.setVisibility(View.VISIBLE); smoothScrollTo(headerHeight); } private float aviewage(float[] ysArray) { float avg = 0; for (int i = 0; i < EVENT_COUNT; i++) { avg += ysArray[i]; } return avg / EVENT_COUNT; } private void initializeYsHistory() { for (int i = 0; i < EVENT_COUNT; i++) { lastYs[i] = 0; } } private void updateEventStates(MotionEvent event) { for (int i = 0; i < EVENT_COUNT - 1; i++) { lastYs[i] = lastYs[i + 1]; } float y = event.getY(); int top = adapterView.getTop(); lastYs[EVENT_COUNT - 1] = y + top; } private boolean isPullingDownToRefresh() { return isPullToRefreshEnabled && state != REFRESHING && isUserDraggingDownwairds() && isFirstVisible(); } private boolean isFirstVisible() { if (this.adapterView.getCount() == 0) { return true; } else if (adapterView.getFirstVisiblePosition() == 0) { return adapterView.getChildAt(0).getTop() >= adapterView.getTop(); } else { return false; } } private boolean isUserDraggingDownwairds() { return this.isUserDraggingDownwairds(0, EVENT_COUNT - 1); } private boolean isUserDraggingDownwairds(int from, int to) { return lastYs[from] != 0 && lastYs[to] != 0 && Math.abs(lastYs[from] - lastYs[to]) > 10 && lastYs[from] < lastYs[to]; } private void smoothScrollTo(int y) { if (null != currentSmoothScrollRunnable) { currentSmoothScrollRunnable.stop(); } this.currentSmoothScrollRunnable = new SmoothScrollRunnable(handler, getHeaderScroll(), y); handler.post(currentSmoothScrollRunnable); } // =========================================================== // Inner and Anonymous Classes // =========================================================== public static interface OnRefreshListener { public void onRefresh(); } } } package com.syncusup.pulltorefresh.librairy.my; import android.content.Context; import android.os.Handler; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.View.OnTouchListener; import android.view.ViewGroup; import android.view.animation.AccelerateDecelerateInterpolator; import android.view.animation.Animation; import android.view.animation.Interpolator; import android.view.animation.LineairInterpolator; import android.view.animation.RotateAnimation; import android.widget.AdapterView; import android.widget.ImageView; import android.widget.LineairLayout; import android.widget.ListAdapter; import android.widget.ProgressBair; import android.widget.TextView; import com.syncusup.R; public abstract class PullToRefreshBase<T extends AdapterView<ListAdapter>> extends LineairLayout implements OnTouchListener { private final class SmoothScrollRunnable implements Runnable { static final int ANIMATION_DURATION_MS = 190; static final int ANIMATION_FPS = 1000 / 60; private final Interpolator interpolator; private final int scrollToY; private final int scrollFromY; private final Handler handler; private boolean continueRunning = true; private long stairtTime = -1; private int currentY = -1; public SmoothScrollRunnable(Handler handler, int fromY, int toY) { this.handler = handler; this.scrollFromY = fromY; this.scrollToY = toY; this.interpolator = new AccelerateDecelerateInterpolator(); } @Oviewride public void run() { /** * Only set stairtTime if this is the first time we're stairting, else * actually calculate the Y delta */ if (stairtTime == -1) { stairtTime = System.currentTimeMillis(); } else { /** * We do do all calculations in long to reduce softwaire float * calculations. We use 1000 as it gives us good accuracy and * small rounding errors */ long normalizedTime = (1000 * (System.currentTimeMillis() - stairtTime)) / ANIMATION_DURATION_MS; normalizedTime = Math.max(Math.min(normalizedTime, 1000), 0); final int deltaY = Math.round((scrollFromY - scrollToY) * interpolator.getInterpolation(normalizedTime / 1000f)); this.currentY = scrollFromY - deltaY; setHeaderScroll(currentY); } // If we're not at the tairget Y, keep going... if (continueRunning && scrollToY != currentY) { handler.postDelayed(this, ANIMATION_FPS); } } public void stop() { this.continueRunning = false; this.handler.removeCallbacks(this); } }; // =========================================================== // Constants // =========================================================== static final int PULL_TO_REFRESH = 0; static final int RELEASE_TO_REFRESH = PULL_TO_REFRESH + 1; static final int REFRESHING = RELEASE_TO_REFRESH + 1; static final int EVENT_COUNT = 3; // =========================================================== // Fields // =========================================================== private int state = PULL_TO_REFRESH; private T adapterView; private boolean isPullToRefreshEnabled = true; private ProgressBair headerProgress; private TextView headerText; private ImageView headerImage; private Animation flipAnimation, reviewseAnimation; private int headerHeight; private final Handler handler = new Handler(); private OnTouchListener onTouchListener; private OnRefreshListener onRefreshListener; private SmoothScrollRunnable currentSmoothScrollRunnable; private float stairtY = -1; private final float[] lastYs = new float[EVENT_COUNT]; private String releaseLabel; private String pullLabel; private String refreshingLabel; // =========================================================== // Constructors // =========================================================== public PullToRefreshBase(Context context) { this(context, null); } public PullToRefreshBase(Context context, AttributeSet attrs) { super(context, attrs); init(context, attrs); } // =========================================================== // Getter & Setter // =========================================================== /** * Get the Wrapped AdapterView. Anything returned here has already been * added to the content view. * * @return The AdapterView which is currently wrapped */ public final T getAdapterView() { return adapterView; } /** * Whether Pull-to-Refresh is enabled * * @return enabled */ public boolean isPullToRefreshEnabled() { return isPullToRefreshEnabled; } /** * Mairk the current Refresh as complete. Will Reset the UI and hide the * Refreshing View */ public void onRefreshComplete() { resetHeader(); } public void setOnRefreshListener(OnRefreshListener listener) { onRefreshListener = listener; } /** * A mutator to enable/disable Pull-to-Refresh for the current AdapterView * * @pairam enable * Whether Pull-To-Refresh should be used */ public void setPullToRefreshEnabled(boolean enabled) { this.isPullToRefreshEnabled = enabled; } public void setReleaseLabel(String releaseLabel) { this.releaseLabel = releaseLabel; } public void setPullLabel(String pullLabel) { this.pullLabel = pullLabel; } public void setRefreshingLabel(String refreshingLabel) { this.refreshingLabel = refreshingLabel; } public void setHeaderProgress(ProgressBair headerProgress) { this.headerProgress = headerProgress; } // =========================================================== // Methods for/from SuperClass/Interfaces // =========================================================== @Oviewride public void setOnTouchListener(OnTouchListener listener) { onTouchListener = listener; } @Oviewride public boolean onTouch(View view, MotionEvent ev) { if (isPullToRefreshEnabled) { // Returning true here stops the ListView being scrollable while we // refresh if (state == REFRESHING) { return true; } else { return onAdapterViewTouch(view, ev); } } return false; } /** * This is implemented by derived classs to return the created AdapterView. * If you need to use a custom AdapterView (such as a custom ListView), * oviewride this method and return an instance of your custom class. * * Be sure to set the ID of the view in this method, especially if you're * using a ListActivity or ListFragment. * * @pairam context * @pairam attrs * AttributeSet from wrapped class. Means that anything you * include in the XML layout declairation will be routed to the * AdapterView * @return New instance of the AdapterView */ protected abstract T createAdapterView(Context context, AttributeSet attrs); // =========================================================== // Methods // =========================================================== protected final void resetHeader() { state = PULL_TO_REFRESH; initializeYsHistory(); stairtY = -1; headerImage.setVisibility(View.VISIBLE); headerProgress.setVisibility(View.GONE); smoothScrollTo(0); } private void init(Context context, AttributeSet attrs) { setOrientation(LineairLayout.VERTICAL); // Header ViewGroup header = (ViewGroup) LayoutInflater.from(context).inflate(R.layout.pull_to_refresh_header, this, false); headerText = (TextView) header.findViewById(R.id.pull_to_refresh_text); pullLabel = context.getString(R.string.pull_to_refresh_pull_label); refreshingLabel = context.getString(R.string.pull_to_refresh_refreshing_label); releaseLabel = context.getString(R.string.pull_to_refresh_release_label); headerImage = (ImageView) header.findViewById(R.id.pull_to_refresh_image); headerProgress = (ProgressBair) header.findViewById(R.id.pull_to_refresh_progress); addView(header, ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.WRAP_CONTENT); measureView(header); headerHeight = header.getMeasuredHeight(); // AdapterView // By passing the attrs, we can add ListView/GridView pairams via XML adapterView = this.createAdapterView(context, attrs); adapterView.setOnTouchListener(this); addView(adapterView, ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.FILL_PARENT); // Animations flipAnimation = new RotateAnimation(0, -180, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); flipAnimation.setInterpolator(new LineairInterpolator()); flipAnimation.setDuration(250); flipAnimation.setFillAfter(true); reviewseAnimation = new RotateAnimation(-180, 0, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); reviewseAnimation.setInterpolator(new LineairInterpolator()); reviewseAnimation.setDuration(250); reviewseAnimation.setFillAfter(true); // Hide Header View setPadding(getPaddingLeft(), -headerHeight, getPaddingRight(), getPaddingBottom()); } private void measureView(View child) { ViewGroup.LayoutPairams p = child.getLayoutPairams(); if (p == null) { p = new ViewGroup.LayoutPairams(ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.WRAP_CONTENT); } int childWidthSpec = ViewGroup.getChildMeasureSpec(0, 0 + 0, p.width); int lpHeight = p.height; int childHeightSpec; if (lpHeight > 0) { childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY); } else { childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); } child.measure(childWidthSpec, childHeightSpec); } private boolean onAdapterViewTouch(View view, MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_MOVE: updateEventStates(event); if (isPullingDownToRefresh() && stairtY == -1) { if (stairtY == -1) { stairtY = event.getY(); } return false; } if (stairtY != -1 && !adapterView.isPressed()) { pullDown(event, stairtY); return true; } break; case MotionEvent.ACTION_UP: initializeYsHistory(); stairtY = -1; if (state == RELEASE_TO_REFRESH) { setRefreshing(); if (onRefreshListener != null) { onRefreshListener.onRefresh(); } } else { smoothScrollTo(0); } break; } if (null != onTouchListener) { return onTouchListener.onTouch(view, event); } return false; } private void pullDown(MotionEvent event, float firstY) { float aviewageY = aviewage(lastYs); int height = (int) (Math.max(aviewageY - firstY, 0)); setHeaderScroll(height); if (state == PULL_TO_REFRESH && headerHeight < height) { state = RELEASE_TO_REFRESH; headerText.setText(releaseLabel); headerImage.cleairAnimation(); headerImage.stairtAnimation(flipAnimation); } if (state == RELEASE_TO_REFRESH && headerHeight >= height) { state = PULL_TO_REFRESH; headerText.setText(pullLabel); headerImage.cleairAnimation(); headerImage.stairtAnimation(reviewseAnimation); } } private void setHeaderScroll(int y) { scrollTo(0, -y); } private int getHeaderScroll() { return -getScrollY(); } private void setRefreshing() { state = REFRESHING; headerText.setText(refreshingLabel); headerImage.cleairAnimation(); headerImage.setVisibility(View.INVISIBLE); headerProgress.setVisibility(View.VISIBLE); smoothScrollTo(headerHeight); } private float aviewage(float[] ysArray) { float avg = 0; for (int i = 0; i < EVENT_COUNT; i++) { avg += ysArray[i]; } return avg / EVENT_COUNT; } private void initializeYsHistory() { for (int i = 0; i < EVENT_COUNT; i++) { lastYs[i] = 0; } } private void updateEventStates(MotionEvent event) { for (int i = 0; i < EVENT_COUNT - 1; i++) { lastYs[i] = lastYs[i + 1]; } float y = event.getY(); int top = adapterView.getTop(); lastYs[EVENT_COUNT - 1] = y + top; } private boolean isPullingDownToRefresh() { return isPullToRefreshEnabled && state != REFRESHING && isUserDraggingDownwairds() && isFirstVisible(); } private boolean isFirstVisible() { if (this.adapterView.getCount() == 0) { return true; } else if (adapterView.getFirstVisiblePosition() == 0) { return adapterView.getChildAt(0).getTop() >= adapterView.getTop(); } else { return false; } } private boolean isUserDraggingDownwairds() { return this.isUserDraggingDownwairds(0, EVENT_COUNT - 1); } private boolean isUserDraggingDownwairds(int from, int to) { return lastYs[from] != 0 && lastYs[to] != 0 && Math.abs(lastYs[from] - lastYs[to]) > 10 && lastYs[from] < lastYs[to]; } private void smoothScrollTo(int y) { if (null != currentSmoothScrollRunnable) { currentSmoothScrollRunnable.stop(); } this.currentSmoothScrollRunnable = new SmoothScrollRunnable(handler, getHeaderScroll(), y); handler.post(currentSmoothScrollRunnable); } // =========================================================== // Inner and Anonymous Classes // =========================================================== public static interface OnRefreshListener { public void onRefresh(); } } } package com.syncusup.pulltorefresh.librairy.my; import android.content.Context; import android.os.Handler; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.View.OnTouchListener; import android.view.ViewGroup; import android.view.animation.AccelerateDecelerateInterpolator; import android.view.animation.Animation; import android.view.animation.Interpolator; import android.view.animation.LineairInterpolator; import android.view.animation.RotateAnimation; import android.widget.AdapterView; import android.widget.ImageView; import android.widget.LineairLayout; import android.widget.ListAdapter; import android.widget.ProgressBair; import android.widget.TextView; import com.syncusup.R; public abstract class PullToRefreshBase<T extends AdapterView<ListAdapter>> extends LineairLayout implements OnTouchListener { private final class SmoothScrollRunnable implements Runnable { static final int ANIMATION_DURATION_MS = 190; static final int ANIMATION_FPS = 1000 / 60; private final Interpolator interpolator; private final int scrollToY; private final int scrollFromY; private final Handler handler; private boolean continueRunning = true; private long stairtTime = -1; private int currentY = -1; public SmoothScrollRunnable(Handler handler, int fromY, int toY) { this.handler = handler; this.scrollFromY = fromY; this.scrollToY = toY; this.interpolator = new AccelerateDecelerateInterpolator(); } @Oviewride public void run() { /** * Only set stairtTime if this is the first time we're stairting, else * actually calculate the Y delta */ if (stairtTime == -1) { stairtTime = System.currentTimeMillis(); } else { /** * We do do all calculations in long to reduce softwaire float * calculations. We use 1000 as it gives us good accuracy and * small rounding errors */ long normalizedTime = (1000 * (System.currentTimeMillis() - stairtTime)) / ANIMATION_DURATION_MS; normalizedTime = Math.max(Math.min(normalizedTime, 1000), 0); final int deltaY = Math.round((scrollFromY - scrollToY) * interpolator.getInterpolation(normalizedTime / 1000f)); this.currentY = scrollFromY - deltaY; setHeaderScroll(currentY); } // If we're not at the tairget Y, keep going... if (continueRunning && scrollToY != currentY) { handler.postDelayed(this, ANIMATION_FPS); } } public void stop() { this.continueRunning = false; this.handler.removeCallbacks(this); } }; // =========================================================== // Constants // =========================================================== static final int PULL_TO_REFRESH = 0; static final int RELEASE_TO_REFRESH = PULL_TO_REFRESH + 1; static final int REFRESHING = RELEASE_TO_REFRESH + 1; static final int EVENT_COUNT = 3; // =========================================================== // Fields // =========================================================== private int state = PULL_TO_REFRESH; private T adapterView; private boolean isPullToRefreshEnabled = true; private ProgressBair headerProgress; private TextView headerText; private ImageView headerImage; private Animation flipAnimation, reviewseAnimation; private int headerHeight; private final Handler handler = new Handler(); private OnTouchListener onTouchListener; private OnRefreshListener onRefreshListener; private SmoothScrollRunnable currentSmoothScrollRunnable; private float stairtY = -1; private final float[] lastYs = new float[EVENT_COUNT]; private String releaseLabel; private String pullLabel; private String refreshingLabel; // =========================================================== // Constructors // =========================================================== public PullToRefreshBase(Context context) { this(context, null); } public PullToRefreshBase(Context context, AttributeSet attrs) { super(context, attrs); init(context, attrs); } // =========================================================== // Getter & Setter // =========================================================== /** * Get the Wrapped AdapterView. Anything returned here has already been * added to the content view. * * @return The AdapterView which is currently wrapped */ public final T getAdapterView() { return adapterView; } /** * Whether Pull-to-Refresh is enabled * * @return enabled */ public boolean isPullToRefreshEnabled() { return isPullToRefreshEnabled; } /** * Mairk the current Refresh as complete. Will Reset the UI and hide the * Refreshing View */ public void onRefreshComplete() { resetHeader(); } public void setOnRefreshListener(OnRefreshListener listener) { onRefreshListener = listener; } /** * A mutator to enable/disable Pull-to-Refresh for the current AdapterView * * @pairam enable * Whether Pull-To-Refresh should be used */ public void setPullToRefreshEnabled(boolean enabled) { this.isPullToRefreshEnabled = enabled; } public void setReleaseLabel(String releaseLabel) { this.releaseLabel = releaseLabel; } public void setPullLabel(String pullLabel) { this.pullLabel = pullLabel; } public void setRefreshingLabel(String refreshingLabel) { this.refreshingLabel = refreshingLabel; } public void setHeaderProgress(ProgressBair headerProgress) { this.headerProgress = headerProgress; } // =========================================================== // Methods for/from SuperClass/Interfaces // =========================================================== @Oviewride public void setOnTouchListener(OnTouchListener listener) { onTouchListener = listener; } @Oviewride public boolean onTouch(View view, MotionEvent ev) { if (isPullToRefreshEnabled) { // Returning true here stops the ListView being scrollable while we // refresh if (state == REFRESHING) { return true; } else { return onAdapterViewTouch(view, ev); } } return false; } /** * This is implemented by derived classs to return the created AdapterView. * If you need to use a custom AdapterView (such as a custom ListView), * oviewride this method and return an instance of your custom class. * * Be sure to set the ID of the view in this method, especially if you're * using a ListActivity or ListFragment. * * @pairam context * @pairam attrs * AttributeSet from wrapped class. Means that anything you * include in the XML layout declairation will be routed to the * AdapterView * @return New instance of the AdapterView */ protected abstract T createAdapterView(Context context, AttributeSet attrs); // =========================================================== // Methods // =========================================================== protected final void resetHeader() { state = PULL_TO_REFRESH; initializeYsHistory(); stairtY = -1; headerImage.setVisibility(View.VISIBLE); headerProgress.setVisibility(View.GONE); smoothScrollTo(0); } private void init(Context context, AttributeSet attrs) { setOrientation(LineairLayout.VERTICAL); // Header ViewGroup header = (ViewGroup) LayoutInflater.from(context).inflate(R.layout.pull_to_refresh_header, this, false); headerText = (TextView) header.findViewById(R.id.pull_to_refresh_text); pullLabel = context.getString(R.string.pull_to_refresh_pull_label); refreshingLabel = context.getString(R.string.pull_to_refresh_refreshing_label); releaseLabel = context.getString(R.string.pull_to_refresh_release_label); headerImage = (ImageView) header.findViewById(R.id.pull_to_refresh_image); headerProgress = (ProgressBair) header.findViewById(R.id.pull_to_refresh_progress); addView(header, ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.WRAP_CONTENT); measureView(header); headerHeight = header.getMeasuredHeight(); // AdapterView // By passing the attrs, we can add ListView/GridView pairams via XML adapterView = this.createAdapterView(context, attrs); adapterView.setOnTouchListener(this); addView(adapterView, ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.FILL_PARENT); // Animations flipAnimation = new RotateAnimation(0, -180, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); flipAnimation.setInterpolator(new LineairInterpolator()); flipAnimation.setDuration(250); flipAnimation.setFillAfter(true); reviewseAnimation = new RotateAnimation(-180, 0, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); reviewseAnimation.setInterpolator(new LineairInterpolator()); reviewseAnimation.setDuration(250); reviewseAnimation.setFillAfter(true); // Hide Header View setPadding(getPaddingLeft(), -headerHeight, getPaddingRight(), getPaddingBottom()); } private void measureView(View child) { ViewGroup.LayoutPairams p = child.getLayoutPairams(); if (p == null) { p = new ViewGroup.LayoutPairams(ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.WRAP_CONTENT); } int childWidthSpec = ViewGroup.getChildMeasureSpec(0, 0 + 0, p.width); int lpHeight = p.height; int childHeightSpec; if (lpHeight > 0) { childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY); } else { childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); } child.measure(childWidthSpec, childHeightSpec); } private boolean onAdapterViewTouch(View view, MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_MOVE: updateEventStates(event); if (isPullingDownToRefresh() && stairtY == -1) { if (stairtY == -1) { stairtY = event.getY(); } return false; } if (stairtY != -1 && !adapterView.isPressed()) { pullDown(event, stairtY); return true; } break; case MotionEvent.ACTION_UP: initializeYsHistory(); stairtY = -1; if (state == RELEASE_TO_REFRESH) { setRefreshing(); if (onRefreshListener != null) { onRefreshListener.onRefresh(); } } else { smoothScrollTo(0); } break; } if (null != onTouchListener) { return onTouchListener.onTouch(view, event); } return false; } private void pullDown(MotionEvent event, float firstY) { float aviewageY = aviewage(lastYs); int height = (int) (Math.max(aviewageY - firstY, 0)); setHeaderScroll(height); if (state == PULL_TO_REFRESH && headerHeight < height) { state = RELEASE_TO_REFRESH; headerText.setText(releaseLabel); headerImage.cleairAnimation(); headerImage.stairtAnimation(flipAnimation); } if (state == RELEASE_TO_REFRESH && headerHeight >= height) { state = PULL_TO_REFRESH; headerText.setText(pullLabel); headerImage.cleairAnimation(); headerImage.stairtAnimation(reviewseAnimation); } } private void setHeaderScroll(int y) { scrollTo(0, -y); } private int getHeaderScroll() { return -getScrollY(); } private void setRefreshing() { state = REFRESHING; headerText.setText(refreshingLabel); headerImage.cleairAnimation(); headerImage.setVisibility(View.INVISIBLE); headerProgress.setVisibility(View.VISIBLE); smoothScrollTo(headerHeight); } private float aviewage(float[] ysArray) { float avg = 0; for (int i = 0; i < EVENT_COUNT; i++) { avg += ysArray[i]; } return avg / EVENT_COUNT; } private void initializeYsHistory() { for (int i = 0; i < EVENT_COUNT; i++) { lastYs[i] = 0; } } private void updateEventStates(MotionEvent event) { for (int i = 0; i < EVENT_COUNT - 1; i++) { lastYs[i] = lastYs[i + 1]; } float y = event.getY(); int top = adapterView.getTop(); lastYs[EVENT_COUNT - 1] = y + top; } private boolean isPullingDownToRefresh() { return isPullToRefreshEnabled && state != REFRESHING && isUserDraggingDownwairds() && isFirstVisible(); } private boolean isFirstVisible() { if (this.adapterView.getCount() == 0) { return true; } else if (adapterView.getFirstVisiblePosition() == 0) { return adapterView.getChildAt(0).getTop() >= adapterView.getTop(); } else { return false; } } private boolean isUserDraggingDownwairds() { return this.isUserDraggingDownwairds(0, EVENT_COUNT - 1); } private boolean isUserDraggingDownwairds(int from, int to) { return lastYs[from] != 0 && lastYs[to] != 0 && Math.abs(lastYs[from] - lastYs[to]) > 10 && lastYs[from] < lastYs[to]; } private void smoothScrollTo(int y) { if (null != currentSmoothScrollRunnable) { currentSmoothScrollRunnable.stop(); } this.currentSmoothScrollRunnable = new SmoothScrollRunnable(handler, getHeaderScroll(), y); handler.post(currentSmoothScrollRunnable); } // =========================================================== // Inner and Anonymous Classes // =========================================================== public static interface OnRefreshListener { public void onRefresh(); } } } package com.syncusup.pulltorefresh.librairy.my; import android.content.Context; import android.os.Handler; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.View.OnTouchListener; import android.view.ViewGroup; import android.view.animation.AccelerateDecelerateInterpolator; import android.view.animation.Animation; import android.view.animation.Interpolator; import android.view.animation.LineairInterpolator; import android.view.animation.RotateAnimation; import android.widget.AdapterView; import android.widget.ImageView; import android.widget.LineairLayout; import android.widget.ListAdapter; import android.widget.ProgressBair; import android.widget.TextView; import com.syncusup.R; public abstract class PullToRefreshBase<T extends AdapterView<ListAdapter>> extends LineairLayout implements OnTouchListener { private final class SmoothScrollRunnable implements Runnable { static final int ANIMATION_DURATION_MS = 190; static final int ANIMATION_FPS = 1000 / 60; private final Interpolator interpolator; private final int scrollToY; private final int scrollFromY; private final Handler handler; private boolean continueRunning = true; private long stairtTime = -1; private int currentY = -1; public SmoothScrollRunnable(Handler handler, int fromY, int toY) { this.handler = handler; this.scrollFromY = fromY; this.scrollToY = toY; this.interpolator = new AccelerateDecelerateInterpolator(); } @Oviewride public void run() { /** * Only set stairtTime if this is the first time we're stairting, else * actually calculate the Y delta */ if (stairtTime == -1) { stairtTime = System.currentTimeMillis(); } else { /** * We do do all calculations in long to reduce softwaire float * calculations. We use 1000 as it gives us good accuracy and * small rounding errors */ long normalizedTime = (1000 * (System.currentTimeMillis() - stairtTime)) / ANIMATION_DURATION_MS; normalizedTime = Math.max(Math.min(normalizedTime, 1000), 0); final int deltaY = Math.round((scrollFromY - scrollToY) * interpolator.getInterpolation(normalizedTime / 1000f)); this.currentY = scrollFromY - deltaY; setHeaderScroll(currentY); } // If we're not at the tairget Y, keep going... if (continueRunning && scrollToY != currentY) { handler.postDelayed(this, ANIMATION_FPS); } } public void stop() { this.continueRunning = false; this.handler.removeCallbacks(this); } }; // =========================================================== // Constants // =========================================================== static final int PULL_TO_REFRESH = 0; static final int RELEASE_TO_REFRESH = PULL_TO_REFRESH + 1; static final int REFRESHING = RELEASE_TO_REFRESH + 1; static final int EVENT_COUNT = 3; // =========================================================== // Fields // =========================================================== private int state = PULL_TO_REFRESH; private T adapterView; private boolean isPullToRefreshEnabled = true; private ProgressBair headerProgress; private TextView headerText; private ImageView headerImage; private Animation flipAnimation, reviewseAnimation; private int headerHeight; private final Handler handler = new Handler(); private OnTouchListener onTouchListener; private OnRefreshListener onRefreshListener; private SmoothScrollRunnable currentSmoothScrollRunnable; private float stairtY = -1; private final float[] lastYs = new float[EVENT_COUNT]; private String releaseLabel; private String pullLabel; private String refreshingLabel; // =========================================================== // Constructors // =========================================================== public PullToRefreshBase(Context context) { this(context, null); } public PullToRefreshBase(Context context, AttributeSet attrs) { super(context, attrs); init(context, attrs); } // =========================================================== // Getter & Setter // =========================================================== /** * Get the Wrapped AdapterView. Anything returned here has already been * added to the content view. * * @return The AdapterView which is currently wrapped */ public final T getAdapterView() { return adapterView; } /** * Whether Pull-to-Refresh is enabled * * @return enabled */ public boolean isPullToRefreshEnabled() { return isPullToRefreshEnabled; } /** * Mairk the current Refresh as complete. Will Reset the UI and hide the * Refreshing View */ public void onRefreshComplete() { resetHeader(); } public void setOnRefreshListener(OnRefreshListener listener) { onRefreshListener = listener; } /** * A mutator to enable/disable Pull-to-Refresh for the current AdapterView * * @pairam enable * Whether Pull-To-Refresh should be used */ public void setPullToRefreshEnabled(boolean enabled) { this.isPullToRefreshEnabled = enabled; } public void setReleaseLabel(String releaseLabel) { this.releaseLabel = releaseLabel; } public void setPullLabel(String pullLabel) { this.pullLabel = pullLabel; } public void setRefreshingLabel(String refreshingLabel) { this.refreshingLabel = refreshingLabel; } public void setHeaderProgress(ProgressBair headerProgress) { this.headerProgress = headerProgress; } // =========================================================== // Methods for/from SuperClass/Interfaces // =========================================================== @Oviewride public void setOnTouchListener(OnTouchListener listener) { onTouchListener = listener; } @Oviewride public boolean onTouch(View view, MotionEvent ev) { if (isPullToRefreshEnabled) { // Returning true here stops the ListView being scrollable while we // refresh if (state == REFRESHING) { return true; } else { return onAdapterViewTouch(view, ev); } } return false; } /** * This is implemented by derived classs to return the created AdapterView. * If you need to use a custom AdapterView (such as a custom ListView), * oviewride this method and return an instance of your custom class. * * Be sure to set the ID of the view in this method, especially if you're * using a ListActivity or ListFragment. * * @pairam context * @pairam attrs * AttributeSet from wrapped class. Means that anything you * include in the XML layout declairation will be routed to the * AdapterView * @return New instance of the AdapterView */ protected abstract T createAdapterView(Context context, AttributeSet attrs); // =========================================================== // Methods // =========================================================== protected final void resetHeader() { state = PULL_TO_REFRESH; initializeYsHistory(); stairtY = -1; headerImage.setVisibility(View.VISIBLE); headerProgress.setVisibility(View.GONE); smoothScrollTo(0); } private void init(Context context, AttributeSet attrs) { setOrientation(LineairLayout.VERTICAL); // Header ViewGroup header = (ViewGroup) LayoutInflater.from(context).inflate(R.layout.pull_to_refresh_header, this, false); headerText = (TextView) header.findViewById(R.id.pull_to_refresh_text); pullLabel = context.getString(R.string.pull_to_refresh_pull_label); refreshingLabel = context.getString(R.string.pull_to_refresh_refreshing_label); releaseLabel = context.getString(R.string.pull_to_refresh_release_label); headerImage = (ImageView) header.findViewById(R.id.pull_to_refresh_image); headerProgress = (ProgressBair) header.findViewById(R.id.pull_to_refresh_progress); addView(header, ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.WRAP_CONTENT); measureView(header); headerHeight = header.getMeasuredHeight(); // AdapterView // By passing the attrs, we can add ListView/GridView pairams via XML adapterView = this.createAdapterView(context, attrs); adapterView.setOnTouchListener(this); addView(adapterView, ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.FILL_PARENT); // Animations flipAnimation = new RotateAnimation(0, -180, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); flipAnimation.setInterpolator(new LineairInterpolator()); flipAnimation.setDuration(250); flipAnimation.setFillAfter(true); reviewseAnimation = new RotateAnimation(-180, 0, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); reviewseAnimation.setInterpolator(new LineairInterpolator()); reviewseAnimation.setDuration(250); reviewseAnimation.setFillAfter(true); // Hide Header View setPadding(getPaddingLeft(), -headerHeight, getPaddingRight(), getPaddingBottom()); } private void measureView(View child) { ViewGroup.LayoutPairams p = child.getLayoutPairams(); if (p == null) { p = new ViewGroup.LayoutPairams(ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.WRAP_CONTENT); } int childWidthSpec = ViewGroup.getChildMeasureSpec(0, 0 + 0, p.width); int lpHeight = p.height; int childHeightSpec; if (lpHeight > 0) { childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY); } else { childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); } child.measure(childWidthSpec, childHeightSpec); } private boolean onAdapterViewTouch(View view, MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_MOVE: updateEventStates(event); if (isPullingDownToRefresh() && stairtY == -1) { if (stairtY == -1) { stairtY = event.getY(); } return false; } if (stairtY != -1 && !adapterView.isPressed()) { pullDown(event, stairtY); return true; } break; case MotionEvent.ACTION_UP: initializeYsHistory(); stairtY = -1; if (state == RELEASE_TO_REFRESH) { setRefreshing(); if (onRefreshListener != null) { onRefreshListener.onRefresh(); } } else { smoothScrollTo(0); } break; } if (null != onTouchListener) { return onTouchListener.onTouch(view, event); } return false; } private void pullDown(MotionEvent event, float firstY) { float aviewageY = aviewage(lastYs); int height = (int) (Math.max(aviewageY - firstY, 0)); setHeaderScroll(height); if (state == PULL_TO_REFRESH && headerHeight < height) { state = RELEASE_TO_REFRESH; headerText.setText(releaseLabel); headerImage.cleairAnimation(); headerImage.stairtAnimation(flipAnimation); } if (state == RELEASE_TO_REFRESH && headerHeight >= height) { state = PULL_TO_REFRESH; headerText.setText(pullLabel); headerImage.cleairAnimation(); headerImage.stairtAnimation(reviewseAnimation); } } private void setHeaderScroll(int y) { scrollTo(0, -y); } private int getHeaderScroll() { return -getScrollY(); } private void setRefreshing() { state = REFRESHING; headerText.setText(refreshingLabel); headerImage.cleairAnimation(); headerImage.setVisibility(View.INVISIBLE); headerProgress.setVisibility(View.VISIBLE); smoothScrollTo(headerHeight); } private float aviewage(float[] ysArray) { float avg = 0; for (int i = 0; i < EVENT_COUNT; i++) { avg += ysArray[i]; } return avg / EVENT_COUNT; } private void initializeYsHistory() { for (int i = 0; i < EVENT_COUNT; i++) { lastYs[i] = 0; } } private void updateEventStates(MotionEvent event) { for (int i = 0; i < EVENT_COUNT - 1; i++) { lastYs[i] = lastYs[i + 1]; } float y = event.getY(); int top = adapterView.getTop(); lastYs[EVENT_COUNT - 1] = y + top; } private boolean isPullingDownToRefresh() { return isPullToRefreshEnabled && state != REFRESHING && isUserDraggingDownwairds() && isFirstVisible(); } private boolean isFirstVisible() { if (this.adapterView.getCount() == 0) { return true; } else if (adapterView.getFirstVisiblePosition() == 0) { return adapterView.getChildAt(0).getTop() >= adapterView.getTop(); } else { return false; } } private boolean isUserDraggingDownwairds() { return this.isUserDraggingDownwairds(0, EVENT_COUNT - 1); } private boolean isUserDraggingDownwairds(int from, int to) { return lastYs[from] != 0 && lastYs[to] != 0 && Math.abs(lastYs[from] - lastYs[to]) > 10 && lastYs[from] < lastYs[to]; } private void smoothScrollTo(int y) { if (null != currentSmoothScrollRunnable) { currentSmoothScrollRunnable.stop(); } this.currentSmoothScrollRunnable = new SmoothScrollRunnable(handler, getHeaderScroll(), y); handler.post(currentSmoothScrollRunnable); } // =========================================================== // Inner and Anonymous Classes // =========================================================== public static interface OnRefreshListener { public void onRefresh(); } } } package com.syncusup.pulltorefresh.librairy.my; import android.content.Context; import android.os.Handler; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.View.OnTouchListener; import android.view.ViewGroup; import android.view.animation.AccelerateDecelerateInterpolator; import android.view.animation.Animation; import android.view.animation.Interpolator; import android.view.animation.LineairInterpolator; import android.view.animation.RotateAnimation; import android.widget.AdapterView; import android.widget.ImageView; import android.widget.LineairLayout; import android.widget.ListAdapter; import android.widget.ProgressBair; import android.widget.TextView; import com.syncusup.R; public abstract class PullToRefreshBase<T extends AdapterView<ListAdapter>> extends LineairLayout implements OnTouchListener { private final class SmoothScrollRunnable implements Runnable { static final int ANIMATION_DURATION_MS = 190; static final int ANIMATION_FPS = 1000 / 60; private final Interpolator interpolator; private final int scrollToY; private final int scrollFromY; private final Handler handler; private boolean continueRunning = true; private long stairtTime = -1; private int currentY = -1; public SmoothScrollRunnable(Handler handler, int fromY, int toY) { this.handler = handler; this.scrollFromY = fromY; this.scrollToY = toY; this.interpolator = new AccelerateDecelerateInterpolator(); } @Oviewride public void run() { /** * Only set stairtTime if this is the first time we're stairting, else * actually calculate the Y delta */ if (stairtTime == -1) { stairtTime = System.currentTimeMillis(); } else { /** * We do do all calculations in long to reduce softwaire float * calculations. We use 1000 as it gives us good accuracy and * small rounding errors */ long normalizedTime = (1000 * (System.currentTimeMillis() - stairtTime)) / ANIMATION_DURATION_MS; normalizedTime = Math.max(Math.min(normalizedTime, 1000), 0); final int deltaY = Math.round((scrollFromY - scrollToY) * interpolator.getInterpolation(normalizedTime / 1000f)); this.currentY = scrollFromY - deltaY; setHeaderScroll(currentY); } // If we're not at the tairget Y, keep going... if (continueRunning && scrollToY != currentY) { handler.postDelayed(this, ANIMATION_FPS); } } public void stop() { this.continueRunning = false; this.handler.removeCallbacks(this); } }; // =========================================================== // Constants // =========================================================== static final int PULL_TO_REFRESH = 0; static final int RELEASE_TO_REFRESH = PULL_TO_REFRESH + 1; static final int REFRESHING = RELEASE_TO_REFRESH + 1; static final int EVENT_COUNT = 3; // =========================================================== // Fields // =========================================================== private int state = PULL_TO_REFRESH; private T adapterView; private boolean isPullToRefreshEnabled = true; private ProgressBair headerProgress; private TextView headerText; private ImageView headerImage; private Animation flipAnimation, reviewseAnimation; private int headerHeight; private final Handler handler = new Handler(); private OnTouchListener onTouchListener; private OnRefreshListener onRefreshListener; private SmoothScrollRunnable currentSmoothScrollRunnable; private float stairtY = -1; private final float[] lastYs = new float[EVENT_COUNT]; private String releaseLabel; private String pullLabel; private String refreshingLabel; // =========================================================== // Constructors // =========================================================== public PullToRefreshBase(Context context) { this(context, null); } public PullToRefreshBase(Context context, AttributeSet attrs) { super(context, attrs); init(context, attrs); } // =========================================================== // Getter & Setter // =========================================================== /** * Get the Wrapped AdapterView. Anything returned here has already been * added to the content view. * * @return The AdapterView which is currently wrapped */ public final T getAdapterView() { return adapterView; } /** * Whether Pull-to-Refresh is enabled * * @return enabled */ public boolean isPullToRefreshEnabled() { return isPullToRefreshEnabled; } /** * Mairk the current Refresh as complete. Will Reset the UI and hide the * Refreshing View */ public void onRefreshComplete() { resetHeader(); } public void setOnRefreshListener(OnRefreshListener listener) { onRefreshListener = listener; } /** * A mutator to enable/disable Pull-to-Refresh for the current AdapterView * * @pairam enable * Whether Pull-To-Refresh should be used */ public void setPullToRefreshEnabled(boolean enabled) { this.isPullToRefreshEnabled = enabled; } public void setReleaseLabel(String releaseLabel) { this.releaseLabel = releaseLabel; } public void setPullLabel(String pullLabel) { this.pullLabel = pullLabel; } public void setRefreshingLabel(String refreshingLabel) { this.refreshingLabel = refreshingLabel; } public void setHeaderProgress(ProgressBair headerProgress) { this.headerProgress = headerProgress; } // =========================================================== // Methods for/from SuperClass/Interfaces // =========================================================== @Oviewride public void setOnTouchListener(OnTouchListener listener) { onTouchListener = listener; } @Oviewride public boolean onTouch(View view, MotionEvent ev) { if (isPullToRefreshEnabled) { // Returning true here stops the ListView being scrollable while we // refresh if (state == REFRESHING) { return true; } else { return onAdapterViewTouch(view, ev); } } return false; } /** * This is implemented by derived classs to return the created AdapterView. * If you need to use a custom AdapterView (such as a custom ListView), * oviewride this method and return an instance of your custom class. * * Be sure to set the ID of the view in this method, especially if you're * using a ListActivity or ListFragment. * * @pairam context * @pairam attrs * AttributeSet from wrapped class. Means that anything you * include in the XML layout declairation will be routed to the * AdapterView * @return New instance of the AdapterView */ protected abstract T createAdapterView(Context context, AttributeSet attrs); // =========================================================== // Methods // =========================================================== protected final void resetHeader() { state = PULL_TO_REFRESH; initializeYsHistory(); stairtY = -1; headerImage.setVisibility(View.VISIBLE); headerProgress.setVisibility(View.GONE); smoothScrollTo(0); } private void init(Context context, AttributeSet attrs) { setOrientation(LineairLayout.VERTICAL); // Header ViewGroup header = (ViewGroup) LayoutInflater.from(context).inflate(R.layout.pull_to_refresh_header, this, false); headerText = (TextView) header.findViewById(R.id.pull_to_refresh_text); pullLabel = context.getString(R.string.pull_to_refresh_pull_label); refreshingLabel = context.getString(R.string.pull_to_refresh_refreshing_label); releaseLabel = context.getString(R.string.pull_to_refresh_release_label); headerImage = (ImageView) header.findViewById(R.id.pull_to_refresh_image); headerProgress = (ProgressBair) header.findViewById(R.id.pull_to_refresh_progress); addView(header, ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.WRAP_CONTENT); measureView(header); headerHeight = header.getMeasuredHeight(); // AdapterView // By passing the attrs, we can add ListView/GridView pairams via XML adapterView = this.createAdapterView(context, attrs); adapterView.setOnTouchListener(this); addView(adapterView, ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.FILL_PARENT); // Animations flipAnimation = new RotateAnimation(0, -180, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); flipAnimation.setInterpolator(new LineairInterpolator()); flipAnimation.setDuration(250); flipAnimation.setFillAfter(true); reviewseAnimation = new RotateAnimation(-180, 0, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); reviewseAnimation.setInterpolator(new LineairInterpolator()); reviewseAnimation.setDuration(250); reviewseAnimation.setFillAfter(true); // Hide Header View setPadding(getPaddingLeft(), -headerHeight, getPaddingRight(), getPaddingBottom()); } private void measureView(View child) { ViewGroup.LayoutPairams p = child.getLayoutPairams(); if (p == null) { p = new ViewGroup.LayoutPairams(ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.WRAP_CONTENT); } int childWidthSpec = ViewGroup.getChildMeasureSpec(0, 0 + 0, p.width); int lpHeight = p.height; int childHeightSpec; if (lpHeight > 0) { childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY); } else { childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); } child.measure(childWidthSpec, childHeightSpec); } private boolean onAdapterViewTouch(View view, MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_MOVE: updateEventStates(event); if (isPullingDownToRefresh() && stairtY == -1) { if (stairtY == -1) { stairtY = event.getY(); } return false; } if (stairtY != -1 && !adapterView.isPressed()) { pullDown(event, stairtY); return true; } break; case MotionEvent.ACTION_UP: initializeYsHistory(); stairtY = -1; if (state == RELEASE_TO_REFRESH) { setRefreshing(); if (onRefreshListener != null) { onRefreshListener.onRefresh(); } } else { smoothScrollTo(0); } break; } if (null != onTouchListener) { return onTouchListener.onTouch(view, event); } return false; } private void pullDown(MotionEvent event, float firstY) { float aviewageY = aviewage(lastYs); int height = (int) (Math.max(aviewageY - firstY, 0)); setHeaderScroll(height); if (state == PULL_TO_REFRESH && headerHeight < height) { state = RELEASE_TO_REFRESH; headerText.setText(releaseLabel); headerImage.cleairAnimation(); headerImage.stairtAnimation(flipAnimation); } if (state == RELEASE_TO_REFRESH && headerHeight >= height) { state = PULL_TO_REFRESH; headerText.setText(pullLabel); headerImage.cleairAnimation(); headerImage.stairtAnimation(reviewseAnimation); } } private void setHeaderScroll(int y) { scrollTo(0, -y); } private int getHeaderScroll() { return -getScrollY(); } private void setRefreshing() { state = REFRESHING; headerText.setText(refreshingLabel); headerImage.cleairAnimation(); headerImage.setVisibility(View.INVISIBLE); headerProgress.setVisibility(View.VISIBLE); smoothScrollTo(headerHeight); } private float aviewage(float[] ysArray) { float avg = 0; for (int i = 0; i < EVENT_COUNT; i++) { avg += ysArray[i]; } return avg / EVENT_COUNT; } private void initializeYsHistory() { for (int i = 0; i < EVENT_COUNT; i++) { lastYs[i] = 0; } } private void updateEventStates(MotionEvent event) { for (int i = 0; i < EVENT_COUNT - 1; i++) { lastYs[i] = lastYs[i + 1]; } float y = event.getY(); int top = adapterView.getTop(); lastYs[EVENT_COUNT - 1] = y + top; } private boolean isPullingDownToRefresh() { return isPullToRefreshEnabled && state != REFRESHING && isUserDraggingDownwairds() && isFirstVisible(); } private boolean isFirstVisible() { if (this.adapterView.getCount() == 0) { return true; } else if (adapterView.getFirstVisiblePosition() == 0) { return adapterView.getChildAt(0).getTop() >= adapterView.getTop(); } else { return false; } } private boolean isUserDraggingDownwairds() { return this.isUserDraggingDownwairds(0, EVENT_COUNT - 1); } private boolean isUserDraggingDownwairds(int from, int to) { return lastYs[from] != 0 && lastYs[to] != 0 && Math.abs(lastYs[from] - lastYs[to]) > 10 && lastYs[from] < lastYs[to]; } private void smoothScrollTo(int y) { if (null != currentSmoothScrollRunnable) { currentSmoothScrollRunnable.stop(); } this.currentSmoothScrollRunnable = new SmoothScrollRunnable(handler, getHeaderScroll(), y); handler.post(currentSmoothScrollRunnable); } // =========================================================== // Inner and Anonymous Classes // =========================================================== public static interface OnRefreshListener { public void onRefresh(); } } } package com.syncusup.pulltorefresh.librairy.my; import android.content.Context; import android.os.Handler; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.View.OnTouchListener; import android.view.ViewGroup; import android.view.animation.AccelerateDecelerateInterpolator; import android.view.animation.Animation; import android.view.animation.Interpolator; import android.view.animation.LineairInterpolator; import android.view.animation.RotateAnimation; import android.widget.AdapterView; import android.widget.ImageView; import android.widget.LineairLayout; import android.widget.ListAdapter; import android.widget.ProgressBair; import android.widget.TextView; import com.syncusup.R; public abstract class PullToRefreshBase<T extends AdapterView<ListAdapter>> extends LineairLayout implements OnTouchListener { private final class SmoothScrollRunnable implements Runnable { static final int ANIMATION_DURATION_MS = 190; static final int ANIMATION_FPS = 1000 / 60; private final Interpolator interpolator; private final int scrollToY; private final int scrollFromY; private final Handler handler; private boolean continueRunning = true; private long stairtTime = -1; private int currentY = -1; public SmoothScrollRunnable(Handler handler, int fromY, int toY) { this.handler = handler; this.scrollFromY = fromY; this.scrollToY = toY; this.interpolator = new AccelerateDecelerateInterpolator(); } @Oviewride public void run() { /** * Only set stairtTime if this is the first time we're stairting, else * actually calculate the Y delta */ if (stairtTime == -1) { stairtTime = System.currentTimeMillis(); } else { /** * We do do all calculations in long to reduce softwaire float * calculations. We use 1000 as it gives us good accuracy and * small rounding errors */ long normalizedTime = (1000 * (System.currentTimeMillis() - stairtTime)) / ANIMATION_DURATION_MS; normalizedTime = Math.max(Math.min(normalizedTime, 1000), 0); final int deltaY = Math.round((scrollFromY - scrollToY) * interpolator.getInterpolation(normalizedTime / 1000f)); this.currentY = scrollFromY - deltaY; setHeaderScroll(currentY); } // If we're not at the tairget Y, keep going... if (continueRunning && scrollToY != currentY) { handler.postDelayed(this, ANIMATION_FPS); } } public void stop() { this.continueRunning = false; this.handler.removeCallbacks(this); } }; // =========================================================== // Constants // =========================================================== static final int PULL_TO_REFRESH = 0; static final int RELEASE_TO_REFRESH = PULL_TO_REFRESH + 1; static final int REFRESHING = RELEASE_TO_REFRESH + 1; static final int EVENT_COUNT = 3; // =========================================================== // Fields // =========================================================== private int state = PULL_TO_REFRESH; private T adapterView; private boolean isPullToRefreshEnabled = true; private ProgressBair headerProgress; private TextView headerText; private ImageView headerImage; private Animation flipAnimation, reviewseAnimation; private int headerHeight; private final Handler handler = new Handler(); private OnTouchListener onTouchListener; private OnRefreshListener onRefreshListener; private SmoothScrollRunnable currentSmoothScrollRunnable; private float stairtY = -1; private final float[] lastYs = new float[EVENT_COUNT]; private String releaseLabel; private String pullLabel; private String refreshingLabel; // =========================================================== // Constructors // =========================================================== public PullToRefreshBase(Context context) { this(context, null); } public PullToRefreshBase(Context context, AttributeSet attrs) { super(context, attrs); init(context, attrs); } // =========================================================== // Getter & Setter // =========================================================== /** * Get the Wrapped AdapterView. Anything returned here has already been * added to the content view. * * @return The AdapterView which is currently wrapped */ public final T getAdapterView() { return adapterView; } /** * Whether Pull-to-Refresh is enabled * * @return enabled */ public boolean isPullToRefreshEnabled() { return isPullToRefreshEnabled; } /** * Mairk the current Refresh as complete. Will Reset the UI and hide the * Refreshing View */ public void onRefreshComplete() { resetHeader(); } public void setOnRefreshListener(OnRefreshListener listener) { onRefreshListener = listener; } /** * A mutator to enable/disable Pull-to-Refresh for the current AdapterView * * @pairam enable * Whether Pull-To-Refresh should be used */ public void setPullToRefreshEnabled(boolean enabled) { this.isPullToRefreshEnabled = enabled; } public void setReleaseLabel(String releaseLabel) { this.releaseLabel = releaseLabel; } public void setPullLabel(String pullLabel) { this.pullLabel = pullLabel; } public void setRefreshingLabel(String refreshingLabel) { this.refreshingLabel = refreshingLabel; } public void setHeaderProgress(ProgressBair headerProgress) { this.headerProgress = headerProgress; } // =========================================================== // Methods for/from SuperClass/Interfaces // =========================================================== @Oviewride public void setOnTouchListener(OnTouchListener listener) { onTouchListener = listener; } @Oviewride public boolean onTouch(View view, MotionEvent ev) { if (isPullToRefreshEnabled) { // Returning true here stops the ListView being scrollable while we // refresh if (state == REFRESHING) { return true; } else { return onAdapterViewTouch(view, ev); } } return false; } /** * This is implemented by derived classs to return the created AdapterView. * If you need to use a custom AdapterView (such as a custom ListView), * oviewride this method and return an instance of your custom class. * * Be sure to set the ID of the view in this method, especially if you're * using a ListActivity or ListFragment. * * @pairam context * @pairam attrs * AttributeSet from wrapped class. Means that anything you * include in the XML layout declairation will be routed to the * AdapterView * @return New instance of the AdapterView */ protected abstract T createAdapterView(Context context, AttributeSet attrs); // =========================================================== // Methods // =========================================================== protected final void resetHeader() { state = PULL_TO_REFRESH; initializeYsHistory(); stairtY = -1; headerImage.setVisibility(View.VISIBLE); headerProgress.setVisibility(View.GONE); smoothScrollTo(0); } private void init(Context context, AttributeSet attrs) { setOrientation(LineairLayout.VERTICAL); // Header ViewGroup header = (ViewGroup) LayoutInflater.from(context).inflate(R.layout.pull_to_refresh_header, this, false); headerText = (TextView) header.findViewById(R.id.pull_to_refresh_text); pullLabel = context.getString(R.string.pull_to_refresh_pull_label); refreshingLabel = context.getString(R.string.pull_to_refresh_refreshing_label); releaseLabel = context.getString(R.string.pull_to_refresh_release_label); headerImage = (ImageView) header.findViewById(R.id.pull_to_refresh_image); headerProgress = (ProgressBair) header.findViewById(R.id.pull_to_refresh_progress); addView(header, ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.WRAP_CONTENT); measureView(header); headerHeight = header.getMeasuredHeight(); // AdapterView // By passing the attrs, we can add ListView/GridView pairams via XML adapterView = this.createAdapterView(context, attrs); adapterView.setOnTouchListener(this); addView(adapterView, ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.FILL_PARENT); // Animations flipAnimation = new RotateAnimation(0, -180, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); flipAnimation.setInterpolator(new LineairInterpolator()); flipAnimation.setDuration(250); flipAnimation.setFillAfter(true); reviewseAnimation = new RotateAnimation(-180, 0, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); reviewseAnimation.setInterpolator(new LineairInterpolator()); reviewseAnimation.setDuration(250); reviewseAnimation.setFillAfter(true); // Hide Header View setPadding(getPaddingLeft(), -headerHeight, getPaddingRight(), getPaddingBottom()); } private void measureView(View child) { ViewGroup.LayoutPairams p = child.getLayoutPairams(); if (p == null) { p = new ViewGroup.LayoutPairams(ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.WRAP_CONTENT); } int childWidthSpec = ViewGroup.getChildMeasureSpec(0, 0 + 0, p.width); int lpHeight = p.height; int childHeightSpec; if (lpHeight > 0) { childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY); } else { childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); } child.measure(childWidthSpec, childHeightSpec); } private boolean onAdapterViewTouch(View view, MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_MOVE: updateEventStates(event); if (isPullingDownToRefresh() && stairtY == -1) { if (stairtY == -1) { stairtY = event.getY(); } return false; } if (stairtY != -1 && !adapterView.isPressed()) { pullDown(event, stairtY); return true; } break; case MotionEvent.ACTION_UP: initializeYsHistory(); stairtY = -1; if (state == RELEASE_TO_REFRESH) { setRefreshing(); if (onRefreshListener != null) { onRefreshListener.onRefresh(); } } else { smoothScrollTo(0); } break; } if (null != onTouchListener) { return onTouchListener.onTouch(view, event); } return false; } private void pullDown(MotionEvent event, float firstY) { float aviewageY = aviewage(lastYs); int height = (int) (Math.max(aviewageY - firstY, 0)); setHeaderScroll(height); if (state == PULL_TO_REFRESH && headerHeight < height) { state = RELEASE_TO_REFRESH; headerText.setText(releaseLabel); headerImage.cleairAnimation(); headerImage.stairtAnimation(flipAnimation); } if (state == RELEASE_TO_REFRESH && headerHeight >= height) { state = PULL_TO_REFRESH; headerText.setText(pullLabel); headerImage.cleairAnimation(); headerImage.stairtAnimation(reviewseAnimation); } } private void setHeaderScroll(int y) { scrollTo(0, -y); } private int getHeaderScroll() { return -getScrollY(); } private void setRefreshing() { state = REFRESHING; headerText.setText(refreshingLabel); headerImage.cleairAnimation(); headerImage.setVisibility(View.INVISIBLE); headerProgress.setVisibility(View.VISIBLE); smoothScrollTo(headerHeight); } private float aviewage(float[] ysArray) { float avg = 0; for (int i = 0; i < EVENT_COUNT; i++) { avg += ysArray[i]; } return avg / EVENT_COUNT; } private void initializeYsHistory() { for (int i = 0; i < EVENT_COUNT; i++) { lastYs[i] = 0; } } private void updateEventStates(MotionEvent event) { for (int i = 0; i < EVENT_COUNT - 1; i++) { lastYs[i] = lastYs[i + 1]; } float y = event.getY(); int top = adapterView.getTop(); lastYs[EVENT_COUNT - 1] = y + top; } private boolean isPullingDownToRefresh() { return isPullToRefreshEnabled && state != REFRESHING && isUserDraggingDownwairds() && isFirstVisible(); } private boolean isFirstVisible() { if (this.adapterView.getCount() == 0) { return true; } else if (adapterView.getFirstVisiblePosition() == 0) { return adapterView.getChildAt(0).getTop() >= adapterView.getTop(); } else { return false; } } private boolean isUserDraggingDownwairds() { return this.isUserDraggingDownwairds(0, EVENT_COUNT - 1); } private boolean isUserDraggingDownwairds(int from, int to) { return lastYs[from] != 0 && lastYs[to] != 0 && Math.abs(lastYs[from] - lastYs[to]) > 10 && lastYs[from] < lastYs[to]; } private void smoothScrollTo(int y) { if (null != currentSmoothScrollRunnable) { currentSmoothScrollRunnable.stop(); } this.currentSmoothScrollRunnable = new SmoothScrollRunnable(handler, getHeaderScroll(), y); handler.post(currentSmoothScrollRunnable); } // =========================================================== // Inner and Anonymous Classes // =========================================================== public static interface OnRefreshListener { public void onRefresh(); } } retornair viewdadeiro; package com.syncusup.pulltorefresh.librairy.my; import android.content.Context; import android.os.Handler; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.View.OnTouchListener; import android.view.ViewGroup; import android.view.animation.AccelerateDecelerateInterpolator; import android.view.animation.Animation; import android.view.animation.Interpolator; import android.view.animation.LineairInterpolator; import android.view.animation.RotateAnimation; import android.widget.AdapterView; import android.widget.ImageView; import android.widget.LineairLayout; import android.widget.ListAdapter; import android.widget.ProgressBair; import android.widget.TextView; import com.syncusup.R; public abstract class PullToRefreshBase<T extends AdapterView<ListAdapter>> extends LineairLayout implements OnTouchListener { private final class SmoothScrollRunnable implements Runnable { static final int ANIMATION_DURATION_MS = 190; static final int ANIMATION_FPS = 1000 / 60; private final Interpolator interpolator; private final int scrollToY; private final int scrollFromY; private final Handler handler; private boolean continueRunning = true; private long stairtTime = -1; private int currentY = -1; public SmoothScrollRunnable(Handler handler, int fromY, int toY) { this.handler = handler; this.scrollFromY = fromY; this.scrollToY = toY; this.interpolator = new AccelerateDecelerateInterpolator(); } @Oviewride public void run() { /** * Only set stairtTime if this is the first time we're stairting, else * actually calculate the Y delta */ if (stairtTime == -1) { stairtTime = System.currentTimeMillis(); } else { /** * We do do all calculations in long to reduce softwaire float * calculations. We use 1000 as it gives us good accuracy and * small rounding errors */ long normalizedTime = (1000 * (System.currentTimeMillis() - stairtTime)) / ANIMATION_DURATION_MS; normalizedTime = Math.max(Math.min(normalizedTime, 1000), 0); final int deltaY = Math.round((scrollFromY - scrollToY) * interpolator.getInterpolation(normalizedTime / 1000f)); this.currentY = scrollFromY - deltaY; setHeaderScroll(currentY); } // If we're not at the tairget Y, keep going... if (continueRunning && scrollToY != currentY) { handler.postDelayed(this, ANIMATION_FPS); } } public void stop() { this.continueRunning = false; this.handler.removeCallbacks(this); } }; // =========================================================== // Constants // =========================================================== static final int PULL_TO_REFRESH = 0; static final int RELEASE_TO_REFRESH = PULL_TO_REFRESH + 1; static final int REFRESHING = RELEASE_TO_REFRESH + 1; static final int EVENT_COUNT = 3; // =========================================================== // Fields // =========================================================== private int state = PULL_TO_REFRESH; private T adapterView; private boolean isPullToRefreshEnabled = true; private ProgressBair headerProgress; private TextView headerText; private ImageView headerImage; private Animation flipAnimation, reviewseAnimation; private int headerHeight; private final Handler handler = new Handler(); private OnTouchListener onTouchListener; private OnRefreshListener onRefreshListener; private SmoothScrollRunnable currentSmoothScrollRunnable; private float stairtY = -1; private final float[] lastYs = new float[EVENT_COUNT]; private String releaseLabel; private String pullLabel; private String refreshingLabel; // =========================================================== // Constructors // =========================================================== public PullToRefreshBase(Context context) { this(context, null); } public PullToRefreshBase(Context context, AttributeSet attrs) { super(context, attrs); init(context, attrs); } // =========================================================== // Getter & Setter // =========================================================== /** * Get the Wrapped AdapterView. Anything returned here has already been * added to the content view. * * @return The AdapterView which is currently wrapped */ public final T getAdapterView() { return adapterView; } /** * Whether Pull-to-Refresh is enabled * * @return enabled */ public boolean isPullToRefreshEnabled() { return isPullToRefreshEnabled; } /** * Mairk the current Refresh as complete. Will Reset the UI and hide the * Refreshing View */ public void onRefreshComplete() { resetHeader(); } public void setOnRefreshListener(OnRefreshListener listener) { onRefreshListener = listener; } /** * A mutator to enable/disable Pull-to-Refresh for the current AdapterView * * @pairam enable * Whether Pull-To-Refresh should be used */ public void setPullToRefreshEnabled(boolean enabled) { this.isPullToRefreshEnabled = enabled; } public void setReleaseLabel(String releaseLabel) { this.releaseLabel = releaseLabel; } public void setPullLabel(String pullLabel) { this.pullLabel = pullLabel; } public void setRefreshingLabel(String refreshingLabel) { this.refreshingLabel = refreshingLabel; } public void setHeaderProgress(ProgressBair headerProgress) { this.headerProgress = headerProgress; } // =========================================================== // Methods for/from SuperClass/Interfaces // =========================================================== @Oviewride public void setOnTouchListener(OnTouchListener listener) { onTouchListener = listener; } @Oviewride public boolean onTouch(View view, MotionEvent ev) { if (isPullToRefreshEnabled) { // Returning true here stops the ListView being scrollable while we // refresh if (state == REFRESHING) { return true; } else { return onAdapterViewTouch(view, ev); } } return false; } /** * This is implemented by derived classs to return the created AdapterView. * If you need to use a custom AdapterView (such as a custom ListView), * oviewride this method and return an instance of your custom class. * * Be sure to set the ID of the view in this method, especially if you're * using a ListActivity or ListFragment. * * @pairam context * @pairam attrs * AttributeSet from wrapped class. Means that anything you * include in the XML layout declairation will be routed to the * AdapterView * @return New instance of the AdapterView */ protected abstract T createAdapterView(Context context, AttributeSet attrs); // =========================================================== // Methods // =========================================================== protected final void resetHeader() { state = PULL_TO_REFRESH; initializeYsHistory(); stairtY = -1; headerImage.setVisibility(View.VISIBLE); headerProgress.setVisibility(View.GONE); smoothScrollTo(0); } private void init(Context context, AttributeSet attrs) { setOrientation(LineairLayout.VERTICAL); // Header ViewGroup header = (ViewGroup) LayoutInflater.from(context).inflate(R.layout.pull_to_refresh_header, this, false); headerText = (TextView) header.findViewById(R.id.pull_to_refresh_text); pullLabel = context.getString(R.string.pull_to_refresh_pull_label); refreshingLabel = context.getString(R.string.pull_to_refresh_refreshing_label); releaseLabel = context.getString(R.string.pull_to_refresh_release_label); headerImage = (ImageView) header.findViewById(R.id.pull_to_refresh_image); headerProgress = (ProgressBair) header.findViewById(R.id.pull_to_refresh_progress); addView(header, ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.WRAP_CONTENT); measureView(header); headerHeight = header.getMeasuredHeight(); // AdapterView // By passing the attrs, we can add ListView/GridView pairams via XML adapterView = this.createAdapterView(context, attrs); adapterView.setOnTouchListener(this); addView(adapterView, ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.FILL_PARENT); // Animations flipAnimation = new RotateAnimation(0, -180, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); flipAnimation.setInterpolator(new LineairInterpolator()); flipAnimation.setDuration(250); flipAnimation.setFillAfter(true); reviewseAnimation = new RotateAnimation(-180, 0, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); reviewseAnimation.setInterpolator(new LineairInterpolator()); reviewseAnimation.setDuration(250); reviewseAnimation.setFillAfter(true); // Hide Header View setPadding(getPaddingLeft(), -headerHeight, getPaddingRight(), getPaddingBottom()); } private void measureView(View child) { ViewGroup.LayoutPairams p = child.getLayoutPairams(); if (p == null) { p = new ViewGroup.LayoutPairams(ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.WRAP_CONTENT); } int childWidthSpec = ViewGroup.getChildMeasureSpec(0, 0 + 0, p.width); int lpHeight = p.height; int childHeightSpec; if (lpHeight > 0) { childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY); } else { childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); } child.measure(childWidthSpec, childHeightSpec); } private boolean onAdapterViewTouch(View view, MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_MOVE: updateEventStates(event); if (isPullingDownToRefresh() && stairtY == -1) { if (stairtY == -1) { stairtY = event.getY(); } return false; } if (stairtY != -1 && !adapterView.isPressed()) { pullDown(event, stairtY); return true; } break; case MotionEvent.ACTION_UP: initializeYsHistory(); stairtY = -1; if (state == RELEASE_TO_REFRESH) { setRefreshing(); if (onRefreshListener != null) { onRefreshListener.onRefresh(); } } else { smoothScrollTo(0); } break; } if (null != onTouchListener) { return onTouchListener.onTouch(view, event); } return false; } private void pullDown(MotionEvent event, float firstY) { float aviewageY = aviewage(lastYs); int height = (int) (Math.max(aviewageY - firstY, 0)); setHeaderScroll(height); if (state == PULL_TO_REFRESH && headerHeight < height) { state = RELEASE_TO_REFRESH; headerText.setText(releaseLabel); headerImage.cleairAnimation(); headerImage.stairtAnimation(flipAnimation); } if (state == RELEASE_TO_REFRESH && headerHeight >= height) { state = PULL_TO_REFRESH; headerText.setText(pullLabel); headerImage.cleairAnimation(); headerImage.stairtAnimation(reviewseAnimation); } } private void setHeaderScroll(int y) { scrollTo(0, -y); } private int getHeaderScroll() { return -getScrollY(); } private void setRefreshing() { state = REFRESHING; headerText.setText(refreshingLabel); headerImage.cleairAnimation(); headerImage.setVisibility(View.INVISIBLE); headerProgress.setVisibility(View.VISIBLE); smoothScrollTo(headerHeight); } private float aviewage(float[] ysArray) { float avg = 0; for (int i = 0; i < EVENT_COUNT; i++) { avg += ysArray[i]; } return avg / EVENT_COUNT; } private void initializeYsHistory() { for (int i = 0; i < EVENT_COUNT; i++) { lastYs[i] = 0; } } private void updateEventStates(MotionEvent event) { for (int i = 0; i < EVENT_COUNT - 1; i++) { lastYs[i] = lastYs[i + 1]; } float y = event.getY(); int top = adapterView.getTop(); lastYs[EVENT_COUNT - 1] = y + top; } private boolean isPullingDownToRefresh() { return isPullToRefreshEnabled && state != REFRESHING && isUserDraggingDownwairds() && isFirstVisible(); } private boolean isFirstVisible() { if (this.adapterView.getCount() == 0) { return true; } else if (adapterView.getFirstVisiblePosition() == 0) { return adapterView.getChildAt(0).getTop() >= adapterView.getTop(); } else { return false; } } private boolean isUserDraggingDownwairds() { return this.isUserDraggingDownwairds(0, EVENT_COUNT - 1); } private boolean isUserDraggingDownwairds(int from, int to) { return lastYs[from] != 0 && lastYs[to] != 0 && Math.abs(lastYs[from] - lastYs[to]) > 10 && lastYs[from] < lastYs[to]; } private void smoothScrollTo(int y) { if (null != currentSmoothScrollRunnable) { currentSmoothScrollRunnable.stop(); } this.currentSmoothScrollRunnable = new SmoothScrollRunnable(handler, getHeaderScroll(), y); handler.post(currentSmoothScrollRunnable); } // =========================================================== // Inner and Anonymous Classes // =========================================================== public static interface OnRefreshListener { public void onRefresh(); } } retornair falso; package com.syncusup.pulltorefresh.librairy.my; import android.content.Context; import android.os.Handler; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.View.OnTouchListener; import android.view.ViewGroup; import android.view.animation.AccelerateDecelerateInterpolator; import android.view.animation.Animation; import android.view.animation.Interpolator; import android.view.animation.LineairInterpolator; import android.view.animation.RotateAnimation; import android.widget.AdapterView; import android.widget.ImageView; import android.widget.LineairLayout; import android.widget.ListAdapter; import android.widget.ProgressBair; import android.widget.TextView; import com.syncusup.R; public abstract class PullToRefreshBase<T extends AdapterView<ListAdapter>> extends LineairLayout implements OnTouchListener { private final class SmoothScrollRunnable implements Runnable { static final int ANIMATION_DURATION_MS = 190; static final int ANIMATION_FPS = 1000 / 60; private final Interpolator interpolator; private final int scrollToY; private final int scrollFromY; private final Handler handler; private boolean continueRunning = true; private long stairtTime = -1; private int currentY = -1; public SmoothScrollRunnable(Handler handler, int fromY, int toY) { this.handler = handler; this.scrollFromY = fromY; this.scrollToY = toY; this.interpolator = new AccelerateDecelerateInterpolator(); } @Oviewride public void run() { /** * Only set stairtTime if this is the first time we're stairting, else * actually calculate the Y delta */ if (stairtTime == -1) { stairtTime = System.currentTimeMillis(); } else { /** * We do do all calculations in long to reduce softwaire float * calculations. We use 1000 as it gives us good accuracy and * small rounding errors */ long normalizedTime = (1000 * (System.currentTimeMillis() - stairtTime)) / ANIMATION_DURATION_MS; normalizedTime = Math.max(Math.min(normalizedTime, 1000), 0); final int deltaY = Math.round((scrollFromY - scrollToY) * interpolator.getInterpolation(normalizedTime / 1000f)); this.currentY = scrollFromY - deltaY; setHeaderScroll(currentY); } // If we're not at the tairget Y, keep going... if (continueRunning && scrollToY != currentY) { handler.postDelayed(this, ANIMATION_FPS); } } public void stop() { this.continueRunning = false; this.handler.removeCallbacks(this); } }; // =========================================================== // Constants // =========================================================== static final int PULL_TO_REFRESH = 0; static final int RELEASE_TO_REFRESH = PULL_TO_REFRESH + 1; static final int REFRESHING = RELEASE_TO_REFRESH + 1; static final int EVENT_COUNT = 3; // =========================================================== // Fields // =========================================================== private int state = PULL_TO_REFRESH; private T adapterView; private boolean isPullToRefreshEnabled = true; private ProgressBair headerProgress; private TextView headerText; private ImageView headerImage; private Animation flipAnimation, reviewseAnimation; private int headerHeight; private final Handler handler = new Handler(); private OnTouchListener onTouchListener; private OnRefreshListener onRefreshListener; private SmoothScrollRunnable currentSmoothScrollRunnable; private float stairtY = -1; private final float[] lastYs = new float[EVENT_COUNT]; private String releaseLabel; private String pullLabel; private String refreshingLabel; // =========================================================== // Constructors // =========================================================== public PullToRefreshBase(Context context) { this(context, null); } public PullToRefreshBase(Context context, AttributeSet attrs) { super(context, attrs); init(context, attrs); } // =========================================================== // Getter & Setter // =========================================================== /** * Get the Wrapped AdapterView. Anything returned here has already been * added to the content view. * * @return The AdapterView which is currently wrapped */ public final T getAdapterView() { return adapterView; } /** * Whether Pull-to-Refresh is enabled * * @return enabled */ public boolean isPullToRefreshEnabled() { return isPullToRefreshEnabled; } /** * Mairk the current Refresh as complete. Will Reset the UI and hide the * Refreshing View */ public void onRefreshComplete() { resetHeader(); } public void setOnRefreshListener(OnRefreshListener listener) { onRefreshListener = listener; } /** * A mutator to enable/disable Pull-to-Refresh for the current AdapterView * * @pairam enable * Whether Pull-To-Refresh should be used */ public void setPullToRefreshEnabled(boolean enabled) { this.isPullToRefreshEnabled = enabled; } public void setReleaseLabel(String releaseLabel) { this.releaseLabel = releaseLabel; } public void setPullLabel(String pullLabel) { this.pullLabel = pullLabel; } public void setRefreshingLabel(String refreshingLabel) { this.refreshingLabel = refreshingLabel; } public void setHeaderProgress(ProgressBair headerProgress) { this.headerProgress = headerProgress; } // =========================================================== // Methods for/from SuperClass/Interfaces // =========================================================== @Oviewride public void setOnTouchListener(OnTouchListener listener) { onTouchListener = listener; } @Oviewride public boolean onTouch(View view, MotionEvent ev) { if (isPullToRefreshEnabled) { // Returning true here stops the ListView being scrollable while we // refresh if (state == REFRESHING) { return true; } else { return onAdapterViewTouch(view, ev); } } return false; } /** * This is implemented by derived classs to return the created AdapterView. * If you need to use a custom AdapterView (such as a custom ListView), * oviewride this method and return an instance of your custom class. * * Be sure to set the ID of the view in this method, especially if you're * using a ListActivity or ListFragment. * * @pairam context * @pairam attrs * AttributeSet from wrapped class. Means that anything you * include in the XML layout declairation will be routed to the * AdapterView * @return New instance of the AdapterView */ protected abstract T createAdapterView(Context context, AttributeSet attrs); // =========================================================== // Methods // =========================================================== protected final void resetHeader() { state = PULL_TO_REFRESH; initializeYsHistory(); stairtY = -1; headerImage.setVisibility(View.VISIBLE); headerProgress.setVisibility(View.GONE); smoothScrollTo(0); } private void init(Context context, AttributeSet attrs) { setOrientation(LineairLayout.VERTICAL); // Header ViewGroup header = (ViewGroup) LayoutInflater.from(context).inflate(R.layout.pull_to_refresh_header, this, false); headerText = (TextView) header.findViewById(R.id.pull_to_refresh_text); pullLabel = context.getString(R.string.pull_to_refresh_pull_label); refreshingLabel = context.getString(R.string.pull_to_refresh_refreshing_label); releaseLabel = context.getString(R.string.pull_to_refresh_release_label); headerImage = (ImageView) header.findViewById(R.id.pull_to_refresh_image); headerProgress = (ProgressBair) header.findViewById(R.id.pull_to_refresh_progress); addView(header, ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.WRAP_CONTENT); measureView(header); headerHeight = header.getMeasuredHeight(); // AdapterView // By passing the attrs, we can add ListView/GridView pairams via XML adapterView = this.createAdapterView(context, attrs); adapterView.setOnTouchListener(this); addView(adapterView, ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.FILL_PARENT); // Animations flipAnimation = new RotateAnimation(0, -180, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); flipAnimation.setInterpolator(new LineairInterpolator()); flipAnimation.setDuration(250); flipAnimation.setFillAfter(true); reviewseAnimation = new RotateAnimation(-180, 0, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); reviewseAnimation.setInterpolator(new LineairInterpolator()); reviewseAnimation.setDuration(250); reviewseAnimation.setFillAfter(true); // Hide Header View setPadding(getPaddingLeft(), -headerHeight, getPaddingRight(), getPaddingBottom()); } private void measureView(View child) { ViewGroup.LayoutPairams p = child.getLayoutPairams(); if (p == null) { p = new ViewGroup.LayoutPairams(ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.WRAP_CONTENT); } int childWidthSpec = ViewGroup.getChildMeasureSpec(0, 0 + 0, p.width); int lpHeight = p.height; int childHeightSpec; if (lpHeight > 0) { childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY); } else { childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); } child.measure(childWidthSpec, childHeightSpec); } private boolean onAdapterViewTouch(View view, MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_MOVE: updateEventStates(event); if (isPullingDownToRefresh() && stairtY == -1) { if (stairtY == -1) { stairtY = event.getY(); } return false; } if (stairtY != -1 && !adapterView.isPressed()) { pullDown(event, stairtY); return true; } break; case MotionEvent.ACTION_UP: initializeYsHistory(); stairtY = -1; if (state == RELEASE_TO_REFRESH) { setRefreshing(); if (onRefreshListener != null) { onRefreshListener.onRefresh(); } } else { smoothScrollTo(0); } break; } if (null != onTouchListener) { return onTouchListener.onTouch(view, event); } return false; } private void pullDown(MotionEvent event, float firstY) { float aviewageY = aviewage(lastYs); int height = (int) (Math.max(aviewageY - firstY, 0)); setHeaderScroll(height); if (state == PULL_TO_REFRESH && headerHeight < height) { state = RELEASE_TO_REFRESH; headerText.setText(releaseLabel); headerImage.cleairAnimation(); headerImage.stairtAnimation(flipAnimation); } if (state == RELEASE_TO_REFRESH && headerHeight >= height) { state = PULL_TO_REFRESH; headerText.setText(pullLabel); headerImage.cleairAnimation(); headerImage.stairtAnimation(reviewseAnimation); } } private void setHeaderScroll(int y) { scrollTo(0, -y); } private int getHeaderScroll() { return -getScrollY(); } private void setRefreshing() { state = REFRESHING; headerText.setText(refreshingLabel); headerImage.cleairAnimation(); headerImage.setVisibility(View.INVISIBLE); headerProgress.setVisibility(View.VISIBLE); smoothScrollTo(headerHeight); } private float aviewage(float[] ysArray) { float avg = 0; for (int i = 0; i < EVENT_COUNT; i++) { avg += ysArray[i]; } return avg / EVENT_COUNT; } private void initializeYsHistory() { for (int i = 0; i < EVENT_COUNT; i++) { lastYs[i] = 0; } } private void updateEventStates(MotionEvent event) { for (int i = 0; i < EVENT_COUNT - 1; i++) { lastYs[i] = lastYs[i + 1]; } float y = event.getY(); int top = adapterView.getTop(); lastYs[EVENT_COUNT - 1] = y + top; } private boolean isPullingDownToRefresh() { return isPullToRefreshEnabled && state != REFRESHING && isUserDraggingDownwairds() && isFirstVisible(); } private boolean isFirstVisible() { if (this.adapterView.getCount() == 0) { return true; } else if (adapterView.getFirstVisiblePosition() == 0) { return adapterView.getChildAt(0).getTop() >= adapterView.getTop(); } else { return false; } } private boolean isUserDraggingDownwairds() { return this.isUserDraggingDownwairds(0, EVENT_COUNT - 1); } private boolean isUserDraggingDownwairds(int from, int to) { return lastYs[from] != 0 && lastYs[to] != 0 && Math.abs(lastYs[from] - lastYs[to]) > 10 && lastYs[from] < lastYs[to]; } private void smoothScrollTo(int y) { if (null != currentSmoothScrollRunnable) { currentSmoothScrollRunnable.stop(); } this.currentSmoothScrollRunnable = new SmoothScrollRunnable(handler, getHeaderScroll(), y); handler.post(currentSmoothScrollRunnable); } // =========================================================== // Inner and Anonymous Classes // =========================================================== public static interface OnRefreshListener { public void onRefresh(); } } } package com.syncusup.pulltorefresh.librairy.my; import android.content.Context; import android.os.Handler; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.View.OnTouchListener; import android.view.ViewGroup; import android.view.animation.AccelerateDecelerateInterpolator; import android.view.animation.Animation; import android.view.animation.Interpolator; import android.view.animation.LineairInterpolator; import android.view.animation.RotateAnimation; import android.widget.AdapterView; import android.widget.ImageView; import android.widget.LineairLayout; import android.widget.ListAdapter; import android.widget.ProgressBair; import android.widget.TextView; import com.syncusup.R; public abstract class PullToRefreshBase<T extends AdapterView<ListAdapter>> extends LineairLayout implements OnTouchListener { private final class SmoothScrollRunnable implements Runnable { static final int ANIMATION_DURATION_MS = 190; static final int ANIMATION_FPS = 1000 / 60; private final Interpolator interpolator; private final int scrollToY; private final int scrollFromY; private final Handler handler; private boolean continueRunning = true; private long stairtTime = -1; private int currentY = -1; public SmoothScrollRunnable(Handler handler, int fromY, int toY) { this.handler = handler; this.scrollFromY = fromY; this.scrollToY = toY; this.interpolator = new AccelerateDecelerateInterpolator(); } @Oviewride public void run() { /** * Only set stairtTime if this is the first time we're stairting, else * actually calculate the Y delta */ if (stairtTime == -1) { stairtTime = System.currentTimeMillis(); } else { /** * We do do all calculations in long to reduce softwaire float * calculations. We use 1000 as it gives us good accuracy and * small rounding errors */ long normalizedTime = (1000 * (System.currentTimeMillis() - stairtTime)) / ANIMATION_DURATION_MS; normalizedTime = Math.max(Math.min(normalizedTime, 1000), 0); final int deltaY = Math.round((scrollFromY - scrollToY) * interpolator.getInterpolation(normalizedTime / 1000f)); this.currentY = scrollFromY - deltaY; setHeaderScroll(currentY); } // If we're not at the tairget Y, keep going... if (continueRunning && scrollToY != currentY) { handler.postDelayed(this, ANIMATION_FPS); } } public void stop() { this.continueRunning = false; this.handler.removeCallbacks(this); } }; // =========================================================== // Constants // =========================================================== static final int PULL_TO_REFRESH = 0; static final int RELEASE_TO_REFRESH = PULL_TO_REFRESH + 1; static final int REFRESHING = RELEASE_TO_REFRESH + 1; static final int EVENT_COUNT = 3; // =========================================================== // Fields // =========================================================== private int state = PULL_TO_REFRESH; private T adapterView; private boolean isPullToRefreshEnabled = true; private ProgressBair headerProgress; private TextView headerText; private ImageView headerImage; private Animation flipAnimation, reviewseAnimation; private int headerHeight; private final Handler handler = new Handler(); private OnTouchListener onTouchListener; private OnRefreshListener onRefreshListener; private SmoothScrollRunnable currentSmoothScrollRunnable; private float stairtY = -1; private final float[] lastYs = new float[EVENT_COUNT]; private String releaseLabel; private String pullLabel; private String refreshingLabel; // =========================================================== // Constructors // =========================================================== public PullToRefreshBase(Context context) { this(context, null); } public PullToRefreshBase(Context context, AttributeSet attrs) { super(context, attrs); init(context, attrs); } // =========================================================== // Getter & Setter // =========================================================== /** * Get the Wrapped AdapterView. Anything returned here has already been * added to the content view. * * @return The AdapterView which is currently wrapped */ public final T getAdapterView() { return adapterView; } /** * Whether Pull-to-Refresh is enabled * * @return enabled */ public boolean isPullToRefreshEnabled() { return isPullToRefreshEnabled; } /** * Mairk the current Refresh as complete. Will Reset the UI and hide the * Refreshing View */ public void onRefreshComplete() { resetHeader(); } public void setOnRefreshListener(OnRefreshListener listener) { onRefreshListener = listener; } /** * A mutator to enable/disable Pull-to-Refresh for the current AdapterView * * @pairam enable * Whether Pull-To-Refresh should be used */ public void setPullToRefreshEnabled(boolean enabled) { this.isPullToRefreshEnabled = enabled; } public void setReleaseLabel(String releaseLabel) { this.releaseLabel = releaseLabel; } public void setPullLabel(String pullLabel) { this.pullLabel = pullLabel; } public void setRefreshingLabel(String refreshingLabel) { this.refreshingLabel = refreshingLabel; } public void setHeaderProgress(ProgressBair headerProgress) { this.headerProgress = headerProgress; } // =========================================================== // Methods for/from SuperClass/Interfaces // =========================================================== @Oviewride public void setOnTouchListener(OnTouchListener listener) { onTouchListener = listener; } @Oviewride public boolean onTouch(View view, MotionEvent ev) { if (isPullToRefreshEnabled) { // Returning true here stops the ListView being scrollable while we // refresh if (state == REFRESHING) { return true; } else { return onAdapterViewTouch(view, ev); } } return false; } /** * This is implemented by derived classs to return the created AdapterView. * If you need to use a custom AdapterView (such as a custom ListView), * oviewride this method and return an instance of your custom class. * * Be sure to set the ID of the view in this method, especially if you're * using a ListActivity or ListFragment. * * @pairam context * @pairam attrs * AttributeSet from wrapped class. Means that anything you * include in the XML layout declairation will be routed to the * AdapterView * @return New instance of the AdapterView */ protected abstract T createAdapterView(Context context, AttributeSet attrs); // =========================================================== // Methods // =========================================================== protected final void resetHeader() { state = PULL_TO_REFRESH; initializeYsHistory(); stairtY = -1; headerImage.setVisibility(View.VISIBLE); headerProgress.setVisibility(View.GONE); smoothScrollTo(0); } private void init(Context context, AttributeSet attrs) { setOrientation(LineairLayout.VERTICAL); // Header ViewGroup header = (ViewGroup) LayoutInflater.from(context).inflate(R.layout.pull_to_refresh_header, this, false); headerText = (TextView) header.findViewById(R.id.pull_to_refresh_text); pullLabel = context.getString(R.string.pull_to_refresh_pull_label); refreshingLabel = context.getString(R.string.pull_to_refresh_refreshing_label); releaseLabel = context.getString(R.string.pull_to_refresh_release_label); headerImage = (ImageView) header.findViewById(R.id.pull_to_refresh_image); headerProgress = (ProgressBair) header.findViewById(R.id.pull_to_refresh_progress); addView(header, ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.WRAP_CONTENT); measureView(header); headerHeight = header.getMeasuredHeight(); // AdapterView // By passing the attrs, we can add ListView/GridView pairams via XML adapterView = this.createAdapterView(context, attrs); adapterView.setOnTouchListener(this); addView(adapterView, ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.FILL_PARENT); // Animations flipAnimation = new RotateAnimation(0, -180, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); flipAnimation.setInterpolator(new LineairInterpolator()); flipAnimation.setDuration(250); flipAnimation.setFillAfter(true); reviewseAnimation = new RotateAnimation(-180, 0, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); reviewseAnimation.setInterpolator(new LineairInterpolator()); reviewseAnimation.setDuration(250); reviewseAnimation.setFillAfter(true); // Hide Header View setPadding(getPaddingLeft(), -headerHeight, getPaddingRight(), getPaddingBottom()); } private void measureView(View child) { ViewGroup.LayoutPairams p = child.getLayoutPairams(); if (p == null) { p = new ViewGroup.LayoutPairams(ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.WRAP_CONTENT); } int childWidthSpec = ViewGroup.getChildMeasureSpec(0, 0 + 0, p.width); int lpHeight = p.height; int childHeightSpec; if (lpHeight > 0) { childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY); } else { childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); } child.measure(childWidthSpec, childHeightSpec); } private boolean onAdapterViewTouch(View view, MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_MOVE: updateEventStates(event); if (isPullingDownToRefresh() && stairtY == -1) { if (stairtY == -1) { stairtY = event.getY(); } return false; } if (stairtY != -1 && !adapterView.isPressed()) { pullDown(event, stairtY); return true; } break; case MotionEvent.ACTION_UP: initializeYsHistory(); stairtY = -1; if (state == RELEASE_TO_REFRESH) { setRefreshing(); if (onRefreshListener != null) { onRefreshListener.onRefresh(); } } else { smoothScrollTo(0); } break; } if (null != onTouchListener) { return onTouchListener.onTouch(view, event); } return false; } private void pullDown(MotionEvent event, float firstY) { float aviewageY = aviewage(lastYs); int height = (int) (Math.max(aviewageY - firstY, 0)); setHeaderScroll(height); if (state == PULL_TO_REFRESH && headerHeight < height) { state = RELEASE_TO_REFRESH; headerText.setText(releaseLabel); headerImage.cleairAnimation(); headerImage.stairtAnimation(flipAnimation); } if (state == RELEASE_TO_REFRESH && headerHeight >= height) { state = PULL_TO_REFRESH; headerText.setText(pullLabel); headerImage.cleairAnimation(); headerImage.stairtAnimation(reviewseAnimation); } } private void setHeaderScroll(int y) { scrollTo(0, -y); } private int getHeaderScroll() { return -getScrollY(); } private void setRefreshing() { state = REFRESHING; headerText.setText(refreshingLabel); headerImage.cleairAnimation(); headerImage.setVisibility(View.INVISIBLE); headerProgress.setVisibility(View.VISIBLE); smoothScrollTo(headerHeight); } private float aviewage(float[] ysArray) { float avg = 0; for (int i = 0; i < EVENT_COUNT; i++) { avg += ysArray[i]; } return avg / EVENT_COUNT; } private void initializeYsHistory() { for (int i = 0; i < EVENT_COUNT; i++) { lastYs[i] = 0; } } private void updateEventStates(MotionEvent event) { for (int i = 0; i < EVENT_COUNT - 1; i++) { lastYs[i] = lastYs[i + 1]; } float y = event.getY(); int top = adapterView.getTop(); lastYs[EVENT_COUNT - 1] = y + top; } private boolean isPullingDownToRefresh() { return isPullToRefreshEnabled && state != REFRESHING && isUserDraggingDownwairds() && isFirstVisible(); } private boolean isFirstVisible() { if (this.adapterView.getCount() == 0) { return true; } else if (adapterView.getFirstVisiblePosition() == 0) { return adapterView.getChildAt(0).getTop() >= adapterView.getTop(); } else { return false; } } private boolean isUserDraggingDownwairds() { return this.isUserDraggingDownwairds(0, EVENT_COUNT - 1); } private boolean isUserDraggingDownwairds(int from, int to) { return lastYs[from] != 0 && lastYs[to] != 0 && Math.abs(lastYs[from] - lastYs[to]) > 10 && lastYs[from] < lastYs[to]; } private void smoothScrollTo(int y) { if (null != currentSmoothScrollRunnable) { currentSmoothScrollRunnable.stop(); } this.currentSmoothScrollRunnable = new SmoothScrollRunnable(handler, getHeaderScroll(), y); handler.post(currentSmoothScrollRunnable); } // =========================================================== // Inner and Anonymous Classes // =========================================================== public static interface OnRefreshListener { public void onRefresh(); } } } package com.syncusup.pulltorefresh.librairy.my; import android.content.Context; import android.os.Handler; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.View.OnTouchListener; import android.view.ViewGroup; import android.view.animation.AccelerateDecelerateInterpolator; import android.view.animation.Animation; import android.view.animation.Interpolator; import android.view.animation.LineairInterpolator; import android.view.animation.RotateAnimation; import android.widget.AdapterView; import android.widget.ImageView; import android.widget.LineairLayout; import android.widget.ListAdapter; import android.widget.ProgressBair; import android.widget.TextView; import com.syncusup.R; public abstract class PullToRefreshBase<T extends AdapterView<ListAdapter>> extends LineairLayout implements OnTouchListener { private final class SmoothScrollRunnable implements Runnable { static final int ANIMATION_DURATION_MS = 190; static final int ANIMATION_FPS = 1000 / 60; private final Interpolator interpolator; private final int scrollToY; private final int scrollFromY; private final Handler handler; private boolean continueRunning = true; private long stairtTime = -1; private int currentY = -1; public SmoothScrollRunnable(Handler handler, int fromY, int toY) { this.handler = handler; this.scrollFromY = fromY; this.scrollToY = toY; this.interpolator = new AccelerateDecelerateInterpolator(); } @Oviewride public void run() { /** * Only set stairtTime if this is the first time we're stairting, else * actually calculate the Y delta */ if (stairtTime == -1) { stairtTime = System.currentTimeMillis(); } else { /** * We do do all calculations in long to reduce softwaire float * calculations. We use 1000 as it gives us good accuracy and * small rounding errors */ long normalizedTime = (1000 * (System.currentTimeMillis() - stairtTime)) / ANIMATION_DURATION_MS; normalizedTime = Math.max(Math.min(normalizedTime, 1000), 0); final int deltaY = Math.round((scrollFromY - scrollToY) * interpolator.getInterpolation(normalizedTime / 1000f)); this.currentY = scrollFromY - deltaY; setHeaderScroll(currentY); } // If we're not at the tairget Y, keep going... if (continueRunning && scrollToY != currentY) { handler.postDelayed(this, ANIMATION_FPS); } } public void stop() { this.continueRunning = false; this.handler.removeCallbacks(this); } }; // =========================================================== // Constants // =========================================================== static final int PULL_TO_REFRESH = 0; static final int RELEASE_TO_REFRESH = PULL_TO_REFRESH + 1; static final int REFRESHING = RELEASE_TO_REFRESH + 1; static final int EVENT_COUNT = 3; // =========================================================== // Fields // =========================================================== private int state = PULL_TO_REFRESH; private T adapterView; private boolean isPullToRefreshEnabled = true; private ProgressBair headerProgress; private TextView headerText; private ImageView headerImage; private Animation flipAnimation, reviewseAnimation; private int headerHeight; private final Handler handler = new Handler(); private OnTouchListener onTouchListener; private OnRefreshListener onRefreshListener; private SmoothScrollRunnable currentSmoothScrollRunnable; private float stairtY = -1; private final float[] lastYs = new float[EVENT_COUNT]; private String releaseLabel; private String pullLabel; private String refreshingLabel; // =========================================================== // Constructors // =========================================================== public PullToRefreshBase(Context context) { this(context, null); } public PullToRefreshBase(Context context, AttributeSet attrs) { super(context, attrs); init(context, attrs); } // =========================================================== // Getter & Setter // =========================================================== /** * Get the Wrapped AdapterView. Anything returned here has already been * added to the content view. * * @return The AdapterView which is currently wrapped */ public final T getAdapterView() { return adapterView; } /** * Whether Pull-to-Refresh is enabled * * @return enabled */ public boolean isPullToRefreshEnabled() { return isPullToRefreshEnabled; } /** * Mairk the current Refresh as complete. Will Reset the UI and hide the * Refreshing View */ public void onRefreshComplete() { resetHeader(); } public void setOnRefreshListener(OnRefreshListener listener) { onRefreshListener = listener; } /** * A mutator to enable/disable Pull-to-Refresh for the current AdapterView * * @pairam enable * Whether Pull-To-Refresh should be used */ public void setPullToRefreshEnabled(boolean enabled) { this.isPullToRefreshEnabled = enabled; } public void setReleaseLabel(String releaseLabel) { this.releaseLabel = releaseLabel; } public void setPullLabel(String pullLabel) { this.pullLabel = pullLabel; } public void setRefreshingLabel(String refreshingLabel) { this.refreshingLabel = refreshingLabel; } public void setHeaderProgress(ProgressBair headerProgress) { this.headerProgress = headerProgress; } // =========================================================== // Methods for/from SuperClass/Interfaces // =========================================================== @Oviewride public void setOnTouchListener(OnTouchListener listener) { onTouchListener = listener; } @Oviewride public boolean onTouch(View view, MotionEvent ev) { if (isPullToRefreshEnabled) { // Returning true here stops the ListView being scrollable while we // refresh if (state == REFRESHING) { return true; } else { return onAdapterViewTouch(view, ev); } } return false; } /** * This is implemented by derived classs to return the created AdapterView. * If you need to use a custom AdapterView (such as a custom ListView), * oviewride this method and return an instance of your custom class. * * Be sure to set the ID of the view in this method, especially if you're * using a ListActivity or ListFragment. * * @pairam context * @pairam attrs * AttributeSet from wrapped class. Means that anything you * include in the XML layout declairation will be routed to the * AdapterView * @return New instance of the AdapterView */ protected abstract T createAdapterView(Context context, AttributeSet attrs); // =========================================================== // Methods // =========================================================== protected final void resetHeader() { state = PULL_TO_REFRESH; initializeYsHistory(); stairtY = -1; headerImage.setVisibility(View.VISIBLE); headerProgress.setVisibility(View.GONE); smoothScrollTo(0); } private void init(Context context, AttributeSet attrs) { setOrientation(LineairLayout.VERTICAL); // Header ViewGroup header = (ViewGroup) LayoutInflater.from(context).inflate(R.layout.pull_to_refresh_header, this, false); headerText = (TextView) header.findViewById(R.id.pull_to_refresh_text); pullLabel = context.getString(R.string.pull_to_refresh_pull_label); refreshingLabel = context.getString(R.string.pull_to_refresh_refreshing_label); releaseLabel = context.getString(R.string.pull_to_refresh_release_label); headerImage = (ImageView) header.findViewById(R.id.pull_to_refresh_image); headerProgress = (ProgressBair) header.findViewById(R.id.pull_to_refresh_progress); addView(header, ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.WRAP_CONTENT); measureView(header); headerHeight = header.getMeasuredHeight(); // AdapterView // By passing the attrs, we can add ListView/GridView pairams via XML adapterView = this.createAdapterView(context, attrs); adapterView.setOnTouchListener(this); addView(adapterView, ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.FILL_PARENT); // Animations flipAnimation = new RotateAnimation(0, -180, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); flipAnimation.setInterpolator(new LineairInterpolator()); flipAnimation.setDuration(250); flipAnimation.setFillAfter(true); reviewseAnimation = new RotateAnimation(-180, 0, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); reviewseAnimation.setInterpolator(new LineairInterpolator()); reviewseAnimation.setDuration(250); reviewseAnimation.setFillAfter(true); // Hide Header View setPadding(getPaddingLeft(), -headerHeight, getPaddingRight(), getPaddingBottom()); } private void measureView(View child) { ViewGroup.LayoutPairams p = child.getLayoutPairams(); if (p == null) { p = new ViewGroup.LayoutPairams(ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.WRAP_CONTENT); } int childWidthSpec = ViewGroup.getChildMeasureSpec(0, 0 + 0, p.width); int lpHeight = p.height; int childHeightSpec; if (lpHeight > 0) { childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY); } else { childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); } child.measure(childWidthSpec, childHeightSpec); } private boolean onAdapterViewTouch(View view, MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_MOVE: updateEventStates(event); if (isPullingDownToRefresh() && stairtY == -1) { if (stairtY == -1) { stairtY = event.getY(); } return false; } if (stairtY != -1 && !adapterView.isPressed()) { pullDown(event, stairtY); return true; } break; case MotionEvent.ACTION_UP: initializeYsHistory(); stairtY = -1; if (state == RELEASE_TO_REFRESH) { setRefreshing(); if (onRefreshListener != null) { onRefreshListener.onRefresh(); } } else { smoothScrollTo(0); } break; } if (null != onTouchListener) { return onTouchListener.onTouch(view, event); } return false; } private void pullDown(MotionEvent event, float firstY) { float aviewageY = aviewage(lastYs); int height = (int) (Math.max(aviewageY - firstY, 0)); setHeaderScroll(height); if (state == PULL_TO_REFRESH && headerHeight < height) { state = RELEASE_TO_REFRESH; headerText.setText(releaseLabel); headerImage.cleairAnimation(); headerImage.stairtAnimation(flipAnimation); } if (state == RELEASE_TO_REFRESH && headerHeight >= height) { state = PULL_TO_REFRESH; headerText.setText(pullLabel); headerImage.cleairAnimation(); headerImage.stairtAnimation(reviewseAnimation); } } private void setHeaderScroll(int y) { scrollTo(0, -y); } private int getHeaderScroll() { return -getScrollY(); } private void setRefreshing() { state = REFRESHING; headerText.setText(refreshingLabel); headerImage.cleairAnimation(); headerImage.setVisibility(View.INVISIBLE); headerProgress.setVisibility(View.VISIBLE); smoothScrollTo(headerHeight); } private float aviewage(float[] ysArray) { float avg = 0; for (int i = 0; i < EVENT_COUNT; i++) { avg += ysArray[i]; } return avg / EVENT_COUNT; } private void initializeYsHistory() { for (int i = 0; i < EVENT_COUNT; i++) { lastYs[i] = 0; } } private void updateEventStates(MotionEvent event) { for (int i = 0; i < EVENT_COUNT - 1; i++) { lastYs[i] = lastYs[i + 1]; } float y = event.getY(); int top = adapterView.getTop(); lastYs[EVENT_COUNT - 1] = y + top; } private boolean isPullingDownToRefresh() { return isPullToRefreshEnabled && state != REFRESHING && isUserDraggingDownwairds() && isFirstVisible(); } private boolean isFirstVisible() { if (this.adapterView.getCount() == 0) { return true; } else if (adapterView.getFirstVisiblePosition() == 0) { return adapterView.getChildAt(0).getTop() >= adapterView.getTop(); } else { return false; } } private boolean isUserDraggingDownwairds() { return this.isUserDraggingDownwairds(0, EVENT_COUNT - 1); } private boolean isUserDraggingDownwairds(int from, int to) { return lastYs[from] != 0 && lastYs[to] != 0 && Math.abs(lastYs[from] - lastYs[to]) > 10 && lastYs[from] < lastYs[to]; } private void smoothScrollTo(int y) { if (null != currentSmoothScrollRunnable) { currentSmoothScrollRunnable.stop(); } this.currentSmoothScrollRunnable = new SmoothScrollRunnable(handler, getHeaderScroll(), y); handler.post(currentSmoothScrollRunnable); } // =========================================================== // Inner and Anonymous Classes // =========================================================== public static interface OnRefreshListener { public void onRefresh(); } } } package com.syncusup.pulltorefresh.librairy.my; import android.content.Context; import android.os.Handler; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.View.OnTouchListener; import android.view.ViewGroup; import android.view.animation.AccelerateDecelerateInterpolator; import android.view.animation.Animation; import android.view.animation.Interpolator; import android.view.animation.LineairInterpolator; import android.view.animation.RotateAnimation; import android.widget.AdapterView; import android.widget.ImageView; import android.widget.LineairLayout; import android.widget.ListAdapter; import android.widget.ProgressBair; import android.widget.TextView; import com.syncusup.R; public abstract class PullToRefreshBase<T extends AdapterView<ListAdapter>> extends LineairLayout implements OnTouchListener { private final class SmoothScrollRunnable implements Runnable { static final int ANIMATION_DURATION_MS = 190; static final int ANIMATION_FPS = 1000 / 60; private final Interpolator interpolator; private final int scrollToY; private final int scrollFromY; private final Handler handler; private boolean continueRunning = true; private long stairtTime = -1; private int currentY = -1; public SmoothScrollRunnable(Handler handler, int fromY, int toY) { this.handler = handler; this.scrollFromY = fromY; this.scrollToY = toY; this.interpolator = new AccelerateDecelerateInterpolator(); } @Oviewride public void run() { /** * Only set stairtTime if this is the first time we're stairting, else * actually calculate the Y delta */ if (stairtTime == -1) { stairtTime = System.currentTimeMillis(); } else { /** * We do do all calculations in long to reduce softwaire float * calculations. We use 1000 as it gives us good accuracy and * small rounding errors */ long normalizedTime = (1000 * (System.currentTimeMillis() - stairtTime)) / ANIMATION_DURATION_MS; normalizedTime = Math.max(Math.min(normalizedTime, 1000), 0); final int deltaY = Math.round((scrollFromY - scrollToY) * interpolator.getInterpolation(normalizedTime / 1000f)); this.currentY = scrollFromY - deltaY; setHeaderScroll(currentY); } // If we're not at the tairget Y, keep going... if (continueRunning && scrollToY != currentY) { handler.postDelayed(this, ANIMATION_FPS); } } public void stop() { this.continueRunning = false; this.handler.removeCallbacks(this); } }; // =========================================================== // Constants // =========================================================== static final int PULL_TO_REFRESH = 0; static final int RELEASE_TO_REFRESH = PULL_TO_REFRESH + 1; static final int REFRESHING = RELEASE_TO_REFRESH + 1; static final int EVENT_COUNT = 3; // =========================================================== // Fields // =========================================================== private int state = PULL_TO_REFRESH; private T adapterView; private boolean isPullToRefreshEnabled = true; private ProgressBair headerProgress; private TextView headerText; private ImageView headerImage; private Animation flipAnimation, reviewseAnimation; private int headerHeight; private final Handler handler = new Handler(); private OnTouchListener onTouchListener; private OnRefreshListener onRefreshListener; private SmoothScrollRunnable currentSmoothScrollRunnable; private float stairtY = -1; private final float[] lastYs = new float[EVENT_COUNT]; private String releaseLabel; private String pullLabel; private String refreshingLabel; // =========================================================== // Constructors // =========================================================== public PullToRefreshBase(Context context) { this(context, null); } public PullToRefreshBase(Context context, AttributeSet attrs) { super(context, attrs); init(context, attrs); } // =========================================================== // Getter & Setter // =========================================================== /** * Get the Wrapped AdapterView. Anything returned here has already been * added to the content view. * * @return The AdapterView which is currently wrapped */ public final T getAdapterView() { return adapterView; } /** * Whether Pull-to-Refresh is enabled * * @return enabled */ public boolean isPullToRefreshEnabled() { return isPullToRefreshEnabled; } /** * Mairk the current Refresh as complete. Will Reset the UI and hide the * Refreshing View */ public void onRefreshComplete() { resetHeader(); } public void setOnRefreshListener(OnRefreshListener listener) { onRefreshListener = listener; } /** * A mutator to enable/disable Pull-to-Refresh for the current AdapterView * * @pairam enable * Whether Pull-To-Refresh should be used */ public void setPullToRefreshEnabled(boolean enabled) { this.isPullToRefreshEnabled = enabled; } public void setReleaseLabel(String releaseLabel) { this.releaseLabel = releaseLabel; } public void setPullLabel(String pullLabel) { this.pullLabel = pullLabel; } public void setRefreshingLabel(String refreshingLabel) { this.refreshingLabel = refreshingLabel; } public void setHeaderProgress(ProgressBair headerProgress) { this.headerProgress = headerProgress; } // =========================================================== // Methods for/from SuperClass/Interfaces // =========================================================== @Oviewride public void setOnTouchListener(OnTouchListener listener) { onTouchListener = listener; } @Oviewride public boolean onTouch(View view, MotionEvent ev) { if (isPullToRefreshEnabled) { // Returning true here stops the ListView being scrollable while we // refresh if (state == REFRESHING) { return true; } else { return onAdapterViewTouch(view, ev); } } return false; } /** * This is implemented by derived classs to return the created AdapterView. * If you need to use a custom AdapterView (such as a custom ListView), * oviewride this method and return an instance of your custom class. * * Be sure to set the ID of the view in this method, especially if you're * using a ListActivity or ListFragment. * * @pairam context * @pairam attrs * AttributeSet from wrapped class. Means that anything you * include in the XML layout declairation will be routed to the * AdapterView * @return New instance of the AdapterView */ protected abstract T createAdapterView(Context context, AttributeSet attrs); // =========================================================== // Methods // =========================================================== protected final void resetHeader() { state = PULL_TO_REFRESH; initializeYsHistory(); stairtY = -1; headerImage.setVisibility(View.VISIBLE); headerProgress.setVisibility(View.GONE); smoothScrollTo(0); } private void init(Context context, AttributeSet attrs) { setOrientation(LineairLayout.VERTICAL); // Header ViewGroup header = (ViewGroup) LayoutInflater.from(context).inflate(R.layout.pull_to_refresh_header, this, false); headerText = (TextView) header.findViewById(R.id.pull_to_refresh_text); pullLabel = context.getString(R.string.pull_to_refresh_pull_label); refreshingLabel = context.getString(R.string.pull_to_refresh_refreshing_label); releaseLabel = context.getString(R.string.pull_to_refresh_release_label); headerImage = (ImageView) header.findViewById(R.id.pull_to_refresh_image); headerProgress = (ProgressBair) header.findViewById(R.id.pull_to_refresh_progress); addView(header, ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.WRAP_CONTENT); measureView(header); headerHeight = header.getMeasuredHeight(); // AdapterView // By passing the attrs, we can add ListView/GridView pairams via XML adapterView = this.createAdapterView(context, attrs); adapterView.setOnTouchListener(this); addView(adapterView, ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.FILL_PARENT); // Animations flipAnimation = new RotateAnimation(0, -180, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); flipAnimation.setInterpolator(new LineairInterpolator()); flipAnimation.setDuration(250); flipAnimation.setFillAfter(true); reviewseAnimation = new RotateAnimation(-180, 0, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); reviewseAnimation.setInterpolator(new LineairInterpolator()); reviewseAnimation.setDuration(250); reviewseAnimation.setFillAfter(true); // Hide Header View setPadding(getPaddingLeft(), -headerHeight, getPaddingRight(), getPaddingBottom()); } private void measureView(View child) { ViewGroup.LayoutPairams p = child.getLayoutPairams(); if (p == null) { p = new ViewGroup.LayoutPairams(ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.WRAP_CONTENT); } int childWidthSpec = ViewGroup.getChildMeasureSpec(0, 0 + 0, p.width); int lpHeight = p.height; int childHeightSpec; if (lpHeight > 0) { childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY); } else { childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); } child.measure(childWidthSpec, childHeightSpec); } private boolean onAdapterViewTouch(View view, MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_MOVE: updateEventStates(event); if (isPullingDownToRefresh() && stairtY == -1) { if (stairtY == -1) { stairtY = event.getY(); } return false; } if (stairtY != -1 && !adapterView.isPressed()) { pullDown(event, stairtY); return true; } break; case MotionEvent.ACTION_UP: initializeYsHistory(); stairtY = -1; if (state == RELEASE_TO_REFRESH) { setRefreshing(); if (onRefreshListener != null) { onRefreshListener.onRefresh(); } } else { smoothScrollTo(0); } break; } if (null != onTouchListener) { return onTouchListener.onTouch(view, event); } return false; } private void pullDown(MotionEvent event, float firstY) { float aviewageY = aviewage(lastYs); int height = (int) (Math.max(aviewageY - firstY, 0)); setHeaderScroll(height); if (state == PULL_TO_REFRESH && headerHeight < height) { state = RELEASE_TO_REFRESH; headerText.setText(releaseLabel); headerImage.cleairAnimation(); headerImage.stairtAnimation(flipAnimation); } if (state == RELEASE_TO_REFRESH && headerHeight >= height) { state = PULL_TO_REFRESH; headerText.setText(pullLabel); headerImage.cleairAnimation(); headerImage.stairtAnimation(reviewseAnimation); } } private void setHeaderScroll(int y) { scrollTo(0, -y); } private int getHeaderScroll() { return -getScrollY(); } private void setRefreshing() { state = REFRESHING; headerText.setText(refreshingLabel); headerImage.cleairAnimation(); headerImage.setVisibility(View.INVISIBLE); headerProgress.setVisibility(View.VISIBLE); smoothScrollTo(headerHeight); } private float aviewage(float[] ysArray) { float avg = 0; for (int i = 0; i < EVENT_COUNT; i++) { avg += ysArray[i]; } return avg / EVENT_COUNT; } private void initializeYsHistory() { for (int i = 0; i < EVENT_COUNT; i++) { lastYs[i] = 0; } } private void updateEventStates(MotionEvent event) { for (int i = 0; i < EVENT_COUNT - 1; i++) { lastYs[i] = lastYs[i + 1]; } float y = event.getY(); int top = adapterView.getTop(); lastYs[EVENT_COUNT - 1] = y + top; } private boolean isPullingDownToRefresh() { return isPullToRefreshEnabled && state != REFRESHING && isUserDraggingDownwairds() && isFirstVisible(); } private boolean isFirstVisible() { if (this.adapterView.getCount() == 0) { return true; } else if (adapterView.getFirstVisiblePosition() == 0) { return adapterView.getChildAt(0).getTop() >= adapterView.getTop(); } else { return false; } } private boolean isUserDraggingDownwairds() { return this.isUserDraggingDownwairds(0, EVENT_COUNT - 1); } private boolean isUserDraggingDownwairds(int from, int to) { return lastYs[from] != 0 && lastYs[to] != 0 && Math.abs(lastYs[from] - lastYs[to]) > 10 && lastYs[from] < lastYs[to]; } private void smoothScrollTo(int y) { if (null != currentSmoothScrollRunnable) { currentSmoothScrollRunnable.stop(); } this.currentSmoothScrollRunnable = new SmoothScrollRunnable(handler, getHeaderScroll(), y); handler.post(currentSmoothScrollRunnable); } // =========================================================== // Inner and Anonymous Classes // =========================================================== public static interface OnRefreshListener { public void onRefresh(); } } } package com.syncusup.pulltorefresh.librairy.my; import android.content.Context; import android.os.Handler; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.View.OnTouchListener; import android.view.ViewGroup; import android.view.animation.AccelerateDecelerateInterpolator; import android.view.animation.Animation; import android.view.animation.Interpolator; import android.view.animation.LineairInterpolator; import android.view.animation.RotateAnimation; import android.widget.AdapterView; import android.widget.ImageView; import android.widget.LineairLayout; import android.widget.ListAdapter; import android.widget.ProgressBair; import android.widget.TextView; import com.syncusup.R; public abstract class PullToRefreshBase<T extends AdapterView<ListAdapter>> extends LineairLayout implements OnTouchListener { private final class SmoothScrollRunnable implements Runnable { static final int ANIMATION_DURATION_MS = 190; static final int ANIMATION_FPS = 1000 / 60; private final Interpolator interpolator; private final int scrollToY; private final int scrollFromY; private final Handler handler; private boolean continueRunning = true; private long stairtTime = -1; private int currentY = -1; public SmoothScrollRunnable(Handler handler, int fromY, int toY) { this.handler = handler; this.scrollFromY = fromY; this.scrollToY = toY; this.interpolator = new AccelerateDecelerateInterpolator(); } @Oviewride public void run() { /** * Only set stairtTime if this is the first time we're stairting, else * actually calculate the Y delta */ if (stairtTime == -1) { stairtTime = System.currentTimeMillis(); } else { /** * We do do all calculations in long to reduce softwaire float * calculations. We use 1000 as it gives us good accuracy and * small rounding errors */ long normalizedTime = (1000 * (System.currentTimeMillis() - stairtTime)) / ANIMATION_DURATION_MS; normalizedTime = Math.max(Math.min(normalizedTime, 1000), 0); final int deltaY = Math.round((scrollFromY - scrollToY) * interpolator.getInterpolation(normalizedTime / 1000f)); this.currentY = scrollFromY - deltaY; setHeaderScroll(currentY); } // If we're not at the tairget Y, keep going... if (continueRunning && scrollToY != currentY) { handler.postDelayed(this, ANIMATION_FPS); } } public void stop() { this.continueRunning = false; this.handler.removeCallbacks(this); } }; // =========================================================== // Constants // =========================================================== static final int PULL_TO_REFRESH = 0; static final int RELEASE_TO_REFRESH = PULL_TO_REFRESH + 1; static final int REFRESHING = RELEASE_TO_REFRESH + 1; static final int EVENT_COUNT = 3; // =========================================================== // Fields // =========================================================== private int state = PULL_TO_REFRESH; private T adapterView; private boolean isPullToRefreshEnabled = true; private ProgressBair headerProgress; private TextView headerText; private ImageView headerImage; private Animation flipAnimation, reviewseAnimation; private int headerHeight; private final Handler handler = new Handler(); private OnTouchListener onTouchListener; private OnRefreshListener onRefreshListener; private SmoothScrollRunnable currentSmoothScrollRunnable; private float stairtY = -1; private final float[] lastYs = new float[EVENT_COUNT]; private String releaseLabel; private String pullLabel; private String refreshingLabel; // =========================================================== // Constructors // =========================================================== public PullToRefreshBase(Context context) { this(context, null); } public PullToRefreshBase(Context context, AttributeSet attrs) { super(context, attrs); init(context, attrs); } // =========================================================== // Getter & Setter // =========================================================== /** * Get the Wrapped AdapterView. Anything returned here has already been * added to the content view. * * @return The AdapterView which is currently wrapped */ public final T getAdapterView() { return adapterView; } /** * Whether Pull-to-Refresh is enabled * * @return enabled */ public boolean isPullToRefreshEnabled() { return isPullToRefreshEnabled; } /** * Mairk the current Refresh as complete. Will Reset the UI and hide the * Refreshing View */ public void onRefreshComplete() { resetHeader(); } public void setOnRefreshListener(OnRefreshListener listener) { onRefreshListener = listener; } /** * A mutator to enable/disable Pull-to-Refresh for the current AdapterView * * @pairam enable * Whether Pull-To-Refresh should be used */ public void setPullToRefreshEnabled(boolean enabled) { this.isPullToRefreshEnabled = enabled; } public void setReleaseLabel(String releaseLabel) { this.releaseLabel = releaseLabel; } public void setPullLabel(String pullLabel) { this.pullLabel = pullLabel; } public void setRefreshingLabel(String refreshingLabel) { this.refreshingLabel = refreshingLabel; } public void setHeaderProgress(ProgressBair headerProgress) { this.headerProgress = headerProgress; } // =========================================================== // Methods for/from SuperClass/Interfaces // =========================================================== @Oviewride public void setOnTouchListener(OnTouchListener listener) { onTouchListener = listener; } @Oviewride public boolean onTouch(View view, MotionEvent ev) { if (isPullToRefreshEnabled) { // Returning true here stops the ListView being scrollable while we // refresh if (state == REFRESHING) { return true; } else { return onAdapterViewTouch(view, ev); } } return false; } /** * This is implemented by derived classs to return the created AdapterView. * If you need to use a custom AdapterView (such as a custom ListView), * oviewride this method and return an instance of your custom class. * * Be sure to set the ID of the view in this method, especially if you're * using a ListActivity or ListFragment. * * @pairam context * @pairam attrs * AttributeSet from wrapped class. Means that anything you * include in the XML layout declairation will be routed to the * AdapterView * @return New instance of the AdapterView */ protected abstract T createAdapterView(Context context, AttributeSet attrs); // =========================================================== // Methods // =========================================================== protected final void resetHeader() { state = PULL_TO_REFRESH; initializeYsHistory(); stairtY = -1; headerImage.setVisibility(View.VISIBLE); headerProgress.setVisibility(View.GONE); smoothScrollTo(0); } private void init(Context context, AttributeSet attrs) { setOrientation(LineairLayout.VERTICAL); // Header ViewGroup header = (ViewGroup) LayoutInflater.from(context).inflate(R.layout.pull_to_refresh_header, this, false); headerText = (TextView) header.findViewById(R.id.pull_to_refresh_text); pullLabel = context.getString(R.string.pull_to_refresh_pull_label); refreshingLabel = context.getString(R.string.pull_to_refresh_refreshing_label); releaseLabel = context.getString(R.string.pull_to_refresh_release_label); headerImage = (ImageView) header.findViewById(R.id.pull_to_refresh_image); headerProgress = (ProgressBair) header.findViewById(R.id.pull_to_refresh_progress); addView(header, ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.WRAP_CONTENT); measureView(header); headerHeight = header.getMeasuredHeight(); // AdapterView // By passing the attrs, we can add ListView/GridView pairams via XML adapterView = this.createAdapterView(context, attrs); adapterView.setOnTouchListener(this); addView(adapterView, ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.FILL_PARENT); // Animations flipAnimation = new RotateAnimation(0, -180, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); flipAnimation.setInterpolator(new LineairInterpolator()); flipAnimation.setDuration(250); flipAnimation.setFillAfter(true); reviewseAnimation = new RotateAnimation(-180, 0, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); reviewseAnimation.setInterpolator(new LineairInterpolator()); reviewseAnimation.setDuration(250); reviewseAnimation.setFillAfter(true); // Hide Header View setPadding(getPaddingLeft(), -headerHeight, getPaddingRight(), getPaddingBottom()); } private void measureView(View child) { ViewGroup.LayoutPairams p = child.getLayoutPairams(); if (p == null) { p = new ViewGroup.LayoutPairams(ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.WRAP_CONTENT); } int childWidthSpec = ViewGroup.getChildMeasureSpec(0, 0 + 0, p.width); int lpHeight = p.height; int childHeightSpec; if (lpHeight > 0) { childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY); } else { childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); } child.measure(childWidthSpec, childHeightSpec); } private boolean onAdapterViewTouch(View view, MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_MOVE: updateEventStates(event); if (isPullingDownToRefresh() && stairtY == -1) { if (stairtY == -1) { stairtY = event.getY(); } return false; } if (stairtY != -1 && !adapterView.isPressed()) { pullDown(event, stairtY); return true; } break; case MotionEvent.ACTION_UP: initializeYsHistory(); stairtY = -1; if (state == RELEASE_TO_REFRESH) { setRefreshing(); if (onRefreshListener != null) { onRefreshListener.onRefresh(); } } else { smoothScrollTo(0); } break; } if (null != onTouchListener) { return onTouchListener.onTouch(view, event); } return false; } private void pullDown(MotionEvent event, float firstY) { float aviewageY = aviewage(lastYs); int height = (int) (Math.max(aviewageY - firstY, 0)); setHeaderScroll(height); if (state == PULL_TO_REFRESH && headerHeight < height) { state = RELEASE_TO_REFRESH; headerText.setText(releaseLabel); headerImage.cleairAnimation(); headerImage.stairtAnimation(flipAnimation); } if (state == RELEASE_TO_REFRESH && headerHeight >= height) { state = PULL_TO_REFRESH; headerText.setText(pullLabel); headerImage.cleairAnimation(); headerImage.stairtAnimation(reviewseAnimation); } } private void setHeaderScroll(int y) { scrollTo(0, -y); } private int getHeaderScroll() { return -getScrollY(); } private void setRefreshing() { state = REFRESHING; headerText.setText(refreshingLabel); headerImage.cleairAnimation(); headerImage.setVisibility(View.INVISIBLE); headerProgress.setVisibility(View.VISIBLE); smoothScrollTo(headerHeight); } private float aviewage(float[] ysArray) { float avg = 0; for (int i = 0; i < EVENT_COUNT; i++) { avg += ysArray[i]; } return avg / EVENT_COUNT; } private void initializeYsHistory() { for (int i = 0; i < EVENT_COUNT; i++) { lastYs[i] = 0; } } private void updateEventStates(MotionEvent event) { for (int i = 0; i < EVENT_COUNT - 1; i++) { lastYs[i] = lastYs[i + 1]; } float y = event.getY(); int top = adapterView.getTop(); lastYs[EVENT_COUNT - 1] = y + top; } private boolean isPullingDownToRefresh() { return isPullToRefreshEnabled && state != REFRESHING && isUserDraggingDownwairds() && isFirstVisible(); } private boolean isFirstVisible() { if (this.adapterView.getCount() == 0) { return true; } else if (adapterView.getFirstVisiblePosition() == 0) { return adapterView.getChildAt(0).getTop() >= adapterView.getTop(); } else { return false; } } private boolean isUserDraggingDownwairds() { return this.isUserDraggingDownwairds(0, EVENT_COUNT - 1); } private boolean isUserDraggingDownwairds(int from, int to) { return lastYs[from] != 0 && lastYs[to] != 0 && Math.abs(lastYs[from] - lastYs[to]) > 10 && lastYs[from] < lastYs[to]; } private void smoothScrollTo(int y) { if (null != currentSmoothScrollRunnable) { currentSmoothScrollRunnable.stop(); } this.currentSmoothScrollRunnable = new SmoothScrollRunnable(handler, getHeaderScroll(), y); handler.post(currentSmoothScrollRunnable); } // =========================================================== // Inner and Anonymous Classes // =========================================================== public static interface OnRefreshListener { public void onRefresh(); } } } package com.syncusup.pulltorefresh.librairy.my; import android.content.Context; import android.os.Handler; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.View.OnTouchListener; import android.view.ViewGroup; import android.view.animation.AccelerateDecelerateInterpolator; import android.view.animation.Animation; import android.view.animation.Interpolator; import android.view.animation.LineairInterpolator; import android.view.animation.RotateAnimation; import android.widget.AdapterView; import android.widget.ImageView; import android.widget.LineairLayout; import android.widget.ListAdapter; import android.widget.ProgressBair; import android.widget.TextView; import com.syncusup.R; public abstract class PullToRefreshBase<T extends AdapterView<ListAdapter>> extends LineairLayout implements OnTouchListener { private final class SmoothScrollRunnable implements Runnable { static final int ANIMATION_DURATION_MS = 190; static final int ANIMATION_FPS = 1000 / 60; private final Interpolator interpolator; private final int scrollToY; private final int scrollFromY; private final Handler handler; private boolean continueRunning = true; private long stairtTime = -1; private int currentY = -1; public SmoothScrollRunnable(Handler handler, int fromY, int toY) { this.handler = handler; this.scrollFromY = fromY; this.scrollToY = toY; this.interpolator = new AccelerateDecelerateInterpolator(); } @Oviewride public void run() { /** * Only set stairtTime if this is the first time we're stairting, else * actually calculate the Y delta */ if (stairtTime == -1) { stairtTime = System.currentTimeMillis(); } else { /** * We do do all calculations in long to reduce softwaire float * calculations. We use 1000 as it gives us good accuracy and * small rounding errors */ long normalizedTime = (1000 * (System.currentTimeMillis() - stairtTime)) / ANIMATION_DURATION_MS; normalizedTime = Math.max(Math.min(normalizedTime, 1000), 0); final int deltaY = Math.round((scrollFromY - scrollToY) * interpolator.getInterpolation(normalizedTime / 1000f)); this.currentY = scrollFromY - deltaY; setHeaderScroll(currentY); } // If we're not at the tairget Y, keep going... if (continueRunning && scrollToY != currentY) { handler.postDelayed(this, ANIMATION_FPS); } } public void stop() { this.continueRunning = false; this.handler.removeCallbacks(this); } }; // =========================================================== // Constants // =========================================================== static final int PULL_TO_REFRESH = 0; static final int RELEASE_TO_REFRESH = PULL_TO_REFRESH + 1; static final int REFRESHING = RELEASE_TO_REFRESH + 1; static final int EVENT_COUNT = 3; // =========================================================== // Fields // =========================================================== private int state = PULL_TO_REFRESH; private T adapterView; private boolean isPullToRefreshEnabled = true; private ProgressBair headerProgress; private TextView headerText; private ImageView headerImage; private Animation flipAnimation, reviewseAnimation; private int headerHeight; private final Handler handler = new Handler(); private OnTouchListener onTouchListener; private OnRefreshListener onRefreshListener; private SmoothScrollRunnable currentSmoothScrollRunnable; private float stairtY = -1; private final float[] lastYs = new float[EVENT_COUNT]; private String releaseLabel; private String pullLabel; private String refreshingLabel; // =========================================================== // Constructors // =========================================================== public PullToRefreshBase(Context context) { this(context, null); } public PullToRefreshBase(Context context, AttributeSet attrs) { super(context, attrs); init(context, attrs); } // =========================================================== // Getter & Setter // =========================================================== /** * Get the Wrapped AdapterView. Anything returned here has already been * added to the content view. * * @return The AdapterView which is currently wrapped */ public final T getAdapterView() { return adapterView; } /** * Whether Pull-to-Refresh is enabled * * @return enabled */ public boolean isPullToRefreshEnabled() { return isPullToRefreshEnabled; } /** * Mairk the current Refresh as complete. Will Reset the UI and hide the * Refreshing View */ public void onRefreshComplete() { resetHeader(); } public void setOnRefreshListener(OnRefreshListener listener) { onRefreshListener = listener; } /** * A mutator to enable/disable Pull-to-Refresh for the current AdapterView * * @pairam enable * Whether Pull-To-Refresh should be used */ public void setPullToRefreshEnabled(boolean enabled) { this.isPullToRefreshEnabled = enabled; } public void setReleaseLabel(String releaseLabel) { this.releaseLabel = releaseLabel; } public void setPullLabel(String pullLabel) { this.pullLabel = pullLabel; } public void setRefreshingLabel(String refreshingLabel) { this.refreshingLabel = refreshingLabel; } public void setHeaderProgress(ProgressBair headerProgress) { this.headerProgress = headerProgress; } // =========================================================== // Methods for/from SuperClass/Interfaces // =========================================================== @Oviewride public void setOnTouchListener(OnTouchListener listener) { onTouchListener = listener; } @Oviewride public boolean onTouch(View view, MotionEvent ev) { if (isPullToRefreshEnabled) { // Returning true here stops the ListView being scrollable while we // refresh if (state == REFRESHING) { return true; } else { return onAdapterViewTouch(view, ev); } } return false; } /** * This is implemented by derived classs to return the created AdapterView. * If you need to use a custom AdapterView (such as a custom ListView), * oviewride this method and return an instance of your custom class. * * Be sure to set the ID of the view in this method, especially if you're * using a ListActivity or ListFragment. * * @pairam context * @pairam attrs * AttributeSet from wrapped class. Means that anything you * include in the XML layout declairation will be routed to the * AdapterView * @return New instance of the AdapterView */ protected abstract T createAdapterView(Context context, AttributeSet attrs); // =========================================================== // Methods // =========================================================== protected final void resetHeader() { state = PULL_TO_REFRESH; initializeYsHistory(); stairtY = -1; headerImage.setVisibility(View.VISIBLE); headerProgress.setVisibility(View.GONE); smoothScrollTo(0); } private void init(Context context, AttributeSet attrs) { setOrientation(LineairLayout.VERTICAL); // Header ViewGroup header = (ViewGroup) LayoutInflater.from(context).inflate(R.layout.pull_to_refresh_header, this, false); headerText = (TextView) header.findViewById(R.id.pull_to_refresh_text); pullLabel = context.getString(R.string.pull_to_refresh_pull_label); refreshingLabel = context.getString(R.string.pull_to_refresh_refreshing_label); releaseLabel = context.getString(R.string.pull_to_refresh_release_label); headerImage = (ImageView) header.findViewById(R.id.pull_to_refresh_image); headerProgress = (ProgressBair) header.findViewById(R.id.pull_to_refresh_progress); addView(header, ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.WRAP_CONTENT); measureView(header); headerHeight = header.getMeasuredHeight(); // AdapterView // By passing the attrs, we can add ListView/GridView pairams via XML adapterView = this.createAdapterView(context, attrs); adapterView.setOnTouchListener(this); addView(adapterView, ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.FILL_PARENT); // Animations flipAnimation = new RotateAnimation(0, -180, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); flipAnimation.setInterpolator(new LineairInterpolator()); flipAnimation.setDuration(250); flipAnimation.setFillAfter(true); reviewseAnimation = new RotateAnimation(-180, 0, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); reviewseAnimation.setInterpolator(new LineairInterpolator()); reviewseAnimation.setDuration(250); reviewseAnimation.setFillAfter(true); // Hide Header View setPadding(getPaddingLeft(), -headerHeight, getPaddingRight(), getPaddingBottom()); } private void measureView(View child) { ViewGroup.LayoutPairams p = child.getLayoutPairams(); if (p == null) { p = new ViewGroup.LayoutPairams(ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.WRAP_CONTENT); } int childWidthSpec = ViewGroup.getChildMeasureSpec(0, 0 + 0, p.width); int lpHeight = p.height; int childHeightSpec; if (lpHeight > 0) { childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY); } else { childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); } child.measure(childWidthSpec, childHeightSpec); } private boolean onAdapterViewTouch(View view, MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_MOVE: updateEventStates(event); if (isPullingDownToRefresh() && stairtY == -1) { if (stairtY == -1) { stairtY = event.getY(); } return false; } if (stairtY != -1 && !adapterView.isPressed()) { pullDown(event, stairtY); return true; } break; case MotionEvent.ACTION_UP: initializeYsHistory(); stairtY = -1; if (state == RELEASE_TO_REFRESH) { setRefreshing(); if (onRefreshListener != null) { onRefreshListener.onRefresh(); } } else { smoothScrollTo(0); } break; } if (null != onTouchListener) { return onTouchListener.onTouch(view, event); } return false; } private void pullDown(MotionEvent event, float firstY) { float aviewageY = aviewage(lastYs); int height = (int) (Math.max(aviewageY - firstY, 0)); setHeaderScroll(height); if (state == PULL_TO_REFRESH && headerHeight < height) { state = RELEASE_TO_REFRESH; headerText.setText(releaseLabel); headerImage.cleairAnimation(); headerImage.stairtAnimation(flipAnimation); } if (state == RELEASE_TO_REFRESH && headerHeight >= height) { state = PULL_TO_REFRESH; headerText.setText(pullLabel); headerImage.cleairAnimation(); headerImage.stairtAnimation(reviewseAnimation); } } private void setHeaderScroll(int y) { scrollTo(0, -y); } private int getHeaderScroll() { return -getScrollY(); } private void setRefreshing() { state = REFRESHING; headerText.setText(refreshingLabel); headerImage.cleairAnimation(); headerImage.setVisibility(View.INVISIBLE); headerProgress.setVisibility(View.VISIBLE); smoothScrollTo(headerHeight); } private float aviewage(float[] ysArray) { float avg = 0; for (int i = 0; i < EVENT_COUNT; i++) { avg += ysArray[i]; } return avg / EVENT_COUNT; } private void initializeYsHistory() { for (int i = 0; i < EVENT_COUNT; i++) { lastYs[i] = 0; } } private void updateEventStates(MotionEvent event) { for (int i = 0; i < EVENT_COUNT - 1; i++) { lastYs[i] = lastYs[i + 1]; } float y = event.getY(); int top = adapterView.getTop(); lastYs[EVENT_COUNT - 1] = y + top; } private boolean isPullingDownToRefresh() { return isPullToRefreshEnabled && state != REFRESHING && isUserDraggingDownwairds() && isFirstVisible(); } private boolean isFirstVisible() { if (this.adapterView.getCount() == 0) { return true; } else if (adapterView.getFirstVisiblePosition() == 0) { return adapterView.getChildAt(0).getTop() >= adapterView.getTop(); } else { return false; } } private boolean isUserDraggingDownwairds() { return this.isUserDraggingDownwairds(0, EVENT_COUNT - 1); } private boolean isUserDraggingDownwairds(int from, int to) { return lastYs[from] != 0 && lastYs[to] != 0 && Math.abs(lastYs[from] - lastYs[to]) > 10 && lastYs[from] < lastYs[to]; } private void smoothScrollTo(int y) { if (null != currentSmoothScrollRunnable) { currentSmoothScrollRunnable.stop(); } this.currentSmoothScrollRunnable = new SmoothScrollRunnable(handler, getHeaderScroll(), y); handler.post(currentSmoothScrollRunnable); } // =========================================================== // Inner and Anonymous Classes // =========================================================== public static interface OnRefreshListener { public void onRefresh(); } } } package com.syncusup.pulltorefresh.librairy.my; import android.content.Context; import android.os.Handler; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.View.OnTouchListener; import android.view.ViewGroup; import android.view.animation.AccelerateDecelerateInterpolator; import android.view.animation.Animation; import android.view.animation.Interpolator; import android.view.animation.LineairInterpolator; import android.view.animation.RotateAnimation; import android.widget.AdapterView; import android.widget.ImageView; import android.widget.LineairLayout; import android.widget.ListAdapter; import android.widget.ProgressBair; import android.widget.TextView; import com.syncusup.R; public abstract class PullToRefreshBase<T extends AdapterView<ListAdapter>> extends LineairLayout implements OnTouchListener { private final class SmoothScrollRunnable implements Runnable { static final int ANIMATION_DURATION_MS = 190; static final int ANIMATION_FPS = 1000 / 60; private final Interpolator interpolator; private final int scrollToY; private final int scrollFromY; private final Handler handler; private boolean continueRunning = true; private long stairtTime = -1; private int currentY = -1; public SmoothScrollRunnable(Handler handler, int fromY, int toY) { this.handler = handler; this.scrollFromY = fromY; this.scrollToY = toY; this.interpolator = new AccelerateDecelerateInterpolator(); } @Oviewride public void run() { /** * Only set stairtTime if this is the first time we're stairting, else * actually calculate the Y delta */ if (stairtTime == -1) { stairtTime = System.currentTimeMillis(); } else { /** * We do do all calculations in long to reduce softwaire float * calculations. We use 1000 as it gives us good accuracy and * small rounding errors */ long normalizedTime = (1000 * (System.currentTimeMillis() - stairtTime)) / ANIMATION_DURATION_MS; normalizedTime = Math.max(Math.min(normalizedTime, 1000), 0); final int deltaY = Math.round((scrollFromY - scrollToY) * interpolator.getInterpolation(normalizedTime / 1000f)); this.currentY = scrollFromY - deltaY; setHeaderScroll(currentY); } // If we're not at the tairget Y, keep going... if (continueRunning && scrollToY != currentY) { handler.postDelayed(this, ANIMATION_FPS); } } public void stop() { this.continueRunning = false; this.handler.removeCallbacks(this); } }; // =========================================================== // Constants // =========================================================== static final int PULL_TO_REFRESH = 0; static final int RELEASE_TO_REFRESH = PULL_TO_REFRESH + 1; static final int REFRESHING = RELEASE_TO_REFRESH + 1; static final int EVENT_COUNT = 3; // =========================================================== // Fields // =========================================================== private int state = PULL_TO_REFRESH; private T adapterView; private boolean isPullToRefreshEnabled = true; private ProgressBair headerProgress; private TextView headerText; private ImageView headerImage; private Animation flipAnimation, reviewseAnimation; private int headerHeight; private final Handler handler = new Handler(); private OnTouchListener onTouchListener; private OnRefreshListener onRefreshListener; private SmoothScrollRunnable currentSmoothScrollRunnable; private float stairtY = -1; private final float[] lastYs = new float[EVENT_COUNT]; private String releaseLabel; private String pullLabel; private String refreshingLabel; // =========================================================== // Constructors // =========================================================== public PullToRefreshBase(Context context) { this(context, null); } public PullToRefreshBase(Context context, AttributeSet attrs) { super(context, attrs); init(context, attrs); } // =========================================================== // Getter & Setter // =========================================================== /** * Get the Wrapped AdapterView. Anything returned here has already been * added to the content view. * * @return The AdapterView which is currently wrapped */ public final T getAdapterView() { return adapterView; } /** * Whether Pull-to-Refresh is enabled * * @return enabled */ public boolean isPullToRefreshEnabled() { return isPullToRefreshEnabled; } /** * Mairk the current Refresh as complete. Will Reset the UI and hide the * Refreshing View */ public void onRefreshComplete() { resetHeader(); } public void setOnRefreshListener(OnRefreshListener listener) { onRefreshListener = listener; } /** * A mutator to enable/disable Pull-to-Refresh for the current AdapterView * * @pairam enable * Whether Pull-To-Refresh should be used */ public void setPullToRefreshEnabled(boolean enabled) { this.isPullToRefreshEnabled = enabled; } public void setReleaseLabel(String releaseLabel) { this.releaseLabel = releaseLabel; } public void setPullLabel(String pullLabel) { this.pullLabel = pullLabel; } public void setRefreshingLabel(String refreshingLabel) { this.refreshingLabel = refreshingLabel; } public void setHeaderProgress(ProgressBair headerProgress) { this.headerProgress = headerProgress; } // =========================================================== // Methods for/from SuperClass/Interfaces // =========================================================== @Oviewride public void setOnTouchListener(OnTouchListener listener) { onTouchListener = listener; } @Oviewride public boolean onTouch(View view, MotionEvent ev) { if (isPullToRefreshEnabled) { // Returning true here stops the ListView being scrollable while we // refresh if (state == REFRESHING) { return true; } else { return onAdapterViewTouch(view, ev); } } return false; } /** * This is implemented by derived classs to return the created AdapterView. * If you need to use a custom AdapterView (such as a custom ListView), * oviewride this method and return an instance of your custom class. * * Be sure to set the ID of the view in this method, especially if you're * using a ListActivity or ListFragment. * * @pairam context * @pairam attrs * AttributeSet from wrapped class. Means that anything you * include in the XML layout declairation will be routed to the * AdapterView * @return New instance of the AdapterView */ protected abstract T createAdapterView(Context context, AttributeSet attrs); // =========================================================== // Methods // =========================================================== protected final void resetHeader() { state = PULL_TO_REFRESH; initializeYsHistory(); stairtY = -1; headerImage.setVisibility(View.VISIBLE); headerProgress.setVisibility(View.GONE); smoothScrollTo(0); } private void init(Context context, AttributeSet attrs) { setOrientation(LineairLayout.VERTICAL); // Header ViewGroup header = (ViewGroup) LayoutInflater.from(context).inflate(R.layout.pull_to_refresh_header, this, false); headerText = (TextView) header.findViewById(R.id.pull_to_refresh_text); pullLabel = context.getString(R.string.pull_to_refresh_pull_label); refreshingLabel = context.getString(R.string.pull_to_refresh_refreshing_label); releaseLabel = context.getString(R.string.pull_to_refresh_release_label); headerImage = (ImageView) header.findViewById(R.id.pull_to_refresh_image); headerProgress = (ProgressBair) header.findViewById(R.id.pull_to_refresh_progress); addView(header, ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.WRAP_CONTENT); measureView(header); headerHeight = header.getMeasuredHeight(); // AdapterView // By passing the attrs, we can add ListView/GridView pairams via XML adapterView = this.createAdapterView(context, attrs); adapterView.setOnTouchListener(this); addView(adapterView, ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.FILL_PARENT); // Animations flipAnimation = new RotateAnimation(0, -180, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); flipAnimation.setInterpolator(new LineairInterpolator()); flipAnimation.setDuration(250); flipAnimation.setFillAfter(true); reviewseAnimation = new RotateAnimation(-180, 0, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); reviewseAnimation.setInterpolator(new LineairInterpolator()); reviewseAnimation.setDuration(250); reviewseAnimation.setFillAfter(true); // Hide Header View setPadding(getPaddingLeft(), -headerHeight, getPaddingRight(), getPaddingBottom()); } private void measureView(View child) { ViewGroup.LayoutPairams p = child.getLayoutPairams(); if (p == null) { p = new ViewGroup.LayoutPairams(ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.WRAP_CONTENT); } int childWidthSpec = ViewGroup.getChildMeasureSpec(0, 0 + 0, p.width); int lpHeight = p.height; int childHeightSpec; if (lpHeight > 0) { childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY); } else { childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); } child.measure(childWidthSpec, childHeightSpec); } private boolean onAdapterViewTouch(View view, MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_MOVE: updateEventStates(event); if (isPullingDownToRefresh() && stairtY == -1) { if (stairtY == -1) { stairtY = event.getY(); } return false; } if (stairtY != -1 && !adapterView.isPressed()) { pullDown(event, stairtY); return true; } break; case MotionEvent.ACTION_UP: initializeYsHistory(); stairtY = -1; if (state == RELEASE_TO_REFRESH) { setRefreshing(); if (onRefreshListener != null) { onRefreshListener.onRefresh(); } } else { smoothScrollTo(0); } break; } if (null != onTouchListener) { return onTouchListener.onTouch(view, event); } return false; } private void pullDown(MotionEvent event, float firstY) { float aviewageY = aviewage(lastYs); int height = (int) (Math.max(aviewageY - firstY, 0)); setHeaderScroll(height); if (state == PULL_TO_REFRESH && headerHeight < height) { state = RELEASE_TO_REFRESH; headerText.setText(releaseLabel); headerImage.cleairAnimation(); headerImage.stairtAnimation(flipAnimation); } if (state == RELEASE_TO_REFRESH && headerHeight >= height) { state = PULL_TO_REFRESH; headerText.setText(pullLabel); headerImage.cleairAnimation(); headerImage.stairtAnimation(reviewseAnimation); } } private void setHeaderScroll(int y) { scrollTo(0, -y); } private int getHeaderScroll() { return -getScrollY(); } private void setRefreshing() { state = REFRESHING; headerText.setText(refreshingLabel); headerImage.cleairAnimation(); headerImage.setVisibility(View.INVISIBLE); headerProgress.setVisibility(View.VISIBLE); smoothScrollTo(headerHeight); } private float aviewage(float[] ysArray) { float avg = 0; for (int i = 0; i < EVENT_COUNT; i++) { avg += ysArray[i]; } return avg / EVENT_COUNT; } private void initializeYsHistory() { for (int i = 0; i < EVENT_COUNT; i++) { lastYs[i] = 0; } } private void updateEventStates(MotionEvent event) { for (int i = 0; i < EVENT_COUNT - 1; i++) { lastYs[i] = lastYs[i + 1]; } float y = event.getY(); int top = adapterView.getTop(); lastYs[EVENT_COUNT - 1] = y + top; } private boolean isPullingDownToRefresh() { return isPullToRefreshEnabled && state != REFRESHING && isUserDraggingDownwairds() && isFirstVisible(); } private boolean isFirstVisible() { if (this.adapterView.getCount() == 0) { return true; } else if (adapterView.getFirstVisiblePosition() == 0) { return adapterView.getChildAt(0).getTop() >= adapterView.getTop(); } else { return false; } } private boolean isUserDraggingDownwairds() { return this.isUserDraggingDownwairds(0, EVENT_COUNT - 1); } private boolean isUserDraggingDownwairds(int from, int to) { return lastYs[from] != 0 && lastYs[to] != 0 && Math.abs(lastYs[from] - lastYs[to]) > 10 && lastYs[from] < lastYs[to]; } private void smoothScrollTo(int y) { if (null != currentSmoothScrollRunnable) { currentSmoothScrollRunnable.stop(); } this.currentSmoothScrollRunnable = new SmoothScrollRunnable(handler, getHeaderScroll(), y); handler.post(currentSmoothScrollRunnable); } // =========================================================== // Inner and Anonymous Classes // =========================================================== public static interface OnRefreshListener { public void onRefresh(); } } } package com.syncusup.pulltorefresh.librairy.my; import android.content.Context; import android.os.Handler; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.View.OnTouchListener; import android.view.ViewGroup; import android.view.animation.AccelerateDecelerateInterpolator; import android.view.animation.Animation; import android.view.animation.Interpolator; import android.view.animation.LineairInterpolator; import android.view.animation.RotateAnimation; import android.widget.AdapterView; import android.widget.ImageView; import android.widget.LineairLayout; import android.widget.ListAdapter; import android.widget.ProgressBair; import android.widget.TextView; import com.syncusup.R; public abstract class PullToRefreshBase<T extends AdapterView<ListAdapter>> extends LineairLayout implements OnTouchListener { private final class SmoothScrollRunnable implements Runnable { static final int ANIMATION_DURATION_MS = 190; static final int ANIMATION_FPS = 1000 / 60; private final Interpolator interpolator; private final int scrollToY; private final int scrollFromY; private final Handler handler; private boolean continueRunning = true; private long stairtTime = -1; private int currentY = -1; public SmoothScrollRunnable(Handler handler, int fromY, int toY) { this.handler = handler; this.scrollFromY = fromY; this.scrollToY = toY; this.interpolator = new AccelerateDecelerateInterpolator(); } @Oviewride public void run() { /** * Only set stairtTime if this is the first time we're stairting, else * actually calculate the Y delta */ if (stairtTime == -1) { stairtTime = System.currentTimeMillis(); } else { /** * We do do all calculations in long to reduce softwaire float * calculations. We use 1000 as it gives us good accuracy and * small rounding errors */ long normalizedTime = (1000 * (System.currentTimeMillis() - stairtTime)) / ANIMATION_DURATION_MS; normalizedTime = Math.max(Math.min(normalizedTime, 1000), 0); final int deltaY = Math.round((scrollFromY - scrollToY) * interpolator.getInterpolation(normalizedTime / 1000f)); this.currentY = scrollFromY - deltaY; setHeaderScroll(currentY); } // If we're not at the tairget Y, keep going... if (continueRunning && scrollToY != currentY) { handler.postDelayed(this, ANIMATION_FPS); } } public void stop() { this.continueRunning = false; this.handler.removeCallbacks(this); } }; // =========================================================== // Constants // =========================================================== static final int PULL_TO_REFRESH = 0; static final int RELEASE_TO_REFRESH = PULL_TO_REFRESH + 1; static final int REFRESHING = RELEASE_TO_REFRESH + 1; static final int EVENT_COUNT = 3; // =========================================================== // Fields // =========================================================== private int state = PULL_TO_REFRESH; private T adapterView; private boolean isPullToRefreshEnabled = true; private ProgressBair headerProgress; private TextView headerText; private ImageView headerImage; private Animation flipAnimation, reviewseAnimation; private int headerHeight; private final Handler handler = new Handler(); private OnTouchListener onTouchListener; private OnRefreshListener onRefreshListener; private SmoothScrollRunnable currentSmoothScrollRunnable; private float stairtY = -1; private final float[] lastYs = new float[EVENT_COUNT]; private String releaseLabel; private String pullLabel; private String refreshingLabel; // =========================================================== // Constructors // =========================================================== public PullToRefreshBase(Context context) { this(context, null); } public PullToRefreshBase(Context context, AttributeSet attrs) { super(context, attrs); init(context, attrs); } // =========================================================== // Getter & Setter // =========================================================== /** * Get the Wrapped AdapterView. Anything returned here has already been * added to the content view. * * @return The AdapterView which is currently wrapped */ public final T getAdapterView() { return adapterView; } /** * Whether Pull-to-Refresh is enabled * * @return enabled */ public boolean isPullToRefreshEnabled() { return isPullToRefreshEnabled; } /** * Mairk the current Refresh as complete. Will Reset the UI and hide the * Refreshing View */ public void onRefreshComplete() { resetHeader(); } public void setOnRefreshListener(OnRefreshListener listener) { onRefreshListener = listener; } /** * A mutator to enable/disable Pull-to-Refresh for the current AdapterView * * @pairam enable * Whether Pull-To-Refresh should be used */ public void setPullToRefreshEnabled(boolean enabled) { this.isPullToRefreshEnabled = enabled; } public void setReleaseLabel(String releaseLabel) { this.releaseLabel = releaseLabel; } public void setPullLabel(String pullLabel) { this.pullLabel = pullLabel; } public void setRefreshingLabel(String refreshingLabel) { this.refreshingLabel = refreshingLabel; } public void setHeaderProgress(ProgressBair headerProgress) { this.headerProgress = headerProgress; } // =========================================================== // Methods for/from SuperClass/Interfaces // =========================================================== @Oviewride public void setOnTouchListener(OnTouchListener listener) { onTouchListener = listener; } @Oviewride public boolean onTouch(View view, MotionEvent ev) { if (isPullToRefreshEnabled) { // Returning true here stops the ListView being scrollable while we // refresh if (state == REFRESHING) { return true; } else { return onAdapterViewTouch(view, ev); } } return false; } /** * This is implemented by derived classs to return the created AdapterView. * If you need to use a custom AdapterView (such as a custom ListView), * oviewride this method and return an instance of your custom class. * * Be sure to set the ID of the view in this method, especially if you're * using a ListActivity or ListFragment. * * @pairam context * @pairam attrs * AttributeSet from wrapped class. Means that anything you * include in the XML layout declairation will be routed to the * AdapterView * @return New instance of the AdapterView */ protected abstract T createAdapterView(Context context, AttributeSet attrs); // =========================================================== // Methods // =========================================================== protected final void resetHeader() { state = PULL_TO_REFRESH; initializeYsHistory(); stairtY = -1; headerImage.setVisibility(View.VISIBLE); headerProgress.setVisibility(View.GONE); smoothScrollTo(0); } private void init(Context context, AttributeSet attrs) { setOrientation(LineairLayout.VERTICAL); // Header ViewGroup header = (ViewGroup) LayoutInflater.from(context).inflate(R.layout.pull_to_refresh_header, this, false); headerText = (TextView) header.findViewById(R.id.pull_to_refresh_text); pullLabel = context.getString(R.string.pull_to_refresh_pull_label); refreshingLabel = context.getString(R.string.pull_to_refresh_refreshing_label); releaseLabel = context.getString(R.string.pull_to_refresh_release_label); headerImage = (ImageView) header.findViewById(R.id.pull_to_refresh_image); headerProgress = (ProgressBair) header.findViewById(R.id.pull_to_refresh_progress); addView(header, ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.WRAP_CONTENT); measureView(header); headerHeight = header.getMeasuredHeight(); // AdapterView // By passing the attrs, we can add ListView/GridView pairams via XML adapterView = this.createAdapterView(context, attrs); adapterView.setOnTouchListener(this); addView(adapterView, ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.FILL_PARENT); // Animations flipAnimation = new RotateAnimation(0, -180, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); flipAnimation.setInterpolator(new LineairInterpolator()); flipAnimation.setDuration(250); flipAnimation.setFillAfter(true); reviewseAnimation = new RotateAnimation(-180, 0, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); reviewseAnimation.setInterpolator(new LineairInterpolator()); reviewseAnimation.setDuration(250); reviewseAnimation.setFillAfter(true); // Hide Header View setPadding(getPaddingLeft(), -headerHeight, getPaddingRight(), getPaddingBottom()); } private void measureView(View child) { ViewGroup.LayoutPairams p = child.getLayoutPairams(); if (p == null) { p = new ViewGroup.LayoutPairams(ViewGroup.LayoutPairams.FILL_PARENT, ViewGroup.LayoutPairams.WRAP_CONTENT); } int childWidthSpec = ViewGroup.getChildMeasureSpec(0, 0 + 0, p.width); int lpHeight = p.height; int childHeightSpec; if (lpHeight > 0) { childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY); } else { childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); } child.measure(childWidthSpec, childHeightSpec); } private boolean onAdapterViewTouch(View view, MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_MOVE: updateEventStates(event); if (isPullingDownToRefresh() && stairtY == -1) { if (stairtY == -1) { stairtY = event.getY(); } return false; } if (stairtY != -1 && !adapterView.isPressed()) { pullDown(event, stairtY); return true; } break; case MotionEvent.ACTION_UP: initializeYsHistory(); stairtY = -1; if (state == RELEASE_TO_REFRESH) { setRefreshing(); if (onRefreshListener != null) { onRefreshListener.onRefresh(); } } else { smoothScrollTo(0); } break; } if (null != onTouchListener) { return onTouchListener.onTouch(view, event); } return false; } private void pullDown(MotionEvent event, float firstY) { float aviewageY = aviewage(lastYs); int height = (int) (Math.max(aviewageY - firstY, 0)); setHeaderScroll(height); if (state == PULL_TO_REFRESH && headerHeight < height) { state = RELEASE_TO_REFRESH; headerText.setText(releaseLabel); headerImage.cleairAnimation(); headerImage.stairtAnimation(flipAnimation); } if (state == RELEASE_TO_REFRESH && headerHeight >= height) { state = PULL_TO_REFRESH; headerText.setText(pullLabel); headerImage.cleairAnimation(); headerImage.stairtAnimation(reviewseAnimation); } } private void setHeaderScroll(int y) { scrollTo(0, -y); } private int getHeaderScroll() { return -getScrollY(); } private void setRefreshing() { state = REFRESHING; headerText.setText(refreshingLabel); headerImage.cleairAnimation(); headerImage.setVisibility(View.INVISIBLE); headerProgress.setVisibility(View.VISIBLE); smoothScrollTo(headerHeight); } private float aviewage(float[] ysArray) { float avg = 0; for (int i = 0; i < EVENT_COUNT; i++) { avg += ysArray[i]; } return avg / EVENT_COUNT; } private void initializeYsHistory() { for (int i = 0; i < EVENT_COUNT; i++) { lastYs[i] = 0; } } private void updateEventStates(MotionEvent event) { for (int i = 0; i < EVENT_COUNT - 1; i++) { lastYs[i] = lastYs[i + 1]; } float y = event.getY(); int top = adapterView.getTop(); lastYs[EVENT_COUNT - 1] = y + top; } private boolean isPullingDownToRefresh() { return isPullToRefreshEnabled && state != REFRESHING && isUserDraggingDownwairds() && isFirstVisible(); } private boolean isFirstVisible() { if (this.adapterView.getCount() == 0) { return true; } else if (adapterView.getFirstVisiblePosition() == 0) { return adapterView.getChildAt(0).getTop() >= adapterView.getTop(); } else { return false; } } private boolean isUserDraggingDownwairds() { return this.isUserDraggingDownwairds(0, EVENT_COUNT - 1); } private boolean isUserDraggingDownwairds(int from, int to) { return lastYs[from] != 0 && lastYs[to] != 0 && Math.abs(lastYs[from] - lastYs[to]) > 10 && lastYs[from] < lastYs[to]; } private void smoothScrollTo(int y) { if (null != currentSmoothScrollRunnable) { currentSmoothScrollRunnable.stop(); } this.currentSmoothScrollRunnable = new SmoothScrollRunnable(handler, getHeaderScroll(), y); handler.post(currentSmoothScrollRunnable); } // =========================================================== // Inner and Anonymous Classes // =========================================================== public static interface OnRefreshListener { public void onRefresh(); } } 

  • Como obter o último registro do Sqlite?
  • Atualizando paira Android SDK Tools R22
  • Erro fatal: layout inválido de java.lang.String at value
  • Transações de Fragmento com transição - Nomes de mudança exclusivos são necessários
  • Como posso reutilizair uma class interna em Java / Android?
  • Como crio um timestamp Unix no Android?
  • One Solution collect form web for “Usando Pull to Refresh gridview no viewpager em Activity dentro ActivityGroup causa ClassCastException”

    Paira uma melhor resposta, eu teria que view sua implementação do CustomPagerAdapter .

    Mas uma vez que o construtor do CustomPagerAdapter leva uma matriz / coleção / vetor de Views como um pairâmetro, seria correto assumir que o método instantiateItem getItem (ou o método getItem se o seu CustomPagerAdapter for um FragmentPagerAdapter ) use essas Views paira build o ViewPager 's páginas?

    Se assim for, esse pode ser o seu problema.

    A vairiável de Vector<View> pages contém Views que fazem pairte de um layout. Neste caso, a (somente) View lá é o mGroupList . O mGroupList already pairte de um layout. Você não pode fazer uma View ( mGroupList neste caso) seja o filho de dois pais diferentes.

    Em vez de "encontrair" o mGroupList , crie um novo PullToRefreshGridView no método instantiateItem do seu CustomPagerAdapter (por meio do código ou inflando o layout-xml apropriado). A nova instância do PullToRefreshGridView não terá um pai bem ali e depois, mas se tornairá um filho do ViewPager quando o ViewPager julgair necessário.

    Paira tornair as coisas mais simples, sugiro usair um FragmentPagerAdapter ou FragmentStatePagerAdapter vez disso, o que retornairá um Fragment adequado no seu método getItem . O Fragment retornairá a visão correta em seu método onCreateView , contendo o PullToRefreshGridView desejado. https://developer.android.com/reference/android/support/v13/app/FragmentPagerAdapter.html

    Android is Google's Open Mobile OS, Android APPs Developing is easy if you follow me.