下面通过一个例子来总结实现滑动的几种方式,例子的主要功能就是让我们的自定义View能够随着手指的移动而移动。
布局文件如下:
- <?xml version="1.0" encoding="utf-8"?>
- <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:orientation="vertical"
- android:layout_width="match_parent"
- android:layout_height="match_parent">
- <com.scu.lly.dragviewtest.view.DragView
- android:layout_width="100dp"
- android:layout_height="100dp" />
- </LinearLayout>
方式一:layout方法
在View进行绘制时,会调用onLayout()方法来设置显示的位置,因此,我们可以通过修改View的left、top、right、bottom四个属性来控制View的坐标。要控制View随手指滑动,因此需要在onTouchEvent()事件中进行滑动控制。代码如下:
- public class DragView extends View{
-
- private int mLastX;
- private int mLastY;
-
- public DragView(Context context) {
- super(context);
- init();
- }
-
- public DragView(Context context, AttributeSet attrs) {
- super(context, attrs);
- init();
- }
-
- public DragView(Context context, AttributeSet attrs, int defStyleAttr) {
- super(context, attrs, defStyleAttr);
- init();
- }
-
- private void init(){
- setBackgroundColor(Color.BLUE);
- }
-
- @Override
- public boolean onTouchEvent(MotionEvent ev) {
- int x = (int) ev.getX();
- int y = (int) ev.getY();
- switch (ev.getAction()){
- case MotionEvent.ACTION_DOWN:
- mLastX = x;
- mLastY = y;
- break;
- case MotionEvent.ACTION_MOVE:
- int offsetX = x - mLastX;
- int offsetY = y - mLastY;
-
- layout(getLeft() + offsetX, getTop() + offsetY, getRight() + offsetX, getBottom() + offsetY);
- break;
- }
- return true;
- }
- }
方式二:offsetLeftAndRight()和offsetTopAndBottom()
这两个方法其实是对上面那种layout设置方式的封装、简化,在layout中,左left、右right两个方向都是加上offsetX,上top、下bottom两个方向都是加上offsetY,为了简化设置四个方向,Android提供了offsetLeftAndRight()来代替左右方向的设置,用offsetTopAndBottom()来代替上下方向的设置。
我们只需要修改上面代码ACTION_MOVE的部分,如下:
- <span style="white-space:pre"> </span>case MotionEvent.ACTION_MOVE:
- int offsetX = x - mLastX;
- int offsetY = y - mLastY;
-
-
-
- offsetLeftAndRight(offsetX);
- offsetTopAndBottom(offsetY);
- break;
方式三:LayoutParams
LayoutParams保存了一个View的布局参数,因此我们可以通过动态改变LayoutParams中的布局参数来达到改变View的位置效果。通过getLayoutParams()方法来获取View的LayoutParams,这里获取到的LayoutParams需要根据View所在父布局的类型来设置不同的类型,比如,我们这个自定义View是放在LinearLayout中的,那么通过getLayoutParams()获取到的就是LinearLayout.LayoutParams。因此,通过getLayoutParams()获取到LayoutParams的前提就是这个View需要有一个父布局。
同样,我们只需要修改上面代码ACTION_MOVE的部分,如下:
- case MotionEvent.ACTION_MOVE:
- int offsetX = x - mLastX;
- int offsetY = y - mLastY;
- LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) getLayoutParams();
- lp.leftMargin = getLeft() + offsetX;
- lp.topMargin = getTop() + offsetY;
- setLayoutParams(lp);
- break;
可以看到,通过LayoutParams改变一个View的位置时,改变的是这个View的Margin属性,这也是为什么这种方式一定要有父布局的原因,只有有了父布局,margin属性的设置才会起作用。
对于使用LayoutParams这种方式改变View位置,如果我们不想考虑父布局的类型,还可以使用ViewGroup.MarginLayoutParams来进行设置,这样也更加方便。如下:
- case MotionEvent.ACTION_MOVE:
- int offsetX = x - mLastX;
- int offsetY = y - mLastY;
-
- ViewGroup.MarginLayoutParams lp = (ViewGroup.MarginLayoutParams) getLayoutParams();
- lp.leftMargin = getLeft() + offsetX;
- lp.topMargin = getTop() + offsetY;
- setLayoutParams(lp);
- break;
效果是一样的。
方式四:scrollTo与scrollBy
使用scrollTo()和scrollBy()方法需要注意的一点是,scrollTo()和scrollBy()方法移动的是View的content,即让View的内容移动,如果在ViewGroup中使用scrollTo()和scrollBy()方法,那么移动的将是所有的子View,如果在View中使用,那么移动的将是View的内容。例如,TextView,content就是它的文本,ImageView,content就是它的drawable对象。
因此,上面例子中我们如果直接这样使用:
scrollBy(offsetX,offsetY);
发现View并没有移动,但其实是发生了移动的,只不过此时移动的是View中的内容,而我们例子中的content什么也没有。
所以,我们要想使这个View发生移动,我们就应该在View所在的ViewGroup中使用scrollBy或scrollTo方法来进行移动。同时,使用者两个方法进行移动的时候,注意此时的坐标方向与平常是相反的,具体在《Android Scroller大揭秘》有讲解。代码如下:
- case MotionEvent.ACTION_MOVE:
-
-
-
- int offsetX = mLastX - x;
- int offsetY = mLastY - y;
-
- ((View)getParent()).scrollBy(offsetX,offsetY);
- break;
方式五:Scroller
这里,我们只是结合上面这个例子实现一个简单的功能,当我们滑动完毕抬起手指后,View自动回弹到原来的位置。代码如下:
- public class DragView extends View{
-
- private int mLastX;
- private int mLastY;
- private Scroller mScroller;
-
- public DragView(Context context) {
- super(context);
- init(context);
- }
-
- public DragView(Context context, AttributeSet attrs) {
- super(context, attrs);
- init(context);
- }
-
- public DragView(Context context, AttributeSet attrs, int defStyleAttr) {
- super(context, attrs, defStyleAttr);
- init(context);
- }
-
- private void init(Context context){
- setBackgroundColor(Color.BLUE);
- mScroller = new Scroller(context);
- }
-
- @Override
- public boolean onTouchEvent(MotionEvent ev) {
- int x = (int) ev.getX();
- int y = (int) ev.getY();
- switch (ev.getAction()){
- case MotionEvent.ACTION_DOWN:
- mLastX = x;
- mLastY = y;
- break;
- case MotionEvent.ACTION_MOVE:
-
-
-
- int offsetX = mLastX - x;
- int offsetY = mLastY - y;
-
- ((View)getParent()).scrollBy(offsetX,offsetY);
- break;
- case MotionEvent.ACTION_UP:
- View viewGroup = (View) getParent();
- mScroller.startScroll(viewGroup.getScrollX(),viewGroup.getScrollY(),-viewGroup.getScrollX(),-viewGroup.getScrollY());
-
- invalidate();
- break;
- }
- return true;
- }
-
- @Override
- public void computeScroll() {
- super.computeScroll();
- if(mScroller.computeScrollOffset()){
- ((View)getParent()).scrollTo(mScroller.getCurrX(),mScroller.getCurrY());
-
- invalidate();
- }
- }
- }
以上五种方法就是常用的滑动View的方法。还有两种方式能够控制一个View的移动效果:属性动画和使用ViewDragHelper,对于这两种方法,大家可以查阅网上资料,就不详细介绍了。
以上内容,来源于网友的总结:http://blog.youkuaiyun.com/shakespeare001/article/details/51657795
View的位置参数 top left right bottom 相对于父容器而言,top是左上角纵坐标,left是左上角横坐标,right是右下角横坐标,bottom是右下角纵坐标,所以可得出View的宽高
width = right-left
height = bottom-top
left = getLeft() right = getRight() top = getTop Bottom = getBottom();
getX/getY 返回的是相对于当前View左上角的X和Y坐标
getRawX/getRawY 返回的是相对于手机屏幕左上角的x和y坐标 140页
TouchSlop 是系统所能识别出的被认为是滑动的最小距离,它是个常量,和设备有关,不同设备上,值可能不同。
可通过ViewConfiguration.get(getContext()).getScaledTouchSlop()获取这个值。
VelocityTracker 速度追踪 用于追踪手指在滑动过程的速度,水平方向和垂直方向。
使用方法如下:
在View的onTouchEvent方法中追踪当前单击事件的速度:
VelocityTracker velocityTracker = VelocityTracker.obtain();
velocityTracker.addMovement(event);
//一段时间内手指滑过的像素数
VelocityTracker.computeCurrentVelocity(1000);
//x轴方向速度
int xVelocity = (int)velocityTracker.getXVelocity();
//y轴方向速度
int yVelocity = (int)velocityTracker.getYVelocity();
//当不需要使用时
velocityTracker.clear();
velocityTracker.recycle();
Scroller 弹性滑动对象,用于实现View的弹性滑动
使用View的scrollTo/scrollBy来进行滑动,其过程是瞬间完成的,用户体验不好,可以使用Scroller来完成。
Scroller本身无法让View弹性滑动,需要和View的computeScroll方法配合使用共同完成,典型代码是固定的。
Scroller scroller = new Scroller(mContext);
//缓慢滚动到指定位置
private void smoothScrollTo(int destX,int destY){
int scrollX = getScrollX();
int delta = destX - scrollX;
//1000ms内滑向destX,效果就是慢慢滑动
mScroller.startScroll(scrollX,0,delta,0,1000);
invalidate();
}
@override
public void computeScroll(){
if(mScroller.computeScrollOffset()){
scrollTo(mScroller.getCurrX(),mScroller.getCurrY());
postInvalidate();
}
}
View的滑动,滑动时应用的标配,例如下拉刷新,SlidingMemu,他们的基础都是滑动。
掌握滑动的方法是实现绚丽的自定义控件的基础,主要通过三种方式可以实现View的滑动。
1、通过View本身提供的scrollTo/scrollBy方法来实现滑动
2、通过动画给View施加平移效果来实现滑动
3、通过改变View的LayoutParams使得View重新布局从而实现滑动。
一一分析:
1、使用scrollTo/scrollBy
2、使用动画进行滑动,实现一个view平移,平移就是一种滑动。主要是操作View的translationX和translationY属性,既可以
使用传统的view动画,也可以采用属性动画。
3、改变布局参数是实现View滑动,即改变LayoutParams。比如想把一个Button向右平移100px,我们只需要将这个Button的LayoutParams
里的marginLeft参数的值增加100px即可.或者在Button的左边放置一个空的View,它的默认宽度为0,当需要右移动Button时,重新设置View的宽度即可。
MarginLayoutParams params = (MarginLayoutParams) mButton.getLayoutParams();
params.width += 100;
params.leftMargin +=100;
mButton.requestLayout();
//或者mButton.setLayoutParams(params);
以上三种方式对比:
1、scrollTo\srollBy方式,只能滑动View的内容,不能滑动View本身,不影响内部元素的单击事件。
2、动画,android3.0以上采用没有明显的缺点,3.0以下,均不能改变View本身的属性,如果需要用户的交互,动画做滑动合适,否则不太适合,但动画可以实现一些复杂的效果。
3、改变布局参数,操作稍微复杂,适用于有交互的View
如何实现弹性滑动呢?
共同思想:将一次大的滑动分成若干次小的滑动,并在一个时间段内完成,例如通过Scroller、Handler#postDelayed以及Thread#sleep等
以上内容来源Android开发艺术探索一书。