首先介绍位置参数
View的位置top,left,right,bottom,四个参数是相对于父容器来说的。
translationX和translationY是View左上角相对于父容器的偏移量,也只能在父控件里移动,若超过范围,刚无法看到,translationX= 最后的相对于父容器的偏移量X - left,所以如果往左移,translationX为负值;translationY= 最后的相对于父容器的偏移量Y - top,所以如果往上移,translationY 为负值。
x = left + translationX;
y = top + translationY;
还有两个移动的重要参数:mScroolerX和mScrollerY
mScrollerX = 父容器的左边界-View的X,所以当View左边缘在View内容左边缘的右边时,mScrollerX为正值,否则为负值。同理,当View上边缘在View内容上边缘的下边时,mScrollerY为正值,否则为负值。
Scroller的滑动原理是将一次大的滑动分成若干次小的滑动,并在一个时间段内完成。
通过调用mScroller.startScroller()->invalidate()->导致界面重绘->View的computeScroll()方法执行,需要重写这个方法,并调用scrollTo完成一次小的滑动,并postInvalidate();
具体例子如下:
package com.luoxf.drag.view; import android.content.Context; import android.graphics.Point; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.ViewConfiguration; import android.view.WindowManager; import android.widget.RelativeLayout; import android.widget.Scroller; /** * Created by luoxf on 2015/10/27. */ public class MyRelativeLayout extends RelativeLayout{ private final String TAG = MyRelativeLayout.class.getSimpleName(); private int screenHeight; private int screenWidth; private Context mContext; private Scroller mScroller; private int mLastX; private int mLastY; private int touchSlop; //最小滑动距离 private boolean isNeedToUp = false; public MyRelativeLayout(Context context) { super(context); initView(context); } public MyRelativeLayout(Context context, AttributeSet attrs) { super(context, attrs); initView(context); } public MyRelativeLayout(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); initView(context); } private void initView(Context context) { this.mContext = context; this.mScroller = new Scroller(context); touchSlop = ViewConfiguration.get(context).getScaledTouchSlop(); WindowManager wm = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE); Point point = new Point(); wm.getDefaultDisplay().getSize(point); screenWidth = point.x; screenHeight = point.y; } //调用此方法滚动到目标位置 public void smoothScrollTo(int fx, int fy) { int dx = fx - mScroller.getFinalX(); int dy = fy - mScroller.getFinalY(); smoothScrollBy(dx, dy); } //调用此方法设置滚动的相对偏移 public void smoothScrollBy(int dx, int dy) { //设置mScroller的滚动偏移量 mScroller.startScroll(mScroller.getFinalX(), mScroller.getFinalY(), dx, dy); invalidate();//这里必须调用invalidate()才能保证computeScroll()会被调用,否则不一定会刷新界面,看不到滚动效果 } @Override public void computeScroll() { //先判断mScroller滚动是否完成 if (mScroller.computeScrollOffset()) { //这里调用View的scrollTo()完成实际的滚动 scrollTo(mScroller.getCurrX(), mScroller.getCurrY()); //必须调用该方法,否则不一定能看到滚动效果 postInvalidate(); } } @Override public boolean onTouchEvent(MotionEvent event) { int x = (int) event.getX(); int y = (int) event.getY(); switch (event.getAction()) { case MotionEvent.ACTION_MOVE: int deltaY = y - mLastY; //滑到顶部 if(mScroller.getFinalY() - deltaY >= 0) { smoothScrollTo(0, 0); isNeedToUp = false; } //下滑大于屏幕三分之一 else if(mScroller.getFinalY() - deltaY < -screenHeight / 3) { smoothScrollTo(0, -screenHeight / 3); isNeedToUp = true; } else { smoothScrollBy(0, -deltaY); isNeedToUp = true; } break; case MotionEvent.ACTION_UP: //自动滑到顶部 if(isNeedToUp) { smoothScrollTo(0, 0); } break; default: break; } mLastX = x; mLastY = y; return true; } }
布局文件:
<com.luoxf.drag.view.MyRelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/myRelativiLayout" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@android:color/black" tools:context=".MainActivity"> <RelativeLayout android:layout_width="match_parent" android:layout_height="match_parent" android:background="@android:color/white"> <TextView android:id="@+id/drag_demo" android:layout_width="wrap_content" android:layout_height="wrap_content" android:padding="10dp" android:text="ScrollDEMO" android:layout_centerInParent="true"/> </RelativeLayout> </com.luoxf.drag.view.MyRelativeLayout>