轉自:http://blog.csdn.net/cym492224103/article/details/39177275
androidresidemenu
github:https://github.com/specialcyci/androidresidemenu csdn:http://download.csdn.net/detail/cym492224103/7887801
先看看如何使用:
把項目源碼下載下傳下來導入工程,可以看到
residemenu為引用工程,再看看如何使用這個引用工程來建構出residemenu,
1.先new一個residemenu對象
[java] view
plaincopyprint?
residemenu = new residemenu(this);
2.設定它的背景圖檔
residemenu.setbackground(r.drawable.menu_background);
3.綁定目前activity
residemenu.attachtoactivity(this);
4.設定監聽
residemenu.setmenulistener(menulistener);
可以監聽菜單打開和關閉狀态
private residemenu.onmenulistener menulistener = new residemenu.onmenulistener() {
@override
public void openmenu() {
toast.maketext(mcontext, "menu is opened!", toast.length_short).show();
}
public void closemenu() {
toast.maketext(mcontext, "menu is closed!", toast.length_short).show();
};
5.設定内容縮放比例(0.1~1f)
//valid scale factor is between 0.0f and 1.0f. leftmenu'width is 150dip.
residemenu.setscalevalue(0.6f);
6.建立子菜單
// create menu items;
itemhome = new residemenuitem(this, r.drawable.icon_home, "home");
itemprofile = new residemenuitem(this, r.drawable.icon_profile, "profile");
itemcalendar = new residemenuitem(this, r.drawable.icon_calendar, "calendar");
itemsettings = new residemenuitem(this, r.drawable.icon_settings, "settings");
7.設定點選事件及将剛建立的子菜單添加到側換菜單中(可以看到它是通過常量來控制子菜單的添加位置)
itemhome.setonclicklistener(this);
itemprofile.setonclicklistener(this);
itemcalendar.setonclicklistener(this);
itemsettings.setonclicklistener(this);
residemenu.addmenuitem(itemhome, residemenu.direction_left);
residemenu.addmenuitem(itemprofile, residemenu.direction_left);
residemenu.addmenuitem(itemcalendar, residemenu.direction_right);
residemenu.addmenuitem(itemsettings, residemenu.direction_right);
8.設定title按鈕的點選事件,設定左右菜單的開關
// you can disable a direction by setting ->
// residemenu.setswipedirectiondisable(residemenu.direction_right);
findviewbyid(r.id.title_bar_left_menu).setonclicklistener(new view.onclicklistener() {
@override
public void onclick(view view) {
residemenu.openmenu(residemenu.direction_left);
}
});
findviewbyid(r.id.title_bar_right_menu).setonclicklistener(new view.onclicklistener() {
residemenu.openmenu(residemenu.direction_right);
9.還重寫了dispatchtouchevent
@override
public boolean dispatchtouchevent(motionevent ev) {
return residemenu.dispatchtouchevent(ev);
10.菜單關閉方法
residemenu.closemenu();
11.屏蔽菜單方法
使用方法已經說完了,接下來,看看它的源碼,先看看源碼的項目結構。
很多人初學者都曾糾結,看源碼,如何從何看起,我個人建議從上面使用的順序看起,并且在看的時候要帶個問題去看去思考,這樣更容易了解。
上面的第一步是,建立residemenu對象,我們就看看residemenu的構造。
public residemenu(context context) {
super(context);
initviews(context);
從上面代碼,看到構造裡面就一個初始化view,思考問題:如何初始化view及初始化了什麼view。
private void initviews(context context){
layoutinflater inflater = (layoutinflater)
context.getsystemservice(context.layout_inflater_service);
inflater.inflate(r.layout.residemenu, this);
scrollviewleftmenu = (scrollview) findviewbyid(r.id.sv_left_menu);
scrollviewrightmenu = (scrollview) findviewbyid(r.id.sv_right_menu);
imageviewshadow = (imageview) findviewbyid(r.id.iv_shadow);
layoutleftmenu = (linearlayout) findviewbyid(r.id.layout_left_menu);
layoutrightmenu = (linearlayout) findviewbyid(r.id.layout_right_menu);
imageviewbackground = (imageview) findviewbyid(r.id.iv_background);
原理分析:從上面的代碼可以看到,加載了一個residemenu的布局,先看布局
<?xml version="1.0" encoding="utf-8"?>
<framelayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<imageview
android:id="@+id/iv_background"
android:adjustviewbounds="true"
android:scaletype="centercrop"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
android:id="@+id/iv_shadow"
android:background="@drawable/shadow"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:scaletype="fitxy"/>
<scrollview
android:id="@+id/sv_left_menu"
android:scrollbars="none"
android:paddingleft="30dp"
android:layout_width="150dp"
android:layout_height="fill_parent">
<linearlayout
android:id="@+id/layout_left_menu"
android:orientation="vertical"
android:layout_gravity="center_vertical"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
</linearlayout>
</scrollview>
android:id="@+id/sv_right_menu"
android:paddingright="30dp"
android:layout_gravity="right">
android:id="@+id/layout_right_menu"
android:layout_height="wrap_content"
android:gravity="right">
</framelayout>
布局顯示效果
從布局檔案,以及顯示效果我們可以看到,它是一個幀布局,第一個imageview是背景,第二個imageview是.9的陰影效果的圖檔(看下面的圖),
兩個(scrollview包裹着一個linerlayout),可以從上面圖看到結構分别是左菜單和右菜單
<img src="http://img.blog.csdn.net/20140910100807704?watermark/2/text/ahr0cdovl2jsb2cuy3nkbi5uzxqvy3ltndkymji0mtaz/font/5a6l5l2t/fontsize/400/fill/i0jbqkfcma==/dissolve/70/gravity/southeast" style="font-family: arial; background-color: rgb(255, 255, 255);" alt="" />
1.初始化布局以及布局檔案分析完畢,2.接下來是設定背景圖,初始化view的時候就已經拿到了背景控件,是以設定背景圖也是非常好實作的事情了。
public void setbackground(int imageresrouce){
imageviewbackground.setimageresource(imageresrouce);
}
3.綁定activity,思考問題:它做了什麼?
/**
* use the method to set up the activity which residemenu need to show;
*
* @param activity
*/
public void attachtoactivity(activity activity){
initvalue(activity);
setshadowadjustscalexbyorientation();
viewdecor.addview(this, 0);
setviewpadding();
}
原理分析:綁定activity做了4件事情,分别是:
1.初始化參數:
private void initvalue(activity activity){
this.activity = activity;
leftmenuitems = new arraylist<residemenuitem>();
rightmenuitems = new arraylist<residemenuitem>();
ignoredviews = new arraylist<view>();
viewdecor = (viewgroup) activity.getwindow().getdecorview();
viewactivity = new touchdisableview(this.activity);
view mcontent = viewdecor.getchildat(0);
viewdecor.removeviewat(0);
viewactivity.setcontent(mcontent);
addview(viewactivity);
viewgroup parent = (viewgroup) scrollviewleftmenu.getparent();
parent.removeview(scrollviewleftmenu);
parent.removeview(scrollviewrightmenu);
}
2.正對橫豎屏縮放比例進行調整
private void setshadowadjustscalexbyorientation(){
int orientation = getresources().getconfiguration().orientation;
if (orientation == configuration.orientation_landscape) {
shadowadjustscalex = 0.034f;
shadowadjustscaley = 0.12f;
} else if (orientation == configuration.orientation_portrait) {
shadowadjustscalex = 0.06f;
shadowadjustscaley = 0.07f;
}
}
3.添加目前view
viewdecor.addview(this, 0);
4.設定view邊距
* we need the call the method before the menu show, because the
* padding of activity can't get at the moment of oncreateview();
*/
private void setviewpadding(){
this.setpadding(viewactivity.getpaddingleft(),
viewactivity.getpaddingtop(),
viewactivity.getpaddingright(),
viewactivity.getpaddingbottom());
4.設定監聽,思考問題:它什麼時候調用監聽,原理分析:動畫監聽開始執行動畫掉哦那個openmenu動畫結束調用closemenu,從此我們可以想到,但它調用openmenu(int direction)和closemenu()都會設定這個監聽。
private animator.animatorlistener animationlistener = new animator.animatorlistener() {
@override
public void onanimationstart(animator animation) {
if (isopened()){
showscrollviewmenu();
if (menulistener != null)
menulistener.openmenu();
}
public void onanimationend(animator animation) {
// reset the view;
if(isopened()){
viewactivity.settouchdisable(true);
viewactivity.setonclicklistener(viewactivityonclicklistener);
}else{
viewactivity.settouchdisable(false);
viewactivity.setonclicklistener(null);
hidescrollviewmenu();
menulistener.closemenu();
public void onanimationcancel(animator animation) {
public void onanimationrepeat(animator animation) {
};
5.設定内容縮放比例(0.1~1f),細心的同學會發現在當縮完成後還可以在往裡面拉到更小,有種彈性的感覺,挺有趣的。但是有些人的需求不想要有這種彈性效果,我們可以通過修改源碼修改這個彈性效果,找到gettargetscale這個方法,修改下面0.5這個數值。使用時設定了0.6的縮放比例,預設下面的彈性參數是0.5是以我們當縮完成後還可以在往裡面拉0.1的比例。
private float gettargetscale(float currentrawx){
float scalefloatx = ((currentrawx - lastrawx) / getscreenwidth()) * 0.75f;
scalefloatx = scaledirection == direction_right ? - scalefloatx : scalefloatx;
float targetscale = viewhelper.getscalex(viewactivity) - scalefloatx;
targetscale = targetscale > 1.0f ? 1.0f : targetscale;
targetscale = targetscale < 0.5f ? 0.5f : targetscale;
return targetscale;
預設縮放比例:
//valid scale factor is between 0.0f and 1.0f.
private float mscalevalue = 0.5f;
animatorset scaledown_activity = buildscaledownanimation(viewactivity, mscalevalue, mscalevalue);
* a helper method to build scale down animation;
*
* @param target
* @param targetscalex
* @param targetscaley
* @return
private animatorset buildscaledownanimation(view target,float targetscalex,float targetscaley){
animatorset scaledown = new animatorset();
scaledown.playtogether(
objectanimator.offloat(target, "scalex", targetscalex),
objectanimator.offloat(target, "scaley", targetscaley)
);
scaledown.setinterpolator(animationutils.loadinterpolator(activity,
android.r.anim.decelerate_interpolator));
scaledown.setduration(250);
return scaledown;
6.建立子菜單,看下子菜單的構造,我們通過上面的學習,原理分析:我們可以猜測到,無非就是加載布局設定内容
public residemenuitem(context context, int icon, string title) {
iv_icon.setimageresource(icon);
tv_title.settext(title);
private void initviews(context context){
layoutinflater inflater=(layoutinflater) context.getsystemservice(context.layout_inflater_service);
inflater.inflate(r.layout.residemenu_item, this);
iv_icon = (imageview) findviewbyid(r.id.iv_icon);
tv_title = (textview) findviewbyid(r.id.tv_title);
布局檔案:
[html] view
<linearlayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:paddingtop="30dp">
android:layout_width="30dp"
android:layout_height="30dp"
android:id="@+id/iv_icon"/>
<textview
android:layout_height="wrap_content"
android:textcolor="@android:color/white"
android:textsize="18sp"
android:layout_marginleft="10dp"
android:id="@+id/tv_title"/>
</linearlayout>
顯示效果圖:
7.子菜單添加到側換菜單中(可以看到它是通過常量來控制子菜單的添加位置)原理分析:根據不同的常量來區分添加不同菜單的子菜單
* add a single items;
*
* @param menuitem
* @param direction
*/
public void addmenuitem(residemenuitem menuitem, int direction){
if (direction == direction_left){
this.leftmenuitems.add(menuitem);
layoutleftmenu.addview(menuitem);
}else{
this.rightmenuitems.add(menuitem);
layoutrightmenu.addview(menuitem);
}
8.設定title按鈕的點選事件,設定左右菜單的開關,原理分析:先設定了縮放方向然後在設定動畫,正如我們上面想的一樣還設定了動畫監聽。
* show the reside menu;
*/
public void openmenu(int direction){
setscaledirection(direction);
isopened = true;
animatorset scaledown_activity = buildscaledownanimation(viewactivity, mscalevalue, mscalevalue);
animatorset scaledown_shadow = buildscaledownanimation(imageviewshadow,
mscalevalue + shadowadjustscalex, mscalevalue + shadowadjustscaley);
animatorset alpha_menu = buildmenuanimation(scrollviewmenu, 1.0f);
scaledown_shadow.addlistener(animationlistener);
scaledown_activity.playtogether(scaledown_shadow);
scaledown_activity.playtogether(alpha_menu);
scaledown_activity.start();
設定縮放方向及計算x,y軸位置。
private void setscaledirection(int direction){
int screenwidth = getscreenwidth();
float pivotx;
float pivoty = getscreenheight() * 0.5f;
if (direction == direction_left){
scrollviewmenu = scrollviewleftmenu;
pivotx = screenwidth * 1.5f;
}else{
scrollviewmenu = scrollviewrightmenu;
pivotx = screenwidth * -0.5f;
viewhelper.setpivotx(viewactivity, pivotx);
viewhelper.setpivoty(viewactivity, pivoty);
viewhelper.setpivotx(imageviewshadow, pivotx);
viewhelper.setpivoty(imageviewshadow, pivoty);
scaledirection = direction;
9.重寫dispatchtouchevent,問題思考:如何到根據手指滑動自動縮放
如果還不了解,dispatchtouchevent這個函數如何調用?什麼時候調用?請先看看http://blog.csdn.net/cym492224103/article/details/39179311
public boolean dispatchtouchevent(motionevent ev) {
float currentactivityscalex = viewhelper.getscalex(viewactivity);
if (currentactivityscalex == 1.0f)
setscaledirectionbyrawx(ev.getrawx());
switch (ev.getaction()){
case motionevent.action_down:
lastactiondownx = ev.getx();
lastactiondowny = ev.gety();
isinignoredview = isinignoredview(ev) && !isopened();
pressedstate = pressed_down;
break;
case motionevent.action_move:
if (isinignoredview || isindisabledirection(scaledirection))
break;
if(pressedstate != pressed_down &&
pressedstate != pressed_move_horizantal)
int xoffset = (int) (ev.getx() - lastactiondownx);
int yoffset = (int) (ev.gety() - lastactiondowny);
if(pressedstate == pressed_down) {
if(yoffset > 25 || yoffset < -25) {
pressedstate = pressed_move_vertical;
break;
}
if(xoffset < -50 || xoffset > 50) {
pressedstate = pressed_move_horizantal;
ev.setaction(motionevent.action_cancel);
} else if(pressedstate == pressed_move_horizantal) {
if (currentactivityscalex < 0.95)
showscrollviewmenu();
float targetscale = gettargetscale(ev.getrawx());
viewhelper.setscalex(viewactivity, targetscale);
viewhelper.setscaley(viewactivity, targetscale);
viewhelper.setscalex(imageviewshadow, targetscale + shadowadjustscalex);
viewhelper.setscaley(imageviewshadow, targetscale + shadowadjustscaley);
viewhelper.setalpha(scrollviewmenu, (1 - targetscale) * 2.0f);
lastrawx = ev.getrawx();
return true;
case motionevent.action_up:
if (isinignoredview) break;
if (pressedstate != pressed_move_horizantal) break;
pressedstate = pressed_done;
if (currentactivityscalex > 0.56f)
closemenu();
else
openmenu(scaledirection);
if (currentactivityscalex < 0.94f){
}else{
lastrawx = ev.getrawx();
return super.dispatchtouchevent(ev);
上面代碼量有點多,看上去有點暈,接下來我們來分别從按下、移動、放開、來原理分析:
motionevent.action_down:
記錄了x,y軸的坐标點,判斷是否打開,設定了按下的狀态為pressed_down
motionevent.action_move:
拿到目前x,y減去down下記錄下來的x,y,這樣得到了移動的x,y,
然後判斷如果如果移動的x,y大于25或者小于-25就改變按下狀态為pressed_move_vertical
如果移動的x,y大于50或者小于-50就改變狀态為pressed_move_horizantal
狀态為pressed_move_horizantal就改變菜單主視圖内容以及陰影圖檔大小,在改變的同時還設定了目前菜單的透明度。
motionevent.action_up:
判斷是否菜單是否打開狀态,在擷取目前縮放的x比例,
判斷比例小于0.56f,則關閉菜單,反正開啟菜單。
看完後,我們在回去看看代碼,就會發現其實也不過如此~!
10.菜單關閉方法,同樣也設定了動畫監聽之前的想法也是成立的。
* close the reslide menu;
public void closemenu(){
isopened = false;
animatorset scaleup_activity = buildscaleupanimation(viewactivity, 1.0f, 1.0f);
animatorset scaleup_shadow = buildscaleupanimation(imageviewshadow, 1.0f, 1.0f);
animatorset alpha_menu = buildmenuanimation(scrollviewmenu, 0.0f);
scaleup_activity.addlistener(animationlistener);
scaleup_activity.playtogether(scaleup_shadow);
scaleup_activity.playtogether(alpha_menu);
scaleup_activity.start();
public void setswipedirectiondisable(int direction){
disabledswipedirection.add(direction);
private boolean isindisabledirection(int direction){
return disabledswipedirection.contains(direction);
原理分析:在重寫dispatchtouchevent的時候,細心的同學應該會看到,action_move下面有個判斷
if (isinignoredview || isindisabledirection(scaledirection))
如果這個方向的菜單被屏蔽了,就滑不出來了。
最後我們會發現我們一直都沒說到touchdisableview,其實initvalue的時候就初始化了,它就是viewactivity,是我們的内容視圖。
我們來看看它做了什麼?
protected void onmeasure(int widthmeasurespec, int heightmeasurespec) {
int width = getdefaultsize(0, widthmeasurespec);
int height = getdefaultsize(0, heightmeasurespec);
setmeasureddimension(width, height);
final int contentwidth = getchildmeasurespec(widthmeasurespec, 0, width);
final int contentheight = getchildmeasurespec(heightmeasurespec, 0, height);
mcontent.measure(contentwidth, contentheight);
protected void onlayout(boolean changed, int l, int t, int r, int b) {
final int width = r - l;
final int height = b - t;
mcontent.layout(0, 0, width, height);
public boolean onintercepttouchevent(motionevent ev) {
return mtouchdisabled;
void settouchdisable(boolean disabletouch) {
mtouchdisabled = disabletouch;
boolean istouchdisabled() {
動态設定寬高,設定事件是否傳遞下去的flag。