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


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

    一类是小米的单手小屏模式:将原本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开发艺术探索》

  • 相关阅读:
    小程序与普通网页的区别
    小程序的结构
    Vuex四个map的方法使用
    Vuex的基本使用
    getters的使用
    nodeffi从入门到放弃(安装篇)
    nodejs如何调用c语言 (FFI)
    解决 nodegyp 错误问题
    NodeJS 调用C++(Nodeffi)
    Electron9.x +vue+ffinapi 调用Dll动态链接库
  • 原文地址:https://www.cnblogs.com/android-blogs/p/4975221.html
Copyright © 2020-2023  润新知