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主界面进入:见代码(具体都有注释)

@Overrideprotected 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() {@Overridepublic 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() {@Overridepublic 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;}@Overridepublic void initialize(int width, int height, int parentWidth, int parentHeight) {super.initialize(width, height, parentWidth, parentHeight);//初始化CameramCamera = new Camera();}//此处为在加速器时间范围内重复调用,这样来实现动画的流畅播放@Overrideprotected 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实现方法:见代码

@Overridepublic 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的时候调用RevealAnimatorRevealAnimator 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控件

Side-Menu源码分析讲解相关推荐

  1. u-boot 源码分析讲解

    简介===> 1.U-Boot系统加载器 U-Boot是一个规模庞大的开源Bootloader软件,最初是由denx(www.denx.de)发起.U-Boot的前身是PPCBoot,目前是So ...

  2. AMCL源码架构讲解与详细分析

    ROS进阶教程(三)AMCL源码分析 AMCL算法简介 AMCL包结构与通信 CmakeLists研究 体系结构与研究 节点文件函数讲解 订阅话题函数 scan_topic initial_pose ...

  3. 详细讲解:RocketMQ的限时订单实战与RocketMQ的源码分析!

    目录 一.限时订单实战 1.1.什么是限时订单 1.2.如何实现限时订单 1.2.1.限时订单的流程 1.2.2.限时订单实现的关键 1.2.3.轮询数据库? 1.2.4.Java 本身的提供的解决方 ...

  4. Mybatis源码分析: MapperMethod功能讲解

    canmengqian </div><!--end: blogTitle 博客的标题和副标题 --> <div id="navigator"> ...

  5. Java设计模式——工厂模式讲解以及在JDK中源码分析

    需求:便于手机种类的扩展 手机的种类很多(比如HuaWeiPhone.XiaoMiPhone等) 手机的制作有prepare,production, assemble, box 完成手机店订购功能. ...

  6. 【Android SDM660源码分析】- 01 - 如何创建 UEFI XBL Protocol DXE_DRIVER 驱动及UEFI_APPLICATION 应用程序

    [Android SDM660源码分析]- 01 - 如何创建 UEFI XBL Protocol DXE_DRIVER 驱动及UEFI_APPLICATION 应用程序 一.创建DXE_DRIVER ...

  7. android源码分析

    01_Android系统概述 02_Android系统的开发综述 03_Android的Linux内核与驱动程序 04_Android的底层库和程序 05_Android的JAVA虚拟机和JAVA环境 ...

  8. FreeCAD源码分析:FreeCADGui模块

    FreeCAD源码分析:FreeCADGui模块 济南友泉软件有限公司 FreeCADGui项目实现了界面操作.模型显示与交互等相关功能,项目构建生成FreeCAD(_d).dll动态链接库. Fre ...

  9. Android上百实例源码分析以及开源分析集合打包

    感谢网友banketree的收集,压缩包的内容如下: 1.360新版特性界面源代码 实现了360新版特性界面的效果,主要涉及到Qt的一些事件处理与自定义控件.但源码好像是c++. 2.aidl跨进程调 ...

最新文章

  1. c语言的特点能够编制出复杂的功能程序,以下不是C语言的特点的是()
  2. 好书征集第2弹 | 你pick哪本人工智能好书
  3. docker管理平台 shipyard安装
  4. 大数据和人工智能的关系,超全解析
  5. Arduino--超声波
  6. Vue + ESLint——编译错误[‘xxx‘ is defined but never used]解决方案
  7. 安卓5.0以上设备最简单激活XPOSED框架的步骤
  8. Android中异步任务AsyncTask的使用
  9. 忆阻器的matlab建模_忆阻器Simulink建模和图形用户界面设计.PDF
  10. Hadoop较全面的概述
  11. 父进程给子进程传参数
  12. 多模块顺序_软件架构基础 3: 什么是好的模块化代码?高内聚、低耦合如何衡量?...
  13. Win7 XAMPP apache无法启动的问题
  14. vue-i18n使用ES6语法以及空格换行问题
  15. PikPak磁力网盘
  16. cad 打开硬件加速卡_CAD:“你的图纸缺少shx字体!”不知道该怎么办?不存在的!...
  17. html中hr标签有哪些属性,hr标签的属性有哪些?
  18. c语言srand函数作用,C语言中srand随机函数怎么用?
  19. Lake Shore Cernox低温温度传感器之温度探头
  20. WIN10插入耳机没声音,Realtek音频管理器打不开

热门文章

  1. 数学与计算机学院女生节标语,女生节 | 各学院写给小仙女们的专属情书
  2. 第02章 Tableau连接数据源
  3. 如何有效选择一款移动考勤管理系统
  4. 社区团购微信小程序的设计与实现
  5. halfstone 原理_half of --中的一半.当它所指代的是不可数名词时.代表单数.如果half of 后边所接的是可数名词的复数.那么它所代表的也是复数概念....
  6. Starling Button
  7. 每日学一个设计模式8——抽象工厂模式
  8. 应急/linux 挂D盾扫描方法
  9. 日立电梯与招商蛇口签订2020-2022年全国范围电梯战略采购合作
  10. 智能家居真的做到全屋智能了吗?细数一下智能家居的痛点