Fast scrolling issue with ListAdapter and SectionIndexer

I have a list of events that are separated by month and year (Jun 2010, July 2010, etc.). I turned on fast scrolling because the list is really long. I also implemented SectionIndexer so that people can see what month and year they are viewing when scrolling through the list of events at speed.

I have no problem with the implementation of how the information is shown. Scrolling quickly with SectionIndexer apparently can really support a single letter shortcut. If the list was in alphabetical order, that would be ideal, however I want it to display a bit more text.

If you look at the screenshot, you will see the problem that I am facing.

Screenshot of the described problem http://blog.matto1990.com/wp-content/uploads/2010/07/dispay_problem.png

What I want to know: is it possible to change the way text is displayed in the center of the screen. Can I change it in any way so that it looks right (with a background covering the entire text).

Thanks in advance. If you need any clarification or code, just ask.

+25
android listview
Jul 11 '10 at 23:44
source share
2 answers

EDIT: Full code example for this solution here .

I had the same problem - I needed to display the full text in the overlay rectangle, and not just one character. I was able to solve this using the following code as an example: http://code.google.com/p/apps-for-android/source/browse/trunk/RingsExtended/src/com/example/android/rings_extended/FastScrollView. java

The author said that this was copied from the Contacts application, which apparently uses its own implementation, not just setting fastScrollEnabled="true" on the ListView . I changed it a bit so you can adjust the width of the overlap rectangle, the height of the overlay rectangle, the size of the overlay text, and the width of the scroll thumb.

For the record, the final result is as follows: http://nolanwlawson.files.wordpress.com/2011/03/pokedroid_1.png

All you have to do is add these values ​​to your res / values ​​/attrs.xml:

 <declare-styleable name="CustomFastScrollView"> <attr name="overlayWidth" format="dimension"/> <attr name="overlayHeight" format="dimension"/> <attr name="overlayTextSize" format="dimension"/> <attr name="overlayScrollThumbWidth" format="dimension"/> </declare-styleable> 

And then use this CustomFastScrollView, not the one specified in the link:

 public class CustomFastScrollView extends FrameLayout implements OnScrollListener, OnHierarchyChangeListener { private Drawable mCurrentThumb; private Drawable mOverlayDrawable; private int mThumbH; private int mThumbW; private int mThumbY; private RectF mOverlayPos; // custom values I defined private int mOverlayWidth; private int mOverlayHeight; private float mOverlayTextSize; private int mOverlayScrollThumbWidth; private boolean mDragging; private ListView mList; private boolean mScrollCompleted; private boolean mThumbVisible; private int mVisibleItem; private Paint mPaint; private int mListOffset; private Object [] mSections; private String mSectionText; private boolean mDrawOverlay; private ScrollFade mScrollFade; private Handler mHandler = new Handler(); private BaseAdapter mListAdapter; private boolean mChangedBounds; public static interface SectionIndexer { Object[] getSections(); int getPositionForSection(int section); int getSectionForPosition(int position); } public CustomFastScrollView(Context context) { super(context); init(context, null); } public CustomFastScrollView(Context context, AttributeSet attrs) { super(context, attrs); init(context, attrs); } public CustomFastScrollView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); init(context, attrs); } private void useThumbDrawable(Drawable drawable) { mCurrentThumb = drawable; mThumbW = mOverlayScrollThumbWidth;//mCurrentThumb.getIntrinsicWidth(); mThumbH = mCurrentThumb.getIntrinsicHeight(); mChangedBounds = true; } private void init(Context context, AttributeSet attrs) { // set all attributes from xml if (attrs != null) { TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.CustomFastScrollView); mOverlayHeight = typedArray.getDimensionPixelSize( R.styleable.CustomFastScrollView_overlayHeight, 0); mOverlayWidth = typedArray.getDimensionPixelSize( R.styleable.CustomFastScrollView_overlayWidth, 0); mOverlayTextSize = typedArray.getDimensionPixelSize( R.styleable.CustomFastScrollView_overlayTextSize, 0); mOverlayScrollThumbWidth = typedArray.getDimensionPixelSize( R.styleable.CustomFastScrollView_overlayScrollThumbWidth, 0); } // Get both the scrollbar states drawables final Resources res = context.getResources(); Drawable thumbDrawable = res.getDrawable(R.drawable.scrollbar_handle_accelerated_anim2); useThumbDrawable(thumbDrawable); mOverlayDrawable = res.getDrawable(android.R.drawable.alert_dark_frame); mScrollCompleted = true; setWillNotDraw(false); // Need to know when the ListView is added setOnHierarchyChangeListener(this); mOverlayPos = new RectF(); mScrollFade = new ScrollFade(); mPaint = new Paint(); mPaint.setAntiAlias(true); mPaint.setTextAlign(Paint.Align.CENTER); mPaint.setTextSize(mOverlayTextSize); mPaint.setColor(0xFFFFFFFF); mPaint.setStyle(Paint.Style.FILL_AND_STROKE); } private void removeThumb() { mThumbVisible = false; // Draw one last time to remove thumb invalidate(); } @Override public void draw(Canvas canvas) { super.draw(canvas); if (!mThumbVisible) { // No need to draw the rest return; } final int y = mThumbY; final int viewWidth = getWidth(); final CustomFastScrollView.ScrollFade scrollFade = mScrollFade; int alpha = -1; if (scrollFade.mStarted) { alpha = scrollFade.getAlpha(); if (alpha < ScrollFade.ALPHA_MAX / 2) { mCurrentThumb.setAlpha(alpha * 2); } int left = viewWidth - (mThumbW * alpha) / ScrollFade.ALPHA_MAX; mCurrentThumb.setBounds(left, 0, viewWidth, mThumbH); mChangedBounds = true; } canvas.translate(0, y); mCurrentThumb.draw(canvas); canvas.translate(0, -y); // If user is dragging the scroll bar, draw the alphabet overlay if (mDragging && mDrawOverlay) { mOverlayDrawable.draw(canvas); final Paint paint = mPaint; float descent = paint.descent(); final RectF rectF = mOverlayPos; canvas.drawText(mSectionText, (int) (rectF.left + rectF.right) / 2, (int) (rectF.bottom + rectF.top) / 2 + descent, paint); } else if (alpha == 0) { scrollFade.mStarted = false; removeThumb(); } else { invalidate(viewWidth - mThumbW, y, viewWidth, y + mThumbH); } } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); if (mCurrentThumb != null) { mCurrentThumb.setBounds(w - mThumbW, 0, w, mThumbH); } final RectF pos = mOverlayPos; pos.left = (w - mOverlayWidth) / 2; pos.right = pos.left + mOverlayWidth; pos.top = h / 10; // 10% from top pos.bottom = pos.top + mOverlayHeight; mOverlayDrawable.setBounds((int) pos.left, (int) pos.top, (int) pos.right, (int) pos.bottom); } public void onScrollStateChanged(AbsListView view, int scrollState) { } public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { if (totalItemCount - visibleItemCount > 0 && !mDragging) { mThumbY = ((getHeight() - mThumbH) * firstVisibleItem) / (totalItemCount - visibleItemCount); if (mChangedBounds) { final int viewWidth = getWidth(); mCurrentThumb.setBounds(viewWidth - mThumbW, 0, viewWidth, mThumbH); mChangedBounds = false; } } mScrollCompleted = true; if (firstVisibleItem == mVisibleItem) { return; } mVisibleItem = firstVisibleItem; if (!mThumbVisible || mScrollFade.mStarted) { mThumbVisible = true; mCurrentThumb.setAlpha(ScrollFade.ALPHA_MAX); } mHandler.removeCallbacks(mScrollFade); mScrollFade.mStarted = false; if (!mDragging) { mHandler.postDelayed(mScrollFade, 1500); } } private void getSections() { Adapter adapter = mList.getAdapter(); if (adapter instanceof HeaderViewListAdapter) { mListOffset = ((HeaderViewListAdapter)adapter).getHeadersCount(); adapter = ((HeaderViewListAdapter)adapter).getWrappedAdapter(); } if (adapter instanceof SectionIndexer) { mListAdapter = (BaseAdapter) adapter; mSections = ((SectionIndexer) mListAdapter).getSections(); } } public void onChildViewAdded(View parent, View child) { if (child instanceof ListView) { mList = (ListView)child; mList.setOnScrollListener(this); getSections(); } } public void onChildViewRemoved(View parent, View child) { if (child == mList) { mList = null; mListAdapter = null; mSections = null; } } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { if (mThumbVisible && ev.getAction() == MotionEvent.ACTION_DOWN) { if (ev.getX() > getWidth() - mThumbW && ev.getY() >= mThumbY && ev.getY() <= mThumbY + mThumbH) { mDragging = true; return true; } } return false; } private void scrollTo(float position) { int count = mList.getCount(); mScrollCompleted = false; final Object[] sections = mSections; int sectionIndex; if (sections != null && sections.length > 1) { final int nSections = sections.length; int section = (int) (position * nSections); if (section >= nSections) { section = nSections - 1; } sectionIndex = section; final SectionIndexer baseAdapter = (SectionIndexer) mListAdapter; int index = baseAdapter.getPositionForSection(section); // Given the expected section and index, the following code will // try to account for missing sections (no names starting with..) // It will compute the scroll space of surrounding empty sections // and interpolate the currently visible letter range across the // available space, so that there is always some list movement while // the user moves the thumb. int nextIndex = count; int prevIndex = index; int prevSection = section; int nextSection = section + 1; // Assume the next section is unique if (section < nSections - 1) { nextIndex = baseAdapter.getPositionForSection(section + 1); } // Find the previous index if we're slicing the previous section if (nextIndex == index) { // Non-existent letter while (section > 0) { section--; prevIndex = baseAdapter.getPositionForSection(section); if (prevIndex != index) { prevSection = section; sectionIndex = section; break; } } } // Find the next index, in case the assumed next index is not // unique. For instance, if there is no P, then request for P // position actually returns Q's. So we need to look ahead to make // sure that there is really a Q at Q position. If not, move // further down... int nextNextSection = nextSection + 1; while (nextNextSection < nSections && baseAdapter.getPositionForSection(nextNextSection) == nextIndex) { nextNextSection++; nextSection++; } // Compute the beginning and ending scroll range percentage of the // currently visible letter. This could be equal to or greater than // (1 / nSections). float fPrev = (float) prevSection / nSections; float fNext = (float) nextSection / nSections; index = prevIndex + (int) ((nextIndex - prevIndex) * (position - fPrev) / (fNext - fPrev)); // Don't overflow if (index > count - 1) index = count - 1; mList.setSelectionFromTop(index + mListOffset, 0); } else { int index = (int) (position * count); mList.setSelectionFromTop(index + mListOffset, 0); sectionIndex = -1; } if (sectionIndex >= 0) { String text = mSectionText = sections[sectionIndex].toString(); mDrawOverlay = (text.length() != 1 || text.charAt(0) != ' ') && sectionIndex < sections.length; } else { mDrawOverlay = false; } } private void cancelFling() { // Cancel the list fling MotionEvent cancelFling = MotionEvent.obtain(0, 0, MotionEvent.ACTION_CANCEL, 0, 0, 0); mList.onTouchEvent(cancelFling); cancelFling.recycle(); } @Override public boolean onTouchEvent(MotionEvent me) { if (me.getAction() == MotionEvent.ACTION_DOWN) { if (me.getX() > getWidth() - mThumbW && me.getY() >= mThumbY && me.getY() <= mThumbY + mThumbH) { mDragging = true; if (mListAdapter == null && mList != null) { getSections(); } cancelFling(); return true; } } else if (me.getAction() == MotionEvent.ACTION_UP) { if (mDragging) { mDragging = false; final Handler handler = mHandler; handler.removeCallbacks(mScrollFade); handler.postDelayed(mScrollFade, 1000); return true; } } else if (me.getAction() == MotionEvent.ACTION_MOVE) { if (mDragging) { final int viewHeight = getHeight(); mThumbY = (int) me.getY() - mThumbH + 10; if (mThumbY < 0) { mThumbY = 0; } else if (mThumbY + mThumbH > viewHeight) { mThumbY = viewHeight - mThumbH; } // If the previous scrollTo is still pending if (mScrollCompleted) { scrollTo((float) mThumbY / (viewHeight - mThumbH)); } return true; } } return super.onTouchEvent(me); } public class ScrollFade implements Runnable { long mStartTime; long mFadeDuration; boolean mStarted; static final int ALPHA_MAX = 200; static final long FADE_DURATION = 200; void startFade() { mFadeDuration = FADE_DURATION; mStartTime = SystemClock.uptimeMillis(); mStarted = true; } int getAlpha() { if (!mStarted) { return ALPHA_MAX; } int alpha; long now = SystemClock.uptimeMillis(); if (now > mStartTime + mFadeDuration) { alpha = 0; } else { alpha = (int) (ALPHA_MAX - ((now - mStartTime) * ALPHA_MAX) / mFadeDuration); } return alpha; } public void run() { if (!mStarted) { startFade(); invalidate(); } if (getAlpha() > 0) { final int y = mThumbY; final int viewWidth = getWidth(); invalidate(viewWidth - mThumbW, y, viewWidth, y + mThumbH); } else { mStarted = false; removeThumb(); } } } } 

You can also adjust the transparency of the scroll finger using ALPHA_MAX.

Then add something like this to your layout xml file:

  <com.myapp.CustomFastScrollView android:layout_width="wrap_content" android:layout_height="fill_parent" myapp:overlayWidth="175dp" myapp:overlayHeight="110dp" myapp:overlayTextSize="36dp" myapp:overlayScrollThumbWidth="60dp" android:id="@+id/fast_scroll_view"> <ListView android:id="@android:id/list" android:layout_width="wrap_content" android:layout_height="fill_parent"/> <TextView android:id="@android:id/empty" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="" /> </com.myapp.CustomFastScrollView> 

Remember to also declare your attributes in this layout XML file:

  ... xmlns:myapp= "http://schemas.android.com/apk/res/com.myapp" ... 

You will also need to capture R.drawable.scrollbar_handle_accelerated_anim2 drawings from this Android source code. The link above contains only mdpi.

+24
Mar 29 '11 at 15:02
source share

The FastScroller widget is responsible for drawing the overlay. You should probably take a look at its source:
https://android.googlesource.com/platform/frameworks/base/+/gingerbread-release/core/java/android/widget/FastScroller.java

Search for comment:

 // If user is dragging the scroll bar, draw the alphabet overlay 
+2
Jul 12 '10 at 3:10
source share



All Articles