ViewDragHelper的用法

ViewDragHelper是Google2013年IO大会提出来用于解决界面控件拖拽移动的问题(位于v4兼容包下),最近在做QQ侧滑菜单那样的效果,用到了ViewDragHelper,做个笔记记录下。

结合简单的demo一点一点介绍ViewDragHelper,首先创建一个类DragLayout,继承自LinearLayout[LinearLayout继承自VIewGroup的嘛],提供相应的构造器,接着需要创建ViewDragHelper对象,发现new不出来它,打开源码发现它的构造器是私有的,继续查看源码,发现它提供了两个静态的工厂方法,如下:

/**
* Factory method to create a new ViewDragHelper.
*
* @param forParent Parent view to monitor
* @param cb Callback to provide information and receive events
* @return a new ViewDragHelper instance
*/

public static ViewDragHelper create(ViewGroup forParent, Callback cb) {
return new ViewDragHelper(forParent.getContext(), forParent, cb);
}

/**
* Factory method to create a new ViewDragHelper.
*
* @param forParent Parent view to monitor
* @param sensitivity Multiplier for how sensitive the helper should be about detecting
* the start of a drag. Larger values are more sensitive. 1.0f is normal.
* @param cb Callback to provide information and receive events
* @return a new ViewDragHelper instance
*/

public static ViewDragHelper create(ViewGroup forParent, float sensitivity, Callback cb) {
final ViewDragHelper helper = create(forParent, cb);
helper.mTouchSlop = (int) (helper.mTouchSlop * (1 / sensitivity));
return helper;
}

一个提供了两个参数,一个提供了三个参数,唯一不同的是多了一个灵敏度,并且这个灵敏度的默认值是1.0f,我这里使用两个参数的静态工厂方法创建ViewDragHelper。它的第一个参数是父View,在我的代码里,也就是DragLayout,传入this即可,最后一个参数是回调接口,实现这个回调接口即可。
到这里,我的代码如下:

public class DragLayout extends LinearLayout {

private ViewDragHelper dragHelper;

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

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

public DragLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
//创建ViewDragHelper的静态工厂方法有两个,一个带float sensitivity参数,一个不带,这个参数的默认值是1.0f
//第一个参数是父view,也就是这里的DragLayout,给个this即可
//第二个参数(若有),是灵敏度,默认值是1.0f
//第三个参数是回调接口
//ViewDragHelper.create(this,1.0f,mCallback);
dragHelper = ViewDragHelper.create(this, mCallback);
}

/**
* 该接口默认实现了public boolean tryCaptureView(View child, int pointerId)方法
* 当然还需要实现很多其他的方法
*/

private ViewDragHelper.Callback mCallback = new ViewDragHelper.Callback() {
/**
* 根据返回结果决定当前child是否可以拖拽
* @param child 当前被拖拽的view
* @param pointerId 区分多点触摸的id
* @return
*/

@Override
public boolean tryCaptureView(View child, int pointerId) {
Log.e("Test","child view:" + child.toString());
return true;
}
};

看到回调接口默认实现了public boolean tryCaptureView(View child, int pointerId)方法,第一个参数是当前被拖拽的view,第二个参数是区分多点触摸的id,暂且不管第二个参数,这个方法根据返回结果决定当前child是否可以拖拽,我先返回true,让当前所有的child都可以被拖动,在拖动的时候,打印view对象的值。
此时在MainActivity的布局中使用DragLayout,并添加两个子view(颜色区分),如下:

<me.chenfuduo.myviewdraghelperusage.drag.DragLayout 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"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:orientation="vertical"
tools:context=".MainActivity">


<LinearLayout
android:layout_gravity="center_horizontal"
android:background="#009688"
android:layout_width="200dp"
android:layout_height="200dp"></LinearLayout>


<LinearLayout
android:layout_marginTop="20dp"
android:layout_gravity="center_horizontal"
android:background="#FF4081"
android:layout_width="200dp"
android:layout_height="200dp"></LinearLayout>


</me.chenfuduo.myviewdraghelperusage.drag.DragLayout>

ok,此时运行,去拖动view,发现并没有打印view对象的值,因为我们还缺少一步没有去做。
接下来,我们需要把触摸事件交给ViewDragHelper去处理,回忆在自定义控件里,我们重写的几个方法:

public boolean dispatchTouchEvent(MotionEvent ev)
public boolean onInterceptTouchEvent(MotionEvent ev)
public boolean onTouchEvent(MotionEvent event)

我们这里不需要事件分发的方法,重写后两个即可。首先是public boolean onInterceptTouchEvent(MotionEvent ev),这个方法将事件拦截下来,相当于把自定义控件的事件交给ViewDragHelper去处理。

/**
* 将事件拦截下来,相当于把自定义控件的事件交给ViewDragHelper去处理
* @param ev
* @return
*/

@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
//传递给ViewDragHelper
return dragHelper.shouldInterceptTouchEvent(ev);
}

接着,将拦截下来的事件进行处理,onTouchEvent返回true,持续接收事件.

/**
* 将拦截下来的事件做处理
* @param event
* @return
*/

@Override
public boolean onTouchEvent(MotionEvent event) {
//对多点触摸有点问题
try {
dragHelper.processTouchEvent(event);
} catch (Exception e) {
e.printStackTrace();
}
//返回true,持续接收事件
return true;
}

此时运行程序,发现已经打印值。(不管向哪里拖动,view都会回到左边的位置)

06-29 08:39:02.224  22132-22132/me.chenfuduo.myviewdraghelperusage E/Test﹕ child view:android.widget.LinearLayout@53585d10
06-29 08:39:03.876 22132-22132/me.chenfuduo.myviewdraghelperusage E/Test﹕ child view:android.widget.LinearLayout@53585d10
06-29 08:39:05.324 22132-22132/me.chenfuduo.myviewdraghelperusage E/Test﹕ child view:android.widget.LinearLayout@53585d10
06-29 08:39:08.196 22132-22132/me.chenfuduo.myviewdraghelperusage E/Test﹕ child view:android.widget.LinearLayout@53585f18

ok,下面就来控制view拖拽的距离。为了实现控制两个view拖拽距离的控制,得先找到刚刚在MainActivity的布局中定义的两个LinearLayout,怎么找到这两个LinearLayout?(没有id啊)
重写下面的方法onFinishInflate(),它在xml文件被填充结束后调用,这个时候它的所有的子孩子都被添加了。

@Override
protected void onFinishInflate() {
super.onFinishInflate();
blueView = (ViewGroup) getChildAt(0);
pinkView = (ViewGroup) getChildAt(1);
}

我在xml文件中,每个LinearLayout都有一个背景颜色,第一个是蓝色的,第二个是粉红色的。这样就得到了两个view.
重写callback中的方法clampViewPositionHorizontal(View child, int left, int dx)clampViewPositionVertical(View child, int top, int dy)

/**
* 根据建议值修正将要移动到的位置(水平)
* @param child
* @param left
* @param dx
* @return
*/

@Override
public int clampViewPositionHorizontal(View child, int left, int dx) {
super.clampViewPositionHorizontal(child, left, dx);
//向左 负的
Log.e("Test","left:" + left + " dx:" + dx);
return left;
}

@Override
public int clampViewPositionVertical(View child, int top, int dy) {
super.clampViewPositionVertical(child, top, dy);
//向上 负的
Log.e("Test","top:" + top + " dy:" + dy);
return top;
}

实现的效果图如下:
11
它们分别负责:根据建议值修正将要移动到的位置(水平或者垂直)。当然这里可能会移除超出屏幕的边界。这个时候修改public boolean tryCaptureView(View child, int pointerId),让其不是总是返回true,如下:

@Override
public boolean tryCaptureView(View child, int pointerId) {
Log.e("Test", "child view:" + child.toString());
return child == blueView;
}

此时我们看到红色的view能打印出view对象的值,但是不能移动,只有蓝色的可以移动。
总结上面的,使用ViewDragerHelper可以分为三个步骤:

  • 初始化 使用静态工厂方法创建ViewDragHelper对象
  • 接收触摸事件
  • 重写Callback的方法
    后面会写一个QQ侧滑面板的综合案例,本篇的代码和后面的一起上传。

晚饭后看到这样的一篇文章,说是FrameLayout的效率比LinearLayout和RelativeLayout的效率高。


  • RelativeLayout会让子View调用两次onMeasure,LinearLayout在有weight时,也会调用子View两次onMeasure。
  • RelativeLayout的子View如果高度和RelativeLayout不同,则会引发效率问题,当子View很复杂时,这个问题会更加严重。如果可以,使用padding代替margin。
  • 在不响应层级深度的情况下,使用Linearlayout和FrameLayout而不是RelativeLayout(上帝是公平的,灵活性导致了效率上的问题)。

下面是View的measure()方法的源码:

/**
* <p>
* This is called to find out how big a view should be. The parent
* supplies constraint information in the width and height parameters.
* </p>
*
* <p>
* The actual measurement work of a view is performed in
* {@link #onMeasure(int, int)}, called by this method. Therefore, only
* {@link #onMeasure(int, int)} can and must be overridden by subclasses.
* </p>
*
*
* @param widthMeasureSpec Horizontal space requirements as imposed by the
* parent
* @param heightMeasureSpec Vertical space requirements as imposed by the
* parent
*
* @see #onMeasure(int, int)
*/

public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
boolean optical = isLayoutModeOptical(this);
if (optical != isLayoutModeOptical(mParent)) {
Insets insets = getOpticalInsets();
int oWidth = insets.left + insets.right;
int oHeight = insets.top + insets.bottom;
widthMeasureSpec = MeasureSpec.adjust(widthMeasureSpec, optical ? -oWidth : oWidth);
heightMeasureSpec = MeasureSpec.adjust(heightMeasureSpec, optical ? -oHeight : oHeight);
}

// Suppress sign extension for the low bytes
long key = (long) widthMeasureSpec << 32 | (long) heightMeasureSpec & 0xffffffffL;
if (mMeasureCache == null) mMeasureCache = new LongSparseLongArray(2);

final boolean forceLayout = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT;
final boolean isExactly = MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY &&
MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY;
final boolean matchingSize = isExactly &&
getMeasuredWidth() == MeasureSpec.getSize(widthMeasureSpec) &&
getMeasuredHeight() == MeasureSpec.getSize(heightMeasureSpec);
if (forceLayout || !matchingSize &&
(widthMeasureSpec != mOldWidthMeasureSpec ||
heightMeasureSpec != mOldHeightMeasureSpec)) {

// first clears the measured dimension flag
mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;

resolveRtlPropertiesIfNeeded();

int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key);
if (cacheIndex < 0 || sIgnoreMeasureCache) {
// measure ourselves, this should set the measured dimension flag back
onMeasure(widthMeasureSpec, heightMeasureSpec);
mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
} else {
long value = mMeasureCache.valueAt(cacheIndex);
// Casting a long to int drops the high 32 bits, no mask needed
setMeasuredDimensionRaw((int) (value >> 32), (int) value);
mPrivateFlags3 |= PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
}

// flag not set, setMeasuredDimension() was not invoked, we raise
// an exception to warn the developer
if ((mPrivateFlags & PFLAG_MEASURED_DIMENSION_SET) != PFLAG_MEASURED_DIMENSION_SET) {
throw new IllegalStateException("onMeasure() did not set the"
+ " measured dimension by calling"
+ " setMeasuredDimension()");
}

mPrivateFlags |= PFLAG_LAYOUT_REQUIRED;
}

mOldWidthMeasureSpec = widthMeasureSpec;
mOldHeightMeasureSpec = heightMeasureSpec;

mMeasureCache.put(key, ((long) mMeasuredWidth) << 32 |
(long) mMeasuredHeight & 0xffffffffL); // suppress sign extension
}

注意到这里的方法是final的 也就是无法被继承改变了,在这里调用了onMeasure(),而每个View的子类也在onMeasure中真正完成了测量,而ViewGroup们也在onMeasure中完成了对子View的测量。
注意到有widthMeasureSpecheightMeasureSpec这2个变量。这两个变量描述的是父View对子View的大小的要求。xxMeasureSpec(widthMeasureSpecheightMeasureSpec)是int类型,它的高16位是mode,低16位是size;用MeasureSpec.getMode(),MeasureSpec.getSize()可以计算出mode和size.
mode分为3种:MeasureSpec.EXACTLY,MeasureSpec.AT_MOST,MeasureSpec.UNSPECIFIED.主要看AT_MOST(最多这么大)和EXACTLY(确切这么大);
下面是ViewGroup.getChildMeasureSpec()方法。作用是根据父View的Spec生成子View的Spec,传入的spec是父View的spec,padding是父View的padding,childDemension对应的是xml中我们常写的layout_width,layout_height.

/**
* Does the hard part of measureChildren: figuring out the MeasureSpec to
* pass to a particular child. This method figures out the right MeasureSpec
* for one dimension (height or width) of one child view.
*
* The goal is to combine information from our MeasureSpec with the
* LayoutParams of the child to get the best possible results. For example,
* if the this view knows its size (because its MeasureSpec has a mode of
* EXACTLY), and the child has indicated in its LayoutParams that it wants
* to be the same size as the parent, the parent should ask the child to
* layout given an exact size.
*
* @param spec The requirements for this view
* @param padding The padding of this view for the current dimension and
* margins, if applicable
* @param childDimension How big the child wants to be in the current
* dimension
* @return a MeasureSpec integer for the child
*/

public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
int specMode = MeasureSpec.getMode(spec);
int specSize = MeasureSpec.getSize(spec);

int size = Math.max(0, specSize - padding);

int resultSize = 0;
int resultMode = 0;

switch (specMode) {
// Parent has imposed an exact size on us
case MeasureSpec.EXACTLY:
if (childDimension >= 0) {
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size. So be it.
resultSize = size;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size. It can't be
// bigger than us.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;

// Parent has imposed a maximum size on us
case MeasureSpec.AT_MOST:
if (childDimension >= 0) {
// Child wants a specific size... so be it
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size, but our size is not fixed.
// Constrain child to not be bigger than us.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size. It can't be
// bigger than us.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;

// Parent asked to see how big we want to be
case MeasureSpec.UNSPECIFIED:
if (childDimension >= 0) {
// Child wants a specific size... let him have it
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size... find out how big it should
// be
resultSize = 0;
resultMode = MeasureSpec.UNSPECIFIED;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size.... find out how
// big it should be
resultSize = 0;
resultMode = MeasureSpec.UNSPECIFIED;
}
break;
}
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}

比如说父View是100px80px,padding是10px,模式是EXACTLY,而子View是martchParent的,那子view的measure方法传入的参数为:80px60px,而mode是EXACTLY的.
View.measure()方法中的一个优化:

public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
if ((mPrivateFlags & FORCE_LAYOUT) == FORCE_LAYOUT ||
widthMeasureSpec != mOldWidthMeasureSpec ||
heightMeasureSpec != mOldHeightMeasureSpec) {
...
mOldWidthMeasureSpec = widthMeasureSpec;
mOldHeightMeasureSpec = heightMeasureSpec;
}

如果我们或者我们的子View没有要求强制刷新,而父View给子View的传入值也没有变化(也就是说子View的位置没变化),就不会做无谓的测量。View的measure流程基本就是这样。下面来看一下RelativeLayout的onMeasure()中干了点啥吧.

  • 首先RelativeLayout中由于有依赖关系,而且依赖关系可能和xml中view的顺序并不相同,在确定每个人的位置的时候,就需要先给所有的子View排序一下。
  • 又因为RelativeLayout允许A,B 2个子View,横向上B依赖A,纵向上A依赖B。所以需要横向纵向各排序依次。
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (mDirtyHierarchy) {
mDirtyHierarchy = false;
sortChildren();
}

int myWidth = -1;
int myHeight = -1;

int width = 0;
int height = 0;

final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
final int widthSize = MeasureSpec.getSize(widthMeasureSpec);
final int heightSize = MeasureSpec.getSize(heightMeasureSpec);

// Record our dimensions if they are known;
if (widthMode != MeasureSpec.UNSPECIFIED) {
myWidth = widthSize;
}

if (heightMode != MeasureSpec.UNSPECIFIED) {
myHeight = heightSize;
}

if (widthMode == MeasureSpec.EXACTLY) {
width = myWidth;
}

if (heightMode == MeasureSpec.EXACTLY) {
height = myHeight;
}

mHasBaselineAlignedChild = false;

View ignore = null;
int gravity = mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK;
final boolean horizontalGravity = gravity != Gravity.START && gravity != 0;
gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
final boolean verticalGravity = gravity != Gravity.TOP && gravity != 0;

int left = Integer.MAX_VALUE;
int top = Integer.MAX_VALUE;
int right = Integer.MIN_VALUE;
int bottom = Integer.MIN_VALUE;

boolean offsetHorizontalAxis = false;
boolean offsetVerticalAxis = false;

if ((horizontalGravity || verticalGravity) && mIgnoreGravity != View.NO_ID) {
ignore = findViewById(mIgnoreGravity);
}

final boolean isWrapContentWidth = widthMode != MeasureSpec.EXACTLY;
final boolean isWrapContentHeight = heightMode != MeasureSpec.EXACTLY;

// We need to know our size for doing the correct computation of children positioning in RTL
// mode but there is no practical way to get it instead of running the code below.
// So, instead of running the code twice, we just set the width to a "default display width"
// before the computation and then, as a last pass, we will update their real position with
// an offset equals to "DEFAULT_WIDTH - width".
final int layoutDirection = getLayoutDirection();
if (isLayoutRtl() && myWidth == -1) {
myWidth = DEFAULT_WIDTH;
}

View[] views = mSortedHorizontalChildren;
int count = views.length;

for (int i = 0; i < count; i++) {
View child = views[i];
if (child.getVisibility() != GONE) {
LayoutParams params = (LayoutParams) child.getLayoutParams();
int[] rules = params.getRules(layoutDirection);

applyHorizontalSizeRules(params, myWidth, rules);
measureChildHorizontal(child, params, myWidth, myHeight);

if (positionChildHorizontal(child, params, myWidth, isWrapContentWidth)) {
offsetHorizontalAxis = true;
}
}
}

views = mSortedVerticalChildren;
count = views.length;
final int targetSdkVersion = getContext().getApplicationInfo().targetSdkVersion;

for (int i = 0; i < count; i++) {
View child = views[i];
if (child.getVisibility() != GONE) {
LayoutParams params = (LayoutParams) child.getLayoutParams();

applyVerticalSizeRules(params, myHeight);
measureChild(child, params, myWidth, myHeight);
if (positionChildVertical(child, params, myHeight, isWrapContentHeight)) {
offsetVerticalAxis = true;
}

if (isWrapContentWidth) {
if (isLayoutRtl()) {
if (targetSdkVersion < Build.VERSION_CODES.KITKAT) {
width = Math.max(width, myWidth - params.mLeft);
} else {
width = Math.max(width, myWidth - params.mLeft - params.leftMargin);
}
} else {
if (targetSdkVersion < Build.VERSION_CODES.KITKAT) {
width = Math.max(width, params.mRight);
} else {
width = Math.max(width, params.mRight + params.rightMargin);
}
}
}

if (isWrapContentHeight) {
if (targetSdkVersion < Build.VERSION_CODES.KITKAT) {
height = Math.max(height, params.mBottom);
} else {
height = Math.max(height, params.mBottom + params.bottomMargin);
}
}

if (child != ignore || verticalGravity) {
left = Math.min(left, params.mLeft - params.leftMargin);
top = Math.min(top, params.mTop - params.topMargin);
}

if (child != ignore || horizontalGravity) {
right = Math.max(right, params.mRight + params.rightMargin);
bottom = Math.max(bottom, params.mBottom + params.bottomMargin);
}
}
}

if (mHasBaselineAlignedChild) {
for (int i = 0; i < count; i++) {
View child = getChildAt(i);
if (child.getVisibility() != GONE) {
LayoutParams params = (LayoutParams) child.getLayoutParams();
alignBaseline(child, params);

if (child != ignore || verticalGravity) {
left = Math.min(left, params.mLeft - params.leftMargin);
top = Math.min(top, params.mTop - params.topMargin);
}

if (child != ignore || horizontalGravity) {
right = Math.max(right, params.mRight + params.rightMargin);
bottom = Math.max(bottom, params.mBottom + params.bottomMargin);
}
}
}
}

if (isWrapContentWidth) {
// Width already has left padding in it since it was calculated by looking at
// the right of each child view
width += mPaddingRight;

if (mLayoutParams != null && mLayoutParams.width >= 0) {
width = Math.max(width, mLayoutParams.width);
}

width = Math.max(width, getSuggestedMinimumWidth());
width = resolveSize(width, widthMeasureSpec);

if (offsetHorizontalAxis) {
for (int i = 0; i < count; i++) {
View child = getChildAt(i);
if (child.getVisibility() != GONE) {
LayoutParams params = (LayoutParams) child.getLayoutParams();
final int[] rules = params.getRules(layoutDirection);
if (rules[CENTER_IN_PARENT] != 0 || rules[CENTER_HORIZONTAL] != 0) {
centerHorizontal(child, params, width);
} else if (rules[ALIGN_PARENT_RIGHT] != 0) {
final int childWidth = child.getMeasuredWidth();
params.mLeft = width - mPaddingRight - childWidth;
params.mRight = params.mLeft + childWidth;
}
}
}
}
}

if (isWrapContentHeight) {
// Height already has top padding in it since it was calculated by looking at
// the bottom of each child view
height += mPaddingBottom;

if (mLayoutParams != null && mLayoutParams.height >= 0) {
height = Math.max(height, mLayoutParams.height);
}

height = Math.max(height, getSuggestedMinimumHeight());
height = resolveSize(height, heightMeasureSpec);

if (offsetVerticalAxis) {
for (int i = 0; i < count; i++) {
View child = getChildAt(i);
if (child.getVisibility() != GONE) {
LayoutParams params = (LayoutParams) child.getLayoutParams();
final int[] rules = params.getRules(layoutDirection);
if (rules[CENTER_IN_PARENT] != 0 || rules[CENTER_VERTICAL] != 0) {
centerVertical(child, params, height);
} else if (rules[ALIGN_PARENT_BOTTOM] != 0) {
final int childHeight = child.getMeasuredHeight();
params.mTop = height - mPaddingBottom - childHeight;
params.mBottom = params.mTop + childHeight;
}
}
}
}
}

if (horizontalGravity || verticalGravity) {
final Rect selfBounds = mSelfBounds;
selfBounds.set(mPaddingLeft, mPaddingTop, width - mPaddingRight,
height - mPaddingBottom);

final Rect contentBounds = mContentBounds;
Gravity.apply(mGravity, right - left, bottom - top, selfBounds, contentBounds,
layoutDirection);

final int horizontalOffset = contentBounds.left - left;
final int verticalOffset = contentBounds.top - top;
if (horizontalOffset != 0 || verticalOffset != 0) {
for (int i = 0; i < count; i++) {
View child = getChildAt(i);
if (child.getVisibility() != GONE && child != ignore) {
LayoutParams params = (LayoutParams) child.getLayoutParams();
if (horizontalGravity) {
params.mLeft += horizontalOffset;
params.mRight += horizontalOffset;
}
if (verticalGravity) {
params.mTop += verticalOffset;
params.mBottom += verticalOffset;
}
}
}
}
}

if (isLayoutRtl()) {
final int offsetWidth = myWidth - width;
for (int i = 0; i < count; i++) {
View child = getChildAt(i);
if (child.getVisibility() != GONE) {
LayoutParams params = (LayoutParams) child.getLayoutParams();
params.mLeft -= offsetWidth;
params.mRight -= offsetWidth;
}
}

}

setMeasuredDimension(width, height);
}

发现RelativeLayout会根据2次排列的结果对子View各做一次measure。而在做横向的测量时,纵向的测量结果尚未完成,只好暂时使用myHeight传入子View系统。这样必然会导致子View的高度和RelativeLayout的高度不同时,第3点中所说的优化会失效,在View系统足够复杂时,效率问题就会出现。
在我的代码里,我改了下DragLayout的继承者,使其继承自FrameLayout,并修改了布局。
实现的效果图如下:
1

猜你喜欢

转载自blog.csdn.net/hanhailong726188/article/details/47093923
今日推荐