I have a WebView inside a FrameLayout, to add tabs because i am creating a browser. The FrameLayout is inside a SwipeRefreshLayout.
The problem: Whenever i scroll the content up fast in the WebView, the refresh icon appears from behind the toolbar. It should only happen when the WebView content is at the top. It has something to do with the FrameLayout, when i remove it, the issue is gone.
The layout looks like this:
<SwipeRefreshLayout
android:id="#+id/swipeRefresh"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<FrameLayout
android:id="#+id/webViewFrame"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<WebView
android:id="#+id/webView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</FrameLayout>
</SwipeRefreshLayout>
Just implement your Fragment or Activity with ViewTreeObserver.OnScrollChangedListener then set Listener like webview.getViewTreeObserver().addOnScrollChangedListener(this);
In onScrollChanged() method do like this
#Override
public void onScrollChanged() {
if (webview.getScrollY() == 0) {
swipeLayout.setEnabled(true);
} else {
swipeLayout.setEnabled(false);
}
}
No Solution worked for me. Here is my approach.
#Override
public boolean dispatchTouchEvent(MotionEvent event)
{
super.dispatchTouchEvent(event);
int x = (int)event.getX();
int y = (int)event.getY();
if(y>800){
swipeRefreshLayout.setEnabled(false);
}else {
swipeRefreshLayout.setEnabled(true);
}
return false;
}
Related
I am currently experimenting with CoordinatorLayout + AppbarLayout + CollapsingToolbarLayout in a way such that:
1) Scroll down the Appbar using "Toolbar" [ No Nested ScrollView / RecyclerView ].
2) The content below the appbar should move along with the appbar scrolling.
3) Multiple images kept under ViewPager.
4) The last item in the ViewPager would be an textview.
I have achieved 1) and 2) using the following layout :
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:tools="http://schemas.android.com/tools">
<android.support.design.widget.AppBarLayout
android:id="#+id/flexible.example.appbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="#style/ThemeOverlay.AppCompat.Dark.ActionBar"
>
<android.support.design.widget.CollapsingToolbarLayout
android:id="#+id/flexible.example.collapsing"
android:layout_width="match_parent"
android:layout_height="300dp"
app:expandedTitleMarginBottom="94dp"
app:layout_scrollFlags="scroll|exitUntilCollapsed|snap"
app:contentScrim="?colorPrimary"
>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:id="#+id/text_sample"
android:scrollbars="vertical"
android:scrollIndicators="right"
app:layout_collapseMode="parallax"
app:layout_scrollFlags="scroll|enterAlways"
android:layout_gravity="center"
android:nestedScrollingEnabled="true"
/>
</android.support.design.widget.CollapsingToolbarLayout>
<android.support.v7.widget.Toolbar
android:id="#+id/ioexample.toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="#color/PM01"
android:elevation="4dp"
app:layout_collapseMode="pin"
app:layout_anchor="#id/flexible.example.collapsing"
app:layout_anchorGravity="bottom"
app:theme="#style/ThemeOverlay.AppCompat.Light"
style="#style/ToolBarWithNavigationBack"
app:layout_scrollFlags="scroll|enterAlways|snap"
>
</android.support.v7.widget.Toolbar>
</android.support.design.widget.AppBarLayout>
<android.support.v7.widget.RecyclerView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="#+id/recyclerviewcontainer"
app:layout_behavior="#string/appbar_scrolling_view_behavior" />
</android.support.design.widget.CoordinatorLayout>
What I am trying to achieve now is to make the textview inside collpasingtoolbarlayout is to be scrollable (#4 above). Since my search till now has made me believe that the Appbar is handling all the touch events by itself, this doesn't seems to be easy. But since it is a requirement, I would be more than happy to have a guidance / pointers to help me complete this.
Can someone please let me know what and where to look for achieving this functionality.
After a lot of research and some more searching through SO, I got an idea what I need to do in order to achieve the desired effect:
1) Implement a custom behavior for appbarlayout :
public class AppBarLayoutCustomBehavior extends AppBarLayout.Behavior {
private boolean setIntercept = false;
private boolean lockAppBar = false;
DragCallback mDragCallback = new DragCallback() {
#Override
public boolean canDrag(#NonNull AppBarLayout appBarLayout) {
return !lockAppBar;
}
};
#Override
public boolean onInterceptTouchEvent(CoordinatorLayout parent, AppBarLayout child, MotionEvent ev) {
super.onInterceptTouchEvent(parent, child, ev);
return setIntercept;
}
public void setInterceptTouchEvent(boolean set) {
setIntercept = set;
}
public AppBarLayoutCustomBehavior() {
super();
setDragCallback(mDragCallback);
}
public AppBarLayoutCustomBehavior(Context ctx, AttributeSet attributeSet) {
super(ctx, attributeSet);
setDragCallback(mDragCallback);
}
public void lockAppBar() {
lockAppBar = true;
}
public void unlockAppBar() {
lockAppBar = false;
}
}
2) Use this custom behavior with app bar :
CoordinatorLayout.LayoutParams lp = (CoordinatorLayout.LayoutParams)appBarLayout.getLayoutParams();
final AppBarLayoutCustomBehavior mBehavior = new AppBarLayoutCustomBehavior();
lp.setBehavior(mBehavior);
/// use toolbar to enable/disable dragging on the appbar behavior. This way
/// out toolbar acts as a drag handle for the app bar.
Toolbar toolbar = (Toolbar) activity.findViewById(R.id.main_toolbar);
toolbar.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
switch(event.getAction()) {
case MotionEvent.ACTION_DOWN:
mBehavior.setInterceptTouchEvent(true);
return true;
case MotionEvent.ACTION_CANCEL:
mBehavior.setInterceptTouchEvent(false);
return true;
}
return false;
}
});
3) Set movement method on the textview to make it scrollable
textView.setMovementMethod(ScrollingMovementMethod.getInstance());
I have a layout with custom ScrollView that can be switched off. There are some fragments loading into this ScrollView.
Today I added RecycleView to the new fragment and noticed a strange behaviour. When RecycleView has android:height="match_parent" it expands to full its height inside ScrollView.
Is there any way to disable this (I want RecycleView to scroll internally and to be screen_height size) ?
Main content xml (located inside CoordinatorLayout)
<xxx.SwitchableScrollView
android:id="#+id/main_scroll_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="true">
<LinearLayout
android:id="#+id/fragment_holder"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:ignore="ScrollViewSize"/>
</xxx.SwitchableScrollView>
Fragment layout:
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<android.support.v7.widget.RecyclerView
android:id="#+id/appeals_list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:visibility="visible"/>
<LinearLayout
android:id="#+id/loading_progress"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center">
<ProgressBar
android:layout_width="#dimen/big_progress_diameter"
android:layout_height="#dimen/big_progress_diameter"
android:indeterminate="true"
android:indeterminateDrawable="#drawable/custom_progress_bar_primary"/>
</LinearLayout>
</FrameLayout>
Extended ScrollView:
public class SwitchableScrollView extends ScrollView {
boolean allowScroll = true;
public boolean isAllowScroll() {
return allowScroll;
}
public void setAllowScroll(boolean allowScroll) {
this.allowScroll = allowScroll;
}
// .... constructors skipped
#Override
public boolean onTouchEvent(MotionEvent ev) {
return allowScroll && super.onTouchEvent(ev);
}
#Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
return allowScroll && super.onInterceptTouchEvent(ev);
}
}
RecycleView init:
listView = (NestedRecyclerView) v.findViewById(R.id.appeals_list);
listView.setHorizontalScrollBarEnabled(false);
listView.setVerticalScrollBarEnabled(true);
listView.setNestedScrollingEnabled(true);
adapter = new AppealListAdapter(appealList);
LinearLayoutManager layoutManager = new LinearLayoutManager(getActivity());
listView.setLayoutManager(layoutManager);
listView.setAdapter(adapter);
new GetAppeals().execute();
I'm using support library 23.2.1 (also tried 23.2.0). ListView and GridView works good in another fragments.
Finally I found an answer. I spent 6 hours(!) to add this one line to code:
layoutManager.setAutoMeasureEnabled(false);
They should be crazy to enable such thing by default...
This is the app I'm trying to build with all the elements mapped out below:
Everything works, however, I want the inner horizontal recyclerview not to capture any of the vertical scrolls. All vertical scrolls must go towards the outer vertical recyclerview, not the horizontal one, so that the vertical scroll would allow for the toolbar to exit out of view according to it's scrollFlag.
When I put my finger on the "StrawBerry Plant" part of the recyclerview and scroll up, it scroll out the toolbar:
If I put my finger on the horizontal scrollview and scroll up, it does not scroll out the toolbar at all.
The following is my xml layout code so far.
The Activity xml layout:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/fragment_container"
android:clipChildren="false">
<android.support.design.widget.CoordinatorLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/container"
>
<android.support.design.widget.AppBarLayout
android:id="#+id/appBarLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<android.support.v7.widget.Toolbar
android:id="#+id/toolbar"
android:minHeight="?attr/actionBarSize"
android:background="?attr/colorPrimary"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_scrollFlags="scroll|enterAlways">
</android.support.v7.widget.Toolbar>
<android.support.design.widget.TabLayout
android:id="#+id/sliding_tabs"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/colorPrimary"
style="#style/CustomTabLayout"
/>
</android.support.design.widget.AppBarLayout>
<android.support.v4.view.ViewPager
android:id="#+id/viewPager"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="#string/appbar_scrolling_view_behavior"
/>
</android.support.design.widget.CoordinatorLayout>
</FrameLayout>
The "Fruits" fragment xml layout (which is the code for the fragment - the fragment is labeled in the above picture):
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ProgressBar
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="#+id/progressBar"
android:visibility="gone"
android:layout_centerInParent="true"
android:indeterminate="true"/>
<!-- <android.support.v7.widget.RecyclerView-->
<com.example.simon.customshapes.VerticallyScrollRecyclerView
android:id="#+id/main_recyclerview"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
</RelativeLayout>
I have used a custom class called VerticallyScrollRecyclerView which follows google example of handling touch events in a viewgroup. Its aim is to intercept and consume all the vertical scroll events so that it will scroll in / out the toolbar: http://developer.android.com/training/gestures/viewgroup.html
The code for VerticallyScrollRecyclerView is below:
public class VerticallyScrollRecyclerView extends RecyclerView {
public VerticallyScrollRecyclerView(Context context) {
super(context);
}
public VerticallyScrollRecyclerView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public VerticallyScrollRecyclerView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
ViewConfiguration vc = ViewConfiguration.get(this.getContext());
private int mTouchSlop = vc.getScaledTouchSlop();
private boolean mIsScrolling;
private float startY;
#Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
final int action = MotionEventCompat.getActionMasked(ev);
// Always handle the case of the touch gesture being complete.
if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
// Release the scroll.
mIsScrolling = false;
startY = ev.getY();
return super.onInterceptTouchEvent(ev); // Do not intercept touch event, let the child handle it
}
switch (action) {
case MotionEvent.ACTION_MOVE: {
Log.e("VRecView", "its moving");
if (mIsScrolling) {
// We're currently scrolling, so yes, intercept the
// touch event!
return true;
}
// If the user has dragged her finger horizontally more than
// the touch slop, start the scroll
// left as an exercise for the reader
final float yDiff = calculateDistanceY(ev.getY());
Log.e("yDiff ", ""+yDiff);
// Touch slop should be calculated using ViewConfiguration
// constants.
if (Math.abs(yDiff) > 5) {
// Start scrolling!
Log.e("Scroll", "we are scrolling vertically");
mIsScrolling = true;
return true;
}
break;
}
}
return super.onInterceptTouchEvent(ev);
}
private float calculateDistanceY(float endY) {
return startY - endY;
}
}
The "Favourite" layout which is the recyclerview within the vertical recyclerview:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:android="http://schemas.android.com/apk/res/android"
android:background="#color/white"
xmlns:app="http://schemas.android.com/apk/res-auto">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Favourite"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
android:layout_marginLeft="16dp"
android:id="#+id/header_fav"/>
<android.support.v7.widget.RecyclerView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_below="#+id/header_fav"
android:id="#+id/recyclerview_fav">
</android.support.v7.widget.RecyclerView>
</RelativeLayout>
This has been bugging me for a while now and I have not managed to come up with a solution. Does anyone know how to solve this problem?
5 points to Griffindor for the correct answer and of course, reputation points on SO.
Tested solution:
All you need is to call mInnerRecycler.setNestedScrollingEnabled(false); on your inner RecyclerViews
Explanation:
RecyclerView has support for nested scrolling introduced in API 21 through implementing the NestedScrollingChild interface. This is a valuable feature when you have a scrolling view inside another one that scrolls in the same direction and you want to scroll the inner View only when focused.
In any case, RecyclerView by default calls RecyclerView.setNestedScrollingEnabled(true); on itself when initializing. Now, back to the problem, since both of your RecyclerViews are within the same ViewPager that has the AppBarBehavior, the CoordinateLayout has to decide which scroll to respond to when you scroll from your inner RecyclerView; when your inner RecyclerView's nested scrolling is enabled, it gets the scrolling focus and the CoordinateLayout will choose to respond to its scrolling over the outer RecyclerView's scrolling. The thing is that, since your inner RecyclerViews don't scroll vertically, there is no vertical scroll change (from the CoordinateLayout's point of view), and if there is no change, the AppBarLayout doesn't change either.
In your case, because your inner RecyclerViews are scrolling in a different direction, you can disable it, thus causing the CoordinateLayout to disregard its scrolling and respond to the outer RecyclerView's scrolling.
Notice:
The xml attribute android:nestedScrollingEnabled="boolean" is not intended for use with the RecyclerView, and an attempt to use android:nestedScrollingEnabled="false" will result in a java.lang.NullPointerException so, at least for now, you will have to do it in code.
if any one still looking , try this :
private val Y_BUFFER = 10
private var preX = 0f
private var preY = 0f
mView.rv.addOnItemTouchListener(object : RecyclerView.OnItemTouchListener {
override fun onTouchEvent(p0: RecyclerView, p1: MotionEvent) {
}
override fun onInterceptTouchEvent(rv: RecyclerView, e: MotionEvent): Boolean {
when (e.action) {
MotionEvent.ACTION_DOWN -> rv.parent.requestDisallowInterceptTouchEvent(true)
MotionEvent.ACTION_MOVE -> {
if (Math.abs(e.x - preX) > Math.abs(e.y - preY)) {
rv.parent.requestDisallowInterceptTouchEvent(true)
} else if (Math.abs(e.y - preY) > Y_BUFFER) {
rv.parent.requestDisallowInterceptTouchEvent(false)
}
}
}
preX = e.x
preY = e.y
return false
}
override fun onRequestDisallowInterceptTouchEvent(p0: Boolean) {
}
})
it checks if currently scrolling horizontal then don't allow parent to handel event
I am a bit late but this will defintly work for others facing the same problem
mRecyclerView.addOnItemTouchListener(new RecyclerView.OnItemTouchListener() {
#Override
public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
int action = e.getAction();
// Toast.makeText(getActivity(),"HERE",Toast.LENGTH_SHORT).show();
switch (action) {
case MotionEvent.ACTION_POINTER_UP:
rv.getParent().requestDisallowInterceptTouchEvent(true);
break;
}
return false;
}
Tested solution, use a custom NestedScrollView().
Code:
public class CustomNestedScrollView extends NestedScrollView {
public CustomNestedScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
}
#Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
// Explicitly call computeScroll() to make the Scroller compute itself
computeScroll();
}
return super.onInterceptTouchEvent(ev);
}
}
try
public OuterRecyclerViewAdapter(List<Item> items) {
//Constructor stuff
viewPool = new RecyclerView.RecycledViewPool();
}
#Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
//Create viewHolder etc
holder.innerRecyclerView.setRecycledViewPool(viewPool);
}
inner recylerview will use the same viewpool and it'll be smoother
I would suggest you add the horizontal recyclerview inside fragments like the google app
i read through the offered answers of using setNestedScrollingEnabled to false and it was awful for me as it makes the recyclerview not recycle and you can get crashes in memory, performance issues maybe etc if you have a huge list. so i will give you a algorithm to make it work without any code.
have a listener on the vertical recyclerview, such as a scroll listener. anytime the list is scrolled you will get a callback that its being scrolled. you should also get a call back when its idle.
now when vertical recylerview is being scrolled, setNestedScrollingEnabled = false on the horizontal list
once vertical recyclerview is idle setNestedScrollingEnabled = true on the same horizontal list.
also initially set the horizontal recyclerview to setNestedScrollingEnabled = false in xml
this can also work great also with appbarlayout when coordinatorLayout gets confused with two recyclerviews in different directions, but thats another question.
lets take a look at another more simple way in kotlin, to get this done with a real example. here we will focus just on the horizontal recyclerView:
assume we have RecyclerViewVertical & RecyclerViewHorizontal:
RecyclerViewHorizontal.apply {
addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
isNestedScrollingEnabled = RecyclerView.SCROLL_STATE_IDLE != newState
}
})
}
what this code says is if RecyclerViewHorizontal is not idle then enable nestedScrolling, otherwise disable it. That means when its idle we can now use the RecyclerViewVertical in a coordinatorlayout without any interference from the RecyclerViewHorizontal since we have disabled it when its idle.
I'm trying to get a fullscreen CollapsingToolbar but when I set match_parent to the height of AppBarLayout I'm not able to scroll the ImageView which is inside CollapsingToolbarLayout. I have to leave some space so that I can touch the "white" of the activity (in AppBarLayout I added android:layout-marginBottom:"16dp" ) and only then, after I touched it, I can scroll the ImageView otherwise I can't.
This happens everytime I run the app and touch the layout for the first time. So I have to touch the white first and then scroll the image.
Could you help me?
<android.support.v4.widget.DrawerLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_height="match_parent"
android:layout_width="match_parent"
android:id="#+id/drawer">
<android.support.design.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.design.widget.AppBarLayout
android:id="#+id/app_bar_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginBottom="16dp"
android:theme="#style/ThemeOverlay.AppCompat.Dark.ActionBar">
<android.support.design.widget.CollapsingToolbarLayout
android:id="#+id/collapsing_toolbar"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_scrollFlags="scroll|exitUntilCollapsed"
app:contentScrim="?attr/colorPrimary"
app:expandedTitleMarginStart="48dp"
app:expandedTitleMarginEnd="64dp">
<ImageView
android:id="#+id/image"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:scaleType="centerCrop"
android:fitsSystemWindows="true"
app:layout_collapseMode="parallax"
android:contentDescription="#null"
android:src="#drawable/background" />
<android.support.v7.widget.Toolbar
android:layout_height="?attr/actionBarSize"
android:layout_width="match_parent"
app:popupTheme="#style/ThemeOverlay.AppCompat.Light"
app:layout_collapseMode="pin"
app:theme="#style/ToolbarTheme"
android:id="#+id/toolbar"/>
</android.support.design.widget.CollapsingToolbarLayout>
</android.support.design.widget.AppBarLayout>
<android.support.v4.widget.NestedScrollView
android:id="#+id/scroll"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
app:layout_behavior="#string/appbar_scrolling_view_behavior">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
</FrameLayout>
</android.support.v4.widget.NestedScrollView>
</android.support.design.widget.CoordinatorLayout>
<com.myapplication.ScrimInsetsFrameLayout
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="304dp"
android:layout_height="match_parent"
android:layout_gravity="start"
android:fitsSystemWindows="true"
app:insetForeground="#4000"
android:clickable="true"
android:background="#ffffff">
...
</com.myapplication.ScrimInsetsFrameLayout>
</android.support.v4.widget.DrawerLayout>
EDIT #PPartisan I've done what you said but here's what I got:
This isn't a nice solution, but it does work on my test device. It kick starts the scrolling process by explicitly assigning a touch listener to the AppBar that triggers a nested scroll.
First, create a custom class that extends NestedScrollView and add the following method so it look something like this:
public class CustomNestedScrollView extends NestedScrollView {
private int y;
public CustomNestedScrollView(Context context) {
super(context);
}
public CustomNestedScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public boolean dispatchHandlerScroll(MotionEvent e) {
switch (e.getAction()) {
case MotionEvent.ACTION_DOWN:
y = (int) e.getY();
startNestedScroll(2);
break;
case MotionEvent.ACTION_MOVE:
int dY = y - ((int)e.getY());
dispatchNestedPreScroll(0, dY, null, null);
dispatchNestedScroll(0, 0, 0, dY, null);
break;
case MotionEvent.ACTION_UP:
stopNestedScroll();
break;
}
return true;
}
}
Then, inside your Activity class, assign a TouchListener to your AppBarLayout:
appBarLayout.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
return customNestedScrollView.dispatchHandlerScroll(event);
}
});
and remove it when the AppBar collapses fully for the first time:
#Override
public boolean dispatchTouchEvent(MotionEvent ev) {
if (Math.abs(appBarLayout.getY()) == appBarLayout.getTotalScrollRange()) {
appBarLayout.setOnTouchListener(null);
}
return super.dispatchTouchEvent(ev);
}
Edit
Take the following steps to get it up and running:
Replace the NestedScrollView in your xml(android.support.v4.widget.NestedScrollView) with the CustomNestedScrollView (which will take the form of com.something.somethingelse.CustomNestedScrollView, depending on where it is in your project).
Assign it to a variable in your Activity onCreate()(i.e. CustomScrollView customScrollView = (CustomScrollView) findViewById(R.id.custom_scroll_view_id);)
Set up the TouchListener on your appBarLayout as you have done in your edit. Now when you call dispatchHandlerScroll(), it will be on your customNestedScrollView instance.
dispatchTouchEvent() is a method you override in your Activity class, so it should be outside the TouchListener
So, for example:
public class MainActivity extends AppCompatActivity {
private AppBarLayout appBarLayout;
private CustomNestedScrollView customNestedScrollView;
//...
#Override
protected void onCreate(Bundle savedInstanceState) {
//...
customNestedScrollView = (CustomNestedScrollView) findViewById(R.id.scroll);
appBarLayout = (AppBarLayout) findViewById(R.id.app_bar_layout);
appBarLayout.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
return customNestedScrollView.dispatchHandlerScroll(event);
}
});
}
#Override
public boolean dispatchTouchEvent(MotionEvent ev) {
if (Math.abs(appBarLayout.getY()) == appBarLayout.getTotalScrollRange()) {
appBarLayout.setOnTouchListener(null);
}
return super.dispatchTouchEvent(ev);
}
}
Hope that's cleared things up.
Try to add a tiny margin so the white space below will be almost invisible (you might also want to change white to accent color so the space will not be visible)
Another approach is to set the height of app bar layout dynamically by getting the height of the screen.
EDIT:
This might be a focus problem, try to add dummy layout to your main content, that will be focused automatically
<LinearLayout
android:layout_width="0px"
android:layout_height="0px"
android:focusable="true"
android:focusableInTouchMode="true" />
or even just add these attributes to your content layout
android:focusable="true"
android:focusableInTouchMode="true"
my question is simple. Can I use an HorizontalScrollView inside the content menu of a DrawerLayout?
My DrawerLayout looks like this:
<android.support.v4.widget.DrawerLayout
android:id="#+id/pnlMenu"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<!-- Main content view -->
<ListView
android:id="#+id/lst"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clickable="true"
android:fastScrollEnabled="true"
android:focusable="true"
android:focusableInTouchMode="true"
tools:listitem="#layout/item_layout" >
</ListView>
<!-- Content of menu -->
<FrameLayout
android:id="#+id/drawerFrame"
android:layout_width="300dp"
android:layout_height="match_parent"
android:layout_gravity="start"
android:clickable="true"
android:background="#color/black" >
<fragment
android:id="#+id/frag"
android:layout_width="match_parent"
android:layout_height="match_parent"
class="com.test.TestFragment" />
</FrameLayout>
</android.support.v4.widget.DrawerLayout>
Inside the fragment I have the HorizontalScrollView but when I try to touch it nothing happen because the drawer layout follow my finger.
I think that disabling the touch events inside the content menu and make DrawerLayout closable only when main content view is clicked will solve my problem. Is that true? If not, can someone tell me what can I do?
Thank you.
Based on this solution: https://stackoverflow.com/a/7258579/452486
I've been able to make HorizontalScrollView scrollable.
Create a class extends DrawerLayout:
public class AllowChildInterceptTouchEventDrawerLayout extends DrawerLayout {
private int mInterceptTouchEventChildId;
public void setInterceptTouchEventChildId(int id) {
this.mInterceptTouchEventChildId = id;
}
public AllowChildInterceptTouchEventDrawerLayout(Context context) {
super(context);
}
public AllowChildInterceptTouchEventDrawerLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
#Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
if (mInterceptTouchEventChildId > 0) {
View scroll = findViewById(mInterceptTouchEventChildId);
if (scroll != null) {
Rect rect = new Rect();
scroll.getHitRect(rect);
if (rect.contains((int) ev.getX(), (int) ev.getY())) {
return false;
}
}
}
return super.onInterceptTouchEvent(ev);
}
}
And add the child id which you want to intercept the touch event of drawerlayout
AllowChildInterceptTouchEventDrawerLayout drawerLayout = (AllowChildInterceptTouchEventDrawerLayout) findViewById(R.id.layoutdrawer_id);
drawerLayout.setInterceptTouchEventChildId(R.id.horizontalscrollview_id);
It's better to set requestDisallowInterceptTouchEvent(true) flag instead of returning false. When We have a HorizontalScrollView and below it there's another view (eg. ListView with some menu items) and we make a dynamic gesture from HorizontalScrollView area to the aforementioned ListView the app will crash with a NPE. This case happens when you open the app for the first time and go to a DrawerLayout through the hamburger icon. Use this:
if (rect.contains((int) ev.getX(), (int) ev.getY())) {
this.requestDisallowInterceptTouchEvent(true);
}
If you want to have HorizontalScrollView inside your DrawerLayout an alternative strategy to all the answers so far would be to lock the drawer when opened. That means user won't be able to close the drawer with a swipe but the scrolling inside HorizontalScrollView will work as expected.
The locking is achieved by calling
mDrawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_OPEN);
A convenient place for this call would be in onDrawerOpened.
Unfortunately the locking also prevents drawer from closing when user taps the scrim (the dimmed part of the screen not occupied by drawer). You'll need to catch that tap and close it yourself, with something like this:
mDrawerLayout.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
int x = (int) event.getX();
int drawerWidth = (int)getResources().getDimension(R.dimen.your_drawer_width);
if (x > drawerWidth) {
// inside scrim
mDrawerLayout.closeDrawer();
return true;
}
return false;
}
});