في نظام التشغيل Android، يتم عادةً تنفيذ عملية التمرير باستخدام الفئة
ScrollView. يمكنك تضمين أي تنسيق عادي قد يمتد إلى ما بعد حدود الحاوية الخاصة به في ScrollView لتوفير طريقة عرض قابلة للتمرير يديرها إطار العمل. لا يكون تنفيذ أداة تمرير مخصّصة ضروريًا إلا في حالات خاصة. يوضّح هذا المستند كيفية عرض تأثير التمرير استجابةً لإيماءات اللمس باستخدام أدوات التمرير.
يمكن لتطبيقك استخدام أدوات التمرير، مثل Scroller أو OverScroller، لجمع البيانات اللازمة لإنشاء رسم متحرك للتمرير استجابةً لحدث لمس. وهي تتشابه مع OverScroller، ولكنّها تتضمّن أيضًا طرقًا لتوضيح للمستخدمين أنّهم وصلوا إلى حدود المحتوى بعد تنفيذ إيماءة تحريك أو تمرير سريع.
- بدءًا من نظام التشغيل Android 12 (المستوى 31 من واجهة برمجة التطبيقات)، تتمدّد العناصر المرئية ثم تعود إلى وضعها السابق عند حدوث حدث سحب، كما تتمدّد ثم تعود إلى وضعها السابق عند حدوث حدث رمي.
- في نظام التشغيل Android 11 (المستوى 30 من واجهة برمجة التطبيقات) والإصدارات الأقدم، تعرض الحدود تأثير "التوهّج" بعد تنفيذ إيماءة سحب أو تمرير سريع إلى الحافة.
يستخدم نموذج InteractiveChart في هذا المستند الفئة EdgeEffect لعرض تأثيرات تجاوز حد التمرير هذه.
يمكنك استخدام أداة تمرير لإنشاء رسوم متحركة للتمرير بمرور الوقت، وذلك باستخدام آليات التمرير المتوافقة مع النظام الأساسي، مثل الاحتكاك والسرعة وغيرها من الخصائص. لا يرسم شريط التمرير نفسه أي شيء. تتتبّع أدوات التمرير السريع إزاحات التمرير السريع بمرور الوقت، ولكنّها لا تطبّق هذه المواضع تلقائيًا على طريقة العرض. يجب الحصول على إحداثيات جديدة وتطبيقها بمعدّل يجعل حركة التمرير تبدو سلسة.
فهم مصطلحات التمرير
التمرير هو كلمة يمكن أن تشير إلى معانٍ مختلفة في Android، وذلك حسب السياق.
التمرير هو العملية العامة لتحريك نافذة العرض، أي "نافذة" المحتوى الذي تشاهده. عندما يكون التمرير في المحورين x وy، يُطلق عليه اسم تحريك. يوضّح
InteractiveChart التطبيق النموذجي في هذا المستند نوعَين مختلفَين من التمرير والسحب والتحريك السريع:
- السحب: هو نوع التمرير الذي يحدث عندما يسحب المستخدم إصبعه على شاشة اللمس. يمكنك تنفيذ عملية السحب من خلال إلغاء
onScroll()فيGestureDetector.OnGestureListener. لمزيد من المعلومات حول السحب، يُرجى الاطّلاع على السحب وتغيير الحجم. - التحريك السريع: هو نوع التمرير الذي يحدث عندما يسحب المستخدم إصبعه ويرفعه بسرعة. بعد أن يرفع المستخدم إصبعه، عليك عادةً مواصلة تحريك إطار العرض، ولكن مع خفض السرعة تدريجيًا إلى أن يتوقف إطار العرض عن الحركة. يمكنك تنفيذ عملية التحريك السريع من خلال إلغاء
onFling()فيGestureDetector.OnGestureListenerواستخدام عنصر للتمرير. - التحريك: يُطلق اسم التحريك على التمرير في الوقت نفسه على المحورَين س وص.
ويشيع استخدام عناصر التمرير مع إيماءة النقر السريع، ولكن يمكنك استخدامها في أي سياق تريد فيه أن تعرض واجهة المستخدم التمرير استجابةً لحدث لمس. على سبيل المثال، يمكنك إلغاء
onTouchEvent()
لمعالجة أحداث اللمس مباشرةً وإنشاء تأثير تنقّل من قسم إلى آخر أو
رسوم متحركة "للانتقال إلى الصفحة" استجابةً لأحداث اللمس هذه.
المكوّنات التي تتضمّن عمليات تنفيذ التمرير المضمّنة
تحتوي مكوّنات Android التالية على دعم مدمج لسلوك التمرير والتمرير السريع:
GridViewHorizontalScrollViewListViewNestedScrollViewRecyclerViewScrollViewViewPagerViewPager2
إذا كان تطبيقك بحاجة إلى إتاحة التمرير والتمرير السريع داخل مكوّن مختلف، أكمِل الخطوات التالية:
- إنشاء عملية تنفيذ مخصّصة للتمرير المستند إلى اللمس
- لإتاحة التوافق مع الأجهزة التي تعمل بالإصدار 12 من نظام التشغيل Android والإصدارات الأحدث، عليك تنفيذ تأثير تجاوز حد التمرير الممتد.
إنشاء عملية تنفيذ مخصّصة للتمرير المستند إلى اللمس
يوضّح هذا القسم كيفية إنشاء شريط تمرير خاص بك إذا كان تطبيقك يستخدم مكونًا لا يتضمّن إمكانية مدمجة للتمرير والتمرير الزائد.
يأتي المقتطف التالي من
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 في كلا الاتجاهين، سيكون الحجم المعروض أكبر بمقدار الضعف أفقيًا وعموديًا.
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())); }
للاطّلاع على مثال آخر لاستخدام أداة التمرير، راجِع الرمز المصدر للفئة ViewPager. ويتم الانتقال سريعًا للأعلى أو للأسفل استجابةً للحركات السريعة، ويتم استخدام التمرير لتنفيذ الصورة المتحركة "الانتقال السريع إلى الصفحة".
تنفيذ تأثير تجاوز حد التمرير
بدءًا من نظام التشغيل Android 12، يضيف EdgeEffect واجهات برمجة التطبيقات التالية لتنفيذ تأثير تجاوز حد التمرير الممتد:
getDistance()onPullDistance()
لتقديم أفضل تجربة للمستخدمين عند استخدام ميزة "تجاوز حد التمرير الممتد"، اتّبِع الخطوات التالية:
- عندما تكون الصورة المتحركة الممتدة سارية عندما يلمس المستخدم المحتوى، سجِّل اللمسة على أنّها "التقاط". يتوقّف المستخدم عن تحريك الصورة المتحركة ويبدأ في تعديلها مرة أخرى.
- عندما يحرك المستخدم إصبعه في الاتجاه المعاكس للتمديد، يجب إيقاف التمديد إلى أن يختفي تمامًا، ثم البدء في الانتقال.
- عندما ينفّذ المستخدم حركة التمرير السريع ثم رفعه بسرعة أثناء التمديد، مرّر سريعًا ثم ارفع إصبعك بسرعة على
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، إذا تم تمرير قيم سالبة deltaDistance إلى onPull() أو onPullDistance() عندما تكون قيمة getDistance() هي 0، لن يتغيّر تأثير التمديد. في نظام التشغيل Android 11 والإصدارات الأقدم، تتيح السمة onPull() عرض تأثيرات التوهّج للقيم السالبة للمسافة الإجمالية.
إيقاف تجاوز حد التمرير
يمكنك إيقاف تجاوز حد التمرير في ملف التصميم أو بشكل آلي.
لإيقاف هذه الميزة في ملف التنسيق، اضبط android:overScrollMode على النحو الموضّح في المثال التالي:
<MyCustomView android:overScrollMode="never"> ... </MyCustomView>
لإيقاف عرض AMP آليًا، استخدِم رمزًا مشابهًا لما يلي:
Kotlin
customView.overScrollMode = View.OVER_SCROLL_NEVER
Java
customView.setOverScrollMode(View.OVER_SCROLL_NEVER);
مراجع إضافية
يُرجى الاطّلاع على المراجع ذات الصلة التالية: