UC浏览器首页滑动动画实现

实现效果



UC浏览器首页效果

我们先来看下UC浏览器首页的滑动动画和我最终实现的动画效果

使用方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<cn.ittiger.ucpage.view.UCIndexView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:ucindexview="http://schemas.android.com/apk/res-auto"
android:id="@+id/ucindexview"
android:layout_width="match_parent"
android:layout_height="match_parent"
ucindexview:pageHeadViewHeight="@dimen/dp_40"
ucindexview:isPageHeadViewFixed="false"
ucindexview:isContentHeadViewEnable="true"
ucindexview:isPullRestoreEnable="false"
ucindexview:contentHeadViewHeight="@dimen/dp_40"
ucindexview:pageNavigationViewHeight="@dimen/dp_200"
ucindexview:pageHeadViewLayoutId="@layout/page_head_view_layout"
ucindexview:pageNavigationViewLayoutId="@layout/page_navigation_view_layout"
ucindexview:contentHeadViewLayoutId="@layout/content_head_view_layout"
ucindexview:contentViewLayoutId="@layout/content_view_layout">
</cn.ittiger.ucpage.view.UCIndexView>

使用方式只需要如此布局即可,没有其他的任何代码操作,非常简单。几个重要属性如下:

  1. isPageHeadViewFixed:设置PageHeadView是否固定显示(效果如上图2)
  2. isContentHeadViewEnable:设置ContentHeadView是否启用(效果如上图3)
  3. isPullRestoreEnable:设置是否可以通过下拉手势恢复到初始状态,UC首页下拉不能恢复初始状态
  4. contentHeadViewHeight:设置contentHeadView视图的高度
  5. pageNavigationViewHeight:设置PageNavigationView视图的高度
  6. pageHeadViewLayoutId:设置PagetHeadView视图的内容布局,如图中的文字UC头条布局
  7. pageNavigationViewLayoutId:设置PageNavigationView视图的内容布局,如图中的文字网址导航布局
  8. contentHeadViewLayoutId:设置ContentHeadViev视图的内容布局,如图中的文字新闻头部导航布局
  9. contentViewLayoutId:设置ContentView视图的内容布局,如图中的文字新闻内容区布局

github地址

实现及Demo地址:https://github.com/huyongli/UCIndexAnimation

实现过程

下面来讲讲我的实现过程:

首先来分析下UC首页这个动画中涉及到的元素和要点

  1. 向上滑动过程顶部的UC头条会慢慢的显示出来,我把这部分视图称为:PageHeadView(页面头部视图),很明显其初始化时是在屏幕外部的
  2. 向上滑动过程中UC漫站上面会有个新闻Tab菜单头部慢慢的显示出来,我把这部分视图称为:ContentHeadView(新闻头部视图),初始时是被ContentView遮盖,而后慢慢滑出,显然它的滑动速度是比ContentView快的
  3. 向上滑动过程中天气、搜索、网站导航会稍微往上滑动一段距离最终隐藏,我把这部分视图称为:PageNavigationView(页面导航视图),随着不断的滑动,会慢慢的被其余三个视图共同遮盖掉
  4. UC漫站整个新闻内容视图慢慢的向上滑动直至跟UC头条相接,我把这部分称为:ContentView(新闻内容视图)
  5. 根据动画效果来看,上面所说的四个不同的视图部分都是同时停止滑动,但是他们滑动的距离明显是不相同的
  6. PageHeadView滑动的距离为其自身高度,
  7. ContentHeadView的滑动距离为其自身高度与ContentView滑动的距离之和,而其相对ContentView视图的滑动距离为其自身高度
  8. ContentView滑动距离为其自身初始距离顶部的边距减去 PageHeadViewContentHeadView两者的高度
  9. PageNavigationView的滑动距离很小
  10. 上面的分析把UC首页整体划分成四个不同的View部分,另外其首页中只有ContentView、PageNavigationView两个视图会处理滑动事件

    实现技术点和细节

    根据上面的分析我们知道有三个视图是从被遮盖到显示或者是从显示到遮盖,剩下的ContentView一直是遮盖其他的视图。根据视图遮盖很容易联想到FrameLayout布局,多个FrameLayout布局叠加在一起就类似PS中的图层叠加,滑动可以看成是不同层级的FrameLayoutmarginTop不断变化的过程。

实现类结构图

我的实现中先要说明两点:

  1. 我把上滑称为展示状态或者叫Show状态
  2. 下滑称为恢复初始化状态或者叫Hide状态
    下图是我的实现类结构图:

主要类实现解析

  1. UCIndexView:模仿UC首页的最终实现,使用时在布局文件中使用此类即可
  2. PageHeadView、PageNavigationView、ContentView、ContentHeadView:这四个类的作用见我上面的分析
  3. MoveView:页面四个视图的基类,主要包含滑动过程中的一些基本属性和方法
  4. mNeedMoveHeight:视图需要滑动的距离(ContentHeadView的此属性设置为相对ContentView的滑动距离),此属性主要用来计算各自视图在滑动过程中的步长
  5. mShowStopMarginTop:视图进行Show操作时,当视图的marginTop值等于该值时,结束Show操作。此值用来确定上滑过程中,视图滑动结束时的位置。
  6. mHideStopMarginTop:视图进行Hide操作时,当视图的marginTop值等于该值时,结束Hide操作。此值用来确定下滑过程中,视图滑动结束时的位置。通常此值为视图初始化成功后的marginTop值。
  7. getMarginTop():获取视图当前的marginTop
  8. updateMarginTop(flaot step):根据当前的滑动步长更新视图的marginTop
  9. isHideFinish():判断当前视图的Hide操作是否完成
  10. isShowFinish():判断当前视图的Show操作是否完成
  11. getShowMoveStep(float step):获取视图Show过程中的实际可滑动步长,有可能计算得到的滑动步长超过了视图的可滑动距离
  12. getHideMoveStep(float step):获取视图Hide过程中的实际可滑动步长,有可能计算得到的滑动步长超过了视图的可滑动距离
  13. TouchMoveView:用来处理手指触摸事件从而滑动的视图基类

    滑动事件处理

    直接上代码:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    @Override
    public void onTouchMoveEvent(MotionEvent event) {
    switch (event.getAction()) {
    case MotionEvent.ACTION_DOWN:
    mLastTouchY = event.getRawY();
    break;
    case MotionEvent.ACTION_MOVE:
    //手指滑动过程中的滑动步长
    mDelY = event.getRawY() - mLastTouchY;
    viewMove(mDelY, mIsPullRestoreEnable);
    mLastTouchY = event.getRawY();
    break;
    case MotionEvent.ACTION_UP:
    int offset = 0;
    if(mDelY > 0) {//hide,下拉
    if(!mIsPullRestoreEnable) {//当前不允许下拉恢复
    return;
    }
    offset = mContentView.getHideOffset();
    } else {//show, 上拉
    offset = mContentView.getShowOffset();
    }
    if(offset <= mPageHeadView.getNeedMoveHeight() / 2) {//没有滑过二分之一高度
    slip(-mDelY, mIsPullRestoreEnable);
    } else {
    slip(mDelY, mIsPullRestoreEnable);
    }
    break;
    }
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
/**
* 对所有视图进行滑动操作
* @param delY 当前的滑动步长
* @param isPullRestoreEnable 是否允许下拉恢复
*/
private void viewMove(float delY, boolean isPullRestoreEnable) {
float step = Math.abs(delY);
//根据滑动距离的比例计算PageHeadView的滑动步长
float pageHeadViewStep = step * mPageHeadView.getNeedMoveHeight() / mContentView.getNeedMoveHeight();
//手指滑动距离作为ContentView的滑动步长
float contentViewStep = step;
//ContentHeadView初始不固定显示时,其实际滑动步长为ContentView的滑动步长加上其相对ContentView的滑动步长
float contentHeadViewStep = mIsContentHeadViewEnable ? step + step * mContentHeadView.getNeedMoveHeight() / mContentView.getNeedMoveHeight() : 0;
float pageNavigationViewStep = step * mPageNavigationView.getNeedMoveHeight() / mContentView.getNeedMoveHeight();
if(delY > 0) {//下滑
if(!isPullRestoreEnable) {//当前不允许下拉恢复
return;
}
if(!isHideFinish()) {//恢复状态是否已完成
if(mIsPageHeadViewFixed == false) {
mPageHeadView.onHideAnimation(pageHeadViewStep);
}
mContentView.onHideAnimation(contentViewStep);
if(mIsContentHeadViewEnable) {
mContentHeadView.onHideAnimation(contentHeadViewStep);
}
mPageNavigationView.onHideAnimation(pageNavigationViewStep);
}
} else {//上滑
if(!isShowFinish()) {//展示状态是否已完成
if(mIsPageHeadViewFixed == false) {//PageHeadView没有被固定时才进行滑动
mPageHeadView.onShowAnimation(pageHeadViewStep);
}
mContentView.onShowAnimation(contentViewStep);
if(mIsContentHeadViewEnable) {//ContentHeadView启用时才进行滑动
mContentHeadView.onShowAnimation(contentHeadViewStep);
}
mPageNavigationView.onShowAnimation(pageNavigationViewStep);
}
}
}
```
```java
/**
* 手指松开屏幕后,视图自动滑动
* 每隔 mAutoSlipTimeStep 长时间滑动 mAutoSlipStep 距离
* @param delY 当前的滑动距离
* @param isPullRestoreEnable 是否允许下拉恢复
*/
private void slip(float delY, final boolean isPullRestoreEnable) {
if(delY > 0) {//当前滑动为向下滑动,即处于恢复状态
if(isHideFinish()) {//已经恢复结束
return;
}
postDelayed(new Runnable() {
@Override
public void run() {
viewMove(mAutoSlipStep, isPullRestoreEnable);
slip(mAutoSlipStep, isPullRestoreEnable);//准备下一次滑动
}
}, mAutoSlipTimeStep);
} else {//当前滑动为向上滑动,即处于展示状态
if(isShowFinish()) {//已经展示结束
return;
}
postDelayed(new Runnable() {
@Override
public void run() {
viewMove(-mAutoSlipStep, isPullRestoreEnable);
slip(-mAutoSlipStep, isPullRestoreEnable);//准备下一次滑动
}
}, mAutoSlipTimeStep);
}
}

上面的代码中我把手指每次的滑动距离当做ContentView的滑动步长,再根据ContentView的滑动步长计算其他视图的当前滑动步长,计算具体方式可以看代码中的注释。

其实上面代码里的实现步骤还是比较清晰简单的,主要是如下几个步骤:

  1. MotionEvent.MOVE中先得到当前手指滑动的距离,此距离作为ContentView的此次滑动步长
  2. 根据ContentView的滑动步长计算其他三个视图的滑动步长
  3. 根据当前是上滑还是下滑,判断是否滑动结束,没有结束则按照计算得到的步长继续对View进行滑动处理
  4. 当手指松开后,在MotionEvent.UP中判断ContentView的滑动距离是否达到了PageHeadView高度的一半,从而决定是继续同方向滑动至结束还是恢复到原状态
  5. 手指松开后,最终调用slip方法开始自动循环滑动处理,直至滑动结束

原创文章,本文采用知识共享署名 2.5(中国大陆许可协议)进行许可,欢迎转载,但转载请注明来自ittiger.cn,并保证转载后文章内容的完整性。本人(laohu)保留所有版权相关权利。



评论