仿android5.0分享控件,Scroller先占半屏,慢慢滑动至全屏

以下代码都从android 5.0中电话界面扣出来的控件 MultiShrinkScroller


package com.jtv.toolmg.ui;

import com.jtv.toolmg.R;
import com.jtv.toolmg.activity.SchedulingUtils;

import android.animation.Animator;
import android.animation.Animator.AnimatorListener;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.animation.ValueAnimator.AnimatorUpdateListener;
import android.app.Activity;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.support.v4.view.ViewCompat;
import android.support.v4.widget.EdgeEffectCompat;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.animation.AnimationUtils;
import android.view.animation.Interpolator;
import android.widget.FrameLayout;
import android.widget.ScrollView;
import android.widget.Scroller;
import android.widget.TextView;

/**
 * 仿android 5.0 电话界面的滚动
 * <p>
 *
 * @author 更生
 * @version 2016年6月1日
 */
public class MultiShrinkScroller extends FrameLayout {

	private VelocityTracker mVelocityTracker;
	private boolean mReceivedDown;
	private boolean mIsBeingDragged;
	private float[] mLastEventPosition = { 0, 0 };
	private boolean mHasEverTouchedTheTop;
	private int mMaximumHeaderHeight;
	private int mMinimumHeaderHeight;
	private int mIntermediateHeaderHeight;
	private boolean mIsOpenContactSquare;
	private Scroller mScroller;
	private EdgeEffectCompat mEdgeGlowBottom;
	private int mTouchSlop;
	private int mMaximumVelocity = 10;
	private int mMinimumVelocity;
	private int mTransparentStartHeight;
	private boolean mIsTwoPanel;// 是否有两个面板
	private View mScrollViewChild;
	private View mScrollView;
	private View mTransparentView;
	private static final float INTERMEDIATE_HEADER_HEIGHT_RATIO = 0.5f;
	private static final float MAXIMUM_FLING_VELOCITY = 2000;
	private static final int EXIT_FLING_ANIMATION_DURATION_MS = 300;
	private MultiShrinkScrollerListener mListener;
	// private Context context;
	private View mToolbar;// 这个控件的宽度决定了头的高度
	// private int mMaximumTitleMargin;
	private int mMaximumTitleMargin;
	private int mActionBarSize;
	private View mPhotoViewContainer;
	private TextView mLargeTextView;

	public MultiShrinkScroller(Context context, AttributeSet attrs, int defStyleAttr) {
		super(context, attrs, defStyleAttr);
		initFirst(context, attrs, defStyleAttr);
	}

	public MultiShrinkScroller(Context context) {
		this(context, null);
	}

	public MultiShrinkScroller(Context context, AttributeSet attrs) {
		this(context, attrs, 0);
	}

	private boolean shouldStartDrag(MotionEvent event) {
		if (mIsBeingDragged) {
			mIsBeingDragged = false;
			return false;
		}

		switch (event.getAction()) {
		// If we are in the middle of a fling and there is a down event, we'll steal it and
		// start a drag.
		case MotionEvent.ACTION_DOWN:
			updateLastEventPosition(event);
			if (!mScroller.isFinished()) {
				startDrag();
				return true;
			} else {
				mReceivedDown = true;
			}
			break;

		// Otherwise, we will start a drag if there is enough motion in the direction we are
		// capable of scrolling.
		case MotionEvent.ACTION_MOVE:
			if (motionShouldStartDrag(event)) {
				updateLastEventPosition(event);
				startDrag();
				return true;
			}
			break;
		}

		return false;
	}

	private void updateLastEventPosition(MotionEvent event) {
		mLastEventPosition[0] = event.getX();
		mLastEventPosition[1] = event.getY();
	}

	private boolean motionShouldStartDrag(MotionEvent event) {
		final float deltaX = event.getX() - mLastEventPosition[0];
		final float deltaY = event.getY() - mLastEventPosition[1];
		final boolean draggedX = (deltaX > mTouchSlop || deltaX < -mTouchSlop);
		final boolean draggedY = (deltaY > mTouchSlop || deltaY < -mTouchSlop);
		return draggedY && !draggedX;
	}

	private float updatePositionAndComputeDelta(MotionEvent event) {
		final int VERTICAL = 1;
		final float position = mLastEventPosition[VERTICAL];
		updateLastEventPosition(event);
		return position - mLastEventPosition[VERTICAL];
	}

	private void smoothScrollBy(int delta) {
		if (delta == 0) {
			// Delta=0 implies the code calling smoothScrollBy is sloppy. We should avoid doing
			// this, since it prevents Views from being able to register any clicks for 250ms.
			throw new IllegalArgumentException("Smooth scrolling by delta=0 is " + "pointless and harmful");
		}
		mScroller.startScroll(0, getScroll(), 0, delta);
		invalidate();
	}

	/**
	 * Return ratio of non-transparent:viewgroup-height for this viewgroup at the starting position.
	 */
	public float getStartingTransparentHeightRatio() {
		return getTransparentHeightRatio(mTransparentStartHeight);
	}

	private float getTransparentHeightRatio(int transparentHeight) {
		final float heightRatio = (float) transparentHeight / getHeight();
		// Clamp between [0, 1] in case this is called before height is initialized.
		return 1.0f - Math.max(Math.min(1.0f, heightRatio), 0f);
	}

	/**
	 * Amount of transparent space above the header/toolbar.
	 */
	public int getScrollNeededToBeFullScreen() {
		return getTransparentViewHeight();
	}

	private static final Interpolator sInterpolator = new Interpolator() {

		/**
		 * {@inheritDoc}
		 */
		@Override
		public float getInterpolation(float t) {
			t -= 1.0f;
			return t * t * t * t * t + 1.0f;
		}
	};

	public void setHeaderHeight(int height) {
		final ViewGroup.LayoutParams toolbarLayoutParams = mToolbar.getLayoutParams();
		toolbarLayoutParams.height = height;
		mToolbar.setLayoutParams(toolbarLayoutParams);
		// updatePhotoTintAndDropShadow();
		// updateHeaderTextSizeAndMargin();
	}

	/**
	 * Set the header size and padding, based on the current scroll position.
	 */
	// private void updateHeaderTextSizeAndMargin() {
	// if (mIsTwoPanel) {
	// // The text size stays at a constant size & location in two panel layouts.
	// return;
	// }
	//
	// // The pivot point for scaling should be middle of the starting side.
	// if (isLayoutRtl()) {
	// mLargeTextView.setPivotX(mLargeTextView.getWidth());
	// } else {
	// mLargeTextView.setPivotX(0);
	// }
	// mLargeTextView.setPivotY(mLargeTextView.getHeight() / 2);
	//
	// final int toolbarHeight = mToolbar.getLayoutParams().height;
	// mPhotoTouchInterceptOverlay.setClickable(toolbarHeight != mMaximumHeaderHeight);
	//
	// if (toolbarHeight >= mMaximumHeaderHeight) {
	// // Everything is full size when the header is fully expanded.
	// mLargeTextView.setScaleX(1);
	// mLargeTextView.setScaleY(1);
	// setInterpolatedTitleMargins(1);
	// return;
	// }
	//
	// final float ratio = (toolbarHeight - mMinimumHeaderHeight)
	// / (float) (mMaximumHeaderHeight - mMinimumHeaderHeight);
	// final float minimumSize = mInvisiblePlaceholderTextView.getHeight();
	// float bezierOutput = mTextSizePathInterpolator.getInterpolation(ratio);
	// float scale = (minimumSize + (mMaximumHeaderTextSize - minimumSize) * bezierOutput) / mMaximumHeaderTextSize;
	//
	// // Clamp to reasonable/finite values before passing into framework. The values
	// // can be wacky before the first pre-render.
	// bezierOutput = (float) Math.min(bezierOutput, 1.0f);
	// scale = (float) Math.min(scale, 1.0f);
	//
	// mLargeTextView.setScaleX(scale);
	// mLargeTextView.setScaleY(scale);
	// setInterpolatedTitleMargins(bezierOutput);
	// updateJoynView();
	// }

	// private void updatePhotoTintAndDropShadow() {
	// // Let's keep an eye on how long this method takes to complete. Right now, it takes ~0.2ms
	// // on a Nexus 5. If it starts to get much slower, there are a number of easy optimizations
	// // available.
	// Trace.beginSection("updatePhotoTintAndDropShadow");
	//
	// if (mIsTwoPanel && !mPhotoView.isBasedOffLetterTile()) {
	// // When in two panel mode, UX considers photo tinting unnecessary for non letter
	// // tile photos.
	// mTitleGradientDrawable.setAlpha(0xFF);
	// mActionBarGradientDrawable.setAlpha(0xFF);
	// return;
	// }
	//
	// // We need to use toolbarLayoutParams to determine the height, since the layout
	// // params can be updated before the height change is reflected inside the View#getHeight().
	// final int toolbarHeight = getToolbarHeight();
	//
	// if (toolbarHeight <= mMinimumHeaderHeight && !mIsTwoPanel) {
	// mPhotoViewContainer.setElevation(mToolbarElevation);
	// } else {
	// mPhotoViewContainer.setElevation(0);
	// }
	//
	// // Reuse an existing mColorFilter (to avoid GC pauses) to change the photo's tint.
	// mPhotoView.clearColorFilter();
	//
	// // Ratio of current size to maximum size of the header.
	// final float ratio;
	// // The value that "ratio" will have when the header is at its starting/intermediate size.
	// final float intermediateRatio = calculateHeightRatio(
	// (int) (mMaximumPortraitHeaderHeight * INTERMEDIATE_HEADER_HEIGHT_RATIO));
	// if (!mIsTwoPanel) {
	// ratio = calculateHeightRatio(toolbarHeight);
	// } else {
	// // We want the ratio and intermediateRatio to have the *approximate* values
	// // they would have in portrait mode when at the intermediate position.
	// ratio = intermediateRatio;
	// }
	//
	// final float linearBeforeMiddle = Math.max(1 - (1 - ratio) / intermediateRatio, 0);
	//
	// // Want a function with a derivative of 0 at x=0. I don't want it to grow too
	// // slowly before x=0.5. x^1.1 satisfies both requirements.
	// final float EXPONENT_ALMOST_ONE = 1.1f;
	// final float semiLinearBeforeMiddle = (float) Math.pow(linearBeforeMiddle, EXPONENT_ALMOST_ONE);
	// mColorMatrix.reset();
	// mColorMatrix.setSaturation(semiLinearBeforeMiddle);
	// mColorMatrix
	// .postConcat(alphaMatrix(1 - mWhiteBlendingPathInterpolator.getInterpolation(1 - ratio), Color.WHITE));
	//
	// final float colorAlpha;
	// if (mPhotoView.isBasedOffLetterTile()) {
	// // Since the letter tile only has white and grey, tint it more slowly. Otherwise
	// // it will be completely invisible before we reach the intermediate point. The values
	// // for TILE_EXPONENT and slowingFactor are chosen to achieve DESIRED_INTERMEDIATE_ALPHA
	// // at the intermediate/starting position.
	// final float DESIRED_INTERMEDIATE_ALPHA = 0.9f;
	// final float TILE_EXPONENT = 1.5f;
	// final float slowingFactor = (float) ((1 - intermediateRatio) / intermediateRatio
	// / (1 - Math.pow(1 - DESIRED_INTERMEDIATE_ALPHA, 1 / TILE_EXPONENT)));
	// float linearBeforeMiddleish = Math.max(1 - (1 - ratio) / intermediateRatio / slowingFactor, 0);
	// colorAlpha = 1 - (float) Math.pow(linearBeforeMiddleish, TILE_EXPONENT);
	// mColorMatrix.postConcat(alphaMatrix(colorAlpha, mHeaderTintColor));
	// } else {
	// colorAlpha = 1 - semiLinearBeforeMiddle;
	// mColorMatrix.postConcat(multiplyBlendMatrix(mHeaderTintColor, colorAlpha));
	// }
	//
	// mPhotoView.setColorFilter(new ColorMatrixColorFilter(mColorMatrix));
	// // Tell the photo view what tint we are trying to achieve. Depending on the type of
	// // drawable used, the photo view may or may not use this tint.
	// mPhotoView.setTint(mHeaderTintColor);
	//
	// final int gradientAlpha = (int) (255 * linearBeforeMiddle);
	// mTitleGradientDrawable.setAlpha(gradientAlpha);
	// mActionBarGradientDrawable.setAlpha(gradientAlpha);
	//
	// Trace.endSection();
	// }

	public void initValue(Activity con, MultiShrinkScrollerListener listener) {

		mScrollView = (ScrollView) con.findViewById(R.id.content_scroller);
		mScrollViewChild = con.findViewById(R.id.card_container);
		mTransparentView = con.findViewById(R.id.transparent_view);
		mToolbar = con.findViewById(R.id.toolbar_parent);
		mPhotoViewContainer = findViewById(R.id.toolbar_parent);
		mLargeTextView = (TextView) findViewById(R.id.large_title);
		this.mListener = listener;

		defaultDraw();
	}

	private void setTransparentViewHeight(int height) {
		mTransparentView.getLayoutParams().height = height;
		mTransparentView.setLayoutParams(mTransparentView.getLayoutParams());
	}

	private void scrollUp(int delta) {
		if (getTransparentViewHeight() != 0) {
			final int originalValue = getTransparentViewHeight();
			setTransparentViewHeight(getTransparentViewHeight() - delta);
			setTransparentViewHeight(Math.max(0, getTransparentViewHeight()));
			delta -= originalValue - getTransparentViewHeight();
		}
		final ViewGroup.LayoutParams toolbarLayoutParams = mToolbar.getLayoutParams();
		if (toolbarLayoutParams.height > getFullyCompressedHeaderHeight()) {
			final int originalValue = toolbarLayoutParams.height;
			toolbarLayoutParams.height -= delta;
			toolbarLayoutParams.height = Math.max(toolbarLayoutParams.height, getFullyCompressedHeaderHeight());
			mToolbar.setLayoutParams(toolbarLayoutParams);
			delta -= originalValue - toolbarLayoutParams.height;
		}
		mScrollView.scrollBy(0, delta);
	}

	private void scrollDown(int delta) {
		if (mScrollView.getScrollY() > 0) {
			final int originalValue = mScrollView.getScrollY();
			mScrollView.scrollBy(0, delta);
			delta -= mScrollView.getScrollY() - originalValue;
		}
		final ViewGroup.LayoutParams toolbarLayoutParams = mToolbar.getLayoutParams();
		if (toolbarLayoutParams.height < getMaximumScrollableHeaderHeight()) {
			final int originalValue = toolbarLayoutParams.height;
			toolbarLayoutParams.height -= delta;
			toolbarLayoutParams.height = Math.min(toolbarLayoutParams.height, getMaximumScrollableHeaderHeight());
			mToolbar.setLayoutParams(toolbarLayoutParams);
			delta -= originalValue - toolbarLayoutParams.height;
		}
		setTransparentViewHeight(getTransparentViewHeight() - delta);

		if (getScrollUntilOffBottom() <= 0) {
			post(new Runnable() {

				@Override
				public void run() {
					if (mListener != null) {
						mListener.onScrolledOffBottom();
						// No other messages need to be sent to the listener.
						mListener = null;
					}
				}
			});
		}
	}

	@Override
	public void scrollTo(int x, int y) {
		final int delta = y - getScroll();
		boolean wasFullscreen = getScrollNeededToBeFullScreen() <= 0;
		if (delta > 0) {
			scrollUp(delta);
		} else {
			scrollDown(delta);
		}
		// updatePhotoTintAndDropShadow();
		// updateHeaderTextSizeAndMargin();
		final boolean isFullscreen = getScrollNeededToBeFullScreen() <= 0;
		mHasEverTouchedTheTop |= isFullscreen;
		if (mListener != null) {
			if (wasFullscreen && !isFullscreen) {
				mListener.onExitFullscreen();
			} else if (!wasFullscreen && isFullscreen) {
				mListener.onEnterFullscreen();
			}
			if (!isFullscreen || !wasFullscreen) {
				mListener.onTransparentViewHeightChange(getTransparentHeightRatio(getTransparentViewHeight()));
			}
		}
	}

	private void defaultDraw() {
		SchedulingUtils.doOnPreDraw(this, /* drawNextFrame = */ false, new Runnable() {

			@Override
			public void run() {
				/** M: Bug Fix for ALPS1747395 @{ */
				if (getContext() == null) {
					return;
				}
				/** }@ */
				/// M: bug fix for ALPS01768247
				mIsTwoPanel = getResources().getBoolean(R.bool.quickcontact_two_panel);
				if (!mIsTwoPanel) {
					// We never want the height of the photo view to exceed its width.
					mMaximumHeaderHeight = mPhotoViewContainer.getWidth();
					mIntermediateHeaderHeight = (int) (mMaximumHeaderHeight * INTERMEDIATE_HEADER_HEIGHT_RATIO);
				}
				// final boolean isLandscape = getResources()
				// .getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE;
				// mMaximumPortraitHeaderHeight = isLandscape ? getHeight() : mPhotoViewContainer.getWidth();
				setHeaderHeight(getMaximumScrollableHeaderHeight());
				// mMaximumHeaderTextSize = mLargeTextView.getHeight();
				if (mIsTwoPanel) {
					mMaximumHeaderHeight = getHeight();
					mMinimumHeaderHeight = mMaximumHeaderHeight;
					mIntermediateHeaderHeight = mMaximumHeaderHeight;

					// Permanently set photo width and height.
					final TypedValue photoRatio = new TypedValue();
					getResources().getValue(R.vals.quickcontact_photo_ratio, photoRatio, /* resolveRefs = */ true);
					final ViewGroup.LayoutParams photoLayoutParams = mPhotoViewContainer.getLayoutParams();
					photoLayoutParams.height = mMaximumHeaderHeight;
					photoLayoutParams.width = (int) (mMaximumHeaderHeight * photoRatio.getFloat());
					mPhotoViewContainer.setLayoutParams(photoLayoutParams);

					// Permanently set title width and margin.
					final FrameLayout.LayoutParams largeTextLayoutParams = (FrameLayout.LayoutParams) mLargeTextView
							.getLayoutParams();
					largeTextLayoutParams.width = photoLayoutParams.width - largeTextLayoutParams.leftMargin
							- largeTextLayoutParams.rightMargin;
					largeTextLayoutParams.gravity = Gravity.BOTTOM | Gravity.START;
					mLargeTextView.setLayoutParams(largeTextLayoutParams);
				} else {
					// Set the width of mLargeTextView as if it was nested inside
					// mPhotoViewContainer.
					mLargeTextView.setWidth(mPhotoViewContainer.getWidth() - 2 * mMaximumTitleMargin);
				}
				// updateHeaderTextSizeAndMargin();
				// configureGradientViewHeights();
			}
		});
	}

	private boolean isLayoutRtl() {
		return false;
	}

	@Override
	public void draw(Canvas canvas) {
		super.draw(canvas);

		if (!mEdgeGlowBottom.isFinished()) {
			final int restoreCount = canvas.save();
			final int width = getWidth() - getPaddingLeft() - getPaddingRight();
			final int height = getHeight();

			// Draw the EdgeEffect on the bottom of the Window (Or a little bit below the bottom
			// of the Window if we start to scroll upwards while EdgeEffect is visible). This
			// does not need to consider the case where this MultiShrinkScroller doesn't fill
			// the Window, since the nested ScrollView should be set to fillViewport.
			canvas.translate(-width + getPaddingLeft(), height + getMaximumScrollUpwards() - getScroll());

			canvas.rotate(180, width, 0);
			if (mIsTwoPanel) {
				// Only show the EdgeEffect on the bottom of the ScrollView.
				mEdgeGlowBottom.setSize(mScrollView.getWidth(), height);
				if (isLayoutRtl()) {
					canvas.translate(mPhotoViewContainer.getWidth(), 0);
				}
			} else {
				mEdgeGlowBottom.setSize(width, height);
			}
			if (mEdgeGlowBottom.draw(canvas)) {
				postInvalidateOnAnimationCurr();
			}
			canvas.restoreToCount(restoreCount);
		}
	}

	// private void calculateCollapsedLargeTitlePadding() {
	// final Rect largeTextViewRect = new Rect();
	// final Rect invisiblePlaceholderTextViewRect = new Rect();
	// mToolbar.getBoundsOnScreen(largeTextViewRect);
	// mInvisiblePlaceholderTextView.getBoundsOnScreen(invisiblePlaceholderTextViewRect);
	// if (isLayoutRtl()) {
	// mCollapsedTitleStartMargin = largeTextViewRect.right
	// - invisiblePlaceholderTextViewRect.right;
	// } else {
	// mCollapsedTitleStartMargin = invisiblePlaceholderTextViewRect.left
	// - largeTextViewRect.left;
	// }
	//
	// // Distance between top of toolbar to the center of the target rectangle.
	// final int desiredTopToCenter = (
	// invisiblePlaceholderTextViewRect.top + invisiblePlaceholderTextViewRect.bottom)
	// / 2 - largeTextViewRect.top;
	// // Padding needed on the mLargeTextView so that it has the same amount of
	// // padding as the target rectangle.
	// mCollapsedTitleBottomMargin = desiredTopToCenter - mLargeTextView.getHeight() / 2;
	// }

	public interface MultiShrinkScrollerListener {

		void onScrolledOffBottom();

		void onStartScrollOffBottom();

		void onTransparentViewHeightChange(float ratio);

		void onEntranceAnimationDone();

		void onEnterFullscreen();

		void onExitFullscreen();
	}

	public void initFirst(Context context, AttributeSet attrs, int defStyleAttr) {
		final ViewConfiguration configuration = ViewConfiguration.get(context);
		setFocusable(false);
		// Drawing must be enabled in order to support EdgeEffect
		setWillNotDraw(/* willNotDraw = */ false);

		mEdgeGlowBottom = new EdgeEffectCompat(context);
		mScroller = new Scroller(context, sInterpolator);
		mTouchSlop = configuration.getScaledTouchSlop();
		mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
		mMaximumVelocity = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, MAXIMUM_FLING_VELOCITY,
				getResources().getDisplayMetrics());
		// 控制着初始化头部空白的高度
		mTransparentStartHeight = (int) getResources().getDimension(R.dimen.quickcontact_starting_empty_height);
		// mToolbarElevation = getResources().getDimension(
		// R.dimen.quick_contact_toolbar_elevation);
		mIsTwoPanel = getResources().getBoolean(R.bool.quickcontact_two_panel);
		// mMaximumTitleMargin = (int) getResources().getDimension(
		// R.dimen.quickcontact_title_initial_margin);

		final TypedArray attributeArray = context.obtainStyledAttributes(new int[] { android.R.attr.actionBarSize });
		mActionBarSize = attributeArray.getDimensionPixelSize(0, 0);
		mMinimumHeaderHeight = mActionBarSize;
		// This value is approximately equal to the portrait ActionBar size. It isn't exactly the
		// same, since the landscape and portrait ActionBar sizes can be different.
		// mMinimumPortraitHeaderHeight = mMinimumHeaderHeight;
		attributeArray.recycle();
	}

	@Override
	public boolean onTouchEvent(MotionEvent event) {
		final int action = event.getAction();

		if (mVelocityTracker == null) {
			mVelocityTracker = VelocityTracker.obtain();
		}
		mVelocityTracker.addMovement(event);

		if (!mIsBeingDragged) {
			if (shouldStartDrag(event)) {
				return true;
			}

			if (action == MotionEvent.ACTION_UP && mReceivedDown) {
				mReceivedDown = false;
				return performClick();
			}
			return true;
		}

		switch (action) {
		case MotionEvent.ACTION_MOVE:
			final float delta = updatePositionAndComputeDelta(event);
			scrollTo(0, getScroll() + (int) delta);
			mReceivedDown = false;

			if (mIsBeingDragged) {
				final int distanceFromMaxScrolling = getMaximumScrollUpwards() - getScroll();
				if (delta > distanceFromMaxScrolling) {
					// The ScrollView is being pulled upwards while there is no more
					// content offscreen, and the view port is already fully expanded.
					mEdgeGlowBottom.onPull(delta / getHeight(), 1 - event.getX() / getWidth());
				}

				if (!mEdgeGlowBottom.isFinished()) {
					postInvalidateOnAnimationCurr();
				}

			}
			break;

		case MotionEvent.ACTION_UP:
		case MotionEvent.ACTION_CANCEL:
			stopDrag(action == MotionEvent.ACTION_CANCEL);
			mReceivedDown = false;
			break;
		}

		return true;
	}

	public void postInvalidateOnAnimationCurr() {
		// postInvalidateOnAnimation();
		ViewCompat.postInvalidateOnAnimation(this);
	}

	/**
	 * Returns the minimum size that we want to compress the header to, given that we don't want to allow the the
	 * ScrollView to scroll unless there is new content off of the edge of ScrollView.
	 */
	private int getFullyCompressedHeaderHeight() {
		return Math.min(Math.max((getHeightTitle() - getOverflowingChildViewSize()), mMinimumHeaderHeight),
				getMaximumScrollableHeaderHeight());
	}

	private int getHeightTitle() {
		if (mToolbar == null)
			return 1;
		android.view.ViewGroup.LayoutParams layoutParams = mToolbar.getLayoutParams();
		if (layoutParams == null) {
			return 0;
		}
		return layoutParams.height;
	}

	private int getMaximumScrollableHeaderHeight() {
		return mIsOpenContactSquare ? mMaximumHeaderHeight : mIntermediateHeaderHeight;
	}

	private int getOverflowingChildViewSize() {
		final int usedScrollViewSpace = mScrollViewChild.getHeight();
		return -getHeight() + usedScrollViewSpace + getHeightTitle();
	}

	private int getMaximumScrollUpwards() {
		if (!mIsTwoPanel) {
			return mTransparentStartHeight
					// How much the Header view can compress
					+ getMaximumScrollableHeaderHeight() - getFullyCompressedHeaderHeight()
					// How much the ScrollView can scroll. 0, if child is smaller than ScrollView.
					+ Math.max(0, mScrollViewChild.getHeight() - getHeight() + getFullyCompressedHeaderHeight());
		} else {
			return mTransparentStartHeight
					// How much the ScrollView can scroll. 0, if child is smaller than ScrollView.
					+ Math.max(0, mScrollViewChild.getHeight() - getHeight());
		}
	}

	private void startDrag() {
		mIsBeingDragged = true;
		mScroller.abortAnimation();
	}

	private void fling(float velocity) {
		if (Math.abs(mMaximumVelocity) < Math.abs(velocity)) {
			velocity = -mMaximumVelocity * Math.signum(velocity);
		}
		// For reasons I do not understand, scrolling is less janky when maxY=Integer.MAX_VALUE
		// then when maxY is set to an actual value.
		mScroller.fling(0, getScroll(), 0, (int) velocity, 0, 0, -Integer.MAX_VALUE, Integer.MAX_VALUE);
		invalidate();
	}

	private void stopDrag(boolean cancelled) {
		mIsBeingDragged = false;
		if (!cancelled && getChildCount() > 0) {
			final float velocity = getCurrentVelocity();
			if (velocity > mMinimumVelocity || velocity < -mMinimumVelocity) {
				fling(-velocity);
				onDragFinished(mScroller.getFinalY() - mScroller.getStartY());
			} else {
				onDragFinished(/* flingDelta = */ 0);
			}
		} else {
			onDragFinished(/* flingDelta = */ 0);
		}

		if (mVelocityTracker != null) {
			mVelocityTracker.recycle();
			mVelocityTracker = null;
		}

		mEdgeGlowBottom.onRelease();
	}

	private static final int PIXELS_PER_SECOND = 1000;

	private float getCurrentVelocity() {
		if (mVelocityTracker == null) {
			return 0;
		}
		mVelocityTracker.computeCurrentVelocity(PIXELS_PER_SECOND, mMaximumVelocity);
		return mVelocityTracker.getYVelocity();
	}

	private void onDragFinished(int flingDelta) {
		if (!snapToTop(flingDelta)) {
			// The drag/fling won't result in the content at the top of the Window. Consider
			// snapping the content to the bottom of the window.
			snapToBottom(flingDelta);
		}
	}

	/**
	 * If needed, snap the subviews to the top of the Window.
	 */
	private boolean snapToTop(int flingDelta) {
		if (mHasEverTouchedTheTop) {
			// Only when first interacting with QuickContacts should QuickContacts snap to the top
			// of the screen. After this, QuickContacts can be placed most anywhere on the screen.
			return false;
		}
		final int requiredScroll = -getScroll_ignoreOversizedHeaderForSnapping() + mTransparentStartHeight;
		if (-getScroll_ignoreOversizedHeaderForSnapping() - flingDelta < 0
				&& -getScroll_ignoreOversizedHeaderForSnapping() - flingDelta > -mTransparentStartHeight
				&& requiredScroll != 0) {
			// We finish scrolling above the empty starting height, and aren't projected
			// to fling past the top of the Window, so elastically snap the empty space shut.
			mScroller.forceFinished(true);
			smoothScrollBy(requiredScroll);
			return true;
		}
		return false;
	}

	private int getScroll_ignoreOversizedHeaderForSnapping() {
		return mTransparentStartHeight - getTransparentViewHeight()
				+ Math.max(getMaximumScrollableHeaderHeight() - getToolbarHeight(), 0) + mScrollView.getScrollY();
	}

	private int getTransparentViewHeight() {
		return mTransparentView.getLayoutParams().height;
	}

	public int getToolbarHeight() {
		return getHeightTitle();
	}

	public void setScroll(int scroll) {
		scrollTo(0, scroll);
	}

	/**
	 * Returns the total amount scrolled inside the nested ScrollView + the amount of shrinking performed on the
	 * ToolBar. This is the value inspected by animators.
	 */

	public int getScroll() {
		return mTransparentStartHeight - getTransparentViewHeight() + getMaximumScrollableHeaderHeight()
				- getToolbarHeight() + mScrollView.getScrollY();
	}

	/**
	 * If needed, scroll all the subviews off the bottom of the Window.
	 */
	private void snapToBottom(int flingDelta) {
		if (mHasEverTouchedTheTop) {
			// If QuickContacts has touched the top of the screen previously, then we
			// will less aggressively snap to the bottom of the screen.
			final float predictedScrollPastTop = -getScroll() + mTransparentStartHeight - flingDelta;
			final boolean isLandscape = getResources()
					.getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE;
			if (isLandscape) {
				// In landscape orientation, we dismiss the QC once it goes below the starting
				// starting offset that is used when QC starts in collapsed mode.
				if (predictedScrollPastTop > mTransparentStartHeight) {
					scrollOffBottom();
				}
			} else {
				// In portrait orientation, we dismiss the QC once it goes below
				// mIntermediateHeaderHeight within the bottom of the screen.
				final float heightMinusHeader = getHeight() - mIntermediateHeaderHeight;
				if (predictedScrollPastTop > heightMinusHeader) {
					scrollOffBottom();
				}
			}
			return;
		}
		if (-getScroll() - flingDelta > 0) {
			scrollOffBottom();
		}
	}

	private int getScrollUntilOffBottom() {
		return getHeight() + getScroll_ignoreOversizedHeaderForSnapping() - mTransparentStartHeight;
	}

	private final AnimatorListener mSnapToBottomListener = new AnimatorListenerAdapter() {

		@Override
		public void onAnimationEnd(Animator animation) {
			if (getScrollUntilOffBottom() > 0 && mListener != null) {
				// Due to a rounding error, after the animation finished we haven't fully scrolled
				// off the screen. Lie to the listener: tell it that we did scroll off the screen.
				mListener.onScrolledOffBottom();
				// No other messages need to be sent to the listener.
				mListener = null;
			}
		}
	};

	/**
	 * @param scrollToCurrentPosition if true, will scroll from the bottom of the screen to the current position.
	 *            Otherwise, will scroll from the bottom of the screen to the top of the screen.
	 */
	public void scrollUpForEntranceAnimation(boolean scrollToCurrentPosition) {
		final int currentPosition = getScroll();
		final int bottomScrollPosition = currentPosition - (getHeight() - getTransparentViewHeight()) + 1;
		Interpolator interpolator = AnimationUtils.loadInterpolator(getContext(),
				android.R.interpolator.linear_out_slow_in);
		final int desiredValue = currentPosition
				+ (scrollToCurrentPosition ? currentPosition : getTransparentViewHeight());
		final ObjectAnimator animator = ObjectAnimator.ofInt(this, "scroll", bottomScrollPosition, desiredValue);
		animator.setInterpolator(interpolator);
		animator.addUpdateListener(new AnimatorUpdateListener() {

			@Override
			public void onAnimationUpdate(ValueAnimator animation) {
				if (animation.getAnimatedValue().equals(desiredValue) && mListener != null) {
					mListener.onEntranceAnimationDone();
				}
			}
		});
		animator.start();
	}

	/**
	 * Interpolator that enforces a specific starting velocity. This is useful to avoid a discontinuity between dragging
	 * speed and flinging speed.
	 *
	 * Similar to a {@link android.view.animation.AccelerateInterpolator} in the sense that getInterpolation() is a
	 * quadratic function.
	 */
	private static class AcceleratingFlingInterpolator implements Interpolator {

		private final float mStartingSpeedPixelsPerFrame;
		private final float mDurationMs;
		private final int mPixelsDelta;
		private final float mNumberFrames;

		public AcceleratingFlingInterpolator(int durationMs, float startingSpeedPixelsPerSecond, int pixelsDelta) {
			mStartingSpeedPixelsPerFrame = startingSpeedPixelsPerSecond / getRefreshRate();
			mDurationMs = durationMs;
			mPixelsDelta = pixelsDelta;
			mNumberFrames = mDurationMs / getFrameIntervalMs();
		}

		@Override
		public float getInterpolation(float input) {
			final float animationIntervalNumber = mNumberFrames * input;
			final float linearDelta = (animationIntervalNumber * mStartingSpeedPixelsPerFrame) / mPixelsDelta;
			// Add the results of a linear interpolator (with the initial speed) with the
			// results of a AccelerateInterpolator.
			if (mStartingSpeedPixelsPerFrame > 0) {
				return Math.min(input * input + linearDelta, 1);
			} else {
				// Initial fling was in the wrong direction, make sure that the quadratic component
				// grows faster in order to make up for this.
				return Math.min(input * (input - linearDelta) + linearDelta, 1);
			}
		}

		private float getRefreshRate() {
			// DisplayInfo di = DisplayManagerGlobal.getInstance().getDisplayInfo(Display.DEFAULT_DISPLAY);
			return 1;
		}

		public long getFrameIntervalMs() {
			return (long) (1000 / getRefreshRate());
		}
	}

	// 滚动到底部
	public void scrollOffBottom() {
		final Interpolator interpolator = new AcceleratingFlingInterpolator(EXIT_FLING_ANIMATION_DURATION_MS,
				getCurrentVelocity(), getScrollUntilOffBottom());
		mScroller.forceFinished(true);
		ObjectAnimator translateAnimation = ObjectAnimator.ofInt(this, "scroll",
				getScroll() - getScrollUntilOffBottom());
		translateAnimation.setRepeatCount(0);
		translateAnimation.setInterpolator(interpolator);
		translateAnimation.setDuration(EXIT_FLING_ANIMATION_DURATION_MS);
		translateAnimation.addListener(mSnapToBottomListener);
		translateAnimation.start();
		if (mListener != null) {
			mListener.onStartScrollOffBottom();
		}
	}

	/**** --------------------工具------------------ ****/

	/**
	 * 运行时动画,不用
	 */
	private void showActivity() {
		if (this != null) {
			this.setVisibility(View.VISIBLE);
			SchedulingUtils.doOnPreDraw(this, /* drawNextFrame = */ false, new Runnable() {

				@Override
				public void run() {
					runEntranceAnimation();
				}
			});
		}
	}

	private int mExtraMode;
	public static final int MODE_FULLY_EXPANDED = 4;// 滚动全屏
	private static final int DEFAULT_SCRIM_ALPHA = 0xC8;

	private void runEntranceAnimation() {
		this.scrollUpForEntranceAnimation(mExtraMode != MODE_FULLY_EXPANDED);
	}

	/**
	 * 设置背景透明
	 * 
	 * @param mHasAlreadyBeenOpened
	 */
	private void setBackGroundAlpha(final boolean mHasAlreadyBeenOpened) {
		SchedulingUtils.doOnPreDraw(this, /* drawNextFrame = */ true, new Runnable() {

			@Override
			public void run() {
				int SCRIM_COLOR = Color.argb(DEFAULT_SCRIM_ALPHA, 0, 0, 0);
				ColorDrawable mWindowScrim = new ColorDrawable(SCRIM_COLOR);

				// mWindowScrim.setAlpha((int) (0xFF * ratio));//设置背景

				if (!mHasAlreadyBeenOpened) {
					final float alphaRatio = mExtraMode == MODE_FULLY_EXPANDED ? 1
							: getStartingTransparentHeightRatio();
					final int duration = getResources().getInteger(android.R.integer.config_shortAnimTime);
					final int desiredAlpha = (int) (0xFF * alphaRatio);
					ObjectAnimator o = ObjectAnimator.ofInt(mWindowScrim, "alpha", 0, desiredAlpha)
							.setDuration(duration);
					o.start();
				}
			}
		});
	}

}
package com.jtv.toolmg.ui;

import android.content.Context;
import android.os.Parcelable;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.widget.ScrollView;

/**
 * A {@link ScrollView} that doesn't respond or intercept touch events.
 *
 * This is used in combination with {@link com.android.contacts.widget.MultiShrinkScroller} so
 * that MultiShrinkScroller can handle all scrolling & saving.
 */
public class TouchlessScrollView extends ScrollView {

    public TouchlessScrollView(Context context) {
        this(context, null);
    }

    public TouchlessScrollView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public TouchlessScrollView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    protected Parcelable onSaveInstanceState() {
        // Do not save the current scroll position. Always store scrollY=0 and delegate
        // responsibility of saving state to the MultiShrinkScroller.
        final int scrollY = getScrollY();
        setScrollY(0);
        final Parcelable returnValue = super.onSaveInstanceState();
        setScrollY(scrollY);
        return returnValue;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        return false;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        return false;
    }
}

package com.jtv.toolmg.activity;

import com.jtv.toolmg.R;
import com.jtv.toolmg.ui.MultiShrinkScroller;
import com.jtv.toolmg.ui.MultiShrinkScroller.MultiShrinkScrollerListener;

import android.annotation.SuppressLint;
import android.app.Activity;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;

public class Test extends Activity {

	private MultiShrinkScroller mScroller;
	private boolean mHasAlreadyBeenOpened;
	private int mExtraMode;
	private boolean mIsExitAnimationInProgress;
	public static final int MODE_FULLY_EXPANDED = 4;
	private static final int DEFAULT_SCRIM_ALPHA = 0xC8;
	private static final int SCRIM_COLOR = Color.argb(DEFAULT_SCRIM_ALPHA, 0, 0, 0);

	@SuppressLint("NewApi")
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.test);
		getWindow().setStatusBarColor(Color.TRANSPARENT);
		mScroller = (MultiShrinkScroller) findViewById(R.id.multiscroller);
		mScroller.initValue(this, mMultiShrinkScrollerListener);
		mWindowScrim = new ColorDrawable(SCRIM_COLOR);
		mWindowScrim.setAlpha(0);
		// showActivity();
		getWindow().setBackgroundDrawable(mWindowScrim);
		final View transparentView = findViewById(R.id.transparent_view);
		if (mScroller != null) {
			transparentView.setOnClickListener(new OnClickListener() {

				@Override
				public void onClick(View v) {
					mScroller.scrollOffBottom();
				}
			});
		}
//		mScroller.showActivity();
//		mScroller.setBackGroundAlpha(false);
	}

	@Override
	public void onBackPressed() {
		if (mScroller != null) {
			if (!mIsExitAnimationInProgress) {
				mScroller.scrollOffBottom();
			}
		} else {
			super.onBackPressed();
		}
	}

	private void showActivity() {
		if (mScroller != null) {
			mScroller.setVisibility(View.VISIBLE);
			SchedulingUtils.doOnPreDraw(mScroller, /* drawNextFrame = */ false, new Runnable() {

				@Override
				public void run() {
					runEntranceAnimation();
				}
			});
		}
	}

	private void runEntranceAnimation() {
		if (mHasAlreadyBeenOpened) {
			return;
		}
		mHasAlreadyBeenOpened = true;
		mScroller.scrollUpForEntranceAnimation(mExtraMode != MODE_FULLY_EXPANDED);
	}

	private ColorDrawable mWindowScrim;
	final MultiShrinkScrollerListener mMultiShrinkScrollerListener = new MultiShrinkScrollerListener() {

		private boolean mIsEntranceAnimationFinished;

		@Override
		public void onScrolledOffBottom() {
			finish();
		}

		@Override
		public void onEnterFullscreen() {
			// updateStatusBarColor();
		}

		@Override
		public void onExitFullscreen() {
			// updateStatusBarColor();
		}

		@Override
		public void onStartScrollOffBottom() {
			mIsExitAnimationInProgress = true;
		}

		@Override
		public void onEntranceAnimationDone() {
			mIsEntranceAnimationFinished = true;
		}

		@Override
		public void onTransparentViewHeightChange(float ratio) {
			if (mIsEntranceAnimationFinished) {
				mWindowScrim.setAlpha((int) (0xFF * ratio));
			}
		}
	};

	@Override
	public void finish() {
		super.finish();

		overridePendingTransition(0, 0);
	}
}


xml布局

<?xml version="1.0" encoding="utf-8"?>
<com.jtv.toolmg.ui.MultiShrinkScroller xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/multiscroller"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/transparent"
    android:descendantFocusability="afterDescendants"
    android:focusable="true"
    android:focusableInTouchMode="true"
    android:orientation="vertical" >

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical" >

        <View
            android:id="@+id/transparent_view"
            android:layout_width="match_parent"
            android:layout_height="@dimen/quickcontact_starting_empty_height" />

        <FrameLayout
            android:id="@+id/toolbar_parent"
            android:layout_width="0dp"
            android:layout_height="match_parent" >
        </FrameLayout>

        <com.jtv.toolmg.ui.TouchlessScrollView
            android:id="@+id/content_scroller"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:background="@color/red"
            android:fillViewport="true" >

            <!-- All the cards should be inserted into this LinearLayout -->

            <LinearLayout
                android:id="@+id/card_container"
                android:layout_width="match_parent"
                android:layout_height="200dp"
                android:background="@color/white"
                android:orientation="vertical"
                android:paddingTop="10dp" >

                <LinearLayout
                    android:id="@+id/ll_num1"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:gravity="center_vertical"
                    android:orientation="horizontal"
                    android:padding="5dp" >

                    <TextView
                        android:id="@+id/tv_title1"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:text="@string/package_number" />

                    <TextView
                        android:id="@+id/tv_reducenum1"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:layout_marginLeft="8dp"
                        android:background="@drawable/selecter_btn_conners"
                        android:gravity="center"
                        android:onClick="onClick"
                        android:padding="5dp"
                        android:text="-" />

                    <EditText
                        android:id="@+id/et_num1"
                        style="@style/base_text_color"
                        android:layout_width="0dp"
                        android:layout_height="wrap_content"
                        android:layout_marginLeft="5dp"
                        android:layout_weight="1"
                        android:background="@drawable/edit_style_line"
                        android:enabled="true"
                        android:focusable="true"
                        android:gravity="center"
                        android:inputType="number" />

                    <TextView
                        android:id="@+id/tv_addnum1"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:layout_marginLeft="5dp"
                        android:background="@drawable/selecter_btn_conners"
                        android:gravity="center"
                        android:onClick="onClick"
                        android:padding="5dp"
                        android:text="+" />
                </LinearLayout>

                <LinearLayout
                    android:id="@+id/ll_num2"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:gravity="center_vertical"
                    android:orientation="horizontal"
                    android:padding="5dp"
                    android:visibility="visible" >

                    <TextView
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:text="未下道旧料" />

                    <TextView
                        android:id="@+id/tv_reducenum2"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:layout_marginLeft="8dp"
                        android:background="@drawable/selecter_btn_conners"
                        android:gravity="center"
                        android:onClick="onClick"
                        android:padding="5dp"
                        android:text="-" />

                    <EditText
                        android:id="@+id/et_num2"
                        style="@style/base_text_color"
                        android:layout_width="0dp"
                        android:layout_height="wrap_content"
                        android:layout_marginLeft="5dp"
                        android:layout_weight="1"
                        android:background="@drawable/edit_style_line"
                        android:enabled="true"
                        android:focusable="true"
                        android:gravity="center"
                        android:inputType="number" />

                    <TextView
                        android:id="@+id/tv_addnum2"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:layout_marginLeft="5dp"
                        android:background="@drawable/selecter_btn_conners"
                        android:gravity="center"
                        android:onClick="onClick"
                        android:padding="5dp"
                        android:text="+" />
                </LinearLayout>

                <LinearLayout
                    android:id="@+id/ll_num3"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:gravity="center_vertical"
                    android:orientation="horizontal"
                    android:padding="5dp"
                    android:visibility="visible" >

                    <TextView
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:text="剩余新料" />

                    <TextView
                        android:id="@+id/tv_reducenum3"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:layout_marginLeft="8dp"
                        android:background="@drawable/selecter_btn_conners"
                        android:gravity="center"
                        android:onClick="onClick"
                        android:padding="5dp"
                        android:text="-" />

                    <EditText
                        android:id="@+id/et_num3"
                        style="@style/base_text_color"
                        android:layout_width="0dp"
                        android:layout_height="wrap_content"
                        android:layout_marginLeft="5dp"
                        android:layout_weight="1"
                        android:background="@drawable/edit_style_line"
                        android:enabled="true"
                        android:focusable="true"
                        android:gravity="center"
                        android:inputType="number" />

                    <TextView
                        android:id="@+id/tv_addnum3"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:layout_marginLeft="5dp"
                        android:background="@drawable/selecter_btn_conners"
                        android:gravity="center"
                        android:onClick="onClick"
                        android:padding="5dp"
                        android:text="+" />
                </LinearLayout>
            </LinearLayout>
        </com.jtv.toolmg.ui.TouchlessScrollView>
    </LinearLayout>

    <TextView
        android:id="@+id/large_title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="top|start"
        android:layout_marginBottom="@dimen/quickcontact_title_initial_margin"
        android:layout_marginEnd="@dimen/quickcontact_title_initial_margin"
        android:layout_marginStart="@dimen/quickcontact_title_initial_margin"
        android:ellipsize="end"
        android:importantForAccessibility="no"
        android:text="测试"
        android:textAlignment="viewStart"
        android:textColor="@color/blue_text" >
    </TextView>

</com.jtv.toolmg.ui.MultiShrinkScroller>


 <dimen name="quickcontact_starting_empty_height">280dp</dimen>
    <dimen name="quickcontact_title_initial_margin">16dp</dimen>
<bool name="quickcontact_two_panel">false</bool>
    <bool name="contact_all_list_show_card_frame">false</bool>

<item name="quickcontact_photo_ratio" type="vals" format="float">0.7</item>



  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Android Scroller 是一个用于实现平滚动效果的工具类。它可以用于在 Android 应用中实现滑动的动画效果,如平滚动到指定位置或者平滚动到顶部。 使用 Android Scroller 需要以下步骤: 1. 创建一个 Scroller 实例:使用 `new Scroller(context)` 创建一个 Scroller 对象。 2. 在 View 的 `computeScroll()` 方法中更新滚动位置:在需要实现滑动效果的 View 类里重写 `computeScroll()` 方法,然后在该方法中调用 `scroller.computeScrollOffset()` 获取当前的滚动位置,并根据需要更新 View 的位置。 3. 处理触摸事件:在触摸事件的回调方法中调用 Scroller 的 `startScroll()` 方法来启动滚动效果。可以根据触摸事件的不同情况调用不同的方法,如 `startScroll(int startX, int startY, int dx, int dy)` 或者 `startScroll(int startX, int startY, int dx, int dy, int duration)` 来指定滚动的起点、偏移量和持续时间。 4. 在 View 的 `invalidate()` 方法中不断重绘:在 `computeScroll()` 方法中更新了 View 的位置后,需要在 View 的 `invalidate()` 方法中调用,以便触发 View 的重新绘制。 需要注意的是,尽管 Android Scroller 提供了平滚动的功能,但它仅仅是一个工具类,实际的滚动效果实现还需要结合其他相关的 API 和组件来完成,如使用 `ViewGroup.LayoutParams` 来设置 View 的位置和大小,或者使用 `ViewPropertyAnimator` 实现更复杂的动画效果。 希望这个回答对你有帮助!如果有更多问题,请继续提问。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值