自定义控件-SlidingMenu_RelativeLayout

public class SlidingMenu extends RelativeLayout {

	private static final String TAG = SlidingMenu.class.getSimpleName();

	public static final int SLIDING_WINDOW = 0;
	public static final int SLIDING_CONTENT = 1;
	private boolean mActionbarOverlay = false;

	/**
	 * Constant value for use with setTouchModeAbove(). Allows the SlidingMenu
	 * to be opened with a swipe gesture on the screen's margin
	 */
	public static final int TOUCHMODE_MARGIN = 0;

	/**
	 * Constant value for use with setTouchModeAbove(). Allows the SlidingMenu
	 * to be opened with a swipe gesture anywhere on the screen
	 */
	public static final int TOUCHMODE_FULLSCREEN = 1;

	/**
	 * Constant value for use with setTouchModeAbove(). Denies the SlidingMenu
	 * to be opened with a swipe gesture
	 */
	public static final int TOUCHMODE_NONE = 2;

	/**
	 * Constant value for use with setMode(). Puts the menu to the left of the
	 * content.
	 */
	public static final int LEFT = 0;

	/**
	 * Constant value for use with setMode(). Puts the menu to the right of the
	 * content.
	 */
	public static final int RIGHT = 1;

	/**
	 * Constant value for use with setMode(). Puts menus to the left and right
	 * of the content.
	 */
	public static final int LEFT_RIGHT = 2;

	private CustomViewAbove mViewAbove;

	private CustomViewBehind mViewBehind;

	private OnOpenListener mOpenListener;

	private OnOpenListener mSecondaryOpenListner;

	private OnCloseListener mCloseListener;

	/**
	 * The listener interface for receiving onOpen events. The class that is
	 * interested in processing a onOpen event implements this interface, and
	 * the object created with that class is registered with a component using
	 * the component's <code>addOnOpenListener<code> method. When the onOpen
	 * event occurs, that object's appropriate method is invoked
	 */
	public interface OnOpenListener {

		/**
		 * On open.
		 */
		public void onOpen();
	}

	/**
	 * The listener interface for receiving onOpened events. The class that is
	 * interested in processing a onOpened event implements this interface, and
	 * the object created with that class is registered with a component using
	 * the component's <code>addOnOpenedListener<code> method. When the onOpened
	 * event occurs, that object's appropriate method is invoked.
	 *
	 * @see OnOpenedEvent
	 */
	public interface OnOpenedListener {

		/**
		 * On opened.
		 */
		public void onOpened();
	}

	/**
	 * The listener interface for receiving onClose events. The class that is
	 * interested in processing a onClose event implements this interface, and
	 * the object created with that class is registered with a component using
	 * the component's <code>addOnCloseListener<code> method. When the onClose
	 * event occurs, that object's appropriate method is invoked.
	 *
	 * @see OnCloseEvent
	 */
	public interface OnCloseListener {

		/**
		 * On close.
		 */
		public void onClose();
	}

	/**
	 * The listener interface for receiving onClosed events. The class that is
	 * interested in processing a onClosed event implements this interface, and
	 * the object created with that class is registered with a component using
	 * the component's <code>addOnClosedListener<code> method. When the onClosed
	 * event occurs, that object's appropriate method is invoked.
	 *
	 * @see OnClosedEvent
	 */
	public interface OnClosedListener {

		/**
		 * On closed.
		 */
		public void onClosed();
	}

	/**
	 * The Interface CanvasTransformer.
	 */
	public interface CanvasTransformer {

		/**
		 * Transform canvas.
		 *
		 * @param canvas
		 *            the canvas
		 * @param percentOpen
		 *            the percent open
		 */
		public void transformCanvas(Canvas canvas, float percentOpen);
	}

	/**
	 * Instantiates a new SlidingMenu.
	 *
	 * @param context
	 *            the associated Context
	 */
	public SlidingMenu(Context context) {
		this(context, null);
	}

	/**
	 * Instantiates a new SlidingMenu and attach to Activity.
	 *
	 * @param activity
	 *            the activity to attach slidingmenu
	 * @param slideStyle
	 *            the slidingmenu style
	 */
	public SlidingMenu(Activity activity, int slideStyle) {
		this(activity, null);
		this.attachToActivity(activity, slideStyle);
	}

	/**
	 * Instantiates a new SlidingMenu.
	 *
	 * @param context
	 *            the associated Context
	 * @param attrs
	 *            the attrs
	 */
	public SlidingMenu(Context context, AttributeSet attrs) {
		this(context, attrs, 0);
	}

	/**
	 * Instantiates a new SlidingMenu.
	 *
	 * @param context
	 *            the associated Context
	 * @param attrs
	 *            the attrs
	 * @param defStyle
	 *            the def style
	 */
	public SlidingMenu(Context context, AttributeSet attrs, int defStyle) {
		super(context, attrs, defStyle);

		LayoutParams behindParams = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
		mViewBehind = new CustomViewBehind(context);
		addView(mViewBehind, behindParams);
		LayoutParams aboveParams = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
		mViewAbove = new CustomViewAbove(context);
		addView(mViewAbove, aboveParams);
		// register the CustomViewBehind with the CustomViewAbove
		mViewAbove.setCustomViewBehind(mViewBehind);
		mViewBehind.setCustomViewAbove(mViewAbove);
		mViewAbove.setOnPageChangeListener(new OnPageChangeListener() {
			public static final int POSITION_OPEN = 0;
			public static final int POSITION_CLOSE = 1;
			public static final int POSITION_SECONDARY_OPEN = 2;

			public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
			}

			public void onPageSelected(int position) {
				if (position == POSITION_OPEN && mOpenListener != null) {
					mOpenListener.onOpen();
				} else if (position == POSITION_CLOSE && mCloseListener != null) {
					mCloseListener.onClose();
				} else if (position == POSITION_SECONDARY_OPEN && mSecondaryOpenListner != null) {
					mSecondaryOpenListner.onOpen();
				}
			}
		});

		// now style everything!
		TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.SlidingMenu);
		// set the above and behind views if defined in xml
		int mode = ta.getInt(R.styleable.SlidingMenu_mode, LEFT);
		setMode(mode);
		int viewAbove = ta.getResourceId(R.styleable.SlidingMenu_viewAbove, -1);
		if (viewAbove != -1) {
			setContent(viewAbove);
		} else {
			setContent(new FrameLayout(context));
		}
		int viewBehind = ta.getResourceId(R.styleable.SlidingMenu_viewBehind, -1);
		if (viewBehind != -1) {
			setMenu(viewBehind);
		} else {
			setMenu(new FrameLayout(context));
		}
		int touchModeAbove = ta.getInt(R.styleable.SlidingMenu_touchModeAbove, TOUCHMODE_MARGIN);
		setTouchModeAbove(touchModeAbove);
		int touchModeBehind = ta.getInt(R.styleable.SlidingMenu_touchModeBehind, TOUCHMODE_MARGIN);
		setTouchModeBehind(touchModeBehind);

		int offsetBehind = (int) ta.getDimension(R.styleable.SlidingMenu_behindOffset, -1);
		int widthBehind = (int) ta.getDimension(R.styleable.SlidingMenu_behindWidth, -1);
		if (offsetBehind != -1 && widthBehind != -1)
			throw new IllegalStateException("Cannot set both behindOffset and behindWidth for a SlidingMenu");
		else if (offsetBehind != -1)
			setBehindOffset(offsetBehind);
		else if (widthBehind != -1)
			setBehindWidth(widthBehind);
		else
			setBehindOffset(0);
		float scrollOffsetBehind = ta.getFloat(R.styleable.SlidingMenu_behindScrollScale, 0.33f);
		setBehindScrollScale(scrollOffsetBehind);
		int shadowRes = ta.getResourceId(R.styleable.SlidingMenu_shadowDrawable, -1);
		if (shadowRes != -1) {
			setShadowDrawable(shadowRes);
		}
		int shadowWidth = (int) ta.getDimension(R.styleable.SlidingMenu_shadowWidth, 0);
		setShadowWidth(shadowWidth);
		boolean fadeEnabled = ta.getBoolean(R.styleable.SlidingMenu_fadeEnabled, true);
		setFadeEnabled(fadeEnabled);
		float fadeDeg = ta.getFloat(R.styleable.SlidingMenu_fadeDegree, 0.33f);
		setFadeDegree(fadeDeg);
		boolean selectorEnabled = ta.getBoolean(R.styleable.SlidingMenu_selectorEnabled, false);
		setSelectorEnabled(selectorEnabled);
		int selectorRes = ta.getResourceId(R.styleable.SlidingMenu_selectorDrawable, -1);
		if (selectorRes != -1)
			setSelectorDrawable(selectorRes);
		ta.recycle();
	}

	/**
	 * Attaches the SlidingMenu to an entire Activity
	 * 
	 * @param activity
	 *            the Activity
	 * @param slideStyle
	 *            either SLIDING_CONTENT or SLIDING_WINDOW
	 */
	public void attachToActivity(Activity activity, int slideStyle) {
		attachToActivity(activity, slideStyle, false);
	}

	/**
	 * Attaches the SlidingMenu to an entire Activity
	 * 
	 * @param activity
	 *            the Activity
	 * @param slideStyle
	 *            either SLIDING_CONTENT or SLIDING_WINDOW
	 * @param actionbarOverlay
	 *            whether or not the ActionBar is overlaid
	 */
	public void attachToActivity(Activity activity, int slideStyle, boolean actionbarOverlay) {
		if (slideStyle != SLIDING_WINDOW && slideStyle != SLIDING_CONTENT)
			throw new IllegalArgumentException("slideStyle must be either SLIDING_WINDOW or SLIDING_CONTENT");

		if (getParent() != null)
			throw new IllegalStateException("This SlidingMenu appears to already be attached");

		// get the window background
		TypedArray a = activity.getTheme().obtainStyledAttributes(new int[] { android.R.attr.windowBackground });
		int background = a.getResourceId(0, 0);
		a.recycle();

		switch (slideStyle) {
		case SLIDING_WINDOW:
			mActionbarOverlay = false;
			ViewGroup decor = (ViewGroup) activity.getWindow().getDecorView();
			ViewGroup decorChild = (ViewGroup) decor.getChildAt(0);
			// save ActionBar themes that have transparent assets
			decorChild.setBackgroundResource(background);
			decor.removeView(decorChild);
			decor.addView(this);
			setContent(decorChild);
			break;
		case SLIDING_CONTENT:
			mActionbarOverlay = actionbarOverlay;
			// take the above view out of
			ViewGroup contentParent = (ViewGroup) activity.findViewById(android.R.id.content);
			View content = contentParent.getChildAt(0);
			contentParent.removeView(content);
			contentParent.addView(this);
			setContent(content);
			// save people from having transparent backgrounds
			if (content.getBackground() == null)
				content.setBackgroundResource(background);
			break;
		}
	}

	/**
	 * Set the above view content from a layout resource. The resource will be
	 * inflated, adding all top-level views to the above view.
	 *
	 * @param res
	 *            the new content
	 */
	public void setContent(int res) {
		setContent(LayoutInflater.from(getContext()).inflate(res, null));
	}

	/**
	 * Set the above view content to the given View.
	 *
	 * @param view
	 *            The desired content to display.
	 */
	public void setContent(View view) {
		mViewAbove.setContent(view);
		showContent();
	}

	/**
	 * Retrieves the current content.
	 * 
	 * @return the current content
	 */
	public View getContent() {
		return mViewAbove.getContent();
	}

	/**
	 * Set the behind view (menu) content from a layout resource. The resource
	 * will be inflated, adding all top-level views to the behind view.
	 *
	 * @param res
	 *            the new content
	 */
	public void setMenu(int res) {
		setMenu(LayoutInflater.from(getContext()).inflate(res, null));
	}

	/**
	 * Set the behind view (menu) content to the given View.
	 *
	 * @param view
	 *            The desired content to display.
	 */
	public void setMenu(View v) {
		mViewBehind.setContent(v);
	}

	/**
	 * Retrieves the main menu.
	 * 
	 * @return the main menu
	 */
	public View getMenu() {
		return mViewBehind.getContent();
	}

	/**
	 * Set the secondary behind view (right menu) content from a layout
	 * resource. The resource will be inflated, adding all top-level views to
	 * the behind view.
	 *
	 * @param res
	 *            the new content
	 */
	public void setSecondaryMenu(int res) {
		setSecondaryMenu(LayoutInflater.from(getContext()).inflate(res, null));
	}

	/**
	 * Set the secondary behind view (right menu) content to the given View.
	 *
	 * @param view
	 *            The desired content to display.
	 */
	public void setSecondaryMenu(View v) {
		mViewBehind.setSecondaryContent(v);
		// mViewBehind.invalidate();
	}

	/**
	 * Retrieves the current secondary menu (right).
	 * 
	 * @return the current menu
	 */
	public View getSecondaryMenu() {
		return mViewBehind.getSecondaryContent();
	}

	/**
	 * Sets the sliding enabled.
	 *
	 * @param b
	 *            true to enable sliding, false to disable it.
	 */
	public void setSlidingEnabled(boolean b) {
		mViewAbove.setSlidingEnabled(b);
	}

	/**
	 * Checks if is sliding enabled.
	 *
	 * @return true, if is sliding enabled
	 */
	public boolean isSlidingEnabled() {
		return mViewAbove.isSlidingEnabled();
	}

	/**
	 * Sets which side the SlidingMenu should appear on.
	 * 
	 * @param mode
	 *            must be either SlidingMenu.LEFT or SlidingMenu.RIGHT
	 */
	public void setMode(int mode) {
		if (mode != LEFT && mode != RIGHT && mode != LEFT_RIGHT) {
			throw new IllegalStateException("SlidingMenu mode must be LEFT, RIGHT, or LEFT_RIGHT");
		}
		mViewBehind.setMode(mode);
	}

	/**
	 * Returns the current side that the SlidingMenu is on.
	 * 
	 * @return the current mode, either SlidingMenu.LEFT or SlidingMenu.RIGHT
	 */
	public int getMode() {
		return mViewBehind.getMode();
	}

	/**
	 * Sets whether or not the SlidingMenu is in static mode (i.e. nothing is
	 * moving and everything is showing)
	 *
	 * @param b
	 *            true to set static mode, false to disable static mode.
	 */
	public void setStatic(boolean b) {
		if (b) {
			setSlidingEnabled(false);
			mViewAbove.setCustomViewBehind(null);
			mViewAbove.setCurrentItem(1);
			// mViewBehind.setCurrentItem(0);
		} else {
			mViewAbove.setCurrentItem(1);
			// mViewBehind.setCurrentItem(1);
			mViewAbove.setCustomViewBehind(mViewBehind);
			setSlidingEnabled(true);
		}
	}

	/**
	 * Opens the menu and shows the menu view.
	 */
	public void showMenu() {
		showMenu(true);
	}

	/**
	 * Opens the menu and shows the menu view.
	 *
	 * @param animate
	 *            true to animate the transition, false to ignore animation
	 */
	public void showMenu(boolean animate) {
		mViewAbove.setCurrentItem(0, animate);
	}

	/**
	 * Opens the menu and shows the secondary menu view. Will default to the
	 * regular menu if there is only one.
	 */
	public void showSecondaryMenu() {
		showSecondaryMenu(true);
	}

	/**
	 * Opens the menu and shows the secondary (right) menu view. Will default to
	 * the regular menu if there is only one.
	 *
	 * @param animate
	 *            true to animate the transition, false to ignore animation
	 */
	public void showSecondaryMenu(boolean animate) {
		mViewAbove.setCurrentItem(2, animate);
	}

	/**
	 * Closes the menu and shows the above view.
	 */
	public void showContent() {
		showContent(true);
	}

	/**
	 * Closes the menu and shows the above view.
	 *
	 * @param animate
	 *            true to animate the transition, false to ignore animation
	 */
	public void showContent(boolean animate) {
		mViewAbove.setCurrentItem(1, animate);
	}

	/**
	 * Toggle the SlidingMenu. If it is open, it will be closed, and vice versa.
	 */
	public void toggle() {
		toggle(true);
	}

	/**
	 * Toggle the SlidingMenu. If it is open, it will be closed, and vice versa.
	 *
	 * @param animate
	 *            true to animate the transition, false to ignore animation
	 */
	public void toggle(boolean animate) {
		if (isMenuShowing()) {
			showContent(animate);
		} else {
			showMenu(animate);
		}
	}

	/**
	 * Checks if is the behind view showing.
	 *
	 * @return Whether or not the behind view is showing
	 */
	public boolean isMenuShowing() {
		return mViewAbove.getCurrentItem() == 0 || mViewAbove.getCurrentItem() == 2;
	}

	/**
	 * Checks if is the behind view showing.
	 *
	 * @return Whether or not the behind view is showing
	 */
	public boolean isSecondaryMenuShowing() {
		return mViewAbove.getCurrentItem() == 2;
	}

	/**
	 * Gets the behind offset.
	 *
	 * @return The margin on the right of the screen that the behind view
	 *         scrolls to
	 */
	public int getBehindOffset() {
		return ((RelativeLayout.LayoutParams) mViewBehind.getLayoutParams()).rightMargin;
	}

	/**
	 * Sets the behind offset.
	 *
	 * @param i
	 *            The margin, in pixels, on the right of the screen that the
	 *            behind view scrolls to.
	 */
	public void setBehindOffset(int i) {
		// RelativeLayout.LayoutParams params =
		// ((RelativeLayout.LayoutParams)mViewBehind.getLayoutParams());
		// int bottom = params.bottomMargin;
		// int top = params.topMargin;
		// int left = params.leftMargin;
		// params.setMargins(left, top, i, bottom);
		mViewBehind.setWidthOffset(i);
	}

	/**
	 * Sets the behind offset.
	 *
	 * @param resID
	 *            The dimension resource id to be set as the behind offset. The
	 *            menu, when open, will leave this width margin on the right of
	 *            the screen.
	 */
	public void setBehindOffsetRes(int resID) {
		int i = (int) getContext().getResources().getDimension(resID);
		setBehindOffset(i);
	}

	/**
	 * Sets the above offset.
	 *
	 * @param i
	 *            the new above offset, in pixels
	 */
	public void setAboveOffset(int i) {
		mViewAbove.setAboveOffset(i);
	}

	/**
	 * Sets the above offset.
	 *
	 * @param resID
	 *            The dimension resource id to be set as the above offset.
	 */
	public void setAboveOffsetRes(int resID) {
		int i = (int) getContext().getResources().getDimension(resID);
		setAboveOffset(i);
	}

	/**
	 * Sets the behind width.
	 *
	 * @param i
	 *            The width the Sliding Menu will open to, in pixels
	 */
	@SuppressWarnings("deprecation")
	public void setBehindWidth(int i) {
		int width;
		Display display = ((WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();
		try {
			Class<?> cls = Display.class;
			Class<?>[] parameterTypes = { Point.class };
			Point parameter = new Point();
			Method method = cls.getMethod("getSize", parameterTypes);
			method.invoke(display, parameter);
			width = parameter.x;
		} catch (Exception e) {
			width = display.getWidth();
		}
		setBehindOffset(width - i);
	}

	/**
	 * Sets the behind width.
	 *
	 * @param res
	 *            The dimension resource id to be set as the behind width
	 *            offset. The menu, when open, will open this wide.
	 */
	public void setBehindWidthRes(int res) {
		int i = (int) getContext().getResources().getDimension(res);
		setBehindWidth(i);
	}

	/**
	 * Gets the behind scroll scale.
	 *
	 * @return The scale of the parallax scroll
	 */
	public float getBehindScrollScale() {
		return mViewBehind.getScrollScale();
	}

	/**
	 * Gets the touch mode margin threshold
	 * 
	 * @return the touch mode margin threshold
	 */
	public int getTouchmodeMarginThreshold() {
		return mViewBehind.getMarginThreshold();
	}

	/**
	 * Set the touch mode margin threshold
	 * 
	 * @param touchmodeMarginThreshold
	 */
	public void setTouchmodeMarginThreshold(int touchmodeMarginThreshold) {
		mViewBehind.setMarginThreshold(touchmodeMarginThreshold);
	}

	/**
	 * Sets the behind scroll scale.
	 *
	 * @param f
	 *            The scale of the parallax scroll (i.e. 1.0f scrolls 1 pixel
	 *            for every 1 pixel that the above view scrolls and 0.0f scrolls
	 *            0 pixels)
	 */
	public void setBehindScrollScale(float f) {
		if (f < 0 && f > 1)
			throw new IllegalStateException("ScrollScale must be between 0 and 1");
		mViewBehind.setScrollScale(f);
	}

	/**
	 * Sets the behind canvas transformer.
	 *
	 * @param t
	 *            the new behind canvas transformer
	 */
	public void setBehindCanvasTransformer(CanvasTransformer t) {
		mViewBehind.setCanvasTransformer(t);
	}

	/**
	 * Gets the touch mode above.
	 *
	 * @return the touch mode above
	 */
	public int getTouchModeAbove() {
		return mViewAbove.getTouchMode();
	}

	/**
	 * Controls whether the SlidingMenu can be opened with a swipe gesture.
	 * Options are {@link #TOUCHMODE_MARGIN TOUCHMODE_MARGIN},
	 * {@link #TOUCHMODE_FULLSCREEN TOUCHMODE_FULLSCREEN}, or
	 * {@link #TOUCHMODE_NONE TOUCHMODE_NONE}
	 *
	 * @param i
	 *            the new touch mode
	 */
	public void setTouchModeAbove(int i) {
		if (i != TOUCHMODE_FULLSCREEN && i != TOUCHMODE_MARGIN && i != TOUCHMODE_NONE) {
			throw new IllegalStateException(
					"TouchMode must be set to either" + "TOUCHMODE_FULLSCREEN or TOUCHMODE_MARGIN or TOUCHMODE_NONE.");
		}
		mViewAbove.setTouchMode(i);
	}

	/**
	 * Controls whether the SlidingMenu can be opened with a swipe gesture.
	 * Options are {@link #TOUCHMODE_MARGIN TOUCHMODE_MARGIN},
	 * {@link #TOUCHMODE_FULLSCREEN TOUCHMODE_FULLSCREEN}, or
	 * {@link #TOUCHMODE_NONE TOUCHMODE_NONE}
	 *
	 * @param i
	 *            the new touch mode
	 */
	public void setTouchModeBehind(int i) {
		if (i != TOUCHMODE_FULLSCREEN && i != TOUCHMODE_MARGIN && i != TOUCHMODE_NONE) {
			throw new IllegalStateException(
					"TouchMode must be set to either" + "TOUCHMODE_FULLSCREEN or TOUCHMODE_MARGIN or TOUCHMODE_NONE.");
		}
		mViewBehind.setTouchMode(i);
	}

	/**
	 * Sets the shadow drawable.
	 *
	 * @param resId
	 *            the resource ID of the new shadow drawable
	 */
	public void setShadowDrawable(int resId) {
		setShadowDrawable(getContext().getResources().getDrawable(resId));
	}

	/**
	 * Sets the shadow drawable.
	 *
	 * @param d
	 *            the new shadow drawable
	 */
	public void setShadowDrawable(Drawable d) {
		mViewBehind.setShadowDrawable(d);
	}

	/**
	 * Sets the secondary (right) shadow drawable.
	 *
	 * @param resId
	 *            the resource ID of the new shadow drawable
	 */
	public void setSecondaryShadowDrawable(int resId) {
		setSecondaryShadowDrawable(getContext().getResources().getDrawable(resId));
	}

	/**
	 * Sets the secondary (right) shadow drawable.
	 *
	 * @param d
	 *            the new shadow drawable
	 */
	public void setSecondaryShadowDrawable(Drawable d) {
		mViewBehind.setSecondaryShadowDrawable(d);
	}

	/**
	 * Sets the shadow width.
	 *
	 * @param resId
	 *            The dimension resource id to be set as the shadow width.
	 */
	public void setShadowWidthRes(int resId) {
		setShadowWidth((int) getResources().getDimension(resId));
	}

	/**
	 * Sets the shadow width.
	 *
	 * @param pixels
	 *            the new shadow width, in pixels
	 */
	public void setShadowWidth(int pixels) {
		mViewBehind.setShadowWidth(pixels);
	}

	/**
	 * Enables or disables the SlidingMenu's fade in and out
	 *
	 * @param b
	 *            true to enable fade, false to disable it
	 */
	public void setFadeEnabled(boolean b) {
		mViewBehind.setFadeEnabled(b);
	}

	/**
	 * Sets how much the SlidingMenu fades in and out. Fade must be enabled, see
	 * {@link #setFadeEnabled(boolean) setFadeEnabled(boolean)}
	 *
	 * @param f
	 *            the new fade degree, between 0.0f and 1.0f
	 */
	public void setFadeDegree(float f) {
		mViewBehind.setFadeDegree(f);
	}

	/**
	 * Enables or disables whether the selector is drawn
	 *
	 * @param b
	 *            true to draw the selector, false to not draw the selector
	 */
	public void setSelectorEnabled(boolean b) {
		mViewBehind.setSelectorEnabled(true);
	}

	/**
	 * Sets the selected view. The selector will be drawn here
	 *
	 * @param v
	 *            the new selected view
	 */
	public void setSelectedView(View v) {
		mViewBehind.setSelectedView(v);
	}

	/**
	 * Sets the selector drawable.
	 *
	 * @param res
	 *            a resource ID for the selector drawable
	 */
	public void setSelectorDrawable(int res) {
		mViewBehind.setSelectorBitmap(BitmapFactory.decodeResource(getResources(), res));
	}

	/**
	 * Sets the selector drawable.
	 *
	 * @param b
	 *            the new selector bitmap
	 */
	public void setSelectorBitmap(Bitmap b) {
		mViewBehind.setSelectorBitmap(b);
	}

	/**
	 * Add a View ignored by the Touch Down event when mode is Fullscreen
	 *
	 * @param v
	 *            a view to be ignored
	 */
	public void addIgnoredView(View v) {
		mViewAbove.addIgnoredView(v);
	}

	/**
	 * Remove a View ignored by the Touch Down event when mode is Fullscreen
	 *
	 * @param v
	 *            a view not wanted to be ignored anymore
	 */
	public void removeIgnoredView(View v) {
		mViewAbove.removeIgnoredView(v);
	}

	/**
	 * Clear the list of Views ignored by the Touch Down event when mode is
	 * Fullscreen
	 */
	public void clearIgnoredViews() {
		mViewAbove.clearIgnoredViews();
	}

	/**
	 * Sets the OnOpenListener. {@link OnOpenListener#onOpen()
	 * OnOpenListener.onOpen()} will be called when the SlidingMenu is opened
	 *
	 * @param listener
	 *            the new OnOpenListener
	 */
	public void setOnOpenListener(OnOpenListener listener) {
		// mViewAbove.setOnOpenListener(listener);
		mOpenListener = listener;
	}

	/**
	 * Sets the OnOpenListner for secondary menu {@link OnOpenListener#onOpen()
	 * OnOpenListener.onOpen()} will be called when the secondary SlidingMenu is
	 * opened
	 * 
	 * @param listener
	 *            the new OnOpenListener
	 */

	public void setSecondaryOnOpenListner(OnOpenListener listener) {
		mSecondaryOpenListner = listener;
	}

	/**
	 * Sets the OnCloseListener. {@link OnCloseListener#onClose()
	 * OnCloseListener.onClose()} will be called when any one of the SlidingMenu
	 * is closed
	 *
	 * @param listener
	 *            the new setOnCloseListener
	 */
	public void setOnCloseListener(OnCloseListener listener) {
		// mViewAbove.setOnCloseListener(listener);
		mCloseListener = listener;
	}

	/**
	 * Sets the OnOpenedListener. {@link OnOpenedListener#onOpened()
	 * OnOpenedListener.onOpened()} will be called after the SlidingMenu is
	 * opened
	 *
	 * @param listener
	 *            the new OnOpenedListener
	 */
	public void setOnOpenedListener(OnOpenedListener listener) {
		mViewAbove.setOnOpenedListener(listener);
	}

	/**
	 * Sets the OnClosedListener. {@link OnClosedListener#onClosed()
	 * OnClosedListener.onClosed()} will be called after the SlidingMenu is
	 * closed
	 *
	 * @param listener
	 *            the new OnClosedListener
	 */
	public void setOnClosedListener(OnClosedListener listener) {
		mViewAbove.setOnClosedListener(listener);
	}

	public static class SavedState extends BaseSavedState {

		private final int mItem;

		public SavedState(Parcelable superState, int item) {
			super(superState);
			mItem = item;
		}

		private SavedState(Parcel in) {
			super(in);
			mItem = in.readInt();
		}

		public int getItem() {
			return mItem;
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see android.view.AbsSavedState#writeToParcel(android.os.Parcel, int)
		 */
		public void writeToParcel(Parcel out, int flags) {
			super.writeToParcel(out, flags);
			out.writeInt(mItem);
		}

		public static final Parcelable.Creator<SavedState> CREATOR = new Parcelable.Creator<SavedState>() {
			public SavedState createFromParcel(Parcel in) {
				return new SavedState(in);
			}

			public SavedState[] newArray(int size) {
				return new SavedState[size];
			}
		};

	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see android.view.View#onSaveInstanceState()
	 */
	@Override
	protected Parcelable onSaveInstanceState() {
		Parcelable superState = super.onSaveInstanceState();
		SavedState ss = new SavedState(superState, mViewAbove.getCurrentItem());
		return ss;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see android.view.View#onRestoreInstanceState(android.os.Parcelable)
	 */
	@Override
	protected void onRestoreInstanceState(Parcelable state) {
		SavedState ss = (SavedState) state;
		super.onRestoreInstanceState(ss.getSuperState());
		mViewAbove.setCurrentItem(ss.getItem());
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see android.view.ViewGroup#fitSystemWindows(android.graphics.Rect)
	 */
	@SuppressLint("NewApi")
	@Override
	protected boolean fitSystemWindows(Rect insets) {
		int leftPadding = insets.left;
		int rightPadding = insets.right;
		int topPadding = insets.top;
		int bottomPadding = insets.bottom;
		if (!mActionbarOverlay) {
			Log.v(TAG, "setting padding!");
			setPadding(leftPadding, topPadding, rightPadding, bottomPadding);
		}
		return true;
	}

	@TargetApi(Build.VERSION_CODES.HONEYCOMB)
	public void manageLayers(float percentOpen) {
		if (Build.VERSION.SDK_INT < 11)
			return;

		boolean layer = percentOpen > 0.0f && percentOpen < 1.0f;
		final int layerType = layer ? View.LAYER_TYPE_HARDWARE : View.LAYER_TYPE_NONE;

		if (layerType != getContent().getLayerType()) {
			getHandler().post(new Runnable() {
				public void run() {
					Log.v(TAG, "changing layerType. hardware? " + (layerType == View.LAYER_TYPE_HARDWARE));
					getContent().setLayerType(layerType, null);
					getMenu().setLayerType(layerType, null);
					if (getSecondaryMenu() != null) {
						getSecondaryMenu().setLayerType(layerType, null);
					}
				}
			});
		}
	}



<Scroll效果研究-系统ScrollView源码分析>
http://www.eoeandroid.com/thread-553375-1-1.html

自定义控件教程第一篇
http://www.eoeandroid.com/thread-548644-1-1.html


SlidingMenu设计思路三个主要的ViewGroup, 主ViewGroup里面包含两个重叠的ViewGroup,盖在上面的就是显示内容,而下面的就是菜单
上面的侧滑以后露出后面的View~
主ViewGroup作为一个自定义控件,里面的内容和菜单利用自定义属性设置

SlidingMenu(继承RelativeLayout),就是这个主ViewGroup,
其中又包含两个,内容CustomViewAbove和菜单CustomViewBehind(都继承ViewGroup),
SlidingMenu提供两个自定义属性,供使用者传入两个布局id,对应主体内容和菜单布局,
此外还有其他一些属性供开发者设置菜单效果

下面是代码分析
----------------------------------------------------------------------------------------

构造函数中主要代码如下:

  1. LayoutParams behindParams = new LayoutParams(LayoutParams. MATCH_PARENT, LayoutParams.MATCH_PARENT);
  2. mViewBehind = new CustomViewBehind(context);
  3. addView(mViewBehind , behindParams);
  4. LayoutParams aboveParams = new LayoutParams(LayoutParams. MATCH_PARENT, LayoutParams.MATCH_PARENT);
  5. mViewAbove = new CustomViewAbove(context);
  6. addView(mViewAbove , aboveParams);
  7. // register the CustomViewBehind with the CustomViewAbove
  8. mViewAbove.setCustomViewBehind(mViewBehind );
  9. mViewBehind.setCustomViewAbove(mViewAbove );
  10. mViewAbove.setOnPageChangeListener(new OnPageChangeListener() {
  11.       public static final int POSITION_OPEN = 0;
  12.       public static final int POSITION_CLOSE = 1;
  13.       public static final int POSITION_SECONDARY_OPEN = 2;

  14.       public void onPageScrolled( int position, float positionOffset,
  15.                    int positionOffsetPixels) { }

  16.       public void onPageSelected( int position) {
  17.              if (position == POSITION_OPEN && mOpenListener != null) {
  18.                    mOpenListener.onOpen();
  19.             } else if (position == POSITION_CLOSE && mCloseListener != null) {
  20.                    mCloseListener.onClose();
  21.             } else if (position == POSITION_SECONDARY_OPEN && mSecondaryOpenListner != null ) {
  22.                    mSecondaryOpenListner.onOpen();
  23.             }
  24.       }
  25. });

  26. // now style everything!
  27. TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.SlidingMenu);
  28. // set the above and behind views if defined in xml
  29. int mode = ta.getInt(R.styleable.SlidingMenu_mode, LEFT);
  30. setMode(mode);
  31. int viewAbove = ta.getResourceId(R.styleable. SlidingMenu_viewAbove, -1);
  32. if (viewAbove != -1) {
  33.       setContent(viewAbove );
  34. } else {
  35.       setContent( new FrameLayout(context));
  36. }
  37. int viewBehind = ta.getResourceId(R.styleable.SlidingMenu_viewBehind, -1);
  38. if (viewBehind != -1) {
  39.       setMenu(viewBehind);
  40. } else {
  41.       setMenu(new FrameLayout(context));
  42. }
复制代码

新建两个自定义控件CustomViewAbove/Behind将其addView到SlidingMenu中,
然后在通过属性获取到view的id后会在setContent/Menu方法中设置对应布局
下面是content处理相关代码,menu同理
  1. /**
  2. * Set the above view content to the given View.
  3. *
  4. * @param view The desired content to display.
  5. */
  6. public void setContent(View view) {
  7.       mViewAbove.setContent(view);
  8.       showContent();
  9. }
复制代码
mViewAbove的setContent方法代码为
  1. /**
  2. * Set the behind view (menu) content to the given View.
  3. *
  4. * @param view The desired content to display.
  5. */
  6. public void setMenu(View v) {
  7.       mViewBehind.setContent(v);
  8. }
复制代码

下面分别介绍Above和Behind两部分

-----------------------------------------------------------------------------------------

CustViewAbove类,包含主要内容的类,即显示在SlidingMenu上面/前端的部分
由于SlidingMenu实现效果重点在于滑动,滑动前端显示的内容然后展现出下面的菜单部分,
所以重中之重自然就是这个CustViewAbove类里的onTouchEvent里的处理了

以下是核心方法ouTouchEvent中的分析,请先看完教程  Scroll效果研究-系统ScrollView源码分析



SlidingMenu的CustomViewAbove和ScrollView处理没什么区别,看注释就知道很多都是直接拷贝过去的

同理分按下,滑动,抬起几部分看
1.按下
  1. case MotionEvent.ACTION_DOWN :
  2.       /*
  3.        * If being flinged and user touches, stop the fling. isFinished
  4.        * will be false if being flinged.
  5.        */
  6.       completeScroll();

  7.       // Remember where the motion event started
  8.       int index = MotionEventCompat. getActionIndex(ev);
  9.       mActivePointerId = MotionEventCompat. getPointerId(ev, index);
  10.       mLastMotionX = mInitialMotionX = ev.getX();
复制代码
如果还在滚动则停止~
记住开始在哪里点下的,这里只记录了x轴坐标,因为侧滑只关注横线移动~
mActivityPointerId是记录多点触碰的activity第一个点的id,总之是用来处理多点除控时拖动的稳定性


2.滑动
  1. case MotionEvent.ACTION_MOVE :
  2.       if (!mIsBeingDragged) { 
  3.             determineDrag(ev);
  4.              if ( mIsUnableToDrag)
  5.                    return false;
  6.       }
  7.       if (mIsBeingDragged) {
  8.              // Scroll to follow the motion event
  9.              final int activePointerIndex = getPointerIndex(ev, mActivePointerId);
  10.              if ( mActivePointerId == INVALID_POINTER)
  11.                    break;
  12.              final float x = MotionEventCompat. getX(ev, activePointerIndex);
  13.              final float deltaX = mLastMotionX - x;
  14.              mLastMotionX = x;
  15.              float oldScrollX = getScrollX();
  16.              float scrollX = oldScrollX + deltaX;
  17.              final float leftBound = getLeftBound();
  18.              final float rightBound = getRightBound();
  19.              if (scrollX < leftBound) {
  20.                   scrollX = leftBound;
  21.             } else if (scrollX > rightBound) {
  22.                   scrollX = rightBound;
  23.             }
  24.              // Don't lose the rounded component
  25.              mLastMotionX += scrollX - ( int) scrollX;
  26.             scrollTo(( int) scrollX, getScrollY());
  27.             pageScrolled(( int) scrollX);
  28.       }
  29.       break;
复制代码

挑重点介绍了
首先是determineDrag方法,即确定当前动作是否算是一个我们需要的拖动事件,是否要消费处理之~
方法如下
  1. private void determineDrag(MotionEvent ev) {
  2.       final int activePointerId = mActivePointerId;
  3.       final int pointerIndex = getPointerIndex(ev, activePointerId);
  4.       if (activePointerId == INVALID_POINTER || pointerIndex == INVALID_POINTER)
  5.              return;
  6.       final float x = MotionEventCompat. getX(ev, pointerIndex);
  7.       final float dx = x - mLastMotionX;
  8.       final float xDiff = Math.abs(dx);
  9.       final float y = MotionEventCompat. getY(ev, pointerIndex);
  10.       final float dy = y - mLastMotionY;
  11.       final float yDiff = Math.abs(dy);
  12.       if (xDiff > (isMenuOpen()? mTouchSlop/2: mTouchSlop) && xDiff > yDiff && thisSlideAllowed(dx)) {          
  13.             startDrag();
  14.              mLastMotionX = x;
  15.              mLastMotionY = y;
  16.             setScrollingCacheEnabled( true);
  17.              // TODO add back in touch slop check
  18.       } else if (xDiff > mTouchSlop) {
  19.              mIsUnableToDrag = true;
  20.       }
  21. }
复制代码

最主要是地方在于if 对diff的判断语句
其中x/yDiff是x和y轴的移动距离长度,
mTouchSlop是系统对于拖动的最低长度判断,即至少移动多少多少距离,才算是一个拖动的动作~

所以判断的条件就是:
当菜单没打开时,xDiff大于mTouchSlop时才视为一个我们所需要的拖动,打开时则只要一半即可,
并且要同时满足xDiff>yDiff,即拖动是一个横向大于45度的
thisSlideAllowed可以理解为是判断是否是在菜单打开时touch到了菜单部分的位置

总结起来就是
如果拖动距离达到最小阀值且大于横向45度角度,且是touch到主体部分,则为我们需要的触摸事件

然后记录x,y的赋值给mLastMotionX/Y


当determineDrag判断这是一个我们需要的拖动事件了以后,就开始让aboveView随着动作滚动了
首先是最后一行pageScrolled(( int) scrollX);设置监听数据的,先无视
最重要是scrollTo方法,在系统的scrollTo基础上又加了点其他处理,如下
  1. @Override
  2. public void scrollTo(int x, int y) {
  3.       super.scrollTo(x, y);
  4.       mScrollX = x;
  5.       mViewBehind.scrollBehindTo( mContent, x, y);     
  6.       ((SlidingMenu)getParent()).manageLayers(getPercentOpen());
  7. }
复制代码
下面代码中manageLayers是提高性能用的,不深入研究了
super.scrollTo就是让aboveView随着手势拖动滚动,这个比较简单

此外,
使用SlidingMenu的时候可以注意到,滚动上面主体内容展示后面菜单时,菜单也有一个滚动展开效果
这个mViewBehind.scrollBehindTo( mContent, x, y);就是处理这个的
这里先知道作用,分析完above部分后再详细分析behind部分,会介绍这个作用


然后是抬起方法,对于SlidingMenu,如果使用过肯定会有一定了解,以下是一些效果需求
如果菜单打开到一定位置,则抬起时菜单会完全打开
如果菜单打开部分太小,则抬起时菜单会收回去
还要有甩的处理,随着手势甩开菜单,甩关闭菜单~

下面是代码部分
  1. case MotionEvent.ACTION_UP :
  2.       if (mIsBeingDragged) {
  3.              final VelocityTracker velocityTracker = mVelocityTracker;
  4.             velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
  5.              int initialVelocity = ( int) VelocityTrackerCompat.getXVelocity(
  6.                         velocityTracker, mActivePointerId);
  7.              final int scrollX = getScrollX();
  8.              final float pageOffset = ( float) (scrollX - getDestScrollX(mCurItem)) / getBehindWidth();
  9.              final int activePointerIndex = getPointerIndex(ev, mActivePointerId);
  10.              if ( mActivePointerId != INVALID_POINTER) {
  11.                    final float x = MotionEventCompat. getX(ev, activePointerIndex);
  12.                    final int totalDelta = ( int) (x - mInitialMotionX);
  13.                    int nextPage = determineTargetPage(pageOffset, initialVelocity, totalDelta);
  14.                   setCurrentItemInternal(nextPage, true, true, initialVelocity);
  15.             } else {    
  16.                   setCurrentItemInternal( mCurItem, true, true, initialVelocity);
  17.             }
  18.              mActivePointerId = INVALID_POINTER;
  19.             endDrag();
  20.       } else if (mQuickReturn && mViewBehind.menuTouchInQuickReturn( mContent, mCurItem, ev.getX() + mScrollX)) {
  21.              // close the menu
  22.             setCurrentItem(1);
  23.             endDrag();
  24.       }
  25.       break;
复制代码
同样,简单的不介绍了,参考之前的ScrollView源码分析教程
直接定位到SlidingMenu中特殊处理的部分

pageOffset为菜单当前打开比例,是一个0~1的值,比如打开一半了比例就是0.5,
该值可以是正可以是负,用于表示方向,向右为正向左为负~
计算方法为
  1. final float pageOffset = ( float) (scrollX - getDestScrollX(mCurItem )) / getBehindWidth();
复制代码
mCurItem为当前页索引,可能是0,1,2
0代表左边的菜单打开状态,1是没有菜单打开,2是右边菜单打开

计算出页pageOffset后再根据速度和移动距离最终算出目标页位置,方法如下
  1. private int determineTargetPage (float pageOffset, int velocity, int deltaX) {
  2.       int targetPage = mCurItem;
  3.       if (Math.abs(deltaX) > mFlingDistance && Math.abs(velocity) > mMinimumVelocity) {
  4.              if (velocity > 0 && deltaX > 0) {
  5.                   targetPage -= 1;
  6.             } else if (velocity < 0 && deltaX < 0){
  7.                   targetPage += 1;
  8.             }
  9.       } else {
  10.             targetPage = ( int) Math. round(mCurItem + pageOffset);
  11.       }
  12.       return targetPage;
  13. }
复制代码
速度和距离达到最小值后,则根据速度和距离的正负控制当前页索引加或减1,
当达不到if条件时,则计算当前页索引mCurItem与菜单打开状态比例值pageOffset和的四舍五入值~


举个else处理的例子
比如当前页为1即未打开菜单状态,此时左移了菜单宽度十分之三的距离,即pageOffset=-0.3
那结果就是 1 - 0.3 = 0.7 取四舍五入就是1~ 当前页还是1,即速度不够时移动三分之一抬起菜单还会收回去
还是刚才的条件,左移换成了十分之八的距离,即pageOffset=-0.8,
结果就是1 - 0.8 = 0.2 四舍五入就是0~ 即当前页应该是0左菜单了


以上简单总结就是,速度距离足够后,根据速度控制左右滑动来显示菜单或者内容
如果速度不够,则移动超过菜单一半距离时就切换菜单打开关闭状态,不然就收回去


注意,determineTargetPage方法只是获取目标页位置,实际跳转工作是下面的方法
  1. void setCurrentItemInternal(int item, boolean smoothScroll, boolean always, int velocity) {
  2.       if (!always && mCurItem == item) {
  3.             setScrollingCacheEnabled( false);
  4.              return;
  5.       }

  6.       item = mViewBehind.getMenuPage(item);

  7.       final boolean dispatchSelected = mCurItem != item;
  8.       mCurItem = item;
  9.       final int destX = getDestScrollX(mCurItem );
  10.       if (dispatchSelected && mOnPageChangeListener != null) {
  11.              mOnPageChangeListener.onPageSelected(item);
  12.       }
  13.       if (dispatchSelected && mInternalPageChangeListener != null ) {
  14.              mInternalPageChangeListener.onPageSelected(item);
  15.       }
  16.       if (smoothScroll) {
  17.             smoothScrollTo(destX, 0, velocity);
  18.       } else {
  19.             completeScroll();
  20.             scrollTo(destX, 0);
  21.       }
  22. }
复制代码
监听设置无视,核心方法是smoothScrollTo方法,如下
  1. /**
  2. * Like {@link View#scrollBy}, but scroll smoothly instead of immediately.
  3. *
  4. * @param x the number of pixels to scroll by on the X axis
  5. * @param y the number of pixels to scroll by on the Y axis
  6. * @param velocity the velocity associated with a fling, if applicable. (0 otherwise)
  7. */
  8. void smoothScrollTo(int x, int y, int velocity) {
  9.       if (getChildCount() == 0) {
  10.              // Nothing to do.
  11.             setScrollingCacheEnabled( false);
  12.              return;
  13.       }
  14.       int sx = getScrollX();
  15.       int sy = getScrollY();
  16.       int dx = x - sx;
  17.       int dy = y - sy;
  18.       if (dx == 0 && dy == 0) {
  19.             completeScroll();
  20.              if (isMenuOpen()) {
  21.                    if ( mOpenedListener != null)
  22.                          mOpenedListener.onOpened();
  23.             } else {
  24.                    if ( mClosedListener != null)
  25.                          mClosedListener.onClosed();
  26.             }
  27.              return;
  28.       }

  29.       setScrollingCacheEnabled( true);
  30.       mScrolling = true;

  31.       final int width = getBehindWidth();
  32.       final int halfWidth = width / 2;
  33.       final float distanceRatio = Math. min(1f, 1.0f * Math.abs(dx) / width);
  34.       final float distance = halfWidth + halfWidth *
  35.                   distanceInfluenceForSnapDuration(distanceRatio);

  36.       int duration = 0;
  37.       velocity = Math.abs(velocity);
  38.       if (velocity > 0) {
  39.             duration = 4 * Math. round(1000 * Math.abs (distance / velocity));
  40.       } else {
  41.              final float pageDelta = ( float) Math. abs(dx) / width;
  42.             duration = ( int) ((pageDelta + 1) * 100);
  43.             duration = MAX_SETTLE_DURATION;
  44.       }
  45.       duration = Math.min(duration, MAX_SETTLE_DURATION);

  46.       mScroller.startScroll(sx, sy, dx, dy, duration);
  47.       invalidate();
  48. }
复制代码
最后发现内部实现最终还是Scroller类的startScroll方法~让它继续完成打开/关闭菜单的剩下动画
至于duration滑动持续时间的计算算法就不研究了~Scroller用法也不介绍了,之前ScrollView教程里有

------------------------------------------------------------------------------------------------------

以上部分其实看懂ScrollView原理以后看完全没压力理解,但还有一个没介绍的知识点,touch事件分发问题~

问题场景:
在菜单打开的时候要进行判断,
如果点击在主体部分,则自动收回菜单,即让主体部分消费这个touch事件,
如果此时点击在菜单部分,那above中应该不消费此事件,要将其传给behind部分,
然后让behind中的listview或者button等自定义设置的控件去获取处理touch事件~


事件分发可以参考
http://www.eoeandroid.com/thread-277371-1-1.html

此外还有菜单状态打开时点主体部分则直接关闭菜单,或者back键直接关闭菜单等,原理上面都介绍了,
这些逻辑方面的处理还有其他一些优化部分就不介绍了~

------------------------------------------------------------------------------------------------------

菜单部分CustomViewBehind中内容不多,主要有两大块内容

1.滚动
研究主体内容部分的时候已经提到过,当前端内容部分拖动显示/关闭菜单时,菜单也有一个随之滚动的效果~

2.效果绘制
包括阴影,淡入淡出效果等


绘制部分比较复杂需要以后有时间专门介绍,先介绍滚动相关的部分即1
相关方法如下,该方法在CustomViewAbove的scrollTo中调用
  1. public void scrollBehindTo(View content, int x, int y) {
  2.       int vis = View. VISIBLE;
  3.       if (mMode == SlidingMenu. LEFT) {
  4.              if (x >= content.getLeft()) vis = View. INVISIBLE;
  5.             scrollTo(( int)((x + getBehindWidth())* mScrollScale), y);
  6.       } else if (mMode == SlidingMenu. RIGHT) {
  7.              if (x <= content.getLeft()) vis = View. INVISIBLE;
  8.             scrollTo(( int)(getBehindWidth() - getWidth() +
  9.                         (x-getBehindWidth())* mScrollScale), y);
  10.       } else if (mMode == SlidingMenu. LEFT_RIGHT) {
  11.              mContent.setVisibility(x >= content.getLeft() ? View.INVISIBLE : View.VISIBLE );
  12.              mSecondaryContent.setVisibility(x <= content.getLeft() ? View.INVISIBLE : View.VISIBLE );
  13.             vis = x == 0 ? View. INVISIBLE : View. VISIBLE;
  14.              if (x <= content.getLeft()) {
  15.                   scrollTo(( int)((x + getBehindWidth())* mScrollScale), y);                     
  16.             } else {
  17.                   scrollTo(( int)(getBehindWidth() - getWidth() +
  18.                               (x-getBehindWidth())* mScrollScale), y);                        
  19.             }
  20.       }
  21.       if (vis == View. INVISIBLE)
  22.             Log. v(TAG, "behind INVISIBLE" );
  23.       setVisibility(vis);
  24. }
复制代码

只分析LEFT的情况(请他情况同理)
比如菜单的width即getBehindWidth的宽度为300
那从菜单关闭状态一直滚到菜单完全打开状态,above主体内容x轴上的scroll变化就是0 ~ -300
此时如果mScrollScale即滚动比例为0.3
那按照上面scrollBehindTo中的算法, behind菜单部分x轴上的scroll变化就是
(0+300)*0.3 ~ (-300+300)*0.3即100~0

如果比例换成0.6,那behind的x变化就是180~0


极端情况下
1.mScrollScale=0, behind的x变化为 0~0, 此时会发现behind菜单部分在打开关闭的过程中无任何滚动
2.mScrollScale=1,behind的换标为300~0, 此时的效果就是主体和菜单紧挨着以同一个速度滚动


注意,这里的x为滚动的偏移量,即scrollTo使用的参数,
比如scrollView中,scrollTo(0, 100)会往下滚动到y偏移量100的位置,
此时scrollView里面的内容视觉上看其实是向上移了~
这里同理
虽然朝右拖动的时候view看上去是朝右运动,但x轴上滚动偏移量是减少的

------------------------------------------------------------------------------------------------------

最后是demo,自定义底部滑出菜单控件
根据SlidingMenu修改的,删除了SlidingMenu中大量代码,只保留了最核心的部分方便理解~

缺陷
限定死了behind菜单部分的高度和底部运动的比例值等在SlidingMenu中是可以设定的信息



老样子,回复可以免积分
链接:http://pan.baidu.com/s/1bnCPQiF 密码:pa0v


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值