/* * Copyright (C) 2014 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.example.android.elevationdrag; import android.content.Context; 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.FrameLayout; import java.util.ArrayList; import java.util.List; /** * A {@link FrameLayout} that allows the user to drag and reposition child views. * 一个帧布局允许用户去拖动和复位子view */ public class DragFrameLayout extends FrameLayout { /** * The list of {@link View}s that will be draggable. * 允许被拖动的view列表,保存被拖动的view */ private List<View> mDragViews; /** * The {@link DragFrameLayoutController} that will be notify on drag. * 拖动的时候被DragFrameLayoutController通知 */ private DragFrameLayoutController mDragFrameLayoutController; private ViewDragHelper mDragHelper; public DragFrameLayout(Context context) { this(context, null, 0, 0); } public DragFrameLayout(Context context, AttributeSet attrs) { this(context, attrs, 0, 0); } public DragFrameLayout(Context context, AttributeSet attrs, int defStyleAttr) { this(context, attrs, defStyleAttr, 0); } public DragFrameLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); mDragViews = new ArrayList<>(); /** * Create the {@link ViewDragHelper} and set its callback. * 创建ViewDragHelper对象和设置回调,第二参数为灵敏度,如果值越大,相应的源码的helper.mTouchSlop越小,拖动开始前拖动的距离 * ViewDragHelper一个父类View用作交流的回调的渠道, * 当重要的事件和一些访问方法期望从提供的ViewDragHelper上得到更多有关父类的View的状态信息时on*方法被调用, * 回调也决定着子view的可拖动范围 */ mDragHelper = ViewDragHelper.create(this, 1.0f, new ViewDragHelper.Callback() { //tryCaptureView如何返回ture则表示可以捕获该view,你可以根据传入的第一个view参数决定哪些可以捕获 @Override public boolean tryCaptureView(View child, int pointerId) { Log.e("ViewDragHelper","pointerId = " + pointerId); return mDragViews.contains(child); } @Override public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) { super.onViewPositionChanged(changedView, left, top, dx, dy); } //限制沿水平轴拖动子视图的动作,默认实现不允许水平动作,继承的类必须复写这个方法,并提供所需的值 //可以在该方法中对child移动的边界进行控制 @Override public int clampViewPositionHorizontal(View child, int left, int dx) { int rightBound = getMeasuredWidth() - child.getMeasuredWidth(); int leftBound = 0; int newBound = Math.min(rightBound,Math.max(leftBound,left)); return newBound; } @Override public int clampViewPositionVertical(View child, int top, int dy) { int buttomBound = getMeasuredHeight() - child.getMeasuredHeight(); int topBound = 0; int newBound = Math.min(buttomBound,Math.max(topBound,top)); return newBound; } @Override public void onViewCaptured(View capturedChild, int activePointerId) { super.onViewCaptured(capturedChild, activePointerId); if (mDragFrameLayoutController != null) { mDragFrameLayoutController.onDragDrop(true); } } //当用户释放子view的时候调用 @Override public void onViewReleased(View releasedChild, float xvel, float yvel) { super.onViewReleased(releasedChild, xvel, yvel); if (mDragFrameLayoutController != null) { mDragFrameLayoutController.onDragDrop(false); } } @Override public void onViewDragStateChanged(int state) { super.onViewDragStateChanged(state); switch (state){ case ViewDragHelper.STATE_DRAGGING: Log.e("ViewDragHelper","STATE_DRAGGING"); break; case ViewDragHelper.STATE_IDLE: Log.e("ViewDragHelper","STATE_IDLE"); break; case ViewDragHelper.STATE_SETTLING: Log.e("ViewDragHelper","STATE_SETTLING"); break; } } //如果设置了setEdgeTrackingEnabled,从边界开始触摸的话将会调用该方法 @Override public void onEdgeTouched(int edgeFlags, int pointerId) { super.onEdgeTouched(edgeFlags, pointerId); switch(edgeFlags){ case ViewDragHelper.EDGE_BOTTOM: Log.e("ViewDragHelper","EDGE_BOTTOM"); break; case ViewDragHelper.EDGE_LEFT: Log.e("ViewDragHelper","EDGE_LEFT"); break; case ViewDragHelper.EDGE_RIGHT: Log.e("ViewDragHelper","EDGE_RIGHT"); break; case ViewDragHelper.EDGE_TOP: Log.e("ViewDragHelper","EDGE_TOP"); break; } } @Override public boolean onEdgeLock(int edgeFlags) { //父类默认返回的是false return super.onEdgeLock(edgeFlags); } //该方法在onEdgeTouched方法之后调用 @Override public void onEdgeDragStarted(int edgeFlags, int pointerId) { super.onEdgeDragStarted(edgeFlags, pointerId); switch(edgeFlags){ case ViewDragHelper.EDGE_BOTTOM: Log.e("ViewDragHelper","onEdgeDragStarted EDGE_BOTTOM"); break; case ViewDragHelper.EDGE_LEFT: Log.e("ViewDragHelper","onEdgeDragStarted EDGE_LEFT"); break; case ViewDragHelper.EDGE_RIGHT: Log.e("ViewDragHelper","onEdgeDragStarted EDGE_RIGHT"); break; case ViewDragHelper.EDGE_TOP: Log.e("ViewDragHelper","onEdgeDragStarted EDGE_TOP"); break; } } @Override public int getOrderedChildIndex(int index) { Log.e("ViewDragHelper","index = " + index); //父类就是直接返回index return super.getOrderedChildIndex(index); } @Override public int getViewHorizontalDragRange(View child) { return getMeasuredWidth() - child.getMeasuredWidth(); } //子类在垂直方向可拖动的幅度,view在垂直方向不可移动的话这个方法应该返回0 //当本view拦截触摸监听的话,就会调用本方法和getViewHorizontalDragRange方法,如果返回0的话,就会捕获不了 //只有这两个返回值大于0,才可以被捕获 @Override public int getViewVerticalDragRange(View child) { return getMeasuredHeight() - child.getMeasuredHeight(); } }); //设置拖动的边界 mDragHelper.setEdgeTrackingEnabled(ViewDragHelper.EDGE_LEFT); } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { final int action = ev.getActionMasked(); if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) { //对此方法的调用的结果等效于TouchEvent MotionEvent处理接收的ACTION_CANCEL事件 mDragHelper.cancel(); return false; } //传递中断事件给父view return mDragHelper.shouldInterceptTouchEvent(ev); } @Override public boolean onTouchEvent(MotionEvent ev) { //Process a touch event received by the parent view. This method will dispatch callback events // as needed before returning. The parent view's onTouchEvent implementation should call this. //处理一个触摸事件被父类接收,这个方法在返回前将需要分发回调事件,父view的onTouchEvent实现应该在这里调用 mDragHelper.processTouchEvent(ev); return true; } /** * Adds a new {@link View} to the list of views that are draggable within the container. * @param dragView the {@link View} to make draggable * 增加一个新的view到view的列表中,增加的view可以在容器中被拖动 */ public void addDragView(View dragView) { mDragViews.add(dragView); } /** * Sets the {@link DragFrameLayoutController} that will receive the drag events. * @param dragFrameLayoutController a {@link DragFrameLayoutController} */ public void setDragFrameController(DragFrameLayoutController dragFrameLayoutController) { mDragFrameLayoutController = dragFrameLayoutController; } /** * A controller that will receive the drag events. * 接受拖动事件的控制器 */ public interface DragFrameLayoutController { public void onDragDrop(boolean captured); } }