解析6种常用View 的滑动方法
View 的滑动是Android 实现自定义控件的基础,实现View 滑动有很多种方法,在这里主要讲解6 种滑动方法,分别是layout()、offsetLeftAndRight()与offsetTopAndBottom()、LayoutParams、动画、scollTo 与scollBy,以及Scroller。
View 的滑动是Android 实现自定义控件的基础,同时在开发中我们也难免会遇到View 的滑动处理。其实不管是哪种滑动方式,其基本思想都是类似的:当点击事件传到View 时,系统记下触摸点的坐标,手指移动时系统记下移动后触摸的坐标并算出偏移量,并通过偏移量来修改View 的坐标。实现View 滑动有很多种方法,在这里主要讲解6 种滑动方法,分别是layout()、offsetLeftAndRight()与offsetTopAndBottom()、LayoutParams、动画、scollTo 与scollBy,以及Scroller。
1 layout()方法
View 进行绘制的时候会调用onLayout()方法来设置显示的位置,因此我们同样也可以通过修改View 的left、top、right、bottom 这4 种属性来控制View 的坐标。首先我们要自定义一个View,在onTouchEvent()方法中获取触摸点的坐标,代码如下所示:
public boolean onTouchEvent(MotionEvent event) {
//获取手指触摸点的横坐标和纵坐标
int x = (int) event.getX();
int y = (int) event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
lastX = x;
lastY = y;
break;
接下来我们在ACTION_MOVE 事件中计算偏移量,再调用layout()方法重新放置这个自定义View 的位置即可。
case MotionEvent.ACTION_MOVE:
//计算移动的距离
int offsetX = x - lastX;
int offsetY = y - lastY;
//调用layout 方法来重新放置它的位置
layout(getLeft()+offsetX, getTop()+offsetY,getRight()+offsetX , getBottom()+offsetY);
break;
在每次移动时都会调用layout()方法对屏幕重新布局,从而达到移动View 的效果。自定义View,CustomView 的全部代码如下所示:
public class CustomView extends View {
private int lastX;
private int lastY;
public CustomView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public CustomView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public CustomView(Context context) {
super(context);
}
public boolean onTouchEvent(MotionEvent event) {
//获取手指触摸点的横坐标和纵坐标
int x = (int) event.getX();
int y = (int) event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
lastX = x;
lastY = y;
break;
case MotionEvent.ACTION_MOVE:
//计算移动的距离
int offsetX = x - lastX;
int offsetY = y - lastY;
//调用layout 方法来重新放置它的位置
layout(getLeft()+offsetX, getTop()+offsetY,getRight()+offsetX , getBottom()+offsetY);
break;
}
return true;
}
}
随后,我们在布局中引用自定义View 就可以了。
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<com.example.liuwangshu.moonviewslide.CustomView
android:id="@+id/customview"
android:layout_width="80dp"
android:layout_height="80dp"
android:layout_margin="50dp"
android:background="@android:color/holo_red_light" />
</LinearLayout>
运行程序,效果如图1 所示。图1 中的方块就是我们自定义的CustomView,它会随着我们手指的滑动改变自己的位置。
图1 View 的滑动
2 offsetLeftAndRight()与offsetTopAndBottom()
这两种方法和layout()方法的效果差不多,其使用方式也差不多。我们将ACTION_MOVE中的代码替换成如下代码:
case MotionEvent.ACTION_MOVE:
//计算移动的距离
int offsetX = x - lastX;
int offsetY = y - lastY;
//对left 和right 进行偏移
offsetLeftAndRight(offsetX);
//对top 和bottom 进行偏移
offsetTopAndBottom(offsetY);
break;
3 LayoutParams(改变布局参数)
LayoutParams 主要保存了一个View 的布局参数,因此我们可以通过LayoutParams 来改变View 的布局参数从而达到改变View 位置的效果。同样,我们将ACTION_MOVE 中的代码替换成如下代码:
LinearLayout.LayoutParams layoutParams= (LinearLayout.LayoutParams)getLayoutParams();
layoutParams.leftMargin = getLeft() + offsetX;
layoutParams.topMargin = getTop() + offsetY;
setLayoutParams(layoutParams);
因为父控件是LinearLayout,所以我们用了LinearLayout.LayoutParams。如果父控件是RelativeLayout,则要使用RelativeLayout.LayoutParams。除了使用布局的LayoutParams 外,我们还可以用ViewGroup.MarginLayoutParams 来实现:
ViewGroup.MarginLayoutParams layoutParams = (ViewGroup.MarginLayoutParams)
getLayoutParams();
layoutParams.leftMargin = getLeft() + offsetX;
layoutParams.topMargin = getTop() + offsetY;
setLayoutParams(layoutParams);
4 动画
可以采用View 动画来移动,在res 目录新建anim 文件夹并创建translate.xml:
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate
android:duration="1000"
android:fromXDelta="0"
android:toXDelta="300" />
</set>
接下来在Java 代码中调用就好了,代码如下所示:
mCustomView.setAnimation(AnimationUtils.loadAnimation(this, R.anim.translate));
运行程序,我们设置的小方块会向右平移300 像素,然后又会回到原来的位置。为了解决这个问题,我们需要在translate.xml 中加上fillAfter=”true”,代码如下所示。运行代码后会发现,方块向右平移300 像素后就停留在当前位置了。
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android" android:fillAfter="true">
<translate
android:duration="1000"
android:fromXDelta="0"
android:toXDelta="300" />
</set>
需要注意的是,View 动画并不能改变View 的位置参数。如果对一个Button 进行如上的平移动画操作,当Button 平移300 像素停留在当前位置时,我们点击这个Button 并不会触发点击事件,但在我们点击这个Button 的原始位置时却触发了点击事件。对于系统来说这个Button 并没有改变原有的位置,所以我们点击其他位置当然不会触发这个Button 的点击事件。在Android3.0 时出现的属性动画解决了上述问题,因为它不仅可以执行动画,还能够改变View 的位置参数。当然,这里使用属性动画移动那就更简单了,我们让CustomView 在1000ms 内沿着X 轴向右平移300 像素,代码如下所示。
ObjectAnimator.ofFloat(mCustomView,"translationX",0,300).setDuration(100
0).start();
5 scrollTo 与scollBy
scrollTo(x,y)表示移动到一个具体的坐标点,而scrollBy(dx,dy)则表示移动的增量为dx、dy。其中,scollBy 最终也是要调用scollTo 的。View.java 的scollBy 和scollTo 的源码如下所示:
public void scrollTo(int x, int y) {
if (mScrollX != x || mScrollY != y) {
int oldX = mScrollX;
int oldY = mScrollY;
mScrollX = x;
mScrollY = y;
invalidateParentCaches();
onScrollChanged(mScrollX, mScrollY, oldX, oldY);
if (!awakenScrollBars()) {
postInvalidateOnAnimation();
}
}
}
public void scrollBy(int x, int y) {
scrollTo(mScrollX + x, mScrollY + y);
}
scollTo、scollBy 移动的是View 的内容,如果在ViewGroup 中使用,则是移动其所有的子View。我们将ACTION_MOVE 中的代码替换成如下代码:
((View)getParent()).scrollBy(-offsetX,-offsetY);
这里若要实现CustomView 随手指移动的效果,就需要将偏移量设置为负值。为什么要设置为负值呢?下面具体讲解一下。假设我们正用放大镜来看报纸,放大镜用来显示字的内容。同样我们可以把放大镜看作我们的手机屏幕,它们都是负责显示内容的;而报纸则可以被看作屏幕下的画布,它们都是用来提供内容的。放大镜外的内容,也就是报纸的内容不会随着放大镜的移动而消失,它一直存在。同样,我们的手机屏幕看不到的视图并不代表其不存在,如图2 所示。
图2 初始情况
画布上有3 个控件,即Button、EditText 和SwichButton。只有Button 在手机屏幕中显示,它的Android 坐标为(60,60) 。现在我们调用scrollBy(50,50),按照字面的意思,这个Button 应该会在屏幕右下侧,可是事实并非如此。如果我们调用scrollBy(50,50),里面的参数都是正值,我们的手机屏幕向X 轴正方向,也就是向右边平移50,然后手机屏幕向Y 轴正方向,也就是向下方平移50,平移后的效果如图3所示。
图3 调用scrollBy(50,50)后
虽然我们设置的数值是正数并且在X 轴和Y 轴的正方向移动,但Button 却向相反方向移动了,这是参考对象不同导致的差异。所以我们用scrollBy 方法的时候要设置负数才会达到自己想要的效果。
6 Scroller
我们在用scollTo/scollBy 方法进行滑动时,这个过程是瞬间完成的,所以用户体验不大好。这里我们可以使用Scroller 来实现有过渡效果的滑动,这个过程不是瞬间完成的,而是在一定的时间间隔内完成的。Scroller 本身是不能实现View 的滑动的,它需要与View 的computeScroll()
方法配合才能实现弹性滑动的效果。在这里我们实现CustomView 平滑地向右移动。首先我们要初始化Scroller,代码如下所示:
public CustomView(Context context, AttributeSet attrs) {
super(context, attrs);
mScroller = new Scroller(context);
}
接下来重写computeScroll()方法,系统会在绘制View 的时候在draw()方法中调用该方法。在这个方法中,我们调用父类的scrollTo()方法并通过Scroller 来不断获取当前的滚动值,每滑动一小段距离我们就调用invalidate()方法不断地进行重绘,重绘就会调用computeScroll()方法,这样我们通过不断地移动一个小的距离并连贯起来就实现了平滑移动的效果。
@Override
public void computeScroll() {
super.computeScroll();
if(mScroller.computeScrollOffset()){
((View) getParent()).scrollTo(mScroller.getCurrX(),mScroller.getCurrY());
invalidate();
}
}
我们在CustomView 中写一个smoothScrollTo 方法,调用Scroller 的startScroll()方法,在2000ms 内沿X 轴平移delta 像素,代码如下所示:
public void smoothScrollTo(int destX,int destY){
int scrollX=getScrollX();
int delta=destX-scrollX;
mScroller.startScroll(scrollX,0,delta,0,2000);
invalidate();
}
最后我们在ViewSlideActivity.java 中调用CustomView 的smoothScrollTo()方法。这里我们设定CustomView 沿着X 轴向右平移400 像素。
mCustomView.smoothScrollTo(-400,0);
本文选自《Android进阶之光》,点此链接可在博文视点官网查看此书。
想及时获得更多精彩文章,可在微信中搜索“博文视点”或者扫描下方二维码并关注。
解析6种常用View 的滑动方法的更多相关文章
- CSS的5种常用的垂直居中的方法
1.绝对定位上下百分之五十然后上外边距做外边距都是他的宽高的一半 #child{ width: 200px; height: 150px; position: absolute; left: 50%; ...
- CSS的7种常用的垂直居中的方法
1.绝对定位上下百分之五十然后上外边距做外边距都是他的宽高的一半 #child{ width: 200px; height: 150px; position: absolute; left: 50%; ...
- js中几种常用的数组处理方法的总结
一.filter()用法 功能:用于筛选数组中满足条件的元素,返回一个筛选后的新数组. <script> $(function(){ var arr = [1,-2,3,4,-5]; va ...
- java十五个常用类学习及方法举例
<code class="language-java">import java.util.Scanner; import java.util.Properties; i ...
- POI使用:用poi接口不区分xls/xlsx格式解析Excel文档(41种日期格式解析方法,5种公式结果类型解析方法,3种常用数值类型精度控制办法)
一.使用poi解析excel文档 注:全部采用poi接口进行解析,不需要区分xls.xlsx格式,不需要判断文档类型. poi中的日期格式判断仅支持欧美日期习惯,对国内的日期格式并不支持判断,怎么办? ...
- iOS开发CoreAnimation解读之三——几种常用Layer的使用解析
iOS开发CoreAnimation解读之三——几种常用Layer的使用解析 一.CAEmitterLayer 二.CAGradientLayer 三.CAReplicatorLayer 四.CASh ...
- android中实现view可以滑动的六种方法
在android开发中,经常会遇到一个view需要它能够支持滑动的需求.今天就来总结实现其滑动的六种方法.其实每一种方法的 思路都是一样的,即:监听手势触摸的坐标来实现view坐标的变化,从而实现vi ...
- QF——UI之几种常用的隐藏键盘的方法
怎么在填写完UITextField之后,点击空白处,隐藏软键盘. 下面两个方法都可以隐藏键盘 [tf resignFirstResponder]; 停止textfield的第一响应者 [self.vi ...
- js跨域请求数据的3种常用的方法
由于js同源策略的影响,当在某一域名下请求其他域名,或者同一域名,不同端口下的url时,就会变成不被允许的跨域请求.那这个时候通常怎么解决呢,对此菜鸟光头我稍作了整理:1.JavaScript 在 ...
随机推荐
- powershell 设置环境变量 -- go 单元测试 exit status 3221225781
执行单元测试时出错 go test -run TestImage 错误提示如下: exit status 3221225781 这个错误的意思是需要加载对应的库文件找不到,加载对应的库文件就习. 但是 ...
- rhel7配置链路聚合(双网卡热备)
team方法 1). nmcli connection add type team con-name team0 ifname team0 config '{"runer":{&q ...
- MySQL 删除重复记录
==========A really easy way to do this is to add a UNIQUE index on the 3 columns. When you write the ...
- storm(一) window机制
Watermark作用 在解释storm的window之前先说明一下watermark原理. Watermark中文翻译为水位线更为恰当. 顺序的数据从源头开始发送到到操作,中间过程肯定会出现数据乱序 ...
- 第九篇:Spark SQL 源码分析之 In-Memory Columnar Storage源码分析之 cache table
/** Spark SQL源码分析系列文章*/ Spark SQL 可以将数据缓存到内存中,我们可以见到的通过调用cache table tableName即可将一张表缓存到内存中,来极大的提高查询效 ...
- Java常用的几种线程池
常用的几种线程池 5.1 newCachedThreadPool 创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程. 这种类型的线程池特点是: 工作线程的创 ...
- spring-boot 加入拦截器Interceptor
1.spring boot拦截器默认有 HandlerInterceptorAdapter AbstractHandlerMapping UserRoleAuthorizationIntercepto ...
- tp5.1升级
# php think version v5.1.23 # composer update Loading composer repositories with package information ...
- SQL使用CASE 语句
CASE 语句可以在SELECT 子句和ORDER BY 子句中使用 CASE语句分为两种Case Simple Expression and Case Search Expression Case ...
- flask学习(三):flask入门(URL)
一. flask简介 flask是一款非常流行的python web框架,出生于2010年,作者是Armin Ronacher,本来这个项目只是作者在愚人节的一个玩笑,后来由于非常受欢迎,进而成为一个 ...