问题
I am implementing a custom expandable action bar in my game.
I am using ViewDragHelper to handle the dragging and flinging of the bar view, which is contained in a LinearLayout
I subclassed to attach the ViewDragHelper.
The following links have been of great help to achieve this:
- Tutorial on the ViewDragHelper: http://flavienlaurent.com/blog/2013/08/28/each-navigation-drawer-hides-a-viewdraghelper/
- Mastering the Android touch system: https://www.youtube.com/watch?v=EZAoJU-nUyI (which has given me they key to make Views clickable and draggable)
The only issue I am encountering is the behavior of the layout() of my parent LinearLayout: on every call to layout()
/ onLayout()
, the child draggable/expandable action bar is reset to its original position (the one set in the XML layout).
Why is that ?
(In my experience layout()
never messes with the positions of views that have already been moved)
回答1:
The workaround I'm using is to record the view's offsets after each drag operation, and reapply them in onLayout()
, e.g.
View mVdhView;
int mVdhXOffset;
int mVdhYOffset;
@Override
public void computeScroll() {
if (dragHelper.continueSettling(true)) {
postInvalidateOnAnimation();
} else {
mVdhXOffset = mVdhView.getLeft();
mVdhYOffset = mVdhView.getTop();
}
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
// Reapply VDH offsets
mVdhView.offsetLeftAndRight(mVdhXOffset);
mVdhView.offsetTopAndBottom(mVdhYOffset);
}
回答2:
I encountered the same issue when playing with a DrawerLayout (a layout having draggable drawers), and was initially also surprised.
From reading the source, it seems ViewDragHelper uses offsetLeftAndRight() / offsetTopAndBottom() to visually shift any captured and dragged View, presumably to be able to be usable down to API 4.
To answer your question: layout is NOT messing with the positions of the View, it is just that by using the offset..() functions that ViewDragHelper is not hammering down the positioning of the View for you.
So, in your view's onLayout you'll have to account for any visual positioning done using ViewDragHelper. For example, in DrawerLayout this is internally tracked through a simple 'lp.onScreen' percentage field and in its onLayout() the corresponding shift is added to the parameters in a call to child.layout() for any dragged drawer child View.
回答3:
Sorry I don't know why the layout() is called, but maybe one of your view like ViewPager call getView(View parentView).
Anyway I assume that your using DragViewHelper like this blog describes. So there is the revelant method OnLayoutChangeListener that moves layout to previous position.
MyActivity.java
//parent
rlOuterView = (SlidingLayout) findViewById(R.id.sl_sliding_view);
//child - slides up/down
rlAppBarFooter = (RelativeLayout) findViewById(R.id.rl_footer);
rlMainView.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
@Override
public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
if( rlOuterView.isMoving() ){
v.setTop(oldTop);
v.setBottom(oldBottom);
v.setLeft(oldLeft);
v.setRight(oldRight);
}
}
});
So when is called layout() the method isMoving should be fired up - but it looks like it doesn't. Because DragViewHelper is in STATE_IDLE. I thank for trick to answer by user3452758 . You need to set flag in you custom sliding layout in method computeScroll() , that enable isMoving == true in the right time.
SlidingLayout.java
public class SlidingLayout extends RelativeLayout {
/* ........... */
@Override
public void computeScroll() { // needed for automatic settling.
if (mDragHelper.continueSettling(true)) {
ViewCompat.postInvalidateOnAnimation(this);
preventPositionReset = false;
}else{
preventPositionReset = true;
}
}
public boolean isMoving() {
return (/*mDraggingState == ViewDragHelper.STATE_IDLE ||*/
preventPositionReset ||
mDraggingState == ViewDragHelper.STATE_DRAGGING ||
mDraggingState == ViewDragHelper.STATE_SETTLING);
}
/* ......... */
}
回答4:
This answer is a little late, but the reason why your content gets relayed out to its original position when requestLayout() is called, is (mostly likely) because your code in onLayout() is not taking in to account the changes to the views position.
In flavienlaurent example,his layout code positions the draggable view at a left position of 0 but the top position uses the global scoped variable mTop, which is modified inside of callback onViewPositionChanged(android.view.View, int, int, int, int) to the value of top. You need to track the values of your change and take them into to account inside of onLayout().
A simple way of keeping the position changes bundled with the dragging view is to store them inside of the LayoutParams, and retrieve theme inside of the layout code. This is especially useful if you want to have several draggable views inside of your layout. A excellent example of this pattern is the source code of the Android SDK DrawerLayout (NavigationDrawer). Take a look at the bottom of the class, at the custom LayoutParams inner class. It has a field onScreen that is type float. This variable is used to store the difference in position (relative to the view origin) of the draggable view, and is updated in setDrawerViewOffset().
Another pattern would be to do what user @user3452758 recommended, but again this will get difficult to keep track of if your ever desire to have several draggable views.
来源:https://stackoverflow.com/questions/22865291/why-is-view-dragged-with-viewdraghelper-reset-to-its-original-position-on-layout