4

プルして更新するときに新しいアイテムのみをロードする必要があります。残りのロードされたアイテムは、新しいアイテムがロードされた後に下に移動する必要があります。プルして更新すると、ロード中の 10,10 個のアイテムをもう一度ロードしています。新しく更新されたアイテムの上にロード済みのすべてのアイテムが必要です。

どうやってするか?

((PullAndLoadListView)getListView()).setOnRefreshListener(new OnRefreshListener(){public void onRefresh(){}}
4

1 に答える 1

6

私は間違いなくChrisBanesのpull-to-refreshの実装を見ていきます。彼のコードには、ListViewのこのインタラクションスタイルだけでなく、GridViewとWebViewも含まれています。

特に後者は、コンテンツにアダプターを使用しないビューのプル・トゥ・リフレッシュの実装例であるため、あなたのケースで興味深いでしょう。ソースコードを見ると、Banesのプロジェクトのすべての具体的なpull-to-refrehビューは、アニメーションと更新のロジックのほとんどを含む汎用のPullToRefreshBaseから拡張されていることがわかります。その基本クラスを持つことの利点は、TextViewなどの他のタイプのビューに対して同じことを行うのが非常に簡単なことです。

このアプローチの唯一の欠点は、実装が他のビューのラッパーであるということです。つまり、実際のビューを取得するために2、3行余分に書き込む必要があります。したがって、完全なドロップイン代替品ではありません。ただし、その機能と機能は、その小さな不便さをはるかに超えています。

編集済み:

私が実装した以下のコードを見てください

メインのJAVAファイル内:

PullToRefreshListView listview;

listview = (PullToRefreshListView) findViewById(R.id.airpost_listView);

    listview.setOnRefreshListener(new OnRefreshListener() {
        public void onRefresh() {
            // TODO Auto-generated method stub
            new GetPost().execute(); // AsyncTask where you can put your  logic when to call the listview for getting new data or let it be same as before
        }
    });

xmlファイルで定義されたリストビュー:

 <LinearLayout
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:background="@drawable/bhavesh"
        android:padding="5dp" >

        <com.FlightMate.AirpostTab.PullToRefreshListView
            android:id="@+id/airpost_listView"
            android:layout_width="fill_parent"
            android:layout_height="fill_parent"
            android:background="#ffffff"
            android:cacheColorHint="@android:color/transparent"
            android:divider="#00000000" />
    </LinearLayout>

これがPullToRefreshListViewです:

    package com.FlightMate.AirpostTab;

import android.content.Context;
import android.util.AttributeSet;
import android.widget.ListView;

public class PullToRefreshListView extends PullToRefreshBase<ListView> {

    public PullToRefreshListView(Context context) {
        super(context);
    }

    public PullToRefreshListView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    protected final ListView createAdapterView(Context context,
            AttributeSet attrs) {
        ListView lv = new ListView(context, attrs);
        // Set it to this so it can be used in ListActivity/ListFragment

        lv.setId(android.R.id.list);
        return lv;
    }

}

これがPullToRefreshBaseです:

    package com.FlightMate.AirpostTab;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.os.Handler;
import android.util.AttributeSet;
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.Interpolator;
import android.widget.AbsListView;
import android.widget.AbsListView.OnScrollListener;
import android.widget.LinearLayout;

import com.FlightMate.R;

public abstract class PullToRefreshBase<T extends AbsListView> extends
        LinearLayout implements OnTouchListener, OnScrollListener {

    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 startTime = -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();
        }

        // @Override
        public void run() {

            /**
             * Only set startTime if this is the first time we're starting, else
             * actually calculate the Y delta
             */
            if (startTime == -1) {
                startTime = System.currentTimeMillis();
            } else {

                /**
                 * We do do all calculations in long to reduce software float
                 * calculations. We use 1000 as it gives us good accuracy and
                 * small rounding errors
                 */
                long normalizedTime = (1000 * (System.currentTimeMillis() - startTime))
                        / 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 target 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;

    public static final int MODE_PULL_DOWN_TO_REFRESH = 0x1;
    public static final int MODE_PULL_UP_TO_REFRESH = 0x2;
    public static final int MODE_BOTH = 0x3;

    // ===========================================================
    // Fields
    // ===========================================================

    private int state = PULL_TO_REFRESH;
    private int mode = MODE_PULL_DOWN_TO_REFRESH;
    private int currentMode;
    private boolean disableScrollingWhileRefreshing = true;

    private T adapterView;
    private boolean isPullToRefreshEnabled = true;

    private LoadingLayout headerLayout;
    private LoadingLayout footerLayout;
    private int headerHeight;

    private final Handler handler = new Handler();

    private OnTouchListener onTouchListener;
    private OnRefreshListener onRefreshListener;
    private OnScrollListener onScrollListener;

    private OnLastItemVisibleListener onLastItemVisibleListener;
    private int lastSavedFirstVisibleItem = -1;

    private SmoothScrollRunnable currentSmoothScrollRunnable;

    private float startY = -1;
    private final float[] lastYs = new float[EVENT_COUNT];

    // ===========================================================
    // Constructors
    // ===========================================================

    public PullToRefreshBase(Context context) {
        this(context, null);
    }

    public PullToRefreshBase(Context context, int mode) {
        this(context);
        this.mode = mode;
    }

    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 final boolean isPullToRefreshEnabled() {
        return isPullToRefreshEnabled;
    }

    public void setDisableScrollingWhileRefreshing(
            boolean disableScrollingWhileRefreshing) {
        this.disableScrollingWhileRefreshing = disableScrollingWhileRefreshing;
    }

    /**
     * Mark the current Refresh as complete. Will Reset the UI and hide the
     * Refreshing View
     */
    public final void onRefreshComplete() {
        resetHeader();
    }

    public final void setOnLastItemVisibleListener(
            OnLastItemVisibleListener listener) {
        onLastItemVisibleListener = listener;
    }

    public final void setOnRefreshListener(OnRefreshListener listener) {
        onRefreshListener = listener;
    }

    /**
     * A mutator to enable/disable Pull-to-Refresh for the current AdapterView
     * 
     * @param enable
     *            Whether Pull-To-Refresh should be used
     */
    public final void setPullToRefreshEnabled(boolean enabled) {
        this.isPullToRefreshEnabled = enabled;
    }

    public final void setReleaseLabel(String releaseLabel) {
        if (null != headerLayout) {
            headerLayout.setReleaseLabel(releaseLabel);
        }
        if (null != footerLayout) {
            footerLayout.setReleaseLabel(releaseLabel);
        }
    }

    public final void setPullLabel(String pullLabel) {
        if (null != headerLayout) {
            headerLayout.setPullLabel(pullLabel);
        }
        if (null != footerLayout) {
            footerLayout.setPullLabel(pullLabel);
        }
    }

    public final void setRefreshingLabel(String refreshingLabel) {
        if (null != headerLayout) {
            headerLayout.setRefreshingLabel(refreshingLabel);
        }
        if (null != footerLayout) {
            footerLayout.setRefreshingLabel(refreshingLabel);
        }
    }

    // ===========================================================
    // Methods for/from SuperClass/Interfaces
    // ===========================================================

    public final void setOnScrollListener(OnScrollListener listener) {
        onScrollListener = listener;
    }

    @Override
    public final void setOnTouchListener(OnTouchListener listener) {
        onTouchListener = listener;
    }

    public final void onScroll(final AbsListView view,
            final int firstVisibleItem, final int visibleItemCount,
            final int totalItemCount) {

        if (null != onLastItemVisibleListener) {
            // detect if last item is visible
            if (visibleItemCount > 0 && visibleItemCount < totalItemCount
                    && (firstVisibleItem + visibleItemCount == totalItemCount)) {
                // only process first event
                if (firstVisibleItem != lastSavedFirstVisibleItem) {
                    lastSavedFirstVisibleItem = firstVisibleItem;
                    onLastItemVisibleListener.onLastItemVisible();
                }
            }
        }

        if (null != onScrollListener) {
            onScrollListener.onScroll(view, firstVisibleItem, visibleItemCount,
                    totalItemCount);
        }
    }

    public final void onScrollStateChanged(final AbsListView view,
            final int scrollState) {
        if (null != onScrollListener) {
            onScrollListener.onScrollStateChanged(view, scrollState);
        }
    }

    // @Override
    public final boolean onTouch(View view, MotionEvent ev) {
        if (isPullToRefreshEnabled) {
            // Returning true here stops the ListView being scrollable while we
            // refresh
            if (state == REFRESHING && disableScrollingWhileRefreshing) {
                return true;
            } else if (onAdapterViewTouch(view, ev)) {
                return true;
            }
        }

        if (null != onTouchListener) {
            return onTouchListener.onTouch(view, ev);
        }

        return false;
    }

    /**
     * This is implemented by derived classes to return the created AdapterView.
     * If you need to use a custom AdapterView (such as a custom ListView),
     * override 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.
     * 
     * @param context
     * @param attrs
     *            AttributeSet from wrapped class. Means that anything you
     *            include in the XML layout declaration 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();
        startY = -1;

        if (null != headerLayout) {
            headerLayout.reset();
        }
        if (null != footerLayout) {
            footerLayout.reset();
        }

        smoothScrollTo(0);
    }

    private void init(Context context, AttributeSet attrs) {

        setOrientation(LinearLayout.VERTICAL);

        // Styleables from XML
        final TypedArray a = context.obtainStyledAttributes(attrs,
                R.styleable.PullToRefresh);
        mode = a.getInteger(R.styleable.PullToRefresh_mode,
                MODE_PULL_DOWN_TO_REFRESH);

        // AdapterView
        // By passing the attrs, we can add ListView/GridView params via XML
        adapterView = this.createAdapterView(context, attrs);
        adapterView.setOnTouchListener(this);
        adapterView.setOnScrollListener(this);
        addView(adapterView, new LinearLayout.LayoutParams(
                LayoutParams.FILL_PARENT, 0, 1.0f));

        // Loading View Strings
        String pullLabel = context
                .getString(R.string.pull_to_refresh_pull_label);
        String refreshingLabel = context
                .getString(R.string.pull_to_refresh_refreshing_label);
        String releaseLabel = context
                .getString(R.string.pull_to_refresh_release_label);

        // Add Loading Views
        if (mode == MODE_PULL_DOWN_TO_REFRESH || mode == MODE_BOTH) {
            headerLayout = new LoadingLayout(context,
                    MODE_PULL_DOWN_TO_REFRESH, releaseLabel, pullLabel,
                    refreshingLabel);
            addView(headerLayout, 0, new LinearLayout.LayoutParams(
                    ViewGroup.LayoutParams.FILL_PARENT,
                    ViewGroup.LayoutParams.WRAP_CONTENT));
            measureView(headerLayout);
            headerHeight = headerLayout.getMeasuredHeight();
        }
        if (mode == MODE_PULL_UP_TO_REFRESH || mode == MODE_BOTH) {
            footerLayout = new LoadingLayout(context, MODE_PULL_UP_TO_REFRESH,
                    releaseLabel, pullLabel, refreshingLabel);
            addView(footerLayout, new LinearLayout.LayoutParams(
                    ViewGroup.LayoutParams.FILL_PARENT,
                    ViewGroup.LayoutParams.WRAP_CONTENT));
            measureView(footerLayout);
            headerHeight = footerLayout.getMeasuredHeight();
        }

        // Styleables from XML
        if (a.hasValue(R.styleable.PullToRefresh_headerTextColor)) {
            final int color = a.getColor(
                    R.styleable.PullToRefresh_headerTextColor, Color.BLACK);
            if (null != headerLayout) {
                headerLayout.setTextColor(color);
            }
            if (null != footerLayout) {
                footerLayout.setTextColor(color);
            }
        }
        if (a.hasValue(R.styleable.PullToRefresh_headerBackground)) {
            this.setBackgroundResource(a.getResourceId(
                    R.styleable.PullToRefresh_headerBackground, Color.WHITE));
        }
        if (a.hasValue(R.styleable.PullToRefresh_adapterViewBackground)) {
            adapterView.setBackgroundResource(a.getResourceId(
                    R.styleable.PullToRefresh_adapterViewBackground,
                    Color.WHITE));
        }
        a.recycle();

        // Hide Loading Views
        switch (mode) {
        case MODE_BOTH:
            setPadding(getPaddingLeft(), -headerHeight, getPaddingRight(),
                    -headerHeight);
            break;
        case MODE_PULL_UP_TO_REFRESH:
            setPadding(getPaddingLeft(), getPaddingTop(), getPaddingRight(),
                    -headerHeight);
            break;
        case MODE_PULL_DOWN_TO_REFRESH:
        default:
            setPadding(getPaddingLeft(), -headerHeight, getPaddingRight(),
                    getPaddingBottom());
            break;
        }

        // If we're not using MODE_BOTH, then just set currentMode to current
        // mode
        if (mode != MODE_BOTH) {
            currentMode = mode;
        }
    }

    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);
    }

    private boolean onAdapterViewTouch(View view, MotionEvent event) {

        switch (event.getAction()) {
        case MotionEvent.ACTION_MOVE:
            updateEventStates(event);

            if (isPullingToRefresh() && startY == -1) {
                startY = event.getY();

                // Need to set current Mode if we're using both
                if (mode == MODE_BOTH) {
                    if (isUserDraggingDownwards()) {
                        currentMode = MODE_PULL_DOWN_TO_REFRESH;
                    } else if (isUserDraggingUpwards()) {
                        currentMode = MODE_PULL_UP_TO_REFRESH;
                    }
                }
                return false;
            }

            if (startY != -1 && !adapterView.isPressed()) {
                pullEvent(event, startY);
                return true;
            }
            break;
        case MotionEvent.ACTION_UP:
            initializeYsHistory();
            startY = -1;

            if (state == RELEASE_TO_REFRESH) {
                setRefreshing();
                if (onRefreshListener != null) {
                    onRefreshListener.onRefresh();
                }
            } else {
                smoothScrollTo(0);
            }
            break;
        }

        return false;
    }

    private void pullEvent(MotionEvent event, float firstY) {
        float averageY = average(lastYs);

        final int height;
        switch (currentMode) {
        case MODE_PULL_UP_TO_REFRESH:
            height = (int) Math.max(firstY - averageY, 0);
            setHeaderScroll(height);
            break;
        case MODE_PULL_DOWN_TO_REFRESH:
        default:
            height = (int) Math.max(averageY - firstY, 0);
            setHeaderScroll(-height);
            break;
        }

        if (state == PULL_TO_REFRESH && headerHeight < height) {
            state = RELEASE_TO_REFRESH;
            if (null != headerLayout) {
                headerLayout.releaseToRefresh();
            }
            if (null != footerLayout) {
                footerLayout.releaseToRefresh();
            }
        } else if (state == RELEASE_TO_REFRESH && headerHeight >= height) {
            state = PULL_TO_REFRESH;
            if (null != headerLayout) {
                headerLayout.pullToRefresh();
            }
            if (null != footerLayout) {
                footerLayout.pullToRefresh();
            }
        }
    }

    private void setHeaderScroll(int y) {
        scrollTo(0, y);
    }

    private void setRefreshing() {
        state = REFRESHING;
        if (null != headerLayout) {
            headerLayout.refreshing();
        }
        if (null != footerLayout) {
            footerLayout.refreshing();
        }

        switch (currentMode) {
        case MODE_PULL_DOWN_TO_REFRESH:
            smoothScrollTo(-headerHeight);
            break;
        case MODE_PULL_UP_TO_REFRESH:
            smoothScrollTo(headerHeight);
            break;
        }
    }

    private float average(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, z = event.getHistorySize(); i < z; i++) {
            this.updateEventStates(event.getHistoricalY(i));
        }

        this.updateEventStates(event.getY());
    }

    private void updateEventStates(float y) {
        for (int i = 0; i < EVENT_COUNT - 1; i++) {
            lastYs[i] = lastYs[i + 1];
        }

        lastYs[EVENT_COUNT - 1] = y;
    }

    private boolean isPullingToRefresh() {
        if (isPullToRefreshEnabled && state != REFRESHING) {
            switch (mode) {
            case MODE_PULL_DOWN_TO_REFRESH:
                return isFirstItemVisible() && isUserDraggingDownwards();
            case MODE_PULL_UP_TO_REFRESH:
                return isLastItemVisible() && isUserDraggingUpwards();
            case MODE_BOTH:
                return (isFirstItemVisible() && isUserDraggingDownwards())
                        || (isLastItemVisible() && isUserDraggingUpwards());
            }
        }
        return false;
    }

    private boolean isFirstItemVisible() {
        if (this.adapterView.getCount() == 0) {
            return true;
        } else if (adapterView.getFirstVisiblePosition() == 0) {
            return adapterView.getChildAt(0).getTop() >= adapterView.getTop();
        } else {
            return false;
        }
    }

    private boolean isLastItemVisible() {
        final int count = this.adapterView.getCount();
        if (count == 0) {
            return true;
        } else if (adapterView.getLastVisiblePosition() == count - 1) {
            return true;
        } else {
            return false;
        }
    }

    private boolean isUserDraggingDownwards() {
        return this.isUserDraggingDownwards(0, EVENT_COUNT - 1);
    }

    private boolean isUserDraggingDownwards(int from, int to) {
        return lastYs[from] != 0 && lastYs[to] != 0
                && Math.abs(lastYs[from] - lastYs[to]) > 10
                && lastYs[from] < lastYs[to];
    }

    private boolean isUserDraggingUpwards() {
        return this.isUserDraggingUpwards(0, EVENT_COUNT - 1);
    }

    private boolean isUserDraggingUpwards(int from, int to) {
        return lastYs[from] != 0 && lastYs[to] != 0
                && Math.abs(lastYs[to] - lastYs[from]) > 10
                && lastYs[to] < lastYs[from];
    }

    private void smoothScrollTo(int y) {
        if (null != currentSmoothScrollRunnable) {
            currentSmoothScrollRunnable.stop();
        }

        this.currentSmoothScrollRunnable = new SmoothScrollRunnable(handler,
                getScrollY(), y);
        handler.post(currentSmoothScrollRunnable);
    }

    // ===========================================================
    // Inner and Anonymous Classes
    // ===========================================================

    public static interface OnRefreshListener {

        public void onRefresh();

    }

    public static interface OnLastItemVisibleListener {

        public void onLastItemVisible();

    }

}

それがあなたの問題からあなたを助けることを願っています。それでも問題が解決しない場合はお知らせください。

于 2012-08-17T05:24:25.413 に答える