Side-Menu源码分析讲解
2017-07-07 11:06
260 查看
Android的一份开源侧边菜单,看起来效果还不错,所以就看了一下源码。发现代码为MVP特点的处理方式。
连接地址https://github.com/Yalantis/Side-Menu.Android
展示
分析如下:
一、控件要求
a主界面布局需要使用 DrawerLayout 作为容器(DrawerLayout 用法这里不做讲解)
b基本界面使用了RevealFrameLayout作为界面更换的界面容器(一个有Reveal效果的开源控件,具有Android版本的
兼容作用),具体可见io.codetail.animation.ViewAnimationUtils中createCircularReveal方法
二、接口设计
ScreenShotable:
ContentFragment继承进行界面Bitmap的获取与返回
ViewAnimator.ViewAnimatorListener:
MainActivity主界面继承,设置动画播放时的其他操作ActionBar中Home按钮的状态处理,以及侧边菜单子项的点击
事件onSwitch的处理
三、流程讲解:
MainActivity主界面进入:见代码(具体都有注释)
主界面流程:
1、contentFragment容器的首次展示
2、setActionBar()此处进行ActionBar与DrawerLayout通过ActionBarDrawerToggle建立事件关系
3、createMenuList()进行菜单项数据的初始化
4、ViewAnimator类初始化,主要的事件都是通过ViewAnimator来回调主界面中实现的ViewAnimatorListener方法,进行界面
处理(MVP模式体现)
至此,界面初始化完成。
进行操作流程:
1、点击左上方的按钮:
触发ActionBarDrawerToggle的onDrawerSlide方法,根据偏移量slideOffset与linearLayout子菜单项的个数来进行判断,
保证viewAnimator.showMenuContent()只执行一次;显示出侧边完整的菜单
2、viewAnimator.showMenuContent()分析:代码如下(具体可见注释)
showMenuContent具体流程:
a、for循环生成对应的菜单项视图,并且缓存;
b、然后通过animatorListener.addViewToContainer回调主界面MainActivity的addViewToContainer方法,添加循环添加视图
到一个LinearLayout控件进行展示;
c、对每个Menu进行点击事件的注册:记录点击的位置到location数组,调用switchItem(之后分解)方法
d、根据Menu的初始化顺序进行delay延迟时间的计算,通过线程延时执行animateView来处理动画展示animateView通过
自定义的动画FlipAnimation来实现动画效果
FlipAnimation分解:代码如下(详细见备注)
通过重写Animation的applyTransformation方法,并且利用android.graphics.Camera(并非硬件摄像头)与Matrix实现动画Y轴翻转动画
(Camera与Matrix的使用此处不做讲解)
3、点击Menu菜单:调用switchItem方法:代码如下
(实际为ContentFragment对象,实现了该接口),最后调用hideMenuContent()方法关闭菜单,hideMenuContent与之前showMenuContent类似
4、回到MainActivity的onSwitch实现方法:见代码
执行replaceFragment方法返回一个新的ScreenShotable对象(实际为一个新的contentFragment对象)。
replaceFragment方法分解:代码如下,见备注
总结:大体的执行流程,代码上面已经分析清楚了,有不对的地方欢迎指正。这个开源的思路,代码难度并不大。
重要点在三个地方可以借鉴:
1、采用接口回调的方式来处理,这样很灵活
2、这种菜单实现思路,界面切换的过渡动画可以借鉴
3、Toolbar与自定义菜单相关联的思路
不足之处:
1、定制化的痕迹过于明显,不便于扩展
2、依附于DrawerLayout控件
连接地址https://github.com/Yalantis/Side-Menu.Android
展示
分析如下:
一、控件要求
a主界面布局需要使用 DrawerLayout 作为容器(DrawerLayout 用法这里不做讲解)
b基本界面使用了RevealFrameLayout作为界面更换的界面容器(一个有Reveal效果的开源控件,具有Android版本的
兼容作用),具体可见io.codetail.animation.ViewAnimationUtils中createCircularReveal方法
二、接口设计
ScreenShotable:
ContentFragment继承进行界面Bitmap的获取与返回
ViewAnimator.ViewAnimatorListener:
MainActivity主界面继承,设置动画播放时的其他操作ActionBar中Home按钮的状态处理,以及侧边菜单子项的点击
事件onSwitch的处理
三、流程讲解:
MainActivity主界面进入:见代码(具体都有注释)
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); //进行界面的首次替换展示,菜单对应界面的初始化 contentFragment = ContentFragment.newInstance(R.drawable.content_music); //界面展示 getSupportFragmentManager().beginTransaction() .replace(R.id.content_frame, contentFragment) .commit(); drawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout); // 设置抽屉空余处颜色 drawerLayout.setScrimColor(Color.TRANSPARENT); linearLayout = (LinearLayout) findViewById(R.id.left_drawer); linearLayout.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { //关闭抽屉 drawerLayout.closeDrawers(); } }); //此处进行ActionBar与DrawerLayout通过ActionBarDrawerToggle建立事件关系 setActionBar(); //侧滑菜单项目初始化(还不可见) createMenuList(); //通过自定义ViewAnimator进行相关的事件监控 //分别传入菜单数据list、ContentFragment(实现ScreenShotable接口)、drawerLayout、当前MainActivity对象 viewAnimator = new ViewAnimator<>(this, list, contentFragment, drawerLayout, this); }
主界面流程:
1、contentFragment容器的首次展示
2、setActionBar()此处进行ActionBar与DrawerLayout通过ActionBarDrawerToggle建立事件关系
3、createMenuList()进行菜单项数据的初始化
4、ViewAnimator类初始化,主要的事件都是通过ViewAnimator来回调主界面中实现的ViewAnimatorListener方法,进行界面
处理(MVP模式体现)
至此,界面初始化完成。
进行操作流程:
1、点击左上方的按钮:
触发ActionBarDrawerToggle的onDrawerSlide方法,根据偏移量slideOffset与linearLayout子菜单项的个数来进行判断,
保证viewAnimator.showMenuContent()只执行一次;显示出侧边完整的菜单
2、viewAnimator.showMenuContent()分析:代码如下(具体可见注释)
public void showMenuContent() { setViewsClickable(false); //菜单视图缓存清空 viewList.clear(); double size = list.size(); for (int i = 0; i < size; i++) { //进行图示绘制 View viewMenu = appCompatActivity.getLayoutInflater().inflate(R.layout.menu_list_item, null); final int finalI = i; //子菜单进行点击时间监听 viewMenu.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { int[] location = {0, 0}; v.getLocationOnScreen(location); switchItem(list.get(finalI), location[1] + v.getHeight() / 2); } }); ((ImageView) viewMenu.findViewById(R.id.menu_item_image)).setImageResource(list.get(i).getImageRes()); viewMenu.setVisibility(View.GONE); viewMenu.setEnabled(false); //把子菜单添加入缓存队列 viewList.add(viewMenu); //回调MainActivity实现的addViewToContainer方法 animatorListener.addViewToContainer(viewMenu); final double position = i; //此处是各个子菜单不同延迟delay时间进行动画展示的运算 final double delay = 3 * ANIMATION_DURATION * (position / size); //通过一个Menu一个线程的方式来延迟进行触发动画播放animateView //当最后一个Menu展示后,对传入的ContentFragment进行界面的Bitmap绘制,提供给ViewAnimationUtils使用 new Handler().postDelayed(new Runnable() { public void run() { if (position < viewList.size()) { animateView((int) position); } if (position == viewList.size() - 1) { //传入的ContentFragment是实现了screenShotable接口的,进行当前界面的Bitmap绘制 screenShotable.takeScreenShot(); setViewsClickable(true); } } }, (long) delay); } }
showMenuContent具体流程:
a、for循环生成对应的菜单项视图,并且缓存;
b、然后通过animatorListener.addViewToContainer回调主界面MainActivity的addViewToContainer方法,添加循环添加视图
到一个LinearLayout控件进行展示;
c、对每个Menu进行点击事件的注册:记录点击的位置到location数组,调用switchItem(之后分解)方法
d、根据Menu的初始化顺序进行delay延迟时间的计算,通过线程延时执行animateView来处理动画展示animateView通过
自定义的动画FlipAnimation来实现动画效果
FlipAnimation分解:代码如下(详细见备注)
public class FlipAnimation extends Animation { private final float mFromDegrees; private final float mToDegrees; private final float mCenterX; private final float mCenterY; private Camera mCamera; public FlipAnimation(float fromDegrees, float toDegrees, float centerX, float centerY) { //初始角度 mFromDegrees = fromDegrees; //最后角度 mToDegrees = toDegrees; //中心坐标 mCenterX = centerX; mCenterY = centerY; } @Override public void initialize(int width, int height, int parentWidth, int parentHeight) { super.initialize(width, height, parentWidth, parentHeight); //初始化Camera mCamera = new Camera(); } //此处为在加速器时间范围内重复调用,这样来实现动画的流畅播放 @Override protected void applyTransformation(float interpolatedTime, Transformation t) { final float fromDegrees = mFromDegrees; //根据加速器传入的interpolatedTime时间来计算动画角度变化度数 float degrees = fromDegrees + ((mToDegrees - fromDegrees) * interpolatedTime); final float centerX = mCenterX; final float centerY = mCenterY; final Camera camera = mCamera; final Matrix matrix = t.getMatrix(); //记录当前机位 与restore共同使用 camera.save(); //Y轴翻转 camera.rotateY(degrees); camera.getMatrix(matrix); //重置当前机位 camera.restore(); //通过Matrix矩阵来具体实现,设置中心点 matrix.preTranslate(-centerX, -centerY); matrix.postTranslate(centerX, centerY); } }
通过重写Animation的applyTransformation方法,并且利用android.graphics.Camera(并非硬件摄像头)与Matrix实现动画Y轴翻转动画
(Camera与Matrix的使用此处不做讲解)
3、点击Menu菜单:调用switchItem方法:代码如下
private void switchItem(Resourceble slideMenuItem, int topPosition) { this.screenShotable = animatorListener.onSwitch(slideMenuItem, screenShotable, topPosition); hideMenuContent(); }回调主界面MainActivity中实现的ViewAnimatorListener.onSwitch接口方法函数,并且返回一个ScreenShotable接口对象
(实际为ContentFragment对象,实现了该接口),最后调用hideMenuContent()方法关闭菜单,hideMenuContent与之前showMenuContent类似
4、回到MainActivity的onSwitch实现方法:见代码
@Override public ScreenShotable onSwitch(Resourceble slideMenuItem, ScreenShotable screenShotable, int position) { switch (slideMenuItem.getName()) { //第一个按钮close返回初始化的contentFragment对象 case ContentFragment.CLOSE: return screenShotable; default: //其他返回一个新的contentFragment对象 return replaceFragment(screenShotable, position); } }除了Close按钮之外
@TargetApi(21) public static SupportAnimator createCircularReveal(View view, int centerX, int centerY, float startRadius, float endRadius) { if(LOLLIPOP_PLUS) { return new SupportAnimatorLollipop(android.view.ViewAnimationUtils.createCircularReveal(view, centerX, centerY, startRadius, endRadius)); } else if(!(view.getParent() instanceof RevealAnimator)) { throw new IllegalArgumentException("View must be inside RevealFrameLayout or RevealLinearLayout."); } else { RevealAnimator revealLayout = (RevealAnimator)view.getParent(); revealLayout.setTarget(view); revealLayout.setCenter((float)centerX, (float)centerY); Rect bounds = new Rect(); view.getHitRect(bounds); ObjectAnimator reveal = ObjectAnimator.ofFloat(revealLayout, "revealRadius", new float[]{startRadius, endRadius}); reveal.addListener(getRevealFinishListener(revealLayout, bounds)); return new SupportAnimatorPreL(reveal); } } static { LOLLIPOP_PLUS = VERSION.SDK_INT >= 21; }
执行replaceFragment方法返回一个新的ScreenShotable对象(实际为一个新的contentFragment对象)。
replaceFragment方法分解:代码如下,见备注
public static SupportAnimator createCircularReveal(View view, int centerX, int centerY, float startRadius, float endRadius) { //当Android版本大于等于5.0的时候调用SupportAnimatorLollipop方法 if(LOLLIPOP_PLUS) { return new SupportAnimatorLollipop(android.view.ViewAnimationUtils.createCircularReveal(view, centerX, centerY, startRadius, endRadius)); } //如果Android版本小于5.0的时候进行传入的View的父控件判断 //这里返回activity_main.xml的布局文件,查看传入的View(content_frame) 对象的父控件io.codetail.widget.RevealFrameLayout ,会发现RevealFrameLayout是实现了RevealAnimator的,是不是很巧妙 else if(!(view.getParent() instanceof RevealAnimator)) { throw new IllegalArgumentException("View must be inside RevealFrameLayout or RevealLinearLayout."); } else { //Android小于5.0的时候调用RevealAnimator RevealAnimator revealLayout = (RevealAnimator)view.getParent(); revealLayout.setTarget(view); revealLayout.setCenter((float)centerX, (float)centerY); Rect bounds = new Rect(); view.getHitRect(bounds); ObjectAnimator reveal = ObjectAnimator.ofFloat(revealLayout, "revealRadius", new float[]{startRadius, endRadius}); reveal.addListener(getRevealFinishListener(revealLayout, bounds)); return new SupportAnimatorPreL(reveal); } } static { LOLLIPOP_PLUS = VERSION.SDK_INT >= 21; }根据备注,会发现这里就能够返回一个SupportAnimator对象到MainActivity进行圆形Reveal动画播放了,并且也实现了界面的切换
总结:大体的执行流程,代码上面已经分析清楚了,有不对的地方欢迎指正。这个开源的思路,代码难度并不大。
重要点在三个地方可以借鉴:
1、采用接口回调的方式来处理,这样很灵活
2、这种菜单实现思路,界面切换的过渡动画可以借鉴
3、Toolbar与自定义菜单相关联的思路
不足之处:
1、定制化的痕迹过于明显,不便于扩展
2、依附于DrawerLayout控件
相关文章推荐
- 第二人生的源码分析(九十六)LLMenuItemGL实现菜单的显示
- qq农场,不开通牧场也能给好友添加牧草(数据抓包分析,实现源码,图片讲解)
- Struts-menu源码分析(转贴)
- Java 容器源码分析之1.8HashMap方法讲解
- java虚拟机类加载过程内存情况底层源码分析及ClassLoader讲解
- 由浅入深讲解Javascript继承机制与simple-inheritance源码分析
- Struts-menu源码分析
- Android 7.0 Gallery图库源码分析5 - Menu(菜单栏)显示
- cocos2d-x CCMenu详细源码分析
- Android 之 三级缓存(内存!!!、本地、网络)及内存LruCache扩展 及源码分析--- 学习和代码讲解
- u-boot 源码分析讲解
- qq农场,不开通牧场也能给好友添加牧草(数据抓包分析,实现源码,图片讲解)
- 深入PHP购物车模块功能分析(函数讲解,附源码)
- 第二人生的源码分析(九十六)LLMenuItemGL实现菜单的显示
- Kettle 4.2源码分析第四讲--KettleJob机制与Database插件简介(含讲解PPT)
- 第二人生的源码分析(九十六)LLMenuItemGL实现菜单的显示
- 深入分析Linux内核源码-第五章进程调度(时间片从何而来,如何分配给进程,讲解详细)
- Kettle 4.2源码分析第四讲--KettleJob机制与Database插件简介(含讲解PPT)
- 由浅入深讲解Javascript继承机制与simple-inheritance源码分析
- HBase BlockCache机制讲解&源码分析