博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
魅族/锤子/苹果 悬停效果的实现
阅读量:6508 次
发布时间:2019-06-24

本文共 10889 字,大约阅读时间需要 36 分钟。

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

一类是小米的单手小屏模式:将原本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, childTop116                         + 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
() {156 @Override157 public Point evaluate(float fraction, Point startValue,158 Point endValue) {159 return new Point(Math.round(startValue.x160 + (endValue.x - startValue.x) * fraction),161 Math.round(startValue.y162 + (endValue.y - startValue.y)163 * fraction));164 }165 }, start, end);166 anim.setDuration(250);167 /*监听整个动画过程,每播放一帧动画,onAnimationUpdate就会调用一次*/168 anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {169 @Override170 public void onAnimationUpdate(ValueAnimator animation) {171 /*获得动画播放过程中的Point当前值*/172 Point offset = (Point) animation.getAnimatedValue();173 setOffset(offset.x, offset.y);//根据当前Point值去requestLayout174 }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.LayoutParams11                 .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
contents = new ArrayList
(); 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开发艺术探索》

你可能感兴趣的文章
虚基类&虚继承
查看>>
SRM 670 div2 A B C div1 A(贪心,子问题合并)
查看>>
css 一些常用属性总结
查看>>
泛在电力物联网有项核心技术 你听过没有?
查看>>
构造函数
查看>>
webapi支持跨域访问
查看>>
如何学习FPGA
查看>>
IPS简单使用方法
查看>>
第八次作业
查看>>
[转载] Discrete Mathematics——02 命题逻辑等价与联接词完备
查看>>
核心动画——弹簧动画二
查看>>
db2 基础语法
查看>>
“box-shadow”属性(转)
查看>>
个人站点的日期查询
查看>>
flv FLV帧全面分析。研究心得。 视频帧结构
查看>>
php生成图片注释
查看>>
C# MVC EF框架 用事务
查看>>
jQuery基础一
查看>>
[bzoj 3534][Sdoi2014] 重建
查看>>
IPv4 forwarding is disabled. Networking will not work.
查看>>