Commits

Fred Grott  committed 9e1a8d9

added androidpulltorefresh

  • Participants
  • Parent commits 7f6b3e0

Comments (0)

Files changed (10)

 
 [AndroidGestureListView]()
 
+## From
+
+### AndroidPullToRefresh
+
+[AndroidPullToRefresh]()
 
 
 

File res/drawable-hdpi/ic_pulltorefresh_arrow.png

Added
New image

File res/drawable-ldpi/ic_pulltorefresh_arrow.png

Added
New image

File res/drawable-mdpi/ic_pulltorefresh_arrow.png

Added
New image

File res/drawable/pull_to_refresh_header_background.xml

+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 Johan Nilsson <http://markupartist.com>
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="rectangle">
+    <gradient
+        android:startColor="#ffffff"
+        android:endColor="#EDEDED"
+        android:angle="-90" />
+</shape>

File res/layout/pull_to_refresh_header.xml

+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2011 Johan Nilsson <http://markupartist.com>
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent"
+    android:paddingTop="10dip"
+    android:paddingBottom="15dip"
+    android:gravity="center"
+        android:id="@+id/pull_to_refresh_header"
+    >
+    <ProgressBar 
+        android:id="@+id/pull_to_refresh_progress"
+        android:indeterminate="true"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginLeft="30dip"
+        android:layout_marginRight="20dip"
+        android:layout_marginTop="10dip"
+        android:visibility="gone"
+        android:layout_centerVertical="true"
+        style="?android:attr/progressBarStyleSmall"
+        />
+    <ImageView
+        android:id="@+id/pull_to_refresh_image"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginLeft="30dip"
+        android:layout_marginRight="20dip"
+        android:visibility="gone"
+        android:layout_gravity="center"
+        android:gravity="center"
+        android:src="@drawable/ic_pulltorefresh_arrow"
+        />
+    <TextView
+        android:id="@+id/pull_to_refresh_text"
+        android:text="@string/pull_to_refresh_tap_label"
+        android:textAppearance="?android:attr/textAppearanceMedium"
+        android:textStyle="bold"
+        android:paddingTop="5dip"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center"
+        android:gravity="center"
+        />
+    <TextView
+        android:id="@+id/pull_to_refresh_updated_at"
+        android:layout_below="@+id/pull_to_refresh_text"
+        android:visibility="gone"
+        android:textAppearance="?android:attr/textAppearanceSmall"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center"
+        android:gravity="center"
+        />
+</RelativeLayout>

File res/values-sv/aptr_strings.xml

+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <string name="pull_to_refresh_pull_label">Dra ner för att uppdatera...</string>
+    <string name="pull_to_refresh_release_label">Släpp för att uppdatera...</string>
+    <string name="pull_to_refresh_refreshing_label">Uppdaterar...</string>
+    <string name="pull_to_refresh_tap_label">Tryck för att uppdatera...</string>
+</resources>

File res/values/aptr_strings.xml

+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <!--<string name="app_name">pulltorefresh</string>-->
+    <string name="pull_to_refresh_pull_label">Pull to refresh...</string>
+    <string name="pull_to_refresh_release_label">Release to refresh...</string>
+    <string name="pull_to_refresh_refreshing_label">Loading...</string>
+    <string name="pull_to_refresh_tap_label">Tap to refresh...</string>
+</resources>

File src/com/markupartist/android/widget/PullToRefreshListView.java

+package com.markupartist.android.widget;
+
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.animation.LinearInterpolator;
+import android.view.animation.RotateAnimation;
+import android.widget.AbsListView;
+import android.widget.ImageView;
+import android.widget.ListAdapter;
+import android.widget.ListView;
+import android.widget.ProgressBar;
+import android.widget.RelativeLayout;
+import android.widget.TextView;
+import android.widget.AbsListView.OnScrollListener;
+
+import org.bitbucket.fredgrott.gwsamulet.R;
+
+public class PullToRefreshListView extends ListView implements OnScrollListener {
+
+    private static final int TAP_TO_REFRESH = 1;
+    private static final int PULL_TO_REFRESH = 2;
+    private static final int RELEASE_TO_REFRESH = 3;
+    private static final int REFRESHING = 4;
+
+    private static final String TAG = "PullToRefreshListView";
+
+    private OnRefreshListener mOnRefreshListener;
+
+    /**
+     * Listener that will receive notifications every time the list scrolls.
+     */
+    private OnScrollListener mOnScrollListener;
+    private LayoutInflater mInflater;
+
+    private RelativeLayout mRefreshView;
+    private TextView mRefreshViewText;
+    private ImageView mRefreshViewImage;
+    private ProgressBar mRefreshViewProgress;
+    private TextView mRefreshViewLastUpdated;
+
+    private int mCurrentScrollState;
+    private int mRefreshState;
+
+    private RotateAnimation mFlipAnimation;
+    private RotateAnimation mReverseFlipAnimation;
+
+    private int mRefreshViewHeight;
+    private int mRefreshOriginalTopPadding;
+    private int mLastMotionY;
+
+    private boolean mBounceHack;
+
+    public PullToRefreshListView(Context context) {
+        super(context);
+        init(context);
+    }
+
+    public PullToRefreshListView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        init(context);
+    }
+
+    public PullToRefreshListView(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+        init(context);
+    }
+
+    private void init(Context context) {
+        // Load all of the animations we need in code rather than through XML
+        mFlipAnimation = new RotateAnimation(0, -180,
+                RotateAnimation.RELATIVE_TO_SELF, 0.5f,
+                RotateAnimation.RELATIVE_TO_SELF, 0.5f);
+        mFlipAnimation.setInterpolator(new LinearInterpolator());
+        mFlipAnimation.setDuration(250);
+        mFlipAnimation.setFillAfter(true);
+        mReverseFlipAnimation = new RotateAnimation(-180, 0,
+                RotateAnimation.RELATIVE_TO_SELF, 0.5f,
+                RotateAnimation.RELATIVE_TO_SELF, 0.5f);
+        mReverseFlipAnimation.setInterpolator(new LinearInterpolator());
+        mReverseFlipAnimation.setDuration(250);
+        mReverseFlipAnimation.setFillAfter(true);
+
+        mInflater = (LayoutInflater) context.getSystemService(
+                Context.LAYOUT_INFLATER_SERVICE);
+
+		mRefreshView = (RelativeLayout) mInflater.inflate(
+				R.layout.pull_to_refresh_header, this, false);
+        mRefreshViewText =
+            (TextView) mRefreshView.findViewById(R.id.pull_to_refresh_text);
+        mRefreshViewImage =
+            (ImageView) mRefreshView.findViewById(R.id.pull_to_refresh_image);
+        mRefreshViewProgress =
+            (ProgressBar) mRefreshView.findViewById(R.id.pull_to_refresh_progress);
+        mRefreshViewLastUpdated =
+            (TextView) mRefreshView.findViewById(R.id.pull_to_refresh_updated_at);
+
+        mRefreshViewImage.setMinimumHeight(50);
+        mRefreshView.setOnClickListener(new OnClickRefreshListener());
+        mRefreshOriginalTopPadding = mRefreshView.getPaddingTop();
+
+        mRefreshState = TAP_TO_REFRESH;
+
+        addHeaderView(mRefreshView);
+
+        super.setOnScrollListener(this);
+
+        measureView(mRefreshView);
+        mRefreshViewHeight = mRefreshView.getMeasuredHeight();
+    }
+
+    @Override
+    protected void onAttachedToWindow() {
+        super.onAttachedToWindow();
+        setSelection(1);
+    }
+
+    @Override
+    public void setAdapter(ListAdapter adapter) {
+        super.setAdapter(adapter);
+
+        setSelection(1);
+    }
+
+    /**
+     * Set the listener that will receive notifications every time the list
+     * scrolls.
+     * 
+     * @param l The scroll listener. 
+     */
+    @Override
+    public void setOnScrollListener(AbsListView.OnScrollListener l) {
+        mOnScrollListener = l;
+    }
+
+    /**
+     * Register a callback to be invoked when this list should be refreshed.
+     * 
+     * @param onRefreshListener The callback to run.
+     */
+    public void setOnRefreshListener(OnRefreshListener onRefreshListener) {
+        mOnRefreshListener = onRefreshListener;
+    }
+
+    /**
+     * Set a text to represent when the list was last updated. 
+     * @param lastUpdated Last updated at.
+     */
+    public void setLastUpdated(CharSequence lastUpdated) {
+        if (lastUpdated != null) {
+            mRefreshViewLastUpdated.setVisibility(View.VISIBLE);
+            mRefreshViewLastUpdated.setText(lastUpdated);
+        } else {
+            mRefreshViewLastUpdated.setVisibility(View.GONE);
+        }
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent event) {
+        final int y = (int) event.getY();
+        mBounceHack = false;
+
+        switch (event.getAction()) {
+            case MotionEvent.ACTION_UP:
+                if (!isVerticalScrollBarEnabled()) {
+                    setVerticalScrollBarEnabled(true);
+                }
+                if (getFirstVisiblePosition() == 0 && mRefreshState != REFRESHING) {
+                    if ((mRefreshView.getBottom() >= mRefreshViewHeight
+                            || mRefreshView.getTop() >= 0)
+                            && mRefreshState == RELEASE_TO_REFRESH) {
+                        // Initiate the refresh
+                        mRefreshState = REFRESHING;
+                        prepareForRefresh();
+                        onRefresh();
+                    } else if (mRefreshView.getBottom() < mRefreshViewHeight
+                            || mRefreshView.getTop() <= 0) {
+                        // Abort refresh and scroll down below the refresh view
+                        resetHeader();
+                        setSelection(1);
+                    }
+                }
+                break;
+            case MotionEvent.ACTION_DOWN:
+                mLastMotionY = y;
+                break;
+            case MotionEvent.ACTION_MOVE:
+                applyHeaderPadding(event);
+                break;
+        }
+        return super.onTouchEvent(event);
+    }
+
+    private void applyHeaderPadding(MotionEvent ev) {
+        // getHistorySize has been available since API 1
+        int pointerCount = ev.getHistorySize();
+
+        for (int p = 0; p < pointerCount; p++) {
+            if (mRefreshState == RELEASE_TO_REFRESH) {
+                if (isVerticalFadingEdgeEnabled()) {
+                    setVerticalScrollBarEnabled(false);
+                }
+
+                int historicalY = (int) ev.getHistoricalY(p);
+
+                // Calculate the padding to apply, we divide by 1.7 to
+                // simulate a more resistant effect during pull.
+                int topPadding = (int) (((historicalY - mLastMotionY)
+                        - mRefreshViewHeight) / 1.7);
+
+                mRefreshView.setPadding(
+                        mRefreshView.getPaddingLeft(),
+                        topPadding,
+                        mRefreshView.getPaddingRight(),
+                        mRefreshView.getPaddingBottom());
+            }
+        }
+    }
+
+    /**
+     * Sets the header padding back to original size.
+     */
+    private void resetHeaderPadding() {
+        mRefreshView.setPadding(
+                mRefreshView.getPaddingLeft(),
+                mRefreshOriginalTopPadding,
+                mRefreshView.getPaddingRight(),
+                mRefreshView.getPaddingBottom());
+    }
+
+    /**
+     * Resets the header to the original state.
+     */
+    private void resetHeader() {
+        if (mRefreshState != TAP_TO_REFRESH) {
+            mRefreshState = TAP_TO_REFRESH;
+
+            resetHeaderPadding();
+
+            // Set refresh view text to the pull label
+            mRefreshViewText.setText(R.string.pull_to_refresh_tap_label);
+            // Replace refresh drawable with arrow drawable
+            mRefreshViewImage.setImageResource(R.drawable.ic_pulltorefresh_arrow);
+            // Clear the full rotation animation
+            mRefreshViewImage.clearAnimation();
+            // Hide progress bar and arrow.
+            mRefreshViewImage.setVisibility(View.GONE);
+            mRefreshViewProgress.setVisibility(View.GONE);
+        }
+    }
+
+    @SuppressWarnings("deprecation")
+	private void measureView(View child) {
+        ViewGroup.LayoutParams p = child.getLayoutParams();
+        if (p == null) {
+            p = new ViewGroup.LayoutParams(
+                    ViewGroup.LayoutParams.FILL_PARENT,
+                    ViewGroup.LayoutParams.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);
+    }
+
+    @Override
+    public void onScroll(AbsListView view, int firstVisibleItem,
+            int visibleItemCount, int totalItemCount) {
+        // When the refresh view is completely visible, change the text to say
+        // "Release to refresh..." and flip the arrow drawable.
+        if (mCurrentScrollState == SCROLL_STATE_TOUCH_SCROLL
+                && mRefreshState != REFRESHING) {
+            if (firstVisibleItem == 0) {
+                mRefreshViewImage.setVisibility(View.VISIBLE);
+                if ((mRefreshView.getBottom() >= mRefreshViewHeight + 20
+                        || mRefreshView.getTop() >= 0)
+                        && mRefreshState != RELEASE_TO_REFRESH) {
+                    mRefreshViewText.setText(R.string.pull_to_refresh_release_label);
+                    mRefreshViewImage.clearAnimation();
+                    mRefreshViewImage.startAnimation(mFlipAnimation);
+                    mRefreshState = RELEASE_TO_REFRESH;
+                } else if (mRefreshView.getBottom() < mRefreshViewHeight + 20
+                        && mRefreshState != PULL_TO_REFRESH) {
+                    mRefreshViewText.setText(R.string.pull_to_refresh_pull_label);
+                    if (mRefreshState != TAP_TO_REFRESH) {
+                        mRefreshViewImage.clearAnimation();
+                        mRefreshViewImage.startAnimation(mReverseFlipAnimation);
+                    }
+                    mRefreshState = PULL_TO_REFRESH;
+                }
+            } else {
+                mRefreshViewImage.setVisibility(View.GONE);
+                resetHeader();
+            }
+        } else if (mCurrentScrollState == SCROLL_STATE_FLING
+                && firstVisibleItem == 0
+                && mRefreshState != REFRESHING) {
+            setSelection(1);
+            mBounceHack = true;
+        } else if (mBounceHack && mCurrentScrollState == SCROLL_STATE_FLING) {
+            setSelection(1);
+        }
+
+        if (mOnScrollListener != null) {
+            mOnScrollListener.onScroll(view, firstVisibleItem,
+                    visibleItemCount, totalItemCount);
+        }
+    }
+
+    @Override
+    public void onScrollStateChanged(AbsListView view, int scrollState) {
+        mCurrentScrollState = scrollState;
+
+        if (mCurrentScrollState == SCROLL_STATE_IDLE) {
+            mBounceHack = false;
+        }
+
+        if (mOnScrollListener != null) {
+            mOnScrollListener.onScrollStateChanged(view, scrollState);
+        }
+    }
+
+    public void prepareForRefresh() {
+        resetHeaderPadding();
+
+        mRefreshViewImage.setVisibility(View.GONE);
+        // We need this hack, otherwise it will keep the previous drawable.
+        mRefreshViewImage.setImageDrawable(null);
+        mRefreshViewProgress.setVisibility(View.VISIBLE);
+
+        // Set refresh view text to the refreshing label
+        mRefreshViewText.setText(R.string.pull_to_refresh_refreshing_label);
+
+        mRefreshState = REFRESHING;
+    }
+
+    public void onRefresh() {
+        Log.d(TAG, "onRefresh");
+
+        if (mOnRefreshListener != null) {
+            mOnRefreshListener.onRefresh();
+        }
+    }
+
+    /**
+     * Resets the list to a normal state after a refresh.
+     * @param lastUpdated Last updated at.
+     */
+    public void onRefreshComplete(CharSequence lastUpdated) {
+        setLastUpdated(lastUpdated);
+        onRefreshComplete();
+    }
+
+    /**
+     * Resets the list to a normal state after a refresh.
+     */
+    public void onRefreshComplete() {        
+        Log.d(TAG, "onRefreshComplete");
+
+        resetHeader();
+
+        // If refresh view is visible when loading completes, scroll down to
+        // the next item.
+        if (mRefreshView.getBottom() > 0) {
+            invalidateViews();
+            setSelection(1);
+        }
+    }
+
+    /**
+     * Invoked when the refresh view is clicked on. This is mainly used when
+     * there's only a few items in the list and it's not possible to drag the
+     * list.
+     */
+    private class OnClickRefreshListener implements OnClickListener {
+
+        @Override
+        public void onClick(View v) {
+            if (mRefreshState != REFRESHING) {
+                prepareForRefresh();
+                onRefresh();
+            }
+        }
+
+    }
+
+    /**
+     * Interface definition for a callback to be invoked when list should be
+     * refreshed.
+     */
+    public interface OnRefreshListener {
+        /**
+         * Called when the list should be refreshed.
+         * <p>
+         * A call to {@link PullToRefreshListView #onRefreshComplete()} is
+         * expected to indicate that the refresh has completed.
+         */
+        public void onRefresh();
+    }
+}

File src/com/markupartist/android/widget/package-info.java

+/**
+ * 
+ */
+/**
+ * @author fredgrott
+ *
+ */
+package com.markupartist.android.widget;