實現滑動的最樸素直接的方式就是使用View類自帶的scrollTo/scrollBy方法了。
能夠直接linearLayout.getScaleY(); 和 查看 mScrollX 變量,源碼裏面看註釋就能夠發現:
mScrollX:表示離視圖起始位置的x水平方向的偏移量
mScrollY:表示離視圖起始位置的y垂直方向的偏移量
分別經過getScrollX() 和getScrollY()方法得到。
注意:mScrollX和mScrollY指的並非座標,而是偏移量。java
public void scrollTo(int x, int y) { if (mScrollX != x || mScrollY != y) { int oldX = mScrollX; int oldY = mScrollY; mScrollX = x; mScrollY = y; onScrollChanged(mScrollX, mScrollY, oldX, oldY); if (!awakenScrollBars()) { invalidate(); } } } public void scrollBy(int x, int y) { scrollTo(mScrollX + x, mScrollY + y); }
從以上的代碼能夠看出,scrollTo 和 scrollBy區別,其實2者的效果是同樣的。android
scrollTo(int x,int y):
若是偏移位置發生了改變,就會給mScrollX和mScrollY賦新值,改變當前位置。
注意:x,y表明的不是座標點,而是偏移量。
例如:
我要移動view到座標點(100,100),那麼個人偏移量就是(0,,0) -(100,100) = (-100,-100),我就要執行view.scrollTo(-100,-100),達到這個效果。ide
scrollBy(int x,int y):
從源碼中看出,它其實是調用了scrollTo(mScrollX + x, mScrollY + y);mScrollX + x和mScrollY + y,即表示在原先偏移的基礎上在發生偏移,通俗的說就是相對咱們當前位置偏移。佈局
scrollTo方法是滑動到指定位置,而scrollBy方法是滑動指定的位移量,在原先的基礎上發生偏移。
根據父類VIEW裏面移動,若是移動到了超出的地方,就不會顯示。post
代碼以下 :動畫
咱們瞭解到使用scrollTo/scrollBy方法實現View的滑動是很簡單直接的,那麼簡單的背後有什麼代價呢?代價就是滑動不是「彈性的」,能夠用個定時器不停的調用,讓它開起來是順滑的。this
使用動畫來實現View的滑動主要經過改變View的translationX和translationY參數來實現,使用動畫的好處在於滑動效果是平滑的。
上面咱們提到過,View的x、y參數決定View的當前位置,經過改變translationX和translationY,咱們就能夠改變View的當前位置。
咱們可使用屬性動畫或者補間動畫來實現View的平移。
使用動畫的實現方式比較簡單,下面就使用間補動畫和屬性動畫作個案例,以下 : spa
首先,咱們先來看一下如何使用補間動畫來實現View的平移。補間動畫資源定義以下(anim.xml):code
<?xml version="1.0" encoding="utf-8"?> <set xmlns:android="http://schemas.android.com/apk/res/android" android:fillAfter="true"> <translate android:duration="100" android:fromXDelta="0" android:fromYDelta="0" android:interpolator="@android:anim/linear_interpolator" android:toXDelta="100" android:toYDelta="100"/> </set>
而後在onCreat方法中調用startAnimation方法便可。使用補間動畫實現View的滑動有一個缺陷,那就是移動的知識View的「影像」,這意味着其實View並未真正的移動,只是咱們看起來它移動了而已。拿Button來舉例,倘若咱們經過補間動畫移動了一個Button,咱們會發現,在Button的原來位置點擊屏幕會出發點擊事件,而在移動後的Button上點擊不會觸發點擊事件。xml
接下來,咱們看看如何用屬性動畫來實現View的平移。使用屬性動畫實現View的平移更加簡單,只須要如下一條語句:
button.setOnClickListener((v) -> { float translationX = (int) v.getTranslationX(); ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(v, "translationX", translationX, -100, translationX); objectAnimator.setDuration(3000); objectAnimator.start(); });
以上代碼即實現了使用屬性動畫把targetView在3秒內向右平移100px。使用屬性動畫的限制在於真正的屬性動畫只能夠在Android 3.0+使用(一些第三方庫實現的兼容低版本的屬性動畫不是真正的屬性動畫),優勢就是它能夠真正的移動View而不是僅僅移動View的影像。
通過以上的描述,使用屬性動畫實現View的滑動看起來是個不錯的選擇,並且一些View的複雜的滑動效果只有經過動畫才能比較方便的實現。
屬性動畫實現效果以下 : (其實很順滑的,只是錄製效果有點差。)
經過改變佈局參數來實現View的滑動的思想很簡單:
好比向右移動一個View,只須要把它的marginLeft參數增大,向其它方向移動同理,只需改變相應的margin參數。
還有一種比較拐彎抹角的方法是在要移動的View的旁邊預先放一個View(初始寬高設爲0)。
而後好比咱們要向右移動View,只需把預先放置的那個View的寬度增大,這樣就把View「擠」到右邊了。代碼示例以下:
LinearLayout.LayoutParams lps = (LinearLayout.LayoutParams) v.getLayoutParams(); lps.leftMargin += 100; v.requestLayout();
以上代碼即實現了把mButton向右滑動100px。經過改變佈局參數來實現的滑動效果也不是平滑的。
Android裏Scroller類是爲了實現View平滑滾動的一個Helper類。一般在自定義的View時使用,在View中定義一個私有成員mScroller = new Scroller(context)。設置mScroller滾動的位置時,並不會致使View的滾動,一般是用mScroller記錄/計算View滾動的位置,再重寫View的computeScroll(),完成實際的滾動。 相關API介紹以下
mScroller.getCurrX() //獲取mScroller當前水平滾動的位置 mScroller.getCurrY() //獲取mScroller當前豎直滾動的位置 mScroller.getFinalX() //獲取mScroller最終中止的水平位置 mScroller.getFinalY() //獲取mScroller最終中止的豎直位置 mScroller.setFinalX(int newX) //設置mScroller最終停留的水平位置,沒有動畫效果,直接跳到目標位置 mScroller.setFinalY(int newY) //設置mScroller最終停留的豎直位置,沒有動畫效果,直接跳到目標位置 //滾動,startX, startY爲開始滾動的位置,dx,dy爲滾動的偏移量, duration爲完成滾動的時間 mScroller.startScroll(int startX, int startY, int dx, int dy) //使用默認完成時間250ms mScroller.startScroll(int startX, int startY, int dx, int dy, int duration) mScroller.computeScrollOffset() //返回值爲boolean,true說明滾動還沒有完成,false說明滾動已經完成。這是一個很重要的方法,一般放在View.computeScroll()中,用來判斷是否滾動是否結束。
舉例說明,自定義一個CustomView,使用Scroller實現滾動:
import android.content.Context; import android.util.AttributeSet; import android.util.Log; import android.view.View; import android.widget.LinearLayout; import android.widget.Scroller; public class CustomView extends LinearLayout { private static final String TAG = "Scroller"; private Scroller mScroller; public CustomView(Context context, AttributeSet attrs) { super(context, attrs); mScroller = new Scroller(context); } //調用此方法滾動到目標位置 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(); } super.computeScroll(); } }
效果圖以下 :
ViewDragHelper從名稱上就能夠看出, 這是一個用來簡化view拖拽操做的幫助類。並且使用起來也很簡單, 很方便,只須要幾個方法和1個Callback就能夠實現一個能夠拖動到view。
須要注意個是:ViewDragHelper是做用在一個ViewGroup上,也就是說他不能直接做用到被拖拽的view, 其實這也很好理解,由於view在佈局中的位置是父ViewGroup決定的。
如何使用ViewGroup實現一個能夠拖動的view?
一、獲取ViewDragHelper的實例,注意,這裏不能直接new,而是使用ViewDragHelper的一個靜態方法:
ViewDragHelper.create(ViewGroup forParent, float sensitivity, ViewDragHelper.Callback cb);
參數1: 一個ViewGroup, 也就是ViewDragHelper將要用來拖拽誰下面的子view
參數2:靈敏度,通常設置爲1.0f就行
參數3:一個回調,用來處理拖動到位置
二、繼承ViewDragHelper.Callback類,該類有個抽象方法:tryCaptureView(View view, int pointerId) 表示嘗試捕獲子view,這裏必定要返回true, 返回true表示容許。
三、重寫兩個方法int clampViewPositionHorizontal(View child, int left, int dx)和int clampViewPositionHorizontal(View child, int left, int dx) 這兩個方法分別用來處理x方向和y方向的拖動的,返回值該child如今的位置。
四、重寫ViewGroup的onInterceptTouchEvent(MotionEvent ev)用來攔截事件
五、重寫ViewGroup的onTouchEvent(MotionEvent event) 在這裏面只要作兩件事:mDragHelper.processTouchEvent(event);處理攔截到的事件,這個方法會在返回前分發事件;return true 表示消費了事件。
簡單的5步就能夠實現一個能夠任意拖動到view了,固然咱們須要在clampViewPositionHorizontal和clampViewPositionHorizontal中作一點工做,以防止view超出了邊界。
下面咱們本身定義一個CustomViews來實現能夠隨意拖動的View
public class CustomViews extends LinearLayout { private ViewDragHelper mDragHelper; public CustomViews(Context context) { super(context); init(); } public CustomViews(Context context, AttributeSet attrs) { super(context, attrs); init(); } public CustomViews(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); init(); } private void init() { /** * @params ViewGroup forParent 必須是一個ViewGroup * @params float sensitivity 靈敏度 * @params Callback cb 回調 */ mDragHelper = ViewDragHelper.create(this, 1.0f, new ViewDragCallback()); } private class ViewDragCallback extends ViewDragHelper.Callback { /** * 嘗試捕獲子view,必定要返回true * * @param view 嘗試捕獲的view * @param pointerId 指示器id? * 這裏能夠決定哪一個子view能夠拖動 */ @Override public boolean tryCaptureView(View view, int pointerId) { return true; } /** * 處理水平方向上的拖動 * * @param child 被拖動到view * @param left 移動到達的x軸的距離 * @param dx 建議的移動的x距離 */ @Override public int clampViewPositionHorizontal(View child, int left, int dx) { System.out.println("left = " + left + ", dx = " + dx); // 兩個if主要是爲了讓viewViewGroup裏 if (getPaddingLeft() > left) { return getPaddingLeft(); } if (getWidth() - child.getWidth() < left) { return getWidth() - child.getWidth(); } return left; } /** * 處理豎直方向上的拖動 * * @param child 被拖動到view * @param top 移動到達的y軸的距離 * @param dy 建議的移動的y距離 */ @Override public int clampViewPositionVertical(View child, int top, int dy) { // 兩個if主要是爲了讓viewViewGroup裏 if (getPaddingTop() > top) { return getPaddingTop(); } if (getHeight() - child.getHeight() < top) { return getHeight() - child.getHeight(); } return top; } /** * 當拖拽到狀態改變時回調 * * @params 新的狀態 */ @Override public void onViewDragStateChanged(int state) { switch (state) { case ViewDragHelper.STATE_DRAGGING: // 正在被拖動 break; case ViewDragHelper.STATE_IDLE: // view沒有被拖拽或者 正在進行fling/snap break; case ViewDragHelper.STATE_SETTLING: // fling完畢後被放置到一個位置 break; } super.onViewDragStateChanged(state); } } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { switch (ev.getAction()) { case MotionEvent.ACTION_CANCEL: case MotionEvent.ACTION_DOWN: mDragHelper.cancel(); // 至關於調用 processTouchEvent收到ACTION_CANCEL break; } /** * 檢查是否能夠攔截touch事件 * 若是onInterceptTouchEvent能夠return true 則這裏return true */ return mDragHelper.shouldInterceptTouchEvent(ev); } @Override public boolean onTouchEvent(MotionEvent event) { /** * 處理攔截到的事件 * 這個方法會在返回前分發事件 */ mDragHelper.processTouchEvent(event); return true; } } XML 使用以下 : <com.and.mvp.base.ganhuoi.module.test.CustomViews android:id="@+id/customViews" android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="center" android:orientation="vertical"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="移動我" /> </com.and.mvp.base.ganhuoi.module.test.CustomViews>
效果圖 以下 :
其實這兩個方法分別是對左右移動和上下移動的封裝,傳入的就是偏移量。
下面就寫一個案例,Java代碼以下 :
public class MyText extends TextView { int lastX; int lastY; public MyText(Context context, AttributeSet attrs) { super(context, attrs); } @Override 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 offX = x - lastX; int offY = y - lastY; offsetLeftAndRight(offX); offsetTopAndBottom(offY); break; } return true;//記得返回true,說明被咱們這裏消化了改事件 } }
XML 使用方法以下 :
<com.and.mvp.base.ganhuoi.module.test.MyText android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="移動我" />
效果圖以下 :
(7) layout()
若是你將滑動後的目標位置的座標傳遞給layout(),這樣子就會把view的位置給從新佈置了一下,在視覺上就是view的一個滑動的效果。
這個方法和 第6個是同樣的 只不過最後調用的方法不同罷了
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 offX = x - lastX; int offY = y - lastY; //調用layout方法來從新放置它的位置 ,其餘的和第6個方法是同樣的,只是下面調用的這行代碼不同的罷了。 layout(getLeft() + offX, getTop() + offY, getRight() + offX, getBottom() + offY); break; } return true; }