I have a created a custom viewpager, and i would like the user to swipe and change pages only from a part of the screen.
Every page has a linearlayout at the bottom with some text, and i would like to swipe only from there, not from the hole screen.
Is that possible?
This is my custom viewpager class:
public class MyViewPager extends ViewPager {
private boolean mSwipable = true;
public MyViewPager(Context context) {
super(context);
}
public MyViewPager(Context context, AttributeSet attrs) {
super(context, attrs);
}
#Override
public boolean onInterceptTouchEvent(MotionEvent event) {
return mSwipable ? super.onInterceptTouchEvent(event) : false;
}
#Override
public boolean onTouchEvent(MotionEvent event) {
return mSwipable ? super.onTouchEvent(event) : false;
}
public boolean isSwipable() {
return mSwipable;
}
public void setSwipable(boolean swipable) {
mSwipable = swipable;
}
}
and this is the view of a page:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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:layout_height="match_parent"
android:background="#color/white"
android:clipChildren="false"
android:clipToPadding="false"
android:fillViewport="true"
app:layout_behavior="#string/appbar_scrolling_view_behavior"
tools:showIn="#layout/fragment_search">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_above="#+id/bottomLayout"
android:layout_marginBottom="#dimen/bottom_bar_height">
//show some stuff here
</LinearLayout>
<LinearLayout
android:id="#+id/bottomMenu"
android:layout_width="match_parent"
android:layout_height="#dimen/bottom_bar_height"
android:layout_alignParentBottom="true"
android:layout_gravity="bottom"
android:orientation="horizontal">
// this is my footer, where i need to change the pages by swipe
</LinearLayout>
</RelativeLayout>
Follow this question in order to be able to disable swiping at will How do disable paging by swiping with finger in ViewPager but still be able to swipe programmatically?
And combine that with your SliderAdapter in order to control which pages will be swipeable or not.
If you followed the link above, on your subclass of ViewPager, you can add some GestureDetector on onTouchEvent(MotionEvent event) and feed it with your event, like Android: How to handle right to left swipe gestures
By doing this, you will now have some onSwipeRight and manage there your logic, delegating to the adapter the question if the page should be swiped or not, or where to go next.
Related
I am able to implement TabLayout with ViewPager nicely but when the content inside the ViewPager is being scrolled (the deceleration animation), I cannot swipe left and right to change to other fragments inside ViewPager. I have to wait until the scrolling animation stops and then swipe.
Below is some of my code snippets.
I have a simple activity_main.xml layout like below.
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<android.support.v7.widget.Toolbar
android:id="#+id/toolbarHome"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#color/colorPrimary"/>
<LinearLayout
android:id="#+id/mainContainerLayout"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:orientation="vertical">
</LinearLayout>
<android.support.design.widget.BottomNavigationView
android:id="#+id/btmNavView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#android:color/white"
app:menu="#menu/menu_main" />
</LinearLayout>
I then inflate a fragment containing TabLayout and ViewPager into the LinearLayout in the middle of the activity_main.xml. This layout can be seen below:
<LinearLayout 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:orientation="vertical">
<com.pchatanan.sontana.custom_views.AppTabLayout
android:id="#+id/contactTabLayout"
app:tabMode="fixed"
android:layout_height="wrap_content"
android:layout_width="match_parent"
style="#style/AppTabLayout"/>
<android.support.v4.view.ViewPager
android:id="#+id/contactViewPager"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
The logic inside my fragment is below:
fragmentArray = new Fragment[]{new SimpleFragment(), new SimpleFragment(), new SimpleFragment()};
titleArray = new String[]{"fragment1", "fragment2", "fragment3"};
contactPagerAdapter = new AppPagerAdapter(getChildFragmentManager(), fragmentArray, titleArray);
contactViewPager.setAdapter(contactPagerAdapter);
contactViewPager.setOffscreenPageLimit(fragmentArray.length - 1);
contactTabLayout.setupWithViewPager(contactViewPager);
Use this instead defualt ViewPager.
Detect when ScrollView or RecyclerView is scrolling. Then call
viewPager.setPagingEnabled(pagingEnable);
If pagingEnable
public class CustomViewPager extends ViewPager {
private boolean isPagingEnabled = true;
public CustomViewPager(Context context) {
super(context);
}
public CustomViewPager(Context context, AttributeSet attrs) {
super(context, attrs);
}
public boolean onTouchEvent(MotionEvent event) {
return this.isPagingEnabled && super.onTouchEvent(event);
}
public boolean onInterceptTouchEvent(MotionEvent event) {
return this.isPagingEnabled && super.onInterceptTouchEvent(event);
}
public void setPagingEnabled(boolean b) {
this.isPagingEnabled = b;
}
}
Update:
You can do reverse to like you can disable ScrollView scroll when ViewPager is swiping
mScrollView.requestDisallowInterceptTouchEvent(true);
Similar to what suggested by #Khemraj, the problem is the touch is not intercepted by ViewPager. It is only intercepted by the ScrollView. To fix this, we should allow ViewPage to intercept the touch when scrolling:
scrollView.setOnScrollChangeListener(new View.OnScrollChangeListener() {
#Override
public void onScrollChange(View view, int i, int i1, int i2, int i3) {
viewPager.requestDisallowInterceptTouchEvent(false);
}
});
A better implementation is to make use of CallBackListener onto the fragment containing the scrollView.
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 have a layout:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".StreamActivity"
android:orientation="vertical">
<FrameLayout
android:id="#+id/streamLayout"
android:layout_width="fill_parent"
android:layout_height="wrap_content">
<SurfaceView
android:id="#+id/surface"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="start">
</SurfaceView>
</FrameLayout>
<WebView
android:id="#+id/web_view"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_gravity="start"/>
FrameLayout for a video player. WebView is a chat.
When I begin to type text into the chat, the keyboard hides input field.
I tried experimenting with android: windowSoftInputMode, but it does not work for me. The problem with chat, it is written on Angular and resizing WebView does not change the size of the chat, it must be done manually like webView.loadUrl("javascript: $('section.content-window:first').css('height', '" + finalSize + "px');");
I have a listener that is triggered when the keyboard is shown and hidden.
I would like to manually change the size of the chat when the keyboard is showed, but it does not work well because when I change the size of chat it loses focus and the keyboard hidden.
I also thought to put the content of the Activity in ScrollView. If override OnTouchListener this will lock scroll by user, but I will still be able to scroll from code. So, I wanted if the keyboard is shown then scroll content of the Activity so it would be visible the entire chat. I think this will work, but not good because it will lock the scrolling in WebView.
Advise how to fix this problem with the keyboard and chat?
I found a solution:
<ScrollView ...
<LinearLayout ...
...
<com.package.TouchyWebView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/webView"/>
...
</LinearLayout>
</ScrollView>
and do like this
package com.mypackage.common.custom.android.widgets
public class TouchyWebView extends WebView {
public TouchyWebView(Context context) {
super(context);
}
public TouchyWebView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public TouchyWebView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
#Override
public boolean onTouchEvent(MotionEvent event){
requestDisallowInterceptTouchEvent(true);
return super.onTouchEvent(event);
}
}
and
scrollView.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
return true;
}
});
and to scroll I use code like this
final View activityRootView = findViewById(R.id.main_layout);
activityRootView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
boolean isOpen = false;
#Override
public void onGlobalLayout() {
if (heightDiff > 100) { // if more than 100 pixels, its probably a keyboard...
if (!isOpen) {
scrollView.scrollBy(0, heightDiff);
isOpen = true;
}
} else {
if (isOpen) {
scrollView.scrollBy(0, -heightDiff);
isOpen = false;
}
}
}
});
I made slidinguppanel using this repository https://github.com/dlukashev/AndroidSlidingUpPanel-foursquare-map-demo
However, it contains one bug which is not covered anywhere.
When I touch anywhere to expand panel (listview) works well, but while I'm trying to expand it by holding a top of a list view (blue line on screen2) panel hide under the map (framelayout) (screen3)
How it's even possible that this blue line hiding panel under mapfragment and rest of listview expand it well?
Any ideas why? Please give me just a hint how to fix it?
Please look at the screen:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<org.sothree.slidinguppanel.SlidingUpPanelLayout
android:id="#+id/slidingLayout"
android:gravity="bottom"
app:shadowHeight="0dp"
app:paralaxOffset="#dimen/paralax_offset"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<FrameLayout
android:gravity="top"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_below="#+id/button">
<RelativeLayout
android:id="#+id/mapContainer"
android:layout_width="fill_parent"
android:layout_height="497dp"/>
</FrameLayout>
<RelativeLayout
android:id="#+id/slidingContainer"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_below="#+id/button">
<View
android:id="#+id/transparentView"
android:visibility="gone"
android:layout_width="fill_parent"
android:layout_height="#dimen/map_height"
android:layout_alignParentTop="true"/>
<ListView
android:id="#+id/listView1"
android:cacheColorHint="#android:color/white"
android:drawSelectorOnTop="true"
android:dividerHeight="#dimen/divider_height"
android:divider="#android:color/darker_gray"
android:background="#android:color/transparent"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_below="#id/transparentView"
android:smoothScrollbar="true"
android:layout_marginTop="0dp"/>
</RelativeLayout>
</org.sothree.slidinguppanel.SlidingUpPanelLayout>
</RelativeLayout>
You need to override ListView
public class LockableListView extends ListView {
private boolean mScrollable = true;
public LockableListView(Context context) {
super(context);
}
public LockableListView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public LockableListView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public void setScrollingEnabled(boolean enabled) {
mScrollable = enabled;
}
#Override
public boolean onTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
// if we can scroll pass the event to the superclass
if (mScrollable) {
return super.onTouchEvent(ev);
}
// only continue to handle the touch event if scrolling enabled
return mScrollable; // mScrollable is always false at this point
default:
return super.onTouchEvent(ev);
}
}
#Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
// Don't do anything with intercepted touch events if
// we are not scrollable
if (!mScrollable) {
return false;
} else {
return super.onInterceptTouchEvent(ev);
}
}
}
Then put disable scrolling when needed
private void collapseMap() {
mSpaceView.setVisibility(View.VISIBLE);
mTransparentView.setVisibility(View.GONE);
if (mMap != null && mLocation != null) {
mMap.animateCamera(CameraUpdateFactory.newLatLngZoom(mLocation, 11f), 1000, null);
}
mListView.setScrollingEnabled(true);
}
private void expandMap() {
mSpaceView.setVisibility(View.GONE);
mTransparentView.setVisibility(View.INVISIBLE);
if (mMap != null) {
mMap.animateCamera(CameraUpdateFactory.zoomTo(14f), 1000, null);
}
mListView.setScrollingEnabled(false);
}
I pushed this changes to GitHub https://github.com/dlukashev/AndroidSlidingUpPanel-foursquare-map-demo/commit/7b869394e9d197e6f56a833804426577dcb8458a
Enjoy
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;
}
});