AndroidSideMenu能够让你轻而易举地创建侧滑菜单。需要注意的是,该项目自身并不提供任何创建菜单的工具,因此,开发者可以自由创建内部菜单。
核心类如下:
/* * Copyright dmitry.zaicew@gmail.com Dmitry Zaitsev * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.agimind.widget; import java.util.LinkedList; import java.util.Queue; import android.content.Context; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.PorterDuff.Mode; import android.graphics.Rect; import android.graphics.Region.Op; import android.os.Build; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.View; import android.view.animation.Animation; import android.view.animation.DecelerateInterpolator; import android.view.animation.Transformation; import android.widget.FrameLayout; public class SlideHolder extends FrameLayout { public final static int DIRECTION_LEFT = 1; public final static int DIRECTION_RIGHT = -1; protected final static int MODE_READY = 0; protected final static int MODE_SLIDE = 1; protected final static int MODE_FINISHED = 2; private Bitmap mCachedBitmap; private Canvas mCachedCanvas; private Paint mCachedPaint; private View mMenuView; private int mMode = MODE_READY; private int mDirection = DIRECTION_LEFT; private int mOffset = 0; private int mStartOffset; private int mEndOffset; private boolean mEnabled = true; private boolean mInterceptTouch = true; private boolean mAlwaysOpened = false; private boolean mDispatchWhenOpened = false; private Queue<Runnable> mWhenReady = new LinkedList<Runnable>(); private OnSlideListener mListener; public SlideHolder(Context context) { super(context); initView(); } public SlideHolder(Context context, AttributeSet attrs) { super(context, attrs); initView(); } public SlideHolder(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); initView(); } private void initView() { mCachedPaint = new Paint( Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG | Paint.DITHER_FLAG ); } @Override public void setEnabled(boolean enabled) { mEnabled = enabled; } @Override public boolean isEnabled() { return mEnabled; } /** * * @param direction - direction in which SlideHolder opens. Can be: DIRECTION_LEFT, DIRECTION_RIGHT */ public void setDirection(int direction) { closeImmediately(); mDirection = direction; } /** * * @param allow - if false, SlideHolder won't react to swiping gestures (but still will be able to work by manually invoking mathods) */ public void setAllowInterceptTouch(boolean allow) { mInterceptTouch = allow; } public boolean isAllowedInterceptTouch() { return mInterceptTouch; } /** * * @param dispatch - if true, in open state SlideHolder will dispatch touch events to main layout (in other words - it will be clickable) */ public void setDispatchTouchWhenOpened(boolean dispatch) { mDispatchWhenOpened = dispatch; } public boolean isDispatchTouchWhenOpened() { return mDispatchWhenOpened; } /** * * @param opened - if true, SlideHolder will always be in opened state (which means that swiping won't work) */ public void setAlwaysOpened(boolean opened) { mAlwaysOpened = opened; requestLayout(); } public int getMenuOffset() { return mOffset; } public void setOnSlideListener(OnSlideListener lis) { mListener = lis; } public boolean isOpened() { return mAlwaysOpened || mMode == MODE_FINISHED; } public void toggle(boolean immediately) { if(immediately) { toggleImmediately(); } else { toggle(); } } public void toggle() { if(isOpened()) { close(); } else { open(); } } public void toggleImmediately() { if(isOpened()) { closeImmediately(); } else { openImmediately(); } } public boolean open() { if(isOpened() || mAlwaysOpened || mMode == MODE_SLIDE) { return false; } if(!isReadyForSlide()) { mWhenReady.add(new Runnable() { @Override public void run() { open(); } }); return true; } initSlideMode(); Animation anim = new SlideAnimation(mOffset, mEndOffset); anim.setAnimationListener(mOpenListener); startAnimation(anim); invalidate(); return true; } public boolean openImmediately() { if(isOpened() || mAlwaysOpened || mMode == MODE_SLIDE) { return false; } if(!isReadyForSlide()) { mWhenReady.add(new Runnable() { @Override public void run() { openImmediately(); } }); return true; } mMenuView.setVisibility(View.VISIBLE); mMode = MODE_FINISHED; requestLayout(); if(mListener != null) { mListener.onSlideCompleted(true); } return true; } public boolean close() { if(!isOpened() || mAlwaysOpened || mMode == MODE_SLIDE) { return false; } if(!isReadyForSlide()) { mWhenReady.add(new Runnable() { @Override public void run() { close(); } }); return true; } initSlideMode(); Animation anim = new SlideAnimation(mOffset, mEndOffset); anim.setAnimationListener(mCloseListener); startAnimation(anim); invalidate(); return true; } public boolean closeImmediately() { if(!isOpened() || mAlwaysOpened || mMode == MODE_SLIDE) { return false; } if(!isReadyForSlide()) { mWhenReady.add(new Runnable() { @Override public void run() { closeImmediately(); } }); return true; } mMenuView.setVisibility(View.GONE); mMode = MODE_READY; requestLayout(); if(mListener != null) { mListener.onSlideCompleted(false); } return true; } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { final int parentLeft = 0; final int parentTop = 0; final int parentRight = r - l; final int parentBottom = b - t; View menu = getChildAt(0); int menuWidth = menu.getMeasuredWidth(); if(mDirection == DIRECTION_LEFT) { menu.layout(parentLeft, parentTop, parentLeft+menuWidth, parentBottom); } else { menu.layout(parentRight-menuWidth, parentTop, parentRight, parentBottom); } if(mAlwaysOpened) { if(mDirection == DIRECTION_LEFT) { mOffset = menuWidth; } else { mOffset = 0; } } else if(mMode == MODE_FINISHED) { mOffset = mDirection*menuWidth; } else if(mMode == MODE_READY) { mOffset = 0; } View main = getChildAt(1); main.layout( parentLeft + mOffset, parentTop, parentLeft + mOffset + main.getMeasuredWidth(), parentBottom ); invalidate(); Runnable rn; while((rn = mWhenReady.poll()) != null) { rn.run(); } } private boolean isReadyForSlide() { return (getWidth() > 0 && getHeight() > 0); } @Override protected void onMeasure(int wSp, int hSp) { mMenuView = getChildAt(0); if(mAlwaysOpened) { View main = getChildAt(1); if(mMenuView != null && main != null) { measureChild(mMenuView, wSp, hSp); LayoutParams lp = (LayoutParams) main.getLayoutParams(); if(mDirection == DIRECTION_LEFT) { lp.leftMargin = mMenuView.getMeasuredWidth(); } else { lp.rightMargin = mMenuView.getMeasuredWidth(); } } } super.onMeasure(wSp, hSp); } private byte mFrame = 0; @Override protected void dispatchDraw(Canvas canvas) { try { if(mMode == MODE_SLIDE) { View main = getChildAt(1); if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { /* * On new versions we redrawing main layout only * if it's marked as dirty */ if(main.isDirty()) { mCachedCanvas.drawColor(Color.TRANSPARENT, Mode.CLEAR); main.draw(mCachedCanvas); } } else { /* * On older versions we just redrawing our cache * every 5th frame */ if(++mFrame % 5 == 0) { mCachedCanvas.drawColor(Color.TRANSPARENT, Mode.CLEAR); main.draw(mCachedCanvas); } } /* * Draw only visible part of menu */ View menu = getChildAt(0); final int scrollX = menu.getScrollX(); final int scrollY = menu.getScrollY(); canvas.save(); if(mDirection == DIRECTION_LEFT) { canvas.clipRect(0, 0, mOffset, menu.getHeight(), Op.REPLACE); } else { int menuWidth = menu.getWidth(); int menuLeft = menu.getLeft(); canvas.clipRect(menuLeft+menuWidth+mOffset, 0, menuLeft+menuWidth, menu.getHeight()); } canvas.translate(menu.getLeft(), menu.getTop()); canvas.translate(-scrollX, -scrollY); menu.draw(canvas); canvas.restore(); canvas.drawBitmap(mCachedBitmap, mOffset, 0, mCachedPaint); } else { if(!mAlwaysOpened && mMode == MODE_READY) { mMenuView.setVisibility(View.GONE); } super.dispatchDraw(canvas); } } catch(IndexOutOfBoundsException e) { /* * Possibility of crashes on some devices (especially on Samsung). * Usually, when ListView is empty. */ } } private int mHistoricalX = 0; private boolean mCloseOnRelease = false; @Override public boolean dispatchTouchEvent(MotionEvent ev) { if(((!mEnabled || !mInterceptTouch) && mMode == MODE_READY) || mAlwaysOpened) { return super.dispatchTouchEvent(ev); } if(mMode != MODE_FINISHED) { onTouchEvent(ev); if(mMode != MODE_SLIDE) { super.dispatchTouchEvent(ev); } else { MotionEvent cancelEvent = MotionEvent.obtain(ev); cancelEvent.setAction(MotionEvent.ACTION_CANCEL); super.dispatchTouchEvent(cancelEvent); cancelEvent.recycle(); } return true; } else { final int action = ev.getAction(); Rect rect = new Rect(); View menu = getChildAt(0); menu.getHitRect(rect); if(!rect.contains((int) ev.getX(), (int) ev.getY())) { if (action == MotionEvent.ACTION_UP && mCloseOnRelease && !mDispatchWhenOpened) { close(); mCloseOnRelease = false; } else { if(action == MotionEvent.ACTION_DOWN && !mDispatchWhenOpened) { mCloseOnRelease = true; } onTouchEvent(ev); } if(mDispatchWhenOpened) { super.dispatchTouchEvent(ev); } return true; } else { onTouchEvent(ev); ev.offsetLocation(-menu.getLeft(), -menu.getTop()); menu.dispatchTouchEvent(ev); return true; } } } private boolean handleTouchEvent(MotionEvent ev) { if(!mEnabled) { return false; } float x = ev.getX(); if(ev.getAction() == MotionEvent.ACTION_DOWN) { mHistoricalX = (int) x; return true; } if(ev.getAction() == MotionEvent.ACTION_MOVE) { float diff = x - mHistoricalX; if((mDirection*diff > 50 && mMode == MODE_READY) || (mDirection*diff < -50 && mMode == MODE_FINISHED)) { mHistoricalX = (int) x; initSlideMode(); } else if(mMode == MODE_SLIDE) { mOffset += diff; mHistoricalX = (int) x; if(!isSlideAllowed()) { finishSlide(); } } else { return false; } } if(ev.getAction() == MotionEvent.ACTION_UP) { if(mMode == MODE_SLIDE) { finishSlide(); } mCloseOnRelease = false; return false; } return mMode == MODE_SLIDE; } @Override public boolean onTouchEvent(MotionEvent ev) { boolean handled = handleTouchEvent(ev); invalidate(); return handled; } private void initSlideMode() { mCloseOnRelease = false; View v = getChildAt(1); if(mMode == MODE_READY) { mStartOffset = 0; mEndOffset = mDirection*getChildAt(0).getWidth(); } else { mStartOffset = mDirection*getChildAt(0).getWidth(); mEndOffset = 0; } mOffset = mStartOffset; if(mCachedBitmap == null || mCachedBitmap.isRecycled() || mCachedBitmap.getWidth() != v.getWidth()) { mCachedBitmap = Bitmap.createBitmap(v.getWidth(), v.getHeight(), Bitmap.Config.ARGB_8888); mCachedCanvas = new Canvas(mCachedBitmap); } else { mCachedCanvas.drawColor(Color.TRANSPARENT, Mode.CLEAR); } v.setVisibility(View.VISIBLE); mCachedCanvas.translate(-v.getScrollX(), -v.getScrollY()); v.draw(mCachedCanvas); mMode = MODE_SLIDE; mMenuView.setVisibility(View.VISIBLE); } private boolean isSlideAllowed() { return (mDirection*mEndOffset > 0 && mDirection*mOffset < mDirection*mEndOffset && mDirection*mOffset >= mDirection*mStartOffset) || (mEndOffset == 0 && mDirection*mOffset > mDirection*mEndOffset && mDirection*mOffset <= mDirection*mStartOffset); } private void completeOpening() { mOffset = mDirection*mMenuView.getWidth(); requestLayout(); post(new Runnable() { @Override public void run() { mMode = MODE_FINISHED; mMenuView.setVisibility(View.VISIBLE); } }); if(mListener != null) { mListener.onSlideCompleted(true); } } private Animation.AnimationListener mOpenListener = new Animation.AnimationListener() { @Override public void onAnimationStart(Animation animation) {} @Override public void onAnimationRepeat(Animation animation) {} @Override public void onAnimationEnd(Animation animation) { completeOpening(); } }; private void completeClosing() { mOffset = 0; requestLayout(); post(new Runnable() { @Override public void run() { mMode = MODE_READY; mMenuView.setVisibility(View.GONE); } }); if(mListener != null) { mListener.onSlideCompleted(false); } } private Animation.AnimationListener mCloseListener = new Animation.AnimationListener() { @Override public void onAnimationStart(Animation animation) {} @Override public void onAnimationRepeat(Animation animation) {} @Override public void onAnimationEnd(Animation animation) { completeClosing(); } }; private void finishSlide() { if(mDirection*mEndOffset > 0) { if(mDirection*mOffset > mDirection*mEndOffset/2) { if(mDirection*mOffset > mDirection*mEndOffset) mOffset = mEndOffset; Animation anim = new SlideAnimation(mOffset, mEndOffset); anim.setAnimationListener(mOpenListener); startAnimation(anim); } else { if(mDirection*mOffset < mDirection*mStartOffset) mOffset = mStartOffset; Animation anim = new SlideAnimation(mOffset, mStartOffset); anim.setAnimationListener(mCloseListener); startAnimation(anim); } } else { if(mDirection*mOffset < mDirection*mStartOffset/2) { if(mDirection*mOffset < mDirection*mEndOffset) mOffset = mEndOffset; Animation anim = new SlideAnimation(mOffset, mEndOffset); anim.setAnimationListener(mCloseListener); startAnimation(anim); } else { if(mDirection*mOffset > mDirection*mStartOffset) mOffset = mStartOffset; Animation anim = new SlideAnimation(mOffset, mStartOffset); anim.setAnimationListener(mOpenListener); startAnimation(anim); } } } private class SlideAnimation extends Animation { private static final float SPEED = 0.6f; private float mStart; private float mEnd; public SlideAnimation(float fromX, float toX) { mStart = fromX; mEnd = toX; setInterpolator(new DecelerateInterpolator()); float duration = Math.abs(mEnd - mStart) / SPEED; setDuration((long) duration); } @Override protected void applyTransformation(float interpolatedTime, Transformation t) { super.applyTransformation(interpolatedTime, t); float offset = (mEnd - mStart) * interpolatedTime + mStart; mOffset = (int) offset; postInvalidate(); } } public static interface OnSlideListener { public void onSlideCompleted(boolean opened); } }
使用:
package com.agimind.sidemenuexample; import com.agimind.widget.SlideHolder; import android.os.Bundle; import android.view.MenuItem; import android.view.View; import android.app.ActionBar; import android.app.Activity; public class MainActivity extends Activity { private SlideHolder mSlideHolder; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mSlideHolder = (SlideHolder) findViewById(R.id.slideHolder); // mSlideHolder.setAllowInterceptTouch(false); // mSlideHolder.setAlwaysOpened(true); /* * toggleView can actually be any view you want. Here, for simplicity, * we're using TextView, but you can easily replace it with button. * * Note, when menu opens our textView will become invisible, so it quite * pointless to assign toggle-event to it. In real app consider using UP * button instead. In our case toggle() can be replaced with open(). */ ActionBar actionBar = getActionBar(); actionBar.setDisplayShowHomeEnabled(true); actionBar.setHomeButtonEnabled(true); View toggleView = findViewById(R.id.textView); toggleView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { mSlideHolder.toggle(); } }); } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case android.R.id.home: mSlideHolder.toggle(); break; default: break; } return super.onOptionsItemSelected(item); } }
布局如下:
<com.agimind.widget.SlideHolder xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/slideHolder" android:layout_width="fill_parent" android:layout_height="fill_parent" tools:context=".MainActivity" > <ScrollView android:layout_width="200dp" android:layout_height="fill_parent" android:background="@android:color/black" > <LinearLayout android:layout_width="200dp" android:layout_height="wrap_content" android:orientation="vertical" > <Button android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="@string/menu_settings" /> <Button android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="@string/menu_settings" /> <Button android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="@string/menu_settings" /> <Button android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="@string/menu_settings" /> <Button android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="@string/menu_settings" /> <Button android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="@string/menu_settings" /> <Button android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="@string/menu_settings" /> <Button android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="@string/menu_settings" /> <Button android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="@string/menu_settings" /> <Button android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="@string/menu_settings" /> <Button android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="@string/menu_settings" /> <Button android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="@string/menu_settings" /> <Button android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="@string/menu_settings" /> <Button android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="@string/menu_settings" /> <Button android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="@string/menu_settings" /> <Button android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="@string/menu_settings" /> <Button android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="@string/menu_settings" /> <Button android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="@string/menu_settings" /> <Button android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="@string/menu_settings" /> <Button android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="@string/menu_settings" /> <Button android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="@string/menu_settings" /> <Button android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="@string/menu_settings" /> </LinearLayout> </ScrollView> <RelativeLayout android:layout_width="fill_parent" android:layout_height="fill_parent" > <TextView android:id="@+id/textView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerHorizontal="true" android:layout_centerVertical="true" android:text="@string/swipe" android:textSize="25sp" /> </RelativeLayout> </com.agimind.widget.SlideHolder>
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持武林网。
新闻热点
疑难解答