編輯:關於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();
}
}
}
在消息通知的時候,我們經常用到兩個控件Notification和Toast。特別是重要的和需要長時間顯示的信
什麼是內存洩露?Android虛擬機的垃圾回收采用的是根搜索算法。GC會從根節點(GC Roots)開始對heap進行遍歷。到最後,部分沒有直接或者間接引用到GC Roo
本文實例講述了Android實現登錄功能的方法。分享給大家供大家參考,具體如下:安卓,在小編實習之前的那段歲月裡面,小編都沒有玩兒過,如果說玩兒過,那就是安卓手機了,咳咳
需求:Activity(fragment)跳轉的時候當前界面裂開,上下各自拉出手機屏幕,之後跳轉到相對應的Activity.整體效果圖如下思路:1,在當前Activity