Android开源AndroidSideMenu实现抽屉和侧滑菜单
程序员文章站
2023-12-16 10:35:04
androidsidemenu能够让你轻而易举地创建侧滑菜单。需要注意的是,该项目自身并不提供任何创建菜单的工具,因此,开发者可以*创建内部菜单。
核心类如下:...
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>
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。