Children match_parent 使用 viewdraghelper 在自定义视图中忽略 parent

Children match_parent ignoring parent in custom view with viewdraghelper

我最近一直在研究自定义视图,并试图根据位于此处的教程制作一个特定示例:http://flavienlaurent.com/blog/2013/08/28/each-navigation-drawer-hides-a-viewdraghelper/

我会添加相关代码,防止死机丢失信息link。本教程的作者解释了如何创建类似于 YouTube android 应用程序播放器行为的 ViewDragHelper,这些是他提供的代码

activity_main.xml

<FrameLayout

    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

<ListView
        android:id="@+id/listView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:tag="list"
        />

<com.example.vdh.YoutubeLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/youtubeLayout"
        android:orientation="vertical"
        android:visibility="visible">

    <TextView
            android:id="@+id/viewHeader"
            android:layout_width="match_parent"
            android:layout_height="128dp"
            android:fontFamily="sans-serif-thin"
            android:textSize="25sp"
            android:tag="text"
            android:gravity="center"
            android:textColor="@android:color/white"
            android:background="#AD78CC"/>

    <TextView
            android:id="@+id/viewDesc"
            android:tag="desc"
            android:textSize="35sp"
            android:gravity="center"
            android:text="Loreum Loreum"
            android:textColor="@android:color/white"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:background="#FF00FF"/>

</com.example.vdh.YoutubeLayout>

YoutubeLayout.java

public class YoutubeLayout extends ViewGroup {

    private final ViewDragHelper mDragHelper;

    private View mHeaderView;
    private View mDescView;

    private float mInitialMotionX;
    private float mInitialMotionY;

    private int mDragRange;
    private int mTop;
    private float mDragOffset;


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

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

    @Override
    protected void onFinishInflate() {
        mHeaderView = findViewById(R.id.viewHeader);
        mDescView = findViewById(R.id.viewDesc);
    }

    public YoutubeLayout(Context context, AttributeSet attrs, int defStyle) {
      super(context, attrs, defStyle);
      mDragHelper = ViewDragHelper.create(this, 1f, new DragHelperCallback());
    }

    public void maximize() {
        smoothSlideTo(0f);
    }

    boolean smoothSlideTo(float slideOffset) {
        final int topBound = getPaddingTop();
        int y = (int) (topBound + slideOffset * mDragRange);

        if (mDragHelper.smoothSlideViewTo(mHeaderView, mHeaderView.getLeft(), y)) {
            ViewCompat.postInvalidateOnAnimation(this);
            return true;
        }
        return false;
    }

    private class DragHelperCallback extends ViewDragHelper.Callback {

      @Override
      public boolean tryCaptureView(View child, int pointerId) {
            return child == mHeaderView;
      }

        @Override
      public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
          mTop = top;

          mDragOffset = (float) top / mDragRange;

            mHeaderView.setPivotX(mHeaderView.getWidth());
            mHeaderView.setPivotY(mHeaderView.getHeight());
            mHeaderView.setScaleX(1 - mDragOffset / 2);
            mHeaderView.setScaleY(1 - mDragOffset / 2);

            mDescView.setAlpha(1 - mDragOffset);

            requestLayout();
      }

      @Override
      public void onViewReleased(View releasedChild, float xvel, float yvel) {
          int top = getPaddingTop();
          if (yvel > 0 || (yvel == 0 && mDragOffset > 0.5f)) {
              top += mDragRange;
          }
          mDragHelper.settleCapturedViewAt(releasedChild.getLeft(), top);
      }

      @Override
      public int getViewVerticalDragRange(View child) {
          return mDragRange;
      }

      @Override
      public int clampViewPositionVertical(View child, int top, int dy) {
          final int topBound = getPaddingTop();
          final int bottomBound = getHeight() - mHeaderView.getHeight() - mHeaderView.getPaddingBottom();

          final int newTop = Math.min(Math.max(top, topBound), bottomBound);
          return newTop;
      }

    }

    @Override
    public void computeScroll() {
      if (mDragHelper.continueSettling(true)) {
          ViewCompat.postInvalidateOnAnimation(this);
      }
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
      final int action = MotionEventCompat.getActionMasked(ev);

      if (( action != MotionEvent.ACTION_DOWN)) {
          mDragHelper.cancel();
          return super.onInterceptTouchEvent(ev);
      }

      if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
          mDragHelper.cancel();
          return false;
      }

      final float x = ev.getX();
      final float y = ev.getY();
      boolean interceptTap = false;

      switch (action) {
          case MotionEvent.ACTION_DOWN: {
              mInitialMotionX = x;
              mInitialMotionY = y;
                interceptTap = mDragHelper.isViewUnder(mHeaderView, (int) x, (int) y);
              break;
          }

          case MotionEvent.ACTION_MOVE: {
              final float adx = Math.abs(x - mInitialMotionX);
              final float ady = Math.abs(y - mInitialMotionY);
              final int slop = mDragHelper.getTouchSlop();
              if (ady > slop && adx > ady) {
                  mDragHelper.cancel();
                  return false;
              }
          }
      }

      return mDragHelper.shouldInterceptTouchEvent(ev) || interceptTap;
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
      mDragHelper.processTouchEvent(ev);

      final int action = ev.getAction();
        final float x = ev.getX();
        final float y = ev.getY();

        boolean isHeaderViewUnder = mDragHelper.isViewUnder(mHeaderView, (int) x, (int) y);
        switch (action & MotionEventCompat.ACTION_MASK) {
          case MotionEvent.ACTION_DOWN: {
              mInitialMotionX = x;
              mInitialMotionY = y;
              break;
          }

          case MotionEvent.ACTION_UP: {
              final float dx = x - mInitialMotionX;
              final float dy = y - mInitialMotionY;
              final int slop = mDragHelper.getTouchSlop();
              if (dx * dx + dy * dy < slop * slop && isHeaderViewUnder) {
                  if (mDragOffset == 0) {
                      smoothSlideTo(1f);
                  } else {
                      smoothSlideTo(0f);
                  }
              }
              break;
          }
      }


      return isHeaderViewUnder && isViewHit(mHeaderView, (int) x, (int) y) || isViewHit(mDescView, (int) x, (int) y);
    }


    private boolean isViewHit(View view, int x, int y) {
        int[] viewLocation = new int[2];
        view.getLocationOnScreen(viewLocation);
        int[] parentLocation = new int[2];
        this.getLocationOnScreen(parentLocation);
        int screenX = parentLocation[0] + x;
        int screenY = parentLocation[1] + y;
        return screenX >= viewLocation[0] && screenX < viewLocation[0] + view.getWidth() &&
                screenY >= viewLocation[1] && screenY < viewLocation[1] + view.getHeight();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        measureChildren(widthMeasureSpec, heightMeasureSpec);

        int maxWidth = MeasureSpec.getSize(widthMeasureSpec);
        int maxHeight = MeasureSpec.getSize(heightMeasureSpec);

        setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, 0),
                resolveSizeAndState(maxHeight, heightMeasureSpec, 0));
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
      mDragRange = getHeight() - mHeaderView.getHeight();

        mHeaderView.layout(
                0,
                mTop,
                r,
                mTop + mHeaderView.getMeasuredHeight());

        mDescView.layout(
                0,
                mTop + mHeaderView.getMeasuredHeight(),
                r,
                mTop  + b);
    }
}

作者指出 onLayoutonMeasure 写得不好,我认为这些(或其中之一)可能是 children 之一出现问题的原因.

对于我的 objective,我将 mDescView 替换为包含相应 TextViewFramelayout 视图。它们的高度都设置为 match_parent 并且 parent (mDescView) 确实正确设置了它的高度,但是它的 children (TextView inside mDescView) 忽略 parent 高度并拉伸以适应它们的高度等于屏幕高度(或自定义视图高度,无法区分)。这是一个问题,因为 mDescView children 永远不会根据 parent 到 match_parent 正确调整他们的高度,我一直在寻找解决方案几天,但是 none 被发现,通过研究我找不到发生这种情况的原因。

这是这个问题的结果,请注意 TextView 的高度如何大于它的 parent mDescView 尽管它们的高度都设置为 match_parent

所以我的问题是,如何让 parent(或任何 parents)的 child(children)匹配他们的 parent他们应该的身高。

作为一个额外的请求,如果可能的话,谁能解释为什么作者认为他的一些方法不是 best/right 的方法,以及应该如何 correctly/better 来代替。

我终于纠正了这个问题,虽然我仍然不知道这是否是正确的方法。我的解决方案是在 onLayout 期间设置两个视图的高度 我还在第一次调用此方法时将其设置为仅 运行,因此特定语句仅 运行 一次且仅在第一次(当 firstRuntrue)

@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
    mDragRange = getHeight() - mHeaderView.getHeight();

    if (firstRun) {
        firstRun = false;
        mDescView.getLayoutParams().height = getHeight() - mHeaderView.getMeasuredHeight();
    }

    mHeaderView.layout(
            0,
            mTop,
            r,
            mTop + mHeaderView.getMeasuredHeight());

    mDescView.layout(
            0,
            mTop + mHeaderView.getMeasuredHeight(),
            r,
            mTop  + b);
}