您的位置:首页 > 移动开发 > Android开发

史上最全的Android ViewDragHelper解析

2016-05-16 18:44 260 查看
简介: 一般我们在自定义ViewGroup 的时候会通常都会用到onInterceptTouchEvent ,onTouchEvent 这些方法去进行距离的判断然后利用scroller 去进行目标的移动,从而实现ViewGroup 的自定义。此方法不但判断麻烦,而且逻辑复杂,不易操作,今天给大家要价讲的这个工具ViewDragHelper 是谷歌IO大会上推出的触摸辅助开发工具,极大的简化了开发自定义VIewGroup 的难度。



1.废话不多说 ,首先要初始化 ViewDragHelper 这个工具类:

final float density = getResources().getDisplayMetrics().density;
final float minVel = MIN_FLING_VELOCITY * density;
//指定好需要处理拖动的ViewGroup和回调 就可以开始使用了
mViewDragHelper = ViewDragHelper.create(this, new DefaultDragHelper());
mViewDragHelper.setEdgeTrackingEnabled(ViewDragHelper.EDGE_LEFT);
//设置minVelocity
mViewDragHelper.setMinVelocity(minVel);


2.初始化 ViewDragHelper.Callback 这个触摸操作类:并初始化 要操作VIew的位置控制方法回掉类:

onViewPositionChanged()
@Override
public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
super.onViewPositionChanged(changedView, left, top, dx, dy);

mLeft = left;
mLeftSize =(-left)/ (dip2px(getContext(),80) * 1.0f);
Log.d(TAG, "onViewPositionChanged()--" + "left:" + left + ",top:" + top + ",dx:" + dx + ",dy:" + dy + "mLeftSize--" + mLeftSize);
invalidate();
}


3.接下来我们会操作要移动VIew 的滑动限制距离 方法:

/**
* 限制子View水平拖拉操作。默认不支持水平操作,重写该方法提供新的水平坐标(根据提供的渴望的水平坐标)
* 不重写就不会支持水平坐标变化
*
* @param child Child view being dragged
* @param left  Attempted motion along the X axis
* @param dx    Proposed change in position for left
* @return The new clamped position for left
*/
@Override
public int clampViewPositionHorizontal(View child, int left, int dx) {
final int leftBound =v_content.getPaddingLeft()-dip2px(getContext(),80);
final int rightBound =v_content.getPaddingRight();
final int newLeft = Math.min(Math.max(left, leftBound), rightBound);
Log.d(TAG, "clampViewPositionHorizontal()--left:" + left + ",dx:" + dx+"v_content.getPaddingLeft(),"+v_content.getPaddingLeft());
return newLeft;
}
}


限制View活动的范围后 ,但我们释放手指的时候我们会操作另外一个方法:

/**
* 手指离开屏幕
* 后续View 的坐标处理
* 比如  滑到超过一半 直接滑到满屏 又或者滑到不到一半的时候
* 还原坐标
*
* @param releasedChild
* @param xvel
* @param yvel
* */
@Override
public void onViewReleased(View releasedChild, float xvel, float yvel) {
super.onViewReleased(releasedChild, xvel, yvel);
Log.d(TAG, "onViewReleased()--xv:" + xvel + ",yv:" + yvel);
//上拉
if (mLeftSize > 0.5) {
openCover();
} else {
closeCover();
}
invalidate();
}


这样一个基本的VIew滑动类就基本实现了。

接下来我贴出一个完整的示例:

package com.example.administrator.myapplication.fragment;

import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Toast;

import com.example.administrator.myapplication.R;
import com.example.administrator.myapplication.widget.BotomDragLayout;
import com.example.administrator.myapplication.widget.LeftScrollDeleteDragLayout;

/**
* 侧滑删除列表
* 2016/4/21
* gxj
*/
public class LeftScrollDeleteLayoutFragment extends Fragment {
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getActivity().setTitle("LeftScrollDeleteLayoutFragment");
}

@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
final LeftScrollDeleteDragLayout view = (LeftScrollDeleteDragLayout) inflater.inflate(R.layout.left_scroll_delete_layout, container, false);
View view_bg =view.findViewById(R.id.del);
view_bg.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if(view.STATE == BotomDragLayout.CLOSEING){
view.openCover();
}else{
view.closeCover();
Toast.makeText(getActivity(),"删除",Toast.LENGTH_SHORT).show();
}
}
});
return view;
}

}


所需要的xml布局:

<com.example.administrator.myapplication.widget.LeftScrollDeleteDragLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/white">
<LinearLayout
android:id="@+id/view_bg"
android:layout_width="match_parent"
android:layout_height="200dp"
android:orientation="horizontal"
android:gravity="right">
<Button
android:id="@+id/del"
android:layout_width="80dp"
android:layout_height="match_parent"
android:text="删除"
android:textColor="@color/white"
android:background="@color/red"
android:gravity="center"
/>
</LinearLayout>
<Button
android:id="@+id/view_content"
android:layout_width="match_parent"
android:layout_height="200dp"
android:text="向左滑出删除按钮"
android:gravity="center"
android:background="@color/colorPrimary"
android:textColor="@android:color/white"
/>
</com.example.administrator.myapplication.widget.LeftScrollDeleteDragLayout>


完整的自定义侧滑类代码:

package com.example.administrator.myapplication.widget;
import android.content.Context;
import android.support.v4.view.MotionEventCompat;
import android.support.v4.widget.ViewDragHelper;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;

import com.example.administrator.myapplication.R;

/**
*列表侧滑删除效果
* author gxj
* date 2016.5.22
*/
public class LeftScrollDeleteDragLayout extends ViewGroup {
private final String TAG = this.getClass().getSimpleName();
private ViewDragHelper mViewDragHelper;
/**
* Minimum velocity that will be detected as a fling
*/
private static final int MIN_FLING_VELOCITY = 400; // dips per second
/**
* 菜单栏的状态
*/
public int STATE = 0;
public static final int CLOSEING = 0;
public static final int OPENED = 1;
private View v_content;
private int viewWidth = 0;
/**
* 滑动view 的顶部位置
*/
private int mLeft;
/**
* 滑动view 的滑动距离占自身高度的比例
*/
private float mLeftSize;
private View view_bg;

public LeftScrollDeleteDragLayout(Context context) {
this(context, null);
init();
}

public LeftScrollDeleteDragLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
init();
}

public LeftScrollDeleteDragLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}

@Override
protected void onFinishInflate() {
super.onFinishInflate();
v_content = findViewById(R.id.view_content);
view_bg = findViewById(R.id.view_bg);
viewWidth = v_content.getHeight();
}

@Override
public void computeScroll() {
if (mViewDragHelper.continueSettling(true)) {
postInvalidateOnAnimation();
}
}

public void openCover() {
STATE = LeftScrollDeleteDragLayout.OPENED;
mViewDragHelper.smoothSlideViewTo(v_content, -dip2px(getContext(),80), 0);
postInvalidateOnAnimation();
}

public void closeCover() {
STATE = LeftScrollDeleteDragLayout.CLOSEING;
mViewDragHelper.smoothSlideViewTo(v_content, 0, 0);
postInvalidateOnAnimation();
}

private void init() {final float density = getResources().getDisplayMetrics().density; final float minVel = MIN_FLING_VELOCITY * density; //指定好需要处理拖动的ViewGroup和回调 就可以开始使用了 mViewDragHelper = ViewDragHelper.create(this, new DefaultDragHelper()); mViewDragHelper.setEdgeTrackingEnabled(ViewDragHelper.EDGE_LEFT); //设置minVelocity mViewDragHelper.setMinVelocity(minVel);
}

private class DefaultDragHelper extends ViewDragHelper.Callback {
@Override
public boolean tryCaptureView(View view, int i) {
return view == v_content;
}

@Override
public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
super.onViewPositionChanged(changedView, left, top, dx, dy);

mLeft = left;
mLeftSize =(-left)/ (dip2px(getContext(),80) * 1.0f);
Log.d(TAG, "onViewPositionChanged()--" + "left:" + left + ",top:" + top + ",dx:" + dx + ",dy:" + dy + "mLeftSize--" + mLeftSize);
invalidate();
}

/**
* 当captureview被捕获时回调
*
* @param capturedChild
* @param activePointerId
*/
@Override
public void onViewCaptured(View capturedChild, int activePointerId) {
super.onViewCaptured(capturedChild, activePointerId);
Log.d(TAG, "onViewCaptured()--:");
}

/**
* 手指离开屏幕
* 后续View 的坐标处理
* 比如 滑到超过一半 直接滑到满屏 又或者滑到不到一半的时候
* 还原坐标
*
* @param releasedChild
* @param xvel
* @param yvel
*/
@Override
public void onViewReleased(View releasedChild, float xvel, float yvel) {
super.onViewReleased(releasedChild, xvel, yvel);
Log.d(TAG, "onViewReleased()--xv:" + xvel + ",yv:" + yvel);
//上拉
if (mLeftSize > 0.5) {
openCover();
} else {
closeCover();
}
invalidate();
}

/**
* 当触摸到边缘的时候会调用
* @param edgeFlags
* @param pointerId
*/
@Override
public void onEdgeTouched(int edgeFlags, int pointerId) {
super.onEdgeTouched(edgeFlags, pointerId);
Log.d(TAG, "onEdgeTouched()");
}

@Override
public boolean onEdgeLock(int edgeFlags) {
Log.d(TAG, "onEdgeLock()");
return super.onEdgeLock(edgeFlags);
}

/**
* 当触摸到边缘的时候会调用
*
* @param edgeFlags
* @param pointerId 可以指定触摸边缘的子View
*/
@Override
public void onEdgeDragStarted(int edgeFlags, int pointerId) {
super.onEdgeDragStarted(edgeFlags, pointerId);
Log.d(TAG, "onEdgeDragStarted()");
mViewDragHelper.captureChildView(v_content, pointerId);
}

/**
* 返回当前移动的View 的position
*
* @param index
* @return
*/
@Override
public int getOrderedChildIndex(int index) {
return super.getOrderedChildIndex(index);
}

/**
* 限制水平移动范围
* Return the magnitude of a draggable child view's horizontal range of motion in pixels.
* 似乎作用不大,其他情况只用于判断是否可以拖动
* 具体返回值真正起作用在于{@link ViewDragHelper#smoothSlideViewTo(View, int, int)}
*
* @param child Child view to check
* @return range of horizontal motion in pixels
*/
@Override
public int getViewHorizontalDragRange(View child) {
return child ==v_content?dip2px(getContext(),80):0;
}
/** * 限制子View水平拖拉操作。默认不支持水平操作,重写该方法提供新的水平坐标(根据提供的渴望的水平坐标) * 不重写就不会支持水平坐标变化 * * @param child Child view being dragged * @param left Attempted motion along the X axis * @param dx Proposed change in position for left * @return The new clamped position for left */ @Override public int clampViewPositionHorizontal(View child, int left, int dx) { final int leftBound =v_content.getPaddingLeft()-dip2px(getContext(),80); final int rightBound =v_content.getPaddingRight(); final int newLeft = Math.min(Math.max(left, leftBound), rightBound); Log.d(TAG, "clampViewPositionHorizontal()--left:" + left + ",dx:" + dx+"v_content.getPaddingLeft(),"+v_content.getPaddingLeft()); return newLeft; } }

@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
final int action = MotionEventCompat.getActionMasked(event);
if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
mViewDragHelper.cancel();
return false;
}
//通过这个方法判断是否拦截 滑动事件
boolean flag = mViewDragHelper.shouldInterceptTouchEvent(event);
return flag;
}

@Override
public boolean onTouchEvent(MotionEvent event) {
//通过这个方法判断是否处理拦截的触摸事件
mViewDragHelper.processTouchEvent(event);
return true;
}

/**
* 丈量所有控件的高度
* 可以得到每个控件的最终高度
* @param widthMeasureSpec
* @param heightMeasureSpec
*/
@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));
viewWidth = v_content.getMeasuredWidth();
}

@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
Log.e("onLayout", l + "|" + t + "|" + r + "|" + b);
v_content.layout(mLeft, t, mLeft+viewWidth,dip2px(getContext(),200) );
view_bg.layout(0, 0, r, dip2px(getContext(),200));
}
/**
* 根据手机的分辨率从 dp 的单位 转成为 px(像素)
*/
private int dip2px(Context context, float dpValue) {
final float scale = context.getResources().getDisplayMetrics().density;
return (int) (dpValue * scale + 0.5f);
}
}


最后附上demo的下载地址 : 猛戳这里–https://github.com/g1258624735/MyApplication2

5. 所有的代码已经全部上传,注释很详细,包你看懂,已经详细的不能在详细了,看不懂的可以板砖拍我。

6. 大家在看的过程 当中,如果有什么不懂得话可以发邮件给我 ,1258624735@qq.com

7. ps: 如果大家觉得文章对你有帮助:不妨大赏一下支持原创,你的支持是我最大的动力:
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: