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 - 可视进程 该进程中的组件虽然没有和用户交互,但是仍 ...
随机推荐
- 如何快速打开.iso文件(不借助专门的工具)
如何快速打开.iso文件(不借助专门的工具) 把.iso文件重命名为.rar文件,然后解压即可
- Elasticsearch学习之ES节点类型以及各种节点的分工
ES各种节点的分工 1. 客户端节点 当主节点和数据节点配置都设置为false的时候,该节点只能处理路由请求,处理搜索,分发索引操作等,从本质上来说该客户节点表现为智能负载平衡器.独立的客户端节点在一 ...
- kafka进阶
1. kafka整体结构图 Kafka名词解释和工作方式 Producer :消息生产者,就是向kafka broker发消息的客户端. Consumer :消息消费者,向kafka broker取消 ...
- 部署OpenStack问题汇总(一)--使用packstack安装openstack:源问题的处理
在安装的过程中,遇到了源的问题,找不到包的网页: 重新打开 预装源地址,打开epel-openstack-havana.repo 文件,显示如下: # Place this file in yo ...
- jenkins部署war包到远程服务器的tomcat
一.目的 jenkins上将war包,部署到远程服务器的tomcat上. 这边tomcat在windows 主机A上,版本apache-tomcat-8.5.23. jenkins在主机B上,cent ...
- mvn deploy命令上传包
需求:有的时候需要单独上传release jar包,因为存在工程代码在A内网SVN,Nexus在B内网.这种情况下使用VPN也无法解决Jar包发布的问题. 这个时候采取的方式只能是: 打出jar包 - ...
- 用logstash,elasticSearch,kibana实现数据收集和统计分析工作
原文链接:http://www.open-open.com/lib/view/open1448799635720.html 世界上的软件80%是运行在内网的,为了使得运行在客户端的软件有良好的体验,并 ...
- Unity3D笔记 愤怒的小鸟<六> 弹弓发射小鸟
要实现的目标 实现个性化的鼠标 实现弹弓 选择小鸟.拉升弹弓.发射小鸟 弹弓橡皮筋 声音 1.实现个性化鼠标 效果 2.添加弹弓 建立两个材质 创建一个空GameObject 把两个shoot拖进来统 ...
- 转利用python实现电影推荐
“协同过滤”是推荐系统中的常用技术,按照分析维度的不同可实现“基于用户”和“基于产品”的推荐. 以下是利用python实现电影推荐的具体方法,其中数据集源于<集体编程智慧>一书,后续的编程 ...
- vue 报错./lib/html5-entities.js this relative module was not
原文参考http://www.bozhiyue.com/web/yuyan/2017/0501/1236324.html 然后就把他俩注视了,是不报错了,但是也没有运行不出来: 居然是因为早上我360 ...