如何从屏幕的任何地方拖动底片?
How to drag bottomsheet from anywhere in the screen?
我正在尝试实现类似 youtube 的体验,并允许用户从屏幕的任何位置拖动底部 sheet。我尝试了很多解决方案,但没有任何帮助。
我找到了最终从这个解决方案中得到启发的解决方案
首先,创建 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);
}}
就是这样!
我正在尝试实现类似 youtube 的体验,并允许用户从屏幕的任何位置拖动底部 sheet。我尝试了很多解决方案,但没有任何帮助。
我找到了最终从这个解决方案中得到启发的解决方案
首先,创建 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);
}}
就是这样!