Android ViewDragHelper使用总结

来源:互联网 发布:mac 安装wget命令 编辑:程序博客网 时间:2024/04/30 10:50

Android ViewDragHelper使用总结

       Android中的ViewDragHelper类(2013年出来的),使用的人算是比较少的把,但是它也是有一些特殊效果能实现,一般应用场合是拖动效果的设计。
       要对它进行要对它的一些属性和方法进行了解(这是它的在线版英文API文档):
http://www.android-doc.com/reference/android/support/v4/widget/ViewDragHelper.html
本文程序设计的效果
只能水平方向移动效果1:
s1

只能垂直方向移动效果2:
s2

任意方向移动效果3:
s3

复杂应用效果:

s4
上面点击ListView中的某一个item后弹出一个可以拖拽的视图!

一.ViewDragHelper的基础知识

       ViewDragHelper是一个类,它是在用在一个容器的自定义类中,比如六大布局的类中或者ViewGroup类中。

(一)ViewDragHelper的初始化

mDragHelper = ViewDragHelper.create(this, 1.0f, new DragHelperCallback());  

上面的语句一般是在构造方法中执行,其中1.0f是敏感度参数参数越大越敏感。第一个参数为this,表示该类生成的对象,他是ViewDragHelper的拖动处理对象,必须为ViewGroup。

(二)拖动行为的处理

1.处理横向的拖动:

       在DragHelperCallback中实现clampViewPositionHorizontal方法, 并且返回一个适当的数值就能实现横向拖动效果,lampViewPositionHorizontal的第二个参数是指当前拖动子view应该到达的x坐标。所以按照常理这个方法原封返回第二个参数就可以了,但为了让被拖动的view遇到边界之后就不在拖动,对返回的值做了更多的考虑。
代码如下:

    @Override          public int clampViewPositionHorizontal(View child, int left, int dx) {            Log.e("DragLayout", "clampViewPositionHorizontal " + left + "," + dx);            final int leftBound = getPaddingLeft();            final int rightBound = getWidth() - mDragView.getWidth();            final int newLeft = Math.min(Math.max(left, leftBound), rightBound);            return newLeft;          }

2.处理纵向的拖动

       也是类似的,在DragHelperCallback中实现clampViewPositionVertical方法,

@Override          public int clampViewPositionVertical(View child, int top, int dy) {            final int topBound = getPaddingTop();  //padding顶部的距离           //父框体的宽度减去视图本身的宽度,得到可以移动的最大宽度          final int bottomBound = getHeight() - mDragView.getHeight();           //先取出padding顶部和距离顶部距离的最大值          //然后让这个最大值和这个视图可以移动的距离做比较,取出最小值,得到视图实际可以移动的距离          final int newTop = Math.min(Math.max(top, topBound), bottomBound);            return newTop;          } 

       这里横向处理和纵向处理,如果没有特殊要求格式基本是固定的,如果要横向和纵向都可以移动就两个方法都重写就可以了。
       clampViewPositionHorizontal和clampViewPositionVertical,因为默认它返回的是0。如果不重写是不能拖动图像的。

3.tryCaotureView方法

       通过DragHelperCallback的tryCaptureView方法的返回值可以决定一个parentview中哪个子view可以拖动,现在假设有两个子views (mDragView和mDragView1) ,如下实现tryCaptureView之后,则只有mDragView是可以拖动的。

    //tryCaptureView方法的返回值可以决定一个parentview中哪个子view可以拖动        @Override        public boolean tryCaptureView(View child, int pointerId) {             return mDragView == child;//不返回true就不会被移动            //如果这里有多个View的话,返回值改变成 return child == mDragView1;            //那么只有MDragView1可以被拖拽,其他View不能        }

       还有很多方法,它的具体含义我也是不太懂,有兴趣的话自己看API!怪我英语不好,不能給你解释太多,但是我会基本的调用,已经很满足了!
       下面是程序设计的具体代码,很多解释都在代码中自己看,理解!

二.第一个简单示例程序

这个程序,主要是针对上面的前面三个效果的设计,功能非常简单,但是一个演示了基本用法!

(一)自定义容器类的设计

package com.example.viewdraghelperdemo;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.widget.ImageView;import android.widget.LinearLayout;import android.widget.TextView;import android.widget.Toast;public class DragLayout extends LinearLayout {    private  ViewDragHelper mDragHelper;      private  View mDragView;    private  ImageView mImageView;    private   TextView mTextView;    public DragLayout(Context context) {         this(context,null);    }    public DragLayout(Context context, AttributeSet attrs) {        this(context,null,0);    }    public DragLayout(Context context, AttributeSet attrs, int defStyle) {            super(context, attrs, defStyle);            Log.e("TAG","init3");          initView();        }     //这里一定要在渲染完成后才能实例化控件对象,否则返回的时空的值!    @Override      protected void onFinishInflate() {          mImageView = (ImageView) findViewById(R.id.imageview);        mTextView=(TextView) findViewById(R.id.text);        mDragView=mImageView;        //mDragView=mTextView;    }      //初始化数据    private void initView() {        // setOrientation(LinearLayout.HORIZONTAL);          //实例化DragHelper对象          mDragHelper = ViewDragHelper.create(this, 1.0f, new DragHelperCallback());           //设置手指点击到到左侧边缘时有onEdgeTouched方法回调        mDragHelper.setEdgeTrackingEnabled(ViewDragHelper.EDGE_LEFT);           //设置手指点到到右侧边时有onEdgeTouched方法回调         //  mDragHelper.setEdgeTrackingEnabled(ViewDragHelper.EDGE_RIGHT);           //这里还很设置上边和底部,或EDGRE_ALL表示所有的边缘        //mDragHelper.setEdgeTrackingEnabled(ViewDragHelper.EDGE_ALL);     }    //事件拦截处理,基本是固定的格式    @Override      public boolean onInterceptTouchEvent(MotionEvent ev) {        final int action = MotionEventCompat.getActionMasked(ev);        if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {            mDragHelper.cancel();            return false;        }        return mDragHelper.shouldInterceptTouchEvent(ev);      }      //事件处理,格式也是固定的    @Override      public boolean onTouchEvent(MotionEvent ev) {       mDragHelper.processTouchEvent(ev);        return true;      }     //创建DragHelper的Callback类        class DragHelperCallback extends ViewDragHelper.Callback{        //重写方法,格式也是固定,        //tryCaptureView方法的返回值可以决定一个parentview中哪个子view可以拖动        @Override        public boolean tryCaptureView(View child, int pointerId) {             Log.e("TAG", "mDragView == child " + (mDragView == child));              return mDragView == child;//不返回true就不会被移动            //如果这里有多个View的话,返回值改变成 return child == mDragView1;            //那么只有MDragView1可以被拖拽,其他View不能        }        //如果要实现可以横向拖动,就要重写这个方法clampViewPositionHorizontal,否则默认返回的值为0        /*处理横向的拖动:        在DragHelperCallback中实现clampViewPositionHorizontal方法,        并且返回一个适当的数值就能实现横向拖动效果,        clampViewPositionHorizontal的第二个参数是指当前拖动子view应该到达的x坐标。        所以按照常理这个方法原封返回第二个参数就可以了,        但为了让被拖动的view遇到边界之后就不在拖动,对返回的值做了更多的考虑。*/    @Override          public int clampViewPositionHorizontal(View child, int left, int dx) {            Log.e("DragLayout", "clampViewPositionHorizontal " + left + "," + dx);            final int leftBound = getPaddingLeft();            final int rightBound = getWidth() - mDragView.getWidth();            final int newLeft = Math.min(Math.max(left, leftBound), rightBound);            return newLeft;          }            //同样要实现纵向移动要实现这个方法        @Override          public int clampViewPositionVertical(View child, int top, int dy) {            final int topBound = getPaddingTop();  //padding顶部的距离           //父框体的宽度减去视图本身的宽度,得到可以移动的最大宽度          final int bottomBound = getHeight() - mDragView.getHeight();           //先取出padding顶部和距离顶部距离的最大值          //然后让这个最大值和这个视图可以移动的距离做比较,取出最小值,得到视图实际可以移动的距离          final int newTop = Math.min(Math.max(top, topBound), bottomBound);            return newTop;          }         //重写处理视图滑动到边缘时的事件        //如果上面设置了mDragHelper.setEdgeTrackingEnabled(ViewDragHelper.EDGE_...);下面的方法才会得到回调        //没有得到回调的原因???        //这里滑动到边缘是指手指在容器的边缘滑动,而不是指把指View拖动到边缘时触发        @Override          public void onEdgeTouched(int edgeFlags, int pointerId) {              Log.e("TAG","滑动到左边或右边了");            super.onEdgeTouched(edgeFlags, pointerId);             Toast.makeText(getContext(), "点击到左边了", Toast.LENGTH_SHORT).show();          }    /*  如果你想在边缘滑动的时候根据滑动距离移动一个子view,        可以通过实现onEdgeDragStarted方法,        并在onEdgeDragStarted方法中手动指定要移动的子View*/        @Override  //不太理解!        public void onEdgeDragStarted(int edgeFlags, int pointerId) {                Log.e("TAG","滑onEdgeDragStarted了");             //mDragHelper.captureChildView(mDragView, pointerId);        }      }}

(二)布局文件的设计

<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"     >   <com.example.viewdraghelperdemo.DragLayout       android:layout_width="match_parent"       android:layout_height="match_parent"       >        <ImageView           android:id="@+id/imageview"           android:layout_width="wrap_content"           android:layout_height="wrap_content"          android:src="@drawable/a"           />   <TextView        android:id="@+id/text"       android:layout_width="wrap_content"       android:layout_height="wrap_content"       android:text="文志"       android:textSize="30sp"       />  </com.example.viewdraghelperdemo.DragLayout></RelativeLayout>

(三)主方法类

这里没用任何处理,只要显示出来就可以了。

 package com.example.viewdraghelperdemo;import android.app.Activity;import android.os.Bundle;public class MainActivity extends Activity {    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);    }}

上面是否在某个方向可以移动,决定于是否已经重写了
clampViewPositionHorizontal和clampViewPositionVertical!
程序运行后,单击边缘位置,可以看到:
s6

三.复杂应用效果程序的设计

(一)自定义容器类的设计

package com.example.viewdraghelperdemo2;import android.content.Context;import android.support.v4.view.MotionEventCompat;import android.support.v4.view.ViewCompat;import android.support.v4.widget.ViewDragHelper;import android.util.AttributeSet;import android.view.MotionEvent;import android.view.View;import android.view.ViewGroup;/** *定义的容器类,里面包含可拖拽的控件  * */public class MyViewDragHelper  extends ViewGroup {    private ViewDragHelper mDragHelper;  //定义DragHelper对象    private View mHeaderView;  //定义头部可拖拽的视图VIew    private View mDescView;    //其他的VIew    private float mInitialMotionX;  //初始时的移动X的距离    private float mInitialMotionY;  //初始时的移动Y的距离    private int mDragRange;        //可以拖拽的距离范围    private int mTop;            //距离顶部的距离    private float mDragOffset;     //当前拖拽的距离,这里指的是移动的一小段的距离    //三个构造方法,指向一个构造方法    public MyViewDragHelper(Context context) {        this(context, null, 0);    }    public MyViewDragHelper(Context context, AttributeSet attrs) {        this(context, attrs,0);    }    public MyViewDragHelper(Context context, AttributeSet attrs, int defStyle) {        super(context, attrs, defStyle);        //实例化ViewDragHelper对象        mDragHelper = ViewDragHelper.create(this, 1f, new DragHelperCallback());     }    //容器内的控件的实例化是必须要在页面渲染完成后,否则是返回空值    @Override      protected void onFinishInflate() {          //实例化容器内的控件        mHeaderView = findViewById(R.id.viewHeader);          mDescView = findViewById(R.id.viewDesc);      }    /*     *判断是否完全显示页面     *如果传入0f,那么这个自定义View容器全屏显示     *如果 传入1f,那么这个自定义容器只显示可以拖拽部分View     *这也是非常重要的一个封装方法,可以在外部调用,设置这个视图的显示     */    boolean smoothSlideTo(float slideOffset) {          final int topBound = getPaddingTop();          int y = (int) (topBound + slideOffset * mDragRange);          //smoothSlideViewTo()通过此方法可以把父布局中某一个子View移动到指定的左边,        if (mDragHelper.smoothSlideViewTo(mHeaderView, mHeaderView.getLeft(), y)) {              ViewCompat.postInvalidateOnAnimation(this);              return true;          }          return false;      }      /**     *      *创建ViewDragHelper的callback类     */    private class DragHelperCallback extends ViewDragHelper.Callback {          //设置可以拖动的视图对象      @Override        public boolean tryCaptureView(View child, int pointerId) {              return child == mHeaderView;        }         @Override        public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {            mTop = top;            mDragOffset = (float) top / mDragRange;              mHeaderView.setPivotX(mHeaderView.getWidth());              mHeaderView.setPivotY(mHeaderView.getHeight());              mHeaderView.setScaleX(1 - mDragOffset / 2);              mHeaderView.setScaleY(1 - mDragOffset / 2);              mDescView.setAlpha(1 - mDragOffset);              requestLayout();        }        @Override        public void onViewReleased(View releasedChild, float xvel, float yvel) {            int top = getPaddingTop();            if (yvel > 0 || (yvel == 0 && mDragOffset > 0.5f)) {                top += mDragRange;            }            mDragHelper.settleCapturedViewAt(releasedChild.getLeft(), top);        }        @Override        public int getViewVerticalDragRange(View child) {            return mDragRange;        }        //设置子视图可以在垂直方向移动,返回的是可以移动的最大距离      @Override        public int clampViewPositionVertical(View child, int top, int dy) {            final int topBound = getPaddingTop();            final int bottomBound = getHeight() - mHeaderView.getHeight() - mHeaderView.getPaddingBottom();            final int newTop = Math.min(Math.max(top, topBound), bottomBound);            return newTop;        }      }      @Override      public void computeScroll() {        if (mDragHelper.continueSettling(true)) {            ViewCompat.postInvalidateOnAnimation(this);        }      }      //事件拦截方法    @Override      public boolean onInterceptTouchEvent(MotionEvent ev) {        final int action = MotionEventCompat.getActionMasked(ev);        if (( action != MotionEvent.ACTION_DOWN)) {            mDragHelper.cancel();            return super.onInterceptTouchEvent(ev);        }        if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {            mDragHelper.cancel();            return false;        }        final float x = ev.getX();        final float y = ev.getY();        boolean interceptTap = false;        switch (action) {            case MotionEvent.ACTION_DOWN: {                mInitialMotionX = x;                mInitialMotionY = y;                  interceptTap = mDragHelper.isViewUnder(mHeaderView, (int) x, (int) y);                break;            }            case MotionEvent.ACTION_MOVE: {                final float adx = Math.abs(x - mInitialMotionX);                final float ady = Math.abs(y - mInitialMotionY);                final int slop = mDragHelper.getTouchSlop();                if (ady > slop && adx > ady) {                    mDragHelper.cancel();                    return false;                }            }        }        return mDragHelper.shouldInterceptTouchEvent(ev) || interceptTap;      }      //事件响应方法    @Override      public boolean onTouchEvent(MotionEvent ev) {        mDragHelper.processTouchEvent(ev);        final int action = ev.getAction();          final float x = ev.getX();          final float y = ev.getY();          boolean isHeaderViewUnder = mDragHelper.isViewUnder(mHeaderView, (int) x, (int) y);          switch (action & MotionEventCompat.ACTION_MASK) {            case MotionEvent.ACTION_DOWN: {                mInitialMotionX = x;                mInitialMotionY = y;                break;            }            case MotionEvent.ACTION_UP: {                final float dx = x - mInitialMotionX;                final float dy = y - mInitialMotionY;                final int slop = mDragHelper.getTouchSlop();                if (dx * dx + dy * dy < slop * slop && isHeaderViewUnder) {                    if (mDragOffset == 0) {                        smoothSlideTo(1f);                    } else {                        smoothSlideTo(0f);                    }                }                break;            }        }        return isHeaderViewUnder && isViewHit(mHeaderView, (int) x, (int) y) || isViewHit(mDescView, (int) x, (int) y);      }     //判断View是否隐藏    private boolean isViewHit(View view, int x, int y) {          int[] viewLocation = new int[2];          view.getLocationOnScreen(viewLocation);          int[] parentLocation = new int[2];          this.getLocationOnScreen(parentLocation);          int screenX = parentLocation[0] + x;          int screenY = parentLocation[1] + y;          return screenX >= viewLocation[0] && screenX < viewLocation[0] + view.getWidth() &&                  screenY >= viewLocation[1] && screenY < viewLocation[1] + view.getHeight();      }      //测量布局    @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));      }      //设置容器里面控件的大小    @Override      protected void onLayout(boolean changed, int l, int t, int r, int b) {        mDragRange = getHeight() - mHeaderView.getHeight();          mHeaderView.layout(0, mTop,r, mTop + mHeaderView.getMeasuredHeight());          mDescView.layout(0, mTop + mHeaderView.getMeasuredHeight(), r,mTop  + b);      }  }

(二)布局文件的设计

<FrameLayout          xmlns:android="http://schemas.android.com/apk/res/android"          android:layout_width="match_parent"          android:layout_height="match_parent">      <ListView              android:id="@+id/listView"              android:layout_width="match_parent"              android:layout_height="match_parent"              android:tag="list"              />      <com.example.viewdraghelperdemo2.MyViewDragHelper              android:layout_width="match_parent"              android:layout_height="match_parent"              android:id="@+id/viewDragHelper"              android:orientation="vertical"              android:visibility="visible">          <TextView                  android:id="@+id/viewHeader"                  android:layout_width="match_parent"                  android:layout_height="128dp"                  android:fontFamily="sans-serif-thin"                  android:textSize="25sp"                  android:tag="text"                  android:gravity="center"                   android:text="欢迎你,可以拖拽这块区域"                  android:textColor="@android:color/white"                  android:background="#AD78CC"/>          <TextView                  android:id="@+id/viewDesc"                  android:tag="desc"                  android:textSize="35sp"                  android:gravity="center"                  android:text="这里是显示具体内容区域,拖拽无效!"                  android:textColor="@android:color/white"                  android:layout_width="match_parent"                  android:layout_height="match_parent"                  android:background="#FF00FF"/>      </com.example.viewdraghelperdemo2.MyViewDragHelper >  </FrameLayout>  

(三)主方法类的设计

package com.example.viewdraghelperdemo2;import android.app.Activity;import android.os.Bundle;import android.view.Menu;import android.view.MenuItem;import android.view.View;import android.widget.AdapterView;import android.widget.AdapterView.OnItemClickListener;import android.widget.ArrayAdapter;import android.widget.ListView;import android.widget.TextView;public class MainActivity extends Activity {    //布局内的控件    ListView listView;    TextView headerText;    TextView descText;    MyViewDragHelper myViewDragHelper;    //创建ListView的数据源            String[] resource=new String[100];    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        //实例化控件        listView=(ListView) findViewById(R.id.listView);        headerText=(TextView) findViewById(R.id.viewHeader);        descText=(TextView) findViewById(R.id.viewDesc);        myViewDragHelper=(MyViewDragHelper) findViewById(R.id.viewDragHelper);        //设置数据源        for (int i = 0; i < resource.length; i++) {            resource[i]="这是第"+(i+1)+"章内容";        }        //创建适配器,这里布局使用的是系统的TextView布局        ArrayAdapter adapter=new ArrayAdapter(this, android.R.layout.simple_list_item_1, resource);        listView.setAdapter(adapter);        //給ListView中的条目设置点击的监听对象        listView.setOnItemClickListener(listener);    }    //listVIew条目的监听对象    private OnItemClickListener listener=new OnItemClickListener() {        @Override        public void onItemClick(AdapterView<?> arg0, View arg1, int arg2,                long arg3) {            //改变可拖拽文本的内容            headerText.setText(resource[arg2]+"");             //改变第二个文本的内容            descText.setText(resource[arg2]+"的详细内容如下: →  →  →  →  →  →  → ");            //完全显示自定义的View视图            myViewDragHelper.smoothSlideTo(0f);        }    };}

再展示一下程序效果:
x
       到这里ViewDragHelper的介绍基本结束了,从这里可以看到两个程序,主要还是容器类的设计发挥作用,而ViewDragHelper的方法就是重重之重!
       第二个程序的效果还是蛮酷的!
       ViewDragHelper还有很多设计效果和场合需要大家慢慢探索!

1 0
原创粉丝点击