Fast Scroll display problem with ListAdapter and SectionIndexer

后端 未结 2 1594
灰色年华
灰色年华 2020-11-30 21:14

I have a list of events which are seperated by month and year (Jun 2010, Jul 2010 etc.). I have enabled fast scrolling because the list is really long. I\'ve also implemente

相关标签:
2条回答
  • 2020-11-30 21:26

    EDIT: Full sample code for this solution available here.

    I had this same problem - I needed to display full text in the overlay rectangle rather than just a single character. I managed to solve it 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 app, which apparently uses its own implementation rather than just setting fastScrollEnabled="true" on the ListView. I altered it a little bit so that you can customize the overlay rectangle width, overlay rectangle height, overlay text size, and scroll thumb width.

    For the record, the final result looks like this: http://nolanwlawson.files.wordpress.com/2011/03/pokedroid_1.png

    All you need 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 instead of the one 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's 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's 
                // position actually returns Q's. So we need to look ahead to make
                // sure that there is really a Q at Q's 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 tweak the translucency of the scroll thumb using ALPHA_MAX.

    Then put something like this in 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>   
    

    Don't forget to declare your attributes in that layout xml file as well:

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

    You'll also need to grab the R.drawable.scrollbar_handle_accelerated_anim2 drawables from that Android source code. The link above only contains the mdpi one.

    0 讨论(0)
  • 2020-11-30 21:38

    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
    
    0 讨论(0)
提交回复
热议问题