AndroidResideMenu html
github:https://github.com/SpecialCyCi/AndroidResideMenu csdn:http://download.csdn.net/detail/cym492224103/7887801 java
先看看如何使用: android
把項目源碼下載下來導入工程,能夠看到 git
ResideMenu爲引用工程,再看看如何使用這個引用工程來構建出ResideMenu, github
1.先new一個ResideMenu對象 ide
- 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();
- }
-
- @Override
- 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() {
- @Override
- public void onClick(View view) {
- resideMenu.openMenu(ResideMenu.DIRECTION_RIGHT);
- }
- });
9.還重寫了dispatchTouchEvent
- @Override
- public boolean dispatchTouchEvent(MotionEvent ev) {
- return resideMenu.dispatchTouchEvent(ev);
- }
10.菜單關閉方法
11.屏蔽菜單方法 函數
- // You can disable a direction by setting ->
- // resideMenu.setSwipeDirectionDisable(ResideMenu.DIRECTION_RIGHT);
使用方法已經說完了,接下來,看看它的源碼,先看看源碼的項目結構。 源碼分析
不少人初學者都曾糾結,看源碼,如何從何看起,我我的建議從上面使用的順序看起,而且在看的時候要帶個問題去看去思考,這樣更容易理解。 佈局
上面的第一步是,建立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"/>
-
- <ImageView
- 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>
-
- <ScrollView
- android:id="@+id/sv_right_menu"
- android:scrollbars="none"
- android:paddingRight="30dp"
- android:layout_width="150dp"
- android:layout_height="fill_parent"
- android:layout_gravity="right">
- <LinearLayout
- android:id="@+id/layout_right_menu"
- android:orientation="vertical"
- android:layout_gravity="center_vertical"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:gravity="right">
-
- </LinearLayout>
- </ScrollView>
-
- </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();
- }
- }
-
- @Override
- public void onAnimationEnd(Animator animation) {
- // reset the view;
- if(isOpened()){
- viewActivity.setTouchDisable(true);
- viewActivity.setOnClickListener(viewActivityOnClickListener);
- }else{
- viewActivity.setTouchDisable(false);
- viewActivity.setOnClickListener(null);
- hideScrollViewMenu();
- if (menuListener != null)
- menuListener.closeMenu();
- }
- }
-
- @Override
- public void onAnimationCancel(Animator animation) {
-
- }
-
- @Override
- 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) {
- super(context);
- initViews(context);
- 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);
- }
佈局文件:
- <?xml version="1.0" encoding="utf-8"?>
-
- <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:orientation="horizontal"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:gravity="center_vertical"
- android:paddingTop="30dp">
-
- <ImageView
- android:layout_width="30dp"
- android:layout_height="30dp"
- android:scaleType="centerCrop"
- android:id="@+id/iv_icon"/>
-
- <TextView
- android:layout_width="match_parent"
- 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
- @Override
- 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)
- break;
-
- 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;
- }
-
- break;
-
- case MotionEvent.ACTION_UP:
-
- if (isInIgnoredView) break;
- if (pressedState != PRESSED_MOVE_HORIZANTAL) break;
-
- pressedState = PRESSED_DONE;
- if (isOpened()){
- if (currentActivityScaleX > 0.56f)
- closeMenu();
- else
- openMenu(scaleDirection);
- }else{
- if (currentActivityScaleX < 0.94f){
- openMenu(scaleDirection);
- }else{
- closeMenu();
- }
- }
-
- break;
-
- }
- 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();
- }
11.屏蔽菜單方法
- 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,是咱們的內容視圖。
咱們來看看它作了什麼?
- @Override
- 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);
- }
-
- @Override
- 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);
- }
-
- @Override
- public boolean onInterceptTouchEvent(MotionEvent ev) {
- return mTouchDisabled;
- }
-
- void setTouchDisable(boolean disableTouch) {
- mTouchDisabled = disableTouch;
- }
-
- boolean isTouchDisabled() {
- return mTouchDisabled;
- }
動態設置寬高,設置事件是否傳遞下去的flag。
好了,源碼分析已完畢,喜歡這篇文章的就請關注我吧~!