基于谷歌官方NavigationView实现QQ样式边侧滑抽屉样式缩进缩出
本文是三个系列文章中的第三篇,在附录文章1、2中,分别针对DrawerLayout和SlidingMenu实现了QQ样式的边侧滑抽屉样式缩进缩出。由于在实际的项目中,为了实现这种边侧滑抽屉效果,不同开发者采取的技术路线和标准控件选型不同,为了在此基础上加以改造以实现QQ样式的边侧滑缩进缩出抽屉效果,我分别写三篇文章加以说明,另外,在SlidingMenu、DrawerLayout和NavigationView基础上实现QQ样式的边侧滑抽屉缩进缩出虽实现原理大致相同,但细节地方差别还是不小,故分篇介绍。本文将在NavigationView基础上实现QQ样式的边侧滑抽屉缩进缩出技术。
具体的NavigationView使用细节请参考附录文章4。本文不再展开对NavigationView的具体使用做说明。
写一个主布局activity_main.xml布局文件:
<android.support.v4.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/drawerLayout" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@mipmap/ic_launcher"> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:background="#F44336"> <Button android:id="@+id/button" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="打开 " /> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="center" android:text="主界面" android:textSize="50sp" /> </LinearLayout> <android.support.design.widget.NavigationView android:id="@+id/navigationView" android:layout_width="wrap_content" android:layout_height="match_parent" android:layout_gravity="left" android:fitsSystemWindows="true" app:headerLayout="@layout/header" app:itemIconTint="#ff5252" app:itemTextColor="#42a5f5" app:menu="@menu/menu_main"> </android.support.design.widget.NavigationView> </android.support.v4.widget.DrawerLayout>
NavigationView需要加载一个额外的“头布局”作为头部内容,这一点儿比较常用,现在通用的设计与开发,通常会在左边边侧滑出来的菜单顶部放置用户的头像或醒目信息,res/layout/header.xml代码文件:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <ImageView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:src="@mipmap/ic_launcher" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" android:text="Zhang Phil" /> </LinearLayout>
MainActivity.java代码文件:
package zhangphil.apptest; /** * Created by Phil on 2016/4/14. */ import android.app.Activity; import android.graphics.Color; import android.graphics.PorterDuff; import android.os.Bundle; import android.support.design.widget.NavigationView; import android.support.v4.view.GravityCompat; import android.support.v4.widget.DrawerLayout; import android.util.DisplayMetrics; import android.view.Gravity; import android.view.View; import android.view.MenuItem; import android.widget.Button; import android.widget.Toast; import com.nineoldandroids.view.ViewHelper; public class MainActivity extends Activity { private DrawerLayout drawerLayout; private NavigationView navigationView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); navigationView = (NavigationView) findViewById(R.id.navigationView); drawerLayout = (DrawerLayout) findViewById(R.id.drawerLayout); drawerLayout.setScrimColor(Color.TRANSPARENT); setNavigationViewSize(navigationView, 0.6f, 0.8f); navigationView.setNavigationItemSelectedListener(new NavigationView.OnNavigationItemSelectedListener() { @Override public boolean onNavigationItemSelected(MenuItem menuItem) { menuItem.setChecked(true); // 关闭 drawerLayout.closeDrawers(); String t = menuItem.getTitle() + ""; Toast.makeText(getApplicationContext(), t, Toast.LENGTH_SHORT).show(); return true; } }); Button button = (Button) findViewById(R.id.button); button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { // 由Button也可以打开 // 从左边打开 drawerLayout.openDrawer(GravityCompat.START); } }); drawerLayout.addDrawerListener(new DrawerLayout.DrawerListener() { @Override public void onDrawerClosed(View v) { } @Override public void onDrawerOpened(View v) { } @Override public void onDrawerSlide(View drawerView, float slideOffset) { // 主体窗口 View mainFrame = drawerLayout.getChildAt(0); // 这个就是隐藏起来的边侧滑菜单栏 View leftMenu = drawerView; addQQStyleSlide(mainFrame, leftMenu, slideOffset); } @Override public void onDrawerStateChanged(int arg0) { } }); } //此处将控制NavigationView侧滑出的高度、宽度已经重心位置(居中?靠上?靠下?) private void setNavigationViewSize(NavigationView nv, float w_percent, float h_percent) { DisplayMetrics displayMetrics = getResources().getDisplayMetrics(); //宽度默认是MATCH_PARENT, //NavigationView的宽度 int width = (int) (displayMetrics.widthPixels * w_percent); //NavigationView的高度 int height = (int) (displayMetrics.heightPixels * h_percent); //高度默认是MATCH_PARENT,如果不打算打满屏幕高度:DrawerLayout.LayoutParams.MATCH_PARENT, // 那么比如可以设置成屏幕高度的80%(即0.8f) DrawerLayout.LayoutParams params = new DrawerLayout.LayoutParams(width, height); //主要要设置center,否则侧滑出来的菜单栏将从下往上绘制相应高度和宽度而不是居中 params.gravity = Gravity.START | Gravity.CENTER_VERTICAL; nv.setLayoutParams(params); } // 实现边侧滑的核心代码 private void addQQStyleSlide(View mainFrame, View leftMenu, float slideOffset) { //GAP的值决定左边侧滑出来的宽度和右边的主界面之间在侧滑过程以及侧滑结束后的间距。 //如果不设置此值或者设置为0,则将恢复成Android系统默认的样式,即侧滑出来的界面和主界面之间紧密贴在一起。 int GAP = 100; float leftScale = 0.5f + 0.5f * slideOffset; float rightScale = 1 - 0.2f * slideOffset; ViewHelper.setScaleX(leftMenu, leftScale); ViewHelper.setScaleY(leftMenu, leftScale); ViewHelper.setAlpha(leftMenu, 0.5f + 0.5f * slideOffset); ViewHelper.setTranslationX(mainFrame, (leftMenu.getMeasuredWidth() + GAP) * slideOffset); ViewHelper.setPivotX(mainFrame, 0); ViewHelper.setPivotY(mainFrame, mainFrame.getMeasuredHeight() / 2); mainFrame.invalidate(); ViewHelper.setScaleX(mainFrame, rightScale); ViewHelper.setScaleY(mainFrame, rightScale); // 该处主要是为了使背景的颜色渐变过渡。 // 如果失效,则可能是因为Android DrawerLayout的NavigationView绘制背景的图层互相之间遮盖导致。 //此处不关乎重点实现,作为代码在未来的复用,仍然保留,当然也可以删掉! getWindow().getDecorView().getBackground().setColorFilter(evaluate(slideOffset, Color.BLACK, Color.TRANSPARENT), PorterDuff.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)))); } }
NavigationView需要加载的menu菜单代码文件,res/menu/menu_main.xml代码文件:
<?xml version="1.0" encoding="utf-8"?> <menu xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="wrap_content" android:layout_height="match_parent" > <item android:checkableBehavior="single" android:title="第一组菜单"> <item android:id="@+id/open" android:icon="@mipmap/ic_launcher" android:title="打开"/> <item android:id="@+id/close" android:icon="@mipmap/ic_launcher" android:title="关闭"/> <item android:id="@+id/create" android:icon="@mipmap/ic_launcher" android:title="新建"/> </item> <item android:title="第二组菜单"> <menu> <item android:id="@+id/exit" android:icon="@mipmap/ic_launcher" android:title="退出"/> <item android:id="@+id/about" android:icon="@mipmap/ic_launcher" android:title="关于"/> </menu> </item> </menu>
代码运行结果
初始静止状态:
开始从左往右侧滑过程中的断续截图:
边侧滑结束,抽屉完全打开,静止状态:
附录文章:
1,《基于谷歌官方实现QQ样式边侧滑抽屉缩进缩出技术》链接地址:http://blog.csdn.net/zhangphil/article/details/51074334
2,《改进Android SlidingMenu实现QQ样式边侧滑抽屉技术》链接地址:http://blog.csdn.net/zhangphil/article/details/51044699
3,《基于Android官方DrawerLayout实现抽屉导航菜单》链接地址:http://blog.csdn.net/zhangphil/article/details/48710453
4,《Android Material Design: NavigationView抽屉导航菜单》链接地址:http://blog.csdn.net/zhangphil/article/details/48931221