Android弹性滑动的三种实现方式
引言
上一篇文章我们介绍了实现弹性滑动的三种方式,但仅仅是给出了代码片段和方法理论。今天我们结合一个具体的例子来谈一下如何使用这三种方法来实现弹性滑动。今天我们的例子是仿IOS的下拉操作,我们知道Android系统ListView之类的控件的是不存在下拉操作的,IOS系统大多数界面都可以下拉,然后缓缓恢复,今天我们的例子就是简单的仿IOS的这种效果。
一些准备工作
我们自定义了一个View,让一个LinearLayout填充这个View,模拟占满全屏的效果。XML代码如下:
<com.research.gong.android_view_research.view.PullView
android:layout_width="match_parent"
android:layout_height="wrap_content"> <LinearLayout
android:layout_width="match_parent"
android:layout_height="1000dp"
android:orientation="vertical"
android:background="#4097e6"
android:id="@+id/main"> </LinearLayout> </com.research.gong.android_view_research.view.PullView>
Scroller实现弹性滑动
我们想实现弹性滑动,第一步需要实现的就是View需要能够跟随手指滑动,这当然让我们想到了OnTouchEvent来检测用户的触摸事件。先看核心代码:
@Override
public boolean onTouchEvent(MotionEvent event) {
int y=(int)event.getY();
switch (event.getAction()){
//手指按下时,初始化按下位置的X,Y位置值
case MotionEvent.ACTION_DOWN:
mLastY=y;
break;
//计算滑动的偏移量,产生滑动效果
case MotionEvent.ACTION_MOVE:
//手指向下滑动delayY>0,向上滑动delayY<0
int delayY=y-mLastY;
delayY=delayY*-1;
scrollBy(0,delayY);
break;
case MotionEvent.ACTION_UP:
/**
* scrollY是指:View的上边缘和View内容的上边缘(其实就是第一个ChildView的上边缘)的距离
* scrollY=上边缘-View内容上边缘,scrollTo/By方法滑动的知识View的内容
* 往下滑动scrollY是负值
*/
int scrollY=getScrollY();
smoothScrollByScroller(scrollY);
//smoothScrollByAnim(scrollY);
//smoothScrollByHandler(scrollY);
break;
}
mLastY=y;
return true;
}
在代码中,我们看到当手指按下时,记录按下的位置mLastY,然后我们在ACTION_MOVE事件中不断的计算滑动的偏移量delayY然后使用scrollBy来实现View的滑动,这样我们就可以实现View跟随手指滑动而滑动。细心的朋友可能发现我手指向下滑动,delayY应该是正值,拿View向下滑动为什么需要将delayY*-1变成负数?这是因为Android系统是通过移动可视区域来实现改变View内容位置的,我们自觉上View向下滑动,对于可视区域来说是向上滑动,所以scrollBy需要使用负值,这样感官上和我们向下滑动效果是一样的,这一点需要注意。
下面我们开始分析手指放开的那一段代码逻辑,看代码的22-23行,我们先获取mScrollY的值,这个值我们在上一篇文章中已经介绍过了,是指View的上边缘和View内容上边缘的距离,其实就是我们手指释放的那一刻,滑动的总的大小。我们只需要将View缓缓划过这一段距离,就可以产生弹性滑动的效果。我们看下面的代码如何处理:
/**
* 执行滑动效果
* 使用scroller实现
* @param dy
*/
private void smoothScrollByScroller(int dy){
mScroller.startScroll(0,dy,0,dy*-1,1000);
invalidate();
} @Override
public void computeScroll() {
if (mScroller.computeScrollOffset()) {
scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
postInvalidate();
}
}
我们从代码中看到,我们使用了上一篇博客中的代码范式,将需要滑动的距离dy作为参数进行传递,然后使用startScroll方法来实现滑动。这个方法在上一篇文章中已经介绍过。
使用动画实现滑动
我们上一篇文章中还介绍了使用动画来实现弹性滑动效果,现在我们给出代码来看一下具体的实现思路:
/**
* 使用动画来实现
* @param dy
*/
private void smoothScrollByAnim(int dy){
final float delayY=dy;
ValueAnimator valueAnimator=ValueAnimator.ofInt(0,1).setDuration(1000);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
//计算动画完成的百分比
float percent=animation.getAnimatedFraction();
float dy=(1.0f-percent)*delayY;
scrollTo(0,(int)dy);
}
});
valueAnimator.start();
}
我们看到第一步也是记录滑动的总的距离,然后使用动画的addUpdateListener方法来监听动画的每一帧,然后根据执行动画的百分比来计算现在需要滑动的位置,使用scrollTo方法滑动到指定的位置。dy计算出来都是负数并且越往后,越接近0,也就是可视区域逐渐往下滑动,这样我们看起来就是View往上恢复。
使用Handler或者延时策略
下面我们介绍最后一种方法,使用延时策略来模拟Scroller。我们将1000毫秒分成50此执行,每一次延时20ms,然后在handler中根据执行的次数来计算完成的比例,然后修改View的位置实现滑动,代码如下:
private int count;
private int delayY;
/**
* 使用Handler来实现
* @param dy
*/
private void smoothScrollByHandler(int dy){
delayY=dy;
count=0;
scrollHandler.sendEmptyMessageDelayed(0,20);
} private Handler scrollHandler=new Handler(){
@Override
public void handleMessage(Message msg) {
switch (msg.what){
case 0:
count++;
if(count<=50){
float percent=count/50.0f;
int scrollY=(int)(delayY*(1.0f-percent));
Log.d("scrollY:",String.valueOf(scrollY));
scrollTo(0,scrollY);
scrollHandler.sendEmptyMessageDelayed(0,20);
}
break;
default:
break;
}
}
};
我们看到代码思路和使用动画类似。
总结
上面3种实现弹性滑动的方法,我们建议还是优先选择Scroller来实现,其他两种方法指示提供类似的思路。下面我贴出详细的代码,供各位朋友实验学习。详细代码如下:
package com.research.gong.android_view_research.view; import android.animation.ValueAnimator;
import android.content.Context;
import android.os.Handler;
import android.os.Message;
import android.util.AttributeSet;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Scroller; /**
* 模拟下拉组件
*/
public final class PullView extends ViewGroup { private int mLastY;
private Context mContext;
private Scroller mScroller;
//子View的个数
private int mChildCount; public PullView(Context context){
this(context,null);
} public PullView(Context context, AttributeSet attributeSet){
super(context,attributeSet);
mContext=context;
initView();
} private void initView(){
mScroller=new Scroller(mContext);
} @Override
public boolean onTouchEvent(MotionEvent event) {
int y=(int)event.getY();
switch (event.getAction()){
//手指按下时,初始化按下位置的X,Y位置值
case MotionEvent.ACTION_DOWN:
mLastY=y;
break;
//计算滑动的偏移量,产生滑动效果
case MotionEvent.ACTION_MOVE:
//手指向下滑动delayY>0,向上滑动delayY<0
int delayY=y-mLastY;
delayY=delayY*-1;
scrollBy(0,delayY);
break;
case MotionEvent.ACTION_UP:
/**
* scrollY是指:View的上边缘和View内容的上边缘(其实就是第一个ChildView的上边缘)的距离
* scrollY=上边缘-View内容上边缘,scrollTo/By方法滑动的知识View的内容
* 往下滑动scrollY是负值
*/
int scrollY=getScrollY();
//smoothScrollByScroller(scrollY);
//smoothScrollByAnim(scrollY);
smoothScrollByHandler(scrollY);
break;
}
mLastY=y;
return true;
} /**
* 执行滑动效果
* 使用scroller实现
* @param dy
*/
private void smoothScrollByScroller(int dy){
mScroller.startScroll(0,dy,0,dy*-1,1000);
invalidate();
} @Override
public void computeScroll() {
if (mScroller.computeScrollOffset()) {
scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
postInvalidate();
}
} /**
* 使用动画来实现
* @param dy
*/
private void smoothScrollByAnim(int dy){
final float delayY=dy;
ValueAnimator valueAnimator=ValueAnimator.ofInt(0,1).setDuration(1000);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
//计算动画完成的百分比
float percent=animation.getAnimatedFraction();
float dy=(1.0f-percent)*delayY;
scrollTo(0,(int)dy);
}
});
valueAnimator.start();
} private int count;
private int delayY;
/**
* 使用Handler来实现
* @param dy
*/
private void smoothScrollByHandler(int dy){
delayY=dy;
count=0;
scrollHandler.sendEmptyMessageDelayed(0,20);
} private Handler scrollHandler=new Handler(){
@Override
public void handleMessage(Message msg) {
switch (msg.what){
case 0:
count++;
if(count<=50){
float percent=count/50.0f;
int scrollY=(int)(delayY*(1.0f-percent));
Log.d("scrollY:",String.valueOf(scrollY));
scrollTo(0,scrollY);
scrollHandler.sendEmptyMessageDelayed(0,20);
}
break;
default:
break;
}
}
}; /**
* 重新计算子View的高度和宽度
* @param widthMeasureSpec
* @param heightMeasureSpec
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int measuredWidth;
int measureHeight;
mChildCount = getChildCount();
//测量子View
measureChildren(widthMeasureSpec, heightMeasureSpec);
int widthSpaceSize = MeasureSpec.getSize(widthMeasureSpec);
int widthSpaceMode = MeasureSpec.getMode(widthMeasureSpec);
int heightSpaceSize = MeasureSpec.getSize(heightMeasureSpec);
int heightSpaceMode = MeasureSpec.getMode(heightMeasureSpec); //获取横向的padding值
int paddingLeft=getPaddingLeft();
int paddingRight=getPaddingRight();
final View childView = getChildAt(0);
/**
* 如果子View的数量是0,就读取LayoutParams中数据
* 否则就对子View进行测量
* 此处主要是针对wrap_content这种模式进行处理,因为默认情况下
* wrap_content等于match_parent
*/
if (mChildCount == 0) {
ViewGroup.LayoutParams layoutParams=getLayoutParams();
if(layoutParams!=null){
setMeasuredDimension(layoutParams.width,layoutParams.height);
}else {
setMeasuredDimension(0, 0);
}
} else if (heightSpaceMode == MeasureSpec.AT_MOST && widthSpaceMode == MeasureSpec.AT_MOST) {
measuredWidth = childView.getMeasuredWidth() * mChildCount;
measureHeight = getChildMaxHeight();
//将两侧的padding值加上去
measuredWidth=paddingLeft+measuredWidth+paddingRight;
setMeasuredDimension(measuredWidth, measureHeight);
} else if (heightSpaceMode == MeasureSpec.AT_MOST) {
measureHeight = getChildMaxHeight();
setMeasuredDimension(widthSpaceSize, measureHeight);
} else if (widthSpaceMode == MeasureSpec.AT_MOST) {
measuredWidth = childView.getMeasuredWidth() * mChildCount;
measuredWidth=paddingLeft+measuredWidth+paddingRight;
setMeasuredDimension(measuredWidth, heightSpaceSize);
}
} /**
* 获取子View中最大高度
* @return
*/
private int getChildMaxHeight(){
int maxHeight=0;
for (int i = 0; i < mChildCount; i++) {
View childView = getChildAt(i);
if (childView.getVisibility() != View.GONE) {
int height = childView.getMeasuredHeight();
if(height>maxHeight){
maxHeight=height;
}
}
}
return maxHeight;
} /**
* 设置子View的布局
* @param changed
* @param l
* @param t
* @param r
* @param b
*/
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int childLeft = 0;
for (int i = 0; i < mChildCount; i++) {
View childView = getChildAt(i);
if (childView.getVisibility() != View.GONE) {
int childWidth = childView.getMeasuredWidth();
childView.layout(childLeft, 0, childLeft + childWidth, childView.getMeasuredHeight());
childLeft += childWidth;
}
}
}
}
Android弹性滑动的三种实现方式的更多相关文章
- Android开发——弹性滑动的两种实现方式
0. 前言 欢迎转载,转载请注明出处:http://blog.csdn.net/seu_calvin/article/details/52538723 我们在Android开发--View滑动的三 ...
- Android中的三种XML解析方式
在Android中提供了三种解析XML的方式:SAX(Simple API XML),DOM(Document Objrect Model),以及Android推荐的Pull解析方式.下面就对三种解析 ...
- Android数据的四种存储方式SharedPreferences、SQLite、Content Provider和File (三) —— SharePreferences
除了SQLite数据库外,SharedPreferences也是一种轻型的数据存储方式,它的本质是基于XML文件存储key-value键值对数据,通常用来存储一些简单的配置信息.其存储位置在/data ...
- Android平台中实现对XML的三种解析方式
本文介绍在Android平台中实现对XML的三种解析方式. XML在各种开发中都广泛应用,Android也不例外.作为承载数据的一个重要角色,如何读写XML成为Android开发中一项重要的技能. 在 ...
- (转)Android数据的四种存储方式SharedPreferences、SQLite、Content Provider和File (三) —— SharePreferences
除了SQLite数据库外,SharedPreferences也是一种轻型的数据存储方式,它的本质是基于XML文件存储key-value键值对数据,通常用来存储一些简单的配置信息.其存储位置在/data ...
- JSON的三种解析方式
一.什么是JSON? JSON是一种取代XML的数据结构,和xml相比,它更小巧但描述能力却不差,由于它的小巧所以网络传输数据将减少更多流量从而加快速度. JSON就是一串字符串 只不过元素会使用特定 ...
- Android数据的四种存储方式SharedPreferences、SQLite、Content Provider和File (四) —— ContentProvider
ContentProvider是安卓平台中,在不同应用程序之间实现数据共享的一种机制.一个应用程序如果需要让别的程序可以操作自己的数据,即可采用这种机制.并且此种方式忽略了底层的数据存储实现,Cont ...
- Android数据的四种存储方式SharedPreferences、SQLite、Content Provider和File (二) —— SQLite
SQLite是一种转为嵌入式设备设计的轻型数据库,其只有五种数据类型,分别是: NULL: 空值 INTEGER: 整数 REAL: 浮点数 TEXT: 字符串 BLOB: 大数据 在SQLite中, ...
- Service组件 总结 + 绑定理Service三种实现方式 Messager + Binder + AIDL
在Android中进程按优先级可以分为五类,优先级从高到低排列: - 前台进程 该进程包含正在与用户进行交互的界面组件,比如一个Activity - 可视进程 该进程中的组件虽然没有和用户交互,但是仍 ...
随机推荐
- nginx fastcgi配置
1.1 nginx概述nginx简介Nginx是俄罗斯人编写的十分轻量级的HTTP服务器,Nginx,它的发音为“engine X”, 是一个高性能的HTTP和反向代理服务器,同时也是一个IMAP/P ...
- WinDbg远程调试unable to initialize target machine information win32 error 0n87
Debugging Target:Windows XP SP3 32-bit Debugging Host:Windows Server 2012 64-bit 当附加到目标服务器某个进程后,WinD ...
- 分布式搜索elasticsearch几个概念解析
原文链接:http://blog.csdn.net/july_2/article/details/24367177 介绍下es的几个概念:cluster 代表一个集群,集群中有多个节点,其中有 ...
- gcd 多线程结束通知
另外,GCD还有一些高级用法,例如让后台2个线程并行执行,然后等2个线程都结束后,再汇总执行结果.这个可以用dispatch_group, dispatch_group_async 和 dispatc ...
- Coding 代码管理快速入门(转)
当项目创建好了之后,我们该如何上传代码到 coding 上呢? Coding 网站使用“ Git 仓库”(类似 github )来管理代码. 其操作原理在于:利用 git 服务,将本地的项目目录下的文 ...
- vue--自定义验证指令
参考文档: https://cn.vuejs.org/v2/guide/custom-directive.html https://www.cnblogs.com/ilovexiaoming/p/68 ...
- SSH教程从零打造在线网盘系统前言&目录
本系列教程内容提要 本系列教程是一个学习教程,是关于Java工程师的SSH(Struts2+Spring+Hibernate)系列教程,本教程将会分为四个部分和大家一同打造一个在线网盘系统,由于教程是 ...
- Mysql----索引原理与慢查询优化
一 介绍 为何要有索引? 一般的应用系统,读写比例在10:1左右,而且插入操作和一般的更新操作很少出现性能问题,在生产环境中,我们遇到最多的,也是最容易出问题的,还是一些复杂的查询操作,因此对查询语句 ...
- ZOJ 3781 - Paint the Grid Reloaded - [DFS连通块缩点建图+BFS求深度][第11届浙江省赛F题]
题目链接:http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemCode=3781 Time Limit: 2 Seconds Me ...
- HDU-2680 Choose the best route 单向边+反向dijkstra
https://vjudge.net/problem/HDU-2680 题意:以起始点 终点 长度 给出一个图,已知可以从w个起点出发,求从任一起点到同一个终点s的最短路径.注意是单向边.m<1 ...