Android VIewpager Как добиться связанного эффекта

Гуглю вопрос много дней. Найдите решение, которое добавляет пустой вид в первый и конец Viewpager. когда viewpager.getcurrentItem()==0, то переукажите значение 1. На мой взгляд, этот подход очень плохой。 Потому что это решение изменяет viewpager.getcount(). может вызвать много проблем.

Я нашел класс для обработки этого, http://developer.android.com/reference/android/support/v4/widget/EdgeEffectCompat.html Но я не знаю, как им пользоваться! Может кто-нибудь привести пример?

вот так https://docs.google.com/open?id=0B9poNqa96rJyLU5Va0lnMHJGY0k

Когда я отпускаю палец обратно в исходное положение


person Tony    schedule 07.12.2012    source источник
comment
связанный эффект? или эффект отскока, какой отскок вы ищете, пожалуйста, уточните   -  person Shakti    schedule 07.12.2012
comment
Изображение было загружено в документ Google. можно найти в вопросе   -  person Tony    schedule 10.12.2012
comment
ИЛИ Нравится этот вопрос stackoverflow.com /вопросы/10773565/   -  person Tony    schedule 11.12.2012
comment
вы говорите, что приведенное выше решение изменит значение viewpager.getcount(). Но вы можете переопределить его, чтобы указать значение по вашему выбору. Сделайте getRealCount() для других случаев, когда вам нужна реальная стоимость.   -  person Shakti    schedule 11.12.2012
comment
сделать эффект прокрутки, как резиновая лента на iOS   -  person Tony    schedule 11.12.2012
comment
У них есть патент на это :P   -  person Shakti    schedule 11.12.2012


Ответы (2)


Если кто-то все еще задается вопросом, как это сделать, я нашел решение, основанное на блестящей библиотеке от renard314: https://github.com/inovex/ViewPager3D Вся заслуга принадлежит ему :) В любом случае, расширяет ViewPager:

public class BounceBackViewPager extends ViewPager {

/**
 * maximum z distance to translate child view
 */
final static int DEFAULT_OVERSCROLL_TRANSLATION = 150;

/**
 * duration of overscroll animation in ms
 */
final private static int DEFAULT_OVERSCROLL_ANIMATION_DURATION = 400;

@SuppressWarnings("unused")
private final static String DEBUG_TAG = ViewPager.class.getSimpleName();
private final static int INVALID_POINTER_ID = -1;

/**
 * 
 * @author renard, extended by Piotr Zawadzki
 * 
 */
private class OverscrollEffect {
    private float mOverscroll;
    private Animator mAnimator;

    /**
     * @param deltaDistance [0..1] 0->no overscroll, 1>full overscroll
     */
    public void setPull(final float deltaDistance) {
        mOverscroll = deltaDistance;
        invalidateVisibleChilds(mLastPosition);
    }

    /**
     * called when finger is released. starts to animate back to default position
     */
    private void onRelease() {
        if (mAnimator != null && mAnimator.isRunning()) {
            mAnimator.addListener(new AnimatorListener() {

                @Override
                public void onAnimationStart(Animator animation) {
                }

                @Override
                public void onAnimationRepeat(Animator animation) {
                }

                @Override
                public void onAnimationEnd(Animator animation) {
                    startAnimation(0);
                }

                @Override
                public void onAnimationCancel(Animator animation) {
                }
            });
            mAnimator.cancel();
        } else {
            startAnimation(0);
        }
    }

    private void startAnimation(final float target) {
        mAnimator = ObjectAnimator.ofFloat(this, "pull", mOverscroll, target);
        mAnimator.setInterpolator(new DecelerateInterpolator());
        final float scale = Math.abs(target - mOverscroll);
        mAnimator.setDuration((long) (mOverscrollAnimationDuration * scale));
        mAnimator.start();
    }

    private boolean isOverscrolling() {
        if (mScrollPosition == 0 && mOverscroll < 0) {
            return true;
        }
        final boolean isLast = (getAdapter().getCount() - 1) == mScrollPosition;
        if (isLast && mOverscroll > 0) {
            return true;
        }
        return false;
    }

}

final private OverscrollEffect mOverscrollEffect = new OverscrollEffect();
final private Camera mCamera = new Camera();

private OnPageChangeListener mScrollListener;
private float mLastMotionX;
private int mActivePointerId;
private int mScrollPosition;
private float mScrollPositionOffset;
final private int mTouchSlop;

private float mOverscrollTranslation;
private int mOverscrollAnimationDuration;

public BounceBackViewPager(Context context, AttributeSet attrs) {
    super(context, attrs);
    setStaticTransformationsEnabled(true);
    final ViewConfiguration configuration = ViewConfiguration.get(context);
    mTouchSlop = ViewConfigurationCompat.getScaledPagingTouchSlop(configuration);
    super.setOnPageChangeListener(new MyOnPageChangeListener());
    init(attrs);
}

private void init(AttributeSet attrs) {
    TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.BounceBackViewPager);
    mOverscrollTranslation = a.getDimension(R.styleable.BounceBackViewPager_overscroll_translation, DEFAULT_OVERSCROLL_TRANSLATION);
    mOverscrollAnimationDuration = a.getInt(R.styleable.BounceBackViewPager_overscroll_animation_duration, DEFAULT_OVERSCROLL_ANIMATION_DURATION);
    a.recycle();
}

public int getOverscrollAnimationDuration() {
    return mOverscrollAnimationDuration;
}

public void setOverscrollAnimationDuration(int mOverscrollAnimationDuration) {
    this.mOverscrollAnimationDuration = mOverscrollAnimationDuration;
}

public float getOverscrollTranslation() {
    return mOverscrollTranslation;
}

public void setOverscrollTranslation(int mOverscrollTranslation) {
    this.mOverscrollTranslation = mOverscrollTranslation;
}

@Override
public void setOnPageChangeListener(OnPageChangeListener listener) {
    mScrollListener = listener;
};

private void invalidateVisibleChilds(final int position) {
     for (int i = 0; i < getChildCount(); i++) {
     getChildAt(i).invalidate();

     }
    //this.invalidate();
    // final View child = getChildAt(position);
    // final View previous = getChildAt(position - 1);
    // final View next = getChildAt(position + 1);
    // if (child != null) {
    // child.invalidate();
    // }
    // if (previous != null) {
    // previous.invalidate();
    // }
    // if (next != null) {
    // next.invalidate();
    // }
}

private int mLastPosition = 0;

private class MyOnPageChangeListener implements OnPageChangeListener {

    @Override
    public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
        if (mScrollListener != null) {
            mScrollListener.onPageScrolled(position, positionOffset, positionOffsetPixels);
        }
        mScrollPosition = position;
        mScrollPositionOffset = positionOffset;
        mLastPosition = position;
        invalidateVisibleChilds(position);
    }

    @Override
    public void onPageSelected(int position) {

        if (mScrollListener != null) {
            mScrollListener.onPageSelected(position);
        }
    }

    @Override
    public void onPageScrollStateChanged(final int state) {

        if (mScrollListener != null) {
            mScrollListener.onPageScrollStateChanged(state);
        }
        if (state == SCROLL_STATE_IDLE) {
            mScrollPositionOffset = 0;
        }
    }
}

@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
    try {
        final int action = ev.getAction() & MotionEventCompat.ACTION_MASK;
        switch (action) {
        case MotionEvent.ACTION_DOWN: {
            mLastMotionX = ev.getX();
            mActivePointerId = MotionEventCompat.getPointerId(ev, 0);
            break;
        }
        case MotionEventCompat.ACTION_POINTER_DOWN: {
            final int index = MotionEventCompat.getActionIndex(ev);
            final float x = MotionEventCompat.getX(ev, index);
            mLastMotionX = x;
            mActivePointerId = MotionEventCompat.getPointerId(ev, index);
            break;
        }
        }
        return super.onInterceptTouchEvent(ev);
    } catch (IllegalArgumentException e) {
        e.printStackTrace();
        return false;
    } catch (ArrayIndexOutOfBoundsException e) {
        e.printStackTrace();
        return false;
    }
}

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
    try {
        return super.dispatchTouchEvent(ev);
    } catch (IllegalArgumentException e) {
        e.printStackTrace();
        return false;
    } catch (ArrayIndexOutOfBoundsException e) {
        e.printStackTrace();
        return false;
    }
}

@Override
public boolean onTouchEvent(MotionEvent ev) {
    boolean callSuper = false;

    final int action = ev.getAction();
    switch (action) {
    case MotionEvent.ACTION_DOWN: {
        callSuper = true;
        mLastMotionX = ev.getX();
        mActivePointerId = MotionEventCompat.getPointerId(ev, 0);
        break;
    }
    case MotionEventCompat.ACTION_POINTER_DOWN: {
        callSuper = true;
        final int index = MotionEventCompat.getActionIndex(ev);
        final float x = MotionEventCompat.getX(ev, index);
        mLastMotionX = x;
        mActivePointerId = MotionEventCompat.getPointerId(ev, index);
        break;
    }
    case MotionEvent.ACTION_MOVE: {
        if (mActivePointerId != INVALID_POINTER_ID) {
            // Scroll to follow the motion event
            final int activePointerIndex = MotionEventCompat.findPointerIndex(ev, mActivePointerId);
            final float x = MotionEventCompat.getX(ev, activePointerIndex);
            final float deltaX = mLastMotionX - x;
            final float oldScrollX = getScrollX();
            final int width = getWidth();
            final int widthWithMargin = width + getPageMargin();
            final int lastItemIndex = getAdapter().getCount() - 1;
            final int currentItemIndex = getCurrentItem();
            final float leftBound = Math.max(0, (currentItemIndex - 1) * widthWithMargin);
            final float rightBound = Math.min(currentItemIndex + 1, lastItemIndex) * widthWithMargin;
            final float scrollX = oldScrollX + deltaX;
            if (mScrollPositionOffset == 0) {
                if (scrollX < leftBound) {
                    if (leftBound == 0) {
                        final float over = deltaX + mTouchSlop;
                        mOverscrollEffect.setPull(over / width);
                    }
                } else if (scrollX > rightBound) {
                    if (rightBound == lastItemIndex * widthWithMargin) {
                        final float over = scrollX - rightBound - mTouchSlop;
                        mOverscrollEffect.setPull(over / width);
                    }
                }
            } else {
                mLastMotionX = x;
            }
        } else {
            mOverscrollEffect.onRelease();
        }
        break;
    }
    case MotionEvent.ACTION_UP:
    case MotionEvent.ACTION_CANCEL: {
        callSuper = true;
        mActivePointerId = INVALID_POINTER_ID;
        mOverscrollEffect.onRelease();
        break;
    }
    case MotionEvent.ACTION_POINTER_UP: {
        final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
        final int pointerId = MotionEventCompat.getPointerId(ev, pointerIndex);
        if (pointerId == mActivePointerId) {
            // This was our active pointer going up. Choose a new
            // active pointer and adjust accordingly.
            final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
            mLastMotionX = ev.getX(newPointerIndex);
            mActivePointerId = MotionEventCompat.getPointerId(ev, newPointerIndex);
            callSuper = true;
        }
        break;
    }
    }

    if (mOverscrollEffect.isOverscrolling() && !callSuper) {
        return true;
    } else {
        return super.onTouchEvent(ev);
    }
}

@Override
protected boolean getChildStaticTransformation(View child, Transformation t) {
    if (child.getWidth() == 0) {
        return false;
    }
    final int position = child.getLeft() / child.getWidth();
    final boolean isFirstOrLast = position == 0 || (position == getAdapter().getCount() - 1);
    if (mOverscrollEffect.isOverscrolling() && isFirstOrLast) {
        final float dx = getWidth() / 2;
        final int dy = getHeight() / 2;
        t.getMatrix().reset();
        final float translateX =(float) mOverscrollTranslation * (mOverscrollEffect.mOverscroll > 0 ? Math.min(mOverscrollEffect.mOverscroll, 1) : Math.max(mOverscrollEffect.mOverscroll, -1));
        mCamera.save();
        mCamera.translate(-translateX, 0, 0);
        mCamera.getMatrix(t.getMatrix());
        mCamera.restore();
        t.getMatrix().preTranslate(-dx, -dy);
        t.getMatrix().postTranslate(dx, dy);

        if (getChildCount() == 1) {
            this.invalidate();
        } else {
            child.invalidate();
        }
        return true;
    }
    return false;
}

}

и добавьте файл ресурсов куда-нибудь в папку values:

<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="BounceBackViewPager">
    <!--
    determines the maximum amount of translation along the z-axis during the overscroll. 
    Default is 150.
    -->
    <attr name="overscroll_translation" format="dimension" />

    <!-- Duration of animation when user releases the over scroll. Default is 400 ms. -->
    <attr name="overscroll_animation_duration" format="integer" />
</declare-styleable>

All I had to do was to change the transformation to support translateX. I strongly recommend reading the documentation for ViewPager3D first.

person Piotr Zawadzki    schedule 02.07.2013
comment
Чтобы добавить к отличному ответу Петра, если у кого-то будет такая же проблема, как у меня (аниматор доступен только из Api 11), добавив такое поведение в Api ниже 11, вы можете использовать другую замечательную библиотеку - NineOldAndroids, которая экспортировала все этот API для анимаций, которые были запущены в Honeycomb, которые можно эффективно использовать в приложениях с минимальным API 1. Получил ответ здесь: stackoverflow.com/questions/10267291/animation-before-api-11 - person Lucas; 16.10.2013
comment
Отличный ответ. Это работает только для меня при прокрутке влево на первом элементе и не делает этого эффекта при прокрутке вправо на последнем. Я что-то пропустил? - person Josh Laird; 25.10.2016
comment
Я должен сказать, что я отображаю несколько представлений на странице, переопределяя getPageWidth() в моем адаптере для этого. - person Josh Laird; 25.10.2016

В BounceBackViewPager есть ошибка, когда при отображении нескольких страниц (фрагментов) на одной странице (экране) только первая страница имеет эффект OverscrollEffect, то есть вы можете видеть несколько страниц на экране, только первая страница вернется назад, это выглядит странный. И только левая сторона имеет эффект прокрутки, правая сторона не имеет.

мой инструмент:

@Override
public float getPageWidth(int position) {
          return    1/mShowPictureSize; 
}
person Jonguo    schedule 20.02.2014