Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> ViewDragHelper詳解(側滑欄)

ViewDragHelper詳解(側滑欄)

編輯:關於Android編程

一、概述

Drag拖拽;ViewDrag拖拽視圖,拖拽控件;ViewDragHelper拖拽視圖助手,拖拽操作類。利用ViewDragHelper類可以實現很多絢麗的效果,比如:拖拽刪除,拖拽排序,側滑欄等。本篇主要講解簡易側滑欄的實現。

注意ViewDragHelper是作用在一個ViewGroup上,也就是說他不能直接作用到被拖拽的控件view上, 因為控件的位置是由父控件決定的。

最終效果效果圖:

drag

二、相關概念

1、create

ViewDragHelper的實例是通過靜態工廠方法創建的。

方法預覽:

 public static ViewDragHelper create(ViewGroup forParent, float sensitivity, Callback cb)

參數 forParent 當前ViewGroup
參數 sensitivity 敏感度參數,主要用於設置touchSlop helper.mTouchSlop = (int) (helper.mTouchSlop * (1 / sensitivity));,可見sensitivity值越大,touchSlop值就越小。
參數 cb Callback是連接ViewDragHelperview之間的橋梁

2、setEdgeTrackingEnabled(拖動的方向)

setEdgeTrackingEnabled源碼:

    /**
     * Enable edge tracking for the selected edges of the parent view.
     * The callback's {@link Callback#onEdgeTouched(int, int)} and
     * {@link Callback#onEdgeDragStarted(int, int)} methods will only be invoked
     * for edges for which edge tracking has been enabled.
     *
     * @param edgeFlags Combination of edge flags describing the edges to watch
     * @see #EDGE_LEFT
     * @see #EDGE_TOP
     * @see #EDGE_RIGHT
     * @see #EDGE_BOTTOM
     */
    public void setEdgeTrackingEnabled(int edgeFlags) {
        mTrackingEdges = edgeFlags;
    }

可見edgeFlags參數是枚舉類型,可以從左邊,上邊,右邊,下邊拖動。如果我想實現左右拖動怎麼設置:

mDragHelper.setEdgeTrackingEnabled(ViewDragHelper.EDGE_LEFT | ViewDragHelper.EDGE_RIGHT);

3、setMinVelocity(最小拖動速度)

public void setMinVelocity(float minVel)

4、觸摸相關方法

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        return mDragHelper.shouldInterceptTouchEvent(ev);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        mDragHelper.processTouchEvent(event);
        return true;
    }

我們在拖拽側滑欄的時候,禁止主界面的事件響應。那麼就需要重寫onInterceptTouchEvent方法攔截當前事件,通過mDragHelper.shouldInterceptTouchEvent(event)來決定我們是否應該攔截當前的事件。onTouchEvent觸摸方法返回true,能夠接收到手指down以後的操作,通過mDragHelper.processTouchEvent(event)來處理事件。

5、ViewDragHelper.CallCack相關方法

ViewDragHelper.CallCack相關方法:

        mDragHelper=ViewDragHelper.create(this, 1.0f, new ViewDragHelper.Callback() {
            @Override
            public boolean tryCaptureView(View child, int pointerId) {
                return false;
            }
            @Override
            public int clampViewPositionHorizontal(View child, int left, int dx)
            {
                return left;
            }

            @Override
            public int clampViewPositionVertical(View child, int top, int dy)
            {
                return top;
            }
        });

ViewDragHelper攔截和處理事件時,通過CallBack回調方法來處理那些子視圖View可以拖拽,邊界控制等。

1、tryCaptureView

tryCaptureView捕獲子視圖View,如果返回true,則表示該View被捕獲。試想一個ViewGroup裡面有許多子View,如果我想拖動View0,就可以這麼處理:

   return child == view0;

2、 clampViewPositionHorizontal(水平方向固定被捕獲View的位置)

我們一起來看一看下面這張圖:

drag

上圖可以看出藍色View可以移動的水平區域為灰色區域,假設移動區域為m,則paddingleft<=m<=viewgroup.getWidth()-paddingright-view.getwidth。編寫成代碼:

    @Override
    public int clampViewPositionHorizontal(View child, int left, int dx) {
        int leftBound = getPaddingLeft();
        int rightBound = getWidth() - child.getWidth() - leftBound;
        int newLeft = Math.min(Math.max(left, leftBound), rightBound);
        return newLeft;
    }

這樣就可以實現水平拖拽,上效果圖:

drag

3、 clampViewPositionVertical(垂直方向固定被捕獲View的位置)

原理同上。

   @Override
   public int clampViewPositionVertical(View child, int top, int dy) {
       int topBound = getPaddingTop();
       int bottomBound = getHeight() - child.getHeight() - topBound;
       int newTop = Math.min(Math.max(top, topBound), bottomBound);
       return newTop;
   }

實現水平+垂直拖拽:

drag

4、 onEdgeDragStarted(邊界拖動時回調)

 @Override
 public void onEdgeDragStarted(int edgeFlags, int pointerId) {
     if(edgeFlags==ViewDragHelper.EDGE_LEFT){
         mDragHelper.captureChildView(mDragView, pointerId);
     }
 }

onEdgeDragStarted回調方法中,通過mDragHelper對子View進行捕獲,該方法可以繞過tryCaptureView方法,不管tryCaptureView返回真假。能夠在邊界拖動還要加上:

mDragHelper.setEdgeTrackingEnabled(ViewDragHelper.EDGE_LEFT);

效果圖:

drag

5、onViewReleased(手指釋放的時候回調)

 //手指釋放的時候回調
 @Override
 public void onViewReleased(View releasedChild, float xvel, float yvel) {
     //mAutoBackView手指釋放時可以自動回去
     if (releasedChild == mDragView) {
         mDragHelper.settleCapturedViewAt(200, 200);
         invalidate();
     }
 }

settleCapturedViewAt方法設置釋放後releasedChild回到的位置。從你手機抬起到回到(200,200)是個過程,視圖在不斷的變化,所以會調用刷新視圖的方法invalidate()。注意invalidate()結合computeScroll方法一起使用:

    @Override
    public void computeScroll() {
        if (mDragHelper.continueSettling(true)) {
            invalidate();
        }
    }

效果圖:

drag

注意:如果你拖動View添加了clickable = true 或者為Button,你會發現拖不動了,尼瑪怎麼回事?
原因是拖動的時候onInterceptTouchEvent方法,判斷是否可以捕獲,而在判斷的過程中會去判斷另外兩個回調的方法getViewHorizontalDragRangegetViewVerticalDragRange,只有這兩個方法返回大於0的值才能正常的捕獲。如果未能正常捕獲就會導致手勢down後面的move以及up都沒有進入到onTouchEvent

處理方案:

@Override public int getViewHorizontalDragRange(View child) {
    return getMeasuredWidth() - child.getMeasuredWidth();
}

@Override public int getViewVerticalDragRange(View child) {
    return getMeasuredHeight() - child.getMeasuredHeight();
}

三、側滑欄

1、xml布局

<!--?xml version="1.0" encoding="utf-8"?-->
<com.ws.viewdragdemo.app.vdhlayout android:layout_height="match_parent" android:layout_width="match_parent" android:orientation="vertical" tools:context="com.ws.viewdragdemo.MainActivity" xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools">
 
 
    <!--content-->
    <relativelayout android:background="#44ff0000" android:clickable="true" android:layout_height="match_parent" android:layout_width="match_parent">
 
        <textview android:id="@+id/id_content_tv" android:layout_centerinparent="true" android:layout_height="wrap_content" android:layout_width="wrap_content" android:text="我是側滑欄" android:textsize="40sp">
    </textview></relativelayout>
 
    <!--menu-->
    <framelayout android:id="@+id/id_container_menu" android:layout_height="match_parent" android:layout_width="match_parent">
    </framelayout>
 
 
</com.ws.viewdragdemo.app.vdhlayout>

2、VDHLayout文件

package com.ws.viewdragdemo.app;

import android.content.Context;
import android.support.v4.widget.ViewDragHelper;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;

/**
 * Created by Administrator on 6/6 0006.
 * <p/>
 */
public class VDHLayout extends ViewGroup {

    private static final int MIN_DRAWER_MARGIN = 80; // dp
    /**
     * Minimum velocity that will be detected as a fling
     */
    private static final int MIN_FLING_VELOCITY = 400; // dips per second

    /**
     * drawer離父容器右邊的最小外邊距
     */
    private int mMinDrawerMargin;

    private View mLeftMenuView;
    private View mContentView;

    private ViewDragHelper mDragHelper;
    /**
     * drawer顯示出來的占自身的百分比
     */
    private float mLeftMenuOnScreen;


    public VDHLayout(Context context) {
        this(context, null);
    }

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

    public VDHLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);

        float density = getResources().getDisplayMetrics().density;
        float minVel = MIN_FLING_VELOCITY * density;  //1200
        mMinDrawerMargin = (int) (MIN_DRAWER_MARGIN * density + 0.5f);

        mDragHelper = ViewDragHelper.create(this, 1.0f, new ViewDragHelper.Callback() {
            @Override
            public boolean tryCaptureView(View child, int pointerId) {
                //捕獲該view
                return child == mLeftMenuView;
            }

            @Override
            public int clampViewPositionHorizontal(View child, int left, int dx) {
                int newLeft = Math.max(-child.getWidth(), Math.min(left, 0));
                //始終都是取left的值,初始值為-child.getWidth(),當向右拖動的時候left值增大,當left大於0的時候取0
                return newLeft;
            }

            //手指釋放的時候回調
            @Override
            public void onViewReleased(View releasedChild, float xvel, float yvel) {
                int childWidth = releasedChild.getWidth();
                //0~1f
                float offset = (childWidth + releasedChild.getLeft()) * 1.0f / childWidth;

                mDragHelper.settleCapturedViewAt(xvel > 0 || xvel == 0 && offset > 0.5f ? 0 : -childWidth,
                        releasedChild.getTop());
                //由於offset 取值為0~1,所以settleCapturedViewAt初始值為 -childWidth,滑動小於0.5取值也為-childWidth, 
                //大於0.5取值為0
                invalidate();
            }

            //在邊界拖動時回調
            @Override
            public void onEdgeDragStarted(int edgeFlags, int pointerId) {
                mDragHelper.captureChildView(mLeftMenuView, pointerId);
            }

            @Override
            public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
                int childWidth = changedView.getWidth();
                float offset = (float) (childWidth + left) / childWidth;
                mLeftMenuOnScreen = offset;
                changedView.setVisibility(offset == 0 ? View.INVISIBLE : View.VISIBLE);
                //offset 為0 的時候隱藏 , 不為0顯示
                invalidate();
            }

            @Override
            public int getViewHorizontalDragRange(View child) {
                //始終取值為child.getWidth()
                return mLeftMenuView == child ? child.getWidth() : 0;
            }

        });

        mDragHelper.setEdgeTrackingEnabled(ViewDragHelper.EDGE_LEFT);
        mDragHelper.setMinVelocity(minVel);
    }

    @Override
    public void computeScroll() {
        if (mDragHelper.continueSettling(true)) {
            invalidate();
        }
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);

        setMeasuredDimension(widthSize, heightSize);

        View leftMenuView = getChildAt(1);
        MarginLayoutParams lp = (MarginLayoutParams)
                leftMenuView.getLayoutParams();

        final int drawerWidthSpec = getChildMeasureSpec(widthMeasureSpec,
                mMinDrawerMargin + lp.leftMargin + lp.rightMargin,
                lp.width);
        final int drawerHeightSpec = getChildMeasureSpec(heightMeasureSpec,
                lp.topMargin + lp.bottomMargin,
                lp.height);
        //確定側滑欄尺寸
        leftMenuView.measure(drawerWidthSpec, drawerHeightSpec);


        View contentView = getChildAt(0);
        lp = (MarginLayoutParams) contentView.getLayoutParams();
        final int contentWidthSpec = MeasureSpec.makeMeasureSpec(
                widthSize - lp.leftMargin - lp.rightMargin, MeasureSpec.EXACTLY);
        final int contentHeightSpec = MeasureSpec.makeMeasureSpec(
                heightSize - lp.topMargin - lp.bottomMargin, MeasureSpec.EXACTLY);
        //確定主界面尺寸
        contentView.measure(contentWidthSpec, contentHeightSpec);

        mLeftMenuView = leftMenuView;
        mContentView = contentView;

    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        View menuView = mLeftMenuView;
        View contentView = mContentView;

        MarginLayoutParams lp = (MarginLayoutParams) contentView.getLayoutParams();     
        contentView.layout(lp.leftMargin, lp.topMargin,
                lp.leftMargin + contentView.getMeasuredWidth(),
                lp.topMargin + contentView.getMeasuredHeight());

        lp = (MarginLayoutParams) menuView.getLayoutParams();

        final int menuWidth = menuView.getMeasuredWidth();
        int childLeft = -menuWidth + (int) (menuWidth * mLeftMenuOnScreen);
        //確定側滑欄尺寸
        menuView.layout(childLeft, lp.topMargin, childLeft + menuWidth,
                lp.topMargin + menuView.getMeasuredHeight());


    }

    public void closeDrawer() {
        View menuView = mLeftMenuView;
        mLeftMenuOnScreen = 0.f;
        mDragHelper.smoothSlideViewTo(menuView, -menuView.getWidth(), menuView.getTop());
    }

    public void openDrawer() {
        View menuView = mLeftMenuView;
        mLeftMenuOnScreen = 1.0f;
        mDragHelper.smoothSlideViewTo(menuView, 0, menuView.getTop());
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        return mDragHelper.shouldInterceptTouchEvent(ev);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        mDragHelper.processTouchEvent(event);
        return true;
    }


    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        mContentView = getChildAt(0);
        mLeftMenuView = getChildAt(1);

    }

    @Override
    protected LayoutParams generateDefaultLayoutParams() {
        return new MarginLayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
    }

    @Override
    protected LayoutParams generateLayoutParams(LayoutParams p) {
        return new MarginLayoutParams(p);
    }

    @Override
    public LayoutParams generateLayoutParams(AttributeSet attrs) {
        return new MarginLayoutParams(getContext(), attrs);
    }
}

需要注意的地方我都在程序中注釋了。

3、LeftMenuFragment文件

package com.ws.viewdragdemo;

import android.content.Context;
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.BaseAdapter;
import android.widget.ListView;
import android.widget.TextView;

/**
 * Created by Administrator on 6/7 0007.
 */
public class LeftMenuFragment extends Fragment {

    private ListView lv;
    private Context mContext;

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.left_menu_frgment, container, false);
        lv = (ListView) view.findViewById(R.id.lv);
        lv.setAdapter(new BaseAdapter() {
            @Override
            public int getCount() {
                return 10;
            }

            @Override
            public Object getItem(int i) {
                return i;
            }

            @Override
            public long getItemId(int i) {
                return i;
            }

            @Override
            public View getView(int i, View view, ViewGroup viewGroup) {
                TextView tv = new TextView(mContext);
                tv.setPadding(16, 16, 16, 16);
                tv.setText("我是側滑欄條目" + i);
                return tv;
            }
        });
        return view;
    }

    @Override
    public void onAttach(Context context) {
        super.onAttach(context);
        this.mContext = context;
    }
}

4、MainActivity文件

package com.ws.viewdragdemo;

import android.os.Bundle;
import android.support.v4.app.FragmentManager;
import android.support.v7.app.AppCompatActivity;

public class MainActivity extends AppCompatActivity {

    private LeftMenuFragment mLeftMenuFragment;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        FragmentManager fm = getSupportFragmentManager();
        mLeftMenuFragment = (LeftMenuFragment) fm.findFragmentById(R.id.id_container_menu);
        if (mLeftMenuFragment == null) {
            fm.beginTransaction().add(R.id.id_container_menu, mLeftMenuFragment = new LeftMenuFragment())
                    .commit();
        }
    }
}

 

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved