Trong Android, người dùng thường cuộn bằng cách sử dụng
ScrollView
. Lồng mọi bố cục chuẩn có thể mở rộng ra ngoài giới hạn của bố cục
vùng chứa trong ScrollView
để cung cấp chế độ xem có thể cuộn được quản lý bằng
khung này. Việc triển khai một trình cuộn tuỳ chỉnh chỉ cần thiết cho những
trong trường hợp cụ thể. Tài liệu này mô tả cách hiển thị hiệu ứng cuộn trong phản hồi
chạm vào các cử chỉ bằng công cụ cuộn.
Ứng dụng của bạn có thể sử dụng
trình cuộn – Scroller
hoặc
OverScroller
— đến
thu thập dữ liệu cần thiết để tạo ảnh động cuộn nhằm phản hồi lại một thao tác chạm
sự kiện. Tương tự như vậy, OverScroller
cũng bao gồm các phương thức cho
cho người dùng biết khi nào họ đến mép nội dung sau khi liên tục hoặc hất
cử chỉ.
- Kể từ Android 12 (API cấp 31), các phần tử hình ảnh sẽ kéo giãn và nảy quay lại một sự kiện kéo, hất và bật trở lại trong một sự kiện hất.
- Trên Android 11 (API cấp 30) trở xuống, các ranh giới sẽ hiển thị biểu tượng "phát sáng" sau khi thực hiện cử chỉ kéo hoặc hất sang cạnh.
Mẫu InteractiveChart
trong tài liệu này sử dụng
EdgeEffect
để hiển thị các hiệu ứng cuộn quá mức này.
Bạn có thể sử dụng trình cuộn để tạo hiệu ứng cuộn theo thời gian, sử dụng vật lý cuộn tiêu chuẩn của nền tảng, chẳng hạn như ma sát, vận tốc, v.v. chất lượng. Bản thân trình cuộn không vẽ bất cứ thứ gì. Cuộn theo dõi cuộn giá trị bù trừ cho bạn theo thời gian, nhưng chúng không tự động áp dụng các vị trí đó cho chế độ xem của mình. Bạn phải nhận và áp dụng toạ độ mới theo tỷ lệ khiến hoạt ảnh cuộn trông mượt mà.
Tìm hiểu thuật ngữ về tính năng cuộn
Cuộn là một từ có thể mang nhiều ý nghĩa trong Android, tuỳ thuộc vào ngữ cảnh.
Cuộn là quy trình chung để di chuyển khung nhìn, tức là
"cửa sổ" nội dung bạn đang xem. Khi cuộn nằm trong cả hai
Trục x và y, nên được gọi là di chuyển. Chiến lược phát hành đĩa đơn
Ứng dụng mẫu InteractiveChart
trong tài liệu này minh hoạ 2
các kiểu cuộn, kéo và hất khác nhau:
- Kéo: đây là kiểu cuộn xảy ra khi người dùng
kéo ngón tay trên màn hình cảm ứng. Bạn có thể triển khai tính năng kéo bằng cách
ghi đè
onScroll()
inchGestureDetector.OnGestureListener
Để biết thêm thông tin về cách kéo, hãy xem Kéo và chuyển tỷ lệ. - Di chuyển: đây là kiểu cuộn xảy ra khi người dùng
kéo và nhấc ngón tay một cách nhanh chóng. Sau khi người dùng nhấc ngón tay lên, bạn
thường muốn tiếp tục di chuyển khung nhìn, nhưng hãy giảm tốc cho đến khi
khung nhìn ngừng di chuyển. Bạn có thể thực hiện cử chỉ hất bằng cách ghi đè
onFling()
trongGestureDetector.OnGestureListener
và dùng một trình cuộn . - Xoay: cuộn đồng thời dọc theo cả dấu x- và Trục y được gọi là di chuyển.
Thông thường, bạn nên sử dụng các đối tượng cuộn cùng với cử chỉ hất, nhưng
bạn có thể dùng chúng trong mọi ngữ cảnh mà bạn muốn giao diện người dùng hiển thị thao tác cuộn
phản hồi với một sự kiện chạm. Ví dụ: bạn có thể ghi đè
onTouchEvent()
để xử lý trực tiếp các sự kiện chạm và tạo ra hiệu ứng cuộn hoặc
"gắn vào trang" để phản hồi các sự kiện chạm đó.
Các thành phần có chứa phương thức triển khai tính năng cuộn tích hợp
Các thành phần Android sau đây có hỗ trợ tích hợp sẵn cho thao tác cuộn và hành vi cuộn quá mức:
GridView
HorizontalScrollView
ListView
NestedScrollView
RecyclerView
ScrollView
ViewPager
ViewPager2
Nếu ứng dụng của bạn cần hỗ trợ tính năng cuộn và cuộn quá mức bên trong một thành phần, hãy hoàn tất các bước sau:
- Tạo thao tác cuộn tuỳ chỉnh dựa trên thao tác chạm triển khai.
- Để hỗ trợ các thiết bị chạy Android 12 trở lên, triển khai hiệu ứng kéo giãn khi cuộn quá mức hiệu ứng.
Tạo cách triển khai cuộn dựa trên cảm ứng tuỳ chỉnh
Phần này mô tả cách tạo trình cuộn của riêng bạn nếu ứng dụng của bạn sử dụng thành phần không có tính năng hỗ trợ tích hợp sẵn cho cuộn và cuộn quá mức.
Đoạn mã sau xuất phát từ
InteractiveChart
mẫu. Chiến dịch này sử dụng một
GestureDetector
và ghi đè
GestureDetector.SimpleOnGestureListener
phương thức onFling()
. Hàm này sử dụng OverScroller
để theo dõi
cử chỉ hất. Nếu người dùng đến các cạnh nội dung sau khi thực hiện
cử chỉ hất, vùng chứa cho biết thời điểm người dùng đến cuối
nội dung. Chỉ báo này phụ thuộc vào phiên bản Android mà thiết bị
lần chạy:
- Trên Android 12 trở lên, các thành phần hình ảnh sẽ kéo dài và bật lại.
- Trên Android 11 trở xuống, các phần tử hình ảnh sẽ cho thấy ánh sáng rực rỡ hiệu ứng.
Phần đầu tiên của đoạn mã sau cho thấy cách triển khai
onFling()
:
Kotlin
// Viewport extremes. See currentViewport for a discussion of the viewport. private val AXIS_X_MIN = -1f private val AXIS_X_MAX = 1f private val AXIS_Y_MIN = -1f private val AXIS_Y_MAX = 1f // The current viewport. This rectangle represents the visible chart // domain and range. The viewport is the part of the app that the // user manipulates via touch gestures. private val currentViewport = RectF(AXIS_X_MIN, AXIS_Y_MIN, AXIS_X_MAX, AXIS_Y_MAX) // The current destination rectangle—in pixel coordinates—into which // the chart data must be drawn. private lateinit var contentRect: Rect private lateinit var scroller: OverScroller private lateinit var scrollerStartViewport: RectF ... private val gestureListener = object : GestureDetector.SimpleOnGestureListener() { override fun onDown(e: MotionEvent): Boolean { // Initiates the decay phase of any active edge effects. if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) { releaseEdgeEffects() } scrollerStartViewport.set(currentViewport) // Aborts any active scroll animations and invalidates. scroller.forceFinished(true) ViewCompat.postInvalidateOnAnimation(this@InteractiveLineGraphView) return true } ... override fun onFling( e1: MotionEvent, e2: MotionEvent, velocityX: Float, velocityY: Float ): Boolean { fling((-velocityX).toInt(), (-velocityY).toInt()) return true } } private fun fling(velocityX: Int, velocityY: Int) { // Initiates the decay phase of any active edge effects. // On Android 12 and later, the edge effect (stretch) must // continue. if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) { releaseEdgeEffects() } // Flings use math in pixels, as opposed to math based on the viewport. val surfaceSize: Point = computeScrollSurfaceSize() val (startX: Int, startY: Int) = scrollerStartViewport.run { set(currentViewport) (surfaceSize.x * (left - AXIS_X_MIN) / (AXIS_X_MAX - AXIS_X_MIN)).toInt() to (surfaceSize.y * (AXIS_Y_MAX - bottom) / (AXIS_Y_MAX - AXIS_Y_MIN)).toInt() } // Before flinging, stops the current animation. scroller.forceFinished(true) // Begins the animation. scroller.fling( // Current scroll position. startX, startY, velocityX, velocityY, /* * Minimum and maximum scroll positions. The minimum scroll * position is generally 0 and the maximum scroll position * is generally the content size less the screen size. So if the * content width is 1000 pixels and the screen width is 200 * pixels, the maximum scroll offset is 800 pixels. */ 0, surfaceSize.x - contentRect.width(), 0, surfaceSize.y - contentRect.height(), // The edges of the content. This comes into play when using // the EdgeEffect class to draw "glow" overlays. contentRect.width() / 2, contentRect.height() / 2 ) // Invalidates to trigger computeScroll(). ViewCompat.postInvalidateOnAnimation(this) }
Java
// Viewport extremes. See currentViewport for a discussion of the viewport. private static final float AXIS_X_MIN = -1f; private static final float AXIS_X_MAX = 1f; private static final float AXIS_Y_MIN = -1f; private static final float AXIS_Y_MAX = 1f; // The current viewport. This rectangle represents the visible chart // domain and range. The viewport is the part of the app that the // user manipulates via touch gestures. private RectF currentViewport = new RectF(AXIS_X_MIN, AXIS_Y_MIN, AXIS_X_MAX, AXIS_Y_MAX); // The current destination rectangle—in pixel coordinates—into which // the chart data must be drawn. private final Rect contentRect = new Rect(); private final OverScroller scroller; private final RectF scrollerStartViewport = new RectF(); // Used only for zooms and flings. ... private final GestureDetector.SimpleOnGestureListener gestureListener = new GestureDetector.SimpleOnGestureListener() { @Override public boolean onDown(MotionEvent e) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) { releaseEdgeEffects(); } scrollerStartViewport.set(currentViewport); scroller.forceFinished(true); ViewCompat.postInvalidateOnAnimation(InteractiveLineGraphView.this); return true; } ... @Override public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { fling((int) -velocityX, (int) -velocityY); return true; } }; private void fling(int velocityX, int velocityY) { // Initiates the decay phase of any active edge effects. // On Android 12 and later, the edge effect (stretch) must // continue. if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) { releaseEdgeEffects(); } // Flings use math in pixels, as opposed to math based on the viewport. Point surfaceSize = computeScrollSurfaceSize(); scrollerStartViewport.set(currentViewport); int startX = (int) (surfaceSize.x * (scrollerStartViewport.left - AXIS_X_MIN) / ( AXIS_X_MAX - AXIS_X_MIN)); int startY = (int) (surfaceSize.y * (AXIS_Y_MAX - scrollerStartViewport.bottom) / ( AXIS_Y_MAX - AXIS_Y_MIN)); // Before flinging, stops the current animation. scroller.forceFinished(true); // Begins the animation. scroller.fling( // Current scroll position. startX, startY, velocityX, velocityY, /* * Minimum and maximum scroll positions. The minimum scroll * position is generally 0 and the maximum scroll position * is generally the content size less the screen size. So if the * content width is 1000 pixels and the screen width is 200 * pixels, the maximum scroll offset is 800 pixels. */ 0, surfaceSize.x - contentRect.width(), 0, surfaceSize.y - contentRect.height(), // The edges of the content. This comes into play when using // the EdgeEffect class to draw "glow" overlays. contentRect.width() / 2, contentRect.height() / 2); // Invalidates to trigger computeScroll(). ViewCompat.postInvalidateOnAnimation(this); }
Khi onFling()
gọi
postInvalidateOnAnimation()
,
nó kích hoạt
computeScroll()
để cập nhật giá trị của x và y. Điều này thường được thực hiện khi
thành phần hiển thị con đang tạo ảnh động cho thao tác cuộn bằng cách sử dụng đối tượng cuộn, như đã minh hoạ
ví dụ:
Hầu hết thành phần hiển thị đều truyền trực tiếp vị trí x và y của đối tượng cuộn
đến
scrollTo()
.
Quá trình triển khai computeScroll()
sau đây sử dụng một cách khác
phương thức tiếp cận: phương thức gọi
computeScrollOffset()
để nhận vị trí hiện tại của x và y. Khi các tiêu chí cho
hiển thị "glow" cuộn quá mức đáp ứng hiệu ứng cạnh — tức là màn hình
được phóng to, x hoặc y nằm ngoài giới hạn và ứng dụng chưa ở trạng thái này
hiển thị hiệu ứng cuộn quá mức—mã thiết lập hiệu ứng ánh sáng cuộn quá mức và
gọi postInvalidateOnAnimation()
để kích hoạt lệnh vô hiệu hoá trên
chế độ xem.
Kotlin
// Edge effect/overscroll tracking objects. private lateinit var edgeEffectTop: EdgeEffect private lateinit var edgeEffectBottom: EdgeEffect private lateinit var edgeEffectLeft: EdgeEffect private lateinit var edgeEffectRight: EdgeEffect private var edgeEffectTopActive: Boolean = false private var edgeEffectBottomActive: Boolean = false private var edgeEffectLeftActive: Boolean = false private var edgeEffectRightActive: Boolean = false override fun computeScroll() { super.computeScroll() var needsInvalidate = false // The scroller isn't finished, meaning a fling or // programmatic pan operation is active. if (scroller.computeScrollOffset()) { val surfaceSize: Point = computeScrollSurfaceSize() val currX: Int = scroller.currX val currY: Int = scroller.currY val (canScrollX: Boolean, canScrollY: Boolean) = currentViewport.run { (left > AXIS_X_MIN || right < AXIS_X_MAX) to (top > AXIS_Y_MIN || bottom < AXIS_Y_MAX) } /* * If you are zoomed in, currX or currY is * outside of bounds, and you aren't already * showing overscroll, then render the overscroll * glow edge effect. */ if (canScrollX && currX < 0 && edgeEffectLeft.isFinished && !edgeEffectLeftActive) { edgeEffectLeft.onAbsorb(scroller.currVelocity.toInt()) edgeEffectLeftActive = true needsInvalidate = true } else if (canScrollX && currX > surfaceSize.x - contentRect.width() && edgeEffectRight.isFinished && !edgeEffectRightActive) { edgeEffectRight.onAbsorb(scroller.currVelocity.toInt()) edgeEffectRightActive = true needsInvalidate = true } if (canScrollY && currY < 0 && edgeEffectTop.isFinished && !edgeEffectTopActive) { edgeEffectTop.onAbsorb(scroller.currVelocity.toInt()) edgeEffectTopActive = true needsInvalidate = true } else if (canScrollY && currY > surfaceSize.y - contentRect.height() && edgeEffectBottom.isFinished && !edgeEffectBottomActive) { edgeEffectBottom.onAbsorb(scroller.currVelocity.toInt()) edgeEffectBottomActive = true needsInvalidate = true } ... } }
Java
// Edge effect/overscroll tracking objects. private EdgeEffectCompat edgeEffectTop; private EdgeEffectCompat edgeEffectBottom; private EdgeEffectCompat edgeEffectLeft; private EdgeEffectCompat edgeEffectRight; private boolean edgeEffectTopActive; private boolean edgeEffectBottomActive; private boolean edgeEffectLeftActive; private boolean edgeEffectRightActive; @Override public void computeScroll() { super.computeScroll(); boolean needsInvalidate = false; // The scroller isn't finished, meaning a fling or // programmatic pan operation is active. if (scroller.computeScrollOffset()) { Point surfaceSize = computeScrollSurfaceSize(); int currX = scroller.getCurrX(); int currY = scroller.getCurrY(); boolean canScrollX = (currentViewport.left > AXIS_X_MIN || currentViewport.right < AXIS_X_MAX); boolean canScrollY = (currentViewport.top > AXIS_Y_MIN || currentViewport.bottom < AXIS_Y_MAX); /* * If you are zoomed in, currX or currY is * outside of bounds, and you aren't already * showing overscroll, then render the overscroll * glow edge effect. */ if (canScrollX && currX < 0 && edgeEffectLeft.isFinished() && !edgeEffectLeftActive) { edgeEffectLeft.onAbsorb((int)mScroller.getCurrVelocity()); edgeEffectLeftActive = true; needsInvalidate = true; } else if (canScrollX && currX > (surfaceSize.x - contentRect.width()) && edgeEffectRight.isFinished() && !edgeEffectRightActive) { edgeEffectRight.onAbsorb((int)mScroller.getCurrVelocity()); edgeEffectRightActive = true; needsInvalidate = true; } if (canScrollY && currY < 0 && edgeEffectTop.isFinished() && !edgeEffectTopActive) { edgeEffectRight.onAbsorb((int)mScroller.getCurrVelocity()); edgeEffectTopActive = true; needsInvalidate = true; } else if (canScrollY && currY > (surfaceSize.y - contentRect.height()) && edgeEffectBottom.isFinished() && !edgeEffectBottomActive) { edgeEffectRight.onAbsorb((int)mScroller.getCurrVelocity()); edgeEffectBottomActive = true; needsInvalidate = true; } ... }
Dưới đây là phần mã thực hiện việc thu phóng thực tế:
Kotlin
lateinit var zoomer: Zoomer val zoomFocalPoint = PointF() ... // If a zoom is in progress—either programmatically // or through double touch—this performs the zoom. if (zoomer.computeZoom()) { val newWidth: Float = (1f - zoomer.currZoom) * scrollerStartViewport.width() val newHeight: Float = (1f - zoomer.currZoom) * scrollerStartViewport.height() val pointWithinViewportX: Float = (zoomFocalPoint.x - scrollerStartViewport.left) / scrollerStartViewport.width() val pointWithinViewportY: Float = (zoomFocalPoint.y - scrollerStartViewport.top) / scrollerStartViewport.height() currentViewport.set( zoomFocalPoint.x - newWidth * pointWithinViewportX, zoomFocalPoint.y - newHeight * pointWithinViewportY, zoomFocalPoint.x + newWidth * (1 - pointWithinViewportX), zoomFocalPoint.y + newHeight * (1 - pointWithinViewportY) ) constrainViewport() needsInvalidate = true } if (needsInvalidate) { ViewCompat.postInvalidateOnAnimation(this) }
Java
// Custom object that is functionally similar to Scroller. Zoomer zoomer; private PointF zoomFocalPoint = new PointF(); ... // If a zoom is in progress—either programmatically // or through double touch—this performs the zoom. if (zoomer.computeZoom()) { float newWidth = (1f - zoomer.getCurrZoom()) * scrollerStartViewport.width(); float newHeight = (1f - zoomer.getCurrZoom()) * scrollerStartViewport.height(); float pointWithinViewportX = (zoomFocalPoint.x - scrollerStartViewport.left) / scrollerStartViewport.width(); float pointWithinViewportY = (zoomFocalPoint.y - scrollerStartViewport.top) / scrollerStartViewport.height(); currentViewport.set( zoomFocalPoint.x - newWidth * pointWithinViewportX, zoomFocalPoint.y - newHeight * pointWithinViewportY, zoomFocalPoint.x + newWidth * (1 - pointWithinViewportX), zoomFocalPoint.y + newHeight * (1 - pointWithinViewportY)); constrainViewport(); needsInvalidate = true; } if (needsInvalidate) { ViewCompat.postInvalidateOnAnimation(this); }
Đây là phương thức computeScrollSurfaceSize()
được gọi trong
đoạn mã trước đó. Mô hình này tính toán kích thước của bề mặt có thể cuộn hiện tại trong
điểm ảnh. Ví dụ: nếu toàn bộ vùng biểu đồ hiển thị, thì đây là vùng hiện tại
mContentRect
. Nếu biểu đồ được phóng to 200% ở cả hai
thì kích thước được trả về sẽ lớn gấp đôi theo chiều ngang và chiều dọc.
Kotlin
private fun computeScrollSurfaceSize(): Point { return Point( (contentRect.width() * (AXIS_X_MAX - AXIS_X_MIN) / currentViewport.width()).toInt(), (contentRect.height() * (AXIS_Y_MAX - AXIS_Y_MIN) / currentViewport.height()).toInt() ) }
Java
private Point computeScrollSurfaceSize() { return new Point( (int) (contentRect.width() * (AXIS_X_MAX - AXIS_X_MIN) / currentViewport.width()), (int) (contentRect.height() * (AXIS_Y_MAX - AXIS_Y_MIN) / currentViewport.height())); }
Để biết một ví dụ khác về cách sử dụng trình cuộn, hãy xem
mã nguồn
cho lớp ViewPager
. Cuộn để phản hồi các cử chỉ hất và sử dụng
để triển khai chức năng "gắn vào trang" ảnh động.
Triển khai hiệu ứng kéo giãn khi cuộn quá mức
Kể từ Android 12, EdgeEffect
sẽ thêm
các API sau để triển khai hiệu ứng cuộn quá mức:
getDistance()
onPullDistance()
Để mang lại trải nghiệm người dùng tốt nhất nhờ tính năng cuộn quá mức kéo giãn, hãy làm như sau sau:
- Khi ảnh động kéo giãn có hiệu lực khi người dùng chạm vào hãy đăng ký nội dung chạm là "catch". Người dùng dừng ảnh động rồi bắt đầu điều chỉnh kéo giãn trở lại.
- Khi người dùng di chuyển ngón tay theo hướng ngược lại của động tác kéo giãn, hãy thả lỏng thao tác đó cho đến khi hoàn toàn biến mất, sau đó bắt đầu cuộn.
- Khi người dùng hất trong một khoảng thời gian, hãy hất
EdgeEffect
để tăng cường hiệu ứng co giãn.
Xem ảnh động
Khi người dùng bắt gặp một ảnh động kéo giãn đang hoạt động,
EdgeEffect.getDistance()
trả về 0
. Điều kiện này
cho biết rằng phải kéo giãn bằng chuyển động chạm. Trong hầu hết
vùng chứa, sẽ phát hiện thấy sản phẩm bắt được trong onInterceptTouchEvent()
, như
được hiển thị trong đoạn mã sau:
Kotlin
override fun onInterceptTouchEvent(ev: MotionEvent): Boolean { ... when (action and MotionEvent.ACTION_MASK) { MotionEvent.ACTION_DOWN -> ... isBeingDragged = EdgeEffectCompat.getDistance(edgeEffectBottom) > 0f || EdgeEffectCompat.getDistance(edgeEffectTop) > 0f ... } return isBeingDragged }
Java
@Override public boolean onInterceptTouchEvent(MotionEvent ev) { ... switch (action & MotionEvent.ACTION_MASK) { case MotionEvent.ACTION_DOWN: ... isBeingDragged = EdgeEffectCompat.getDistance(edgeEffectBottom) > 0 || EdgeEffectCompat.getDistance(edgeEffectTop) > 0; ... } }
Trong ví dụ trước, onInterceptTouchEvent()
trả về
true
khi mIsBeingDragged
là true
, vì vậy
chỉ cần xem sự kiện trước khi trẻ có cơ hội
sử dụng nó.
Giải phóng hiệu ứng cuộn quá mức
Bạn cần giải phóng hiệu ứng kéo giãn trước khi cuộn để ngăn chặn khoảng thời gian được áp dụng cho nội dung cuộn. Mã sau đây mẫu sẽ áp dụng phương pháp hay nhất sau:
Kotlin
override fun onTouchEvent(ev: MotionEvent): Boolean { val activePointerIndex = ev.actionIndex when (ev.getActionMasked()) { MotionEvent.ACTION_MOVE -> val x = ev.getX(activePointerIndex) val y = ev.getY(activePointerIndex) var deltaY = y - lastMotionY val pullDistance = deltaY / height val displacement = x / width if (deltaY < 0f && EdgeEffectCompat.getDistance(edgeEffectTop) > 0f) { deltaY -= height * EdgeEffectCompat.onPullDistance(edgeEffectTop, pullDistance, displacement); } if (deltaY > 0f && EdgeEffectCompat.getDistance(edgeEffectBottom) > 0f) { deltaY += height * EdgeEffectCompat.onPullDistance(edgeEffectBottom, -pullDistance, 1 - displacement); } ... }
Java
@Override public boolean onTouchEvent(MotionEvent ev) { final int actionMasked = ev.getActionMasked(); switch (actionMasked) { case MotionEvent.ACTION_MOVE: final float x = ev.getX(activePointerIndex); final float y = ev.getY(activePointerIndex); float deltaY = y - lastMotionY; float pullDistance = deltaY / getHeight(); float displacement = x / getWidth(); if (deltaY < 0 && EdgeEffectCompat.getDistance(edgeEffectTop) > 0) { deltaY -= getHeight() * EdgeEffectCompat.onPullDistance(edgeEffectTop, pullDistance, displacement); } if (deltaY > 0 && EdgeEffectCompat.getDistance(edgeEffectBottom) > 0) { deltaY += getHeight() * EdgeEffectCompat.onPullDistance(edgeEffectBottom, -pullDistance, 1 - displacement); } ...
Khi người dùng đang kéo, hãy sử dụng khoảng cách kéo EdgeEffect
trước khi bạn chuyển sự kiện chạm vào một vùng chứa cuộn lồng nhau hoặc kéo
cuộn. Trong mã mẫu trước đó, getDistance()
trả về một
giá trị dương khi hiệu ứng cạnh đang hiển thị và có thể được huỷ bỏ bằng
chuyển động. Khi sự kiện chạm giải phóng khoảng thời gian, lần đầu tiên nó được sử dụng
EdgeEffect
để phát hành hoàn toàn trước các hiệu ứng khác,
chẳng hạn như cuộn lồng nhau, được hiển thị. Bạn có thể dùng getDistance()
để tìm hiểu khoảng cách kéo cần thiết để giải phóng hiệu ứng hiện tại.
Không giống như onPull()
, onPullDistance()
trả về giá trị
đã tiêu thụ của delta được truyền. Kể từ Android 12, nếu
onPull()
hoặc onPullDistance()
được chuyển sang giá trị âm
deltaDistance
giá trị khi getDistance()
là
0
, hiệu ứng kéo giãn không thay đổi. Trên Android 11
và sớm hơn, onPull()
cho phép các giá trị âm cho tổng khoảng cách
hiện hiệu ứng toả sáng.
Chọn không sử dụng tính năng cuộn quá mức
Bạn có thể chọn không sử dụng tính năng cuộn quá mức trong tệp bố cục hoặc theo phương thức lập trình.
Để chọn không sử dụng trong tệp bố cục, hãy đặt android:overScrollMode
thành
như trong ví dụ sau:
<MyCustomView android:overScrollMode="never"> ... </MyCustomView>
Để chọn không sử dụng theo phương thức lập trình, hãy sử dụng mã như sau:
Kotlin
customView.overScrollMode = View.OVER_SCROLL_NEVER
Java
customView.setOverScrollMode(View.OVER_SCROLL_NEVER);
Tài nguyên khác
Hãy tham khảo các tài nguyên liên quan sau:
- Tổng quan về sự kiện đầu vào
- Tổng quan về cảm biến
- Tạo thành phần hiển thị tuỳ chỉnh có tính tương tác