贴上一个我自己用过的阻尼滑动的ScrollView,像QQ里面那种滑动效果,虽然不是我写的,但是我觉得还可以,贴出来做个记录,有用到的时候免得到处去找。
代码如下:
/*
* Copyright (C) 2006 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.
*/
import android.content.Context;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.FocusFinder;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.ViewParent;
import android.view.animation.AnimationUtils;
import android.view.animation.OvershootInterpolator;
import android.widget.FrameLayout;
import android.widget.Scroller;
import android.view.View.OnTouchListener;
import java.lang.reflect.Field;
import java.util.List;
/**
* Layout container for a view hierarchy that can be scrolled by the user,
* allowing it to be larger than the physical display. A ScrollView is a
* {@link FrameLayout}, meaning you should place one child in it containing the
* entire contents to scroll; this child may itself be a layout manager with a
* complex hierarchy of objects. A child that is often used is a
* {@link LinearLayout} in a vertical orientation, presenting a vertical array
* of top-level items that the user can scroll through.
*
* <p>
* The {@link TextView} class also takes care of its own scrolling, so does not
* require a ScrollView, but using the two together is possible to achieve the
* effect of a text view within a larger container.
*
* <p>
* ScrollView only supports vertical scrolling.
*/
public class OverScrollView extends FrameLayout implements OnTouchListener
{
private static final String TAG = OverScrollView.class.getName();
static final int ANIMATED_SCROLL_GAP = 250;
static final float MAX_SCROLL_FACTOR = 0.5f;
private static final float SCROLL_RATIO = 0.3f;// 阻尼系数
static final float OVERSHOOT_TENSION = 0.75f;
private long mLastScroll;
private final Rect mTempRect = new Rect();
private Scroller mScroller;
protected Context mContext;
Field mScrollYField;
Field mScrollXField;
boolean hasFailedObtainingScrollFields;
int prevScrollY;
boolean isInFlingMode = false;
DisplayMetrics metrics;
LayoutInflater inflater;
protected View child;
private Runnable overScrollerSpringbackTask;
/**
* Flag to indicate that we are moving focus ourselves. This is so the code
* that watches for focus changes initiated outside this ScrollView knows
* that it does not have to do anything.
*/
private boolean mScrollViewMovedFocus;
/**
* Position of the last motion event.
*/
private float mLastMotionY;
/**
* True when the layout has changed but the traversal has not come through
* yet. Ideally the view hierarchy would keep track of this for us.
*/
private boolean mIsLayoutDirty = true;
/**
* The child to give focus to in the event that a child has requested focus
* while the layout is dirty. This prevents the scroll from being wrong if
* the child has not been laid out before requesting focus.
*/
private View mChildToScrollTo = null;
/**
* True if the user is currently dragging this ScrollView around. This is
* not the same as 'is being flinged', which can be checked by
* mScroller.isFinished() (flinging begins when the user lifts his finger).
*/
private boolean mIsBeingDragged = false;
/**
* Determines speed during touch scrolling
*/
private VelocityTracker mVelocityTracker;
/**
* When set to true, the scroll view measure its child to make it fill the
* currently visible area.
*/
private boolean mFillViewport;
/**
* Whether arrow scrolling is animated.
*/
private boolean mSmoothScrollingEnabled = true;
private int mTouchSlop;
private int mMinimumVelocity;
private int mMaximumVelocity;
/**
* ID of the active pointer. This is used to retain consistency during
* drags/flings if multiple pointers are used.
*/
private int mActivePointerId = INVALID_POINTER;
/**
* Sentinel value for no current active pointer. Used by
* {@link #mActivePointerId}.
*/
private static final int INVALID_POINTER = -1;
public OverScrollView(Context context)
{
this(context, null);
mContext = context;
initBounce();
}
public OverScrollView(Context context, AttributeSet attrs)
{
this(context, attrs, 0);
mContext = context;
initBounce();
}
public OverScrollView(Context context, AttributeSet attrs, int defStyle)
{
super(context, attrs, defStyle);
mContext = context;
initScrollView();
setFillViewport(true);
initBounce();
}
private void initBounce()
{
metrics = this.mContext.getResources().getDisplayMetrics();
// init the bouncy scroller, and make sure the layout is being drawn
// after the top padding
mScroller = new Scroller(getContext(), new OvershootInterpolator(OVERSHOOT_TENSION));
overScrollerSpringbackTask = new Runnable()
{
@Override
public void run()
{
// scroll till after the padding
mScroller.computeScrollOffset();
scrollTo(0, mScroller.getCurrY());
if (!mScroller.isFinished())
{
post(this);
}
}
};
prevScrollY = getPaddingTop();
try
{
mScrollXField = View.class.getDeclaredField("mScrollX");
mScrollYField = View.class.getDeclaredField("mScrollY");
} catch (Exception e)
{
hasFailedObtainingScrollFields = true;
}
}
private void SetScrollY(int value)
{
if (mScrollYField != null)
{
try
{
mScrollYField.setInt(this, value);
} catch (Exception e)
{
}
}
}
private void SetScrollX(int value)
{
if (mScrollXField != null)
{
try
{
mScrollXField.setInt(this, value);
} catch (Exception e)
{
}
}
}
public void initChildPointer()
{
Log.i(TAG,"initChildPointer");
child = getChildAt(0);
child.setPadding(0, 1500, 0, 1500);
}
@Override
protected float getTopFadingEdgeStrength()
{
if (getChildCount() == 0)
{
return 0.0f;
}
final int length = getVerticalFadingEdgeLength();
if (getScrollY() < length)
{
return getScrollY() / (float) length;
}
return 1.0f;
}
@Override
protected float getBottomFadingEdgeStrength()
{
if (getChildCount() == 0)
{
return 0.0f;
}
final int length = getVerticalFadingEdgeLength();
final int bottomEdge = getHeight() - getPaddingBottom();
final int span = getChildAt(0).getBottom() - getScrollY() - bottomEdge;
if (span < length)
{
return span / (float) length;
}
return 1.0f;
}
/**
* @return The maximum amount this scroll view will scroll in response to an
* arrow event.
*/
public int getMaxScrollAmount()
{
return (int) (MAX_SCROLL_FACTOR * (getBottom() - getTop()));
}
private void initScrollView()
{
mScroller = new Scroller(getContext());
setFocusable(true);
setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
setWillNotDraw(false);
final ViewConfiguration configuration = ViewConfiguration.get(mContext);
mTouchSlop = configuration.getScaledTouchSlop();
mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
setOnTouchListener(this);
post(new Runnable()
{
public void run()
{
scrollTo(0, child.getPaddingTop());
}
});
}
@Override
public void addView(View child)
{
if (getChildCount() > 0)
{
throw new IllegalStateException("ScrollView can host only one direct child");
}
super.addView(child);
initChildPointer();
}
@Override
public void addView(View child, int index)
{
if (getChildCount() > 0)
{
throw new IllegalStateException("ScrollView can host only one direct child");
}
super.addView(child, index);
initChildPointer();
}
@Override
public void addView(View child, ViewGroup.LayoutParams params)
{
if (getChildCount() > 0)
{
throw new IllegalStateException("ScrollView can host only one direct child");
}
super.addView(child, params);
initChildPointer();
}
@Override
public void addView(View child, int index, ViewGroup.LayoutParams params)
{
if (getChildCount() > 0)
{
throw new IllegalStateException("ScrollView can host only one direct child");
}
super.addView(child, index, params);
}
/**
* @return Returns true this ScrollView can be scrolled
*/
private boolean canScroll()
{
View child = getChildAt(0);
if (child != null)
{
int childHeight = child.getHeight();
return getHeight() < childHeight + getPaddingTop() + getPaddingBottom();
}
return false;
}
/**
* Indicates whether this ScrollView's content is stretched to fill the
* viewport.
*
* @r