Scroller的应用--滑屏实现
1、Scroller源代码分析
package android.widget; import android.content.Context;
import android.hardware.SensorManager;
import android.os.Build;
import android.util.FloatMath;
import android.view.ViewConfiguration;
import android.view.animation.AnimationUtils;
import android.view.animation.Interpolator; /**
* 这个类封装了滚动
* <p>This class encapsulates scrolling. You can use scrollers ({@link Scroller}
* or {@link OverScroller}) to collect the data you need to produce a scrolling
* animation—for example, in response to a fling gesture. Scrollers track
* scroll offsets for you over time, but they don't automatically apply those
* positions to your view. It's your responsibility to get and apply new
* coordinates at a rate that will make the scrolling animation look smooth.</p>
*
* <p>Here is a simple example:</p>
* 简单实例
* <pre> private Scroller mScroller = new Scroller(context);
* ...
* public void zoomIn() {
* // Revert(反复) any animation currently in progress
* mScroller.forceFinished(true);
* // Start scrolling by providing a starting point and
* // the distance to travel
* mScroller.startScroll(0, 0, 100, 0);
* // Invalidate to request a redraw
* invalidate();
* }</pre>
*
* <p>To track the changing positions of the x/y coordinates, use
* {@link #computeScrollOffset}. The method returns a boolean to indicate
* whether the scroller is finished. If it isn't, it means that a fling or
* programmatic pan operation is still in progress. You can use this method to
* find the current offsets of the x and y coordinates, for example:</p>
*
* <pre>if (mScroller.computeScrollOffset()) {
* // Get current x and y positions
* int currX = mScroller.getCurrX();
* int currY = mScroller.getCurrY();
* ...
* }</pre>
*/
public class Scroller {
private int mMode; //分为SCROLL_MODE和FLING_MODE private int mStartX;//起始坐标点,X轴方向
private int mStartY;//起始坐标点。Y轴方向
private int mFinalX;//滑动的终于位置,X轴方向
private int mFinalY;//滑动的终于位置。Y轴方向 private int mMinX;
private int mMaxX;
private int mMinY;
private int mMaxY; private int mCurrX;//当前坐标点 X轴。 即调用startScroll函数后,经过一定时间所达到的值
private int mCurrY;//当前坐标点 Y轴。 即调用startScroll函数后,经过一定时间所达到的值
private long mStartTime;
private int mDuration;
private float mDurationReciprocal;
private float mDeltaX;//应该继续滑动的距离, X轴方向
private float mDeltaY;//应该继续滑动的距离。 Y轴方向
private boolean mFinished;//是否已经完毕本次滑动操作。 假设完毕则为 true
// 被用来修饰动画效果,定义动画的变化率。能够使存在的动画效果accelerated(加速)。decelerated(减速),repeated(反复),bounced(弹跳)等
private Interpolator mInterpolator;
private boolean mFlywheel; private float mVelocity;
private float mCurrVelocity;
private int mDistance; //ViewConfiguration包括了方法和标准的常量用来设置UI的超时、大小和距离
private float mFlingFriction = ViewConfiguration.getScrollFriction(); private static final int DEFAULT_DURATION = 250;//默认的动画时间
private static final int SCROLL_MODE = 0;
private static final int FLING_MODE = 1; private static float DECELERATION_RATE = (float) (Math.log(0.78) / Math.log(0.9));//滑动减速
private static final float INFLEXION = 0.35f; // Tension lines cross at (INFLEXION, 1)
private static final float START_TENSION = 0.5f;
private static final float END_TENSION = 1.0f;
private static final float P1 = START_TENSION * INFLEXION;
private static final float P2 = 1.0f - END_TENSION * (1.0f - INFLEXION); private static final int NB_SAMPLES = 100;
private static final float[] SPLINE_POSITION = new float[NB_SAMPLES + 1];
private static final float[] SPLINE_TIME = new float[NB_SAMPLES + 1]; private float mDeceleration;
private final float mPpi; // A context-specific coefficient adjusted to physical values.
private float mPhysicalCoeff; static {
float x_min = 0.0f;
float y_min = 0.0f;
for (int i = 0; i < NB_SAMPLES; i++) {
final float alpha = (float) i / NB_SAMPLES; float x_max = 1.0f;
float x, tx, coef;
while (true) {
x = x_min + (x_max - x_min) / 2.0f;
coef = 3.0f * x * (1.0f - x);
tx = coef * ((1.0f - x) * P1 + x * P2) + x * x * x;
if (Math.abs(tx - alpha) < 1E-5) break;
if (tx > alpha) x_max = x;
else x_min = x;
}
SPLINE_POSITION[i] = coef * ((1.0f - x) * START_TENSION + x) + x * x * x; float y_max = 1.0f;
float y, dy;
while (true) {
y = y_min + (y_max - y_min) / 2.0f;
coef = 3.0f * y * (1.0f - y);
dy = coef * ((1.0f - y) * START_TENSION + y) + y * y * y;
if (Math.abs(dy - alpha) < 1E-5) break;
if (dy > alpha) y_max = y;
else y_min = y;
}
SPLINE_TIME[i] = coef * ((1.0f - y) * P1 + y * P2) + y * y * y;
}
SPLINE_POSITION[NB_SAMPLES] = SPLINE_TIME[NB_SAMPLES] = 1.0f; // This controls the viscous fluid effect (how much of it)
sViscousFluidScale = 8.0f;
// must be set to 1.0 (used in viscousFluid())
sViscousFluidNormalize = 1.0f;
sViscousFluidNormalize = 1.0f / viscousFluid(1.0f); } private static float sViscousFluidScale;
private static float sViscousFluidNormalize; /**
* Create a Scroller with the default duration and interpolator.
*/
public Scroller(Context context) {
this(context, null);
} /**
* Create a Scroller with the specified interpolator. If the interpolator is
* null, the default (viscous) interpolator will be used. "Flywheel" behavior will
* be in effect for apps targeting Honeycomb or newer.
*/
public Scroller(Context context, Interpolator interpolator) {
this(context, interpolator,
context.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.HONEYCOMB);
} /**
* Create a Scroller with the specified interpolator. If the interpolator is
* null, the default (viscous) interpolator will be used. Specify whether or
* not to support progressive "flywheel" behavior in flinging.
*/
public Scroller(Context context, Interpolator interpolator, boolean flywheel) {
mFinished = true;
mInterpolator = interpolator;
mPpi = context.getResources().getDisplayMetrics().density * 160.0f;
mDeceleration = computeDeceleration(ViewConfiguration.getScrollFriction());
mFlywheel = flywheel; mPhysicalCoeff = computeDeceleration(0.84f); // look and feel tuning
} /**
* The amount of friction applied to flings. The default value
* is {@link ViewConfiguration#getScrollFriction}.
*
* @param friction A scalar dimension-less value representing the coefficient of
* friction.
*摩擦,依据摩擦力,计算出减速
*/
public final void setFriction(float friction) {
mDeceleration = computeDeceleration(friction);
mFlingFriction = friction;
}
//减速
private float computeDeceleration(float friction) {
return SensorManager.GRAVITY_EARTH // g (m/s^2)
* 39.37f // inch/meter
* mPpi // pixels per inch
* friction;
} /**
*
* Returns whether the scroller has finished scrolling.
*
* @return True if the scroller has finished scrolling, false otherwise.
*/
public final boolean isFinished() {
return mFinished;
} /**
* Force the finished field to a particular value.
* //强制结束本次滑屏操作
* @param finished The new finished value.
*/
public final void forceFinished(boolean finished) {
mFinished = finished;
} /**
* Returns how long the scroll event will take, in milliseconds.
*
* @return The duration of the scroll in milliseconds.
*/
public final int getDuration() {
return mDuration;
} /**
* Returns the current X offset in the scroll.
*
* @return The new X offset as an absolute distance from the origin.
*/
public final int getCurrX() {
return mCurrX;
} /**
* Returns the current Y offset in the scroll.
*
* @return The new Y offset as an absolute distance from the origin.
*/
public final int getCurrY() {
return mCurrY;
} /**
* Returns the current velocity.
* 获取当前的速度。依据手势滑动还是自己滚动。假设是自己滚动,使用初始速度-减速,可能是负值
* @return The original velocity less the deceleration. Result may be
* negative.
*/
public float getCurrVelocity() {
return mMode == FLING_MODE ? mCurrVelocity : mVelocity - mDeceleration * timePassed() / 2000.0f;
} /**
* Returns the start X offset in the scroll.
*
* @return The start X offset as an absolute distance from the origin.
*/
public final int getStartX() {
return mStartX;
} /**
* Returns the start Y offset in the scroll.
*
* @return The start Y offset as an absolute distance from the origin.
*/
public final int getStartY() {
return mStartY;
} /**
* Returns where the scroll will end. Valid only for "fling" scrolls.
*
* @return The final X offset as an absolute distance from the origin.
*/
public final int getFinalX() {
return mFinalX;
} /**
* Returns where the scroll will end. Valid only for "fling" scrolls.
*
* @return The final Y offset as an absolute distance from the origin.
*/
public final int getFinalY() {
return mFinalY;
} /**
* Call this when you want to know the new location. If it returns true,
* the animation is not yet finished.
* 返回值为boolean,true说明滚动尚未完毕,false说明滚动已经完毕。这是一个非常重要的方法。
* 通常放在View.computeScroll()中。用来推断是否滚动是否结束
*/
public boolean computeScrollOffset() {
if (mFinished) {//已经完毕了本次动画控制,直接返回为false
return false;
}
//动画使用的时间
int timePassed = (int)(AnimationUtils.currentAnimationTimeMillis() - mStartTime); if (timePassed < mDuration) {
switch (mMode) {
case SCROLL_MODE:
float x = timePassed * mDurationReciprocal; if (mInterpolator == null)
x = viscousFluid(x);
else
x = mInterpolator.getInterpolation(x); mCurrX = mStartX + Math.round(x * mDeltaX);
mCurrY = mStartY + Math.round(x * mDeltaY);
break;
case FLING_MODE:
final float t = (float) timePassed / mDuration;
final int index = (int) (NB_SAMPLES * t);
float distanceCoef = 1.f;
float velocityCoef = 0.f;
if (index < NB_SAMPLES) {
final float t_inf = (float) index / NB_SAMPLES;
final float t_sup = (float) (index + 1) / NB_SAMPLES;
final float d_inf = SPLINE_POSITION[index];
final float d_sup = SPLINE_POSITION[index + 1];
velocityCoef = (d_sup - d_inf) / (t_sup - t_inf);
distanceCoef = d_inf + (t - t_inf) * velocityCoef;
} mCurrVelocity = velocityCoef * mDistance / mDuration * 1000.0f; mCurrX = mStartX + Math.round(distanceCoef * (mFinalX - mStartX));
// Pin to mMinX <= mCurrX <= mMaxX
mCurrX = Math.min(mCurrX, mMaxX);
mCurrX = Math.max(mCurrX, mMinX); mCurrY = mStartY + Math.round(distanceCoef * (mFinalY - mStartY));
// Pin to mMinY <= mCurrY <= mMaxY
mCurrY = Math.min(mCurrY, mMaxY);
mCurrY = Math.max(mCurrY, mMinY); if (mCurrX == mFinalX && mCurrY == mFinalY) {
mFinished = true;
} break;
}
}
else {
mCurrX = mFinalX;
mCurrY = mFinalY;
mFinished = true;
}
return true;
} /** 依据当前已经消逝的时间计算当前的坐标点。保存在mCurrX和mCurrY值中
* Start scrolling by providing a starting point and the distance to travel.
* The scroll will use the default value of 250 milliseconds for the
* duration.
* startX水平偏移量的起始位置,正号是向左滚动,
* @param startX Starting horizontal scroll offset in pixels. Positive
* numbers will scroll the content to the left.
* startY竖直偏移量起始位置。正号是向上滚动
* @param startY Starting vertical scroll offset in pixels. Positive numbers
* will scroll the content up.
* dx水平滑动距离,+向左
* @param dx Horizontal distance to travel. Positive numbers will scroll the
* content to the left.
* dy竖直滑动距离。+向上
* @param dy Vertical distance to travel. Positive numbers will scroll the
* content up.
*開始一个动画控制。由(startX , startY)在duration时间内前进(dx,dy)个单位,到达坐标为
* (startX+dx , startY+dy)处。
*/
public void startScroll(int startX, int startY, int dx, int dy) {
startScroll(startX, startY, dx, dy, DEFAULT_DURATION);
} /**
* Start scrolling by providing a starting point, the distance to travel,
* and the duration of the scroll.
* 滚动。startX, startY为開始滚动的位置。dx,dy为滚动的偏移量, duration为完毕滚动的时间
* @param startX Starting horizontal scroll offset in pixels. Positive
* numbers will scroll the content to the left.
* @param startY Starting vertical scroll offset in pixels. Positive numbers
* will scroll the content up.
* @param dx Horizontal distance to travel. Positive numbers will scroll the
* content to the left.
* @param dy Vertical distance to travel. Positive numbers will scroll the
* content up.
* @param duration Duration of the scroll in milliseconds.
*/
public void startScroll(int startX, int startY, int dx, int dy, int duration) {
mMode = SCROLL_MODE;
mFinished = false;
mDuration = duration;
mStartTime = AnimationUtils.currentAnimationTimeMillis();
mStartX = startX;
mStartY = startY;
mFinalX = startX + dx;
mFinalY = startY + dy;
mDeltaX = dx;
mDeltaY = dy;
mDurationReciprocal = 1.0f / (float) mDuration;
} /**
* Start scrolling based on a fling gesture. The distance travelled will
* depend on the initial velocity of the fling.
*
* @param startX Starting point of the scroll (X)
* @param startY Starting point of the scroll (Y)
* @param velocityX Initial velocity of the fling (X) measured in pixels per
* second.
* @param velocityY Initial velocity of the fling (Y) measured in pixels per
* second
* @param minX Minimum X value. The scroller will not scroll past this
* point.
* @param maxX Maximum X value. The scroller will not scroll past this
* point.
* @param minY Minimum Y value. The scroller will not scroll past this
* point.
* @param maxY Maximum Y value. The scroller will not scroll past this
* point.
*/
public void fling(int startX, int startY, int velocityX, int velocityY,
int minX, int maxX, int minY, int maxY) {
// Continue a scroll or fling in progress
if (mFlywheel && !mFinished) {
float oldVel = getCurrVelocity(); float dx = (float) (mFinalX - mStartX);
float dy = (float) (mFinalY - mStartY);
float hyp = FloatMath.sqrt(dx * dx + dy * dy); float ndx = dx / hyp;
float ndy = dy / hyp; float oldVelocityX = ndx * oldVel;
float oldVelocityY = ndy * oldVel;
if (Math.signum(velocityX) == Math.signum(oldVelocityX) &&
Math.signum(velocityY) == Math.signum(oldVelocityY)) {
velocityX += oldVelocityX;
velocityY += oldVelocityY;
}
} mMode = FLING_MODE;
mFinished = false; float velocity = FloatMath.sqrt(velocityX * velocityX + velocityY * velocityY); mVelocity = velocity;
mDuration = getSplineFlingDuration(velocity);
mStartTime = AnimationUtils.currentAnimationTimeMillis();
mStartX = startX;
mStartY = startY; float coeffX = velocity == 0 ? 1.0f : velocityX / velocity;
float coeffY = velocity == 0 ? 1.0f : velocityY / velocity; double totalDistance = getSplineFlingDistance(velocity);
mDistance = (int) (totalDistance * Math.signum(velocity)); mMinX = minX;
mMaxX = maxX;
mMinY = minY;
mMaxY = maxY; mFinalX = startX + (int) Math.round(totalDistance * coeffX);
// Pin to mMinX <= mFinalX <= mMaxX
mFinalX = Math.min(mFinalX, mMaxX);
mFinalX = Math.max(mFinalX, mMinX); mFinalY = startY + (int) Math.round(totalDistance * coeffY);
// Pin to mMinY <= mFinalY <= mMaxY
mFinalY = Math.min(mFinalY, mMaxY);
mFinalY = Math.max(mFinalY, mMinY);
} private double getSplineDeceleration(float velocity) {
return Math.log(INFLEXION * Math.abs(velocity) / (mFlingFriction * mPhysicalCoeff));
} private int getSplineFlingDuration(float velocity) {
final double l = getSplineDeceleration(velocity);
final double decelMinusOne = DECELERATION_RATE - 1.0;
return (int) (1000.0 * Math.exp(l / decelMinusOne));
} private double getSplineFlingDistance(float velocity) {
final double l = getSplineDeceleration(velocity);
final double decelMinusOne = DECELERATION_RATE - 1.0;
return mFlingFriction * mPhysicalCoeff * Math.exp(DECELERATION_RATE / decelMinusOne * l);
} static float viscousFluid(float x)
{
x *= sViscousFluidScale;
if (x < 1.0f) {
x -= (1.0f - (float)Math.exp(-x));
} else {
float start = 0.36787944117f; // 1/e == exp(-1)
x = 1.0f - (float)Math.exp(1.0f - x);
x = start + x * (1.0f - start);
}
x *= sViscousFluidNormalize;
return x;
} /**
* Stops the animation. Contrary to {@link #forceFinished(boolean)},
* aborting the animating cause the scroller to move to the final x and y
* position
* 终止动画。直接滑动到指定位置
* @see #forceFinished(boolean)
*/
public void abortAnimation() {
mCurrX = mFinalX;
mCurrY = mFinalY;
mFinished = true;
} /**
* Extend the scroll animation. This allows a running animation to scroll
* further and longer, when used with {@link #setFinalX(int)} or {@link #setFinalY(int)}.
* 延长滚动时间
* @param extend Additional time to scroll in milliseconds.
* @see #setFinalX(int)
* @see #setFinalY(int)
*/
public void extendDuration(int extend) {
int passed = timePassed();
mDuration = passed + extend;
mDurationReciprocal = 1.0f / mDuration;
mFinished = false;
} /**
* Returns the time elapsed since the beginning of the scrolling.
* 获得滚动经历的时间
* @return The elapsed time in milliseconds.
*/
public int timePassed() {
return (int)(AnimationUtils.currentAnimationTimeMillis() - mStartTime);
} /**
* Sets the final position (X) for this scroller.
*设置mScroller终于停留的水平位置,没有动画效果,直接跳到目标位置
* @param newX The new X offset as an absolute distance from the origin.
* @see #extendDuration(int)
* @see #setFinalY(int)
*/
public void setFinalX(int newX) {
mFinalX = newX;
mDeltaX = mFinalX - mStartX;
mFinished = false;
} /**
* Sets the final position (Y) for this scroller.
*设置mScroller终于停留的竖直位置,没有动画效果,直接跳到目标位置
* @param newY The new Y offset as an absolute distance from the origin.
* @see #extendDuration(int)
* @see #setFinalX(int)
*/
public void setFinalY(int newY) {
mFinalY = newY;
mDeltaY = mFinalY - mStartY;
mFinished = false;
} /**
* @hide
*/
public boolean isScrollingInDirection(float xvel, float yvel) {
return !mFinished && Math.signum(xvel) == Math.signum(mFinalX - mStartX) &&
Math.signum(yvel) == Math.signum(mFinalY - mStartY);
}
}
2、Scroller调用关系
1 调用public void startScroll(int startX, int startY, int dx, int dy)
该方法为scroll做一些准备工作.
比方设置了移动的起始坐标,滑动的距离和方向以及持续时间等.
该方法并非真正的滑动scroll的開始,感觉叫prepareScroll()更贴切些.
2 调用invalidate()或者postInvalidate()使View(ViewGroup)树重绘
重绘会调用View的draw()方法
draw()一共同拥有六步:
Draw traversal performs several drawing steps which must be executed
in the appropriate order:
1. Draw the background
2. If necessary, save the canvas' layers to prepare for fading
3. Draw view's content
4. Draw children
5. If necessary, draw the fading edges and restore layers
6. Draw decorations (scrollbars for instance)
当中最重要的是第三步和第四步
第三步会去调用onDraw()绘制内容
第四步会去调用dispatchDraw()绘制子View
重绘分两种情况:
2.1 ViewGroup的重绘
在完毕第三步onDraw()以后,进入第四步ViewGroup重写了
父类View的dispatchDraw()绘制子View,于是这样继续调用:
dispatchDraw()-->drawChild()-->child.computeScroll();
2.2 View的重绘
当View调用invalidate()方法时,会导致整个View树进行
从上至下的一次重绘.比方从最外层的Layout到里层的Layout,直到每一个子View.
在重绘View树时ViewGroup和View时按理都会经过onMeasure()和onLayout()以及
onDraw()方法.当然系统会推断这三个方法是否都必须运行,假设没有必要就不会调用.
看到这里就明确了:当这个子View的父容器重绘时,也会调用上面提到的线路:
onDraw()-->dispatchDraw()-->drawChild()-->child.computeScroll();
于是子View(比方此处举例的ButtonSubClass类)中重写的computeScroll()方法
就会被调用到.
3 View树的重绘会调用到View中的computeScroll()方法
4 在computeScroll()方法中
在View的源代码中能够看到public void computeScroll(){}是一个空方法.
具体的实现须要自己来写.在该方法中我们可调用scrollTo()或scrollBy()
来实现移动.该方法才是实现移动的核心.
4.1 利用Scroller的mScroller.computeScrollOffset()推断移动过程是否完毕
注意:该方法是Scroller中的方法而不是View中的!!!!!!
public boolean computeScrollOffset(){ }
Call this when you want to know the new location.
If it returns true,the animation is not yet finished.
loc will be altered to provide the new location.
返回true时表示还移动还没有完毕.
4.2 若动画没有结束,则调用:scrollTo(By)();
使其滑动scrolling
5 再次调用invalidate().
调用invalidate()方法那么又会重绘View树.
从而跳转到第3步,如此循环,直到computeScrollOffset返回false
通俗的理解:
从上可见Scroller运行流程里面的三个核心方法
mScroller.startScroll()
mScroller.computeScrollOffset()
view.computeScroll()
1 在mScroller.startScroll()中为滑动做了一些初始化准备.
比方:起始坐标,滑动的距离和方向以及持续时间(有默认值)等.
事实上除了这些,在该方法内还做了些其它事情:
比較重要的一点是设置了动画開始时间.
2 computeScrollOffset()方法主要是依据当前已经消逝的时间
来计算当前的坐标点而且保存在mCurrX和mCurrY值中.
由于在mScroller.startScroll()中设置了动画时间,那么
在computeScrollOffset()方法中依据已经消逝的时间就非常easy
得到当前时刻应该所处的位置并将其保存在变量mCurrX和mCurrY中.
除此之外该方法还可推断动画是否已经结束.
所以在该演示样例中:
@Override
public void computeScroll() {
super.computeScroll();
if (mScroller.computeScrollOffset()) {
scrollTo(mScroller.getCurrX(), 0);
invalidate();
}
}
先运行mScroller.computeScrollOffset()推断了滑动是否结束
2.1 返回false,滑动已经结束.
2.2 返回true,滑动还没有结束.
而且在该方法内部也计算了最新的坐标值mCurrX和mCurrY.
就是说在当前时刻应该滑动到哪里了.
既然computeScrollOffset()如此贴心,盛情难却啊!
于是我们就覆写View的computeScroll()方法,
调用scrollTo(By)滑动到那里
3、Android中View绘制流程以及invalidate()等相关方法分析
4、VelocityTracker、ViewConfiguration
VelocityTracker从字面意思理解那就是速度追踪器了。在滑动效果的开发中通常都是要使用该类计算出当前手势的初始速度。相应的方法是velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity))并通过getXVelocity或getYVelocity方法得到相应的速度值initialVelocity,并将获得的速度值传递给Scroller类的fling(int startX, int startY, int velocityX, int velocityY, int minX, int maxX, int minY, int maxY) 方法进行控件滚动时各种位置坐标数值的计算,API中对fling 方法的解释是基于一个fling手势開始滑动动作,滑动的距离将由所获得的初始速度initialVelocity来决定。
关于ViewConfiguration 的使用主要使用了该类的以下三个方法:
configuration.getScaledTouchSlop() //获得能够进行手势滑动的距离
configuration.getScaledMinimumFlingVelocity()//获得同意运行一个fling手势动作的最小速度值
configuration.getScaledMaximumFlingVelocity()//获得同意运行一个fling手势动作的最大速度值
须要重写的方法至少要包括以下几个方法:
onTouchEvent(MotionEvent event)//有手势操作必定少不了这种方法了
computeScroll()//必要时由父控件调用请求或通知其一个子节点须要更新它的mScrollX和mScrollY的值。典型的样例就是在一个子节点正在使用Scroller进行滑动动画时将会被运行。
所以。从该方法的凝视来看,继承这种方法的话一般都会有Scroller对象出现。
VelocityTracker的初始化以及资源释放的方法:
private void obtainVelocityTracker(MotionEvent event) {
if (mVelocityTracker == null) {
mVelocityTracker = VelocityTracker.obtain();
}
mVelocityTracker.addMovement(event);
} private void releaseVelocityTracker() {
if (mVelocityTracker != null) {
mVelocityTracker.recycle();
mVelocityTracker = null;
}
}
5、实例开发
package com.jwzhangjie.scrollview; import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Scroller; /**
*
* @author jwzhangjie
*/
public class MultiViewGroup extends ViewGroup { private VelocityTracker mVelocityTracker; // 用于推断甩动手势
private static final int SNAP_VELOCITY = 600; // X轴速度基值,大于该值时进行切换
private Scroller mScroller;// 滑动控制
private int mCurScreen; // 当前页面为第几屏
private int mDefaultScreen = 0;
private float mLastMotionX;// 记住上次触摸屏的位置
private int deltaX; private OnViewChangeListener mOnViewChangeListener; public MultiViewGroup(Context context) {
this(context, null);
} public MultiViewGroup(Context context, AttributeSet attrs) {
super(context, attrs);
init(getContext());
} private void init(Context context) {
mScroller = new Scroller(context);
mCurScreen = mDefaultScreen;
} @Override
public void computeScroll() {
if (mScroller.computeScrollOffset()) {// 会更新Scroller中的当前x,y位置
scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
postInvalidate();
}
} @Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int width = MeasureSpec.getSize(widthMeasureSpec);
int count = getChildCount();
for (int i = 0; i < count; i++) {
measureChild(getChildAt(i), widthMeasureSpec, heightMeasureSpec);
getChildAt(i).measure(widthMeasureSpec, heightMeasureSpec);
}
scrollTo(mCurScreen * width, 0);// 移动到第一页位置
} @Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int margeLeft = 0;
int size = getChildCount();
for (int i = 0; i < size; i++) {
View view = getChildAt(i);
if (view.getVisibility() != View.GONE) {
int childWidth = view.getMeasuredWidth();
// 将内部子孩子横排排列
view.layout(margeLeft, 0, margeLeft + childWidth,
view.getMeasuredHeight());
margeLeft += childWidth;
}
}
} @Override
public boolean onTouchEvent(MotionEvent event) {
int action = event.getAction();
float x = event.getX();
switch (action) {
case MotionEvent.ACTION_DOWN:
obtainVelocityTracker(event);
if (!mScroller.isFinished()) {
mScroller.abortAnimation();
}
mLastMotionX = x;
break;
case MotionEvent.ACTION_MOVE:
deltaX = (int) (mLastMotionX - x);
if (canMoveDis(deltaX)) {
obtainVelocityTracker(event);
mLastMotionX = x;
// 正向或者负向移动,屏幕尾随手指移动
scrollBy(deltaX, 0);
}
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
// 当手指离开屏幕时。记录下mVelocityTracker的记录,并取得X轴滑动速度
obtainVelocityTracker(event);
mVelocityTracker.computeCurrentVelocity(1000);
float velocityX = mVelocityTracker.getXVelocity();
// 当X轴滑动速度大于SNAP_VELOCITY
// velocityX为正值说明手指向右滑动,为负值说明手指向左滑动
if (velocityX > SNAP_VELOCITY && mCurScreen > 0) {
// Fling enough to move left
snapToScreen(mCurScreen - 1);
} else if (velocityX < -SNAP_VELOCITY
&& mCurScreen < getChildCount() - 1) {
// Fling enough to move right
snapToScreen(mCurScreen + 1);
} else {
snapToDestination();
}
releaseVelocityTracker();
break;
}
// super.onTouchEvent(event);
return true;// 这里一定要返回true,不然仅仅接受down
} /**
* 边界检測
*
* @param deltaX
* @return
*/
private boolean canMoveDis(int deltaX) {
int scrollX = getScrollX();
// deltaX<0说明手指向右划
if (deltaX < 0) {
if (scrollX <= 0) {
return false;
} else if (deltaX + scrollX < 0) {
scrollTo(0, 0);
return false;
}
}
// deltaX>0说明手指向左划
int leftX = (getChildCount() - 1) * getWidth();
if (deltaX > 0) {
if (scrollX >= leftX) {
return false;
} else if (scrollX + deltaX > leftX) {
scrollTo(leftX, 0);
return false;
}
}
return true;
} /**
* 使屏幕移动到第whichScreen+1屏
*
* @param whichScreen
*/
public void snapToScreen(int whichScreen) {
int scrollX = getScrollX();
if (scrollX != (whichScreen * getWidth())) {
int delta = whichScreen * getWidth() - scrollX;
mScroller.startScroll(scrollX, 0, delta, 0, Math.abs(delta) * 2);
mCurScreen = whichScreen;
invalidate();
if (mOnViewChangeListener != null) {
mOnViewChangeListener.OnViewChange(mCurScreen);
}
}
} /**
* 当不须要滑动时,会调用该方法
*/
private void snapToDestination() {
int screenWidth = getWidth();
int whichScreen = (getScrollX() + (screenWidth / 2)) / screenWidth;
snapToScreen(whichScreen);
} private void obtainVelocityTracker(MotionEvent event) {
if (mVelocityTracker == null) {
mVelocityTracker = VelocityTracker.obtain();
}
mVelocityTracker.addMovement(event);
} private void releaseVelocityTracker() {
if (mVelocityTracker != null) {
mVelocityTracker.recycle();
mVelocityTracker = null;
}
} public void SetOnViewChangeListener(OnViewChangeListener listener) {
mOnViewChangeListener = listener;
} public interface OnViewChangeListener {
public void OnViewChange(int page);
}
}
package com.jwzhangjie.scrollview; import com.jwzhangjie.scrollview.MultiViewGroup.OnViewChangeListener; import android.os.Bundle;
import android.support.v4.app.FragmentActivity;
import android.view.View;
import android.widget.Toast; public class MultiActivity extends FragmentActivity implements
OnViewChangeListener { private MultiViewGroup multiViewGroup;
private int allScreen;
private int curreScreen = 0; @Override
protected void onCreate(Bundle bundle) {
super.onCreate(bundle);
setContentView(R.layout.activity_main);
multiViewGroup = (MultiViewGroup) findViewById(R.id.screenParent);
multiViewGroup.SetOnViewChangeListener(this);
allScreen = multiViewGroup.getChildCount() - 1;
} public void nextScreen(View view) {
if (curreScreen < allScreen) {
curreScreen++;
} else {
curreScreen = 0; }
multiViewGroup.snapToScreen(curreScreen);
} public void preScreen(View view) {
if (curreScreen > 0) {
curreScreen--;
} else {
curreScreen = allScreen;
}
multiViewGroup.snapToScreen(curreScreen);
} @Override
public void OnViewChange(int page) {
Toast.makeText(getApplicationContext(),
getString(R.string.currePage, page), Toast.LENGTH_SHORT).show();
} }
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent" > <Button
android:id="@+id/nextPage"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="10dip"
android:background="@drawable/bg_item_num_3_button"
android:onClick="nextScreen"
android:text="下一页" /> <Button
android:id="@+id/prePage"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_margin="10dip"
android:background="@drawable/bg_item_num_3_button"
android:onClick="preScreen"
android:text="上一页" /> <com.jwzhangjie.scrollview.MultiViewGroup
android:id="@+id/screenParent"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@id/nextPage" > <LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#f00" > <TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="第一页" />
</LinearLayout> <LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#0f0" > <TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="第二页" />
</LinearLayout> <LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#00f" > <TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="第三页" />
</LinearLayout>
</com.jwzhangjie.scrollview.MultiViewGroup> </RelativeLayout>
下载地址:https://github.com/jwzhangjie/MultiViewGroup
6、引用
Scroller的应用--滑屏实现的更多相关文章
- Android中滑屏实现----手把手教你如何实现触摸滑屏以及Scroller类详解
前言: 虽然本文标题的有点标题党的感觉,但无论如何,通过这篇文章的学习以及你自己的实践认知,写个简单的滑屏小 Demo还是just so so的. 友情提示: 在继续往下面读之前,希望您对以下知识点 ...
- Android中滑屏实现----触摸滑屏以及Scroller类详解 .
转:http://blog.csdn.net/qinjuning/article/details/7419207 知识点一: 关于scrollTo()和scrollBy()以及偏移坐标的设置/取值问 ...
- 修改ViewPager调用setCurrentItem时,滑屏的速度
原文摘自: 修改ViewPager调用setCurrentItem时,滑屏的速度 在使用ViewPager的过程中,有需要直接跳转到某一个页面的情况,这个时候就需要用到ViewPager的setCur ...
- 【Android 界面效果29】研究一下Android滑屏的功能的原理,及scrollTo和scrollBy两个方法
Android中的滑屏功能的原理是很值得我们去研究的,在知道这两个原理之前,有必要先说说View的两个重要方法,它们就是scrollTo 和scrollBy. Android View视图是没有边界的 ...
- Android中滑屏初探 ---- scrollTo 以及 scrollBy方法使用说明
今天给大家介绍下Android中滑屏功能的一个基本实现过程以及原理初探,最后给大家重点讲解View视图中scrollTo 与 scrollBy这两个函数的区别 . 首先 ,我们必须明白在Android ...
- 修改ViewPager调用setCurrentItem时,滑屏的速度 ,解决滑动之间切换动画难看
在使用ViewPager的过程中,有需要直接跳转到某一个页面的情况,这个时候就需要用到ViewPager的setCurrentItem方法了,它的意思是跳转到ViewPager的指定页面,但在使用这个 ...
- H5单页面手势滑屏切换原理
H5单页面手势滑屏切换是采用HTML5 触摸事件(Touch) 和 CSS3动画(Transform,Transition)来实现的,效果图如下所示,本文简单说一下其实现原理和主要思路. 1.实现原理 ...
- 【原】移动web滑屏框架分享
本月26号参加webrebuild深圳站,会上听了彪叔的对初心的讲解,“工匠精神”这个词又一次被提出,也再次引起了我对它的思考.专注一个项目并把它做得好,很好,更好...现实工作中,忙忙碌碌,抱着完成 ...
- iOS - 滑屏方案
参考自:iOS开发- 通过ChildViewCotroller ViewController容器 产品增加新的版面,类似于网易新闻,百度新闻,腾讯新闻等新闻客户端首页多屏幕滑屏切换,找了一些开源代码研 ...
随机推荐
- JS高级——弹出框的美化
替换原有的alert方法,window.alert=function(){} https://blog.csdn.net/kirsten_z/article/details/76242286 http ...
- ARX亮显问题
转载一段acedSSSetFirst的用法仅供参考:打个比方,我创建了一个命令,这个命令的功能是提示用户选择,然后只过滤文本对象作为选择集,随后在屏幕上使得这个选择集的所有成员都亮显,并且能够显示出各 ...
- CAD得到范围内实体(网页版)
主要用到函数说明: IMxDrawSelectionSet::Select 构造选择集.详细说明如下: 参数 说明 [in] MCAD_McSelect Mode 构造选择集方式 [in] VARIA ...
- Linux 安装 Tomcat 详解
说明:安装的 tomcat 为解压版(即免安装版):apache-tomcat-8.5.15.tar.gz (1)使用 root 用户登录虚拟机,在根目录下的 opt 文件夹新建一个 software ...
- 省市区json结构
[ { "label": "北京市", "value": "北京市", "children": [ ...
- Python学习之前
编程语言的分类: 1.机器语言:直接以0和1编写指令代码,计算机能直接识别处理: 特点:运行速度最快,太复杂,开发效率低,可执行操作最多. 2.汇编语言:本质上依然是机器语言,用英文代替0和1,更容易 ...
- Python面向对象之私有属性和方法
私有属性与私有方法 应用场景 在实际开发中,对象的某些属性或者方法 可能只希望在对象的内部被使用,而不希望在外部被访问到: 私有属性 就是对象不希望公开的属性: 私有方法 就是对象不希望公开的方法: ...
- Vue.Draggable实现拖拽效果(快速使用)
1.下载包:npm install vuedraggable 配置:package.json "dependencies": { "element-ui": & ...
- RCC 2014 Warmup (Div. 2) 蛋疼解题总结
A. Elimination time limit per test 1 second memory limit per test 256 megabytes input standard input ...
- 1827 tarjan+缩点
#include<stdio.h> #include<stack> #include<iostream> #include<string.h> #inc ...