android-misc-widgets多方抽屉bug修复版 解决“闪烁”问题

这篇博客介绍了在Android开发中遇到的一个多方抽屉动画问题,即在快速操作时出现的闪烁现象。作者分析了问题原因,发现是动画与setVisibility()的冲突,并分享了解决方案,包括调整动画执行顺序和使用Handler改变控件位置,从而避免了闪烁效果。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

http://blog.youkuaiyun.com/lovehong0306


前几天项目需要用到左侧拉出抽屉,想到了http://blog.youkuaiyun.com/hellogv/article/details/6264706中提到的多方抽屉,拿来试用了下,发现bug还真不少,最不能忍受的是最后那一下“闪烁”,于是乎,改!

下面将修改过程中遇到的问题及其解决方法分享给大家。

首先是出现了如图的情况:

当以光的速度点击handle(就是那个带箭头的Button)并拉出到很远很远的地方 就出先上边那个神奇的现象了

寻找原因,发现是这里的问题

			if (tmpX != mTrackX || tmpY != mTrackY)
			{
				mTrackX = tmpX;
				mTrackY = tmpY;
				// invalidate(); //放在此导致极快速滑动至touch区域外界面不刷新(mTrackX、mTrackY均为0)
			}
			invalidate();

就拿上边那种情况来讲

当瞬间将handle拉至最大位置,即 tmpX=0 的位置,由于mTrackX默认为0,if条件不成立,执行不到invalidate()方法,页面没有刷新

将invalidate()方法移到if'条件语句之外即可解决问题


下一问题:onFling方法在将抽屉快速抽出时基本不能用

抽出来~滑进去~抽出来~滑进去~     (这个抽屉带弹簧的@_@?!)

究其原因,在这里

public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
		mState = State.FLYING;
		mVelocity = mOrientation == VERTICAL? velocityY : velocityX;
		post(startAnimation);
		return true;
	}

mVelocity 使用的是onFling方法传进来的参数velocityX,经多次打印log发现velocityX为负数大致看了下源码,这个速度是基于getX()方法算出来的是
大家都知道,getX()方法是获取以widget左上角为坐标原点计算的X轴坐标值(不知道的看这里:http://blog.youkuaiyun.com/lovehong0306/article/details/7451507
由此推想而知
1.点击handle(此时content为GONE),这时的getX()得到的是以handle左上角为原点的坐标

2.快速滑动以发动onFling方法(快到只有两个event事件发生),这时getX()得到的依然是以handle的左上角为原点的坐标,但是由于content已经可见,handle的位置发生了变化,为抽屉完全抽出时的位置,而action_up事件发生时的getX()得到是在handle原点的左边,即为负值,用此时的X坐标值减去之前得到的那个正的坐标值,结果当然是负的了

3.有负的偏移量和时间,计算出来的速度也就是负的了

这就是为什么拉出抽屉后会滑进去的原因了


修改为如下即可解决:

@Override
		public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
				float velocityY)
		{
			mState = State.FLYING;

			float velocityX2, velocityY2;
			if (lastRawX == -1 && lastRawY == -1)	//见onScroll方法
			{
				velocityX2 = (curRawX - e1.getRawX())
						/ (curEventTime - e1.getEventTime()) * 1000; //  px/s
				velocityY2 = (curRawY - e1.getRawY())
						/ (curEventTime - e1.getEventTime()) * 1000;
			}
			else
			{
				velocityX2 = (curRawX - lastRawX)
						/ (curEventTime - lastEventTime) * 1000;
				velocityY2 = (curRawY - lastRawY)
						/ (curEventTime - lastEventTime) * 1000;
			}

			mVelocity = mOrientation == VERTICAL ? velocityY2 : velocityX2;

			if (Math.abs(mVelocity) > 50)
			{
				if (mVelocity > 0)
				{
					mAnimatedAcceleration = mMaximumAcceleration;
				}
				else
				{
					mAnimatedAcceleration = -mMaximumAcceleration;
				}

				long now = SystemClock.uptimeMillis();
				mAnimationLastTime = now;
				mCurrentAnimationTime = now + ANIMATION_FRAME_DURATION;
				mAnimating = true;
				mHandler.removeMessages(MSG_ANIMATE);
				mHandler.removeMessages(MSG_PREPARE_ANIMATE);
				mHandler.sendMessageAtTime(mHandler.obtainMessage(MSG_ANIMATE),
						mCurrentAnimationTime);
				return true;
			}
			return false;
		}

代码就不多做解释了,命名还算规范,应该能看懂,最后那几行是为解决“闪烁”问题的

下面就来说下最棘手的问题——“闪烁”


那么为什么会闪烁呢?

经多次尝试,发现是动画与setVisibility(GONG)冲突,当把动画设置为setFillAfter(true)后即可发现,动画结束后设置控件setVisibility(GONG),消失的不仅仅是content,handle也一同消失了。

由此可知handle在动画结束后先消失再出现,于是就出现了闪烁的效果


那么好办,只要把content和handle分别同时设置动画不就行了,content在动画结束后setVisibility(GONG),handle不setVisibility(GONG)。

But,这么尝试了一下发现,虽然“几乎”同时start动画,毕竟还是有时间间隔的,机子性能越差越明显,content和handle分开了!!!

此法行不通,另想他法


源码真是个好东西,看看SlidingDrawer是怎么实现的

原来如此,没用系统动画,利用handler重复改变控件位置

好,就按照这个思路,结合当前代码,改!

(完整代码稍后贴出)

把所有post(startAnimation)替换成:

long now = SystemClock.uptimeMillis();
				mAnimationLastTime = now;
				mCurrentAnimationTime = now + ANIMATION_FRAME_DURATION;
				mAnimating = true;
				mHandler.removeMessages(MSG_ANIMATE);
				mHandler.removeMessages(MSG_PREPARE_ANIMATE);
				mHandler.sendMessageAtTime(
						mHandler.obtainMessage(MSG_PREPARE_ANIMATE),
						mCurrentAnimationTime);

这段代码基本上是从SlidingDrawer源码copy过来的,MSG_PREPARE_ANIMATE是自己加的

起初不明白ANIMATION_FRAME_DURATION的作用,为什么要延迟呢?

后来发现,这个延迟是留给onLayout方法的,如果不加这个延迟,后边用到的方法就可能在onLayout方法之前调用,也就导致了在onLayout方法之前用到了mContentWidth或者mContentHeight,此时的值为0,这也是为什么要另加MSG_PREPARE_ANIMATE


prepareAnimation方法代码如下:

private void prepareAnimation()
	{

		switch (mPosition)
		{
			case LEFT:
				if (mIsShrinking)
				{
					mVelocity = -mMaximumMajorVelocity;
					mAnimatedAcceleration = -mMaximumAcceleration;

				}
				else
				{
					mVelocity = mMaximumMajorVelocity;
					mAnimatedAcceleration = mMaximumAcceleration;
					if (mTrackX == 0 && mState == State.ABOUT_TO_ANIMATE)
					{
						mTrackX = -mContentWidth;
					}
				}
				break;
			case RIGHT:
				if (mIsShrinking)
				{
					mVelocity = mMaximumMajorVelocity;
					mAnimatedAcceleration = mMaximumAcceleration;
				}
				else
				{
					mVelocity = -mMaximumMajorVelocity;
					mAnimatedAcceleration = -mMaximumAcceleration;

					if (mTrackX == 0 && mState == State.ABOUT_TO_ANIMATE)
					{
						mTrackX = mContentWidth;
					}
				}
				break;
			case TOP:
				if (mIsShrinking)
				{
					mVelocity = -mMaximumMajorVelocity;
					mAnimatedAcceleration = -mMaximumAcceleration;
				}
				else
				{
					mVelocity = mMaximumMajorVelocity;
					mAnimatedAcceleration = mMaximumAcceleration;

					if (mTrackX == 0 && mState == State.ABOUT_TO_ANIMATE)
					{
						mTrackY = -mContentHeight;
					}
				}
				break;
			case BOTTOM:
				if (mIsShrinking)
				{
					mVelocity = mMaximumMajorVelocity;
					mAnimatedAcceleration = mMaximumAcceleration;
				}
				else
				{
					mVelocity = -mMaximumMajorVelocity;
					mAnimatedAcceleration = -mMaximumAcceleration;

					if (mTrackX == 0 && mState == State.ABOUT_TO_ANIMATE)
					{
						mTrackY = mContentHeight;
					}
				}
				break;
		}

		if (mState == State.TRACKING)
		{
			if (mIsShrinking)
			{
				if ((mOrientation == VERTICAL && Math.abs(mTrackY) < mContentHeight / 2)
						|| (mOrientation == HORIZONTAL && Math.abs(mTrackX) < mContentWidth 						/ 2))
				{
					mVelocity = -mVelocity;
					mAnimatedAcceleration = -mAnimatedAcceleration;
					mIsShrinking = !mIsShrinking;
				}
			}
			else
			{
				if ((mOrientation == VERTICAL && Math.abs(mTrackY) > mContentHeight / 2)
						|| (mOrientation == HORIZONTAL && Math.abs(mTrackX) > mContentWidth 						/ 2))
				{
					mVelocity = -mVelocity;
					mAnimatedAcceleration = -mAnimatedAcceleration;
					mIsShrinking = !mIsShrinking;
				}
			}
		}
		if (mState != State.FLYING && mState != State.TRACKING)
		{
			mState = State.CLICK;
		}
	}

其中类似
if (mTrackX == 0 && mState == State.ABOUT_TO_ANIMATE)
			{
				mTrackX = -mContentWidth;
			}

代码是为解决初次使用控件初始化mTrackX,否则此时单击handle会导致控件直接抽出,无动画效果


if (mState == State.TRACKING)
		{
			if (mIsShrinking)
			{
				if ((mOrientation == VERTICAL && Math.abs(mTrackY) < mContentHeight / 2)
						|| (mOrientation == HORIZONTAL && Math.abs(mTrackX) < mContentWidth 						/ 2))
				{
					mVelocity = -mVelocity;
					mAnimatedAcceleration = -mAnimatedAcceleration;
					mIsShrinking = !mIsShrinking;
				}
			}
			else
			{
				if ((mOrientation == VERTICAL && Math.abs(mTrackY) > mContentHeight / 2)
						|| (mOrientation == HORIZONTAL && Math.abs(mTrackX) > mContentWidth 						/ 2))
				{
					mVelocity = -mVelocity;
					mAnimatedAcceleration = -mAnimatedAcceleration;
					mIsShrinking = !mIsShrinking;
				}
			}
		}

这段代码是判断抽屉拉出是否过半,也就是决定控件在松开鼠标时是回到关闭状态还是抽出状态

doAnimation()方法比较简单,没啥可讲的


之前的变量 mDuration mLinearFlying mInterpolator 没用到,因为感觉没啥用,目前的控件已能满足大部分人需求,有特殊需求的请自行添加


下面上完整代码,不懂的可以留言问我

初次写博文,不周之处,还请见谅


Panel.java

package org.miscwidgets.widget;

import org.miscwidgets.R;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.os.Message;
import android.os.SystemClock;
import android.util.AttributeSet;
import android.util.Log;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewParent;
import android.view.GestureDetector.OnGestureListener;
import android.view.animation.Interpolator;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
/**
 * 
 * Fixed by http://blog.youkuaiyun.com/lovehong0306/article/details/7451264
 * 
 */
public class Panel extends LinearLayout
{

	private static final String TAG = "Panel";

	private static final float MAXIMUM_MAJOR_VELOCITY = 200.0f;
	private static final float MAXIMUM_ACCELERATION = 2000.0f;
	private static final int MSG_ANIMATE = 1000;
	private static final int MSG_PREPARE_ANIMATE = 2000;
	private static final int ANIMATION_FRAME_DURATION = 1000 / 60;

	private final Handler mHandler = new SlidingHandler();
	private float mAnimatedAcceleration;
	private long mAnimationLastTime;
	private long mCurrentAnimationTime;
	private boolean mAnimating;

	private final int mMaximumMajorVelocity;
	private final int mMaximumAcceleration;

	private float lastRawX, lastRawY, curRawX, curRawY;
	private float lastEventTime, curEventTime;

	/**
	 * Callback invoked when the panel is opened/closed.
	 */
	public static interface OnPanelListener
	{
		/**
		 * Invoked when the panel becomes fully closed.
		 */
		public void onPanelClosed(Panel panel);

		/**
		 * Invoked when the panel becomes fully opened.
		 */
		public void onPanelOpened(Panel panel);
	}

	private boolean mIsShrinking;
	private int mPosition;
	private int mDuration;
	private boolean mLinearFlying;
	private int mHandleId;
	private int mContentId;
	private View mHandle;
	private View mContent;
	private Drawable mOpenedHandle;
	private Drawable mClosedHandle;
	private float mTrackX;
	private float mTrackY;
	private float mVelocity;

	private OnPanelListener panelListener;

	public static final int TOP = 0;
	public static final int BOTTOM = 1;
	public static final int LEFT = 2;
	public static final int RIGHT = 3;

	private enum State
	{
		ABOUT_TO_ANIMATE, ANIMATING, READY, TRACKING, FLYING, CLICK
	};

	private State mState;
	private Interpolator mInterpolator;
	private GestureDetector mGestureDetector;
	private int mContentHeight;
	private int mContentWidth;
	private int mOrientation;
	private float mWeight;
	private PanelOnGestureListener mGestureListener;
	private boolean mBringToFront;

	public Panel(Context context, AttributeSet attrs)
	{
		super(context, attrs);
		TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.Panel);
		mDuration = a.getInteger(R.styleable.Panel_animationDuration, 750); 	// duration defaults to 750 ms
		mPosition = a.getInteger(R.styleable.Panel_position, BOTTOM); 			// position defaults to BOTTOM
		mLinearFlying = a.getBoolean(R.styleable.Panel_linearFlying, false); 	// linearFlying defaults to false
		mWeight = a.getFraction(R.styleable.Panel_weight, 0, 1, 0.0f); 			// weight defaults to 0.0
		if (mWeight < 0 || mWeight > 1)
		{
			mWeight = 0.0f;
			Log.w(TAG, a.getPositionDescription()
					+ ": weight must be > 0 and <= 1");
		}
		mOpenedHandle = a.getDrawable(R.styleable.Panel_openedHandle);
		mClosedHandle = a.getDrawable(R.styleable.Panel_closedHandle);

		RuntimeException e = null;
		mHandleId = a.getResourceId(R.styleable.Panel_handle, 0);
		if (mHandleId == 0)
		{
			e = new IllegalArgumentException(
					a.getPositionDescription()
							+ ": The handle attribute is required and must refer to a valid child.");
		}
		mContentId = a.getResourceId(R.styleable.Panel_content, 0);
		if (mContentId == 0)
		{
			e = new IllegalArgumentException(
					a.getPositionDescription()
							+ ": The content attribute is required and must refer to a valid child.");
		}
		a.recycle();

		final float density = getResources().getDisplayMetrics().density;
		mMaximumMajorVelocity = (int) (MAXIMUM_MAJOR_VELOCITY * density + 0.5f);
		mMaximumAcceleration = (int) (MAXIMUM_ACCELERATION * density + 0.5f);

		if (e != null)
		{
			throw e;
		}
		mOrientation = (mPosition == TOP || mPosition == BOTTOM) ? VERTICAL
				: HORIZONTAL;
		setOrientation(mOrientation);
		mState = State.READY;
		mGestureListener = new PanelOnGestureListener();
		mGestureDetector = new GestureDetector(mGestureListener);
		mGestureDetector.setIsLongpressEnabled(false);

		// i DON'T really know why i need this...
		setBaselineAligned(false);
	}

	/**
	 * Sets the listener that receives a notification when the panel becomes
	 * open/close.
	 * 
	 * @param onPanelListener
	 *            The listener to be notified when the panel is opened/closed.
	 */
	public void setOnPanelListener(OnPanelListener onPanelListener)
	{
		panelListener = onPanelListener;
	}

	/**
	 * Gets Panel's mHandle
	 * 
	 * @return Panel's mHandle
	 */
	public View getHandle()
	{
		return mHandle;
	}

	/**
	 * Gets Panel's mContent
	 * 
	 * @return Panel's mContent
	 */
	public View getContent()
	{
		return mContent;
	}

	/**
	 * Sets the acceleration curve for panel's animation.
	 * 
	 * @param i
	 *            The interpolator which defines the acceleration curve
	 */
	public void setInterpolator(Interpolator i)
	{
		mInterpolator = i;
	}

	/**
	 * Set the opened state of Panel.
	 * 
	 * @param open
	 *            True if Panel is to be opened, false if Panel is to be closed.
	 * @param animate
	 *            True if use animation, false otherwise.
	 * 
	 * @return True if operation was performed, false otherwise.
	 * 
	 */
	public boolean setOpen(boolean open, boolean animate)
	{
		if (mState == State.READY && isOpen() ^ open)
		{
			mIsShrinking = !open;
			if (animate)
			{
				mState = State.ABOUT_TO_ANIMATE;
				if (!mIsShrinking)
				{
					// this could make flicker so we test mState in
					// dispatchDraw()
					// to see if is equal to ABOUT_TO_ANIMATE
					mContent.setVisibility(VISIBLE);
				}
				long now = SystemClock.uptimeMillis();
				mAnimationLastTime = now;
				mCurrentAnimationTime = now + ANIMATION_FRAME_DURATION;
				mAnimating = true;
				mHandler.removeMessages(MSG_ANIMATE);
				mHandler.removeMessages(MSG_PREPARE_ANIMATE);
				mHandler.sendMessageAtTime(
						mHandler.obtainMessage(MSG_PREPARE_ANIMATE),
						mCurrentAnimationTime);
			}
			else
			{
				mContent.setVisibility(open ? VISIBLE : GONE);
				postProcess();
			}
			return true;
		}
		return false;
	}

	/**
	 * Returns the opened status for Panel.
	 * 
	 * @return True if Panel is opened, false otherwise.
	 */
	public boolean isOpen()
	{
		return mContent.getVisibility() == VISIBLE;
	}

	@Override
	protected void onFinishInflate()
	{
		super.onFinishInflate();
		mHandle = findViewById(mHandleId);
		if (mHandle == null)
		{
			String name = getResources().getResourceEntryName(mHandleId);
			throw new RuntimeException(
					"Your Panel must have a child View whose id attribute is 'R.id."
							+ name + "'");
		}
		mHandle.setClickable(true);
		mHandle.setOnTouchListener(touchListener);
		// mHandle.setOnClickListener(clickListener);

		mContent = findViewById(mContentId);
		if (mContent == null)
		{
			String name = getResources().getResourceEntryName(mHandleId);
			throw new RuntimeException(
					"Your Panel must have a child View whose id attribute is 'R.id."
							+ name + "'");
		}

		// reposition children
		removeView(mHandle);
		removeView(mContent);
		if (mPosition == TOP || mPosition == LEFT)
		{
			addView(mContent);
			addView(mHandle);
		}
		else
		{
			addView(mHandle);
			addView(mContent);
		}

		if (mClosedHandle != null)
		{
			mHandle.setBackgroundDrawable(mClosedHandle);
		}
		mContent.setClickable(true);
		mContent.setVisibility(GONE);
		if (mWeight > 0)
		{
			ViewGroup.LayoutParams params = mContent.getLayoutParams();
			if (mOrientation == VERTICAL)
			{
				params.height = ViewGroup.LayoutParams.FILL_PARENT;
			}
			else
			{
				params.width = ViewGroup.LayoutParams.FILL_PARENT;
			}
			mContent.setLayoutParams(params);
		}
	}

	@Override
	protected void onAttachedToWindow()
	{
		super.onAttachedToWindow();
		ViewParent parent = getParent();
		if (parent != null && parent instanceof FrameLayout)
		{
			mBringToFront = true;
		}
	}

	@Override
	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
	{
		if (mWeight > 0 && mContent.getVisibility() == VISIBLE)
		{
			View parent = (View) getParent();
			if (parent != null)
			{
				if (mOrientation == VERTICAL)
				{
					heightMeasureSpec = MeasureSpec.makeMeasureSpec(
							(int) (parent.getHeight() * mWeight),
							MeasureSpec.EXACTLY);
				}
				else
				{
					widthMeasureSpec = MeasureSpec.makeMeasureSpec(
							(int) (parent.getWidth() * mWeight),
							MeasureSpec.EXACTLY);
				}
			}
		}
		super.onMeasure(widthMeasureSpec, heightMeasureSpec);
	}

	@Override
	protected void onLayout(boolean changed, int l, int t, int r, int b)
	{
		super.onLayout(changed, l, t, r, b);
		mContentWidth = mContent.getWidth();
		mContentHeight = mContent.getHeight();
	}

	@Override
	protected void dispatchDraw(Canvas canvas)
	{
		// String name = getResources().getResourceEntryName(getId());
		// Log.d(TAG, name + " ispatchDraw " + mState);
		// this is why 'mState' was added:
		// avoid flicker before animation start
		if (mState == State.ABOUT_TO_ANIMATE && !mIsShrinking)
		{
			int delta = mOrientation == VERTICAL ? mContentHeight
					: mContentWidth;
			if (mPosition == LEFT || mPosition == TOP)
			{
				delta = -delta;
			}
			if (mOrientation == VERTICAL)
			{
				canvas.translate(0, delta);
			}
			else
			{
				canvas.translate(delta, 0);
			}
		}
		if (mState == State.TRACKING || mState == State.FLYING
				|| mState == State.CLICK)
		{
			canvas.translate(mTrackX, mTrackY);
		}
		super.dispatchDraw(canvas);
	}

	private float ensureRange(float v, int min, int max)
	{
		v = Math.max(v, min);
		v = Math.min(v, max);
		return v;
	}

	OnTouchListener touchListener = new OnTouchListener()
	{
		
		public boolean onTouch(View v, MotionEvent event)
		{

			if (mAnimating)
			{
				// we are animating
				return true;// 动画中不响应onTouch事件
			}

			int action = event.getAction();
			if (action == MotionEvent.ACTION_DOWN)
			{
				if (mBringToFront)
				{
					bringToFront();
				}
			}

			if (!mGestureDetector.onTouchEvent(event))
			{
				if (action == MotionEvent.ACTION_UP)
				{
					// tup up after scrolling

					long now = SystemClock.uptimeMillis();
					mAnimationLastTime = now;
					mCurrentAnimationTime = now + ANIMATION_FRAME_DURATION;
					mAnimating = true;
					mHandler.removeMessages(MSG_ANIMATE);
					mHandler.removeMessages(MSG_PREPARE_ANIMATE);
					mHandler.sendMessageAtTime(
							mHandler.obtainMessage(MSG_PREPARE_ANIMATE),
							mCurrentAnimationTime);
				}
			}
			return false;
		}
	};

	public boolean initChange()
	{
		if (mState != State.READY)
		{
			// we are animating or just about to animate
			return false;
		}
		mState = State.ABOUT_TO_ANIMATE;
		mIsShrinking = mContent.getVisibility() == VISIBLE;
		if (!mIsShrinking)
		{
			// this could make flicker so we test mState in dispatchDraw()
			// to see if is equal to ABOUT_TO_ANIMATE
			mContent.setVisibility(VISIBLE);
		}
		return true;
	}

	private void postProcess()
	{
		if (mIsShrinking && mClosedHandle != null)
		{
			mHandle.setBackgroundDrawable(mClosedHandle);
		}
		else if (!mIsShrinking && mOpenedHandle != null)
		{
			mHandle.setBackgroundDrawable(mOpenedHandle);
		}
		// invoke listener if any
		if (panelListener != null)
		{
			if (mIsShrinking)
			{
				panelListener.onPanelClosed(Panel.this);
			}
			else
			{
				panelListener.onPanelOpened(Panel.this);
			}
		}
	}

	class PanelOnGestureListener implements OnGestureListener
	{
		float scrollY;
		float scrollX;

		@Override
		public boolean onDown(MotionEvent e)
		{
			scrollX = scrollY = 0;
			lastRawX = curRawX = lastRawY = curRawY = -1;
			lastEventTime = curEventTime = -1;
			initChange();
			return true;
		}

		@Override
		public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
				float velocityY)
		{
			mState = State.FLYING;

			float velocityX2, velocityY2;
			if (lastRawX == -1 && lastRawY == -1)	//见onScroll方法
			{
				velocityX2 = (curRawX - e1.getRawX())
						/ (curEventTime - e1.getEventTime()) * 1000; //  px/s
				velocityY2 = (curRawY - e1.getRawY())
						/ (curEventTime - e1.getEventTime()) * 1000;
			}
			else
			{
				velocityX2 = (curRawX - lastRawX)
						/ (curEventTime - lastEventTime) * 1000;
				velocityY2 = (curRawY - lastRawY)
						/ (curEventTime - lastEventTime) * 1000;
			}

			mVelocity = mOrientation == VERTICAL ? velocityY2 : velocityX2;

			if (Math.abs(mVelocity) > 50)
			{
				if (mVelocity > 0)
				{
					mAnimatedAcceleration = mMaximumAcceleration;
				}
				else
				{
					mAnimatedAcceleration = -mMaximumAcceleration;
				}

				long now = SystemClock.uptimeMillis();
				mAnimationLastTime = now;
				mCurrentAnimationTime = now + ANIMATION_FRAME_DURATION;
				mAnimating = true;
				mHandler.removeMessages(MSG_ANIMATE);
				mHandler.removeMessages(MSG_PREPARE_ANIMATE);
				mHandler.sendMessageAtTime(mHandler.obtainMessage(MSG_ANIMATE),
						mCurrentAnimationTime);
				return true;
			}
			return false;
		}

		@Override
		public void onLongPress(MotionEvent e)
		{
			// not used
		}

		@Override
		public boolean onScroll(MotionEvent e1, MotionEvent e2,
				float distanceX, float distanceY)
		{
			mState = State.TRACKING;
			float tmpY = 0, tmpX = 0;
			if (mOrientation == VERTICAL)
			{
				scrollY -= distanceY;
				if (mPosition == TOP)
				{
					tmpY = ensureRange(scrollY, -mContentHeight, 0);
				}
				else
				{
					tmpY = ensureRange(scrollY, 0, mContentHeight);
				}
			}
			else
			{
				scrollX -= distanceX;
				if (mPosition == LEFT)
				{
					tmpX = ensureRange(scrollX, -mContentWidth, 0);
				}
				else
				{
					tmpX = ensureRange(scrollX, 0, mContentWidth);
				}
			}

			if (tmpX != mTrackX || tmpY != mTrackY)
			{
				mTrackX = tmpX;
				mTrackY = tmpY;
				// invalidate(); //放在此导致极快速滑动至touch区域外界面不刷新(mTrackX、mTrackY均为0)
			}
			invalidate();

			lastRawX = curRawX;
			lastRawY = curRawY;
			lastEventTime = curEventTime;
			curRawX = e2.getRawX();
			curRawY = e2.getRawY();
			curEventTime = e2.getEventTime();
			return true;
		}

		@Override
		public void onShowPress(MotionEvent e)
		{
			// not used
		}

		@Override
		public boolean onSingleTapUp(MotionEvent e)
		{
			// not used
			return false;
		}
	}

	private void prepareAnimation()
	{

		switch (mPosition)
		{
			case LEFT:
				if (mIsShrinking)
				{
					mVelocity = -mMaximumMajorVelocity;
					mAnimatedAcceleration = -mMaximumAcceleration;

				}
				else
				{
					mVelocity = mMaximumMajorVelocity;
					mAnimatedAcceleration = mMaximumAcceleration;
					if (mTrackX == 0 && mState == State.ABOUT_TO_ANIMATE)
					{
						mTrackX = -mContentWidth;
					}
				}
				break;
			case RIGHT:
				if (mIsShrinking)
				{
					mVelocity = mMaximumMajorVelocity;
					mAnimatedAcceleration = mMaximumAcceleration;
				}
				else
				{
					mVelocity = -mMaximumMajorVelocity;
					mAnimatedAcceleration = -mMaximumAcceleration;

					if (mTrackX == 0 && mState == State.ABOUT_TO_ANIMATE)
					{
						mTrackX = mContentWidth;
					}
				}
				break;
			case TOP:
				if (mIsShrinking)
				{
					mVelocity = -mMaximumMajorVelocity;
					mAnimatedAcceleration = -mMaximumAcceleration;
				}
				else
				{
					mVelocity = mMaximumMajorVelocity;
					mAnimatedAcceleration = mMaximumAcceleration;

					if (mTrackX == 0 && mState == State.ABOUT_TO_ANIMATE)
					{
						mTrackY = -mContentHeight;
					}
				}
				break;
			case BOTTOM:
				if (mIsShrinking)
				{
					mVelocity = mMaximumMajorVelocity;
					mAnimatedAcceleration = mMaximumAcceleration;
				}
				else
				{
					mVelocity = -mMaximumMajorVelocity;
					mAnimatedAcceleration = -mMaximumAcceleration;

					if (mTrackX == 0 && mState == State.ABOUT_TO_ANIMATE)
					{
						mTrackY = mContentHeight;
					}
				}
				break;
		}

		if (mState == State.TRACKING)
		{
			if (mIsShrinking)
			{
				if ((mOrientation == VERTICAL && Math.abs(mTrackY) < mContentHeight / 2)
						|| (mOrientation == HORIZONTAL && Math.abs(mTrackX) < mContentWidth / 2))
				{
					mVelocity = -mVelocity;
					mAnimatedAcceleration = -mAnimatedAcceleration;
					mIsShrinking = !mIsShrinking;
				}
			}
			else
			{
				if ((mOrientation == VERTICAL && Math.abs(mTrackY) > mContentHeight / 2)
						|| (mOrientation == HORIZONTAL && Math.abs(mTrackX) > mContentWidth / 2))
				{
					mVelocity = -mVelocity;
					mAnimatedAcceleration = -mAnimatedAcceleration;
					mIsShrinking = !mIsShrinking;
				}
			}
		}
		if (mState != State.FLYING && mState != State.TRACKING)
		{
			mState = State.CLICK;
		}
	}

	private void doAnimation()
	{

		if (mAnimating)
		{
			long now = SystemClock.uptimeMillis();
			float t = (now - mAnimationLastTime) / 1000.0f; 	// ms -> s
			final float v = mVelocity; 							// px/s
			final float a = mAnimatedAcceleration; 				// px/s/s
			mVelocity = v + (a * t); 							// px/s
			mAnimationLastTime = now;

			switch (mPosition)
			{
				case LEFT:
					mTrackX = mTrackX + (v * t) + (0.5f * a * t * t); // px
					if (mTrackX > 0)
					{
						mTrackX = 0;
						mState = State.READY;
						mAnimating = false;
					}
					else if (mTrackX < -mContentWidth)
					{
						mTrackX = -mContentWidth;
						mContent.setVisibility(GONE);
						mState = State.READY;
						mAnimating = false;
					}
					break;
				case RIGHT:
					mTrackX = mTrackX + (v * t) + (0.5f * a * t * t);
					if (mTrackX < 0)
					{
						mTrackX = 0;
						mState = State.READY;
						mAnimating = false;
					}
					else if (mTrackX > mContentWidth)
					{
						mTrackX = mContentWidth;
						mContent.setVisibility(GONE);
						mState = State.READY;
						mAnimating = false;
					}
					break;
				case TOP:
					mTrackY = mTrackY + (v * t) + (0.5f * a * t * t);
					if (mTrackY > 0)
					{
						mTrackY = 0;
						mState = State.READY;
						mAnimating = false;
					}
					else if (mTrackY < -mContentHeight)
					{
						mTrackY = -mContentHeight;
						mContent.setVisibility(GONE);
						mState = State.READY;
						mAnimating = false;
					}
					break;
				case BOTTOM:
					mTrackY = mTrackY + (v * t) + (0.5f * a * t * t);
					if (mTrackY < 0)
					{
						mTrackY = 0;
						mState = State.READY;
						mAnimating = false;
					}
					else if (mTrackY > mContentHeight)
					{
						mTrackY = mContentHeight;
						mContent.setVisibility(GONE);
						mState = State.READY;
						mAnimating = false;
					}
					break;
			}
			invalidate();

			if (!mAnimating)
			{
				postProcess();
				return;
			}
			mCurrentAnimationTime += ANIMATION_FRAME_DURATION;
			mHandler.sendMessageAtTime(mHandler.obtainMessage(MSG_ANIMATE),
					mCurrentAnimationTime);

		}
	}

	private class SlidingHandler extends Handler
	{
		public void handleMessage(Message m)
		{
			switch (m.what)
			{
				case MSG_ANIMATE:
					doAnimation();
					break;
				case MSG_PREPARE_ANIMATE:
					prepareAnimation();
					doAnimation();
					break;
			}
		}
	}
}

http://blog.youkuaiyun.com/lovehong0306


工程下载地址:

http://download.youkuaiyun.com/detail/lovehong0306/4230052


评论 36
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值