我正在尝试实现类似youtube的体验,并允许用户从屏幕上的任意位置拖动底页。我尝试了许多解决方案,但无济于事。

最佳答案

我发现该解决方案最终受此解决方案启发Allow BottomSheet to slide up after threshold is reached on an area outside

首先,创建CustomCoordinatorLayout:

public class CustomCoordinatorLayout extends CoordinatorLayout {

private View proxyView;

public CustomCoordinatorLayout(@NonNull Context context) {
    super(context);
}

public CustomCoordinatorLayout(
    @NonNull Context context,
    @Nullable AttributeSet attrs
) {
    super(context, attrs);
}

public CustomCoordinatorLayout(
    @NonNull Context context,
    @Nullable AttributeSet attrs,
    int defStyleAttr
) {
    super(context, attrs, defStyleAttr);
}

@Override
public boolean isPointInChildBounds(
    @NonNull View child,
    int x,
    int y
) {

    if (super.isPointInChildBounds(child, x, y)) {
        return true;
    }

    // we want to intercept touch events if they are
    // within the proxy view bounds, for this reason
    // we instruct the coordinator layout to check
    // if this is true and let the touch delegation
    // respond to that result
    if (proxyView != null) {
        return super.isPointInChildBounds(proxyView, x, y);
    }

    return false;

}

// for this example we are only interested in intercepting
// touch events for a single view, if more are needed use
// a List<View> viewList instead and iterate in
// isPointInChildBounds
public void setProxyView(View proxyView) {
    this.proxyView = proxyView;
}

}

然后创建CustomBottomSheetBehavior:
    public class CustomBottomSheetBehavior<V extends View> extends
    BottomSheetBehavior<V> {

    // we'll use the device's touch slop value to find out when a tap
    // becomes a scroll by checking how far the finger moved to be
    // considered a scroll. if the finger moves more than the touch
    // slop then it's a scroll, otherwise it is just a tap and we
    // ignore the touch events
    private int touchSlop;
    private float initialY;
    private boolean ignoreUntilClose;

    public CustomBottomSheetBehavior(
        @NonNull Context context,
        @Nullable AttributeSet attrs
    ) {
        super(context, attrs);
        touchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
    }

    @Override
    public boolean onInterceptTouchEvent(
        @NonNull CoordinatorLayout parent,
        @NonNull V child,
        @NonNull MotionEvent event
    ) {

        // touch events are ignored if the bottom sheet is already
        // open and we save that state for further processing
        if (getState() == STATE_EXPANDED) {

            ignoreUntilClose = true;
            return super.onInterceptTouchEvent(parent, child, event);

        }

        switch (event.getAction()) {

            // this is the first event we want to begin observing
            // so we set the initial value for further processing
            // as a positive value to make things easier
            case MotionEvent.ACTION_DOWN:
                initialY = Math.abs(event.getRawY());
                return super.onInterceptTouchEvent(parent, child, event);

            // if the last bottom sheet state was not open then
            // we check if the current finger movement has exceed
            // the touch slop in which case we return true to tell
            // the system we are consuming the touch event
            // otherwise we let the default handling behavior
            // since we don't care about the direction of the
            // movement we ensure its difference is a positive
            // integer to simplify the condition check
            case MotionEvent.ACTION_MOVE:
                return !ignoreUntilClose
                    && Math.abs(initialY - Math.abs(event.getRawY())) > touchSlop
                    || super.onInterceptTouchEvent(parent, child, event);

            // once the tap or movement is completed we reset
            // the initial values to restore normal behavior
            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_CANCEL:
                initialY = 0;
                ignoreUntilClose = false;
                return super.onInterceptTouchEvent(parent, child, event);

        }

        return super.onInterceptTouchEvent(parent, child, event);

    }

}`

然后更改布局以使用自定义协调器:
<?xml version="1.0" encoding="utf-8"?>
<com.example.CustomCoordinatorLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:id="@+id/container"
    android:layout_height="match_parent">
    <FrameLayout
        android:id="@+id/player_container"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/black"
    android:fitsSystemWindows="true">

    <com.google.android.exoplayer2.ui.PlayerView
        android:id="@+id/exo_player"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:animateLayoutChanges="true"
        android:fitsSystemWindows="true"
        app:controller_layout_id="@layout/playback_control_view"
        app:fastforward_increment="10000"
        app:rewind_increment="10000" />

      </FrameLayout>
    <include layout="@layout/bottom_sheet" />
</com.example.CustomCoordinatorLayout>

将相应的 Activity 更改为:
 public class PlayerActivity extends AppCompatActivity{

    private CustomCoordinatorLayout customCoordinatorLayout;
    private FrameLayout playerContainer;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_player);

        customCoordinatorLayout = findViewById(R.id.container);
        playerContainer = findViewById(R.id.player_container);
        customCoordinatorLayout.setProxyView(playerContainer);

}}

就是这样!

09-25 22:04