অ্যান্ড্রয়েডে, সাধারণত ScrollView ক্লাস ব্যবহার করে স্ক্রলিং করা হয়। ফ্রেমওয়ার্ক দ্বারা পরিচালিত একটি স্ক্রলযোগ্য ভিউ প্রদানের জন্য, যেকোনো স্ট্যান্ডার্ড লেআউট যা তার কন্টেইনারের সীমানা ছাড়িয়ে যেতে পারে, সেটিকে একটি ScrollView মধ্যে রাখুন। শুধুমাত্র বিশেষ পরিস্থিতিতেই কাস্টম স্ক্রলার প্রয়োগ করা প্রয়োজন। এই ডকুমেন্টটিতে স্ক্রলার ব্যবহার করে টাচ জেসচারের প্রতিক্রিয়ায় কীভাবে একটি স্ক্রলিং ইফেক্ট প্রদর্শন করা যায় তা বর্ণনা করা হয়েছে।
আপনার অ্যাপ টাচ ইভেন্টের প্রতিক্রিয়ায় একটি স্ক্রলিং অ্যানিমেশন তৈরি করার জন্য প্রয়োজনীয় ডেটা সংগ্রহ করতে স্ক্রলার— Scroller বা OverScroller ব্যবহার করতে পারে। এগুলি একই রকম, কিন্তু OverScroller এমন মেথডও রয়েছে যা প্যান বা ফ্লিং জেসচারের পরে ব্যবহারকারীরা যখন কন্টেন্টের প্রান্তে পৌঁছান, তখন তা নির্দেশ করে।
- অ্যান্ড্রয়েড ১২ (এপিআই লেভেল ৩১) থেকে, ড্র্যাগ ইভেন্টে ভিজ্যুয়াল এলিমেন্টগুলো প্রসারিত হয় ও পূর্বাবস্থায় ফিরে আসে এবং ফ্লিং ইভেন্টে সেগুলো ফ্লিং হয়ে পূর্বাবস্থায় ফিরে আসে।
- অ্যান্ড্রয়েড ১১ (এপিআই লেভেল ৩০) এবং এর আগের সংস্করণগুলোতে, কোনো প্রান্তকে ড্র্যাগ বা ফ্লিং জেসচার দিয়ে নিয়ে যাওয়ার পর সীমানাগুলোতে একটি 'গ্লো' ইফেক্ট দেখা যায়।
এই ডকুমেন্টের InteractiveChart স্যাম্পলটি এই ওভারস্ক্রোল ইফেক্টগুলো প্রদর্শন করতে EdgeEffect ক্লাস ব্যবহার করে।
আপনি ঘর্ষণ, বেগ এবং অন্যান্য বৈশিষ্ট্যের মতো প্ল্যাটফর্ম-স্ট্যান্ডার্ড স্ক্রলিং ফিজিক্স ব্যবহার করে সময়ের সাথে সাথে স্ক্রলিং অ্যানিমেট করতে একটি স্ক্রলার ব্যবহার করতে পারেন। স্ক্রলার নিজে থেকে কিছু আঁকে না। স্ক্রলারগুলো সময়ের সাথে সাথে আপনার জন্য স্ক্রল অফসেট ট্র্যাক করে, কিন্তু সেগুলো স্বয়ংক্রিয়ভাবে আপনার ভিউতে সেই অবস্থানগুলো প্রয়োগ করে না। স্ক্রলিং অ্যানিমেশনটিকে মসৃণ দেখানোর জন্য আপনাকে এমন হারে নতুন স্থানাঙ্ক সংগ্রহ ও প্রয়োগ করতে হবে।
স্ক্রোলিং পরিভাষা বুঝুন
অ্যান্ড্রয়েডে স্ক্রোলিং এমন একটি শব্দ যার অর্থ প্রেক্ষাপটের ওপর নির্ভর করে ভিন্ন ভিন্ন হতে পারে।
স্ক্রলিং হলো ভিউপোর্ট—অর্থাৎ, আপনি যে কন্টেন্টের "উইন্ডো"টি দেখছেন—তাকে সরানোর সাধারণ প্রক্রিয়া। যখন স্ক্রলিং x- এবং y- উভয় অক্ষ বরাবর করা হয়, তখন তাকে প্যানিং বলা হয়। এই ডকুমেন্টের InteractiveChart স্যাম্পল অ্যাপটি দুই ধরনের স্ক্রলিং, যথা ড্র্যাগিং এবং ফ্লিং, প্রদর্শন করে:
- ড্র্যাগিং: এটি এমন এক ধরনের স্ক্রলিং যা ব্যবহারকারী টাচস্ক্রিনের উপর দিয়ে আঙুল টেনে নিয়ে গেলে ঘটে। আপনি
GestureDetector.OnGestureListenerএonScroll()ওভাররাইড করে ড্র্যাগিং প্রয়োগ করতে পারেন। ড্র্যাগিং সম্পর্কে আরও তথ্যের জন্য, ড্র্যাগ এবং স্কেল দেখুন। - ফ্লিংগিং হলো এক ধরনের স্ক্রলিং যা ঘটে যখন কোনো ব্যবহারকারী দ্রুত তার আঙুল টেনে তুলে নেয়। ব্যবহারকারী আঙুল তুলে নেওয়ার পর, আপনি সাধারণত ভিউপোর্টটিকে সচল রাখতে চান, কিন্তু ভিউপোর্টটি পুরোপুরি না থামা পর্যন্ত এর গতি কমিয়ে আনতে চান। আপনি
GestureDetector.OnGestureListenerএonFling()ওভাররাইড করে এবং একটি স্ক্রলার অবজেক্ট ব্যবহার করে ফ্লিংগিং প্রয়োগ করতে পারেন। - প্যানিং: একই সাথে x- এবং y- উভয় অক্ষ বরাবর স্ক্রোল করাকে প্যানিং বলে।
সাধারণত ফ্লিং জেসচারের সাথে স্ক্রলার অবজেক্ট ব্যবহার করা হয়, কিন্তু আপনি যেকোনো ক্ষেত্রেই এগুলো ব্যবহার করতে পারেন যেখানে আপনি চান যে কোনো টাচ ইভেন্টের প্রতিক্রিয়ায় UI স্ক্রলিং প্রদর্শন করুক। উদাহরণস্বরূপ, আপনি সরাসরি টাচ ইভেন্টগুলো প্রসেস করতে এবং সেই টাচ ইভেন্টগুলোর প্রতিক্রিয়ায় একটি স্ক্রলিং এফেক্ট বা একটি "স্ন্যাপ-টু-পেজ" অ্যানিমেশন তৈরি করতে onTouchEvent() ওভাররাইড করতে পারেন।
যে উপাদানগুলিতে অন্তর্নির্মিত স্ক্রোলিং বাস্তবায়ন রয়েছে
নিম্নলিখিত অ্যান্ড্রয়েড কম্পোনেন্টগুলিতে স্ক্রলিং এবং ওভারস্ক্রোলিং আচরণের জন্য অন্তর্নির্মিত সমর্থন রয়েছে:
-
GridView -
HorizontalScrollView -
ListView -
NestedScrollView -
RecyclerView -
ScrollView -
ViewPager -
ViewPager2
আপনার অ্যাপে যদি অন্য কোনো কম্পোনেন্টের ভেতরে স্ক্রলিং এবং ওভারস্ক্রোলিং সমর্থন করার প্রয়োজন হয়, তাহলে নিম্নলিখিত ধাপগুলো সম্পন্ন করুন:
- একটি কাস্টম টাচ-ভিত্তিক স্ক্রলিং বাস্তবায়ন তৈরি করুন ।
- অ্যান্ড্রয়েড ১২ এবং তার পরবর্তী সংস্করণ চালিত ডিভাইসগুলোকে সমর্থন করার জন্য, স্ট্রেচ ওভারস্ক্রোল ইফেক্টটি প্রয়োগ করুন ।
একটি কাস্টম টাচ-ভিত্তিক স্ক্রোলিং বাস্তবায়ন তৈরি করুন
আপনার অ্যাপে ব্যবহৃত কোনো কম্পোনেন্টে যদি স্ক্রলিং ও ওভারস্ক্রলিং-এর বিল্ট-ইন সাপোর্ট না থাকে , তবে কীভাবে নিজের স্ক্রলার তৈরি করবেন, তা এই অংশে বর্ণনা করা হয়েছে।
নিম্নলিখিত কোড স্নিপেটটি InteractiveChart স্যাম্পল থেকে নেওয়া হয়েছে। এটি একটি GestureDetector ব্যবহার করে এবং GestureDetector.SimpleOnGestureListener এর onFling() মেথডটিকে ওভাররাইড করে। এটি ফ্লিং জেসচার ট্র্যাক করার জন্য OverScroller ব্যবহার করে। যদি ব্যবহারকারী ফ্লিং জেসচারটি সম্পাদন করার পর কন্টেন্টের প্রান্তে পৌঁছান, তাহলে কন্টেইনারটি নির্দেশ করে যে ব্যবহারকারী কখন কন্টেন্টের শেষে পৌঁছেছেন। এই নির্দেশটি ডিভাইসে ব্যবহৃত অ্যান্ড্রয়েডের সংস্করণের উপর নির্ভর করে।
- অ্যান্ড্রয়েড ১২ এবং এর পরবর্তী সংস্করণগুলোতে, দৃশ্যমান উপাদানগুলো প্রসারিত হয় এবং পূর্বাবস্থায় ফিরে আসে।
- অ্যান্ড্রয়েড ১১ এবং এর পূর্ববর্তী সংস্করণগুলোতে ভিজ্যুয়াল এলিমেন্টগুলোতে একটি গ্লো ইফেক্ট দেখা যায়।
নিচের কোড স্নিপেটের প্রথম অংশে onFling() এর বাস্তবায়ন দেখানো হয়েছে:
কোটলিন
// 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) }
জাভা
// 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() কল করে, তখন এটি x এবং y এর মান আপডেট করার জন্য computeScroll() কে ট্রিগার করে। এটি সাধারণত তখন করা হয় যখন কোনো ভিউ চাইল্ড একটি স্ক্রলার অবজেক্ট ব্যবহার করে স্ক্রল অ্যানিমেট করে, যেমনটি পূর্ববর্তী উদাহরণে দেখানো হয়েছে।
বেশিরভাগ ভিউ স্ক্রলার অবজেক্টের x এবং y অবস্থান সরাসরি scrollTo() ফাংশনে পাস করে। computeScroll() ফাংশনের নিম্নলিখিত ইমপ্লিমেন্টেশনটি একটি ভিন্ন পদ্ধতি অবলম্বন করে: এটি x এবং y- এর বর্তমান অবস্থান পাওয়ার জন্য computeScrollOffset() কল করে। যখন একটি ওভারস্ক্রোল "গ্লো" এজ এফেক্ট দেখানোর শর্তগুলো পূরণ হয়—অর্থাৎ, ডিসপ্লে জুম ইন করা থাকে, x বা y সীমার বাইরে থাকে, এবং অ্যাপটি আগে থেকেই কোনো ওভারস্ক্রোল না দেখায়—তখন কোডটি ওভারস্ক্রোল গ্লো এফেক্টটি সেট আপ করে এবং ভিউটিতে একটি ইনভ্যালিডেট ট্রিগার করার জন্য postInvalidateOnAnimation() ফাংশনকে কল করে।
কোটলিন
// 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 } ... } }
জাভা
// 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; } ... }
কোডের যে অংশটি প্রকৃত জুম সম্পাদন করে, তা এখানে দেওয়া হলো:
কোটলিন
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) }
জাভা
// 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 এর বর্তমান আকার। যদি চার্টটি উভয় দিকে ২০০% জুম করা হয়, তবে ফেরত আসা আকারটি আনুভূমিক এবং উল্লম্বভাবে দ্বিগুণ বড় হবে।
কোটলিন
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() ) }
জাভা
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 ক্লাসের সোর্স কোড দেখুন। এটি ফ্লিং-এর প্রতিক্রিয়ায় স্ক্রল করে এবং 'স্ন্যাপ-টু-পেজ' অ্যানিমেশনটি বাস্তবায়নের জন্য স্ক্রলিং ব্যবহার করে।
স্ট্রেচ ওভারস্ক্রোল এফেক্ট প্রয়োগ করুন
অ্যান্ড্রয়েড ১২ থেকে, স্ট্রেচ ওভারস্ক্রোল ইফেক্ট বাস্তবায়নের জন্য EdgeEffect নিম্নলিখিত API-গুলো যুক্ত করেছে:
-
getDistance() -
onPullDistance()
স্ট্রেচ ওভারস্ক্রলের মাধ্যমে সর্বোত্তম ব্যবহারকারীর অভিজ্ঞতা প্রদানের জন্য, নিম্নলিখিতগুলি করুন:
- যখন স্ট্রেচ অ্যানিমেশনটি চালু থাকা অবস্থায় ব্যবহারকারী কন্টেন্ট স্পর্শ করেন, তখন সেই স্পর্শটিকে একটি "ক্যাচ" হিসেবে রেজিস্টার করুন। এতে ব্যবহারকারী অ্যানিমেশনটি থামিয়ে দেন এবং পুনরায় স্ট্রেচটি নাড়াচাড়া করা শুরু করেন।
- যখন ব্যবহারকারী প্রসারিত হওয়ার বিপরীত দিকে আঙুল সরাবেন, তখন প্রসারিত অবস্থাটি পুরোপুরি চলে না যাওয়া পর্যন্ত ছেড়ে দিন এবং তারপর স্ক্রোল করা শুরু করুন।
- ব্যবহারকারী যখন স্ট্রেচ করার সময় ফ্লিং করে, তখন স্ট্রেচ এফেক্ট আরও বাড়ানোর জন্য
EdgeEffectফ্লিং করুন।
অ্যানিমেশনটি ধরুন
যখন কোনো ব্যবহারকারী একটি সক্রিয় স্ট্রেচ অ্যানিমেশন ক্যাচ করে, তখন EdgeEffect.getDistance() 0 রিটার্ন করে। এই অবস্থাটি নির্দেশ করে যে স্ট্রেচটি অবশ্যই টাচ মোশনের মাধ্যমে চালিত হতে হবে। বেশিরভাগ কন্টেইনারে, এই ক্যাচটি onInterceptTouchEvent() -এ শনাক্ত করা হয়, যেমনটি নিম্নলিখিত কোড স্নিপেটে দেখানো হয়েছে:
কোটলিন
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 }
জাভা
@Override public boolean onInterceptTouchEvent(MotionEvent ev) { ... switch (action & MotionEvent.ACTION_MASK) { case MotionEvent.ACTION_DOWN: ... isBeingDragged = EdgeEffectCompat.getDistance(edgeEffectBottom) > 0 || EdgeEffectCompat.getDistance(edgeEffectTop) > 0; ... } }
পূর্ববর্তী উদাহরণে, যখন mIsBeingDragged true হয়, তখন onInterceptTouchEvent() true রিটার্ন করে, তাই চাইল্ড অ্যাক্টিভিটি ইভেন্টটি গ্রহণ করার সুযোগ পাওয়ার আগেই তা গ্রহণ করা যথেষ্ট।
ওভারস্ক্রোল প্রভাবটি ছেড়ে দিন
স্ক্রল করার আগে স্ট্রেচ এফেক্টটি রিলিজ করা গুরুত্বপূর্ণ, যাতে স্ক্রলিং কন্টেন্টের উপর স্ট্রেচটি প্রয়োগ না হয়। নিম্নলিখিত কোড স্যাম্পলটি এই সেরা অনুশীলনটি প্রয়োগ করে:
কোটলিন
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); } ... }
জাভা
@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() প্রদত্ত ডেল্টার ব্যবহৃত পরিমাণ ফেরত দেয়। অ্যান্ড্রয়েড ১২ থেকে, যদি getDistance() মান 0 হয় এবং onPull() বা onPullDistance() এ নেতিবাচক deltaDistance মান দেওয়া হয়, তাহলে স্ট্রেচ এফেক্ট পরিবর্তিত হয় না। অ্যান্ড্রয়েড ১১ এবং তার আগের সংস্করণগুলিতে, onPull() মোট দূরত্বের নেতিবাচক মানকেও গ্লো এফেক্ট দেখানোর সুযোগ দিত।
ওভারস্ক্রোল থেকে অপ্ট আউট করুন
আপনি আপনার লেআউট ফাইলে অথবা প্রোগ্রাম্যাটিকভাবে ওভারস্ক্রোল বন্ধ করতে পারেন।
আপনার লেআউট ফাইলে এটি বন্ধ করতে, নিচের উদাহরণে দেখানো অনুযায়ী android:overScrollMode সেট করুন:
<MyCustomView android:overScrollMode="never"> ... </MyCustomView>
প্রোগ্রামের মাধ্যমে অপ্ট আউট করতে, নিচের মতো কোড ব্যবহার করুন:
কোটলিন
customView.overScrollMode = View.OVER_SCROLL_NEVER
জাভা
customView.setOverScrollMode(View.OVER_SCROLL_NEVER);
অতিরিক্ত সম্পদ
নিম্নলিখিত সম্পর্কিত উৎসসমূহ দেখুন:
