編輯:關於Android編程
Drag拖拽;ViewDrag拖拽視圖,拖拽控件;ViewDragHelper拖拽視圖助手,拖拽操作類。利用ViewDragHelper類可以實現很多絢麗的效果,比如:拖拽刪除,拖拽排序,側滑欄等。本篇主要講解簡易側滑欄的實現。
注意:ViewDragHelper是作用在一個ViewGroup上,也就是說他不能直接作用到被拖拽的控件view上, 因為控件的位置是由父控件決定的。
最終效果效果圖:

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是連接ViewDragHelper與view之間的橋梁
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);
public void setMinVelocity(float minVel)
@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)來處理事件。
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可以拖拽,邊界控制等。
tryCaptureView捕獲子視圖View,如果返回true,則表示該View被捕獲。試想一個ViewGroup裡面有許多子View,如果我想拖動View0,就可以這麼處理:
return child == view0;
我們一起來看一看下面這張圖:

上圖可以看出藍色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;
}
這樣就可以實現水平拖拽,上效果圖:

原理同上。
@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;
}
實現水平+垂直拖拽:

@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);
效果圖:

//手指釋放的時候回調
@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();
}
}
效果圖:

注意:如果你拖動View添加了clickable = true 或者為Button,你會發現拖不動了,尼瑪怎麼回事?
原因是拖動的時候onInterceptTouchEvent方法,判斷是否可以捕獲,而在判斷的過程中會去判斷另外兩個回調的方法getViewHorizontalDragRange和getViewVerticalDragRange,只有這兩個方法返回大於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();
}
<!--?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>
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);
}
}
需要注意的地方我都在程序中注釋了。
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;
}
}
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();
}
}
}
詳解Android中Notification的使用方法
在消息通知的時候,我們經常用到兩個控件Notification和Toast。特別是重要的和需要長時間顯示的信
Android 內存洩露簡介、典型情景及檢測解決
什麼是內存洩露?Android虛擬機的垃圾回收采用的是根搜索算法。GC會從根節點(GC Roots)開始對heap進行遍歷。到最後,部分沒有直接或者間接引用到GC Roo
Android實現登錄功能demo示例
本文實例講述了Android實現登錄功能的方法。分享給大家供大家參考,具體如下:安卓,在小編實習之前的那段歲月裡面,小編都沒有玩兒過,如果說玩兒過,那就是安卓手機了,咳咳
Activit跳轉動畫之界面上某個位置並裂開上下拉伸動畫跳轉
需求:Activity(fragment)跳轉的時候當前界面裂開,上下各自拉出手機屏幕,之後跳轉到相對應的Activity.整體效果圖如下思路:1,在當前Activity