Android ViewDragHelper使用总结

46 篇文章 0 订阅

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
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

峥嵘life

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值