2010-07-11 32 views
25

Tôi có một danh sách các sự kiện được phân cách theo tháng và năm (tháng 6 năm 2010, tháng 7 năm 2010, v.v ...). Tôi đã bật tính năng cuộn nhanh vì danh sách thực sự dài. Tôi cũng đã triển khai SectionIndexer để mọi người có thể xem tháng và năm họ hiện đang xem khi cuộn xuống danh sách các sự kiện ở tốc độ.Vấn đề hiển thị Fast Scroll với ListAdapter và SectionIndexer

Tôi không gặp bất kỳ vấn đề nào với việc triển khai, chỉ cách thông tin được hiển thị. Việc cuộn nhanh bằng SectionIndexer dường như chỉ thực sự có thể hỗ trợ nhãn bằng một chữ cái duy nhất. Nếu danh sách được sắp xếp theo thứ tự bảng chữ cái thì điều này sẽ hoàn hảo, tuy nhiên tôi muốn nó hiển thị thêm một chút văn bản.

Nếu bạn nhìn vào ảnh chụp màn hình bên dưới, bạn sẽ thấy sự cố tôi đang gặp phải.

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

Những gì tôi muốn biết là: là nó có thể thay đổi cách các văn bản ở trung tâm của màn hình được hiển thị. Tôi có thể thay đổi nó bằng cách nào đó để làm cho nó trông đúng (với nền bao gồm tất cả các văn bản).

Xin cảm ơn trước. Nếu bạn cần bất kỳ làm rõ, hoặc mã chỉ yêu cầu.

Trả lời

24

EDIT: Full mẫu mã cho giải pháp này có sẵn here.

Tôi đã có cùng một vấn đề này - tôi cần hiển thị toàn văn trong hình chữ nhật lớp phủ thay vì chỉ một ký tự đơn. Tôi quản lý để giải quyết nó bằng cách sử dụng mã sau đây làm ví dụ: http://code.google.com/p/apps-for-android/source/browse/trunk/RingsExtended/src/com/example/android/rings_extended/FastScrollView.java

Tác giả cho biết rằng điều này đã được sao chép từ ứng dụng Danh bạ, sử dụng thực hiện riêng của mình thay vì chỉ đặt fastScrollEnabled="true" trên ListView. Tôi thay đổi nó một chút để bạn có thể tùy chỉnh chiều rộng hình chữ nhật lớp phủ, chiều cao hình chữ nhật lớp phủ, kích thước văn bản lớp phủ và chiều rộng ngón tay cái cuộn.

Đối với hồ sơ, kết quả cuối cùng trông như thế này: http://nolanwlawson.files.wordpress.com/2011/03/pokedroid_1.png

Tất cả bạn cần làm là thêm những giá trị này để res của bạn/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> 

Và sau đó sử dụng này CustomFastScrollView thay vì liên kết trong liên kết:

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(); 
      } 
     } 
    } 
} 

Bạn cũng có thể tinh chỉnh độ mờ của ngón cái cuộn bằng ALPHA_MAX.

Sau đó, đặt một cái gì đó như thế này trong cách bố trí tập tin xml của bạn:

<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> 

Đừng quên khai báo thuộc tính của bạn trong tập tin xml bố trí cũng như:

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

Bạn cũng sẽ cần để lấy các mã số R.drawable.scrollbar_handle_accelerated_anim2 từ mã nguồn Android đó. Liên kết ở trên chỉ chứa mdpi.

+1

Hoàn hảo. Cảm ơn vì điều này! :) – matto1990

+0

Vấn đề của tôi là khi cập nhật bộ điều hợp của tôi, SectionIndexer không upldate. Tôi làm nó như thế nào? –

+0

Lưu ý rằng nếu bạn đang cố gắng sử dụng tính năng này trong thư viện Android, bạn có thể gặp sự cố với thuộc tính tùy chỉnh do lỗi này: http://code.google.com/p/android/issues/detail?id= 9656 # makechang Để làm việc xung quanh nó, bạn phải sao chép tệp bố cục bao gồm thành phần tùy chỉnh vào ứng dụng đang sử dụng thư viện của bạn và sau đó thay đổi thuộc tính xmlns ở trên cùng để sử dụng không gian tên của ứng dụng thay vì thư viện. – johnwayner

2

Tiện ích con FastScroller chịu trách nhiệm vẽ lớp phủ. Có lẽ bạn nên xem xét nguồn gốc của nó:
https://android.googlesource.com/platform/frameworks/base/+/gingerbread-release/core/java/android/widget/FastScroller.java

Tìm kiếm bình luận:

// If user is dragging the scroll bar, draw the alphabet overlay 
+0

Tôi cũng đã thấy bit đó. Làm thế nào tôi sẽ đi về làm cho nó vì vậy tôi có thể thay đổi cách FastScroller hoạt động? AbsListView không cung cấp cách nào thay đổi FastScroller được sử dụng (được lưu trữ trong mFastScroller thành viên riêng). Tôi có thể overwirte tất cả các phương pháp trong AbsListView nơi mFastScroller được thiết lập và sau đó thay đổi nó để MyFastScroller hoặc soemthing. Sau đó tôi chỉ cần tạo một bản sao của FastScroller.java trong gói của tôi và thực hiện các thay đổi mà tôi cần vì đó là một lớp riêng tư. Nó sẽ là tuyệt vời nếu Android cung cấp một cách để làm điều này một cách dễ dàng ;-) – matto1990

+0

Rất tiếc là nhiều phần của Android vẫn yêu cầu sao chép/mở rộng thủ công thay vì có quyền truy cập tốt đẹp qua API. Cách bạn mô tả âm thanh giống như con đường để đi. – TalkLittle

+0

Tôi đã bắt đầu với nó và có khá nhiều mã (và drawables) cần được thay thế. Tôi sẽ giữ nó cho đến khi tôi có được một cái gì đó quản lý được. Nếu tôi nhận được mã để nhìn một nửa phong nha (có thể thử mở rộng lớp ListView để thêm tất cả các chức năng) tôi sẽ phát hành mã ra bởi vì tôi nghĩ rằng đây là một cái gì đó khá một vài người sẽ cần tại một số điểm. Cảm ơn sự giúp đỡ của TalkLittle! – matto1990

Các vấn đề liên quan