zoukankan      html  css  js  c++  java
  • 魅族/锤子/苹果 悬停效果的实现

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

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

  • 相关阅读:
    Running ASP.NET Applications in Debian and Ubuntu using XSP and Mono
    .net extjs 封装
    ext direct spring
    install ubuntu tweak on ubuntu lts 10.04,this software is created by zhouding
    redis cookbook
    aptana eclipse plugin install on sts
    ubuntu open folderpath on terminal
    ubuntu install pae for the 32bit system 4g limited issue
    EXT Designer 正式版延长使用脚本
    用 Vagrant 快速建立開發環境
  • 原文地址:https://www.cnblogs.com/android-blogs/p/4975221.html
Copyright © 2011-2022 走看看