且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

改进Android SlidingMenu实现QQ样式边侧滑抽屉技术

更新时间:2022-08-21 21:05:08

改进Android SlidingMenu实现QQ样式边侧滑抽屉技术

我在之前写的附录文章1中介绍了Android SlidingMenu的使用,Android SlidingMenu作为第三方的边侧滑技术(也被称之为‘抽屉’),在过去的四五年中被广泛使用,但随着后来谷歌官方在Android中以官方支持的形式相继推出了DrawerLayout(附录文章2)和NavigationView(附录文章3)作为抽屉技术的标准实现后,SlidingMenu逐渐在边缘化。在以后的开发中,如果要实现抽屉的边侧滑效果,DrawerLayout和NavigationView是首选。
但是,SlidingMenu在过去毕竟盛行过,一些老旧的APP中作为继承和惯性,还在使用SlidingMenu。如果瞬间说抛弃SlidingMenu,代码切换的时间和工作量有些多。
这篇文章要写的是,如何在不改变SlidingMenu的主题结构情况下,对SlidingMenu进行代码改造和增强,实现类似QQ的边侧滑抽屉效果。
在一段时间,QQ手机客户端的边侧滑(抽屉),和SlidingMenu一样,左滑右滑切换出隐藏的菜单和功能界面,但是需要特别注意的是:QQ的边侧滑与普通的SlidingMenu边侧滑效果不同,QQ的边侧滑、当抽屉打开时候,是一种逐渐缩进缩出的放大/缩小进出效果,同时还带有一定的电影中屏幕逐渐亮/暗效果。本文将在SlidingMenu的基础上改进SlidingMenu代码,作为兼容和增强,实现QQ样式的边侧滑抽屉效果。
写一个布局文件,主布局,通常在实际的开发中,这就是要盛放主体内容的界面activity_main.xml,我简单写一个布局,主要用于测试:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"  
    android:layout_width="match_parent"  
    android:layout_height="match_parent"
    android:background="#FF6F00" >  
      
    <TextView  
        android:layout_width="wrap_content"  
        android:layout_height="wrap_content"  
        android:layout_centerInParent="true" 
        android:textColor="@android:color/white" 
        android:textSize="80sp"
        android:gravity="center"
        android:text="Zhang Phil" />  
  
</RelativeLayout>


MainActivity.java:

package zhangphil.slidingmenu;

import com.jeremyfeinstein.slidingmenu.lib.SlidingMenu;
import com.jeremyfeinstein.slidingmenu.lib.SlidingMenu.CanvasTransformer;
import com.nineoldandroids.view.ViewHelper;

import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.PorterDuff.Mode;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentTransaction;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

public class MainActivity extends FragmentActivity {

	private SlidingMenu mSlidingMenu;

	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);

		setContentView(R.layout.activity_main);

		mSlidingMenu = new SlidingMenu(this);

		mSlidingMenu.setMode(SlidingMenu.LEFT);
		mSlidingMenu.setTouchModeAbove(SlidingMenu.TOUCHMODE_MARGIN);
		mSlidingMenu.attachToActivity(this, SlidingMenu.SLIDING_CONTENT);

		// 左边
		mSlidingMenu.setMenu(R.layout.left_menu);

		Fragment leftFragment = TestFragment.newInstance();
		FragmentManager fm = getSupportFragmentManager();
		FragmentTransaction ft = fm.beginTransaction();
		ft.replace(R.id.left, leftFragment);
		ft.commit();

		// 核心关键处,这就是对SlideMenu的增强和改进。
		addQQSlideStyleSlide();
	}

	// 为SlideMenu添加QQ样式的边侧滑
	private void addQQSlideStyleSlide() {

		CanvasTransformer mTransformer = new CanvasTransformer() {

			@Override
			public void transformCanvas(Canvas canvas, float percentOpen) {

				View mainContent = mSlidingMenu.getContent();
				View leftContent = mSlidingMenu.getMenu();

				animateView(mainContent, leftContent, percentOpen);
			}
		};

		// 设置滑动菜单视图的宽度
		mSlidingMenu.setBehindOffsetRes(R.dimen.slidingmenu_offset);

		mSlidingMenu.setFadeEnabled(false);

		mSlidingMenu.setBehindCanvasTransformer(mTransformer);
	}

	private void animateView(View main, View left, float percent) {
		float f1 = 1 - percent * 0.3f;
		ViewHelper.setScaleX(main, f1);
		ViewHelper.setScaleY(main, f1);
		ViewHelper.setTranslationX(left, -left.getWidth() / 2.3f + left.getWidth() / 2.3f * percent);
		ViewHelper.setScaleX(left, 0.5f + 0.5f * percent);
		ViewHelper.setScaleY(left, 0.5f + 0.5f * percent);
		ViewHelper.setAlpha(left, percent);

		getWindow().getDecorView().getBackground().setColorFilter(evaluate(percent, Color.BLACK, Color.TRANSPARENT),
				Mode.SRC_OVER);
	}

	private Integer evaluate(float fraction, Object startValue, Integer endValue) {
		int startInt = (Integer) startValue;
		int startA = (startInt >> 24) & 0xff;
		int startR = (startInt >> 16) & 0xff;
		int startG = (startInt >> 8) & 0xff;
		int startB = startInt & 0xff;
		int endInt = (Integer) endValue;
		int endA = (endInt >> 24) & 0xff;
		int endR = (endInt >> 16) & 0xff;
		int endG = (endInt >> 8) & 0xff;
		int endB = endInt & 0xff;
		return (int) ((startA + (int) (fraction * (endA - startA))) << 24)
				| (int) ((startR + (int) (fraction * (endR - startR))) << 16)
				| (int) ((startG + (int) (fraction * (endG - startG))) << 8)
				| (int) ((startB + (int) (fraction * (endB - startB))));
	}

	//
	// 仅仅用于生成测试的Fragment。
	// 左边侧边滑打开的抽屉
	//
	public static class TestFragment extends Fragment {

		public static Fragment newInstance() {
			Fragment fragment = new TestFragment();
			return fragment;
		}

		@Override
		public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {

			// 仅仅显示一个TextView。
			TextView tv = new TextView(getActivity());
			tv.setTextColor(Color.WHITE);
			tv.setText("左边");
			tv.setTextSize(60.0f);
			tv.setGravity(Gravity.CENTER);

			return tv;
		}
	}
}


在自己的代码中如何使用SlideMenu本文不再详细介绍,我写的附录文章1有介绍。
如何对SlidingMenu进行改造以实现QQ样式的边侧滑抽屉技术,核心关键处就是我写在MainActivity.java里面的这部分连锁代码:

// 为SlideMenu添加QQ样式的边侧滑
	private void addQQSlideStyleSlide() {

		CanvasTransformer mTransformer = new CanvasTransformer() {

			@Override
			public void transformCanvas(Canvas canvas, float percentOpen) {

				View mainContent = mSlidingMenu.getContent();
				View leftContent = mSlidingMenu.getMenu();

				animateView(mainContent, leftContent, percentOpen);
			}
		};

		// 设置滑动菜单视图的宽度
		mSlidingMenu.setBehindOffsetRes(R.dimen.slidingmenu_offset);

		mSlidingMenu.setFadeEnabled(false);

		mSlidingMenu.setBehindCanvasTransformer(mTransformer);
	}

	private void animateView(View main, View left, float percent) {
		float f1 = 1 - percent * 0.3f;
		ViewHelper.setScaleX(main, f1);
		ViewHelper.setScaleY(main, f1);
		ViewHelper.setTranslationX(left, -left.getWidth() / 2.3f + left.getWidth() / 2.3f * percent);
		ViewHelper.setScaleX(left, 0.5f + 0.5f * percent);
		ViewHelper.setScaleY(left, 0.5f + 0.5f * percent);
		ViewHelper.setAlpha(left, percent);

		getWindow().getDecorView().getBackground().setColorFilter(evaluate(percent, Color.BLACK, Color.TRANSPARENT),
				Mode.SRC_OVER);
	}

	private Integer evaluate(float fraction, Object startValue, Integer endValue) {
		int startInt = (Integer) startValue;
		int startA = (startInt >> 24) & 0xff;
		int startR = (startInt >> 16) & 0xff;
		int startG = (startInt >> 8) & 0xff;
		int startB = startInt & 0xff;
		int endInt = (Integer) endValue;
		int endA = (endInt >> 24) & 0xff;
		int endR = (endInt >> 16) & 0xff;
		int endG = (endInt >> 8) & 0xff;
		int endB = endInt & 0xff;
		return (int) ((startA + (int) (fraction * (endA - startA))) << 24)
				| (int) ((startR + (int) (fraction * (endR - startR))) << 16)
				| (int) ((startG + (int) (fraction * (endG - startG))) << 8)
				| (int) ((startB + (int) (fraction * (endB - startB))));
	}



以下这部分代码,主要是为了实现背景逐渐点亮/逐渐暗淡的效果。

getWindow().getDecorView().getBackground().setColorFilter(evaluate(percent, Color.BLACK, Color.TRANSPARENT),
				Mode.SRC_OVER);


需要设置SlideMenu侧滑打开抽屉后的偏移距离,否则左边打开的窗口Menu将和普通的SlideMenu一样将完全展开铺满整个窗口,达不到QQ的那种各个窗口占据一般的效果。

Values/dimens.xml:

<dimen name="slidingmenu_offset">150dp</dimen>


测试的左边侧边滑打开的抽屉后,加载的Fragment使用的布局left.xml:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:background="#4FC3F7"
    android:orientation="vertical" >

    <FrameLayout
        android:id="@+id/left"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="#F44336" >
    </FrameLayout>

</LinearLayout>


代码写到这里,像QQ样式的边侧滑打开抽屉技术已经实现,但仍有一个细节未实现。QQ边侧滑打开抽屉时候,注意整个屏幕窗口内的背景图,好像是一个星空,要在自己的代码中也有这个背景,那么需要需要对我们自己的Activity略做修改,修改values目录下styles.xml文件主题,把要实现QQ边侧滑抽屉效果的Activity的Theme背景添加一张图片,图片需要事先放到drawable目录,我的这个例子,事先在drawable目录下放一张zhangphil_background.jpg图片:

<resources>

    <!--
        Base application theme, dependent on API level. This theme is replaced
        by AppBaseTheme from res/values-vXX/styles.xml on newer devices.
    -->
    <style name="AppBaseTheme" parent="android:Theme.Light">
        <!--
            Theme customizations available in newer API levels can go in
            res/values-vXX/styles.xml, while customizations related to
            backward-compatibility can go here.
        -->
    </style>

    <!-- Application theme. -->
    <style name="AppTheme" parent="AppBaseTheme">
        <!-- All customizations that are NOT specific to a particular API-level can go here. -->
    
    	<!-- zhang phil增加的背景,作为QQ样式边侧滑打开抽屉时候整个窗体背景图-->
    	<item name="android:windowBackground">@drawable/zhangphil_background</item>
    </style>

</resources>



代码运行结果(初始状态 –> 然后,从左往右的边侧滑,抽屉逐渐打开):

改进Android SlidingMenu实现QQ样式边侧滑抽屉技术

改进Android SlidingMenu实现QQ样式边侧滑抽屉技术

改进Android SlidingMenu实现QQ样式边侧滑抽屉技术



附录文章:
1,《集成Android SlidingMenu(SlideMenu)》链接地址:http://blog.csdn.net/zhangphil/article/details/44078805
2,《基于Android官方DrawerLayout实现抽屉导航菜单》链接地址:http://blog.csdn.net/zhangphil/article/details/48710453
3,《Android Material Design: NavigationView抽屉导航菜单》链接地址:http://blog.csdn.net/zhangphil/article/details/48931221