My app's main interface is a viewpager where the user just slides the pages horizontally to get to the various pages. One of the pages has a google mapview (pasted below). My problem is that if the user is on the map page and uses a horizontal slide gesture, the page slides to the next page instead of the map moving sideways. It's as if the viewpager is getting the gesture before the map.
If the user is clever and begins sliding the map in a diagonal or vertical direction the map begins moving and then the gesture can continue horizontally. But I would prefer the map move instead of the page on a simple horizontal slide gesture. The page can always be slid using the textview.
Is there any way I can make this happen?
thanks,
Gary
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/ContentPanel"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >
<TextView
android:id="#+id/tvMAP"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Map"
style="#style/bigtype" />
<com.google.android.maps.MapView
android:id="#+id/mapview"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:apiKey="mykeygoeshere"
android:clickable="true" />
</LinearLayout>
Of course there is:
public class CustomViewPager extends ViewPager {
public CustomViewPager(Context context) {
super(context);
}
public CustomViewPager(Context context, AttributeSet attrs) {
super(context, attrs);
}
#Override
protected boolean canScroll(View v, boolean checkV, int dx, int x, int y) {
if(v instanceof MapView){
return true;
}
return super.canScroll(v, checkV, dx, x, y);
}
}
This will make the map ignore all the slides inside the map and just care about the slides/draggs outside the map. Hope it helps. (I do this right now for a webview with horizontal scroll)
EDIT: Forgot to mention that instead of the ViewPager you need to use the CustomViewPager in yout layout.
<com.yourpackage.CustomViewPager
android:id="#+id/viewpager"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
/>
If using Google Maps V2, use
scrollingView.getClass().getPackage().getName().startsWith("maps.")
in canScroll method:
#Override
protected boolean canScroll(View scrollingView, boolean checkV, int dx, int x, int y) {
if (scrollingView.getClass().getPackage().getName().startsWith("maps.")) {
return true;
}
return super.canScroll(scrollingView, checkV, dx, x, y);
}
Becase scrollingView is maps.j.b when using maps v2.
Also in my code, these classes are used:
import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.SupportMapFragment;
for ViewPager2, you can solve this issue by creating a custom view that extends, google's mapView. and override the dispatchTouchEvent method. The code should be as below
class CustomMapView: MapView {
private var dontIntercepMove = false
override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {
when (ev?.action) {
MotionEvent.ACTION_DOWN -> dontIntercepMove = ev.x < LEFT_SCROLL_OFFSET * width || ev.x > RIGHT_SCROLL_OFFSET * width
MotionEvent.ACTION_MOVE -> if (!dontIntercepMove && parent != null) parent.requestDisallowInterceptTouchEvent(true)
}
return super.dispatchTouchEvent(ev)
}
}
This will allow the mapview to scroll when you swipe on it.. and when you swipe on the edges the viewpager will work
Related
I have a View Pager (VP) which contains a Horizontal Scroll View (HSV). If the HSV reaches one of its edges or is not able to scroll at all, on a new swipe in the blocked direction VP should take over scrolling to the next page. I hesitated to ask this question because I found similar ones like these:
Can I use Horizontal Scrollview Inside a Viewpager in Android?
or
horizontalscrollview inside viewpager
But the solution did not work for me. 'v instanceof HorizontalScrollView' gets true but viewPager does not scroll
Any other ideas how to achieve the desired behaviour?
public class MyViewPager extends ViewPager {
public MyViewPager(Context context, AttributeSet attrs) {
super(context, attrs);
}
// Update 1
#Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
return true;
//return super.onInterceptTouchEvent(ev);
}
/**
* https://stackoverflow.com/questions/22781496/can-i-use-horizontal-scrollview-inside-a-viewpager-in-android
*/
#Override
protected boolean canScroll(View v, boolean checkV, int dx, int x, int y) {
if (v instanceof HorizontalScrollView) {
return true;
}
return super.canScroll(v, checkV, dx, x, y);
}
}
child view: view_pager_page.xml:
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center">
<HorizontalScrollView
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center">
<include layout="#layout/" />
</LinearLayout>
</HorizontalScrollView>
</LinearLayout>
</FrameLayout>
</RelativeLayout>
parent view: view_pager.xml
<android.support.design.widget.CoordinatorLayout>
...
<LinearLayout>
<packagepath.MyViewPager
android:layout_width="match_parent"
android:layout_height="wrap_content">
</packagepath.MyViewPager>
</LinearLayout>
...
<android.support.design.widget.CoordinatorLayout>
Update 1: When overriding 'onInterceptTouchEvent' and let it always return true VP scrolls, but HSV doesn't. I think this must return true only if HSV reaches edges right? How can I figure out in this method if it is the case?
Update 2: I reconstructed the touch event mechanism of android hoping to get some insight of how to intercept the motion event flow. E.g. in HSV I can simply return false to let VP consume this and all subsequent motion events. Unfortunately I need two motion events of type MotionEvent.MOVE to decide if HSV or VP should scroll when reaching an edge (if HSV has reached right edge, a right swipe scrolls HSV back and a left swipe scrolls to next page of VP). But if I skip the MotionEvent.DOWN action neither HSV or VP starts scrolling... so hard to solve. Any ideas?
Touchevent Mechanism in Android
(Warning: Graphic is not complete and will contain mistakes, everyone is invited to correct it :-))
Update 3: Finally I got it working. Understanding the Touchevent mechanism helped a lot and also the first comment of ZeroOne. I will post my solution when I have time for it.
I solved this with a custom HorizontalScrollView. The key is to override the onTouchEvent() method and return false if you are at the left edge and swiping right, or the right edge and swiping left. Returning false means this view didn't consume the touch event and this event can bubble back up the view hierarchy to be handled by the ViewPager.
public class HorizontalScrollViewForViewPager extends HorizontalScrollView {
float old_x, old_y;
public HorizontalScrollViewForViewPager(Context context) {
super(context);
}
public HorizontalScrollViewForViewPager(Context context, AttributeSet attrs) {
super(context, attrs);
}
public HorizontalScrollViewForViewPager(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
#Override
public boolean onTouchEvent(MotionEvent ev) {
int action = ev.getActionMasked();
if (action == MotionEvent.ACTION_DOWN) {
//Start of touch. Could be tap, could be drag.
old_x = ev.getX();
old_y = ev.getY();
} else if (action == MotionEvent.ACTION_MOVE) {
//Drag movement underway
float deltaX = ev.getX() - old_x;
float deltaY = ev.getY() - old_y;
if (Math.abs(deltaX) > Math.abs(deltaY)) {
//scrolling more left/right than up/down
if (deltaX > 0 && getScrollX() == 0) {
//dragging left, at left edge of HorizontalScrollView. Don't handle this touch event, let it bubble up to ViewPager
return false;
} else {
//dragging right. Use first child to determine width of content inside HorizontalScrollView
int childWidth = getChildAt(0).getWidth();
if (deltaX < 0 && (this.getScrollX() + this.getWidth()) >= childWidth) {
//swiping left, and at right edge of HorizontalScrollView. Don't handle this touch event, let it bubble up to ViewPager
return false;
}
}
}
}
return super.onTouchEvent(ev);
}
}
1.Extend ViewPager Class:
public class ViewPagerContainingHorizontalScrollView extends ViewPager {
private Float x_old;
private boolean bDoIntercept = false;
private boolean bHsvRightEdge = false;
private boolean bHsvLeftEdge = true;
public ViewPagerContainingHorizontalScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
}
private float calculateDistanceSwipe(MotionEvent ev){
float distance = 0;
if (x_old == null) {
x_old = ev.getX();
} else {
distance = ev.getX() - x_old;
x_old = null;
}
return distance;
}
#Override
public boolean dispatchTouchEvent(MotionEvent ev) {
mDoIntercept = false;
if(ev.getAction() == MotionEvent.ACTION_MOVE) {
float distance = calculateDistanceSwipe(ev);
if (distance < 0) {//scrolling left direction
if (bHsvRightEdge) { //HSV right edge
bDoIntercept = true;
//When scrolling slow VP may not switch page.
//Then HSV snaps back into old position.
//To allow HSV to scroll into non blocked direction set following to false.
bHsvRightEdge = false;
}
bHsvLeftEdge = false;//scrolling left means left edge not reached
} else if (distance > 0) {//scrolling right direction
if (bHsvLeftEdge) { //HSV left edge
bDoIntercept = true;
//When scrolling slow VP may not switch page.
//Then HSV snaps back into old position.
//To allow HSV to scroll into non blocked direction set following to false.
bHsvLeftEdge = false;
}
bHsvRightEdge = false;//scrolling right means right edge not reached
}
}
return super.dispatchTouchEvent(ev);
}
#Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
if(bDoIntercept){
return true;
}else{
return super.onInterceptTouchEvent(ev);
}
}
#Override
protected boolean canScroll(View v, boolean checkV, int dx, int x, int y) {
if (v instanceof HorizontalScrollView) {
HorizontalScrollView hsv = (HorizontalScrollView) v;
int max_scrollX = hsv.getChildAt(0).getWidth() - hsv.getWidth();
int min_scrollX = 0;
int current_scroll_x = hsv.getScrollX();
if (current_scroll_x == max_scrollX) { //HSV right edge
bHsvRightEdge = true;
}
if (current_scroll_x == min_scrollX) { //HSV left edge
bHsvLeftEdge = true;
}
return true;
}
return super.canScroll(v, checkV, dx, x, y);
}
}
Use this custom VP in XML.
Enjoy nested HSV scrolling in VP :-)
Touch Event Mechanism Overview for this specific case
I've just started writing a custom view in Android (for the first time) and I've realised that I need to implement a scrolling feature.
The custom view also uses a header containing some text (which should stay fixed and not scroll).
I've read through the documentation on GestureDetector.SimpleOnGestureListener and Scroller. I also read the documentation on Animating a Scroll Gesture but I found the examples difficult to understand. I've also looked at other questions on Stack Overflow which helped a little.
Using what I understood from the documentation with the Stack Overflow answer as a reference to guide me, I have added the following to my custom view:
Variables and fields:
private OverScroller mScroller;
private final GestureDetector mGestureDetector =
new GestureDetector(getContext(), new GestureDetector.SimpleOnGestureListener() {
#Override
public boolean onScroll(MotionEvent e1, MotionEvent e2,
float distanceX, float distanceY) {
// Note 0 as the x-distance to prevent horizontal scrolling
scrollBy(0, (int) distanceY);
return true;
}
#Override
public boolean onFling(MotionEvent e1, MotionEvent e2,
float velocityX, float velocityY) {
final int maxScrollX = 0;
// wholeViewHeight is height of everything that is drawn
int wholeViewHeight = calculateWholeHeight();
int visibleHeight = getHeight();
final int maxScrollY = wholeViewHeight - visibleHeight;
mScroller.forceFinished(true);
mScroller.fling(0, // No startX as there is no horizontal scrolling
getScrollY(),
0, // No velocityX as there is no horizontal scrolling
- (int) velocityY,
0,
maxScrollX,
0,
maxScrollY);
invalidate();
return true;
}
#Override
public boolean onDown(MotionEvent e) {
if (!mScroller.isFinished()) {
mScroller.forceFinished(true);
}
return true;
}
});
Initialization of mScroller:
// Called from the constructor
private void init() {
mScroller = new OverScroller(getContext(), new FastOutLinearInInterpolator());
...
}
Stuff in onDraw():
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
...
if (mScroller.computeScrollOffset()) {
scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
}
}
Stuff in onTouchEvent():
#Override
public boolean onTouchEvent(MotionEvent event) {
return mGestureDetector.onTouchEvent(event);
}
The result of these additions is a custom view which can scroll vertically (and not horizontally), however there are a few issues:
I can scroll further past what is drawn
There is no edge glow effect as I reach the end of the custom view (I mean like a RecyclerView or ScrollView)
All of the custom view scrolls as opposed to just a certain part of it
I don't fully understand what is going on
Could someone explain how scrolling works in a custom view and how to implement it properly with these features?
May I offer a simple layout that has a fixed header and vertically scrolling content that sounds like it will do what you want and does not require complex programming? It may save you from having to spend hours of research and programming in the long run:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<FrameLayout
android:id="#+id/header"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<android.support.v4.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:fillViewport="true">
<FrameLayout
android:id="#+id/scrolling_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</android.support.v4.widget.NestedScrollView>
</LinearLayout>
Using this layout, you would replace or insert inside the FrameLayout with ID "header" with your fixed header view. You would then put your scolling content inside of the FrameLayout with ID "scrolling_content". The NestedScrollingView would be located directly below your fixed header and automatically give you the scrolling behavior you want without any further code.
I have a single vertical nestedscrollview that contains a bunch of recyclerview with a horizontal layoutmanager setup. The idea is pretty similar to how the new google play store looks. I'm able to make it functional but it isn't smooth at all. Here are the problems:
1) The horizontal recyclerview item fails to intercept the touch event most of the times even though i tap right on it. The scroll view seems to take precedence for most of the motions. It's hard for me to get a hook onto the horizontal motion. This UX is frustrating as I need to try a few times before it works. If you check the play store, it is able to intercept the touch event really well and it just works well. I noticed in the play store the way they set it up is many horizontal recyclerviews inside one vertical recyclerview. No scrollview.
2) The height of the horizontal recyclerviews have to be manually set and there is no easy way to calculate the height of the children elements.
Here is the layout I'm using:
<android.support.v4.widget.NestedScrollView
android:id="#+id/scroll"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
android:background="#color/dark_bgd"
app:layout_behavior="#string/appbar_scrolling_view_behavior">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<LinearLayout
android:id="#+id/main_content_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="gone"
tools:visibility="gone"
android:orientation="vertical">
<android.support.v7.widget.RecyclerView
android:id="#+id/starring_list"
android:paddingLeft="#dimen/spacing_major"
android:paddingRight="#dimen/spacing_major"
android:layout_width="match_parent"
android:layout_height="180dp" />
This UI pattern is very basic and most likely used in many different apps. I've read many SO's where ppl say it's a bad idea to put a list within a list, but it is a very common and modern UI pattern used all over the place.Think of netflix like interface with a series of horizontal scroll lists inside a vertical list. Isn't there a smooth way to accomplish this?
Example image from the store:
So the smooth scrolling issue is fixed now. It was caused by a bug in the NestedScrollView in the Design Support Library (currently 23.1.1).
You can read about the issue and the simple fix here:
https://code.google.com/p/android/issues/detail?id=194398
In short, after you performed a fling, the nestedscrollview didn't register a complete on the scroller component and so it needed an additional 'ACTION_DOWN' event to release the parent nestedscrollview from intercepting(eating up) the subsequent events. So what happened was if you tried scrolling your child list(or viewpager), after a fling, the first touch releases the parent NSV bind and the subsequent touches would work. That was making the UX really bad.
Essentially need to add this line on the ACTION_DOWN event of the NSV:
computeScroll();
Here is what I'm using:
public class MyNestedScrollView extends NestedScrollView {
private int slop;
private float mInitialMotionX;
private float mInitialMotionY;
public MyNestedScrollView(Context context) {
super(context);
init(context);
}
private void init(Context context) {
ViewConfiguration config = ViewConfiguration.get(context);
slop = config.getScaledEdgeSlop();
}
public MyNestedScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
public MyNestedScrollView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context);
}
private float xDistance, yDistance, lastX, lastY;
#Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
final float x = ev.getX();
final float y = ev.getY();
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
xDistance = yDistance = 0f;
lastX = ev.getX();
lastY = ev.getY();
// This is very important line that fixes
computeScroll();
break;
case MotionEvent.ACTION_MOVE:
final float curX = ev.getX();
final float curY = ev.getY();
xDistance += Math.abs(curX - lastX);
yDistance += Math.abs(curY - lastY);
lastX = curX;
lastY = curY;
if (xDistance > yDistance) {
return false;
}
}
return super.onInterceptTouchEvent(ev);
}
}
Use this class in place of your nestedscrollview in the xml file, and the child lists should intercept and handle the touch events properly.
Phew, there are actually quite a few bugs like these that makes me want to ditch the design support library altogether and revisit it when its more mature.
Since falc0nit3 solution doesn't work anymore (currently the project using 28.0.0 version of support library), i have found an another one.
The background reason of the issue is still the same, scrollable view eats on down event by returning true on the second tap, where it shouldn't, because naturally second tap on the fling view stops scrolling and may be used with next move event to start opposite scroll
The issue is reproduced as with NestedScrollView as with RecyclerView.
My solution is to stop scrolling manually before native view will be able to intercept it in onInterceptTouchEvent. In this case it won't eat the ACTION_DOWN event, because it have been stopped already.
So, for NestedScrollView:
class NestedScrollViewFixed(context: Context, attrs: AttributeSet) :
NestedScrollView(context, attrs) {
override fun onInterceptTouchEvent(ev: MotionEvent): Boolean {
if (ev.actionMasked == MotionEvent.ACTION_DOWN) {
onTouchEvent(ev)
}
return super.onInterceptTouchEvent(ev)
}
}
For RecyclerView:
class RecyclerViewFixed(context: Context, attrs: AttributeSet) :
RecyclerView(context, attrs) {
override fun onInterceptTouchEvent(e: MotionEvent): Boolean {
if (e.actionMasked == MotionEvent.ACTION_DOWN) {
this.stopScroll()
}
return super.onInterceptTouchEvent(e)
}
}
Despite solution for RecyclerView looks easy to read, for NestedScrollView it's a bit complicated.
Unfortunately, there is no clear way to stop scrolling manually in widget, which the only responsibility is to manage scroll (omg). I'm interesting in abortAnimatedScroll() method, but it is private. It is possible to use reflection to get around it, but for me better is to call method, which calls abortAnimatedScroll() itself.
Look at onTouchEvent handling of ACTION_DOWN:
/*
* If being flinged and user touches, stop the fling. isFinished
* will be false if being flinged.
*/
if (!mScroller.isFinished()) {
Log.i(TAG, "abort animated scroll");
abortAnimatedScroll();
}
Basically stopping fling is managed in this method, but a bit later, than we have to call it to fix the bug
Unfortunately due to this we can't just create OnTouchListener and set it outside, so only inheritance fits the requirements
I've succeded in doing horizontal scrolling in a vertically scrolling parent with a ViewPager :
<android.support.v4.widget.NestedScrollView
...
<android.support.v4.view.ViewPager
android:id="#+id/pager_known_for"
android:layout_width="match_parent"
android:layout_height="350dp"
android:minHeight="350dp"
android:paddingLeft="24dp"
android:paddingRight="24dp"
android:clipToPadding="false"/>
public class UniversityKnownForPagerAdapter extends PagerAdapter {
public UniversityKnownForPagerAdapter(Context context) {
mContext = context;
mInflater = LayoutInflater.from(mContext);
}
#Override
public Object instantiateItem(ViewGroup container, int position) {
View rootView = mInflater.inflate(R.layout.card_university_demographics, container, false);
...
container.addView(rootView);
return rootView;
}
#Override
public void destroyItem(ViewGroup container, int position, Object object) {
container.removeView((View)object);
}
#Override
public int getCount() {
return 4;
}
#Override
public boolean isViewFromObject(View view, Object object) {
return (view == object);
}
Only issue : you must provide a fixed height to the view pager
I try to make a parallax scrolling effect for google MapView and RecycleView using CoordinatorLayour.
so base on some tutorials found on web I made below code.
The 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"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<com.google.android.gms.maps.MapView android:id="#+id/map_view"
android:layout_width="match_parent"
android:layout_height="200dp"
app:layout_behavior="net.lunavulpo.coordinatorlayouttest.MapViewBehavior"
/>
<android.support.v7.widget.RecyclerView
android:id="#+id/my_recycler_view"
android:layout_width="match_parent"
android:layout_marginTop="200dp"
android:layout_height="match_parent"/>
</android.support.design.widget.CoordinatorLayout>
and I made my implementation of CoordinatorLayout.Behavior:
public class MapViewBehavior extends CoordinatorLayout.Behavior<MapView> {
public MapViewBehavior(Context context, AttributeSet attrs) {
}
#Override
public boolean layoutDependsOn(CoordinatorLayout parent, MapView child, View dependency) {
return true;
}
#Override
public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, MapView child, View directTargetChild, View target, int nestedScrollAxes) {
return true;
}
#Override
public void onNestedScroll(CoordinatorLayout coordinatorLayout, MapView child, View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {
//child.setTranslationY(child.getTranslationY() - dyConsumed);
child.setBottom(child.getBottom() - dyConsumed);
//what should I make here?
}
#Override
public boolean onNestedFling(CoordinatorLayout coordinatorLayout, MapView child, View target, float velocityX, float velocityY, boolean consumed) {
//what should be here?
return super.onNestedFling(coordinatorLayout, child, target, velocityX, velocityY, consumed);
}
#Override
public boolean onNestedPreFling(CoordinatorLayout coordinatorLayout, MapView child, View target, float velocityX, float velocityY) {
//what should be here?
return super.onNestedPreFling(coordinatorLayout, child, target, velocityX, velocityY);
}
}
How to correct implement the parallax scrolling effect?
Maybe there is a library with ready to use Behaviors?
or I miss something and there is a simplest way?
I don't want to use Toolbar for this at all.
As #Xaver Kapeller suggested, the most painless way to achieve the parallax is to wrap it in CollapsingToolbarLayout with collapsing mode PARALLAXand parallax multiplier of your choice.
However, if you plan to go with your own behavior, you need to write the implementation in the onPreNestedScroll() method.
First, you only care about vertical scrolling. In onStartNestedScrolling() you check wither nestedScrollAxes is vertical.
Then you accumulate all the dy scrolled and translate the dummy view (that works as header) and MapView. Reset the mTotalDy to 0 when there is a change in the direction of scrolling.
Then for the same negative mTotalDy value translate the ViewCompat.setTranslationY(mDummyView, -mTotalDy). To achieve proper parallax, the parallax should translate slower than the dummy view, which means that the mTotalDy value should be at least half the speed of translation speed of the dummy view, i.e: ViewCompat.setTranslationY(mMapView, -mTotalDy/2). Therefore you will see it translating up/down slower.
View hierarchy should be: Make sure your z-order is preserved as I described
-CordinatorLayout
-FrameLayout
-MapView
-DummyView (same height as MapView, different z-order)
-NestedScrollView (same z-order as DummyView)
You should handle in the same manner when there is nested flinging, hint: use target.getScrollY(), where the target is the view that scrolls.
Update:
I'm going to give you an overview how to do it (I can't post all the answer because I posted it in another question with MORE requeriments than question and my old answer got marked as duplicated...)any way this is how you do it:
For custom CoordinatorLayout:
Because you can't extend BottomSheetBehavior, you have 2 options: trying doing your own Behavior or like I did, just copy paste the code from original BottomSheetBehavior and modifying it to get another state (when google maps open a bottomsheet it stop in the middle of the screen, this is the state that I'm talking about).
here the CustomBottomSheet with the behavior I was talking above
Now for Image parallax effect:
I tried all default parallax effect in XML avoiding a Custom behavior but in the end I ended doing one, its not hard, you need to override 2 methods like this:
public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) {
return dependency instanceof NestedScrollView;
}
public boolean onDependentViewChanged(CoordinatorLayout parent, View child,
View dependency) {
if (mYmultiplier == 0) {
initValues(child, dependency);
return true;
}
float dVerticalScroll = dependency.getY() - mPreviousY;
mPreviousY = dependency.getY();
//going up
if (dVerticalScroll <= 0 && child.getY() <= 0) {
child.setY(0);
return true;
}
//going down
if (dVerticalScroll >= 0 && dependency.getY() <= mImageHeight)
return false;
child.setY( (int)(child.getY() + (dVerticalScroll * mYmultiplier) ) );
return true;
}
Here the link to image parallax behavior
And here is how its looks like:
[]
A link to the project
I must clarify that the previous link has FAB and toolbars animations but if you only want bottom and image behavior just ignore the others java files (clarifying because I have to "adjust to the question")
I'm trying to use the SlidingPaneLayout with ViewPager, like so
<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.widget.SlidingPaneLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/scientific_graph_slidingPaneLayout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<!--
The first child view becomes the left pane.
-->
<ListView
android:id="#+id/left_pane"
android:layout_width="240dp"
android:layout_height="match_parent"
android:layout_gravity="left" />
<!--
The second child becomes the right (content) pane.
-->
<android.support.v4.view.ViewPager
android:id="#+id/scientific_graph_viewPager"
android:layout_width="match_parent"
android:layout_height="match_parent">
</android.support.v4.view.ViewPager>
</android.support.v4.widget.SlidingPaneLayout>
The SlidingPaneLayout slides when I pull from the left edge; however, I can't seem to get the ViewPager to slide when I pull from the right edge. When I pull from the right edge, it slides very little and then snaps back.
Is doing this even possible? Is there a better way to do this?
I found that by moving my finger up and the to the left, I can swipe the view pager.
The root cause is the implementation of #onInterceptTouchEvent. An older implementation of SlidingPaneLayout made a call to #canScroll, which would check if the touch target could scroll, and if so, would scroll the touch target instead of sliding the panel. The most recent implementation looks like it always intercepts the motion event, once the drag threshold exceeds the slop, except in the case where the X drag exceeds the slop and the Y drag exceeds the X drag (as noted by the OP).
One solution to this is to copy SlidingPaneLayout and make a few changes to get this to work. Those changes are:
Modify the ACTION_MOVE case in #onInterceptTouchEvent to also check #canScroll,
if (adx > slop && ady > adx ||
canScroll(this, false, Math.round(x - mInitialMotionX), Math.round(x), Math.round(y)))
{ ... }
Modify the final check in #canScroll to special case ViewPager. This modification could also be done in a subclass by overriding #canScroll, since it doesn't access any private state.
protected boolean canScroll(View v, boolean checkV, int dx, int x, int y) {
...
/* special case ViewPagers, which don't properly implement the scrolling interface */
return checkV && (ViewCompat.canScrollHorizontally(v, -dx) ||
((v instanceof ViewPager) && canViewPagerScrollHorizontally((ViewPager) v, -dx)))
}
boolean canViewPagerScrollHorizontally(ViewPager p, int dx) {
return !(dx < 0 && p.getCurrentItem() <= 0 ||
0 < dx && p.getAdapter().getCount() - 1 <= p.getCurrentItem());
}
There is likely a more elegant way to do this by fixing the ViewDragHelper, but this is something Google should address in a future update of the support package. The hacks above should get the layout working with ViewPagers (and other horizontally scrolling containers?) now.
Building off of #Brien Colwell's solution, I've written a custom subclass of SlidingPaneLayout that handles this for you, and also adds edge swiping so that when the user scrolls far to the right, they don't have to scroll all the way back to the left in order to open the pane.
Since this is a subclass of SlidingPaneLayout, you don't need to change any of your references in Java, just make sure that you instantiate an instance of this class (usually in your XML).
package com.ryanharter.android.view;
import android.content.Context;
import android.support.v4.view.MotionEventCompat;
import android.support.v4.widget.SlidingPaneLayout;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.ViewConfiguration;
/**
* SlidingPaneLayout that, if closed, checks if children can scroll before it intercepts
* touch events. This allows it to contain horizontally scrollable children without
* intercepting all of their touches.
*
* To handle cases where the user is scrolled very far to the right, but should still be
* able to open the pane without the need to scroll all the way back to the start, this
* view also adds edge touch detection, so it will intercept edge swipes to open the pane.
*/
public class PagerEnabledSlidingPaneLayout extends SlidingPaneLayout {
private float mInitialMotionX;
private float mInitialMotionY;
private float mEdgeSlop;
public PagerEnabledSlidingPaneLayout(Context context) {
this(context, null);
}
public PagerEnabledSlidingPaneLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public PagerEnabledSlidingPaneLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
ViewConfiguration config = ViewConfiguration.get(context);
mEdgeSlop = config.getScaledEdgeSlop();
}
#Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
switch (MotionEventCompat.getActionMasked(ev)) {
case MotionEvent.ACTION_DOWN: {
mInitialMotionX = ev.getX();
mInitialMotionY = ev.getY();
break;
}
case MotionEvent.ACTION_MOVE: {
final float x = ev.getX();
final float y = ev.getY();
// The user should always be able to "close" the pane, so we only check
// for child scrollability if the pane is currently closed.
if (mInitialMotionX > mEdgeSlop && !isOpen() && canScroll(this, false,
Math.round(x - mInitialMotionX), Math.round(x), Math.round(y))) {
// How do we set super.mIsUnableToDrag = true?
// send the parent a cancel event
MotionEvent cancelEvent = MotionEvent.obtain(ev);
cancelEvent.setAction(MotionEvent.ACTION_CANCEL);
return super.onInterceptTouchEvent(cancelEvent);
}
}
}
return super.onInterceptTouchEvent(ev);
}
}