扫二维码与项目经理沟通
我们在微信上24小时期待你的声音
解答本文疑问/技术咨询/运营咨询/技术建议/互联网交流
AndroidSideMenu能够让你轻而易举地创建侧滑菜单。需要注意的是,该项目自身并不提供任何创建菜单的工具,因此,开发者可以自由创建内部菜单。
坚守“ 做人真诚 · 做事靠谱 · 口碑至上 · 高效敬业 ”的价值观,专业网站建设服务10余年为成都成都广告推广小微创业公司专业提供企业网站建设营销网站建设商城网站建设手机网站建设小程序网站建设网站改版,从内容策划、视觉设计、底层架构、网页布局、功能开发迭代于一体的高端网站建设服务。
核心类如下:
/* * 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 QueuemWhenReady = new LinkedList (); 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); } }
布局如下:
下载:AndroidSideMenu
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持创新互联。
我们在微信上24小时期待你的声音
解答本文疑问/技术咨询/运营咨询/技术建议/互联网交流