44

ViewPager が現在スクロールする方法は、ジェスチャごとに 1 つの項目です。フリンジ ジェスチャは、フル スクリーンの高速フリングであろうと低速ドラッグであろうと、同じように扱われます。最後のページでは、1 ステップだけ進みます。

既存のフリングの速度に基づいて複数のアイテムをスクロールし (まだ進行中の場合)、フリング ジェスチャが広くて速い場合はさらにスクロールする、速度ベースのフリングを追加するプロジェクトや例はありますか?

そして、このようなものから始める場所がない場合は?

PS賞金が提供されます。Gallery または Horizo​​ntalScrollView への参照を伴う回答はご遠慮ください

4

5 に答える 5

40

ここでのテクニックは、ウィジェットViewPagerからのスクロール ロジックと組み合わせて、ページャーが内部で行うことのほとんどを拡張して模倣することです。Gallery一般的な考え方は、フリング (および速度とそれに付随するスクロール) を監視し、それらを偽のドラッグ イベントとして基になる にフィードすることViewPagerです。ただし、これを単独で行うとうまくいきません (まだ 1 ページしかスクロールできません)。これは、偽のドラッグがスクロールが有効になる境界にキャップを実装するために発生します。拡張で計算を模倣し、ViewPagerこれがいつ発生するかを検出してから、ページをめくって通常どおり続行できます。フェイク ドラッグを使用する利点は、ページへのスナップや の端の処理に対処する必要がないことですViewPager

ViewPagerをこれに置き換えて(レイアウトとアクティビティ内のフィールドの両方で) http://developer.android.com/training/animation/screen-slide.htmlからダウンロード可能なアニメーション デモの例で次のコードをテストしました。)。ScreenSlideActivityVelocityViewPageractivity_screen_slide

/*
 * Copyright 2012 The Android Open Source Project
 *
 * 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.
 * 
 * Author: Dororo @ StackOverflow
 * An extended ViewPager which implements multiple page flinging.
 * 
 */

package com.example.android.animationsdemo;

import android.content.Context;
import android.support.v4.view.ViewPager;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.GestureDetector;
import android.widget.Scroller;

public class VelocityViewPager extends ViewPager implements GestureDetector.OnGestureListener {

private GestureDetector mGestureDetector;
private FlingRunnable mFlingRunnable = new FlingRunnable();
private boolean mScrolling = false;

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

public VelocityViewPager(Context context, AttributeSet attrs) {
    super(context, attrs);
    mGestureDetector = new GestureDetector(context, this);
}

// We have to intercept this touch event else fakeDrag functions won't work as it will
// be in a real drag when we want to initialise the fake drag.
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
    return true;
}

@Override
public boolean onTouchEvent(MotionEvent event) {
    // give all the events to the gesture detector. I'm returning true here so the viewpager doesn't
    // get any events at all, I'm sure you could adjust this to make that not true.
    mGestureDetector.onTouchEvent(event);
    return true;
}

@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velX, float velY) {
    mFlingRunnable.startUsingVelocity((int)velX);
    return false;
}

private void trackMotion(float distX) {

    // The following mimics the underlying calculations in ViewPager
    float scrollX = getScrollX() - distX;
    final int width = getWidth();
    final int widthWithMargin = width + this.getPageMargin();
    final float leftBound = Math.max(0, (this.getCurrentItem() - 1) * widthWithMargin);
    final float rightBound = Math.min(this.getCurrentItem() + 1, this.getAdapter().getCount() - 1) * widthWithMargin;

    if (scrollX < leftBound) {
        scrollX = leftBound;
        // Now we know that we've hit the bound, flip the page
        if (this.getCurrentItem() > 0) {
            this.setCurrentItem(this.getCurrentItem() - 1, false);
        }
    } 
    else if (scrollX > rightBound) {
        scrollX = rightBound;
        // Now we know that we've hit the bound, flip the page
        if (this.getCurrentItem() < (this.getAdapter().getCount() - 1) ) {
            this.setCurrentItem(this.getCurrentItem() + 1, false);
        }
    }

    // Do the fake dragging
    if (mScrolling) {
        this.fakeDragBy(distX);
    }
    else {
        this.beginFakeDrag();
        this.fakeDragBy(distX);
        mScrolling = true;
    }

}

private void endFlingMotion() {
    mScrolling = false;
    this.endFakeDrag();
}

// The fling runnable which moves the view pager and tracks decay
private class FlingRunnable implements Runnable {
    private Scroller mScroller; // use this to store the points which will be used to create the scroll
    private int mLastFlingX;

    private FlingRunnable() {
        mScroller = new Scroller(getContext());
    }

    public void startUsingVelocity(int initialVel) {
        if (initialVel == 0) {
            // there is no velocity to fling!
            return;
        }

        removeCallbacks(this); // stop pending flings

        int initialX = initialVel < 0 ? Integer.MAX_VALUE : 0;
        mLastFlingX = initialX;
        // setup the scroller to calulate the new x positions based on the initial velocity. Impose no cap on the min/max x values.
        mScroller.fling(initialX, 0, initialVel, 0, 0, Integer.MAX_VALUE, 0, Integer.MAX_VALUE);

        post(this);
    }

    private void endFling() {
        mScroller.forceFinished(true);
        endFlingMotion();
    }

    @Override
    public void run() {

        final Scroller scroller = mScroller;
        boolean animationNotFinished = scroller.computeScrollOffset();
        final int x = scroller.getCurrX();
        int delta = x - mLastFlingX;

        trackMotion(delta); 

        if (animationNotFinished) {
            mLastFlingX = x;
            post(this);
        }
        else {
            endFling();
        }

    }
}

@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distX, float distY) {
    trackMotion(-distX);
    return false;
}

    // Unused Gesture Detector functions below

@Override
public boolean onDown(MotionEvent event) {
    return false;
}

@Override
public void onLongPress(MotionEvent event) {
    // we don't want to do anything on a long press, though you should probably feed this to the page being long-pressed.
}

@Override
public void onShowPress(MotionEvent event) {
    // we don't want to show any visual feedback
}

@Override
public boolean onSingleTapUp(MotionEvent event) {
    // we don't want to snap to the next page on a tap so ignore this
    return false;
}

}

これにはいくつかの小さな問題があり、簡単に解決できますが、あなたに任せます。具体的には、スクロールすると (ドラッグではなく、フリンジではなく)、ページ間の途中で終了する可能性があります (スナップしたいでしょう)。 ACTION_UP イベント)。また、タッチ イベントはこれを行うために完全にオーバーライドされるため、関連するイベントを必要に応じて基になるものにフィードする必要がViewPagerあります。

于 2013-02-19T23:54:33.730 に答える
4

ViewPagerもう 1 つのオプションは、サポート ライブラリから実装ソース コード全体をコピーし、determineTargetPage(...)メソッドをカスタマイズすることです。フリングジェスチャでスクロールするページを決定する責任があります。このアプローチはあまり便利ではありませんが、かなりうまく機能します。以下の実装コードを参照してください。

private int determineTargetPage(int curPage, float pageOffset, int velocity, int dx) {
    int target;
    if (Math.abs(dx) > mFlingDistance && Math.abs(velocity) > mMinimumVelocity) {
        target = calculateFinalPage(curPage, velocity);
    } else {
        final float truncator = curPage >= mCurItem ? 0.4f : 0.6f;
        target = (int) (curPage + pageOffset + truncator);
    }
    if (mItems.size() > 0) {
        final ItemInfo first = mItems.get(0);
        final ItemInfo last = mItems.get(mItems.size() - 1);

        // Only let the user target pages we have items for
        target = Math.max(first.position, Math.min(target, last.position));
    }
    return target;
}

private int calculateFinalPage(int curPage, int velocity) {
    float distance = Math.abs(velocity) * MAX_SETTLE_DURATION / 1000f;
    float normalDistance = (float) Math.sqrt(distance / 2) * 25;
    int step = (int) - Math.signum(velocity);
    int width = getClientWidth();
    int page = curPage;
    for (int i = curPage; i >= 0 && i < mAdapter.getCount(); i += step) {
        float pageWidth = mAdapter.getPageWidth(i);
        float remainingDistance = normalDistance - pageWidth * width;
        if (remainingDistance >= 0) {
            normalDistance = remainingDistance;
        } else {
            page = i;
            break;
        }
    }
    return page;
}
于 2014-03-12T15:10:03.990 に答える
2

チェックされた回答よりも優れた認識を見つけました。スクロールを停止したい場合、このViewPagerはタッチでより適切に動作し ます https://github.com/Benjamin-Dobell/VelocityViewPager

于 2016-08-19T10:02:52.670 に答える
1

ViewPager はサポート ライブラリのクラスです。サポート ライブラリのソース コードをダウンロードし、onTouchEvent メソッドの約 10 行のコードを変更して、必要な機能を追加します。

私は自分のプロジェクトで約 1 年間、修正されたサポート ライブラリを使用しています。これは、いくつかのコード行を変更して少し変更したり、新しいメソッドを追加したりする必要があり、コンポーネントのソース コードをコピーしたくないためです。フラグメントとビューページャーの修正版を使用しています。

しかし、1 つ問題があります。約 6 月に 1 回、新しい機能が必要な場合、カスタム サポート ライブラリを新しい公式バージョンにマージする必要があります。また、変更には注意してください。サポート ライブラリ クラスの互換性を壊したくありません。

于 2013-02-26T10:30:45.063 に答える
-1

ScrollView または Horizo​​ntalScrollView クラスをオーバーライドして、その動作を追加できます。ギャラリーには多くのバグがあり、私が覚えているように、API レベル 14 から廃止されました。

于 2012-10-16T08:39:52.517 に答える