当前位置: 首页 > news >正文

魅族/锤子/苹果 悬停效果的实现

一、背景:近日研究当前主流手机的单手操作效果。

一类是小米的单手小屏模式:将原本5寸以上的屏幕缩小到3.5/4寸的大小,以方便单手操作

另外一类是魅族/锤子/苹果的 悬停效果:屏幕可以下拉到下半部分,这样单手可以方便的操作到屏幕上方区域

 

二、关于DecorView的基本概念

一、DecorView为整个Window界面的最顶层View。

二、DecorView只有一个子元素为LinearLayout。代表整个Window界面,包含通知栏,标题栏,内容显示栏三块区域。

三、LinearLayout里有两个FrameLayout子元素。

  (20)为标题栏显示界面。只有一个TextView显示应用的名称。也可以自定义标题栏,载入后的自定义标题栏View将加入FrameLayout中。

  (21)为内容栏显示界面。就是setContentView()方法载入的布局界面,加入其中。

 

DecorView的创建一般是在setContentView时完成的,具体源码在PhoneWindow的setContentView()中

1
installDecor();

  

三、悬停体验的基本设计思路:

1.获取当前Window的DecorView,并将DecorView中的所有View保存下来(其实是保存了一个LinearLayout)

2.设计一个有滚动效果的Layout——HoverLayout,支持整体Move

3.将之前从DecorView中保存下来的View,addView到第二步中有滚动效果的HoverLayout中去。

4.DecorView.removeAllViews()

5.DecorView.addView(HoverLayout)

 

四、具体的代码:

1.HoverLayout的实现:

HoverLayout继承于FrameLayout,最主要的区别于FrameLayout的地方在于

a.对FrameLayout的x,y坐标做属性动画

b.onLayout中,根据FrameLayout的x,y坐标的变化,通过child.layout更新子View的坐标

 
  1 package com.xerrard.hoverdemo;
  2 
  3 import android.animation.TypeEvaluator;
  4 import android.animation.ValueAnimator;
  5 import android.content.Context;
  6 import android.graphics.Point;
  7 import android.graphics.Rect;
  8 import android.util.AttributeSet;
  9 import android.view.Gravity;
 10 import android.view.View;
 11 import android.view.ViewConfiguration;
 12 import android.widget.FrameLayout;
 13 
 14 /**
 15  * Created by xerrard on 2015/11/3.
 16  */
 17 public class HoverLayout extends FrameLayout {
 18     private int mDefaultTouchSlop;
 19     private static int DEFAULT_CHILD_GRAVITY = Gravity.TOP | Gravity.START;
 20     private static final float DEFAULT_SPEED = 1.0f;
 21     private int mOffsetX = 0;
 22     private int mOffsetY = 0;
 23     private Rect mChildRect;
 24 
 25     public HoverLayout(Context context, AttributeSet attrs, int defStyle) {
 26         super(context, attrs, defStyle);
 27         initialize();
 28         fetchAttribute(context, attrs, defStyle);
 29     }
 30 
 31     public HoverLayout(Context context, AttributeSet attrs) {
 32         this(context, attrs, 0);
 33     }
 34 
 35     public HoverLayout(Context context) {
 36         super(context);
 37         initialize();
 38     }
 39 
 40     private void fetchAttribute(Context context, AttributeSet attrs, int defStyle) {
 41     }
 42 
 43     private void initialize() {
 44         mDefaultTouchSlop = ViewConfiguration.get(getContext())
 45                 .getScaledTouchSlop(); //获取滑动的最小距离
 46         mChildRect = new Rect();
 47     }
 48 
 49     @Override
 50     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
 51         layoutChildren(left, top, right, bottom, false /* no force left gravity */);
 52     }
 53 
 54     void layoutChildren(int left, int top, int right, int bottom,
 55                         boolean forceLeftGravity) {
 56         final int count = getChildCount();
 57 
 58         final int parentLeft = getPaddingLeft();
 59         final int parentRight = right - left - getPaddingRight();
 60 
 61         final int parentTop = getPaddingTop();
 62         final int parentBottom = bottom - top - getPaddingBottom();
 63 
 64         for (int i = 0; i < count; i++) {
 65             final View child = getChildAt(i);
 66             if (child.getVisibility() != GONE) {
 67                 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
 68 
 69                 final int width = child.getMeasuredWidth();
 70                 final int height = child.getMeasuredHeight();
 71 
 72                 int childLeft;
 73                 int childTop;
 74 
 75                 int gravity = lp.gravity;
 76                 if (gravity == -1) {
 77                     gravity = DEFAULT_CHILD_GRAVITY;
 78                 }
 79 
 80                 final int layoutDirection = getLayoutDirection();
 81                 final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
 82                 final int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK;
 83 
 84                 switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
 85                     case Gravity.CENTER_HORIZONTAL:
 86                         childLeft = parentLeft + (parentRight - parentLeft - width) / 2 +
 87                                 lp.leftMargin - lp.rightMargin;
 88                         break;
 89                     case Gravity.RIGHT:
 90                         if (!forceLeftGravity) {
 91                             childLeft = parentRight - width - lp.rightMargin;
 92                             break;
 93                         }
 94                     case Gravity.LEFT:
 95                     default:
 96                         childLeft = parentLeft + lp.leftMargin;
 97                 }
 98 
 99                 switch (verticalGravity) {
100                     case Gravity.TOP:
101                         childTop = parentTop + lp.topMargin;
102                         break;
103                     case Gravity.CENTER_VERTICAL:
104                         childTop = parentTop + (parentBottom - parentTop - height) / 2 +
105                                 lp.topMargin - lp.bottomMargin;
106                         break;
107                     case Gravity.BOTTOM:
108                         childTop = parentBottom - height - lp.bottomMargin;
109                         break;
110                     default:
111                         childTop = parentTop + lp.topMargin;
112                 }
113 
114                 //child.layout(childLeft, childTop, childLeft + width, childTop + height);
115                 mChildRect.set(childLeft, childTop, childLeft + width, childTop
116                         + height);
117                 mChildRect.offset(mOffsetX, mOffsetY);
118                 child.layout(mChildRect.left, mChildRect.top, mChildRect.right,
119                         mChildRect.bottom);
120             }
121         }
122     }
123 
124     protected int clamp(int src, int limit) {
125         if (src > limit) {
126             return limit;
127         } else if (src < -limit) {
128             return -limit;
129         }
130         return src;
131     }
132 
133     public void moveToHalf() {
134         move(0, getHeight() / 2, true);
135     }
136 
137     public void move(int deltaX, int deltaY, boolean animation) {
138         deltaX = (int) Math.round(deltaX * DEFAULT_SPEED);
139         deltaY = (int) Math.round(deltaY * DEFAULT_SPEED);
140         moveWithoutSpeed(deltaX, deltaY, animation);
141     }
142 
143     public void moveWithoutSpeed(int deltaX, int deltaY, boolean animation) {
144         int hLimit = getWidth();
145         int vLimit = getHeight();
146         int newX = clamp(mOffsetX + deltaX, hLimit);
147         int newY = clamp(mOffsetY + deltaY, vLimit);
148         if (!animation) {
149             setOffset(newX, newY);
150         } else {
151             Point start = new Point(mOffsetX, mOffsetY);
152             Point end = new Point(newX, newY);
153             /*带有线性插值器(针对x/y坐标)的属性(Point)动画*/
154             ValueAnimator anim = ValueAnimator.ofObject(
155                     new TypeEvaluator<Point>() {
156                         @Override
157                         public Point evaluate(float fraction, Point startValue,
158                                               Point endValue) {
159                             return new Point(Math.round(startValue.x
160                                     + (endValue.x - startValue.x) * fraction),
161                                     Math.round(startValue.y
162                                             + (endValue.y - startValue.y)
163                                             * fraction));
164                         }
165                     }, start, end);
166             anim.setDuration(250);
167             /*监听整个动画过程,每播放一帧动画,onAnimationUpdate就会调用一次*/
168             anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
169                 @Override
170                 public void onAnimationUpdate(ValueAnimator animation) {
171                     /*获得动画播放过程中的Point当前值*/
172                     Point offset = (Point) animation.getAnimatedValue();
173                     setOffset(offset.x, offset.y);//根据当前Point值去requestLayout
174                 }
175             });
176             anim.start();
177         }
178     }
179 
180     public void setOffsetX(int offset) {
181         mOffsetX = offset;
182         requestLayout();
183     }
184 
185     public int getOffsetX() {
186         return mOffsetX;
187     }
188 
189     public void setOffsetY(int offset) {
190         mOffsetY = offset;
191         requestLayout();
192     }
193 
194     public int getOffsetY() {
195         return mOffsetY;
196     }
197 
198     public void setOffset(int x, int y) {
199         mOffsetX = x;
200         mOffsetY = y;
201         requestLayout();
202     }
203 
204     public void goHome(boolean animation) {
205         moveWithoutSpeed(-mOffsetX, -mOffsetY, animation);
206     }
207 
208 }
 

 

2.DecorView中的View放到HoverLayout中,然后将HoverLayout更新到DecorView的代码

 
 1     private void initHoverLayout() {
 2         // setup ContainerView
 3         mContainerView = new FrameLayout(this);
 4         mContainerView.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams
 5                 .MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
 6 
 7         // setup HoverLayout
 8         mHoverLayout = new HoverLayout(this);
 9         mHoverLayout.addView(mContainerView);
10         mHoverLayout.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams
11                 .MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
12 
13 
14     }
 
 
 1     private void attachDecorToHoverLayout() {
 2         ViewGroup decor = (ViewGroup) getWindow().peekDecorView();
 3         Drawable bg= decor.getBackground();
 4         List<View> contents = new ArrayList<View>();
 5         for (int i = 0; i < decor.getChildCount(); ++i) {
 6             contents.add(decor.getChildAt(i));
 7         }
 8         decor.removeAllViews();
 9 
10         FrameLayout backgroud = new FrameLayout(this);
11         backgroud.setBackground(bg);
12         mContainerView.addView(backgroud);
13         for (View v : contents) {
14             mContainerView.addView(v, v.getLayoutParams());
15         }
16         mHoverLayout.setBackground(WallpaperManager.getInstance(this).getDrawable());
17         decor.addView(mHoverLayout);
18     }
 

3.悬停效果

执行:

1
mHoverLayout.move( 0 ,mHoverLayout.getHeight()/ 3 , true );

恢复:

1
mHoverLayout.goHome( true );

可以根据软件的设计采用按键/悬浮球/下滑等方式来触发悬停执行的代码

 

五、悬停效果的导入

1、单个Activity中导入

只需要在Activity的setContentView后执行下面方法,就可以实现悬停的效果。

1
2
initHoverLayout();
attachDecorToFlyingLayout();

 

2.系统导入

系统导入需要修改Android的源码。导入方法和单个Activity的导入类似。由于所有Window(Activity/Toast/Dialog)的setContentView,最终调用的都是Window类的setContentView。而Window类的实现类PhoneWindow类。因此我们在PhoneWindow类中的setContentView方法后执行下面方法即可。

1
2
initHoverLayout();
attachDecorToFlyingLayout();

  

 

参考资料:http://blog.csdn.net/sunny2come/article/details/8899138 Android DecorView浅析

              《Android开发艺术探索》

相关文章:

  • update information -apt authentication issue解决方法
  • OC-ARC
  • [BetterExplained]书写是为了更好的思考(转载)
  • leetcode Merge Two Sorted Lists
  • How to install sharepoint server 2010 sp2 in window 7 x64
  • 【ECJTU_ACM 11级队员2012年暑假训练赛(8) - K - A short problem】
  • 优龙FS2410开发板学习过程遇到问题总结
  • linux信号量
  • android:supportsRtl=true
  • Linux安装卸载软件
  • Swift是花拳绣腿吗?——谈谈开发语言与程序员的职业发展
  • sqlite打印结果集函数
  • Linux内核中的时间
  • 写好注释的方法小结
  • 如何把Access中数据导入Mysql中 (转)
  • JavaScript 如何正确处理 Unicode 编码问题!
  • 【跃迁之路】【733天】程序员高效学习方法论探索系列(实验阶段490-2019.2.23)...
  • download使用浅析
  • EOS是什么
  • iOS动画编程-View动画[ 1 ] 基础View动画
  • Java应用性能调优
  • js数组之filter
  • learning koa2.x
  • Mysql数据库的条件查询语句
  • React中的“虫洞”——Context
  • Redis学习笔记 - pipline(流水线、管道)
  • webpack项目中使用grunt监听文件变动自动打包编译
  • 聊聊directory traversal attack
  • 前嗅ForeSpider采集配置界面介绍
  • 微服务入门【系列视频课程】
  • 一个SAP顾问在美国的这些年
  • 云大使推广中的常见热门问题
  • 正则与JS中的正则
  • 策略 : 一文教你成为人工智能(AI)领域专家
  • ​flutter 代码混淆
  • ​TypeScript都不会用,也敢说会前端?
  • #git 撤消对文件的更改
  • #HarmonyOS:软件安装window和mac预览Hello World
  • #pragma once与条件编译
  • $(document).ready(function(){}), $().ready(function(){})和$(function(){})三者区别
  • (9)STL算法之逆转旋转
  • (附源码)springboot 基于HTML5的个人网页的网站设计与实现 毕业设计 031623
  • (简单) HDU 2612 Find a way,BFS。
  • (亲测有效)解决windows11无法使用1500000波特率的问题
  • (三)Pytorch快速搭建卷积神经网络模型实现手写数字识别(代码+详细注解)
  • (深度全面解析)ChatGPT的重大更新给创业者带来了哪些红利机会
  • (转载)利用webkit抓取动态网页和链接
  • .NET 将混合了多个不同平台(Windows Mac Linux)的文件 目录的路径格式化成同一个平台下的路径
  • .net 微服务 服务保护 自动重试 Polly
  • @kafkalistener消费不到消息_消息队列对战之RabbitMq 大战 kafka
  • [ CTF ] WriteUp- 2022年第三届“网鼎杯”网络安全大赛(白虎组)
  • [ vulhub漏洞复现篇 ] Celery <4.0 Redis未授权访问+Pickle反序列化利用
  • [20150904]exp slow.txt
  • [ASP]青辰网络考试管理系统NES X3.5
  • [BZOJ3757] 苹果树