ใน Android โดยทั่วไปการเลื่อนทำได้โดยการใช้
ScrollView
ฝังเลย์เอาต์มาตรฐานที่อาจขยายเกินขอบเขตของคอนเทนเนอร์ใน ScrollView
เพื่อให้มีมุมมองที่เลื่อนได้ซึ่งจัดการโดยเฟรมเวิร์ก การติดตั้งแถบเลื่อนที่กำหนดเองนั้นจำเป็นสำหรับ
สถานการณ์ เอกสารนี้อธิบายวิธีแสดงเอฟเฟกต์การเลื่อนในการตอบกลับ
เพื่อแตะสัมผัสโดยใช้แถบเลื่อน
แอปของคุณสามารถใช้ตัวเลื่อน Scroller
หรือ OverScroller
เพื่อรวบรวมข้อมูลที่จําเป็นในการสร้างภาพเคลื่อนไหวแบบเลื่อนเพื่อตอบสนองต่อเหตุการณ์การสัมผัส ซึ่งมีความคล้ายคลึงกัน แต่ OverScroller
ยังรวมเมธอดสำหรับ
แสดงแก่ผู้ใช้เมื่อไปถึงขอบเนื้อหาหลังจากการแพนหรือการขว้าง
ท่าทางสัมผัส
- ตั้งแต่ Android 12 (API ระดับ 31) องค์ประกอบภาพจะยืดออกและเด้งกลับในเหตุการณ์การลาก และพุ่งไปข้างหน้าและเด้งกลับในเหตุการณ์การพุ่ง
- ใน Android 11 (API ระดับ 30) และเวอร์ชันก่อนหน้า ขอบเขตจะแสดงเอฟเฟกต์ "เรืองแสง" หลังจากที่ผู้ใช้ใช้ท่าทางสัมผัสลากหรือปัดไปยังขอบ
ตัวอย่าง InteractiveChart
ในเอกสารนี้ใช้คลาส
EdgeEffect
เพื่อแสดงเอฟเฟกต์การเลื่อนผ่าน
คุณสามารถใช้ Scroller เพื่อแสดงภาพเคลื่อนไหวของการเลื่อนเมื่อเวลาผ่านไปได้โดยใช้ฟิสิกส์การเลื่อนมาตรฐานของแพลตฟอร์ม เช่น แรงเสียดทาน ความเร็ว และคุณสมบัติอื่นๆ ตัวเลื่อนเองไม่ได้วาดอะไรเลย ตัวเลื่อนจะติดตามออฟเซตการเลื่อนให้คุณเมื่อเวลาผ่านไป แต่จะไม่ใช้ตำแหน่งเหล่านั้นกับมุมมองของคุณโดยอัตโนมัติ คุณต้องรับและใช้พิกัดใหม่ในอัตราที่ทำให้ภาพเคลื่อนไหวแบบเลื่อนดูดูราบรื่น
ทําความเข้าใจคําศัพท์เกี่ยวกับการเลื่อน
การเลื่อนคือคำที่อาจมีความหมายต่างๆ ใน Android ทั้งนี้ขึ้นอยู่กับ บริบท
การเลื่อนเป็นกระบวนการทั่วไปในการย้ายวิวพอร์ต ซึ่งก็คือ "หน้าต่าง" ของเนื้อหาที่คุณกําลังดู เมื่อการเลื่อนอยู่ในทั้ง
แกน x และ y เรียกว่าการแพน
แอปตัวอย่าง InteractiveChart
รายการในเอกสารนี้แสดง
การเลื่อน การลาก และขว้างประเภทต่างๆ ได้แก่
- การลาก: เป็นการเลื่อนประเภทหนึ่งที่จะเกิดขึ้นเมื่อผู้ใช้ลากนิ้วบนหน้าจอสัมผัส คุณใช้การลากได้โดย overriding
onScroll()
ในGestureDetector.OnGestureListener
ดูข้อมูลเพิ่มเติมเกี่ยวกับการลากได้ที่หัวข้อลากและปรับขนาด - การสะบัด: นี่คือประเภทของการเลื่อนที่เกิดขึ้นเมื่อผู้ใช้
ลากและยกนิ้วอย่างรวดเร็ว หลังจากที่ผู้ใช้ยกนิ้วขึ้น
โดยทั่วไปต้องการเลื่อนวิวพอร์ตต่อไป แต่ลดความเร็วลงจนกว่า
วิวพอร์ตหยุดเคลื่อนที่ คุณใช้การสะบัดได้โดยการลบล้าง
onFling()
ในGestureDetector.OnGestureListener
และใช้แถบเลื่อน ออบเจ็กต์ - การเลื่อน: การเลื่อนพร้อมกันทั้งแกน x และ y เรียกว่าการเลื่อน
เป็นเรื่องปกติที่จะใช้ออบเจ็กต์ตัวเลื่อนร่วมกับท่าทางสัมผัสการสะบัด แต่
คุณสามารถใช้ส่วนขยายประเภทนี้ได้ในทุกบริบทที่คุณต้องการให้ UI แสดงการเลื่อนดู
การตอบสนองต่อกิจกรรมการสัมผัส ตัวอย่างเช่น คุณสามารถลบล้าง
onTouchEvent()
เพื่อประมวลผลเหตุการณ์การแตะโดยตรงและสร้างเอฟเฟกต์การเลื่อนหรือภาพเคลื่อนไหว "พอดีกับหน้าเว็บ" เพื่อตอบสนองต่อเหตุการณ์การแตะเหล่านั้น
คอมโพเนนต์ที่ใช้งานการเลื่อนในตัว
คอมโพเนนต์ของ Android ต่อไปนี้มีการสนับสนุนในตัวสำหรับการเลื่อนและ ลักษณะการทำงานของการเลื่อนเกิน
GridView
HorizontalScrollView
ListView
NestedScrollView
RecyclerView
ScrollView
ViewPager
ViewPager2
หากแอปของคุณต้องรองรับการเลื่อนและการเลื่อนมากเกินไปภายใน ทำตามขั้นตอนต่อไปนี้
- สร้างการเลื่อนแบบสัมผัสที่กำหนดเอง การใช้งาน
- หากต้องการรองรับอุปกรณ์ที่ใช้ Android 12 ขึ้นไป ให้ใช้เอฟเฟกต์การเลื่อนแบบยืด
สร้างการใช้งานการเลื่อนแบบแตะที่กำหนดเอง
ส่วนนี้จะอธิบายวิธีสร้างตัวเลื่อนของคุณเองในกรณีที่แอปใช้คอมโพเนนต์ที่ไม่มีการสนับสนุนในตัวสำหรับการเลื่อนและการเลื่อนผ่าน
ข้อมูลโค้ดต่อไปนี้มาจากตัวอย่าง InteractiveChart
โดยใช้ GestureDetector
และลบล้างเมธอด GestureDetector.SimpleOnGestureListener
onFling()
โดยใช้ OverScroller
เพื่อติดตาม
ท่าทางสัมผัสการสะบัด หากผู้ใช้ไปถึงขอบเนื้อหาหลังจากทำการดำเนินการ
ท่าทางสัมผัสการสะบัด คอนเทนเนอร์จะระบุเมื่อผู้ใช้เลื่อนไปจนสุด
เนื้อหา ตัวบ่งชี้จะขึ้นอยู่กับเวอร์ชันของ Android ที่อุปกรณ์
วิ่ง:
- ใน Android 12 ขึ้นไป องค์ประกอบภาพจะยืดออกและดีดตัวกลับ
- ใน Android 11 และเวอร์ชันก่อนหน้า องค์ประกอบภาพจะแสดงเอฟเฟกต์เรืองแสง
ส่วนแรกของข้อมูลโค้ดต่อไปนี้แสดงการติดตั้งใช้งาน 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); }
เมื่อ onFling()
เรียกใช้ postInvalidateOnAnimation()
ระบบจะทริกเกอร์ computeScroll()
ให้อัปเดตค่าสำหรับ x และ y ซึ่งโดยปกติจะดำเนินการเมื่อ
มุมมองย่อยกำลังทำให้การเลื่อนเคลื่อนไหวโดยใช้ออบเจ็กต์ตัวเลื่อน ดังที่แสดง
มุมมองส่วนใหญ่จะผ่านตำแหน่ง x และ y ของออบเจ็กต์ตัวเลื่อนโดยตรง
ถึง
scrollTo()
การใช้งาน computeScroll()
ต่อไปนี้ใช้แนวทางที่แตกต่างออกไป โดยจะเรียกใช้ computeScrollOffset()
เพื่อรับตําแหน่งปัจจุบันของ x และ y เมื่อเป็นไปตามเกณฑ์ในการแสดงเอฟเฟกต์ขอบ "เรืองแสง" ของการเลื่อนเกินขอบ ซึ่งก็คือ การแสดงผลมีการซูมเข้า, x หรือ y อยู่นอกขอบเขต และแอปไม่ได้แสดงการเลื่อนเกินขอบอยู่แล้ว โค้ดจะตั้งค่าเอฟเฟกต์เรืองแสงของการเลื่อนเกินขอบและเรียกใช้ postInvalidateOnAnimation()
เพื่อทริกเกอร์การทำให้ข้อมูลในมุมมองเป็นโมฆะ
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; } ... }
ส่วนโค้ดที่ทำการซูมจริงมีดังนี้
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); }
นี่คือเมธอด computeScrollSurfaceSize()
ที่เรียกใช้ในข้อมูลโค้ดก่อนหน้า โดยจะคํานวณขนาดพื้นผิวที่เลื่อนได้ในปัจจุบันเป็นพิกเซล เช่น หากมองเห็นพื้นที่แผนภูมิทั้งหมด แสดงว่านี่คือขนาดปัจจุบันของ mContentRect
หากแผนภูมิซูมเข้า 200% ทั้ง 2 ด้าน
ของคุณ ขนาดการแสดงผลจะมีขนาดใหญ่เป็น 2 เท่าในแนวนอนและแนวตั้ง
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())); }
ดูตัวอย่างการใช้งาน Scroller อื่นๆ ได้ที่ซอร์สโค้ดของคลาส ViewPager
โดยจะเลื่อนตามการแตะแล้วลากและใช้การเลื่อนเพื่อแสดงภาพเคลื่อนไหว "พอดีกับหน้า"
ใช้เอฟเฟกต์การเลื่อนไปจนสุด
ตั้งแต่ Android 12 เป็นต้นไป EdgeEffect
ได้เพิ่ม API ต่อไปนี้เพื่อใช้เอฟเฟกต์การเลื่อนแบบยืด
getDistance()
onPullDistance()
หากต้องการให้ผู้ใช้ได้รับประสบการณ์ที่ดีที่สุดด้วยการเลื่อนแบบยืดได้ ให้ทำดังนี้ ดังต่อไปนี้:
- เมื่อภาพเคลื่อนไหวแบบยืดมีผลเมื่อผู้ใช้แตะ เนื้อหา ให้ลงทะเบียนการสัมผัสเป็น "catch" ผู้ใช้หยุดภาพเคลื่อนไหวและ ก็เริ่มชักนำการยืดออกอีกครั้ง
- เมื่อผู้ใช้เลื่อนนิ้วไปในทิศทางตรงข้ามกับการยืด ให้ปล่อยการยืดจนกว่าจะหายไปทั้งหมด แล้วเริ่มเลื่อน
- เมื่อผู้ใช้ยืดเส้นยืดสาย ให้สะบัด
EdgeEffect
เพื่อเพิ่มเอฟเฟกต์การยืด
จับภาพเคลื่อนไหว
เมื่อผู้ใช้จับภาพภาพเคลื่อนไหวการยืดแบบทำงานอยู่
EdgeEffect.getDistance()
จะแสดงผลเป็น 0
เงื่อนไขนี้บ่งชี้ว่าต้องควบคุมการยืดด้วยการเคลื่อนไหวการสัมผัส ในคอนเทนเนอร์ส่วนใหญ่ ระบบจะตรวจพบการจับใน onInterceptTouchEvent()
ดังที่แสดงในข้อมูลโค้ดต่อไปนี้
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; ... } }
ในตัวอย่างก่อนหน้านี้ onInterceptTouchEvent()
จะแสดงผลเป็น true
เมื่อ mIsBeingDragged
เป็น true
ดังนั้นจึงเพียงพอที่จะใช้เหตุการณ์ก่อนที่บุตรหลานจะมีโอกาสใช้
แสดงเอฟเฟกต์การเลื่อนไปจนสุด
คุณต้องยกเลิกเอฟเฟกต์การยืดก่อนเลื่อนเพื่อป้องกันไม่ให้เอฟเฟกต์การยืดมีผลกับเนื้อหาที่เลื่อน ตัวอย่างโค้ดต่อไปนี้ใช้แนวทางปฏิบัติแนะนำนี้
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); } ...
เมื่อผู้ใช้ลาก ให้ใช้การดึงระยะ EdgeEffect
ก่อนที่จะส่งเหตุการณ์การแตะไปยังคอนเทนเนอร์แบบเลื่อนที่ซ้อนกันหรือลาก
เลื่อน ในตัวอย่างโค้ดก่อนหน้านี้ getDistance()
จะแสดงผล
ค่าบวกเมื่อมีการแสดงเอฟเฟกต์ขอบและปล่อยได้ด้วย
ภาพเคลื่อนไหว เมื่อเหตุการณ์การแตะปล่อยการยืด จะมีการใช้ครั้งแรกโดย
EdgeEffect
เพื่อให้ระบบปล่อยวิดีโอออกมาโดยสมบูรณ์ก่อนเอฟเฟกต์อื่นๆ
เช่น การเลื่อนที่ซ้อนกันจะปรากฏขึ้น คุณใช้ getDistance()
ได้
เพื่อเรียนรู้ว่าต้องมีระยะการดึงเท่าใดจึงจะปล่อยเอฟเฟกต์ปัจจุบันได้
ซึ่งแตกต่างจาก onPull()
ตรงที่ onPullDistance()
จะแสดงผล
ปริมาณเดลต้าที่ส่งผ่านปริมาณที่ใช้ไป เริ่มตั้งแต่ Android 12 หาก
onPull()
หรือ onPullDistance()
ส่งผ่านค่าลบ
deltaDistance
ค่าเมื่อ getDistance()
คือ
0
เอฟเฟกต์การยืดขยายจะไม่เปลี่ยนแปลง ใน Android 11
และก่อนหน้านี้ onPull()
จะอนุญาตให้ใช้ค่าลบสำหรับระยะทางรวม
แสดงเอฟเฟกต์แสง
เลือกไม่ใช้การเลื่อนผ่าน
คุณสามารถเลือกไม่ใช้การเลื่อนเกินในไฟล์เลย์เอาต์หรือใช้แบบเป็นโปรแกรมก็ได้
หากต้องการเลือกไม่ใช้ในไฟล์เลย์เอาต์ ให้ตั้งค่า android:overScrollMode
ตามที่แสดงในตัวอย่างต่อไปนี้
<MyCustomView android:overScrollMode="never"> ... </MyCustomView>
หากต้องการเลือกไม่ใช้แบบเป็นโปรแกรม ให้ใช้โค้ดต่อไปนี้
Kotlin
customView.overScrollMode = View.OVER_SCROLL_NEVER
Java
customView.setOverScrollMode(View.OVER_SCROLL_NEVER);
แหล่งข้อมูลเพิ่มเติม
โปรดดูแหล่งข้อมูลที่เกี่ยวข้องต่อไปนี้