I have Bottomsheetdialog with Custom view having EditText and on EditText scrolling bottom sheet goes down, How to disable BottomSheetDialog dragging. Any Help Appreciated.
I might be late, but it works fine for me:
#Override
public void setupDialog(final Dialog dialog, int style) {
super.setupDialog(dialog, style);
// This should do the job
dialog.setCancelable(false);
dialog.setCanceledOnTouchOutside(true);
}
Simple solution, It solved two problem I had.
1- It block the dragging event.
2- It solves the listView scrolling problem
CoordinatorLayout.Behavior<View> behavior;
View profile_config_layout_bottom_sheet = findViewById(R.id.list_view_audience_profile_config_layout);
CoordinatorLayout.LayoutParams layoutParams = (CoordinatorLayout.LayoutParams) profile_config_layout_bottom_sheet.getLayoutParams();
behavior = layoutParams.getBehavior();
assert behavior != null;
((BottomSheetBehavior<View>) behavior).addBottomSheetCallback(new BottomSheetBehavior.BottomSheetCallback() {
#Override
public void onStateChanged(#NonNull View bottomSheet, int newState) {
if (newState == BottomSheetBehavior.STATE_DRAGGING) {
((BottomSheetBehavior<View>) behavior).setState(BottomSheetBehavior.STATE_EXPANDED);
}
}
#Override
public void onSlide(#NonNull View bottomSheet, float slideOffset) {}
});
Show the dialog with:
((BottomSheetBehavior) behavior).setState(BottomSheetBehavior.STATE_EXPANDED);
Background
Suppose you have an app you've created that has a similar UI as the one you can create via the wizard of "scrolling activity", yet you wish the scrolling flags to have snapping, as such:
<android.support.design.widget.CollapsingToolbarLayout ... app:layout_scrollFlags="scroll|exitUntilCollapsed|snap" >
The problem
As it turns out, on many cases it has issues of snapping. Sometimes the UI doesn't snap to top/bottom, making the CollapsingToolbarLayout stay in between.
Sometimes it also tries to snap to one direction, and then decides to snap to the other .
You can see both issues on the attached video here.
What I've tried
I thought it's one of the issues that I got for when I use setNestedScrollingEnabled(false) on a RecyclerView within, so I asked about it here, but then I noticed that even with the solution and without using this command at all and even when using a simple NestedScrollView (as is created by the wizard), I can still notice this behavior.
That's why I decided to report about this as an issue, here.
Sadly, I couldn't find any workaround for those weird bugs here on StackOverflow.
The question
Why does it occur, and more importantly: how can I avoid those issues while still using the behavior it's supposed to have?
EDIT: here's a nice improved Kotlin version of the accepted answer:
class RecyclerViewEx #JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyle: Int = 0) : RecyclerView(context, attrs, defStyle) {
private var mAppBarTracking: AppBarTracking? = null
private var mView: View? = null
private var mTopPos: Int = 0
private var mLayoutManager: LinearLayoutManager? = null
interface AppBarTracking {
fun isAppBarIdle(): Boolean
fun isAppBarExpanded(): Boolean
}
override fun dispatchNestedPreScroll(dx: Int, dy: Int, consumed: IntArray?, offsetInWindow: IntArray?, type: Int): Boolean {
if (mAppBarTracking == null)
return super.dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow, type)
if (type == ViewCompat.TYPE_NON_TOUCH && mAppBarTracking!!.isAppBarIdle()
&& isNestedScrollingEnabled) {
if (dy > 0) {
if (mAppBarTracking!!.isAppBarExpanded()) {
consumed!![1] = dy
return true
}
} else {
mTopPos = mLayoutManager!!.findFirstVisibleItemPosition()
if (mTopPos == 0) {
mView = mLayoutManager!!.findViewByPosition(mTopPos)
if (-mView!!.top + dy <= 0) {
consumed!![1] = dy - mView!!.top
return true
}
}
}
}
if (dy < 0 && type == ViewCompat.TYPE_TOUCH && mAppBarTracking!!.isAppBarExpanded()) {
consumed!![1] = dy
return true
}
val returnValue = super.dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow, type)
if (offsetInWindow != null && !isNestedScrollingEnabled && offsetInWindow[1] != 0)
offsetInWindow[1] = 0
return returnValue
}
override fun setLayoutManager(layout: RecyclerView.LayoutManager) {
super.setLayoutManager(layout)
mLayoutManager = layoutManager as LinearLayoutManager
}
fun setAppBarTracking(appBarTracking: AppBarTracking) {
mAppBarTracking = appBarTracking
}
fun setAppBarTracking(appBarLayout: AppBarLayout) {
val appBarIdle = AtomicBoolean(true)
val appBarExpanded = AtomicBoolean()
appBarLayout.addOnOffsetChangedListener(object : AppBarLayout.OnOffsetChangedListener {
private var mAppBarOffset = Integer.MIN_VALUE
override fun onOffsetChanged(appBarLayout: AppBarLayout, verticalOffset: Int) {
if (mAppBarOffset == verticalOffset)
return
mAppBarOffset = verticalOffset
appBarExpanded.set(verticalOffset == 0)
appBarIdle.set(mAppBarOffset >= 0 || mAppBarOffset <= -appBarLayout.totalScrollRange)
}
})
setAppBarTracking(object : AppBarTracking {
override fun isAppBarIdle(): Boolean = appBarIdle.get()
override fun isAppBarExpanded(): Boolean = appBarExpanded.get()
})
}
override fun fling(velocityX: Int, inputVelocityY: Int): Boolean {
var velocityY = inputVelocityY
if (mAppBarTracking != null && !mAppBarTracking!!.isAppBarIdle()) {
val vc = ViewConfiguration.get(context)
velocityY = if (velocityY < 0) -vc.scaledMinimumFlingVelocity
else vc.scaledMinimumFlingVelocity
}
return super.fling(velocityX, velocityY)
}
}
Update
I have changed the code slightly to address remaining issues - at least the ones that I can reproduce. The key update was to dispose of dy only when the AppBar is expanded or collapsed. In the first iteration, dispatchNestedPreScroll() was disposing of scroll without checking the status of the AppBar for a collapsed state.
Other changes are minor and fall under the category of clean up. The code blocks are updated below.
This answer addresses the question's issue regarding RecyclerView. The other answer I have given still stands and applies here. RecyclerView has the same issues as NestedScrollView that were introduced in 26.0.0-beta2 of the support libraries.
The code below is base upon this answer to a related question but includes the fix for the erratic behavior of the AppBar. I have removed the code that fixed the odd scrolling because it no longer seems to be needed.
AppBarTracking.java
public interface AppBarTracking {
boolean isAppBarIdle();
boolean isAppBarExpanded();
}
MyRecyclerView.java
public class MyRecyclerView extends RecyclerView {
public MyRecyclerView(Context context) {
this(context, null);
}
public MyRecyclerView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public MyRecyclerView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
private AppBarTracking mAppBarTracking;
private View mView;
private int mTopPos;
private LinearLayoutManager mLayoutManager;
#Override
public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow,
int type) {
// App bar latching trouble is only with this type of movement when app bar is expanded
// or collapsed. In touch mode, everything is OK regardless of the open/closed status
// of the app bar.
if (type == ViewCompat.TYPE_NON_TOUCH && mAppBarTracking.isAppBarIdle()
&& isNestedScrollingEnabled()) {
// Make sure the AppBar stays expanded when it should.
if (dy > 0) { // swiped up
if (mAppBarTracking.isAppBarExpanded()) {
// Appbar can only leave its expanded state under the power of touch...
consumed[1] = dy;
return true;
}
} else { // swiped down (or no change)
// Make sure the AppBar stays collapsed when it should.
// Only dy < 0 will open the AppBar. Stop it from opening by consuming dy if needed.
mTopPos = mLayoutManager.findFirstVisibleItemPosition();
if (mTopPos == 0) {
mView = mLayoutManager.findViewByPosition(mTopPos);
if (-mView.getTop() + dy <= 0) {
// Scroll until scroll position = 0 and AppBar is still collapsed.
consumed[1] = dy - mView.getTop();
return true;
}
}
}
}
boolean returnValue = super.dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow, type);
// Fix the scrolling problems when scrolling is disabled. This issue existed prior
// to 26.0.0-beta2.
if (offsetInWindow != null && !isNestedScrollingEnabled() && offsetInWindow[1] != 0) {
offsetInWindow[1] = 0;
}
return returnValue;
}
#Override
public void setLayoutManager(RecyclerView.LayoutManager layout) {
super.setLayoutManager(layout);
mLayoutManager = (LinearLayoutManager) getLayoutManager();
}
public void setAppBarTracking(AppBarTracking appBarTracking) {
mAppBarTracking = appBarTracking;
}
#SuppressWarnings("unused")
private static final String TAG = "MyRecyclerView";
}
ScrollingActivity.java
public class ScrollingActivity extends AppCompatActivity
implements AppBarTracking {
private MyRecyclerView mNestedView;
private int mAppBarOffset;
private boolean mAppBarIdle = false;
private int mAppBarMaxOffset;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_scrolling);
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
mNestedView = findViewById(R.id.nestedView);
final AppBarLayout appBar = findViewById(R.id.app_bar);
appBar.addOnOffsetChangedListener(new AppBarLayout.OnOffsetChangedListener() {
#Override
public final void onOffsetChanged(AppBarLayout appBarLayout, int verticalOffset) {
mAppBarOffset = verticalOffset;
// mAppBarOffset = 0 if app bar is expanded; If app bar is collapsed then
// mAppBarOffset = mAppBarMaxOffset
// mAppBarMaxOffset is always <=0 (-AppBarLayout.getTotalScrollRange())
// mAppBarOffset should never be > zero or less than mAppBarMaxOffset
mAppBarIdle = (mAppBarOffset >= 0) || (mAppBarOffset <= mAppBarMaxOffset);
}
});
appBar.post(new Runnable() {
#Override
public void run() {
mAppBarMaxOffset = -appBar.getTotalScrollRange();
}
});
findViewById(R.id.disableNestedScrollingButton).setOnClickListener(new OnClickListener() {
#Override
public void onClick(final View v) {
// If the AppBar is fully expanded or fully collapsed (idle), then disable
// expansion and apply the patch; otherwise, set a flag to disable the expansion
// and apply the patch when the AppBar is idle.
setExpandEnabled(false);
}
});
findViewById(R.id.enableNestedScrollingButton).setOnClickListener(new OnClickListener() {
#Override
public void onClick(final View v) {
setExpandEnabled(true);
}
});
mNestedView.setAppBarTracking(this);
mNestedView.setLayoutManager(new LinearLayoutManager(this));
mNestedView.setAdapter(new Adapter() {
#Override
public ViewHolder onCreateViewHolder(final ViewGroup parent, final int viewType) {
return new ViewHolder(LayoutInflater.from(parent.getContext()).inflate(
android.R.layout.simple_list_item_1,
parent,
false)) {
};
}
#SuppressLint("SetTextI18n")
#Override
public void onBindViewHolder(final ViewHolder holder, final int position) {
((TextView) holder.itemView.findViewById(android.R.id.text1)).setText("item " + position);
}
#Override
public int getItemCount() {
return 100;
}
});
}
private void setExpandEnabled(boolean enabled) {
mNestedView.setNestedScrollingEnabled(enabled);
}
#Override
public boolean isAppBarExpanded() {
return mAppBarOffset == 0;
}
#Override
public boolean isAppBarIdle() {
return mAppBarIdle;
}
#SuppressWarnings("unused")
private static final String TAG = "ScrollingActivity";
}
What is happening here?
From the question, it was apparent that the layout was failing to snap the app bar closed or open as it should when the user's finger was not on the screen. When dragging, the app bar behaves as it should.
In version 26.0.0-beta2, some new methods were introduced - specifically dispatchNestedPreScroll() with a new type argument. The type argument specifies if the movement specified by dx and dy are due to the user touching the screen ViewCompat.TYPE_TOUCH or not ViewCompat.TYPE_NON_TOUCH.
Although the specific code that causes the problem was not identified, the tack of the fix is to kill vertical movement in dispatchNestedPreScroll() (dispose of dy) when needed by not letting vertical movement propagate. In effect, the app bar is to be latched into place when expanded and will not allowed to start to close until it is closing through a touch gesture. The app bar will also be latched when closed until the RecyclerView is positioned at its topmost extent and there is sufficient dy to open the app bar while performing a touch gesture.
So, this is not so much a fix as much as a discouragement of problematic conditions.
The last part of the MyRecyclerView code deals with an issue that was identified in this question dealing with improper scroll movements when nested scrolling is disabled. This is the part that comes after the call to the super of dispatchNestedPreScroll() that changes the value of offsetInWindow[1]. The thinking behind this code is the same as presented in the accepted answer for the question. The only difference is that since the underlying nested scrolling code has changed, the argument offsetInWindow is sometime null. Fortunately, it seems to be non-null when it matters, so the last part continues to work.
The caveat is that this "fix" is very specific to the question asked and is not a general solution. The fix will likely have a very short shelf life since I expect that such an obvious problem will be addressed shortly.
Looks like onStartNestedScroll and onStopNestedScroll calls can be reordered and it lead to "wobbly" snap. I made a small hack inside AppBarLayout.Behavior. Don't really want to mess up with all that stuff in activity as proposed by other answers.
#SuppressWarnings("unused")
public class ExtAppBarLayoutBehavior extends AppBarLayout.Behavior {
private int mStartedScrollType = -1;
private boolean mSkipNextStop;
public ExtAppBarLayoutBehavior() {
super();
}
public ExtAppBarLayoutBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
}
#Override
public boolean onStartNestedScroll(CoordinatorLayout parent, AppBarLayout child, View directTargetChild, View target, int nestedScrollAxes, int type) {
if (mStartedScrollType != -1) {
onStopNestedScroll(parent, child, target, mStartedScrollType);
mSkipNextStop = true;
}
mStartedScrollType = type;
return super.onStartNestedScroll(parent, child, directTargetChild, target, nestedScrollAxes, type);
}
#Override
public void onStopNestedScroll(CoordinatorLayout coordinatorLayout, AppBarLayout abl, View target, int type) {
if (mSkipNextStop) {
mSkipNextStop = false;
return;
}
if (mStartedScrollType == -1) {
return;
}
mStartedScrollType = -1;
// Always pass TYPE_TOUCH, because want to snap even after fling
super.onStopNestedScroll(coordinatorLayout, abl, target, ViewCompat.TYPE_TOUCH);
}
}
Usage in XML layout:
<android.support.design.widget.CoordinatorLayout>
<android.support.design.widget.AppBarLayout
app:layout_behavior="com.example.ExtAppBarLayoutBehavior">
<!-- Put here everything you usually add to AppBarLayout: CollapsingToolbarLayout, etc... -->
</android.support.design.widget.AppBarLayout>
<!-- Content: recycler for example -->
<android.support.v7.widget.RecyclerView
app:layout_behavior="#string/appbar_scrolling_view_behavior" />
...
</android.support.design.widget.CoordinatorLayout>
It is very likely that the root cause of the problem in the RecyclerView. Do not have an opportunity to dig deeper now.
Edit The code has been updated to bring it more in line with the code for the accepted answer. This answer concerns NestedScrollView while the accepted answer is about RecyclerView.
This is an issue what was introduced in the API 26.0.0-beta2 release. It does not happen on the beta 1 release or with API 25. As you noted, it also happens with API 26.0.0. Generally, the problem seems to be related to how flings and nested scrolling are handled in beta2. There was a major rewrite of nested scrolling (see "Carry on Scrolling"), so it is not surprising that this type of issue has cropped up.
My thinking is that excess scroll is not being disposed of properly somewhere in NestedScrollView. The work-around is to quietly consume certain scrolls that are "non-touch" scrolls (type == ViewCompat.TYPE_NON_TOUCH) when the AppBar is expanded or collapsed. This stops the bouncing, allows snaps and, generally, makes the AppBar better behaved.
ScrollingActivity has been modified to track the status of the AppBar to report whether it is expanded or not. A new class call "MyNestedScrollView" overrides dispatchNestedPreScroll() (the new one, see here) to manipulate the consumption of the excess scroll.
The following code should suffice to stop AppBarLayout from wobbling and refusing to snap. (XML will also have to change to accommodate MyNestedSrollView. The following only applies to support lib 26.0.0-beta2 and above.)
AppBarTracking.java
public interface AppBarTracking {
boolean isAppBarIdle();
boolean isAppBarExpanded();
}
ScrollingActivity.java
public class ScrollingActivity extends AppCompatActivity implements AppBarTracking {
private int mAppBarOffset;
private int mAppBarMaxOffset;
private MyNestedScrollView mNestedView;
private boolean mAppBarIdle = true;
#Override
protected void onCreate(Bundle savedInstanceState) {
AppBarLayout appBar;
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_scrolling);
final Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
appBar = findViewById(R.id.app_bar);
mNestedView = findViewById(R.id.nestedScrollView);
mNestedView.setAppBarTracking(this);
appBar.addOnOffsetChangedListener(new AppBarLayout.OnOffsetChangedListener() {
#Override
public void onOffsetChanged(AppBarLayout appBarLayout, int verticalOffset) {
mAppBarOffset = verticalOffset;
}
});
appBar.addOnOffsetChangedListener(new AppBarLayout.OnOffsetChangedListener() {
#Override
public final void onOffsetChanged(AppBarLayout appBarLayout, int verticalOffset) {
mAppBarOffset = verticalOffset;
// mAppBarOffset = 0 if app bar is expanded; If app bar is collapsed then
// mAppBarOffset = mAppBarMaxOffset
// mAppBarMaxOffset is always <=0 (-AppBarLayout.getTotalScrollRange())
// mAppBarOffset should never be > zero or less than mAppBarMaxOffset
mAppBarIdle = (mAppBarOffset >= 0) || (mAppBarOffset <= mAppBarMaxOffset);
}
});
mNestedView.post(new Runnable() {
#Override
public void run() {
mAppBarMaxOffset = mNestedView.getMaxScrollAmount();
}
});
}
#Override
public boolean isAppBarIdle() {
return mAppBarIdle;
}
#Override
public boolean isAppBarExpanded() {
return mAppBarOffset == 0;
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.menu_scrolling, menu);
return true;
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
//noinspection SimplifiableIfStatement
if (id == R.id.action_settings) {
return true;
}
return super.onOptionsItemSelected(item);
}
#SuppressWarnings("unused")
private static final String TAG = "ScrollingActivity";
}
MyNestedScrollView.java
public class MyNestedScrollView extends NestedScrollView {
public MyNestedScrollView(Context context) {
this(context, null);
}
public MyNestedScrollView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public MyNestedScrollView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
setOnScrollChangeListener(new View.OnScrollChangeListener() {
#Override
public void onScrollChange(View view, int x, int y, int oldx, int oldy) {
mScrollPosition = y;
}
});
}
private AppBarTracking mAppBarTracking;
private int mScrollPosition;
#Override
public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow,
int type) {
// App bar latching trouble is only with this type of movement when app bar is expanded
// or collapsed. In touch mode, everything is OK regardless of the open/closed status
// of the app bar.
if (type == ViewCompat.TYPE_NON_TOUCH && mAppBarTracking.isAppBarIdle()
&& isNestedScrollingEnabled()) {
// Make sure the AppBar stays expanded when it should.
if (dy > 0) { // swiped up
if (mAppBarTracking.isAppBarExpanded()) {
// Appbar can only leave its expanded state under the power of touch...
consumed[1] = dy;
return true;
}
} else { // swiped down (or no change)
// Make sure the AppBar stays collapsed when it should.
if (mScrollPosition + dy < 0) {
// Scroll until scroll position = 0 and AppBar is still collapsed.
consumed[1] = dy + mScrollPosition;
return true;
}
}
}
boolean returnValue = super.dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow, type);
// Fix the scrolling problems when scrolling is disabled. This issue existed prior
// to 26.0.0-beta2. (Not sure that this is a problem for 26.0.0-beta2 and later.)
if (offsetInWindow != null && !isNestedScrollingEnabled() && offsetInWindow[1] != 0) {
Log.d(TAG, "<<<<offsetInWindow[1] forced to zero");
offsetInWindow[1] = 0;
}
return returnValue;
}
public void setAppBarTracking(AppBarTracking appBarTracking) {
mAppBarTracking = appBarTracking;
}
#SuppressWarnings("unused")
private static final String TAG = "MyNestedScrollView";
}
Since the issue is still not fixed as of February 2020 (latest material library version is 1.2.0-alpha5) I want to share my solution to the buggy AppBar animation.
The idea is to implmenet custom snapping logic by extending AppBarLayout.Behavior (Kotlin version):
package com.example
import android.content.Context
import android.os.Handler
import android.util.AttributeSet
import android.view.MotionEvent
import android.view.View
import androidx.coordinatorlayout.widget.CoordinatorLayout
import com.google.android.material.appbar.AppBarLayout
import com.google.android.material.appbar.AppBarLayout.LayoutParams
#Suppress("unused")
class AppBarBehaviorFixed(context: Context?, attrs: AttributeSet?) :
AppBarLayout.Behavior(context, attrs) {
private var view: AppBarLayout? = null
private var snapEnabled = false
private var isUpdating = false
private var isScrolling = false
private var isTouching = false
private var lastOffset = 0
private val handler = Handler()
private val snapAction = Runnable {
val view = view ?: return#Runnable
val offset = -lastOffset
val height = view.run { height - paddingTop - paddingBottom - getChildAt(0).minimumHeight }
if (offset > 1 && offset < height - 1) view.setExpanded(offset < height / 2)
}
private val updateFinishDetector = Runnable {
isUpdating = false
scheduleSnapping()
}
private fun initView(view: AppBarLayout) {
if (this.view != null) return
this.view = view
// Checking "snap" flag existence (applied through child view) and removing it
val child = view.getChildAt(0)
val params = child.layoutParams as LayoutParams
snapEnabled = params.scrollFlags hasFlag LayoutParams.SCROLL_FLAG_SNAP
params.scrollFlags = params.scrollFlags removeFlag LayoutParams.SCROLL_FLAG_SNAP
child.layoutParams = params
// Listening for offset changes
view.addOnOffsetChangedListener(AppBarLayout.OnOffsetChangedListener { _, offset ->
lastOffset = offset
isUpdating = true
scheduleSnapping()
handler.removeCallbacks(updateFinishDetector)
handler.postDelayed(updateFinishDetector, 50L)
})
}
private fun scheduleSnapping() {
handler.removeCallbacks(snapAction)
if (snapEnabled && !isUpdating && !isScrolling && !isTouching) {
handler.postDelayed(snapAction, 50L)
}
}
override fun onLayoutChild(
parent: CoordinatorLayout,
abl: AppBarLayout,
layoutDirection: Int
): Boolean {
initView(abl)
return super.onLayoutChild(parent, abl, layoutDirection)
}
override fun onTouchEvent(
parent: CoordinatorLayout,
child: AppBarLayout,
ev: MotionEvent
): Boolean {
isTouching =
ev.actionMasked != MotionEvent.ACTION_UP && ev.actionMasked != MotionEvent.ACTION_CANCEL
scheduleSnapping()
return super.onTouchEvent(parent, child, ev)
}
override fun onStartNestedScroll(
parent: CoordinatorLayout,
child: AppBarLayout,
directTargetChild: View,
target: View,
nestedScrollAxes: Int,
type: Int
): Boolean {
val started = super.onStartNestedScroll(
parent, child, directTargetChild, target, nestedScrollAxes, type
)
if (started) {
isScrolling = true
scheduleSnapping()
}
return started
}
override fun onStopNestedScroll(
coordinatorLayout: CoordinatorLayout,
abl: AppBarLayout,
target: View,
type: Int
) {
isScrolling = false
scheduleSnapping()
super.onStopNestedScroll(coordinatorLayout, abl, target, type)
}
private infix fun Int.hasFlag(flag: Int) = flag and this == flag
private infix fun Int.removeFlag(flag: Int) = this and flag.inv()
}
And now apply this behavior to the AppBarLayout in xml:
<android.support.design.widget.CoordinatorLayout>
<android.support.design.widget.AppBarLayout
app:layout_behavior="com.example.AppBarBehaviorFixed">
<com.google.android.material.appbar.CollapsingToolbarLayout
app:layout_scrollFlags="scroll|exitUntilCollapsed|snap">
<!-- Toolbar declaration -->
</com.google.android.material.appbar.CollapsingToolbarLayout>
</android.support.design.widget.AppBarLayout>
<!-- Scrolling view (RecyclerView, NestedScrollView) -->
</android.support.design.widget.CoordinatorLayout>
That is still a hack but it seems to work quite well, and it does not require to put dirty code into your activity or extend RecyclerView and NestedScrollView widgets (thanks to #vyndor for this idea).
I know how to change the height of a Bottom Sheet.
There is no problem to increase the height of a bottom sheet. However I cannot decrease its height with the following code .
bottomSheetBehavior.setPeekHeight(peekHeight); // peekHeight < previous height
bottomSheetBehavior.setState(STATE_COLLAPSED);
Anyone encountered the same problem ?
I am trying to do the same and I don't have such problem. I could easily increase and decrease my BottomSheetDialogFragment height. Here is my code of two methods of my fragment:
private BottomSheetBehavior.BottomSheetCallback bottomSheetCallback = new BottomSheetBehavior.BottomSheetCallback() {
#Override
public void onStateChanged(#NonNull View bottomSheet, int newState) {
if (newState == BottomSheetBehavior.STATE_HIDDEN) {
dismiss();
}
}
#Override
public void onSlide(#NonNull View bottomSheet, float slideOffset) {
}
};
public static BottomSheetFragment newInstance() {
return new BottomSheetFragment();
}
#Override
public void setupDialog(Dialog dialog, int style) {
super.setupDialog(dialog, style);
View contentView = View.inflate(getContext(), R.layout.bottom_sheet_dialog_content_view, null);
dialog.setContentView(contentView);
CoordinatorLayout.LayoutParams layoutParams = ((CoordinatorLayout.LayoutParams) ((View) contentView.getParent()).getLayoutParams());
CoordinatorLayout.Behavior behavior = layoutParams.getBehavior();
if (behavior != null && behavior instanceof BottomSheetBehavior) {
((BottomSheetBehavior) behavior).setBottomSheetCallback(bottomSheetCallback);
((BottomSheetBehavior) behavior).setPeekHeight(getResources().getDimensionPixelSize(R.dimen.bottom_sheet_height) / 4);
}
initRecyclerView(contentView);
}
I have a RecyclerView in whose view holder constructor I'm adding an onGlobalLayoutListener as follows
public CustomViewHolder(final View itemView, Context context) {
super(itemView, context);
itemView.getViewTreeObserver().addOnGlobalLayoutListener(
new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
// Get height here
}
});
}
This fires for all itemViews that are visible on the screen, but as I scroll the recyclerView, it doesn't fire for the itemViews that start to appear on the screen. Why is that? How can I capture this listener for those items?
Register an OnLayoutChangeListener like this:
imageViewAvatar.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
#Override
public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
imageViewAvatar.removeOnLayoutChangeListener(this);
// Get height here
}
});
You can use OnPreDrawListener instead of OnGlobalLayoutListener on ViewTreeObserver
public CustomViewHolder(final View itemView, Context context) {
super(itemView, context);
ViewTreeObserver vto = getViewTreeObserver();
vto.addOnPreDrawListener(
new ViewTreeObserver.OnPreDrawListener() {
#Override
public boolean onPreDraw() {
vto.removeOnPreDrawListener(this);
// Get height here
return true;
}
});
}
Or in Kotlin:
val vto = viewTreeObserver
vto.addOnPreDrawListener(
object: ViewTreeObserver.OnPreDrawListener {
override fun onPreDraw(): Boolean {
viewTreeObserver.removeOnPreDrawListener(this)
//Your code
return true
}
}
)