可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
The way the ViewPager scrolls right now is by one item per gesture. It treats flinging gesture the same way no matter if it's full screen fast fling or slow dragging; at the end page advances one step only.
Is there any projects perhaps or examples that would add velocity-based flinging that scrolls multiple items based on velocity of the existing fling (if it still in progress) and scrolls further if the flinging gesture is wide and fast?
And if there's none where to start with something like this?
P.S. The bounty is offered. Please no answers with references to Gallery or HorizontalScrollView
回答1:
The technique here is to extends ViewPager
and mimic most of what the pager will be doing internally, coupled with scrolling logic from the Gallery
widget. The general idea is to monitor the fling (and velocity and accompanying scrolls) and then feed them in as fake drag events to the underlying ViewPager
. If you do this alone, it won't work though (you'll still get only one page scroll). This happens because the fake drag implements caps on the bounds that the scroll will be effective. You can mimic the calculations in the extended ViewPager
and detect when this will happen, then just flip the page and continue as usual. The benefit of using fake drag means you don't have to deal with snapping to pages or handling the edges of the ViewPager
.
I tested the following code on the animation demos example, downloadable from http://developer.android.com/training/animation/screen-slide.html by replacing the ViewPager in ScreenSlideActivity
with this VelocityViewPager
(both in the layout activity_screen_slide
and the field within the Activity).
/* * 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 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()
There are a few minor issues with this, which can be resolved easily but I will leave up to you, namely things like if you scroll (dragging, not flinging) you can end up half way between pages (you'll want to snap on the ACTION_UP event). Also, touch events are being completely overridden in order to do this, so you will need to feed relevant events to the underlying ViewPager
where appropriate.
回答2:
Another option is to copy a whole ViewPager
implementation source code from the support library and customize a determineTargetPage(...)
method. It's responsible for determining to which page to scroll to on fling gesture. This approach is not super convenient, but works pretty well. See implementation code below:
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 = 0) { normalDistance = remainingDistance; } else { page = i; break; } } return page; }
回答3:
I've found a better realization for me than in the checked answer, this ViewPager behaves better with touches when I want to stop scrolling https://github.com/Benjamin-Dobell/VelocityViewPager
回答4:
ViewPager is class from support library. Download support library source code and change about 10 lines of code in onTouchEvent method to add desired feature.
I use modified support library in my projects for about a year, because sometimes I need to modify several lines of code to make a little change or to add new method and I dont want to copy components source code. I use modified version of fragments and viewpager.
But there is one problem you'll get: once in about 6 mounth you have to merge custom support library with new official version if you need new features. And be careful with changes, you dont want to break support library classes compatibility.
回答5:
You can override ScrollView or HorizontalScrollView class, and add that behavior. There are many bugs in Gallery, and as I remember it's deprecated since api level 14.