Android ViewDragHelper仿淘寶拖動加載效果
拖動加載是我在淘寶的商品詳情界面發(fā)現(xiàn)的,感覺很實用。于是就分析它的實現(xiàn)方式,感覺用ViewDragHelper可以很方便的實現(xiàn)這種效果。下面大致把我的思路分步驟寫一下。先上圖吧。

首先建工程什么的我就不多說了。咱從ViewDragHelper的實現(xiàn)開始說吧,ViewDragHelper一般用在一個自定義ViewGroup的內(nèi)部,可以對其子View進行移動操作。
創(chuàng)建自定義ViewGroup:
package com.maxi.viewdraghelpertest.widget;
import android.content.Context;
import android.support.v4.widget.ViewDragHelper;
import android.util.AttributeSet;
import android.view.View;
import android.widget.LinearLayout;
public class DragHelperLayout extends LinearLayout{
private ViewDragHelper mDragHelper;
@SuppressWarnings("static-access")
public DragHelperLayout(Context context, AttributeSet attrs) {
super(context, attrs);
// TODO Auto-generated constructor stub
/*
* 創(chuàng)建帶回調(diào)接口的ViewDragHelper
*/
mDragHelper = ViewDragHelper.create(this, 10.0f,new DragHelperCallback());// 參數(shù)一:該類生成的對象(當前的ViewGroup)
// 參數(shù)二:敏感度(越大越敏感)
}
class DragHelperCallback extends ViewDragHelper.Callback {
@Override
public boolean tryCaptureView(View arg0, int arg1) {
// TODO Auto-generated method stub
return false;
}
}
}
然后將觸摸事件傳遞給ViewDragHelper:
@Override
public boolean onInterceptTouchEvent(MotionEvent event)
{
return mDragHelper.shouldInterceptTouchEvent(event);//是否應該打斷MotionEvent的傳遞
}
@Override
public boolean onTouchEvent(MotionEvent event)
{
mDragHelper.processTouchEvent(event);
return true;
}
接著我們開始實現(xiàn)DragHelperCallback,這個ViewDragHelper.Callback回調(diào)中可以對ViewGroup中的一些View進行操作,在此我們只對本項目涉及到的相關用法做解析,詳細點請自行查閱資料。
class DragHelperCallback extends ViewDragHelper.Callback {
@Override
public boolean tryCaptureView(View arg0, int arg1) {
// TODO Auto-generated method stub
return true; //返回true表示可以捕捉ViewGroup中的View
}
/*
* (non-Javadoc)
* @see android.support.v4.widget.ViewDragHelper.Callback#clampViewPositionVertical(android.view.View, int, int)
* 限定View豎直方向上的活動區(qū)域,防止滑出ViewGroup
*/
@Override
public int clampViewPositionVertical(View child, int top, int dy) {
int topBound = getPaddingTop();
int bottomBound = getHeight() - child.getHeight() - topBound;
int newHeight = Math.min(Math.max(top, topBound), bottomBound);
return newHeight;
}
}
在上面的代碼段中我已經(jīng)做了注釋,在clampViewPositionVertical中我們對View的豎直方向活動區(qū)域做了限制,防止滑出ViewGroup,當然你可以直接return top;不過為了效果我先這么限定一下。還有一個clampViewPositionHorizontal方法,同樣是對其水平邊界進行控制的,先不多說啦。這個時候咱們自定義的ViewGroup初期已經(jīng)完成,先去試試水。
在activity_main.xml中加入
<RelativeLayout 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"
tools:context="com.maxi.viewdraghelpertest.MainActivity" >
<com.maxi.viewdraghelpertest.widget.DragHelperLayout
android:id="@+id/dhl"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:background="@android:color/darker_gray"
>
<TextView
android:layout_width="match_parent"
android:layout_height="100dp"
android:background="@android:color/holo_blue_bright"
/>
<TextView
android:layout_width="match_parent"
android:layout_height="100dp"
android:background="@android:color/holo_orange_dark"
/>
</com.maxi.viewdraghelpertest.widget.DragHelperLayout>
</RelativeLayout>
運行后的效果:

大家是不是都急了,做個拖動加載怎么搞起這東西了,不要急,這才剛剛開始,大家想想拖動加載是不是就是兩個View在同一個ViewGroup里通過ViewDragHelper的滑動操作然后實現(xiàn)的?是不是有思路的?沒有思路也沒關系,咱慢慢來,想要兩個View相關聯(lián),就是拖動一個View然后另一個View跟著它走該怎么實現(xiàn)呢?首先我們需要ViewDragHelper回調(diào)里的另一個方法onViewPositionChanged,該方法是在View位置發(fā)生改變時回調(diào)的。為的就是在上面的View上拉的時候讓下面的View跟著往上走。來看看我們的實現(xiàn)方法:
首先先將兩個View初始化:
private View t1, t2;
/*
* (non-Javadoc)
* @see android.view.View#onFinishInflate()
* 初始化兩個View
*/
@Override
protected void onFinishInflate() {
t1 = getChildAt(0);
t2 = getChildAt(1);
}
得到兩個View后我們在回調(diào)中判斷哪個位置發(fā)生了改變,
@Override
public void onViewPositionChanged(View changedView, int left, int top,
int dx, int dy) {
// TODO Auto-generated method stub
int childIndex = 1;
if (changedView == t2) {
childIndex = 2;
}
viewFollowChanged(childIndex, top);
}
上面的代碼段中有個方法viewFollowChanged,主要實現(xiàn)的就是View跟著動。
private void viewFollowChanged(int viewIndex, int posTop) {
viewH = t1.getMeasuredHeight();
if (viewIndex == 1) {
int offsetTopBottom = viewH + t1.getTop() - t2.getTop();
t2.offsetTopAndBottom(offsetTopBottom);
} else if (viewIndex == 2) {
int offsetTopBottom = t2.getTop() - viewH - t1.getTop();
t1.offsetTopAndBottom(offsetTopBottom);
}
invalidate();
}
在運行是不是發(fā)現(xiàn)沒有被點擊拖動的View會跟著View一起移動,像一個整體雙宿雙飛。圖我就不加了,大家運行看吧。因為我們要獲取View的實際大小所以需要以下代碼段的支持:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
measureChildren(widthMeasureSpec, heightMeasureSpec);
int maxWidth = MeasureSpec.getSize(widthMeasureSpec);
int maxHeight = MeasureSpec.getSize(heightMeasureSpec);
setMeasuredDimension(
resolveSizeAndState(maxWidth, widthMeasureSpec, 0),
resolveSizeAndState(maxHeight, heightMeasureSpec, 0));
}
public static int resolveSizeAndState(int size, int measureSpec,
int childMeasuredState) {
int result = size;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
switch (specMode) {
case MeasureSpec.UNSPECIFIED:
result = size;
break;
case MeasureSpec.AT_MOST:
if (specSize < size) {
result = specSize | MEASURED_STATE_TOO_SMALL;
} else {
result = size;
}
break;
case MeasureSpec.EXACTLY:
result = specSize;
break;
}
return result | (childMeasuredState & MEASURED_STATE_MASK);
}
然后我們可以嘗試將兩個View滿屏,android:layout_height="match_parent",把clampViewPositionVertical方法里限制的邊界去掉吧,暫時先return top;這樣試一下是不是有點像拖動加載了,呵呵噠,可是第一個View下拉的時候由于上面沒有View怎么辦?我們可以在clampViewPositionVertical中將它限定邊界??!
@Override
public int clampViewPositionVertical(View child, int top, int dy) {
int slideTop = top;
if (child == t1) {
if (top > 0) {
slideTop = 0;
}
} else if (child == t2) {
if (top < 0) {
slideTop = 0;
}
}
return child.getTop() + (slideTop - child.getTop());
}

已經(jīng)大致成型了,然后就是拖動的時候?qū)iew自動置頂或置底,因為自動置頂或置底是在滑動松開之后,所以就需要用到ViewDragHelper回調(diào)里的onViewReleased方法,該方法就是在滑動松開之后調(diào)用,接下來實現(xiàn)它:
@Override
public void onViewReleased(View releasedChild, float xvel, float yvel) {
putStickOrDown(releasedChild);// 滑動松開后,需要置頂或置底
}
private void putStickOrDown(View releasedChild, float yvel) {
int finalTop = 0; // 默認是粘到最頂端
if (releasedChild == t1) {
// 滑動第一個view松開
if (yvel < 0)//靈敏度自己調(diào)吧
finalTop = -viewH;
} else {
// 滑動第二個view松開
if (yvel > 0)//同上
finalTop = viewH;
}
if (mDragHelper.smoothSlideViewTo(releasedChild, 0, finalTop)) {
ViewCompat.postInvalidateOnAnimation(this);// 會在下一個Frame開始的時候,發(fā)起一些invalidate操作
}
}
@Override
public void computeScroll() {
if (mDragHelper.continueSettling(true)) {
ViewCompat.postInvalidateOnAnimation(this);
}
}
ok,是可以自動置頂或置底了。對了,那種拖動粘滯效果可以設置clampViewPositionVertical里的返回值,return child.getTop() + (finalTop - child.getTop()) / num;num值越大越粘滯。
然后淘寶第一個View是可以滑動的滑動到最底部然后才把手勢事件交給ViewDragHelper處理的。這塊試想如果用ScrollView的話,手勢事件肯定會優(yōu)先被它消費,這樣肯定達不到我們想要的效果,所以在此我們需要對ScrollView進行自定義,大致的實現(xiàn)思路是當用戶用戶從觸發(fā)屏幕開始判斷是不是ScrollView在最底端,如果在最底端然后判斷手勢是否是向上滑動的如果也是則滿足條件將touch事件交給父View就可以了,即requestDisallowInterceptTouchEvent該方法。然后自定義的ViewGroup中的onInterceptTouchEvent方法也要做相應修改,這里用GestureDetectorCompat處理事件,其回調(diào)用來判斷是否是上下滑動。先聲明private GestureDetectorCompat gestureDC;然后再gestureDC = new GestureDetectorCompat(context,new YSlideDetector());
class YSlideDetector extends SimpleOnGestureListener {
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2,
float distanceX, float distanceY) {
// TODO Auto-generated method stub
return Math.abs(distanceY) > Math.abs(distanceX);//Y方向絕對值大于X方向,上下滑動
}
}<pre name="code" class="java"> @Override
public boolean onInterceptTouchEvent(MotionEvent event) {
boolean is_y_slide = gestureDC.onTouchEvent(event);
boolean shouldIntercept = mDragHelper.shouldInterceptTouchEvent(event);
int action = event.getActionMasked();
if (action == MotionEvent.ACTION_DOWN) {
mDragHelper.processTouchEvent(event);// action_down時就讓mDragHelper開始工作,否則有時候?qū)е庐惓?
}
return shouldIntercept && is_y_slide;
}
OK。就這樣。是不是達到了想要的效果了?
以上就是本文的全部內(nèi)容,希望對大家的學習有所幫助,也希望大家多多支持腳本之家。
- Android實現(xiàn)ImageView圖片縮放和拖動
- Android實現(xiàn)跟隨手指拖動并自動貼邊的View樣式(實例demo)
- Android自定義View實現(xiàn)拖動選擇按鈕
- Android實現(xiàn)單頁面浮層可拖動view的一種方法
- Android通過自定義ImageView控件實現(xiàn)圖片的縮放和拖動的實現(xiàn)代碼
- Android開發(fā)實現(xiàn)可拖動排序的ListView功能【附源碼下載】
- Android DragImageView實現(xiàn)下拉拖動圖片放大效果
- Android RecyclerView滑動刪除和拖動排序
- Android自定義View圓形和拖動圓、跟隨手指拖動效果
- android實現(xiàn)可拖動的浮動view
相關文章
Android?startActivityForResult的調(diào)用與封裝詳解
startActivityForResult?可以說是我們常用的一種操作了,目前有哪些方式實現(xiàn)?startActivityForResult?的功能呢?本文就來和大家詳細聊聊2023-03-03
使用android studio導入模塊的兩種方法(超詳細)
這篇文章主要介紹了使用android studio導入模塊的兩種方法,本文通過圖文并茂的形式給大家介紹的非常詳細,具有一定的參考借鑒價值,需要的朋友可以參考下2018-09-09
Android開發(fā)MQTT協(xié)議的模型及通信淺析
這篇文章主要W為大家介紹了Android開發(fā)MQTT協(xié)議的模型及通信淺析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-03-03
Android TreeView效果實現(xiàn)方法(附demo源碼下載)
這篇文章主要介紹了Android TreeView效果實現(xiàn)方法,結合實例形式分析了Android TreeView效果的實現(xiàn)原理與具體技巧,并附帶demo源碼供讀者下載,需要的朋友可以參考下2016-02-02

