스크롤뷰 터치 핸들링 내의 수평 스크롤뷰
전체 화면을 스크롤할 수 있도록 전체 레이아웃을 감싸는 스크롤 뷰가 있습니다.이 ScrollView의 첫 번째 요소는 수평 스크롤이 가능한 기능을 가진 HorizontalScrollView 블록입니다.터치 이벤트를 처리하고 ACTION_UP 이벤트의 가장 가까운 이미지에 보기를 "스냅"하도록 만들기 위해 수평 스크롤 보기에 온터치 청취기를 추가했습니다.
그래서 제가 원하는 효과는 한 화면에서 다른 화면으로 스크롤할 수 있는 스톡 안드로이드 홈스크린과 같습니다. 손가락을 들면 한 화면으로 스냅됩니다.
이는 한 가지 문제를 제외하고는 모두 잘 작동합니다.ACTION_UP을 등록하려면 거의 완벽한 수평으로 왼쪽에서 오른쪽으로 스와이프해야 합니다.(많은 사람들이 좌우로 스와이프할 때 핸드폰으로 하는 경향이 있다고 생각합니다) 세로로 스와이프하면 ACTION_UP 대신 ACTION_CANCEL이 표시됩니다.수평 스크롤 뷰는 스크롤 뷰 내에 있고, 스크롤 뷰는 수직 스크롤을 허용하기 위해 수직 터치를 하이재킹하고 있기 때문이라는 것이 제 이론입니다.
수평 스크롤 뷰 바로 내에서 스크롤 뷰에 대한 터치 이벤트를 비활성화하면서도 스크롤 뷰의 다른 곳에서는 일반적인 수직 스크롤을 허용하려면 어떻게 해야 합니까?
제 코드의 샘플은 다음과 같습니다.
public class HomeFeatureLayout extends HorizontalScrollView {
private ArrayList<ListItem> items = null;
private GestureDetector gestureDetector;
View.OnTouchListener gestureListener;
private static final int SWIPE_MIN_DISTANCE = 5;
private static final int SWIPE_THRESHOLD_VELOCITY = 300;
private int activeFeature = 0;
public HomeFeatureLayout(Context context, ArrayList<ListItem> items){
super(context);
setLayoutParams(new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT));
setFadingEdgeLength(0);
this.setHorizontalScrollBarEnabled(false);
this.setVerticalScrollBarEnabled(false);
LinearLayout internalWrapper = new LinearLayout(context);
internalWrapper.setLayoutParams(new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT));
internalWrapper.setOrientation(LinearLayout.HORIZONTAL);
addView(internalWrapper);
this.items = items;
for(int i = 0; i< items.size();i++){
LinearLayout featureLayout = (LinearLayout) View.inflate(this.getContext(),R.layout.homefeature,null);
TextView header = (TextView) featureLayout.findViewById(R.id.featureheader);
ImageView image = (ImageView) featureLayout.findViewById(R.id.featureimage);
TextView title = (TextView) featureLayout.findViewById(R.id.featuretitle);
title.setTag(items.get(i).GetLinkURL());
TextView date = (TextView) featureLayout.findViewById(R.id.featuredate);
header.setText("FEATURED");
Image cachedImage = new Image(this.getContext(), items.get(i).GetImageURL());
image.setImageDrawable(cachedImage.getImage());
title.setText(items.get(i).GetTitle());
date.setText(items.get(i).GetDate());
internalWrapper.addView(featureLayout);
}
gestureDetector = new GestureDetector(new MyGestureDetector());
setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
if (gestureDetector.onTouchEvent(event)) {
return true;
}
else if(event.getAction() == MotionEvent.ACTION_UP || event.getAction() == MotionEvent.ACTION_CANCEL ){
int scrollX = getScrollX();
int featureWidth = getMeasuredWidth();
activeFeature = ((scrollX + (featureWidth/2))/featureWidth);
int scrollTo = activeFeature*featureWidth;
smoothScrollTo(scrollTo, 0);
return true;
}
else{
return false;
}
}
});
}
class MyGestureDetector extends SimpleOnGestureListener {
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
try {
//right to left
if(e1.getX() - e2.getX() > SWIPE_MIN_DISTANCE && Math.abs(velocityX) > SWIPE_THRESHOLD_VELOCITY) {
activeFeature = (activeFeature < (items.size() - 1))? activeFeature + 1:items.size() -1;
smoothScrollTo(activeFeature*getMeasuredWidth(), 0);
return true;
}
//left to right
else if (e2.getX() - e1.getX() > SWIPE_MIN_DISTANCE && Math.abs(velocityX) > SWIPE_THRESHOLD_VELOCITY) {
activeFeature = (activeFeature > 0)? activeFeature - 1:0;
smoothScrollTo(activeFeature*getMeasuredWidth(), 0);
return true;
}
} catch (Exception e) {
// nothing
}
return false;
}
}
}
업데이트: 나는 이것을 알아냈습니다.ScrollView에서 onIntercept를 재정의해야 했습니다.Y 동작이 > X 동작인 경우에만 터치 이벤트를 가로채는 TouchEvent 메서드입니다.스크롤뷰의 기본 동작은 Y 동작이 있을 때마다 터치 이벤트를 가로채는 것으로 보입니다.수정을 통해 스크롤 뷰는 사용자가 의도적으로 Y 방향으로 스크롤하고 그 경우 ACTION_CANCEL을 어린이에게 전달하는 경우에만 이벤트를 차단합니다.
다음은 HorizontalScrollView를 포함하는 ScrollView 클래스의 코드입니다.
public class CustomScrollView extends ScrollView {
private GestureDetector mGestureDetector;
public CustomScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
mGestureDetector = new GestureDetector(context, new YScrollDetector());
setFadingEdgeLength(0);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
return super.onInterceptTouchEvent(ev) && mGestureDetector.onTouchEvent(ev);
}
// Return false if we're scrolling in the x direction
class YScrollDetector extends SimpleOnGestureListener {
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
return Math.abs(distanceY) > Math.abs(distanceX);
}
}
}
이 문제를 해결할 수 있는 방법을 알려준 조엘에게 감사드립니다.
(Gesture Detector 없이) 코드를 단순화하여 동일한 효과를 얻을 수 있습니다.
public class VerticalScrollView extends ScrollView {
private float xDistance, yDistance, lastX, lastY;
public VerticalScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
xDistance = yDistance = 0f;
lastX = ev.getX();
lastY = ev.getY();
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);
}
}
더 간단한 해결책을 찾은 것 같은데, 이것만 (부모) ScrollView 대신 ViewPager의 하위 클래스를 사용합니다.
업데이트 2013-07-16: 에 대한 재정의를 추가했습니다.onTouchEvent
뿐만 아니라.YMMV이지만 댓글에 언급된 문제에 도움이 될 수도 있습니다.
public class UninterceptableViewPager extends ViewPager {
public UninterceptableViewPager(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
boolean ret = super.onInterceptTouchEvent(ev);
if (ret)
getParent().requestDisallowInterceptTouchEvent(true);
return ret;
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
boolean ret = super.onTouchEvent(ev);
if (ret)
getParent().requestDisallowInterceptTouchEvent(true);
return ret;
}
}
이것은 안드로이드에서 사용되는 기술과 유사합니다.위젯.갤러리가 Scroll()에 있습니다.Google I/O 2013 Android용 사용자 정의 보기 작성 프레젠테이션을 통해 이에 대해 자세히 설명합니다.
업데이트 2013-12-10: Kirill Grouchnikov의 Android Market 앱에 대한 게시물에도 유사한 접근 방식이 설명되어 있습니다.
한 스크롤뷰는 초점을 되찾고 다른 스크롤뷰는 초점을 잃는 경우가 있다는 것을 알게 되었습니다.스크롤 보기 포커스 중 하나만 허용하면 이를 방지할 수 있습니다.
scrollView1= (ScrollView) findViewById(R.id.scrollscroll);
scrollView1.setAdapter(adapter);
scrollView1.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
scrollView1.getParent().requestDisallowInterceptTouchEvent(true);
return false;
}
});
저는 그게 잘 안 됐어요.제가 바꿨더니 지금은 원활하게 작동합니다.관심 있는 사람이 있다면요.
public class ScrollViewForNesting extends ScrollView {
private final int DIRECTION_VERTICAL = 0;
private final int DIRECTION_HORIZONTAL = 1;
private final int DIRECTION_NO_VALUE = -1;
private final int mTouchSlop;
private int mGestureDirection;
private float mDistanceX;
private float mDistanceY;
private float mLastX;
private float mLastY;
public ScrollViewForNesting(Context context, AttributeSet attrs,
int defStyle) {
super(context, attrs, defStyle);
final ViewConfiguration configuration = ViewConfiguration.get(context);
mTouchSlop = configuration.getScaledTouchSlop();
}
public ScrollViewForNesting(Context context, AttributeSet attrs) {
this(context, attrs,0);
}
public ScrollViewForNesting(Context context) {
this(context,null);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
mDistanceY = mDistanceX = 0f;
mLastX = ev.getX();
mLastY = ev.getY();
mGestureDirection = DIRECTION_NO_VALUE;
break;
case MotionEvent.ACTION_MOVE:
final float curX = ev.getX();
final float curY = ev.getY();
mDistanceX += Math.abs(curX - mLastX);
mDistanceY += Math.abs(curY - mLastY);
mLastX = curX;
mLastY = curY;
break;
}
return super.onInterceptTouchEvent(ev) && shouldIntercept();
}
private boolean shouldIntercept(){
if((mDistanceY > mTouchSlop || mDistanceX > mTouchSlop) && mGestureDirection == DIRECTION_NO_VALUE){
if(Math.abs(mDistanceY) > Math.abs(mDistanceX)){
mGestureDirection = DIRECTION_VERTICAL;
}
else{
mGestureDirection = DIRECTION_HORIZONTAL;
}
}
if(mGestureDirection == DIRECTION_VERTICAL){
return true;
}
else{
return false;
}
}
}
니벡 덕분에 그의 대답은 효과가 있었지만 사용자가 가로 보기(ViewPager)를 가로 방향으로 스크롤하기 시작했을 때(ViewPager) 수직 스크롤이 잠기지 않고 손가락 스크롤을 수직으로 들지 않고 아래 컨테이너 보기(ScrollView)를 스크롤하기 시작했습니다.니박의 코드를 약간 변경해서 고쳤습니다.
private float xDistance, yDistance, lastX, lastY;
int lastEvent=-1;
boolean isLastEventIntercepted=false;
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
xDistance = yDistance = 0f;
lastX = ev.getX();
lastY = ev.getY();
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(isLastEventIntercepted && lastEvent== MotionEvent.ACTION_MOVE){
return false;
}
if(xDistance > yDistance )
{
isLastEventIntercepted=true;
lastEvent = MotionEvent.ACTION_MOVE;
return false;
}
}
lastEvent=ev.getAction();
isLastEventIntercepted=false;
return super.onInterceptTouchEvent(ev);
}
마침내 지원 v4 라이브러리인 NestedScrollView의 일부가 되었습니다.따라서 대부분의 경우 로컬 해킹이 더 이상 필요하지 않습니다.
니벡의 솔루션은 3.2 이상을 실행하는 장치에서 Joel의 솔루션보다 더 잘 작동합니다.안드로이드에 java.lang을 일으킬 버그가 있습니다.불법 논변예외: 포인터스콜뷰 내에서 제스처 감지기를 사용하는 경우 범위를 벗어난 인덱스입니다.문제를 복제하려면 Joel이 제안한 대로 사용자 지정 스콜뷰를 구현하고 뷰 호출기를 내부에 넣습니다.그림을 한 방향(좌/우)으로 끌어 올린 다음 반대 방향으로 끌면 충돌이 나타납니다.Joel의 솔루션에서도 손가락을 대각선으로 움직여 보기 호출기를 끌면 손가락이 보기 호출기의 내용 보기 영역을 벗어나면 호출기가 이전 위치로 되돌아옵니다.이 모든 문제는 조엘의 구현 자체가 똑똑하고 간결한 코드의 일부이기보다는 안드로이드의 내부 디자인이나 부족과 더 관련이 있습니다.
http://code.google.com/p/android/issues/detail?id=18990
날짜 : 2021 - 5월 12일
겉보기엔 말도 안되는..하지만 세로 스크롤 뷰 버터 스무스에서 어떤 뷰라도 가로로 스크롤하고 싶다면 그 시간만큼의 가치가 있다는 것을 믿어주세요!!
Jetpack에서 작업하는 것은 사용자 지정 보기를 만들고 가로로 스크롤할 보기를 확장하여 작성합니다. 세로 스크롤 보기 안에서 AndroidView에서 해당 사용자 지정 보기를 사용하여 합성할 수 있습니다(지금 Jetpack Composition is 1.0.0-beta06).
수평 스크롤을 할 때 수직 스크롤 바가 터치를 가로채지 않고 수평 스크롤 보기를 통해 수직 스크롤 바가 터치를 가로채지 않고 자유롭게 수직 스크롤을 원하는 경우 가장 최적의 솔루션입니다.
private class HorizontallyScrollingView @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null
) : ViewThatYouWannaScrollHorizontally(context, attrs){
override fun onTouchEvent(event: MotionEvent?): Boolean {
// When the user's finger touches the webview and starts moving
if(event?.action == MotionEvent.ACTION_MOVE){
// get the velocity tracker object
val mVelocityTracker = VelocityTracker.obtain();
// connect the velocity tracker object with the event that we are emitting while we are touching the webview
mVelocityTracker.addMovement(event)
// compute the velocity in terms of pixels per 1000 millisecond(i.e 1 second)
mVelocityTracker.computeCurrentVelocity(1000);
// compute the Absolute Velocity in X axis
val xVelocityABS = abs(mVelocityTracker.getXVelocity(event?.getPointerId((event?.actionIndex))));
// compute the Absolute Velocity in Y axis
val yVelocityABS = abs(mVelocityTracker.getYVelocity(event?.getPointerId((event?.actionIndex))));
// If the velocity of x axis is greater than y axis then we'll consider that it's a horizontal scroll and tell the parent layout
// "Hey parent bro! im scrolling horizontally, this has nothing to do with ur scrollview so stop capturing my event and stay the f*** where u are "
if(xVelocityABS > yVelocityABS){
// So, we'll disallow the parent to listen to any touch events until i have moved my fingers off the screen
parent.requestDisallowInterceptTouchEvent(true)
}
} else if (event?.action == MotionEvent.ACTION_CANCEL || event?.action == MotionEvent.ACTION_UP){
// If the touch event has been cancelled or the finger is off the screen then reset it (i.e let the parent capture the touch events on webview as well)
parent.requestDisallowInterceptTouchEvent(false)
}
return super.onTouchEvent(event)
}
}
여기서,ViewThatYouWannaScrollHorizontally
가로 스크롤을 원하는 보기로, 가로 스크롤을 할 때 세로 스크롤 바가 터치를 캡처하지 않도록 하고 "오! 사용자가 세로 스크롤을 하고 있습니다. parent.requestDisallowInterceptTouchEvent(true)
기본적으로 세로 스크롤 바는 "헤이 유! 사용자가 가로로 스크롤하고 있으므로 터치를 캡처하지 마십시오."라고 표시됩니다.
그리고 사용자가 수평 스크롤을 완료하고 수직 스크롤 바 안에 있는 수평 스크롤 바를 통해 수직 스크롤을 시도하면 Y축의 터치 속도가 X축보다 크다는 것을 알 수 있고, 이는 사용자가 수평 스크롤을 하지 않고 있다는 것을 보여주며, 수평 스크롤링 내용은 "야 너! 부모님, 내 말 들립니까?사용자가 나를 수직으로 스크롤하고 있습니다. 이제 터치를 가로채서 내 아래에 있는 것들을 수직 스크롤로 보여줄 수 있습니다."
언급URL : https://stackoverflow.com/questions/2646028/horizontalscrollview-within-scrollview-touch-handling
'programing' 카테고리의 다른 글
#로 재미있는 표기법 (0) | 2023.09.13 |
---|---|
Python 인메모리 캐시(수명 연장) (0) | 2023.09.13 |
이 jQuery ajax 클릭 이벤트가 여러 번 발생하는 이유는 무엇입니까? (0) | 2023.09.13 |
WordPress에서의 콜백 함수로서의 클래스 전달방법 (0) | 2023.09.13 |
컴파일 오류: 프로그램의 stray '\302' 등 (0) | 2023.09.13 |