@@ -2,7 +2,7 @@ CircularFloatingActionMenu 实现原理解析
22====================================
33> 本文为 [ Android 开源项目实现原理解析] ( https://github.com/android-cn/android-open-project-analysis ) 中 circular-foating-action-menu 部分
44> 项目地址:[ CircularFloatingActionMenu] ( https://github.com/oguzbilgener/CircularFloatingActionMenu ) ,分析的版本:[ e9ccdad] ( https://github.com/android-cn/android-open-project-demo/commit/1306e632d5a7734cd8451f4e10dff763f9ab4097 ) ,Demo 地址:[ circular-foating-action-menu] ( https://github.com/android-cn/android-open-project-demo/tree/master/CircularFloatingActionMenu-demo )
5- > 分析者:[ cpacm] ( https://github.com/cpacm ) ,校对者:[ ${校对者} ] (${校对者 Github 地址} ),校对状态:未完成
5+ > 分析者:[ cpacm] ( https://github.com/cpacm ) ,校对者:[ dkmeteor ] ( https://github.com/dkmeteor ) ,校对状态:未完成
66
77###1 . 功能介绍
88“一个灵感来自Path路径的Android上可定制圆形浮动菜单动画”
@@ -87,8 +87,11 @@ CircularFloatingActionMenu 实现原理解析
8787这样子,一个简单的案例就做好了
8888
8989![ 流程图] ( https://github.com/android-cn/android-open-project-analysis/blob/master/circular-floating-action-menu/流程图.jpg " 流程图 ")
90-
91- ###3 详细设计
90+ ###3 . 流程图
91+ ![ 设计流程图] ( https://github.com/android-cn/android-open-project-analysis/blob/master/circular-floating-action-menu/circlemenu.jpg " 流程图 ")
92+
93+ 总体的设计流程图如上图所示,中间最复杂的可能是计算view位置的地方。
94+ ###4 . 详细设计
9295
9396##SubActionButton
9497首先是构造函数
@@ -174,7 +177,7 @@ public SubActionButton(Activity activity, LayoutParams layoutParams, int theme,
174177 }
175178 }
176179```
177- 传入activity,视图特性配置,主题的id,背景图,imageview(子视图),mageview (子视图)的特性配置。用这些来配置选项按钮。
180+ 传入activity,视图特性配置,主题的id,背景图,imageview(子视图),imageview (子视图)的特性配置。用这些来配置选项按钮。
178181
179182##FloatingActionButton
180183菜单按钮其实跟选项按钮的代码模式差不多,也是由设定子视图和一个建造器组成。
@@ -189,47 +192,15 @@ public SubActionButton(Activity activity, LayoutParams layoutParams, int theme,
189192 public void setPosition(int position, FrameLayout . LayoutParams layoutParams) {
190193 int gravity;
191194 switch (position) {
192- case POSITION_TOP_CENTER :
193- gravity = Gravity . TOP | Gravity . CENTER_HORIZONTAL ;
194- break ;
195- case POSITION_TOP_RIGHT :
196- gravity = Gravity . TOP | Gravity . RIGHT ;
197- break ;
198- case POSITION_RIGHT_CENTER :
199- gravity = Gravity . RIGHT | Gravity . CENTER_VERTICAL ;
200- break ;
201- case POSITION_BOTTOM_CENTER :
202- gravity = Gravity . BOTTOM | Gravity . CENTER_HORIZONTAL ;
203- break ;
204- case POSITION_BOTTOM_LEFT :
205- gravity = Gravity . BOTTOM | Gravity . LEFT ;
206- break ;
207- case POSITION_LEFT_CENTER :
208- gravity = Gravity . LEFT | Gravity . CENTER_VERTICAL ;
209- break ;
210- case POSITION_TOP_LEFT :
211- gravity = Gravity . TOP | Gravity . LEFT ;
212- break ;
213- case POSITION_BOTTOM_RIGHT :
214- default :
215- gravity = Gravity . BOTTOM | Gravity . RIGHT ;
216- break ;
195+ ... // 具体代码请自行查看源代码
217196 }
218197 layoutParams. gravity = gravity;
219198 setLayoutParams(layoutParams);
220199 }
221200```
222201
223202将视图绑定到activity的主视图中。这样我们就能在activity的主视图中操作这个view了。
224- ``` java
225- /**
226- * Attaches it to the Activity content view with specified LayoutParams.
227- * @param layoutParams
228- */
229- public void attach(FrameLayout . LayoutParams layoutParams) {
230- ((ViewGroup )getActivityContentView()). addView(this , layoutParams);
231- }
232- ```
203+
233204
234205FloatingActionButton的建造器
235206``` java
@@ -268,111 +239,19 @@ FloatingActionMenu rightLowerMenu = new FloatingActionMenu.Builder(this)
268239* Builder(this)将activity传入menu中
269240* addSubActionView 添加选项按钮到activity的视图中。在FloatingActionMenu中管理SubActionView是一个Item的list集合,每次加一个按钮就往里面添加。Item是一个辅助类,里面包括一个视图,x坐标,y坐标,长度,宽度。
270241* setAnimationHandler 则是设定动画。
271- * attachTo是将menu与activity的视图绑定。(即把菜单按钮的视图添加到activity的视图中)
272-
273- item类
274- ``` java
275- /**
276- * A simple structure to put a view and its x, y, width and height values together
277- */
278- public static class Item {
279- public int x;
280- public int y;
281- public int width;
282- public int height;
283-
284- public View view;
285-
286- public Item (View view , int width , int height ) {
287- this . view = view;
288- this . width = width;
289- this . height = height;
290- x = 0 ;
291- y = 0 ;
292- }
293- }
294- ```
242+ * attachTo是将menu与activity的视图绑定。(即把菜单按钮的视图添加到activity的视图中)
243+
295244FloatingActionMenu类主要是管理菜单按钮和选项按钮的位置和状态(开和关)
296- (1)首先是通过view的onClick监听器来控制状态
297- ``` java
298- /**
299- * A simple click listener used by the main action view
300- */
301- public class ActionViewClickListener implements View .OnClickListener {
245+ (1)首先是通过view的onClick监听器来控制状态
302246
303- @Override
304- public void onClick (View v ) {
305- toggle(animated);
306- }
307- }
308-
309- /**
310- * Toggles the menu
311- * @param animated if true, the open/close action is executed by the current {@link MenuAnimationHandler}
312- */
313- public void toggle(boolean animated) {
314- if (open) {
315- close(animated);
316- }
317- else {
318- open(animated);
319- }
320- }
321- ```
322247(2)开关主要是两种状态,开的时候会获得菜单按钮的中心位置center(getActionViewCenter())和计算item的位置(calculateItemPositions())。然后发送动画的请求到AnimationHandler中(animationHandler.animateMenuOpening(center))。
323248``` java
324249 /**
325250 * Simply opens the menu by doing necessary calculations.
326251 * @param animated if true, this action is executed by the current {@link MenuAnimationHandler}
327252 */
328253 public void open(boolean animated) {
329- // Find the center of the action view
330- Point center = getActionViewCenter();
331- // populate destination x,y coordinates of Items
332- calculateItemPositions();
333-
334- if (animated && animationHandler != null ) {
335- // If animations are enabled and we have a MenuAnimationHandler, let it do the heavy work
336- if (animationHandler. isAnimating()) {
337- // Do not proceed if there is an animation currently going on.
338- return ;
339- }
340-
341- for (int i = 0 ; i < subActionItems. size(); i++ ) {
342- // It is required that these Item views are not currently added to any parent
343- // Because they are supposed to be added to the Activity content view,
344- // just before the animation starts
345- if (subActionItems. get(i). view. getParent() != null ) {
346- throw new RuntimeException (" All of the sub action items have to be independent from a parent." );
347- }
348- // Initially, place all items right at the center of the main action view
349- // Because they are supposed to start animating from that point.
350- FrameLayout . LayoutParams params = new FrameLayout .LayoutParams (subActionItems. get(i). width, subActionItems. get(i). height, Gravity . TOP | Gravity . LEFT );
351- params. setMargins(center. x - subActionItems. get(i). width / 2 , center. y - subActionItems. get(i). height / 2 , 0 , 0 );
352- //
353- ((ViewGroup ) getActivityContentView()). addView(subActionItems. get(i). view, params);
354- }
355- // Tell the current MenuAnimationHandler to animate from the center
356- animationHandler. animateMenuOpening(center);
357- }
358- else {
359- // If animations are disabled, just place each of the items to their calculated destination positions.
360- for (int i = 0 ; i < subActionItems. size(); i++ ) {
361- // This is currently done by giving them large margins
362- final FrameLayout . LayoutParams params = new FrameLayout .LayoutParams (subActionItems. get(i). width, subActionItems. get(i). height, Gravity . TOP | Gravity . LEFT );
363- params. setMargins(subActionItems. get(i). x, subActionItems. get(i). y, 0 , 0 );
364- subActionItems. get(i). view. setLayoutParams(params);
365- // Because they are placed into the main content view of the Activity,
366- // which is itself a FrameLayout
367- ((ViewGroup ) getActivityContentView()). addView(subActionItems. get(i). view, params);
368- }
369- }
370- // do not forget to specify that the menu is open.
371- open = true ;
372-
373- if (stateChangeListener != null ) {
374- stateChangeListener. onMenuOpened(this );
375- }
254+ ... // 具体代码请自行查看源代码
376255 }
377256```
378257其中item的x,y是记录视图的终点位置,然后经过动画把view移到x,y的位置上。
@@ -393,33 +272,7 @@ stateChangeListener为状态变化的监听器,开关都会响应相应的方
393272 * Calculates the desired positions of all items.
394273 */
395274 private void calculateItemPositions() {
396- // Create an arc that starts from startAngle and ends at endAngle
397- // in an area that is as large as 4*radius^2
398- Point center = getActionViewCenter();
399- // 内切弧形路径
400- RectF area = new RectF (center. x - radius, center. y - radius, center. x + radius, center. y + radius);
401- Path orbit = new Path ();
402- orbit. addArc(area, startAngle, endAngle - startAngle);
403-
404- PathMeasure measure = new PathMeasure (orbit, false );
405-
406- // Prevent overlapping when it is a full circle
407- int divisor;
408- if (Math . abs(endAngle - startAngle) >= 360 || subActionItems. size() <= 1 ) {
409- divisor = subActionItems. size();
410- }
411- else {
412- divisor = subActionItems. size() - 1 ;
413- }
414-
415- // Measure this path, in order to find points that have the same distance between each other
416- for (int i= 0 ; i< subActionItems. size(); i++ ) {
417- float [] coords = new float [] {0f , 0f };
418- measure. getPosTan((i) * measure. getLength() / divisor, coords, null );
419- // get the x and y values of these points and set them to each of sub action items.
420- subActionItems. get(i). x = (int ) coords[0 ] - subActionItems. get(i). width / 2 ;
421- subActionItems. get(i). y = (int ) coords[1 ] - subActionItems. get(i). height / 2 ;
422- }
275+ ... // 具体代码请自行查看源代码
423276 }
424277```
425278
@@ -435,24 +288,7 @@ stateChangeListener为状态变化的监听器,开关都会响应相应的方
435288 * @param actionType
436289 */
437290 protected void restoreSubActionViewAfterAnimation(FloatingActionMenu . Item subActionItem, ActionType actionType) {
438- FrameLayout . LayoutParams params = (FrameLayout . LayoutParams ) subActionItem. view. getLayoutParams();
439- subActionItem. view. setTranslationX(0 );
440- subActionItem. view. setTranslationY(0 );
441- subActionItem. view. setRotation(0 );
442- subActionItem. view. setScaleX(1 );
443- subActionItem. view. setScaleY(1 );
444- subActionItem. view. setAlpha(1 );
445- if (actionType == ActionType . OPENING ) {
446- // 与父视图的边框距离,只要设置左上两个方位就能确定位置
447- params. setMargins(subActionItem. x, subActionItem. y, 0 , 0 );
448- subActionItem. view. setLayoutParams(params);
449- }
450- else if (actionType == ActionType . CLOSING ) {
451- Point center = menu. getActionViewCenter();
452- params. setMargins(center. x - subActionItem. width / 2 , center. y - subActionItem. height / 2 , 0 , 0 );
453- subActionItem. view. setLayoutParams(params);
454- ((ViewGroup ) menu. getActivityContentView()). removeView(subActionItem. view);
455- }
291+ ... // 具体代码请自行查看源代码
456292 }
457293```
458294Animator属性动画以及其他动画的实现请参考我写的博客
@@ -462,21 +298,7 @@ Animator属性动画以及其他动画的实现请参考我写的博客
462298
463299
464300
465- ###4 . 杂谈
466- 动画的类型有点少,以及不支持分辨率奇葩的机型,如魅族3
467-
468-
469- ###5 . 修改完善
470- 在完成了上面 5 个部分后,移动模块顺序,将
471- ` 2. 详细设计 ` -> ` 2.1 核心类功能介绍 ` -> ` 2.2 类关系图 ` -> ` 3. 流程图 ` -> ` 4. 总体设计 `
472- 顺序变为
473- ` 2. 总体设计 ` -> ` 3. 流程图 ` -> ` 4. 详细设计 ` -> ` 4.1 类关系图 ` -> ` 4.2 核心类功能介绍 `
474- 并自行校验优化一遍,确认无误后,让` 校对 Buddy ` 进行校对,` 校对 Buddy ` 校队完成后将
475- ` 校对状态:未完成 `
476- 变为:
477- ` 校对状态:已完成 `
301+ ###5 . 杂谈
302+ 动画的类型有点少,以及在屏幕尺寸异常的机子上测试时(如mx3的1800x1080)会出现子选项偏离中心菜单键的问题,原因出在view的位置计算上,它没有考虑到一些特殊机型的机子。
478303
479- ** 完成时间**
480- - ` 两天内 ` 完成
481304
482- ** 到此便大功告成,恭喜大家^_ ^**
0 commit comments