SwipeRefreshLayout + ViewPager, 가로 스크롤 만 제한 하시겠습니까?
내가 구현 한 SwipeRefreshLayout
및 ViewPager
내 응용 프로그램에서하지만 큰 문제가 있습니다 : 내가 페이지 사이를 잘 스위치 / 왼쪽으로 스 와이프 갈거야 때마다 스크롤이 너무 민감하다. 아래로 약간 스 와이프하면 SwipeRefreshLayout
새로 고침도 트리거됩니다 .
수평 스 와이프가 시작될 때로 제한을 설정 한 다음 스 와이프가 끝날 때까지만 수평으로 강제로 설정하고 싶습니다. 즉, 손가락이 수평으로 움직일 때 수직 스 와이프를 취소하고 싶습니다.
이 문제는에서만 발생합니다. ViewPager
아래로 스 와이프하고 SwipeRefreshLayout
새로 고침 기능이 트리거 된 다음 (바가 표시됨) 손가락을 가로로 움직여도 세로 스 와이프 만 허용됩니다.
ViewPager
클래스 를 확장하려고했지만 전혀 작동하지 않습니다.
public class CustomViewPager extends ViewPager {
public CustomViewPager(Context ctx, AttributeSet attrs) {
super(ctx, attrs);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
boolean in = super.onInterceptTouchEvent(ev);
if (in) {
getParent().requestDisallowInterceptTouchEvent(true);
this.requestDisallowInterceptTouchEvent(true);
}
return false;
}
}
레이아웃 xml :
<android.support.v4.widget.SwipeRefreshLayout
android:id="@+id/viewTopic"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.myapp.listloader.foundation.CustomViewPager
android:id="@+id/topicViewPager"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</android.support.v4.widget.SwipeRefreshLayout>
어떤 도움을 주시면 감사하겠습니다.
여전히이 문제가 있는지 확실하지 않지만 Google I / O 앱 iosched는이 문제를 따라서 해결합니다.
viewPager.addOnPageChangeListener( new ViewPager.OnPageChangeListener() {
@Override
public void onPageScrolled( int position, float v, int i1 ) {
}
@Override
public void onPageSelected( int position ) {
}
@Override
public void onPageScrollStateChanged( int state ) {
enableDisableSwipeRefresh( state == ViewPager.SCROLL_STATE_IDLE );
}
} );
private void enableDisableSwipeRefresh(boolean enable) {
if (swipeContainer != null) {
swipeContainer.setEnabled(enable);
}
}
나는 똑같은 것을 사용했고 아주 잘 작동합니다.
편집 : setOnPageChangeListener () 대신 addOnPageChangeListener ()를 사용하십시오.
아무것도 확장하지 않고 매우 간단하게 해결
mPager.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
mLayout.setEnabled(false);
switch (event.getAction()) {
case MotionEvent.ACTION_UP:
mLayout.setEnabled(true);
break;
}
return false;
}
});
매력처럼 일하다
나는 당신의 문제를 만났습니다. SwipeRefreshLayout을 사용자 지정하면 문제가 해결됩니다.
public class CustomSwipeToRefresh extends SwipeRefreshLayout {
private int mTouchSlop;
private float mPrevX;
public CustomSwipeToRefresh(Context context, AttributeSet attrs) {
super(context, attrs);
mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
}
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mPrevX = MotionEvent.obtain(event).getX();
break;
case MotionEvent.ACTION_MOVE:
final float eventX = event.getX();
float xDiff = Math.abs(eventX - mPrevX);
if (xDiff > mTouchSlop) {
return false;
}
}
return super.onInterceptTouchEvent(event);
}
참조 참조 : 링크
나는 이것을 이전 답변을 기반으로했지만 이것이 조금 더 잘 작동한다는 것을 알았습니다. 모션은 ACTION_MOVE 이벤트로 시작하여 내 경험상 ACTION_UP 또는 ACTION_CANCEL로 끝납니다.
mViewPager.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_MOVE:
mSwipeRefreshLayout.setEnabled(false);
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
mSwipeRefreshLayout.setEnabled(true);
break;
}
return false;
}
});
그들에게만 가장 잘 알려진 어떤 이유로 지원 라이브러리 개발자 팀은 어린이가 이벤트의 소유권을 구체적으로 요청하더라도의 하위 레이아웃 에서 모든 수직 드래그 모션 이벤트 를 강제로 가로채는 데 적합하다고 생각했습니다 SwipeRefreshLayout
. 그들이 확인하는 유일한 것은 기본 자식의 세로 스크롤 상태가 0이라는 것입니다 (자식의 경우 세로로 스크롤 할 수 있음). 이 requestDisallowInterceptTouchEvent()
메서드는 빈 본문과 조명 주석 "Nope"로 재정의되었습니다.
이 문제를 해결하는 가장 쉬운 방법은 지원 라이브러리의 클래스를 프로젝트로 복사하고 메서드 재정의를 제거하는 것입니다. ViewGroup
의 구현은 처리를 위해 내부 상태를 사용 onInterceptTouchEvent()
하므로 단순히 메서드를 다시 재정의하고 복제 할 수 없습니다. 실제로 지원 라이브러리 구현 을 재정의 하려면를 호출 할 때 사용자 지정 플래그를 설정하고이를 기반으로 동작을 requestDisallowInterceptTouchEvent()
재정의 onInterceptTouchEvent()
하고 onTouchEvent()
(또는 해킹 할 수 있음 canChildScrollUp()
) 동작을 재정의 해야합니다.
nhasan 솔루션에는 한 가지 문제가 있습니다.
만약 트리거하는 수평 슬쩍 setEnabled(false)
온 전화 SwipeRefreshLayout
에서이 OnPageChangeListener
일이 (가) SwipeRefreshLayout
이미 인식하고 끌어 오기 - 다시로드 투하지만 아직의 알림 콜백, 애니메이션 사라하지만 내부 상태라는되지 SwipeRefreshLayout
는 어떠한 영원히 "새로 고침"에 숙박을 상태를 재설정 할 수있는 알림 콜백이 호출됩니다. 사용자 관점에서 이것은 모든 당기기 제스처가 인식되지 않기 때문에 Pull-to-Reload가 더 이상 작동하지 않음을 의미합니다.
여기서 문제는 disable(false)
호출이 스피너의 애니메이션을 제거하고 알림 콜백이 해당 onAnimationEnd
방식으로 설정된 해당 스피너에 대한 내부 AnimationListener 의 메소드 에서 호출 된다는 것입니다.
이 상황을 유발하는 데 가장 빠른 손가락을 가진 테스터가 필요했지만 현실적인 시나리오에서도 가끔 발생할 수 있습니다.
이 문제를 해결하는 onInterceptTouchEvent
방법은 SwipeRefreshLayout
다음과 같이 메서드 를 재정의하는 것입니다 .
public class MySwipeRefreshLayout extends SwipeRefreshLayout {
private boolean paused;
public MySwipeRefreshLayout(Context context) {
super(context);
setColorScheme();
}
public MySwipeRefreshLayout(Context context, AttributeSet attrs) {
super(context, attrs);
setColorScheme();
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
if (paused) {
return false;
} else {
return super.onInterceptTouchEvent(ev);
}
}
public void setPaused(boolean paused) {
this.paused = paused;
}
}
MySwipeRefreshLayout
레이아웃에서 사용 -파일 및 mhasan 솔루션의 코드를 다음과 같이 변경하십시오.
...
@Override
public void onPageScrollStateChanged(int state) {
swipeRefreshLayout.setPaused(state != ViewPager.SCROLL_STATE_IDLE);
}
...
ViewPager2에 대한 솔루션을 찾았습니다. 다음과 같이 드래그 감도를 줄이기 위해 반사를 사용합니다.
/**
* Reduces drag sensitivity of [ViewPager2] widget
*/
fun ViewPager2.reduceDragSensitivity() {
val recyclerViewField = ViewPager2::class.java.getDeclaredField("mRecyclerView")
recyclerViewField.isAccessible = true
val recyclerView = recyclerViewField.get(this) as RecyclerView
val touchSlopField = RecyclerView::class.java.getDeclaredField("mTouchSlop")
touchSlopField.isAccessible = true
val touchSlop = touchSlopField.get(recyclerView) as Int
touchSlopField.set(recyclerView, touchSlop*8) // "8" was obtained experimentally
}
제게는 매력처럼 작동합니다.
There could be a problem with @huu duy answer when the ViewPager is placed in a vertically-scrollable container which, in turn, is placed in the SwiprRefreshLayout If the content scrollable container is not fully scrolled-up, then it may be not possible to activate swipe-to-refresh in the same scroll-up gesture. Indeed, when you start scrolling the inner container and move finger horizontally more then mTouchSlop unintentionally (which is 8dp by default), the proposed CustomSwipeToRefresh declines this gesture. So a user has to try once more to start refreshing. This may look odd for the user. I extracted the source code f the original SwipeRefreshLayout from the support library to my project and re-wrote the onInterceptTouchEvent().
private float mInitialDownY;
private float mInitialDownX;
private boolean mGestureDeclined;
private boolean mPendingActionDown;
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
ensureTarget();
final int action = ev.getActionMasked();
int pointerIndex;
if (mReturningToStart && action == MotionEvent.ACTION_DOWN) {
mReturningToStart = false;
}
if (!isEnabled() || mReturningToStart || mRefreshing ) {
// Fail fast if we're not in a state where a swipe is possible
if (D) Log.e(LOG_TAG, "Fail because of not enabled OR refreshing OR returning to start. "+motionEventToShortText(ev));
return false;
}
switch (action) {
case MotionEvent.ACTION_DOWN:
setTargetOffsetTopAndBottom(mOriginalOffsetTop - mCircleView.getTop());
mActivePointerId = ev.getPointerId(0);
if ((pointerIndex = ev.findPointerIndex(mActivePointerId)) >= 0) {
if (mNestedScrollInProgress || canChildScrollUp()) {
if (D) Log.e(LOG_TAG, "Fail because of nested content is Scrolling. Set pending DOWN=true. "+motionEventToShortText(ev));
mPendingActionDown = true;
} else {
mInitialDownX = ev.getX(pointerIndex);
mInitialDownY = ev.getY(pointerIndex);
}
}
return false;
case MotionEvent.ACTION_MOVE:
if (mActivePointerId == INVALID_POINTER) {
if (D) Log.e(LOG_TAG, "Got ACTION_MOVE event but don't have an active pointer id.");
return false;
} else if (mGestureDeclined) {
if (D) Log.e(LOG_TAG, "Gesture was declined previously because of horizontal swipe");
return false;
} else if ((pointerIndex = ev.findPointerIndex(mActivePointerId)) < 0) {
return false;
} else if (mNestedScrollInProgress || canChildScrollUp()) {
if (D) Log.e(LOG_TAG, "Fail because of nested content is Scrolling. "+motionEventToShortText(ev));
return false;
} else if (mPendingActionDown) {
// This is the 1-st Move after content stops scrolling.
// Consider this Move as Down (a start of new gesture)
if (D) Log.e(LOG_TAG, "Consider this move as down - setup initial X/Y."+motionEventToShortText(ev));
mPendingActionDown = false;
mInitialDownX = ev.getX(pointerIndex);
mInitialDownY = ev.getY(pointerIndex);
return false;
} else if (Math.abs(ev.getX(pointerIndex) - mInitialDownX) > mTouchSlop) {
mGestureDeclined = true;
if (D) Log.e(LOG_TAG, "Decline gesture because of horizontal swipe");
return false;
}
final float y = ev.getY(pointerIndex);
startDragging(y);
if (!mIsBeingDragged) {
if (D) Log.d(LOG_TAG, "Waiting for dY to start dragging. "+motionEventToShortText(ev));
} else {
if (D) Log.d(LOG_TAG, "Dragging started! "+motionEventToShortText(ev));
}
break;
case MotionEvent.ACTION_POINTER_UP:
onSecondaryPointerUp(ev);
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
mIsBeingDragged = false;
mGestureDeclined = false;
mPendingActionDown = false;
mActivePointerId = INVALID_POINTER;
break;
}
return mIsBeingDragged;
}
See my example project on Github.
'programing' 카테고리의 다른 글
.sql 파일을 SQLite 3으로 어떻게 가져 옵니까? (0) | 2020.09.13 |
---|---|
403을 제공하는 MVC4 스타일 번들 (0) | 2020.09.13 |
Casio FX-991ES 계산기에서 Mod b를 계산하는 방법 (0) | 2020.09.13 |
페이지를 다시로드 한 후 트위터 부트 스트랩으로 현재 탭을 활성화하려면 어떻게해야합니까? (0) | 2020.09.13 |
데이터 바인딩없이 값 렌더링 (0) | 2020.09.13 |